text: /*
   * The MIT License (MIT)
   *
   * Copyright (c) 2015 Microsoft Corporation
   *
   * -=- Robust Distributed System Nucleus (rDSN) -=-
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  /*
   * Description:
   *     Unit-test for perf_counters.
   *
   * Revision history:
   *     Nov., 2015, @qinzuoyan (Zuoyan Qin), first version
   *     xxxx-xx-xx, author, fix bug about xxx
   */
  
  #include <dsn/perf_counter/perf_counters.h>
  #include <dsn/perf_counter/perf_counter_wrapper.h>
  #include <dsn/perf_counter/perf_counter_utils.h>
  #include <gtest/gtest.h>
  
  using namespace ::dsn;
  
  TEST(perf_counters_test, counter_create_remove)
  {
      perf_counter_ptr p;
  
      p = perf_counters::instance().get_global_counter(
          "app", "test", "number_counter", COUNTER_TYPE_NUMBER, "", false);
      ASSERT_EQ(nullptr, p);
      p = perf_counters::instance().get_global_counter(
          "app", "test", "number_counter", COUNTER_TYPE_NUMBER, "", true);
      ASSERT_NE(nullptr, p);
      p = perf_counters::instance().get_global_counter(
          "app", "test", "number_counter", COUNTER_TYPE_NUMBER, "", false);
      ASSERT_NE(nullptr, p);
  
      p = perf_counters::instance().get_global_counter(
          "app", "test", "volatile_number_counter", COUNTER_TYPE_VOLATILE_NUMBER, "", false);
      ASSERT_EQ(nullptr, p);
      p = perf_counters::instance().get_global_counter(
          "app", "test", "volatile_number_counter", COUNTER_TYPE_VOLATILE_NUMBER, "", true);
      ASSERT_NE(nullptr, p);
      p = perf_counters::instance().get_global_counter(
          "app", "test", "volatile_number_counter", COUNTER_TYPE_VOLATILE_NUMBER, "", false);
      ASSERT_NE(nullptr, p);
  
      p = perf_counters::instance().get_global_counter(
          "app", "test", "rate_counter", COUNTER_TYPE_RATE, "", false);
      ASSERT_EQ(nullptr, p);
      p = perf_counters::instance().get_global_counter(
          "app", "test", "rate_counter", COUNTER_TYPE_RATE, "", true);
      ASSERT_NE(nullptr, p);
      p = perf_counters::instance().get_global_counter(
          "app", "test", "rate_counter", COUNTER_TYPE_RATE, "", false);
      ASSERT_NE(nullptr, p);
  
      ASSERT_FALSE(perf_counters::instance().remove_counter("number_counter"));
      ASSERT_FALSE(perf_counters::instance().remove_counter("unexist_counter"));
  
      ASSERT_TRUE(perf_counters::instance().remove_counter("app*test*number_counter"));
      ASSERT_TRUE(perf_counters::instance().remove_counter("app*test*number_counter"));
      ASSERT_FALSE(perf_counters::instance().remove_counter("app*test*number_counter"));
      p = perf_counters::instance().get_global_counter(
          "app", "test", "number_counter", COUNTER_TYPE_NUMBER, "", false);
      ASSERT_EQ(nullptr, p);
  
      ASSERT_TRUE(perf_counters::instance().remove_counter("app*test*volatile_number_counter"));
      ASSERT_TRUE(perf_counters::instance().remove_counter("app*test*volatile_number_counter"));
      ASSERT_FALSE(perf_counters::instance().remove_counter("app*test*volatile_number_counter"));
      p = perf_counters::instance().get_global_counter(
          "app", "test", "volatile_number_counter", COUNTER_TYPE_VOLATILE_NUMBER, "", false);
      ASSERT_EQ(nullptr, p);
  
      ASSERT_TRUE(perf_counters::instance().remove_counter("app*test*rate_counter"));
      ASSERT_TRUE(perf_counters::instance().remove_counter("app*test*rate_counter"));
      ASSERT_FALSE(perf_counters::instance().remove_counter("app*test*rate_counter"));
      p = perf_counters::instance().get_global_counter(
          "app", "test", "rate_counter", COUNTER_TYPE_RATE, "", false);
      ASSERT_EQ(nullptr, p);
  
      p = perf_counters::instance().get_global_counter(
          "app", "test", "unexist_counter", COUNTER_TYPE_NUMBER, "", false);
      ASSERT_EQ(nullptr, p);
      ASSERT_FALSE(perf_counters::instance().remove_counter("app*test*unexist_counter"));
  }
  
  template <typename K, typename V>
  bool check_map_contains(const std::map<K, V> &super, const std::map<K, V> &sub)
  {
      for (const auto &kv : sub) {
          auto it = super.find(kv.first);
          if (it == super.end()) {
              return false;
          }
          if (it->second != kv.second) {
              return false;
          }
      }
      return true;
  }
  
  TEST(perf_counters_test, snapshot)
  {
      std::map<std::string, dsn_perf_counter_type_t> expected;
  
      std::map<std::string, dsn_perf_counter_type_t> counter_keys;
      perf_counters::instance().take_snapshot();
      perf_counters::snapshot_iterator iter =
          [&counter_keys](const perf_counters::counter_snapshot &cs) mutable {
              counter_keys.emplace(cs.name, cs.type);
          };
  
      counter_keys.clear();
      expected = {
          {"replica*server*memused.virt(MB)", COUNTER_TYPE_NUMBER},
          {"replica*server*memused.res(MB)", COUNTER_TYPE_NUMBER},
      };
      perf_counters::instance().iterate_snapshot(iter);
      // in the beginning, builtin counters are in counter_list
      ASSERT_TRUE(check_map_contains(counter_keys, expected));
  
      dsn::perf_counter_wrapper c1;
      c1.init_global_counter("a", "s", "test_counter", COUNTER_TYPE_NUMBER, "");
      dsn::perf_counter_wrapper c2;
      c2.init_global_counter("a", "s", "test_counter", COUNTER_TYPE_NUMBER, "");
  
      dsn::perf_counter_wrapper c3;
      c3.init_global_counter("b", "s", "test_counter", COUNTER_TYPE_VOLATILE_NUMBER, "");
      dsn::perf_counter_wrapper c4;
      c4.init_global_counter("b", "s", "test_counter", COUNTER_TYPE_VOLATILE_NUMBER, "");
  
      // snapshot will contain new counters
      perf_counters::instance().take_snapshot();
      counter_keys.clear();
      expected = {
          {"replica*server*memused.virt(MB)", COUNTER_TYPE_NUMBER},
          {"replica*server*memused.res(MB)", COUNTER_TYPE_NUMBER},
          {"a*s*test_counter", COUNTER_TYPE_NUMBER},
          {"b*s*test_counter", COUNTER_TYPE_VOLATILE_NUMBER},
      };
      perf_counters::instance().iterate_snapshot(iter);
      ASSERT_TRUE(check_map_contains(counter_keys, expected));
  
      dsn::perf_counter_wrapper c5;
      c5.init_global_counter("c", "s", "test_counter", COUNTER_TYPE_RATE, "");
      dsn::perf_counter_wrapper c6;
      c6.init_global_counter("c", "s", "test_counter", COUNTER_TYPE_RATE, "");
  
      dsn::perf_counter_wrapper c7;
      c7.init_global_counter("d", "s", "test_counter", COUNTER_TYPE_NUMBER_PERCENTILES, "");
      dsn::perf_counter_wrapper c8;
      c8.init_global_counter("d", "s", "test_counter", COUNTER_TYPE_NUMBER_PERCENTILES, "");
  
      // new counters won't be contained in snapshot if you don't call "take snapshot"
      counter_keys.clear();
      expected = {
          {"replica*server*memused.virt(MB)", COUNTER_TYPE_NUMBER},
          {"replica*server*memused.res(MB)", COUNTER_TYPE_NUMBER},
          {"a*s*test_counter", COUNTER_TYPE_NUMBER},
          {"b*s*test_counter", COUNTER_TYPE_VOLATILE_NUMBER},
      };
      perf_counters::instance().iterate_snapshot(iter);
      ASSERT_TRUE(check_map_contains(counter_keys, expected));
      ASSERT_TRUE(counter_keys.find("c*s*test_counter") == counter_keys.end());
      ASSERT_TRUE(counter_keys.find("d*s*test_counter") == counter_keys.end());
  
      // after taking snapshot, new counters will be contained
      counter_keys.clear();
      expected = {
          {"replica*server*memused.virt(MB)", COUNTER_TYPE_NUMBER},
          {"replica*server*memused.res(MB)", COUNTER_TYPE_NUMBER},
          {"a*s*test_counter", COUNTER_TYPE_NUMBER},
          {"b*s*test_counter", COUNTER_TYPE_VOLATILE_NUMBER},
          {"c*s*test_counter", COUNTER_TYPE_RATE},
          {"d*s*test_counter", COUNTER_TYPE_NUMBER_PERCENTILES},
      };
      perf_counters::instance().take_snapshot();
      perf_counters::instance().iterate_snapshot(iter);
      ASSERT_TRUE(check_map_contains(counter_keys, expected));
  
      c1.clear();
      c2.clear();
      c3.clear();
      c4.clear();
  
      // although remove counters, but snapshot won't been affected if you don't call take snapshot
      counter_keys.clear();
      perf_counters::instance().iterate_snapshot(iter);
      expected = {
          {"replica*server*memused.virt(MB)", COUNTER_TYPE_NUMBER},
          {"replica*server*memused.res(MB)", COUNTER_TYPE_NUMBER},
          {"a*s*test_counter", COUNTER_TYPE_NUMBER},
          {"b*s*test_counter", COUNTER_TYPE_VOLATILE_NUMBER},
          {"c*s*test_counter", COUNTER_TYPE_RATE},
          {"d*s*test_counter", COUNTER_TYPE_NUMBER_PERCENTILES},
      };
      ASSERT_TRUE(check_map_contains(counter_keys, expected));
  
      // after take snapshot, removed counters will be removed in snapshot
      perf_counters::instance().take_snapshot();
      counter_keys.clear();
      perf_counters::instance().iterate_snapshot(iter);
      expected = {
          {"replica*server*memused.virt(MB)", COUNTER_TYPE_NUMBER},
          {"replica*server*memused.res(MB)", COUNTER_TYPE_NUMBER},
          {"c*s*test_counter", COUNTER_TYPE_RATE},
          {"d*s*test_counter", COUNTER_TYPE_NUMBER_PERCENTILES},
      };
      ASSERT_TRUE(check_map_contains(counter_keys, expected));
      ASSERT_TRUE(counter_keys.find("a*s*test_counter") == counter_keys.end());
      ASSERT_TRUE(counter_keys.find("b*s*test_counter") == counter_keys.end());
  
      // query snapshot
      std::vector<std::string> target_keys = {
          "a*s*test_counter", "c*s*test_counter", "b*s*test_counter", "d*s*test_counter"};
      expected = {
          {"c*s*test_counter", COUNTER_TYPE_RATE},
          {"d*s*test_counter", COUNTER_TYPE_NUMBER_PERCENTILES},
      };
  
      counter_keys.clear();
      perf_counters::instance().query_snapshot(target_keys, iter, nullptr);
      ASSERT_EQ(2, counter_keys.size());
      ASSERT_EQ(expected, counter_keys);
  
      counter_keys.clear();
      std::vector<bool> found;
      perf_counters::instance().query_snapshot(target_keys, iter, &found);
      ASSERT_EQ(4, found.size());
      std::vector<bool> expected_found = {false, true, false, true};
      ASSERT_EQ(expected_found, found);
      ASSERT_EQ(expected, counter_keys);
  }
  
  TEST(perf_counters_test, query_snapshot_by_regexp)
  {
      dsn::perf_counter_wrapper c1;
      c1.init_global_counter("a", "s", "test_counter", COUNTER_TYPE_NUMBER, "");
      dsn::perf_counter_wrapper c2;
      c2.init_global_counter("a", "s", "test_counter", COUNTER_TYPE_NUMBER, "");
  
      dsn::perf_counter_wrapper c3;
      c3.init_global_counter("b", "s", "test_counter", COUNTER_TYPE_VOLATILE_NUMBER, "");
      dsn::perf_counter_wrapper c4;
      c4.init_global_counter("b", "s", "test_counter", COUNTER_TYPE_VOLATILE_NUMBER, "");
  
      dsn::perf_counter_wrapper c5;
      c5.init_global_counter("c", "s", "test_counter", COUNTER_TYPE_RATE, "");
      dsn::perf_counter_wrapper c6;
      c6.init_global_counter("c", "s", "test_counter", COUNTER_TYPE_RATE, "");
  
      dsn::perf_counter_wrapper c7;
      c7.init_global_counter("d", "s", "test_counter", COUNTER_TYPE_NUMBER_PERCENTILES, "");
      dsn::perf_counter_wrapper c8;
      c8.init_global_counter("d", "s", "test_counter", COUNTER_TYPE_NUMBER_PERCENTILES, "");
  
      perf_counters::instance().take_snapshot();
      std::string result = perf_counters::instance().list_snapshot_by_regexp({".*\\*s\\*.*"});
  
      dsn::perf_counter_info info;
      dsn::json::json_forwarder<dsn::perf_counter_info>::decode(
          dsn::blob(result.c_str(), 0, result.size()), info);
      ASSERT_STREQ("OK", info.result.c_str());
      ASSERT_GT(info.timestamp, 0);
      ASSERT_TRUE(!info.timestamp_str.empty());
      printf("got timestamp: %s\n", info.timestamp_str.c_str());
      ASSERT_EQ(4 + 1, info.counters.size()); // add 1 for p999 counter
  
      std::map<std::string, std::string> expected = {
          {"a*s*test_counter", dsn_counter_type_to_string(COUNTER_TYPE_NUMBER)},
          {"b*s*test_counter", dsn_counter_type_to_string(COUNTER_TYPE_VOLATILE_NUMBER)},
          {"c*s*test_counter", dsn_counter_type_to_string(COUNTER_TYPE_RATE)},
          {"d*s*test_counter", dsn_counter_type_to_string(COUNTER_TYPE_NUMBER_PERCENTILES)},
          {"d*s*test_counter.p999", dsn_counter_type_to_string(COUNTER_TYPE_NUMBER_PERCENTILES)},
      };
      std::map<std::string, std::string> actual;
      for (const dsn::perf_counter_metric &m : info.counters) {
          actual.emplace(m.name, m.type);
      }
      ASSERT_EQ(expected, actual);
  
      result = perf_counters::instance().list_snapshot_by_regexp({"hahaha"});
      dsn::json::json_forwarder<dsn::perf_counter_info>::decode(
          dsn::blob(result.c_str(), 0, result.size()), info);
      ASSERT_STREQ("OK", info.result.c_str());
      ASSERT_GT(info.timestamp, 0);
      ASSERT_TRUE(!info.timestamp_str.empty());
      printf("got timestamp: %s\n", info.timestamp_str.c_str());
      ASSERT_TRUE(info.counters.empty());
  
      result = perf_counters::instance().list_snapshot_by_regexp({""});
      dsn::json::json_forwarder<dsn::perf_counter_info>::decode(
          dsn::blob(result.c_str(), 0, result.size()), info);
      ASSERT_STREQ("OK", info.result.c_str());
      ASSERT_GT(info.timestamp, 0);
      ASSERT_TRUE(!info.timestamp_str.empty());
      printf("got timestamp: %s\n", info.timestamp_str.c_str());
      ASSERT_TRUE(info.counters.empty());
  }
  
  TEST(perf_counters_test, get_by_fullname)
  {
      struct test_case
      {
          const char *app;
          const char *section;
          const char *name;
          dsn_perf_counter_type_t type;
          const char *dsptr;
          bool create;
      } tests[] = {{"replica", "eon", "get_by_fullname1", COUNTER_TYPE_NUMBER, "pf1", false},
                   {"replica", "eon", "get_by_fullname2", COUNTER_TYPE_NUMBER, "pf2", true}};
  
      for (auto test : tests) {
          // precondition: make sure the perf counter doesn't exist
          std::string perf_counter_name;
          perf_counter::build_full_name(test.app, test.section, test.name, perf_counter_name);
          perf_counters::instance().remove_counter(perf_counter_name.c_str());
  
          if (test.create) {
              // create perf counter
              perf_counter_wrapper counter;
              counter.init_global_counter(test.app, test.section, test.name, test.type, test.dsptr);
              ASSERT_NE(nullptr, perf_counters::instance().get_counter(perf_counter_name));
          } else {
              ASSERT_EQ(nullptr, perf_counters::instance().get_counter(perf_counter_name));
          }
      }
  }
  