blob: 3c7c89848e07314dff9775ef6b5aaabf910f6c04 [file] [log] [blame]
/*
*
* 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) {}
gpr_timespec last_call_started_time() const {
CallCountingHelper::CounterData data;
node_->CollectData(&data);
return gpr_cycle_counter_to_time(data.last_call_started_cycle);
}
private:
CallCountingHelper* node_;
};
namespace {
std::vector<intptr_t> GetUuidListFromArray(const Json::Array& arr) {
std::vector<intptr_t> uuids;
for (const Json& value : arr) {
EXPECT_EQ(value.type(), Json::Type::OBJECT);
if (value.type() != Json::Type::OBJECT) continue;
const Json::Object& object = value.object_value();
auto it = object.find("ref");
EXPECT_NE(it, object.end());
if (it == object.end()) continue;
EXPECT_EQ(it->second.type(), Json::Type::OBJECT);
if (it->second.type() != Json::Type::OBJECT) continue;
const Json::Object& ref_object = it->second.object_value();
it = ref_object.find("channelId");
EXPECT_NE(it, ref_object.end());
if (it != ref_object.end()) {
uuids.push_back(atoi(it->second.string_value().c_str()));
}
}
return uuids;
}
void ValidateJsonArraySize(const Json& array, size_t expected) {
if (expected == 0) {
ASSERT_EQ(array.type(), Json::Type::JSON_NULL);
} else {
ASSERT_EQ(array.type(), Json::Type::ARRAY);
EXPECT_EQ(array.array_value().size(), expected);
}
}
void ValidateJsonEnd(const Json& json, bool end) {
auto it = json.object_value().find("end");
if (end) {
ASSERT_NE(it, json.object_value().end());
EXPECT_EQ(it->second.type(), Json::Type::JSON_TRUE);
} else {
ASSERT_EQ(it, json.object_value().end());
}
}
void ValidateGetTopChannels(size_t expected_channels) {
std::string json_str = ChannelzRegistry::GetTopChannels(0);
grpc::testing::ValidateGetTopChannelsResponseProtoJsonTranslation(
json_str.c_str());
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
// This check will naturally have to change when we support pagination.
// tracked: https://github.com/grpc/grpc/issues/16019.
ValidateJsonArraySize((*parsed_json.mutable_object())["channel"],
expected_channels);
ValidateJsonEnd(parsed_json, true);
// 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) {
std::string json_str = ChannelzRegistry::GetServers(0);
grpc::testing::ValidateGetServersResponseProtoJsonTranslation(
json_str.c_str());
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
// This check will naturally have to change when we support pagination.
// tracked: https://github.com/grpc/grpc/issues/16019.
ValidateJsonArraySize((*parsed_json.mutable_object())["server"],
expected_servers);
ValidateJsonEnd(parsed_json, true);
// 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_tracer_event_memory = 0) {
grpc_arg client_a[] = {
grpc_channel_arg_integer_create(
const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENT_MEMORY_PER_NODE),
max_tracer_event_memory),
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_tracer_event_memory = 0) {
grpc_arg server_a[] = {
grpc_channel_arg_integer_create(
const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENT_MEMORY_PER_NODE),
max_tracer_event_memory),
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 ValidateChannelDataArgs {
int64_t calls_started;
int64_t calls_failed;
int64_t calls_succeeded;
};
void ValidateChildInteger(const Json::Object& object, const std::string& key,
int64_t expected) {
auto it = object.find(key);
if (expected == 0) {
ASSERT_EQ(it, object.end());
return;
}
ASSERT_NE(it, object.end());
ASSERT_EQ(it->second.type(), Json::Type::STRING);
int64_t gotten_number = static_cast<int64_t>(
strtol(it->second.string_value().c_str(), nullptr, 0));
EXPECT_EQ(gotten_number, expected);
}
void ValidateCounters(const std::string& json_str,
const ValidateChannelDataArgs& args) {
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(json.type(), Json::Type::OBJECT);
Json::Object* object = json.mutable_object();
Json& data = (*object)["data"];
ASSERT_EQ(data.type(), Json::Type::OBJECT);
ValidateChildInteger(data.object_value(), "callsStarted", args.calls_started);
ValidateChildInteger(data.object_value(), "callsFailed", args.calls_failed);
ValidateChildInteger(data.object_value(), "callsSucceeded",
args.calls_succeeded);
}
void ValidateChannel(ChannelNode* channel,
const ValidateChannelDataArgs& args) {
std::string json_str = channel->RenderJsonString();
grpc::testing::ValidateChannelProtoJsonTranslation(json_str.c_str());
ValidateCounters(json_str, args);
// 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, const ValidateChannelDataArgs& args) {
std::string json_str = server->RenderJsonString();
grpc::testing::ValidateServerProtoJsonTranslation(json_str.c_str());
ValidateCounters(json_str, args);
// also check that the core API formats this the correct way
char* core_api_json_str = grpc_channelz_get_server(server->uuid());
grpc::testing::ValidateGetServerResponseProtoJsonTranslation(
core_api_json_str);
gpr_free(core_api_json_str);
}
gpr_timespec GetLastCallStartedTime(CallCountingHelper* channel) {
CallCountingHelperPeer peer(channel);
return peer.last_call_started_time();
}
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;
// explicitly disable channelz
grpc_arg arg[] = {
grpc_channel_arg_integer_create(
const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENT_MEMORY_PER_NODE),
0),
grpc_channel_arg_integer_create(
const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ), false)};
grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
grpc_channel* channel =
grpc_insecure_channel_create("fake_target", &args, 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, LastCallStartedTime) {
grpc_core::ExecCtx exec_ctx;
CallCountingHelper counter;
// start a call to set the last call started timestamp
counter.RecordCallStarted();
gpr_timespec time1 = GetLastCallStartedTime(&counter);
// time gone by should not affect the timestamp
ChannelzSleep(100);
gpr_timespec time2 = GetLastCallStartedTime(&counter);
EXPECT_EQ(gpr_time_cmp(time1, time2), 0);
// calls succeeded or failed should not affect the timestamp
ChannelzSleep(100);
counter.RecordCallFailed();
counter.RecordCallSucceeded();
gpr_timespec time3 = GetLastCallStartedTime(&counter);
EXPECT_EQ(gpr_time_cmp(time1, time3), 0);
// another call started should affect the timestamp
// sleep for extra long to avoid flakes (since we cache Now())
ChannelzSleep(5000);
counter.RecordCallStarted();
gpr_timespec time4 = GetLastCallStartedTime(&counter);
EXPECT_NE(gpr_time_cmp(time1, time4), 0);
}
class ChannelzRegistryBasedTest : public ::testing::TestWithParam<size_t> {
protected:
// ensure we always have a fresh registry for tests.
void SetUp() override {
ChannelzRegistry::Shutdown();
ChannelzRegistry::Init();
}
void TearDown() override {
ChannelzRegistry::Shutdown();
ChannelzRegistry::Init();
}
};
TEST_F(ChannelzRegistryBasedTest, BasicGetTopChannelsTest) {
grpc_core::ExecCtx exec_ctx;
ChannelFixture channel;
ValidateGetTopChannels(1);
}
TEST_F(ChannelzRegistryBasedTest, NoChannelsTest) {
grpc_core::ExecCtx exec_ctx;
ValidateGetTopChannels(0);
}
TEST_F(ChannelzRegistryBasedTest, ManyChannelsTest) {
grpc_core::ExecCtx exec_ctx;
ChannelFixture channels[10];
(void)channels; // suppress unused variable error
ValidateGetTopChannels(10);
}
TEST_F(ChannelzRegistryBasedTest, GetTopChannelsPagination) {
grpc_core::ExecCtx exec_ctx;
// This is over the pagination limit.
ChannelFixture channels[150];
(void)channels; // suppress unused variable error
std::string json_str = ChannelzRegistry::GetTopChannels(0);
grpc::testing::ValidateGetTopChannelsResponseProtoJsonTranslation(
json_str.c_str());
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
// 100 is the pagination limit.
ValidateJsonArraySize((*parsed_json.mutable_object())["channel"], 100);
ValidateJsonEnd(parsed_json, false);
// Now we get the rest.
json_str = ChannelzRegistry::GetTopChannels(101);
grpc::testing::ValidateGetTopChannelsResponseProtoJsonTranslation(
json_str.c_str());
error = GRPC_ERROR_NONE;
parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
ValidateJsonArraySize((*parsed_json.mutable_object())["channel"], 50);
ValidateJsonEnd(parsed_json, true);
}
TEST_F(ChannelzRegistryBasedTest, GetTopChannelsUuidCheck) {
const intptr_t kNumChannels = 50;
grpc_core::ExecCtx exec_ctx;
ChannelFixture channels[kNumChannels];
(void)channels; // suppress unused variable error
std::string json_str = ChannelzRegistry::GetTopChannels(0);
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
Json& array = (*parsed_json.mutable_object())["channel"];
ValidateJsonArraySize(array, kNumChannels);
std::vector<intptr_t> uuids = GetUuidListFromArray(array.array_value());
for (int i = 0; i < kNumChannels; ++i) {
EXPECT_EQ(i + 1, uuids[i]);
}
}
TEST_F(ChannelzRegistryBasedTest, GetTopChannelsMiddleUuidCheck) {
const intptr_t kNumChannels = 50;
const intptr_t kMidQuery = 40;
grpc_core::ExecCtx exec_ctx;
ChannelFixture channels[kNumChannels];
(void)channels; // suppress unused variable error
// Only query for the end of the channels.
std::string json_str = ChannelzRegistry::GetTopChannels(kMidQuery);
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
Json& array = (*parsed_json.mutable_object())["channel"];
ValidateJsonArraySize(array, kNumChannels - kMidQuery + 1);
std::vector<intptr_t> uuids = GetUuidListFromArray(array.array_value());
for (int i = 0; i < uuids.size(); ++i) {
EXPECT_EQ(static_cast<intptr_t>(kMidQuery + i), uuids[i]);
}
}
TEST_F(ChannelzRegistryBasedTest, GetTopChannelsNoHitUuid) {
grpc_core::ExecCtx exec_ctx;
ChannelFixture pre_channels[40]; // will take uuid[1, 40]
(void)pre_channels; // suppress unused variable error
ServerFixture servers[10]; // will take uuid[41, 50]
(void)servers; // suppress unused variable error
ChannelFixture channels[10]; // will take uuid[51, 60]
(void)channels; // suppress unused variable error
// Query in the middle of the server channels.
std::string json_str = ChannelzRegistry::GetTopChannels(45);
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
Json& array = (*parsed_json.mutable_object())["channel"];
ValidateJsonArraySize(array, 10);
std::vector<intptr_t> uuids = GetUuidListFromArray(array.array_value());
for (int i = 0; i < uuids.size(); ++i) {
EXPECT_EQ(static_cast<intptr_t>(51 + i), uuids[i]);
}
}
TEST_F(ChannelzRegistryBasedTest, GetTopChannelsMoreGaps) {
grpc_core::ExecCtx exec_ctx;
ChannelFixture channel_with_uuid1;
{ ServerFixture channel_with_uuid2; }
ChannelFixture channel_with_uuid3;
{ ServerFixture server_with_uuid4; }
ChannelFixture channel_with_uuid5;
// Current state of list: [1, NULL, 3, NULL, 5]
std::string json_str = ChannelzRegistry::GetTopChannels(2);
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
Json array = (*parsed_json.mutable_object())["channel"];
ValidateJsonArraySize(array, 2);
std::vector<intptr_t> uuids = GetUuidListFromArray(array.array_value());
EXPECT_EQ(static_cast<intptr_t>(3), uuids[0]);
EXPECT_EQ(static_cast<intptr_t>(5), uuids[1]);
json_str = ChannelzRegistry::GetTopChannels(4);
error = GRPC_ERROR_NONE;
parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
array = (*parsed_json.mutable_object())["channel"];
ValidateJsonArraySize(array, 1);
uuids = GetUuidListFromArray(array.array_value());
EXPECT_EQ(static_cast<intptr_t>(5), uuids[0]);
}
TEST_F(ChannelzRegistryBasedTest, GetTopChannelsUuidAfterCompaction) {
const intptr_t kLoopIterations = 50;
grpc_core::ExecCtx exec_ctx;
std::vector<std::unique_ptr<ChannelFixture>> even_channels;
{
// these will delete and unregister themselves after this block.
std::vector<std::unique_ptr<ChannelFixture>> odd_channels;
for (int i = 0; i < kLoopIterations; i++) {
odd_channels.push_back(absl::make_unique<ChannelFixture>());
even_channels.push_back(absl::make_unique<ChannelFixture>());
}
}
std::string json_str = ChannelzRegistry::GetTopChannels(0);
grpc_error* error = GRPC_ERROR_NONE;
Json parsed_json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
ASSERT_EQ(parsed_json.type(), Json::Type::OBJECT);
Json& array = (*parsed_json.mutable_object())["channel"];
ValidateJsonArraySize(array, kLoopIterations);
std::vector<intptr_t> uuids = GetUuidListFromArray(array.array_value());
for (int i = 0; i < kLoopIterations; ++i) {
// only the even uuids will still be present.
EXPECT_EQ((i + 1) * 2, uuids[i]);
}
}
TEST_F(ChannelzRegistryBasedTest, InternalChannelTest) {
grpc_core::ExecCtx exec_ctx;
ChannelFixture channels[10];
(void)channels; // suppress unused variable error
// create an internal channel
grpc_arg client_a[] = {
grpc_channel_arg_integer_create(
const_cast<char*>(GRPC_ARG_CHANNELZ_IS_INTERNAL_CHANNEL), 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);
}
TEST(ChannelzServerTest, BasicServerAPIFunctionality) {
grpc_core::ExecCtx exec_ctx;
ServerFixture server(10);
ServerNode* channelz_server = server.server()->core_server->channelz_node();
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_F(ChannelzRegistryBasedTest, BasicGetServersTest) {
grpc_core::ExecCtx exec_ctx;
ServerFixture server;
ValidateGetServers(1);
}
TEST_F(ChannelzRegistryBasedTest, NoServersTest) {
grpc_core::ExecCtx exec_ctx;
ValidateGetServers(0);
}
TEST_F(ChannelzRegistryBasedTest, ManyServersTest) {
grpc_core::ExecCtx exec_ctx;
ServerFixture servers[10];
(void)servers; // suppress unused variable error
ValidateGetServers(10);
}
INSTANTIATE_TEST_SUITE_P(ChannelzChannelTestSweep, ChannelzChannelTest,
::testing::Values(0, 8, 64, 1024, 1024 * 1024));
} // namespace testing
} // namespace channelz
} // namespace grpc_core
int main(int argc, char** argv) {
grpc::testing::TestEnvironment env(argc, argv);
grpc_init();
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
grpc_shutdown();
return ret;
}