Merge branch 'master' of https://github.com/grpc/grpc into channelz-get-top-channels
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 235b8f3..c1c204a 100644
--- a/src/core/ext/filters/client_channel/client_channel_channelz.cc
+++ b/src/core/ext/filters/client_channel/client_channel_channelz.cc
@@ -41,8 +41,9 @@
     client_channel_channelz_cmp};
 
 ClientChannelNode::ClientChannelNode(grpc_channel* channel,
-                                     size_t channel_tracer_max_nodes)
-    : ChannelNode(channel, channel_tracer_max_nodes) {
+                                     size_t channel_tracer_max_nodes,
+                                     bool is_top_level_channel)
+    : ChannelNode(channel, channel_tracer_max_nodes, is_top_level_channel) {
   client_channel_ =
       grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel));
   GPR_ASSERT(client_channel_->filter == &grpc_client_channel_filter);
@@ -102,9 +103,10 @@
 }
 
 RefCountedPtr<ChannelNode> ClientChannelNode::MakeClientChannelNode(
-    grpc_channel* channel, size_t channel_tracer_max_nodes) {
+    grpc_channel* channel, size_t channel_tracer_max_nodes,
+    bool is_top_level_channel) {
   return MakePolymorphicRefCounted<ChannelNode, ClientChannelNode>(
-      channel, channel_tracer_max_nodes);
+      channel, channel_tracer_max_nodes, is_top_level_channel);
 }
 
 }  // namespace channelz
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 0547109..6f27b5c 100644
--- a/src/core/ext/filters/client_channel/client_channel_channelz.h
+++ b/src/core/ext/filters/client_channel/client_channel_channelz.h
@@ -40,7 +40,8 @@
 class ClientChannelNode : public ChannelNode {
  public:
   static RefCountedPtr<ChannelNode> MakeClientChannelNode(
-      grpc_channel* channel, size_t channel_tracer_max_nodes);
+      grpc_channel* channel, size_t channel_tracer_max_nodes,
+      bool is_top_level_channel);
 
   // Override this functionality since client_channels have a notion of
   // channel connectivity.
@@ -56,7 +57,8 @@
  protected:
   GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
   GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_NEW
-  ClientChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes);
+  ClientChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes,
+                    bool is_top_level_channel);
   virtual ~ClientChannelNode() {}
 
  private:
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 f757d60..8553441 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
@@ -1007,6 +1007,10 @@
       // A channel arg indicating the target is a grpclb load balancer.
       grpc_channel_arg_integer_create(
           const_cast<char*>(GRPC_ARG_ADDRESS_IS_GRPCLB_LOAD_BALANCER), 1),
+      // A channel arg indicating this is an internal channels, aka it is
+      // owned by components in Core, not by the user application.
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_CHANNELZ_CHANNEL_IS_INTERNAL_CHANNEL), 1),
   };
   // Construct channel args.
   grpc_channel_args* new_args = grpc_channel_args_copy_and_add_and_remove(
diff --git a/src/core/lib/channel/channel_trace.cc b/src/core/lib/channel/channel_trace.cc
index c17885a..b344331 100644
--- a/src/core/lib/channel/channel_trace.cc
+++ b/src/core/lib/channel/channel_trace.cc
@@ -174,7 +174,7 @@
   }
 }
 
-grpc_json* ChannelTrace::RenderJSON() const {
+grpc_json* ChannelTrace::RenderJson() const {
   if (!max_list_size_)
     return nullptr;  // tracing is disabled if max_events == 0
   grpc_json* json = grpc_json_create(GRPC_JSON_OBJECT);
diff --git a/src/core/lib/channel/channel_trace.h b/src/core/lib/channel/channel_trace.h
index 0dd162a..596af74 100644
--- a/src/core/lib/channel/channel_trace.h
+++ b/src/core/lib/channel/channel_trace.h
@@ -71,7 +71,7 @@
 
   // Creates and returns the raw grpc_json object, so a parent channelz
   // object may incorporate the json before rendering.
-  grpc_json* RenderJSON() const;
+  grpc_json* RenderJson() const;
 
  private:
   // Types of objects that can be references by trace events.
diff --git a/src/core/lib/channel/channelz.cc b/src/core/lib/channel/channelz.cc
index 79a9220..df85b89 100644
--- a/src/core/lib/channel/channelz.cc
+++ b/src/core/lib/channel/channelz.cc
@@ -41,18 +41,22 @@
 namespace grpc_core {
 namespace channelz {
 
-ChannelNode::ChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes)
-    : channel_(channel), target_(nullptr), channel_uuid_(-1) {
+ChannelNode::ChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes,
+                         bool is_top_level_channel)
+    : channel_(channel),
+      target_(nullptr),
+      channel_uuid_(-1),
+      is_top_level_channel_(is_top_level_channel) {
   trace_.Init(channel_tracer_max_nodes);
   target_ = UniquePtr<char>(grpc_channel_get_target(channel_));
-  channel_uuid_ = ChannelzRegistry::Register(this);
+  channel_uuid_ = ChannelzRegistry::RegisterChannelNode(this);
   gpr_atm_no_barrier_store(&last_call_started_millis_,
                            (gpr_atm)ExecCtx::Get()->Now());
 }
 
 ChannelNode::~ChannelNode() {
   trace_.Destroy();
-  ChannelzRegistry::Unregister(channel_uuid_);
+  ChannelzRegistry::UnregisterChannelNode(channel_uuid_);
 }
 
 void ChannelNode::RecordCallStarted() {
@@ -65,7 +69,7 @@
 
 void ChannelNode::PopulateChildRefs(grpc_json* json) {}
 
-char* ChannelNode::RenderJSON() {
+grpc_json* 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);
   grpc_json* json = top_level_json;
@@ -86,13 +90,14 @@
   json = data;
   json_iterator = nullptr;
   PopulateConnectivityState(json);
+  GPR_ASSERT(target_.get() != nullptr);
   json_iterator = grpc_json_create_child(
       json_iterator, json, "target", target_.get(), GRPC_JSON_STRING, false);
   // fill in the channel trace if applicable
-  grpc_json* trace = trace_->RenderJSON();
+  grpc_json* trace = trace_->RenderJson();
   if (trace != nullptr) {
-    // we manuall link up and fill the child since it was created for us in
-    // ChannelTrace::RenderJSON
+    // we manually link up and fill the child since it was created for us in
+    // ChannelTrace::RenderJson
     json_iterator = grpc_json_link_child(json, trace, json_iterator);
     trace->parent = json;
     trace->value = nullptr;
@@ -102,14 +107,18 @@
   // reset the parent to be the data object.
   json = data;
   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 = 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_);
+  if (calls_started_ != 0) {
+    json_iterator = grpc_json_add_number_string_child(
+        json, json_iterator, "callsStarted", calls_started_);
+  }
+  if (calls_succeeded_ != 0) {
+    json_iterator = grpc_json_add_number_string_child(
+        json, json_iterator, "callsSucceeded", calls_succeeded_);
+  }
+  if (calls_failed_) {
+    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 =
@@ -118,25 +127,29 @@
   json = top_level_json;
   json_iterator = nullptr;
   PopulateChildRefs(json);
+  return top_level_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);
+char* ChannelNode::RenderJsonString() {
+  grpc_json* json = RenderJson();
+  char* json_str = grpc_json_dump_to_string(json, 0);
+  grpc_json_destroy(json);
   return json_str;
 }
 
 RefCountedPtr<ChannelNode> ChannelNode::MakeChannelNode(
-    grpc_channel* channel, size_t channel_tracer_max_nodes) {
+    grpc_channel* channel, size_t channel_tracer_max_nodes,
+    bool is_top_level_channel) {
   return MakeRefCounted<grpc_core::channelz::ChannelNode>(
-      channel, channel_tracer_max_nodes);
+      channel, channel_tracer_max_nodes, is_top_level_channel);
 }
 
 SubchannelNode::SubchannelNode() {
-  subchannel_uuid_ = ChannelzRegistry::Register(this);
+  subchannel_uuid_ = ChannelzRegistry::RegisterSubchannelNode(this);
 }
 
 SubchannelNode::~SubchannelNode() {
-  ChannelzRegistry::Unregister(subchannel_uuid_);
+  ChannelzRegistry::UnregisterSubchannelNode(subchannel_uuid_);
 }
 
 }  // namespace channelz
diff --git a/src/core/lib/channel/channelz.h b/src/core/lib/channel/channelz.h
index 7184af9..07eb73d 100644
--- a/src/core/lib/channel/channelz.h
+++ b/src/core/lib/channel/channelz.h
@@ -35,6 +35,10 @@
 #define GRPC_ARG_CHANNELZ_CHANNEL_NODE_CREATION_FUNC \
   "grpc.channelz_channel_node_creation_func"
 
+// Channel arg key to signal that the channel is an internal channel.
+#define GRPC_ARG_CHANNELZ_CHANNEL_IS_INTERNAL_CHANNEL \
+  "grpc.channelz_channel_is_internal_channel"
+
 namespace grpc_core {
 namespace channelz {
 
@@ -45,7 +49,8 @@
 class ChannelNode : public RefCounted<ChannelNode> {
  public:
   static RefCountedPtr<ChannelNode> MakeChannelNode(
-      grpc_channel* channel, size_t channel_tracer_max_nodes);
+      grpc_channel* channel, size_t channel_tracer_max_nodes,
+      bool is_top_level_channel);
 
   void RecordCallStarted();
   void RecordCallFailed() {
@@ -55,7 +60,8 @@
     gpr_atm_no_barrier_fetch_add(&calls_succeeded_, (gpr_atm(1)));
   }
 
-  char* RenderJSON();
+  grpc_json* RenderJson();
+  char* RenderJsonString();
 
   // helper for getting and populating connectivity state. It is virtual
   // because it allows the client_channel specific code to live in ext/
@@ -74,11 +80,13 @@
   bool ChannelIsDestroyed() { return channel_ == nullptr; }
 
   intptr_t channel_uuid() { return channel_uuid_; }
+  bool is_top_level_channel() { return is_top_level_channel_; }
 
  protected:
   GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
   GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_NEW
-  ChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes);
+  ChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes,
+              bool is_top_level_channel);
   virtual ~ChannelNode();
 
  private:
@@ -92,6 +100,7 @@
   gpr_atm calls_failed_ = 0;
   gpr_atm last_call_started_millis_ = 0;
   intptr_t channel_uuid_;
+  bool is_top_level_channel_ = true;
   ManualConstructor<ChannelTrace> trace_;
 };
 
@@ -116,7 +125,7 @@
 // Creation functions
 
 typedef RefCountedPtr<ChannelNode> (*ChannelNodeCreationFunc)(grpc_channel*,
-                                                              size_t);
+                                                              size_t, bool);
 
 }  // namespace channelz
 }  // namespace grpc_core
diff --git a/src/core/lib/channel/channelz_registry.cc b/src/core/lib/channel/channelz_registry.cc
index 023ede5..7ca3fc5 100644
--- a/src/core/lib/channel/channelz_registry.cc
+++ b/src/core/lib/channel/channelz_registry.cc
@@ -19,16 +19,19 @@
 #include <grpc/impl/codegen/port_platform.h>
 
 #include "src/core/lib/channel/channel_trace.h"
+#include "src/core/lib/channel/channelz.h"
 #include "src/core/lib/channel/channelz_registry.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/memory.h"
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
+#include <grpc/support/sync.h>
 
 #include <cstring>
 
 namespace grpc_core {
+namespace channelz {
 namespace {
 
 // singleton instance of the registry.
@@ -49,12 +52,73 @@
 
 ChannelzRegistry::~ChannelzRegistry() { gpr_mu_destroy(&mu_); }
 
-void ChannelzRegistry::InternalUnregister(intptr_t uuid) {
-  GPR_ASSERT(uuid >= 1);
-  gpr_mu_lock(&mu_);
-  GPR_ASSERT(static_cast<size_t>(uuid) <= entities_.size());
-  entities_[uuid - 1] = nullptr;
-  gpr_mu_unlock(&mu_);
+intptr_t ChannelzRegistry::InternalRegisterEntry(const RegistryEntry& entry) {
+  mu_guard guard(&mu_);
+  entities_.push_back(entry);
+  intptr_t uuid = entities_.size();
+  return uuid;
 }
 
+void ChannelzRegistry::InternalUnregisterEntry(intptr_t uuid, EntityType type) {
+  GPR_ASSERT(uuid >= 1);
+  mu_guard guard(&mu_);
+  GPR_ASSERT(static_cast<size_t>(uuid) <= entities_.size());
+  GPR_ASSERT(entities_[uuid - 1].type == type);
+  entities_[uuid - 1].object = nullptr;
+  entities_[uuid - 1].type = EntityType::kUnset;
+}
+
+void* ChannelzRegistry::InternalGetEntry(intptr_t uuid, EntityType type) {
+  mu_guard guard(&mu_);
+  if (uuid < 1 || uuid > static_cast<intptr_t>(entities_.size())) {
+    return nullptr;
+  }
+  if (entities_[uuid - 1].type == type) {
+    return entities_[uuid - 1].object;
+  } else {
+    return nullptr;
+  }
+}
+
+char* ChannelzRegistry::InternalGetTopChannels(intptr_t start_channel_id) {
+  grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+  grpc_json* json = top_level_json;
+  grpc_json* json_iterator = nullptr;
+  InlinedVector<ChannelNode*, 10> top_level_channels;
+  // uuids index into entities one-off (idx 0 is really uuid 1, since 0 is
+  // reserved). However, we want to support requests coming in this
+  // start_channel_id=0, which signifies "give me everything." Hence this
+  // funky looking line below.
+  size_t start_idx = start_channel_id == 0 ? 0 : start_channel_id - 1;
+  for (size_t i = start_idx; i < entities_.size(); ++i) {
+    if (entities_[i].type == EntityType::kChannelNode) {
+      ChannelNode* channel_node =
+          static_cast<ChannelNode*>(entities_[i].object);
+      if (channel_node->is_top_level_channel()) {
+        top_level_channels.push_back(channel_node);
+      }
+    }
+  }
+  if (top_level_channels.size() > 0) {
+    // create list of channels
+    grpc_json* array_parent = grpc_json_create_child(
+        nullptr, json, "channel", nullptr, GRPC_JSON_ARRAY, false);
+    for (size_t i = 0; i < top_level_channels.size(); ++i) {
+      grpc_json* channel_json = top_level_channels[i]->RenderJson();
+      json_iterator =
+          grpc_json_link_child(array_parent, channel_json, json_iterator);
+      channel_json->parent = array_parent;
+    }
+  }
+  // For now we do not have any pagination rules. In the future we could
+  // pick a constant for max_channels_sent for a GetTopChannels request.
+  // Tracking: https://github.com/grpc/grpc/issues/16019.
+  json_iterator = grpc_json_create_child(nullptr, json, "end", nullptr,
+                                         GRPC_JSON_TRUE, false);
+  char* json_str = grpc_json_dump_to_string(top_level_json, 0);
+  grpc_json_destroy(top_level_json);
+  return json_str;
+}
+
+}  // namespace channelz
 }  // namespace grpc_core
diff --git a/src/core/lib/channel/channelz_registry.h b/src/core/lib/channel/channelz_registry.h
index a5a187a..5d7c936 100644
--- a/src/core/lib/channel/channelz_registry.h
+++ b/src/core/lib/channel/channelz_registry.h
@@ -22,11 +22,13 @@
 #include <grpc/impl/codegen/port_platform.h>
 
 #include "src/core/lib/channel/channel_trace.h"
+#include "src/core/lib/channel/channelz.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
 
 #include <stdint.h>
 
 namespace grpc_core {
+namespace channelz {
 
 // singleton registry object to track all objects that are needed to support
 // channelz bookkeeping. All objects share globally distributed uuids.
@@ -35,26 +37,56 @@
   // To be called in grpc_init()
   static void Init();
 
-  // To be callen in grpc_shutdown();
+  // To be called in grpc_shutdown();
   static void Shutdown();
 
-  // globally registers a channelz Object. Returns its unique uuid
-  template <typename Object>
-  static intptr_t Register(Object* object) {
-    return Default()->InternalRegister(object);
+  // Register/Unregister/Get for ChannelNode
+  static intptr_t RegisterChannelNode(ChannelNode* channel_node) {
+    RegistryEntry entry(channel_node, EntityType::kChannelNode);
+    return Default()->InternalRegisterEntry(entry);
+  }
+  static void UnregisterChannelNode(intptr_t uuid) {
+    Default()->InternalUnregisterEntry(uuid, EntityType::kChannelNode);
+  }
+  static ChannelNode* GetChannelNode(intptr_t uuid) {
+    void* gotten = Default()->InternalGetEntry(uuid, EntityType::kChannelNode);
+    return gotten == nullptr ? nullptr : static_cast<ChannelNode*>(gotten);
   }
 
-  // globally unregisters the object that is associated to uuid.
-  static void Unregister(intptr_t uuid) { Default()->InternalUnregister(uuid); }
+  // Register/Unregister/Get for SubchannelNode
+  static intptr_t RegisterSubchannelNode(SubchannelNode* channel_node) {
+    RegistryEntry entry(channel_node, EntityType::kSubchannelNode);
+    return Default()->InternalRegisterEntry(entry);
+  }
+  static void UnregisterSubchannelNode(intptr_t uuid) {
+    Default()->InternalUnregisterEntry(uuid, EntityType::kSubchannelNode);
+  }
+  static SubchannelNode* GetSubchannelNode(intptr_t uuid) {
+    void* gotten =
+        Default()->InternalGetEntry(uuid, EntityType::kSubchannelNode);
+    return gotten == nullptr ? nullptr : static_cast<SubchannelNode*>(gotten);
+  }
 
-  // if object with uuid has previously been registered, returns the
-  // Object associated with that uuid. Else returns nullptr.
-  template <typename Object>
-  static Object* Get(intptr_t uuid) {
-    return Default()->InternalGet<Object>(uuid);
+  // Returns the allocated JSON string that represents the proto
+  // GetTopChannelsResponse as per channelz.proto.
+  static char* GetTopChannels(intptr_t start_channel_id) {
+    return Default()->InternalGetTopChannels(start_channel_id);
   }
 
  private:
+  enum class EntityType {
+    kChannelNode,
+    kSubchannelNode,
+    kUnset,
+  };
+
+  struct RegistryEntry {
+    RegistryEntry(void* object_in, EntityType type_in)
+        : object(object_in), type(type_in) {}
+    void* object;
+    EntityType type;
+  };
+
   GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_NEW
   GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
 
@@ -64,40 +96,25 @@
   // Returned the singleton instance of ChannelzRegistry;
   static ChannelzRegistry* Default();
 
-  // globally registers a channelz Object. Returns its unique uuid
-  template <typename Object>
-  intptr_t InternalRegister(Object* object) {
-    gpr_mu_lock(&mu_);
-    entities_.push_back(static_cast<void*>(object));
-    intptr_t uuid = entities_.size();
-    gpr_mu_unlock(&mu_);
-    return uuid;
-  }
+  // globally registers an Entry. Returns its unique uuid
+  intptr_t InternalRegisterEntry(const RegistryEntry& entry);
 
-  // globally unregisters the object that is associated to uuid.
-  void InternalUnregister(intptr_t uuid);
+  // globally unregisters the object that is associated to uuid. Also does
+  // sanity check that an object doesn't try to unregister the wrong type.
+  void InternalUnregisterEntry(intptr_t uuid, EntityType type);
 
-  // if object with uuid has previously been registered, returns the
-  // Object associated with that uuid. Else returns nullptr.
-  template <typename Object>
-  Object* InternalGet(intptr_t uuid) {
-    gpr_mu_lock(&mu_);
-    if (uuid < 1 || uuid > static_cast<intptr_t>(entities_.size())) {
-      gpr_mu_unlock(&mu_);
-      return nullptr;
-    }
-    Object* ret = static_cast<Object*>(entities_[uuid - 1]);
-    gpr_mu_unlock(&mu_);
-    return ret;
-  }
+  // if object with uuid has previously been registered as the correct type,
+  // returns the void* associated with that uuid. Else returns nullptr.
+  void* InternalGetEntry(intptr_t uuid, EntityType type);
 
-  // private members
+  char* InternalGetTopChannels(intptr_t start_channel_id);
 
   // protects entities_ and uuid_
   gpr_mu mu_;
-  InlinedVector<void*, 20> entities_;
+  InlinedVector<RegistryEntry, 20> entities_;
 };
 
+}  // namespace channelz
 }  // namespace grpc_core
 
 #endif /* GRPC_CORE_LIB_CHANNEL_CHANNELZ_REGISTRY_H */
diff --git a/src/core/lib/surface/channel.cc b/src/core/lib/surface/channel.cc
index 8f3ad6c..7cbd61a 100644
--- a/src/core/lib/surface/channel.cc
+++ b/src/core/lib/surface/channel.cc
@@ -105,6 +105,7 @@
   channel->is_client = grpc_channel_stack_type_is_client(channel_stack_type);
   size_t channel_tracer_max_nodes = 0;  // default to off
   bool channelz_enabled = false;
+  bool internal_channel = false;
   // this creates the default ChannelNode. Different types of channels may
   // override this to ensure a correct ChannelNode is created.
   grpc_core::channelz::ChannelNodeCreationFunc channel_node_create_func =
@@ -158,13 +159,17 @@
       channel_node_create_func =
           reinterpret_cast<grpc_core::channelz::ChannelNodeCreationFunc>(
               args->args[i].value.pointer.p);
+    } else if (0 == strcmp(args->args[i].key,
+                           GRPC_ARG_CHANNELZ_CHANNEL_IS_INTERNAL_CHANNEL)) {
+      internal_channel = grpc_channel_arg_get_bool(&args->args[i], false);
     }
   }
 
   grpc_channel_args_destroy(args);
   if (channelz_enabled) {
-    channel->channelz_channel =
-        channel_node_create_func(channel, channel_tracer_max_nodes);
+    bool is_top_level_channel = channel->is_client && !internal_channel;
+    channel->channelz_channel = channel_node_create_func(
+        channel, channel_tracer_max_nodes, is_top_level_channel);
     channel->channelz_channel->trace()->AddTraceEvent(
         grpc_core::channelz::ChannelTrace::Severity::Info,
         grpc_slice_from_static_string("Channel created"));
diff --git a/src/core/lib/surface/init.cc b/src/core/lib/surface/init.cc
index 16be81e..0ad82fe 100644
--- a/src/core/lib/surface/init.cc
+++ b/src/core/lib/surface/init.cc
@@ -127,7 +127,7 @@
     grpc_slice_intern_init();
     grpc_mdctx_global_init();
     grpc_channel_init_init();
-    grpc_core::ChannelzRegistry::Init();
+    grpc_core::channelz::ChannelzRegistry::Init();
     grpc_security_pre_init();
     grpc_core::ExecCtx::GlobalInit();
     grpc_iomgr_init();
@@ -176,7 +176,7 @@
       grpc_mdctx_global_shutdown();
       grpc_handshaker_factory_registry_shutdown();
       grpc_slice_intern_shutdown();
-      grpc_core::ChannelzRegistry::Shutdown();
+      grpc_core::channelz::ChannelzRegistry::Shutdown();
       grpc_stats_shutdown();
       grpc_core::Fork::GlobalShutdown();
     }
diff --git a/test/core/channel/channel_trace_test.cc b/test/core/channel/channel_trace_test.cc
index bbddee3..99d9a48 100644
--- a/test/core/channel/channel_trace_test.cc
+++ b/test/core/channel/channel_trace_test.cc
@@ -34,8 +34,6 @@
 #include "test/core/util/test_config.h"
 #include "test/cpp/util/channel_trace_proto_helper.h"
 
-// remove me
-#include <grpc/support/string_util.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -88,7 +86,7 @@
 void ValidateChannelTrace(ChannelTrace* tracer,
                           size_t expected_num_event_logged, size_t max_nodes) {
   if (!max_nodes) return;
-  grpc_json* json = tracer->RenderJSON();
+  grpc_json* json = tracer->RenderJson();
   EXPECT_NE(json, nullptr);
   char* json_str = grpc_json_dump_to_string(json, 0);
   grpc_json_destroy(json);
@@ -157,7 +155,7 @@
   AddSimpleTrace(&tracer);
   ChannelFixture channel1(GetParam());
   RefCountedPtr<ChannelNode> sc1 =
-      MakeRefCounted<ChannelNode>(channel1.channel(), GetParam());
+      MakeRefCounted<ChannelNode>(channel1.channel(), GetParam(), true);
   tracer.AddTraceEventReferencingSubchannel(
       ChannelTrace::Severity::Info,
       grpc_slice_from_static_string("subchannel one created"), sc1);
@@ -175,7 +173,7 @@
   ValidateChannelTrace(&tracer, 5, GetParam());
   ChannelFixture channel2(GetParam());
   RefCountedPtr<ChannelNode> sc2 =
-      MakeRefCounted<ChannelNode>(channel2.channel(), GetParam());
+      MakeRefCounted<ChannelNode>(channel2.channel(), GetParam(), true);
   tracer.AddTraceEventReferencingChannel(
       ChannelTrace::Severity::Info,
       grpc_slice_from_static_string("LB channel two created"), sc2);
@@ -204,7 +202,7 @@
   ValidateChannelTrace(&tracer, 2, GetParam());
   ChannelFixture channel1(GetParam());
   RefCountedPtr<ChannelNode> sc1 =
-      MakeRefCounted<ChannelNode>(channel1.channel(), GetParam());
+      MakeRefCounted<ChannelNode>(channel1.channel(), GetParam(), true);
   tracer.AddTraceEventReferencingChannel(
       ChannelTrace::Severity::Info,
       grpc_slice_from_static_string("subchannel one created"), sc1);
@@ -212,7 +210,7 @@
   AddSimpleTrace(sc1->trace());
   ChannelFixture channel2(GetParam());
   RefCountedPtr<ChannelNode> conn1 =
-      MakeRefCounted<ChannelNode>(channel2.channel(), GetParam());
+      MakeRefCounted<ChannelNode>(channel2.channel(), GetParam(), true);
   // nesting one level deeper.
   sc1->trace()->AddTraceEventReferencingSubchannel(
       ChannelTrace::Severity::Info,
@@ -225,7 +223,7 @@
   ValidateChannelTrace(conn1->trace(), 1, GetParam());
   ChannelFixture channel3(GetParam());
   RefCountedPtr<ChannelNode> sc2 =
-      MakeRefCounted<ChannelNode>(channel3.channel(), GetParam());
+      MakeRefCounted<ChannelNode>(channel3.channel(), GetParam(), true);
   tracer.AddTraceEventReferencingSubchannel(
       ChannelTrace::Severity::Info,
       grpc_slice_from_static_string("subchannel two created"), sc2);
diff --git a/test/core/channel/channelz_registry_test.cc b/test/core/channel/channelz_registry_test.cc
index 24e5093..990cd3d 100644
--- a/test/core/channel/channelz_registry_test.cc
+++ b/test/core/channel/channelz_registry_test.cc
@@ -19,17 +19,20 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <grpc/grpc.h>
 #include <gtest/gtest.h>
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_trace.h"
+#include "src/core/lib/channel/channelz.h"
 #include "src/core/lib/channel/channelz_registry.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/memory.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
 #include "src/core/lib/json/json.h"
+#include "src/core/lib/surface/channel.h"
 
 #include "test/core/util/test_config.h"
 
@@ -37,27 +40,26 @@
 #include <string.h>
 
 namespace grpc_core {
+namespace channelz {
 namespace testing {
 
-// Tests basic ChannelTrace functionality like construction, adding trace, and
-// lookups by uuid.
 TEST(ChannelzRegistryTest, UuidStartsAboveZeroTest) {
-  int object_to_register;
-  intptr_t uuid = ChannelzRegistry::Register(&object_to_register);
+  ChannelNode* channelz_channel = nullptr;
+  intptr_t uuid = ChannelzRegistry::RegisterChannelNode(channelz_channel);
   EXPECT_GT(uuid, 0) << "First uuid chose must be greater than zero. Zero if "
                         "reserved according to "
                         "https://github.com/grpc/proposal/blob/master/"
                         "A14-channelz.md";
-  ChannelzRegistry::Unregister(uuid);
+  ChannelzRegistry::UnregisterChannelNode(uuid);
 }
 
 TEST(ChannelzRegistryTest, UuidsAreIncreasing) {
-  int object_to_register;
+  ChannelNode* channelz_channel = nullptr;
   std::vector<intptr_t> uuids;
   uuids.reserve(10);
   for (int i = 0; i < 10; ++i) {
     // reregister the same object. It's ok since we are just testing uuids
-    uuids.push_back(ChannelzRegistry::Register(&object_to_register));
+    uuids.push_back(ChannelzRegistry::RegisterChannelNode(channelz_channel));
   }
   for (size_t i = 1; i < uuids.size(); ++i) {
     EXPECT_LT(uuids[i - 1], uuids[i]) << "Uuids must always be increasing";
@@ -65,60 +67,33 @@
 }
 
 TEST(ChannelzRegistryTest, RegisterGetTest) {
-  int object_to_register = 42;
-  intptr_t uuid = ChannelzRegistry::Register(&object_to_register);
-  int* retrieved = ChannelzRegistry::Get<int>(uuid);
-  EXPECT_EQ(&object_to_register, retrieved);
-}
-
-TEST(ChannelzRegistryTest, MultipleTypeTest) {
-  int int_to_register = 42;
-  intptr_t int_uuid = ChannelzRegistry::Register(&int_to_register);
-  std::string str_to_register = "hello world";
-  intptr_t str_uuid = ChannelzRegistry::Register(&str_to_register);
-  int* retrieved_int = ChannelzRegistry::Get<int>(int_uuid);
-  std::string* retrieved_str = ChannelzRegistry::Get<std::string>(str_uuid);
-  EXPECT_EQ(&int_to_register, retrieved_int);
-  EXPECT_EQ(&str_to_register, retrieved_str);
+  ChannelNode* channelz_channel = nullptr;
+  intptr_t uuid = ChannelzRegistry::RegisterChannelNode(channelz_channel);
+  ChannelNode* retrieved = ChannelzRegistry::GetChannelNode(uuid);
+  EXPECT_EQ(channelz_channel, retrieved);
 }
 
 TEST(ChannelzRegistryTest, RegisterManyItems) {
-  int object_to_register = 42;
+  ChannelNode* channelz_channel = nullptr;
   for (int i = 0; i < 100; i++) {
-    intptr_t uuid = ChannelzRegistry::Register(&object_to_register);
-    int* retrieved = ChannelzRegistry::Get<int>(uuid);
-    EXPECT_EQ(&object_to_register, retrieved);
+    intptr_t uuid = ChannelzRegistry::RegisterChannelNode(channelz_channel);
+    ChannelNode* retrieved = ChannelzRegistry::GetChannelNode(uuid);
+    EXPECT_EQ(channelz_channel, retrieved);
   }
 }
 
-namespace {
-class Foo {
- public:
-  int bar;
-};
-}  // namespace
-
-TEST(ChannelzRegistryTest, CustomObjectTest) {
-  Foo* foo = New<Foo>();
-  foo->bar = 1024;
-  intptr_t uuid = ChannelzRegistry::Register(foo);
-  Foo* retrieved = ChannelzRegistry::Get<Foo>(uuid);
-  EXPECT_EQ(foo, retrieved);
-  Delete(foo);
-}
-
 TEST(ChannelzRegistryTest, NullIfNotPresentTest) {
-  int object_to_register = 42;
-  intptr_t uuid = ChannelzRegistry::Register(&object_to_register);
+  ChannelNode* channelz_channel = nullptr;
+  intptr_t uuid = ChannelzRegistry::RegisterChannelNode(channelz_channel);
   // try to pull out a uuid that does not exist.
-  int* nonexistant = ChannelzRegistry::Get<int>(uuid + 1);
+  ChannelNode* nonexistant = ChannelzRegistry::GetChannelNode(uuid + 1);
   EXPECT_EQ(nonexistant, nullptr);
-  int* retrieved = ChannelzRegistry::Get<int>(uuid);
-  EXPECT_EQ(object_to_register, *retrieved);
-  EXPECT_EQ(&object_to_register, retrieved);
+  ChannelNode* retrieved = ChannelzRegistry::GetChannelNode(uuid);
+  EXPECT_EQ(channelz_channel, retrieved);
 }
 
 }  // namespace testing
+}  // namespace channelz
 }  // namespace grpc_core
 
 int main(int argc, char** argv) {
diff --git a/test/core/channel/channelz_test.cc b/test/core/channel/channelz_test.cc
index 058eea9..d12f529 100644
--- a/test/core/channel/channelz_test.cc
+++ b/test/core/channel/channelz_test.cc
@@ -67,17 +67,45 @@
   return nullptr;
 }
 
+void ValidateJsonArraySize(grpc_json* json, const char* key,
+                           size_t expected_size) {
+  grpc_json* arr = GetJsonChild(json, key);
+  if (expected_size == 0) {
+    ASSERT_EQ(arr, nullptr);
+    return;
+  }
+  ASSERT_NE(arr, nullptr);
+  ASSERT_EQ(arr->type, GRPC_JSON_ARRAY);
+  size_t count = 0;
+  for (grpc_json* child = arr->child; child != nullptr; child = child->next) {
+    ++count;
+  }
+  EXPECT_EQ(count, expected_size);
+}
+
+void ValidateGetTopChannels(size_t expected_channels) {
+  char* json_str = ChannelzRegistry::GetTopChannels(0);
+  grpc::testing::ValidateGetTopChannelsResponseProtoJsonTranslation(json_str);
+  grpc_json* parsed_json = grpc_json_parse_string(json_str);
+  // This check will naturally have to change when we support pagination.
+  // tracked: https://github.com/grpc/grpc/issues/16019.
+  ValidateJsonArraySize(parsed_json, "channel", expected_channels);
+  grpc_json* end = GetJsonChild(parsed_json, "end");
+  ASSERT_NE(end, nullptr);
+  EXPECT_EQ(end->type, GRPC_JSON_TRUE);
+  grpc_json_destroy(parsed_json);
+  gpr_free(json_str);
+}
+
 class ChannelFixture {
  public:
-  ChannelFixture(int max_trace_nodes) {
+  ChannelFixture(int max_trace_nodes = 0) {
     grpc_arg client_a[2];
-    client_a[0].type = GRPC_ARG_INTEGER;
-    client_a[0].key =
-        const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENTS_PER_NODE);
-    client_a[0].value.integer = max_trace_nodes;
-    client_a[1].type = GRPC_ARG_INTEGER;
-    client_a[1].key = const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ);
-    client_a[1].value.integer = true;
+    client_a[0] = grpc_channel_arg_integer_create(
+        const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENTS_PER_NODE),
+        max_trace_nodes);
+    client_a[1] = grpc_channel_arg_integer_create(
+        const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ), true);
     grpc_channel_args client_args = {GPR_ARRAY_SIZE(client_a), client_a};
     channel_ =
         grpc_insecure_channel_create("fake_target", &client_args, nullptr);
@@ -99,6 +127,10 @@
 
 void ValidateChildInteger(grpc_json* json, int64_t expect, const char* key) {
   grpc_json* gotten_json = GetJsonChild(json, key);
+  if (expect == 0) {
+    ASSERT_EQ(gotten_json, nullptr);
+    return;
+  }
   ASSERT_NE(gotten_json, nullptr);
   int64_t gotten_number = (int64_t)strtol(gotten_json->value, nullptr, 0);
   EXPECT_EQ(gotten_number, expect);
@@ -115,7 +147,7 @@
 }
 
 void ValidateChannel(ChannelNode* channel, validate_channel_data_args args) {
-  char* json_str = channel->RenderJSON();
+  char* json_str = channel->RenderJsonString();
   grpc::testing::ValidateChannelProtoJsonTranslation(json_str);
   ValidateCounters(json_str, args);
   gpr_free(json_str);
@@ -141,9 +173,7 @@
   ChannelFixture channel(GetParam());
   ChannelNode* channelz_channel =
       grpc_channel_get_channelz_node(channel.channel());
-  char* json_str = channelz_channel->RenderJSON();
-  ValidateCounters(json_str, {0, 0, 0});
-  gpr_free(json_str);
+  ValidateChannel(channelz_channel, {0, 0, 0});
 }
 
 TEST(ChannelzChannelTest, ChannelzDisabled) {
@@ -199,6 +229,42 @@
   EXPECT_NE(millis1, millis4);
 }
 
+TEST(ChannelzGetTopChannelsTest, BasicTest) {
+  grpc_core::ExecCtx exec_ctx;
+  ChannelFixture channel;
+  ValidateGetTopChannels(1);
+}
+
+TEST(ChannelzGetTopChannelsTest, NoChannelsTest) {
+  grpc_core::ExecCtx exec_ctx;
+  ValidateGetTopChannels(0);
+}
+
+TEST(ChannelzGetTopChannelsTest, ManyChannelsTest) {
+  grpc_core::ExecCtx exec_ctx;
+  ChannelFixture channels[10];
+  (void)channels;  // suppress unused variable error
+  ValidateGetTopChannels(10);
+}
+
+TEST(ChannelzGetTopChannelsTest, InternalChannelTest) {
+  grpc_core::ExecCtx exec_ctx;
+  ChannelFixture channels[10];
+  (void)channels;  // suppress unused variable error
+  // create an internal channel
+  grpc_arg client_a[2];
+  client_a[0] = grpc_channel_arg_integer_create(
+      const_cast<char*>(GRPC_ARG_CHANNELZ_CHANNEL_IS_INTERNAL_CHANNEL), true);
+  client_a[1] = grpc_channel_arg_integer_create(
+      const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ), true);
+  grpc_channel_args client_args = {GPR_ARRAY_SIZE(client_a), client_a};
+  grpc_channel* internal_channel =
+      grpc_insecure_channel_create("fake_target", &client_args, nullptr);
+  // The internal channel should not be returned from the request
+  ValidateGetTopChannels(10);
+  grpc_channel_destroy(internal_channel);
+}
+
 INSTANTIATE_TEST_CASE_P(ChannelzChannelTestSweep, ChannelzChannelTest,
                         ::testing::Values(0, 1, 2, 6, 10, 15));
 
diff --git a/test/core/end2end/tests/channelz.cc b/test/core/end2end/tests/channelz.cc
index eb05211..533703a 100644
--- a/test/core/end2end/tests/channelz.cc
+++ b/test/core/end2end/tests/channelz.cc
@@ -209,27 +209,27 @@
       grpc_channel_get_channelz_node(f.client);
 
   GPR_ASSERT(channelz_channel != nullptr);
-  char* json = channelz_channel->RenderJSON();
+  char* json = channelz_channel->RenderJsonString();
   GPR_ASSERT(json != nullptr);
-  GPR_ASSERT(nullptr != strstr(json, "\"callsStarted\":\"0\""));
-  GPR_ASSERT(nullptr != strstr(json, "\"callsFailed\":\"0\""));
-  GPR_ASSERT(nullptr != strstr(json, "\"callsSucceeded\":\"0\""));
+  // nothing is present yet
+  GPR_ASSERT(nullptr == strstr(json, "\"callsStarted\""));
+  GPR_ASSERT(nullptr == strstr(json, "\"callsFailed\""));
+  GPR_ASSERT(nullptr == strstr(json, "\"callsSucceeded\""));
   gpr_free(json);
 
   // one successful request
   run_one_request(config, f, true);
 
-  json = channelz_channel->RenderJSON();
+  json = channelz_channel->RenderJsonString();
   GPR_ASSERT(json != nullptr);
   GPR_ASSERT(nullptr != strstr(json, "\"callsStarted\":\"1\""));
-  GPR_ASSERT(nullptr != strstr(json, "\"callsFailed\":\"0\""));
   GPR_ASSERT(nullptr != strstr(json, "\"callsSucceeded\":\"1\""));
   gpr_free(json);
 
   // one failed request
   run_one_request(config, f, false);
 
-  json = channelz_channel->RenderJSON();
+  json = channelz_channel->RenderJsonString();
   GPR_ASSERT(json != nullptr);
   gpr_log(GPR_INFO, "%s", json);
   GPR_ASSERT(nullptr != strstr(json, "\"callsStarted\":\"2\""));
@@ -264,7 +264,7 @@
       grpc_channel_get_channelz_node(f.client);
 
   GPR_ASSERT(channelz_channel != nullptr);
-  char* json = channelz_channel->RenderJSON();
+  char* json = channelz_channel->RenderJsonString();
   GPR_ASSERT(json != nullptr);
   gpr_log(GPR_INFO, "%s", json);
   GPR_ASSERT(nullptr != strstr(json, "\"trace\""));
diff --git a/test/cpp/util/channel_trace_proto_helper.cc b/test/cpp/util/channel_trace_proto_helper.cc
index ee31078..137f278 100644
--- a/test/cpp/util/channel_trace_proto_helper.cc
+++ b/test/cpp/util/channel_trace_proto_helper.cc
@@ -64,13 +64,17 @@
 
 }  // namespace
 
-void ValidateChannelTraceProtoJsonTranslation(char* tracer_json_c_str) {
-  VaidateProtoJsonTranslation<grpc::channelz::v1::ChannelTrace>(
-      tracer_json_c_str);
+void ValidateChannelTraceProtoJsonTranslation(char* json_c_str) {
+  VaidateProtoJsonTranslation<grpc::channelz::v1::ChannelTrace>(json_c_str);
 }
 
-void ValidateChannelProtoJsonTranslation(char* channel_json_c_str) {
-  VaidateProtoJsonTranslation<grpc::channelz::v1::Channel>(channel_json_c_str);
+void ValidateChannelProtoJsonTranslation(char* json_c_str) {
+  VaidateProtoJsonTranslation<grpc::channelz::v1::Channel>(json_c_str);
+}
+
+void ValidateGetTopChannelsResponseProtoJsonTranslation(char* json_c_str) {
+  VaidateProtoJsonTranslation<grpc::channelz::v1::GetTopChannelsResponse>(
+      json_c_str);
 }
 
 }  // namespace testing
diff --git a/test/cpp/util/channel_trace_proto_helper.h b/test/cpp/util/channel_trace_proto_helper.h
index d1a3603..74c15f0 100644
--- a/test/cpp/util/channel_trace_proto_helper.h
+++ b/test/cpp/util/channel_trace_proto_helper.h
@@ -22,8 +22,9 @@
 namespace grpc {
 namespace testing {
 
-void ValidateChannelTraceProtoJsonTranslation(char* tracer_json_c_str);
-void ValidateChannelProtoJsonTranslation(char* channel_json_c_str);
+void ValidateChannelTraceProtoJsonTranslation(char* json_c_str);
+void ValidateChannelProtoJsonTranslation(char* json_c_str);
+void ValidateGetTopChannelsResponseProtoJsonTranslation(char* json_c_str);
 
 }  // namespace testing
 }  // namespace grpc