diff --git a/include/grpc/support/string_util.h b/include/grpc/support/string_util.h index 2c7460fa157..2679160c1bb 100644 --- a/include/grpc/support/string_util.h +++ b/include/grpc/support/string_util.h @@ -21,6 +21,8 @@ #include +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/src/core/ext/filters/client_channel/client_channel.cc b/src/core/ext/filters/client_channel/client_channel.cc index 04f7a2c8303..024c9d737e8 100644 --- a/src/core/ext/filters/client_channel/client_channel.cc +++ b/src/core/ext/filters/client_channel/client_channel.cc @@ -3174,6 +3174,16 @@ static void try_to_connect_locked(void* arg, grpc_error* error_ignored) { GRPC_CHANNEL_STACK_UNREF(chand->owning_stack, "try_to_connect"); } +void grpc_client_channel_populate_child_refs( + grpc_channel_element* elem, grpc_core::ChildRefsList* child_subchannels, + grpc_core::ChildRefsList* child_channels) { + channel_data* chand = static_cast(elem->channel_data); + if (chand->lb_policy != nullptr) { + chand->lb_policy->FillChildRefsForChannelz(child_subchannels, + child_channels); + } +} + grpc_connectivity_state grpc_client_channel_check_connectivity_state( grpc_channel_element* elem, int try_to_connect) { channel_data* chand = static_cast(elem->channel_data); diff --git a/src/core/ext/filters/client_channel/client_channel.h b/src/core/ext/filters/client_channel/client_channel.h index a21e5623a7b..0b44a17562f 100644 --- a/src/core/ext/filters/client_channel/client_channel.h +++ b/src/core/ext/filters/client_channel/client_channel.h @@ -21,6 +21,7 @@ #include +#include "src/core/ext/filters/client_channel/client_channel_channelz.h" #include "src/core/ext/filters/client_channel/client_channel_factory.h" #include "src/core/ext/filters/client_channel/resolver.h" #include "src/core/lib/channel/channel_stack.h" @@ -39,6 +40,10 @@ extern grpc_core::TraceFlag grpc_client_channel_trace; extern const grpc_channel_filter grpc_client_channel_filter; +void grpc_client_channel_populate_child_refs( + grpc_channel_element* elem, grpc_core::ChildRefsList* child_subchannels, + grpc_core::ChildRefsList* child_channels); + grpc_connectivity_state grpc_client_channel_check_connectivity_state( grpc_channel_element* elem, int try_to_connect); diff --git a/src/core/ext/filters/client_channel/client_channel_channelz.cc b/src/core/ext/filters/client_channel/client_channel_channelz.cc index 08ceb2dd05a..235b8f3207c 100644 --- a/src/core/ext/filters/client_channel/client_channel_channelz.cc +++ b/src/core/ext/filters/client_channel/client_channel_channelz.cc @@ -63,6 +63,37 @@ void ClientChannelNode::PopulateConnectivityState(grpc_json* json) { false); } +void ClientChannelNode::PopulateChildRefs(grpc_json* json) { + ChildRefsList child_subchannels; + ChildRefsList child_channels; + grpc_json* json_iterator = nullptr; + grpc_client_channel_populate_child_refs(client_channel_, &child_subchannels, + &child_channels); + if (child_subchannels.size() > 0) { + grpc_json* array_parent = grpc_json_create_child( + nullptr, json, "subchannelRef", nullptr, GRPC_JSON_ARRAY, false); + for (size_t i = 0; i < child_subchannels.size(); ++i) { + json_iterator = + grpc_json_create_child(json_iterator, array_parent, nullptr, nullptr, + GRPC_JSON_OBJECT, false); + grpc_json_add_number_string_child(json_iterator, nullptr, "subchannelId", + child_subchannels[i]); + } + } + if (child_channels.size() > 0) { + grpc_json* array_parent = grpc_json_create_child( + nullptr, json, "channelRef", nullptr, GRPC_JSON_ARRAY, false); + json_iterator = nullptr; + for (size_t i = 0; i < child_subchannels.size(); ++i) { + json_iterator = + grpc_json_create_child(json_iterator, array_parent, nullptr, nullptr, + GRPC_JSON_OBJECT, false); + grpc_json_add_number_string_child(json_iterator, nullptr, "channelId", + child_subchannels[i]); + } + } +} + grpc_arg ClientChannelNode::CreateChannelArg() { return grpc_channel_arg_pointer_create( const_cast(GRPC_ARG_CHANNELZ_CHANNEL_NODE_CREATION_FUNC), diff --git a/src/core/ext/filters/client_channel/client_channel_channelz.h b/src/core/ext/filters/client_channel/client_channel_channelz.h index cf3ef7b6f28..0547109d36f 100644 --- a/src/core/ext/filters/client_channel/client_channel_channelz.h +++ b/src/core/ext/filters/client_channel/client_channel_channelz.h @@ -22,9 +22,17 @@ #include #include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/channel_stack.h" #include "src/core/lib/channel/channelz.h" +#include "src/core/lib/gprpp/inlined_vector.h" namespace grpc_core { + +// TODO(ncteisen), this only contains the uuids of the children for now, +// since that is all that is strictly needed. In a future enhancement we will +// add human readable names as in the channelz.proto +typedef InlinedVector ChildRefsList; + namespace channelz { // Subtype of ChannelNode that overrides and provides client_channel specific @@ -38,6 +46,9 @@ class ClientChannelNode : public ChannelNode { // channel connectivity. void PopulateConnectivityState(grpc_json* json) override; + // Override this functionality since client_channels have subchannels + void PopulateChildRefs(grpc_json* json) override; + // Helper to create a channel arg to ensure this type of ChannelNode is // created. static grpc_arg CreateChannelArg(); diff --git a/src/core/ext/filters/client_channel/lb_policy.h b/src/core/ext/filters/client_channel/lb_policy.h index dab4466b21a..3150df8847b 100644 --- a/src/core/ext/filters/client_channel/lb_policy.h +++ b/src/core/ext/filters/client_channel/lb_policy.h @@ -21,6 +21,7 @@ #include +#include "src/core/ext/filters/client_channel/client_channel_channelz.h" #include "src/core/ext/filters/client_channel/client_channel_factory.h" #include "src/core/ext/filters/client_channel/subchannel.h" #include "src/core/lib/gprpp/abstract.h" @@ -143,6 +144,14 @@ class LoadBalancingPolicy /// consider whether this method is still needed. virtual void ExitIdleLocked() GRPC_ABSTRACT; + /// populates child_subchannels and child_channels with the uuids of this + /// LB policy's referenced children. This is not invoked from the + /// client_channel's combiner. The implementation is responsible for + /// providing its own synchronization. + virtual void FillChildRefsForChannelz(ChildRefsList* child_subchannels, + ChildRefsList* child_channels) + GRPC_ABSTRACT; + void Orphan() override { // Invoke ShutdownAndUnrefLocked() inside of the combiner. GRPC_CLOSURE_SCHED( @@ -196,6 +205,12 @@ class LoadBalancingPolicy grpc_pollset_set* interested_parties_; /// Callback to force a re-resolution. grpc_closure* request_reresolution_; + + // Dummy classes needed for alignment issues. + // See https://github.com/grpc/grpc/issues/16032 for context. + // TODO(ncteisen): remove this as soon as the issue is resolved. + ChildRefsList dummy_list_foo; + ChildRefsList dummy_list_bar; }; } // namespace grpc_core diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc index 263b51ae895..f757d6057cd 100644 --- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc +++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc @@ -135,6 +135,9 @@ class GrpcLb : public LoadBalancingPolicy { void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override; void PingOneLocked(grpc_closure* on_initiate, grpc_closure* on_ack) override; void ExitIdleLocked() override; + // TODO(ncteisen): implement this in a follow up PR + void FillChildRefsForChannelz(ChildRefsList* child_subchannels, + ChildRefsList* child_channels) override {} private: /// Linked list of pending pick requests. It stores all information needed to diff --git a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc index ff2140e628a..c50deb96797 100644 --- a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc +++ b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc @@ -58,6 +58,8 @@ class PickFirst : public LoadBalancingPolicy { void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override; void PingOneLocked(grpc_closure* on_initiate, grpc_closure* on_ack) override; void ExitIdleLocked() override; + void FillChildRefsForChannelz(ChildRefsList* child_subchannels, + ChildRefsList* child_channels) override; private: ~PickFirst(); @@ -103,10 +105,23 @@ class PickFirst : public LoadBalancingPolicy { } }; + // Helper class to ensure that any function that modifies the child refs + // data structures will update the channelz snapshot data structures before + // returning. + class AutoChildRefsUpdater { + public: + explicit AutoChildRefsUpdater(PickFirst* pf) : pf_(pf) {} + ~AutoChildRefsUpdater() { pf_->UpdateChildRefsLocked(); } + + private: + PickFirst* pf_; + }; + void ShutdownLocked() override; void StartPickingLocked(); void DestroyUnselectedSubchannelsLocked(); + void UpdateChildRefsLocked(); // All our subchannels. OrphanablePtr subchannel_list_; @@ -122,10 +137,17 @@ class PickFirst : public LoadBalancingPolicy { PickState* pending_picks_ = nullptr; // Our connectivity state tracker. grpc_connectivity_state_tracker state_tracker_; + + /// Lock and data used to capture snapshots of this channels child + /// channels and subchannels. This data is consumed by channelz. + gpr_mu child_refs_mu_; + ChildRefsList child_subchannels_; + ChildRefsList child_channels_; }; PickFirst::PickFirst(const Args& args) : LoadBalancingPolicy(args) { GPR_ASSERT(args.client_channel_factory != nullptr); + gpr_mu_init(&child_refs_mu_); grpc_connectivity_state_init(&state_tracker_, GRPC_CHANNEL_IDLE, "pick_first"); if (grpc_lb_pick_first_trace.enabled()) { @@ -139,6 +161,7 @@ PickFirst::~PickFirst() { if (grpc_lb_pick_first_trace.enabled()) { gpr_log(GPR_INFO, "Destroying Pick First %p", this); } + gpr_mu_destroy(&child_refs_mu_); GPR_ASSERT(subchannel_list_ == nullptr); GPR_ASSERT(latest_pending_subchannel_list_ == nullptr); GPR_ASSERT(pending_picks_ == nullptr); @@ -158,6 +181,7 @@ void PickFirst::HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) { } void PickFirst::ShutdownLocked() { + AutoChildRefsUpdater(this); grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Channel shutdown"); if (grpc_lb_pick_first_trace.enabled()) { gpr_log(GPR_INFO, "Pick First %p Shutting down", this); @@ -280,7 +304,61 @@ void PickFirst::PingOneLocked(grpc_closure* on_initiate, grpc_closure* on_ack) { } } +void PickFirst::FillChildRefsForChannelz( + ChildRefsList* child_subchannels_to_fill, ChildRefsList* ignored) { + mu_guard guard(&child_refs_mu_); + for (size_t i = 0; i < child_subchannels_.size(); ++i) { + // TODO(ncteisen): implement a de dup loop that is not O(n^2). Might + // have to implement lightweight set. For now, we don't care about + // performance when channelz requests are made. + bool found = false; + for (size_t j = 0; j < child_subchannels_to_fill->size(); ++j) { + if ((*child_subchannels_to_fill)[j] == child_subchannels_[i]) { + found = true; + break; + } + } + if (!found) { + child_subchannels_to_fill->push_back(child_subchannels_[i]); + } + } +} + +void PickFirst::UpdateChildRefsLocked() { + ChildRefsList cs; + if (subchannel_list_ != nullptr) { + for (size_t i = 0; i < subchannel_list_->num_subchannels(); ++i) { + if (subchannel_list_->subchannel(i)->subchannel() != nullptr) { + grpc_core::channelz::SubchannelNode* subchannel_node = + grpc_subchannel_get_channelz_node( + subchannel_list_->subchannel(i)->subchannel()); + if (subchannel_node != nullptr) { + cs.push_back(subchannel_node->subchannel_uuid()); + } + } + } + } + if (latest_pending_subchannel_list_ != nullptr) { + for (size_t i = 0; i < latest_pending_subchannel_list_->num_subchannels(); + ++i) { + if (latest_pending_subchannel_list_->subchannel(i)->subchannel() != + nullptr) { + grpc_core::channelz::SubchannelNode* subchannel_node = + grpc_subchannel_get_channelz_node( + latest_pending_subchannel_list_->subchannel(i)->subchannel()); + if (subchannel_node != nullptr) { + cs.push_back(subchannel_node->subchannel_uuid()); + } + } + } + } + // atomically update the data that channelz will actually be looking at. + mu_guard guard(&child_refs_mu_); + child_subchannels_ = std::move(cs); +} + void PickFirst::UpdateLocked(const grpc_channel_args& args) { + AutoChildRefsUpdater guard(this); const grpc_arg* arg = grpc_channel_args_find(&args, GRPC_ARG_LB_ADDRESSES); if (arg == nullptr || arg->type != GRPC_ARG_POINTER) { if (subchannel_list_ == nullptr) { @@ -388,6 +466,7 @@ void PickFirst::UpdateLocked(const grpc_channel_args& args) { void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked( grpc_connectivity_state connectivity_state, grpc_error* error) { PickFirst* p = static_cast(subchannel_list()->policy()); + AutoChildRefsUpdater guard(p); // The notification must be for a subchannel in either the current or // latest pending subchannel lists. GPR_ASSERT(subchannel_list() == p->subchannel_list_.get() || diff --git a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc index 42e8e88ec95..09634a2ad48 100644 --- a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc +++ b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc @@ -69,6 +69,9 @@ class RoundRobin : public LoadBalancingPolicy { void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override; void PingOneLocked(grpc_closure* on_initiate, grpc_closure* on_ack) override; void ExitIdleLocked() override; + // TODO(ncteisen): implement this in a follow up PR + void FillChildRefsForChannelz(ChildRefsList* child_subchannels, + ChildRefsList* child_channels) override {} private: ~RoundRobin(); diff --git a/src/core/ext/filters/client_channel/subchannel.cc b/src/core/ext/filters/client_channel/subchannel.cc index 8ab3fe40f50..9d608c3c55d 100644 --- a/src/core/ext/filters/client_channel/subchannel.cc +++ b/src/core/ext/filters/client_channel/subchannel.cc @@ -134,6 +134,9 @@ struct grpc_subchannel { bool backoff_begun; /** our alarm */ grpc_timer alarm; + + grpc_core::RefCountedPtr + channelz_subchannel; }; struct grpc_subchannel_call { @@ -178,6 +181,7 @@ static void connection_destroy(void* arg, grpc_error* error) { static void subchannel_destroy(void* arg, grpc_error* error) { grpc_subchannel* c = static_cast(arg); + c->channelz_subchannel.reset(); gpr_free((void*)c->filters); grpc_channel_args_destroy(c->args); grpc_connectivity_state_destroy(&c->state_tracker); @@ -374,9 +378,22 @@ grpc_subchannel* grpc_subchannel_create(grpc_connector* connector, c->backoff.Init(backoff_options); gpr_mu_init(&c->mu); + const grpc_arg* arg = + grpc_channel_args_find(c->args, GRPC_ARG_ENABLE_CHANNELZ); + bool channelz_enabled = grpc_channel_arg_get_bool(arg, false); + if (channelz_enabled) { + c->channelz_subchannel = + grpc_core::MakeRefCounted(); + } + return grpc_subchannel_index_register(key, c); } +grpc_core::channelz::SubchannelNode* grpc_subchannel_get_channelz_node( + grpc_subchannel* s) { + return s->channelz_subchannel.get(); +} + static void continue_connect_locked(grpc_subchannel* c) { grpc_connect_in_args args; args.interested_parties = c->pollset_set; diff --git a/src/core/ext/filters/client_channel/subchannel.h b/src/core/ext/filters/client_channel/subchannel.h index e23aec12df4..9e53f7d5423 100644 --- a/src/core/ext/filters/client_channel/subchannel.h +++ b/src/core/ext/filters/client_channel/subchannel.h @@ -21,6 +21,7 @@ #include +#include "src/core/ext/filters/client_channel/client_channel_channelz.h" #include "src/core/ext/filters/client_channel/connector.h" #include "src/core/lib/channel/channel_stack.h" #include "src/core/lib/gpr/arena.h" @@ -115,6 +116,9 @@ grpc_subchannel_call* grpc_subchannel_call_ref( void grpc_subchannel_call_unref( grpc_subchannel_call* call GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +grpc_core::channelz::SubchannelNode* grpc_subchannel_get_channelz_node( + grpc_subchannel* subchannel); + /** Returns a pointer to the parent data associated with \a subchannel_call. The data will be of the size specified in \a parent_data_size field of the args passed to \a grpc_connected_subchannel_create_call(). */ diff --git a/src/core/lib/channel/channel_trace.cc b/src/core/lib/channel/channel_trace.cc index 0f655d87160..c17885a7134 100644 --- a/src/core/lib/channel/channel_trace.cc +++ b/src/core/lib/channel/channel_trace.cc @@ -131,38 +131,6 @@ void ChannelTrace::AddTraceEventReferencingSubchannel( namespace { -// returns an allocated string that represents tm according to RFC-3339, and, -// more specifically, follows: -// https://developers.google.com/protocol-buffers/docs/proto3#json -// -// "Uses RFC 3339, where generated output will always be Z-normalized and uses -// 0, 3, 6 or 9 fractional digits." -char* fmt_time(gpr_timespec tm) { - char time_buffer[35]; - char ns_buffer[11]; // '.' + 9 digits of precision - struct tm* tm_info = localtime((const time_t*)&tm.tv_sec); - strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%dT%H:%M:%S", tm_info); - snprintf(ns_buffer, 11, ".%09d", tm.tv_nsec); - // This loop trims off trailing zeros by inserting a null character that the - // right point. We iterate in chunks of three because we want 0, 3, 6, or 9 - // fractional digits. - for (int i = 7; i >= 1; i -= 3) { - if (ns_buffer[i] == '0' && ns_buffer[i + 1] == '0' && - ns_buffer[i + 2] == '0') { - ns_buffer[i] = '\0'; - // Edge case in which all fractional digits were 0. - if (i == 1) { - ns_buffer[0] = '\0'; - } - } else { - break; - } - } - char* full_time_str; - gpr_asprintf(&full_time_str, "%s%sZ", time_buffer, ns_buffer); - return full_time_str; -} - const char* severity_string(ChannelTrace::Severity severity) { switch (severity) { case ChannelTrace::Severity::Info: @@ -186,9 +154,9 @@ void ChannelTrace::TraceEvent::RenderTraceEvent(grpc_json* json) const { json_iterator = grpc_json_create_child(json_iterator, json, "severity", severity_string(severity_), GRPC_JSON_STRING, false); - json_iterator = - grpc_json_create_child(json_iterator, json, "timestamp", - fmt_time(timestamp_), GRPC_JSON_STRING, true); + json_iterator = grpc_json_create_child(json_iterator, json, "timestamp", + gpr_format_timespec(timestamp_), + GRPC_JSON_STRING, true); if (referenced_channel_ != nullptr) { char* uuid_str; gpr_asprintf(&uuid_str, "%" PRIdPTR, referenced_channel_->channel_uuid()); @@ -216,9 +184,9 @@ grpc_json* ChannelTrace::RenderJSON() const { json_iterator = grpc_json_create_child(json_iterator, json, "numEventsLogged", num_events_logged_str, GRPC_JSON_STRING, true); - json_iterator = - grpc_json_create_child(json_iterator, json, "creationTimestamp", - fmt_time(time_created_), GRPC_JSON_STRING, true); + json_iterator = grpc_json_create_child( + json_iterator, json, "creationTimestamp", + gpr_format_timespec(time_created_), GRPC_JSON_STRING, true); grpc_json* events = grpc_json_create_child(json_iterator, json, "events", nullptr, GRPC_JSON_ARRAY, false); json_iterator = nullptr; diff --git a/src/core/lib/channel/channelz.cc b/src/core/lib/channel/channelz.cc index 2074cb0cc53..79a9220503c 100644 --- a/src/core/lib/channel/channelz.cc +++ b/src/core/lib/channel/channelz.cc @@ -41,53 +41,6 @@ namespace grpc_core { namespace channelz { -namespace { - -// TODO(ncteisen): move this function to a common helper location. -// -// returns an allocated string that represents tm according to RFC-3339, and, -// more specifically, follows: -// https://developers.google.com/protocol-buffers/docs/proto3#json -// -// "Uses RFC 3339, where generated output will always be Z-normalized and uses -// 0, 3, 6 or 9 fractional digits." -char* fmt_time(gpr_timespec tm) { - char time_buffer[35]; - char ns_buffer[11]; // '.' + 9 digits of precision - struct tm* tm_info = localtime((const time_t*)&tm.tv_sec); - strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%dT%H:%M:%S", tm_info); - snprintf(ns_buffer, 11, ".%09d", tm.tv_nsec); - // This loop trims off trailing zeros by inserting a null character that the - // right point. We iterate in chunks of three because we want 0, 3, 6, or 9 - // fractional digits. - for (int i = 7; i >= 1; i -= 3) { - if (ns_buffer[i] == '0' && ns_buffer[i + 1] == '0' && - ns_buffer[i + 2] == '0') { - ns_buffer[i] = '\0'; - // Edge case in which all fractional digits were 0. - if (i == 1) { - ns_buffer[0] = '\0'; - } - } else { - break; - } - } - char* full_time_str; - gpr_asprintf(&full_time_str, "%s%sZ", time_buffer, ns_buffer); - return full_time_str; -} - -// TODO(ncteisen); move this to json library -grpc_json* add_num_str(grpc_json* parent, grpc_json* it, const char* name, - int64_t num) { - char* num_str; - gpr_asprintf(&num_str, "%" PRId64, num); - return grpc_json_create_child(it, parent, name, num_str, GRPC_JSON_STRING, - true); -} - -} // namespace - ChannelNode::ChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes) : channel_(channel), target_(nullptr), channel_uuid_(-1) { trace_.Init(channel_tracer_max_nodes); @@ -110,6 +63,8 @@ void ChannelNode::RecordCallStarted() { void ChannelNode::PopulateConnectivityState(grpc_json* json) {} +void ChannelNode::PopulateChildRefs(grpc_json* json) {} + char* ChannelNode::RenderJSON() { // We need to track these three json objects to build our object grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT); @@ -120,7 +75,8 @@ char* ChannelNode::RenderJSON() { GRPC_JSON_OBJECT, false); json = json_iterator; json_iterator = nullptr; - json_iterator = add_num_str(json, json_iterator, "channelId", channel_uuid_); + json_iterator = grpc_json_add_number_string_child(json, json_iterator, + "channelId", channel_uuid_); // reset json iterators to top level object json = top_level_json; json_iterator = nullptr; @@ -148,17 +104,21 @@ char* ChannelNode::RenderJSON() { json_iterator = nullptr; // We use -1 as sentinel values since proto default value for integers is // zero, and the confuses the parser into thinking the value weren't present - json_iterator = - add_num_str(json, json_iterator, "callsStarted", calls_started_); - json_iterator = - add_num_str(json, json_iterator, "callsSucceeded", calls_succeeded_); - json_iterator = - add_num_str(json, json_iterator, "callsFailed", calls_failed_); + json_iterator = grpc_json_add_number_string_child( + json, json_iterator, "callsStarted", calls_started_); + json_iterator = grpc_json_add_number_string_child( + json, json_iterator, "callsSucceeded", calls_succeeded_); + json_iterator = grpc_json_add_number_string_child( + json, json_iterator, "callsFailed", calls_failed_); gpr_timespec ts = grpc_millis_to_timespec(last_call_started_millis_, GPR_CLOCK_REALTIME); json_iterator = grpc_json_create_child(json_iterator, json, "lastCallStartedTimestamp", - fmt_time(ts), GRPC_JSON_STRING, true); + gpr_format_timespec(ts), GRPC_JSON_STRING, true); + json = top_level_json; + json_iterator = nullptr; + PopulateChildRefs(json); + // render and return the over json object char* json_str = grpc_json_dump_to_string(top_level_json, 0); grpc_json_destroy(top_level_json); @@ -171,5 +131,13 @@ RefCountedPtr ChannelNode::MakeChannelNode( channel, channel_tracer_max_nodes); } +SubchannelNode::SubchannelNode() { + subchannel_uuid_ = ChannelzRegistry::Register(this); +} + +SubchannelNode::~SubchannelNode() { + ChannelzRegistry::Unregister(subchannel_uuid_); +} + } // namespace channelz } // namespace grpc_core diff --git a/src/core/lib/channel/channelz.h b/src/core/lib/channel/channelz.h index 9bd01ece502..7184af93ecb 100644 --- a/src/core/lib/channel/channelz.h +++ b/src/core/lib/channel/channelz.h @@ -62,6 +62,8 @@ class ChannelNode : public RefCounted { // instead of lib/ virtual void PopulateConnectivityState(grpc_json* json); + virtual void PopulateChildRefs(grpc_json* json); + ChannelTrace* trace() { return trace_.get(); } void MarkChannelDestroyed() { @@ -93,6 +95,24 @@ class ChannelNode : public RefCounted { ManualConstructor trace_; }; +// Placeholds channelz class for subchannels. All this can do now is track its +// uuid (this information is needed by the parent channelz class). +// TODO(ncteisen): build this out to support the GetSubchannel channelz request. +class SubchannelNode : public RefCounted { + public: + SubchannelNode(); + virtual ~SubchannelNode(); + + intptr_t subchannel_uuid() { return subchannel_uuid_; } + + protected: + GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE + GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_NEW + + private: + intptr_t subchannel_uuid_; +}; + // Creation functions typedef RefCountedPtr (*ChannelNodeCreationFunc)(grpc_channel*, diff --git a/src/core/lib/gpr/string.cc b/src/core/lib/gpr/string.cc index ef2a6900b45..0a76fc1f54a 100644 --- a/src/core/lib/gpr/string.cc +++ b/src/core/lib/gpr/string.cc @@ -23,8 +23,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -54,6 +56,32 @@ typedef struct { char* data; } dump_out; +char* gpr_format_timespec(gpr_timespec tm) { + char time_buffer[35]; + char ns_buffer[11]; // '.' + 9 digits of precision + struct tm* tm_info = localtime((const time_t*)&tm.tv_sec); + strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%dT%H:%M:%S", tm_info); + snprintf(ns_buffer, 11, ".%09d", tm.tv_nsec); + // This loop trims off trailing zeros by inserting a null character that the + // right point. We iterate in chunks of three because we want 0, 3, 6, or 9 + // fractional digits. + for (int i = 7; i >= 1; i -= 3) { + if (ns_buffer[i] == '0' && ns_buffer[i + 1] == '0' && + ns_buffer[i + 2] == '0') { + ns_buffer[i] = '\0'; + // Edge case in which all fractional digits were 0. + if (i == 1) { + ns_buffer[0] = '\0'; + } + } else { + break; + } + } + char* full_time_str; + gpr_asprintf(&full_time_str, "%s%sZ", time_buffer, ns_buffer); + return full_time_str; +} + static dump_out dump_out_create(void) { dump_out r = {0, 0, nullptr}; return r; diff --git a/src/core/lib/gpr/string.h b/src/core/lib/gpr/string.h index 2e8a4898d9d..ce51fe46321 100644 --- a/src/core/lib/gpr/string.h +++ b/src/core/lib/gpr/string.h @@ -21,6 +21,8 @@ #include +#include + #include #include @@ -81,6 +83,14 @@ char* gpr_strjoin_sep(const char** strs, size_t nstrs, const char* sep, void gpr_string_split(const char* input, const char* sep, char*** strs, size_t* nstrs); +/* Returns an allocated string that represents tm according to RFC-3339, and, + more specifically, follows: + https://developers.google.com/protocol-buffers/docs/proto3#json + + Uses RFC 3339, where generated output will always be Z-normalized and uses + 0, 3, 6 or 9 fractional digits. */ +char* gpr_format_timespec(gpr_timespec); + /* A vector of strings... for building up a final string one piece at a time */ typedef struct { char** strs; diff --git a/src/core/lib/gprpp/abstract.h b/src/core/lib/gprpp/abstract.h index cc96edc49b7..5b7018e07e0 100644 --- a/src/core/lib/gprpp/abstract.h +++ b/src/core/lib/gprpp/abstract.h @@ -28,7 +28,10 @@ // gRPC currently can't depend on libstdc++, so we can't use "= 0" for // pure virtual methods. Instead, we use this macro. -#define GRPC_ABSTRACT \ - { GPR_ASSERT(false); } +#define GRPC_ABSTRACT \ + { \ + gpr_log(GPR_ERROR, "Function marked GRPC_ABSTRACT was not implemented"); \ + GPR_ASSERT(false); \ + } #endif /* GRPC_CORE_LIB_GPRPP_ABSTRACT_H */ diff --git a/src/core/lib/gprpp/inlined_vector.h b/src/core/lib/gprpp/inlined_vector.h index 0d2586e507d..76e2f0a7850 100644 --- a/src/core/lib/gprpp/inlined_vector.h +++ b/src/core/lib/gprpp/inlined_vector.h @@ -22,6 +22,7 @@ #include #include +#include #include "src/core/lib/gprpp/memory.h" @@ -50,9 +51,33 @@ class InlinedVector { InlinedVector() { init_data(); } ~InlinedVector() { destroy_elements(); } - // For now, we do not support copying. - InlinedVector(const InlinedVector&) = delete; - InlinedVector& operator=(const InlinedVector&) = delete; + // copy constructor + InlinedVector(const InlinedVector& v) { + init_data(); + copy_from(v); + } + + InlinedVector& operator=(const InlinedVector& v) { + if (this != &v) { + clear(); + copy_from(v); + } + return *this; + } + + // move constructor + InlinedVector(InlinedVector&& v) { + init_data(); + move_from(v); + } + + InlinedVector& operator=(InlinedVector&& v) { + if (this != &v) { + clear(); + move_from(v); + } + return *this; + } T* data() { return dynamic_ != nullptr ? dynamic_ : reinterpret_cast(inline_); @@ -98,6 +123,33 @@ class InlinedVector { void push_back(T&& value) { emplace_back(std::move(value)); } + void copy_from(const InlinedVector& v) { + // if v is allocated, copy over the buffer. + if (v.dynamic_ != nullptr) { + reserve(v.capacity_); + memcpy(dynamic_, v.dynamic_, v.size_ * sizeof(T)); + } else { + memcpy(inline_, v.inline_, v.size_ * sizeof(T)); + } + // copy over metadata + size_ = v.size_; + capacity_ = v.capacity_; + } + + void move_from(InlinedVector& v) { + // if v is allocated, then we steal its buffer, else we copy it. + if (v.dynamic_ != nullptr) { + dynamic_ = v.dynamic_; + } else { + memcpy(inline_, v.inline_, v.size_ * sizeof(T)); + } + // copy over metadata + size_ = v.size_; + capacity_ = v.capacity_; + // null out the original + v.init_data(); + } + size_t size() const { return size_; } bool empty() const { return size_ == 0; } diff --git a/src/core/lib/json/json.cc b/src/core/lib/json/json.cc index 816241bbf0e..6d359573f86 100644 --- a/src/core/lib/json/json.cc +++ b/src/core/lib/json/json.cc @@ -18,10 +18,12 @@ #include +#include #include #include #include +#include #include "src/core/lib/json/json.h" @@ -84,3 +86,11 @@ grpc_json* grpc_json_create_child(grpc_json* sibling, grpc_json* parent, child->key = key; return child; } + +grpc_json* grpc_json_add_number_string_child(grpc_json* parent, grpc_json* it, + const char* name, int64_t num) { + char* num_str; + gpr_asprintf(&num_str, "%" PRId64, num); + return grpc_json_create_child(it, parent, name, num_str, GRPC_JSON_STRING, + true); +} diff --git a/src/core/lib/json/json.h b/src/core/lib/json/json.h index f93b43048b9..8173845c723 100644 --- a/src/core/lib/json/json.h +++ b/src/core/lib/json/json.h @@ -91,4 +91,9 @@ grpc_json* grpc_json_create_child(grpc_json* sibling, grpc_json* parent, const char* key, const char* value, grpc_json_type type, bool owns_value); +/* Creates a child json string object from the integer num, then links the + json object into the parent's json tree */ +grpc_json* grpc_json_add_number_string_child(grpc_json* parent, grpc_json* it, + const char* name, int64_t num); + #endif /* GRPC_CORE_LIB_JSON_JSON_H */ diff --git a/test/core/gprpp/inlined_vector_test.cc b/test/core/gprpp/inlined_vector_test.cc index 41f4338f8a4..e9d1eb2c763 100644 --- a/test/core/gprpp/inlined_vector_test.cc +++ b/test/core/gprpp/inlined_vector_test.cc @@ -17,20 +17,32 @@ */ #include "src/core/lib/gprpp/inlined_vector.h" +#include #include #include "src/core/lib/gprpp/memory.h" #include "test/core/util/test_config.h" namespace grpc_core { namespace testing { +namespace { + +template +static void FillVector(Vector* v, int len, int start = 0) { + for (int i = 0; i < len; i++) { + v->push_back(i + start); + EXPECT_EQ(i + 1UL, v->size()); + } + EXPECT_EQ(static_cast(len), v->size()); + EXPECT_LE(static_cast(len), v->capacity()); +} + +} // namespace TEST(InlinedVectorTest, CreateAndIterate) { const int kNumElements = 9; InlinedVector v; EXPECT_TRUE(v.empty()); - for (int i = 0; i < kNumElements; ++i) { - v.push_back(i); - } + FillVector(&v, kNumElements); EXPECT_EQ(static_cast(kNumElements), v.size()); EXPECT_FALSE(v.empty()); for (int i = 0; i < kNumElements; ++i) { @@ -42,9 +54,7 @@ TEST(InlinedVectorTest, CreateAndIterate) { TEST(InlinedVectorTest, ValuesAreInlined) { const int kNumElements = 5; InlinedVector v; - for (int i = 0; i < kNumElements; ++i) { - v.push_back(i); - } + FillVector(&v, kNumElements); EXPECT_EQ(static_cast(kNumElements), v.size()); for (int i = 0; i < kNumElements; ++i) { EXPECT_EQ(i, v[i]); @@ -71,19 +81,13 @@ TEST(InlinedVectorTest, ClearAndRepopulate) { const int kNumElements = 10; InlinedVector v; EXPECT_EQ(0UL, v.size()); - for (int i = 0; i < kNumElements; ++i) { - v.push_back(i); - EXPECT_EQ(i + 1UL, v.size()); - } + FillVector(&v, kNumElements); for (int i = 0; i < kNumElements; ++i) { EXPECT_EQ(i, v[i]); } v.clear(); EXPECT_EQ(0UL, v.size()); - for (int i = 0; i < kNumElements; ++i) { - v.push_back(kNumElements + i); - EXPECT_EQ(i + 1UL, v.size()); - } + FillVector(&v, kNumElements, kNumElements); for (int i = 0; i < kNumElements; ++i) { EXPECT_EQ(kNumElements + i, v[i]); } @@ -93,10 +97,7 @@ TEST(InlinedVectorTest, ConstIndexOperator) { constexpr int kNumElements = 10; InlinedVector v; EXPECT_EQ(0UL, v.size()); - for (int i = 0; i < kNumElements; ++i) { - v.push_back(i); - EXPECT_EQ(i + 1UL, v.size()); - } + FillVector(&v, kNumElements); // The following lambda function is exceptionally allowed to use an anonymous // capture due to the erroneous behavior of the MSVC compiler, that refuses to // capture the kNumElements constexpr, something allowed by the standard. @@ -108,6 +109,161 @@ TEST(InlinedVectorTest, ConstIndexOperator) { const_func(v); } +// the following constants and typedefs are used for copy/move +// construction/assignment +const size_t kInlinedLength = 8; +typedef InlinedVector IntVec8; +const size_t kInlinedFillSize = kInlinedLength - 1; +const size_t kAllocatedFillSize = kInlinedLength + 1; + +TEST(InlinedVectorTest, CopyConstructerInlined) { + IntVec8 original; + FillVector(&original, kInlinedFillSize); + IntVec8 copy_constructed(original); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], copy_constructed[i]); + } +} + +TEST(InlinedVectorTest, CopyConstructerAllocated) { + IntVec8 original; + FillVector(&original, kAllocatedFillSize); + IntVec8 copy_constructed(original); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], copy_constructed[i]); + } +} + +TEST(InlinedVectorTest, CopyAssignementInlinedInlined) { + IntVec8 original; + FillVector(&original, kInlinedFillSize); + IntVec8 copy_assigned; + FillVector(©_assigned, kInlinedFillSize, 99); + copy_assigned = original; + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], copy_assigned[i]); + } +} + +TEST(InlinedVectorTest, CopyAssignementInlinedAllocated) { + IntVec8 original; + FillVector(&original, kInlinedFillSize); + IntVec8 copy_assigned; + FillVector(©_assigned, kAllocatedFillSize, 99); + copy_assigned = original; + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], copy_assigned[i]); + } +} + +TEST(InlinedVectorTest, CopyAssignementAllocatedInlined) { + IntVec8 original; + FillVector(&original, kAllocatedFillSize); + IntVec8 copy_assigned; + FillVector(©_assigned, kInlinedFillSize, 99); + copy_assigned = original; + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], copy_assigned[i]); + } +} + +TEST(InlinedVectorTest, CopyAssignementAllocatedAllocated) { + IntVec8 original; + FillVector(&original, kAllocatedFillSize); + IntVec8 copy_assigned; + FillVector(©_assigned, kAllocatedFillSize, 99); + copy_assigned = original; + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], copy_assigned[i]); + } +} + +TEST(InlinedVectorTest, MoveConstructorInlined) { + IntVec8 original; + FillVector(&original, kInlinedFillSize); + IntVec8 tmp(original); + auto* old_data = tmp.data(); + IntVec8 move_constructed(std::move(tmp)); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], move_constructed[i]); + } + // original data was inlined so it should have been copied, not moved. + EXPECT_NE(move_constructed.data(), old_data); +} + +TEST(InlinedVectorTest, MoveConstructorAllocated) { + IntVec8 original; + FillVector(&original, kAllocatedFillSize); + IntVec8 tmp(original); + auto* old_data = tmp.data(); + IntVec8 move_constructed(std::move(tmp)); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], move_constructed[i]); + } + // original data was allocated, so it should been moved, not copied + EXPECT_EQ(move_constructed.data(), old_data); +} + +TEST(InlinedVectorTest, MoveAssignmentInlinedInlined) { + IntVec8 original; + FillVector(&original, kInlinedFillSize); + IntVec8 move_assigned; + FillVector(&move_assigned, kInlinedFillSize, 99); // Add dummy elements + IntVec8 tmp(original); + auto* old_data = tmp.data(); + move_assigned = std::move(tmp); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], move_assigned[i]); + } + // original data was inlined so it should have been copied, not moved. + EXPECT_NE(move_assigned.data(), old_data); +} + +TEST(InlinedVectorTest, MoveAssignmentInlinedAllocated) { + IntVec8 original; + FillVector(&original, kInlinedFillSize); + IntVec8 move_assigned; + FillVector(&move_assigned, kAllocatedFillSize, 99); // Add dummy elements + IntVec8 tmp(original); + auto* old_data = tmp.data(); + move_assigned = std::move(tmp); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], move_assigned[i]); + } + // original data was inlined so it should have been copied, not moved. + EXPECT_NE(move_assigned.data(), old_data); +} + +TEST(InlinedVectorTest, MoveAssignmentAllocatedInlined) { + IntVec8 original; + FillVector(&original, kAllocatedFillSize); + IntVec8 move_assigned; + FillVector(&move_assigned, kInlinedFillSize, 99); // Add dummy elements + IntVec8 tmp(original); + auto* old_data = tmp.data(); + move_assigned = std::move(tmp); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], move_assigned[i]); + } + // original data was allocated so it should have been moved, not copied. + EXPECT_EQ(move_assigned.data(), old_data); +} + +TEST(InlinedVectorTest, MoveAssignmentAllocatedAllocated) { + IntVec8 original; + FillVector(&original, kAllocatedFillSize); + IntVec8 move_assigned; + FillVector(&move_assigned, kAllocatedFillSize, 99); // Add dummy elements + IntVec8 tmp(original); + auto* old_data = tmp.data(); + move_assigned = std::move(tmp); + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], move_assigned[i]); + } + // original data was allocated so it should have been moved, not copied. + EXPECT_EQ(move_assigned.data(), old_data); +} + } // namespace testing } // namespace grpc_core