/* * * Copyright 2017 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include <stdlib.h> #include <string.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/iomgr/exec_ctx.h" #include "src/core/lib/json/json.h" #include "src/core/lib/surface/channel.h" #include "src/core/lib/surface/server.h" #include "test/core/util/test_config.h" #include "test/cpp/util/channel_trace_proto_helper.h" #include <grpc/support/string_util.h> #include <stdlib.h> #include <string.h> namespace grpc_core { namespace channelz { namespace testing { // testing peer to access channel internals class CallCountingHelperPeer { public: explicit CallCountingHelperPeer(CallCountingHelper* node) : node_(node) {} grpc_millis last_call_started_millis() const { return (grpc_millis)gpr_atm_no_barrier_load( &node_->last_call_started_millis_); } private: CallCountingHelper* node_; }; namespace { grpc_json* GetJsonChild(grpc_json* parent, const char* key) { EXPECT_NE(parent, nullptr); for (grpc_json* child = parent->child; child != nullptr; child = child->next) { if (child->key != nullptr && strcmp(child->key, key) == 0) return child; } 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); // also check that the core API formats this correctly char* core_api_json_str = grpc_channelz_get_top_channels(0); grpc::testing::ValidateGetTopChannelsResponseProtoJsonTranslation( core_api_json_str); gpr_free(core_api_json_str); } void ValidateGetServers(size_t expected_servers) { char* json_str = ChannelzRegistry::GetServers(0); grpc::testing::ValidateGetServersResponseProtoJsonTranslation(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, "server", expected_servers); 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); // also check that the core API formats this correctly char* core_api_json_str = grpc_channelz_get_servers(0); grpc::testing::ValidateGetServersResponseProtoJsonTranslation( core_api_json_str); gpr_free(core_api_json_str); } class ChannelFixture { public: ChannelFixture(int max_trace_nodes = 0) { grpc_arg client_a[2]; 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); } ~ChannelFixture() { grpc_channel_destroy(channel_); } grpc_channel* channel() { return channel_; } private: grpc_channel* channel_; }; class ServerFixture { public: explicit ServerFixture(int max_trace_nodes = 0) { grpc_arg server_a[] = { grpc_channel_arg_integer_create( const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENTS_PER_NODE), max_trace_nodes), grpc_channel_arg_integer_create( const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ), true), }; grpc_channel_args server_args = {GPR_ARRAY_SIZE(server_a), server_a}; server_ = grpc_server_create(&server_args, nullptr); } ~ServerFixture() { grpc_server_destroy(server_); } grpc_server* server() const { return server_; } private: grpc_server* server_; }; struct validate_channel_data_args { int64_t calls_started; int64_t calls_failed; int64_t calls_succeeded; }; 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); } void ValidateCounters(char* json_str, validate_channel_data_args args) { grpc_json* json = grpc_json_parse_string(json_str); ASSERT_NE(json, nullptr); grpc_json* data = GetJsonChild(json, "data"); ValidateChildInteger(data, args.calls_started, "callsStarted"); ValidateChildInteger(data, args.calls_failed, "callsFailed"); ValidateChildInteger(data, args.calls_succeeded, "callsSucceeded"); grpc_json_destroy(json); } void ValidateChannel(ChannelNode* channel, validate_channel_data_args args) { char* json_str = channel->RenderJsonString(); grpc::testing::ValidateChannelProtoJsonTranslation(json_str); ValidateCounters(json_str, args); gpr_free(json_str); // also check that the core API formats this the correct way char* core_api_json_str = grpc_channelz_get_channel(channel->uuid()); grpc::testing::ValidateGetChannelResponseProtoJsonTranslation( core_api_json_str); gpr_free(core_api_json_str); } void ValidateServer(ServerNode* server, validate_channel_data_args args) { char* json_str = server->RenderJsonString(); grpc::testing::ValidateServerProtoJsonTranslation(json_str); ValidateCounters(json_str, args); gpr_free(json_str); } grpc_millis GetLastCallStartedMillis(CallCountingHelper* channel) { CallCountingHelperPeer peer(channel); return peer.last_call_started_millis(); } void ChannelzSleep(int64_t sleep_us) { gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_micros(sleep_us, GPR_TIMESPAN))); grpc_core::ExecCtx::Get()->InvalidateNow(); } } // anonymous namespace class ChannelzChannelTest : public ::testing::TestWithParam<size_t> {}; TEST_P(ChannelzChannelTest, BasicChannel) { grpc_core::ExecCtx exec_ctx; ChannelFixture channel(GetParam()); ChannelNode* channelz_channel = grpc_channel_get_channelz_node(channel.channel()); ValidateChannel(channelz_channel, {0, 0, 0}); } TEST(ChannelzChannelTest, ChannelzDisabled) { grpc_core::ExecCtx exec_ctx; grpc_channel* channel = grpc_insecure_channel_create("fake_target", nullptr, nullptr); ChannelNode* channelz_channel = grpc_channel_get_channelz_node(channel); ASSERT_EQ(channelz_channel, nullptr); grpc_channel_destroy(channel); } TEST_P(ChannelzChannelTest, BasicChannelAPIFunctionality) { grpc_core::ExecCtx exec_ctx; ChannelFixture channel(GetParam()); ChannelNode* channelz_channel = grpc_channel_get_channelz_node(channel.channel()); channelz_channel->RecordCallStarted(); channelz_channel->RecordCallFailed(); channelz_channel->RecordCallSucceeded(); ValidateChannel(channelz_channel, {1, 1, 1}); channelz_channel->RecordCallStarted(); channelz_channel->RecordCallFailed(); channelz_channel->RecordCallSucceeded(); channelz_channel->RecordCallStarted(); channelz_channel->RecordCallFailed(); channelz_channel->RecordCallSucceeded(); ValidateChannel(channelz_channel, {3, 3, 3}); } TEST_P(ChannelzChannelTest, LastCallStartedMillis) { grpc_core::ExecCtx exec_ctx; CallCountingHelper counter; // start a call to set the last call started timestamp counter.RecordCallStarted(); grpc_millis millis1 = GetLastCallStartedMillis(&counter); // time gone by should not affect the timestamp ChannelzSleep(100); grpc_millis millis2 = GetLastCallStartedMillis(&counter); EXPECT_EQ(millis1, millis2); // calls succeeded or failed should not affect the timestamp ChannelzSleep(100); counter.RecordCallFailed(); counter.RecordCallSucceeded(); grpc_millis millis3 = GetLastCallStartedMillis(&counter); EXPECT_EQ(millis1, millis3); // another call started should affect the timestamp // sleep for extra long to avoid flakes (since we cache Now()) ChannelzSleep(5000); counter.RecordCallStarted(); grpc_millis millis4 = GetLastCallStartedMillis(&counter); EXPECT_NE(millis1, millis4); } TEST(ChannelzGetTopChannelsTest, BasicGetTopChannelsTest) { 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); } class ChannelzServerTest : public ::testing::TestWithParam<size_t> {}; TEST_P(ChannelzServerTest, BasicServerAPIFunctionality) { grpc_core::ExecCtx exec_ctx; ServerFixture server(10); ServerNode* channelz_server = grpc_server_get_channelz_node(server.server()); channelz_server->RecordCallStarted(); channelz_server->RecordCallFailed(); channelz_server->RecordCallSucceeded(); ValidateServer(channelz_server, {1, 1, 1}); channelz_server->RecordCallStarted(); channelz_server->RecordCallFailed(); channelz_server->RecordCallSucceeded(); channelz_server->RecordCallStarted(); channelz_server->RecordCallFailed(); channelz_server->RecordCallSucceeded(); ValidateServer(channelz_server, {3, 3, 3}); } TEST(ChannelzGetServersTest, BasicGetServersTest) { grpc_core::ExecCtx exec_ctx; ServerFixture server; ValidateGetServers(1); } TEST(ChannelzGetServersTest, NoServersTest) { grpc_core::ExecCtx exec_ctx; ValidateGetServers(0); } TEST(ChannelzGetServersTest, ManyServersTest) { grpc_core::ExecCtx exec_ctx; ServerFixture servers[10]; (void)servers; // suppress unused variable error ValidateGetServers(10); } INSTANTIATE_TEST_CASE_P(ChannelzChannelTestSweep, ChannelzChannelTest, ::testing::Values(0, 1, 2, 6, 10, 15)); INSTANTIATE_TEST_CASE_P(ChannelzServerTestSweep, ChannelzServerTest, ::testing::Values(0, 1, 2, 6, 10, 15)); } // namespace testing } // namespace channelz } // namespace grpc_core int main(int argc, char** argv) { grpc_test_init(argc, argv); grpc_init(); ::testing::InitGoogleTest(&argc, argv); int ret = RUN_ALL_TESTS(); grpc_shutdown(); return ret; }