| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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 <gen_cpp/RuntimeProfile_types.h> |
| #include <gtest/gtest.h> |
| |
| #include <atomic> |
| #include <cstdlib> |
| #include <thread> |
| |
| #include "runtime/runtime_profile.h" |
| |
| namespace doris { |
| class RuntimeProfileProfileLevelTest : public testing::Test { |
| public: |
| RuntimeProfileProfileLevelTest() = default; |
| ~RuntimeProfileProfileLevelTest() override = default; |
| }; |
| |
| TEST_F(RuntimeProfileProfileLevelTest, EmptyProfile) { |
| RuntimeProfile profile("test"); |
| TRuntimeProfileTree tprofile; |
| // Empty RuntimeProfile it self will have a ROOT_COUNTER. |
| profile.to_thrift(&tprofile, 0); |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| const TRuntimeProfileNode& tnode = tprofile.nodes[0]; |
| ASSERT_EQ(tnode.counters.size(), 0); |
| ASSERT_EQ(tnode.child_counters_map.size(), 0); |
| |
| tprofile.nodes.clear(); |
| profile.to_thrift(&tprofile, 1); |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| const TRuntimeProfileNode& tnode1 = tprofile.nodes[0]; |
| ASSERT_EQ(tnode1.counters.size(), 0); |
| ASSERT_EQ(tnode1.child_counters_map.size(), 0); |
| } |
| TEST_F(RuntimeProfileProfileLevelTest, BasicTestNoChildCounter) { |
| /* |
| "" |
| counter01-level-0 |
| counter02-level-0 |
| counter11-level-1 |
| counter12-level-1 |
| counter21-default-levle-2 |
| counter22-default-levle-2 |
| */ |
| // Create RuntimeProfile with different counter with different level |
| RuntimeProfile profile("test"); |
| [[maybe_unused]] auto* counter01 = profile.add_counter("counter01-level-0", TUnit::UNIT, "", 0); |
| [[maybe_unused]] auto* counter02 = profile.add_counter("counter02-level-0", TUnit::UNIT, "", 0); |
| |
| [[maybe_unused]] auto* counter11 = profile.add_counter("counter11-level-1", TUnit::UNIT, "", 1); |
| [[maybe_unused]] auto* counter12 = profile.add_counter("counter12-level-1", TUnit::UNIT, "", 1); |
| |
| [[maybe_unused]] auto* counter21 = |
| profile.add_counter("counter21-default-levle-2", TUnit::UNIT); |
| [[maybe_unused]] auto* counter22 = |
| profile.add_counter("counter22-default-levle-2", TUnit::UNIT); |
| |
| // Call to_thrift will different profile level. |
| TRuntimeProfileTree tprofile; |
| profile.to_thrift(&tprofile, 0); |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| TRuntimeProfileNode& tnode = tprofile.nodes[0]; |
| ASSERT_EQ(tnode.counters.size(), 2); |
| ASSERT_EQ(tnode.child_counters_map.size(), 1); |
| auto entry = tnode.child_counters_map.begin(); |
| ASSERT_TRUE(entry->first == RuntimeProfile::ROOT_COUNTER); |
| // After prune, counters with level 1 and 2 should be removed. |
| ASSERT_EQ(entry->second.size(), 2); |
| for (const auto& counter : tnode.counters) { |
| ASSERT_TRUE(entry->second.contains(counter.name)); |
| } |
| for (const auto& child_counter_name : entry->second) { |
| ASSERT_TRUE(child_counter_name == "counter01-level-0" || |
| child_counter_name == "counter02-level-0"); |
| } |
| |
| tprofile.nodes.clear(); |
| profile.to_thrift(&tprofile, 1); |
| |
| // No child RuntimeProfile, so we just have one node. |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| tnode = tprofile.nodes[0]; |
| // counter21-default-levle-2 |
| // counter22-default-levle-2 |
| // should be removed. |
| ASSERT_EQ(tnode.counters.size(), 4); |
| // Only one parent counter, so child_counter_map should have only one entry. |
| ASSERT_EQ(tnode.child_counters_map.size(), 1); |
| entry = tnode.child_counters_map.begin(); |
| // ROOT_COUNTER is the only parent counter. |
| ASSERT_TRUE(entry->first == RuntimeProfile::ROOT_COUNTER); |
| // ROOT_COUNTER has four children. |
| ASSERT_EQ(entry->second.size(), 4); |
| |
| for (const auto& counter : tnode.counters) { |
| ASSERT_TRUE(entry->second.contains(counter.name)); |
| } |
| for (const auto& child_counter_name : entry->second) { |
| ASSERT_TRUE(child_counter_name == "counter01-level-0" || |
| child_counter_name == "counter02-level-0" || |
| child_counter_name == "counter11-level-1" || |
| child_counter_name == "counter12-level-1"); |
| } |
| |
| tprofile.nodes.clear(); |
| profile.to_thrift(&tprofile, 2); |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| const TRuntimeProfileNode& tnode2 = tprofile.nodes[0]; |
| ASSERT_EQ(tnode2.counters.size(), 6); |
| ASSERT_EQ(tnode2.child_counters_map.size(), 1); |
| const auto entry2 = tnode2.child_counters_map.begin(); |
| ASSERT_TRUE(entry2->first == RuntimeProfile::ROOT_COUNTER); |
| ASSERT_EQ(entry2->second.size(), 6); |
| for (const auto& counter : tnode2.counters) { |
| ASSERT_TRUE(entry2->second.contains(counter.name)); |
| } |
| for (const auto& child_counter_name : entry2->second) { |
| ASSERT_TRUE(child_counter_name == "counter01-level-0" || |
| child_counter_name == "counter02-level-0" || |
| child_counter_name == "counter11-level-1" || |
| child_counter_name == "counter12-level-1" || |
| child_counter_name == "counter21-default-levle-2" || |
| child_counter_name == "counter22-default-levle-2"); |
| } |
| |
| tprofile.nodes.clear(); |
| profile.to_thrift(&tprofile, 3); |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| const TRuntimeProfileNode& tnode3 = tprofile.nodes[0]; |
| ASSERT_EQ(tnode3.counters.size(), 6); |
| ASSERT_EQ(tnode3.child_counters_map.size(), 1); |
| const auto entry3 = tnode3.child_counters_map.begin(); |
| ASSERT_TRUE(entry3->first == RuntimeProfile::ROOT_COUNTER); |
| ASSERT_EQ(entry3->second.size(), 6); |
| for (const auto& counter : tnode3.counters) { |
| ASSERT_TRUE(entry3->second.contains(counter.name)); |
| } |
| |
| for (const auto& child_counter_name : entry3->second) { |
| ASSERT_TRUE(child_counter_name == "counter01-level-0" || |
| child_counter_name == "counter02-level-0" || |
| child_counter_name == "counter11-level-1" || |
| child_counter_name == "counter12-level-1" || |
| child_counter_name == "counter21-default-levle-2" || |
| child_counter_name == "counter22-default-levle-2"); |
| } |
| } |
| |
| TEST_F(RuntimeProfileProfileLevelTest, BasicTestWithChildCounter) { |
| /* |
| "" |
| counter01 |
| counter01_child01 |
| counter01_child02 |
| counter02 |
| counter02_child01 |
| */ |
| // Create RuntimeProfile with different counter with different level |
| // They must have other counter as its parent counter. |
| RuntimeProfile profile("test"); |
| [[maybe_unused]] auto* counter01 = |
| profile.add_counter("counter01", TUnit::UNIT, RuntimeProfile::ROOT_COUNTER, 0); |
| [[maybe_unused]] auto* counter02 = |
| profile.add_counter("counter02", TUnit::UNIT, RuntimeProfile::ROOT_COUNTER, 0); |
| |
| [[maybe_unused]] auto* counter01_child01 = |
| profile.add_counter("counter01_child01", TUnit::UNIT, "counter01", 0); |
| [[maybe_unused]] auto* counter01_child02 = |
| profile.add_counter("counter01_child02", TUnit::UNIT, "counter01", 0); |
| [[maybe_unused]] auto* counter02_child01 = |
| profile.add_counter("counter02_child01", TUnit::UNIT, "counter02", 0); |
| |
| TRuntimeProfileTree tprofile; |
| profile.to_thrift(&tprofile, 0); |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| TRuntimeProfileNode& tnode = tprofile.nodes[0]; |
| ASSERT_EQ(tnode.name, "test"); |
| ASSERT_EQ(tnode.counters.size(), 5); |
| ASSERT_EQ(tnode.child_counters_map.size(), 3); |
| |
| std::set<std::string> root_counter_children = |
| tnode.child_counters_map[RuntimeProfile::ROOT_COUNTER]; |
| ASSERT_EQ(root_counter_children.size(), 2); |
| |
| std::set<std::string> child01_children = tnode.child_counters_map["counter01"]; |
| ASSERT_EQ(child01_children.size(), 2); |
| |
| std::set<std::string> child02_children = tnode.child_counters_map["counter02"]; |
| ASSERT_EQ(child02_children.size(), 1); |
| |
| [[maybe_unused]] auto* counter01_child11 = |
| profile.add_counter("counter01_child11", TUnit::UNIT, "counter01", 1); |
| [[maybe_unused]] auto* counter01_child12 = |
| profile.add_counter("counter01_child12", TUnit::UNIT, "counter01", 1); |
| [[maybe_unused]] auto* counter01_child13 = |
| profile.add_counter("counter01_child13", TUnit::UNIT, "counter02", 1); |
| |
| tprofile.nodes.clear(); |
| profile.to_thrift(&tprofile, 1); |
| ASSERT_EQ(tprofile.nodes.size(), 1); |
| const TRuntimeProfileNode& tnode1 = tprofile.nodes[0]; |
| ASSERT_EQ(tnode1.counters.size(), 8); |
| ASSERT_EQ(tnode1.child_counters_map.size(), 3); |
| root_counter_children = tnode.child_counters_map[RuntimeProfile::ROOT_COUNTER]; |
| ASSERT_EQ(root_counter_children.size(), 2); |
| |
| child01_children = tnode.child_counters_map["counter01"]; |
| ASSERT_EQ(child01_children.size(), 4); |
| |
| child02_children = tnode.child_counters_map["counter02"]; |
| ASSERT_EQ(child02_children.size(), 2); |
| } |
| |
| TEST_F(RuntimeProfileProfileLevelTest, ConcurrentTest) { |
| RuntimeProfile profile("ConcurrentTest"); |
| std::atomic_bool stop {false}; |
| |
| std::thread add_counter_thread([&profile, &stop]() { |
| int idx = 0; |
| while (!stop) { |
| profile.add_counter("counter" + std::to_string(idx), TUnit::UNIT, "", 1); |
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| idx++; |
| } |
| }); |
| |
| std::thread to_thrift_thread([&profile, &stop]() { |
| while (!stop) { |
| TRuntimeProfileTree tprofile; |
| profile.to_thrift(&tprofile, 2); |
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| } |
| }); |
| |
| std::this_thread::sleep_for(std::chrono::seconds(1)); |
| |
| stop = true; |
| |
| add_counter_thread.join(); |
| to_thrift_thread.join(); |
| } |
| |
| // Verify that pretty_print() respects profile_level parameter for flat counters. |
| TEST_F(RuntimeProfileProfileLevelTest, PrettyPrintLevelFilteringFlat) { |
| RuntimeProfile profile("test"); |
| auto* counter_l0 = profile.add_counter("CounterLevel0", TUnit::UNIT, "", 0); |
| auto* counter_l1 = profile.add_counter("CounterLevel1", TUnit::UNIT, "", 1); |
| auto* counter_l2 = profile.add_counter("CounterLevel2", TUnit::UNIT, "", 2); |
| counter_l0->set(int64_t(100)); |
| counter_l1->set(int64_t(200)); |
| counter_l2->set(int64_t(300)); |
| |
| // Level 0: only level-0 counters should appear |
| { |
| std::stringstream ss; |
| profile.pretty_print(&ss, "", 0); |
| std::string output = ss.str(); |
| EXPECT_TRUE(output.find("CounterLevel0") != std::string::npos) |
| << "Level-0 counter should appear at profile_level=0"; |
| EXPECT_TRUE(output.find("CounterLevel1") == std::string::npos) |
| << "Level-1 counter should NOT appear at profile_level=0"; |
| EXPECT_TRUE(output.find("CounterLevel2") == std::string::npos) |
| << "Level-2 counter should NOT appear at profile_level=0"; |
| } |
| |
| // Level 1: level-0 and level-1 counters should appear |
| { |
| std::stringstream ss; |
| profile.pretty_print(&ss, "", 1); |
| std::string output = ss.str(); |
| EXPECT_TRUE(output.find("CounterLevel0") != std::string::npos); |
| EXPECT_TRUE(output.find("CounterLevel1") != std::string::npos); |
| EXPECT_TRUE(output.find("CounterLevel2") == std::string::npos) |
| << "Level-2 counter should NOT appear at profile_level=1"; |
| } |
| |
| // Level 2: all counters should appear |
| { |
| std::stringstream ss; |
| profile.pretty_print(&ss, "", 2); |
| std::string output = ss.str(); |
| EXPECT_TRUE(output.find("CounterLevel0") != std::string::npos); |
| EXPECT_TRUE(output.find("CounterLevel1") != std::string::npos); |
| EXPECT_TRUE(output.find("CounterLevel2") != std::string::npos); |
| } |
| } |
| |
| // Verify that pretty_print() respects profile_level for nested (parent-child) counters. |
| TEST_F(RuntimeProfileProfileLevelTest, PrettyPrintLevelFilteringNested) { |
| /* |
| * Tree structure: |
| * ROOT_COUNTER |
| * parent_l0 (level 0) |
| * child_l0 (level 0) |
| * child_l1 (level 1) |
| * child_l2 (level 2) |
| */ |
| RuntimeProfile profile("nested_test"); |
| profile.add_counter("parent_l0", TUnit::UNIT, RuntimeProfile::ROOT_COUNTER, 0); |
| profile.add_counter("child_l0", TUnit::UNIT, "parent_l0", 0); |
| profile.add_counter("child_l1", TUnit::UNIT, "parent_l0", 1); |
| profile.add_counter("child_l2", TUnit::UNIT, "parent_l0", 2); |
| |
| // Level 0: parent_l0 and child_l0 should appear, child_l1/l2 should be pruned |
| { |
| std::stringstream ss; |
| profile.pretty_print(&ss, "", 0); |
| std::string output = ss.str(); |
| EXPECT_TRUE(output.find("parent_l0") != std::string::npos); |
| EXPECT_TRUE(output.find("child_l0") != std::string::npos); |
| EXPECT_TRUE(output.find("child_l1") == std::string::npos); |
| EXPECT_TRUE(output.find("child_l2") == std::string::npos); |
| } |
| |
| // Level 1: parent_l0, child_l0, child_l1 should appear |
| { |
| std::stringstream ss; |
| profile.pretty_print(&ss, "", 1); |
| std::string output = ss.str(); |
| EXPECT_TRUE(output.find("parent_l0") != std::string::npos); |
| EXPECT_TRUE(output.find("child_l0") != std::string::npos); |
| EXPECT_TRUE(output.find("child_l1") != std::string::npos); |
| EXPECT_TRUE(output.find("child_l2") == std::string::npos); |
| } |
| } |
| |
| // Verify pretty_print indentation: ROOT_COUNTER's direct children should be at |
| // the same prefix level, and grandchildren should be indented by 2 more spaces. |
| TEST_F(RuntimeProfileProfileLevelTest, PrettyPrintIndentation) { |
| RuntimeProfile profile("indent_test"); |
| profile.add_counter("top_counter", TUnit::UNIT, RuntimeProfile::ROOT_COUNTER, 0); |
| profile.add_counter("sub_counter", TUnit::UNIT, "top_counter", 0); |
| |
| std::stringstream ss; |
| profile.pretty_print(&ss, "PFX", 2); |
| std::string output = ss.str(); |
| |
| // ROOT_COUNTER's direct children should have prefix "PFX - " |
| // (PFX + " - " from Counter::pretty_print) |
| EXPECT_TRUE(output.find("PFX - top_counter:") != std::string::npos) |
| << "Top-level counter should be at prefix level. Output:\n" |
| << output; |
| |
| // Child of top_counter should have prefix "PFX " + " - " = "PFX - " |
| EXPECT_TRUE(output.find("PFX - sub_counter:") != std::string::npos) |
| << "Sub-counter should be indented by 2 more spaces. Output:\n" |
| << output; |
| } |
| |
| } // namespace doris |