text: /**
  * Copyright (C) Mellanox Technologies Ltd. 2001-2013.  ALL RIGHTS RESERVED.
  * Copyright (C) UT-Battelle, LLC. 2014. ALL RIGHTS RESERVED.
  * See file LICENSE for terms.
  */
  
  #include <common/test.h>
  extern "C" {
  #include <ucs/stats/stats.h>
  }
  
  #include <sys/socket.h>
  #include <netinet/in.h>
  
  #if ENABLE_STATS
  #define NUM_DATA_NODES 20
  
  class stats_test : public ucs::test {
  public:
  
      stats_test() {
          size_t size = sizeof(ucs_stats_class_t) +
                        NUM_COUNTERS * sizeof(m_data_stats_class->counter_names[0]);
          m_data_stats_class                   = (ucs_stats_class_t*)malloc(size);
          m_data_stats_class->name             = "data";
          m_data_stats_class->num_counters     = NUM_COUNTERS;
          m_data_stats_class->counter_names[0] = "counter0";
          m_data_stats_class->counter_names[1] = "counter1";
          m_data_stats_class->counter_names[2] = "counter2";
          m_data_stats_class->counter_names[3] = "counter3";
      }
  
      ~stats_test() {
          free(m_data_stats_class);
      }
  
      virtual void init() {
          ucs::test::init();
          ucs_stats_cleanup();
          push_config();
          modify_config("STATS_DEST",    stats_dest_config().c_str());
          modify_config("STATS_TRIGGER", stats_trigger_config().c_str());
          ucs_stats_init();
          ASSERT_TRUE(ucs_stats_is_active());
      }
  
      virtual void cleanup() {
          ucs_stats_cleanup();
          pop_config();
          ucs_stats_init();
          ucs::test::cleanup();
      }
  
      virtual std::string stats_dest_config()    = 0;
      virtual std::string stats_trigger_config() = 0;
  
      void prepare_nodes(ucs_stats_node_t **cat_node,
                         ucs_stats_node_t *data_nodes[NUM_DATA_NODES]) {
          static ucs_stats_class_t category_stats_class = {
              "category", 0
          };
  
          ucs_status_t status = UCS_STATS_NODE_ALLOC(cat_node,
                                                     &category_stats_class,
                                                     ucs_stats_get_root());
          ASSERT_UCS_OK(status);
          for (unsigned i = 0; i < NUM_DATA_NODES; ++i) {
              status = UCS_STATS_NODE_ALLOC(&data_nodes[i], m_data_stats_class,
                                            *cat_node, "-%d", i);
              ASSERT_UCS_OK(status);
  
              UCS_STATS_UPDATE_COUNTER(data_nodes[i], 0, 10);
              UCS_STATS_UPDATE_COUNTER(data_nodes[i], 1, 20);
              UCS_STATS_UPDATE_COUNTER(data_nodes[i], 2, 30);
              UCS_STATS_UPDATE_COUNTER(data_nodes[i], 3, 40);
          }
  
          /* make sure our original node is ok */
          check_cat_node(*cat_node, data_nodes);
      }
  
      void free_nodes(ucs_stats_node_t *cat_node,
                      ucs_stats_node_t *data_nodes[NUM_DATA_NODES]) {
          for (unsigned i = 0; i < NUM_DATA_NODES; ++i) {
              UCS_STATS_NODE_FREE(data_nodes[i]);
          }
          UCS_STATS_NODE_FREE(cat_node);
      }
  
      void check_tree(ucs_stats_node_t *root,
                      ucs_stats_node_t *data_nodes[NUM_DATA_NODES]) {
          EXPECT_EQ(1ul, ucs_list_length(&root->children[UCS_STATS_ACTIVE_CHILDREN]));
          check_cat_node(ucs_list_head(&root->children[UCS_STATS_ACTIVE_CHILDREN],
                                       ucs_stats_node_t, list), data_nodes);
      }
  
      void check_cat_node(ucs_stats_node_t *cat_node,
                          ucs_stats_node_t *data_nodes[NUM_DATA_NODES]) {
          EXPECT_EQ(std::string("category"), std::string(cat_node->cls->name));
          EXPECT_EQ((unsigned)0, cat_node->cls->num_counters);
  
          ucs_stats_node_t *data_node;
          ucs_list_for_each(data_node, &cat_node->children[UCS_STATS_ACTIVE_CHILDREN], list) {
              EXPECT_EQ(std::string("data"),     std::string(data_node->cls->name));
              EXPECT_EQ(unsigned(NUM_COUNTERS),  data_node->cls->num_counters);
              EXPECT_EQ(std::string("counter0"), std::string(data_node->cls->counter_names[0]));
  
              EXPECT_EQ((unsigned)10, data_node->counters[0]);
              EXPECT_EQ((unsigned)20, data_node->counters[1]);
              EXPECT_EQ((unsigned)30, data_node->counters[2]);
              EXPECT_EQ((unsigned)40, data_node->counters[3]);
          }
      }
  
  protected:    
      static const unsigned NUM_COUNTERS   = 4;
  
      ucs_stats_class_t *m_data_stats_class;
  };
  
  class stats_udp_test : public stats_test {
  public:
      virtual void init() {
          ucs_status_t status = ucs_stats_server_start(0, &m_server);
          ASSERT_UCS_OK(status);
          stats_test::init();
      }
  
      virtual void cleanup() {
          stats_test::cleanup();
          ucs_stats_server_destroy(m_server);
      }
  
      void wait_for_stats() {
          do {
              usleep(1000 * ucs::test_time_multiplier());
          } while (ucs_stats_server_rcvd_packets(m_server) == 0);
      }
  
      virtual std::string stats_dest_config() {
          int port = ucs_stats_server_get_port(m_server);
          EXPECT_GT(port, 0);
          return "udp:localhost:" + ucs::to_string(port);
      }
  
      virtual std::string stats_trigger_config() {
          return "timer:0.1s";
      }
  
      void read_and_check_stats(ucs_stats_node_t *data_nodes[NUM_DATA_NODES]) {
          ucs_list_link_t *list = ucs_stats_server_get_stats(m_server);
          ASSERT_EQ(1ul, ucs_list_length(list));
          check_tree(ucs_list_head(list, ucs_stats_node_t, list), data_nodes);
          ucs_stats_server_purge_stats(m_server);
      }
  
  protected:
      ucs_stats_server_h m_server;
  };
  
  class stats_file_test : public stats_test {
  public:
      stats_file_test() {
          m_pipefds[0] = -1;
          m_pipefds[1] = -1;
      }
  
      virtual void init() {
          /* Note: this test assumes data <64k, o/w stats dump will block forever */
          int ret = pipe(m_pipefds);
          ASSERT_EQ(0, ret);
          stats_test::init();
      }
  
      void close_pipes()
      {
          close(m_pipefds[0]);
          close(m_pipefds[1]);
          m_pipefds[0] = -1;
          m_pipefds[1] = -1;
      }
  
      virtual void cleanup() {
          stats_test::cleanup();
          close_pipes();
      }
  
      virtual std::string stats_dest_config() {
          return "file:/dev/fd/" + ucs::to_string(m_pipefds[1]) + ":bin";
      }
  
      std::string get_data() {
          std::string data(65536, '\0');
          ssize_t ret = read(m_pipefds[0], &data[0], data.size());
          EXPECT_GE(ret, 0);
          data.resize(ret);
          return data;
      }
  
      virtual std::string stats_trigger_config() {
          return "";
      }
  
  protected:
      int m_pipefds[2];
  };
  
  class stats_on_demand_test : public stats_udp_test {
  public:
      virtual std::string stats_trigger_config() {
          return "";
      }
  };
  
  class stats_on_signal_test : public stats_udp_test {
  public:
      virtual std::string stats_trigger_config() {
          return "signal:USR1";
      }
  };
  
  class stats_on_exit_test : public stats_file_test {
  public:
      virtual std::string stats_dest_config() {
          return "file:/dev/fd/" + ucs::to_string(m_pipefds[1]);
      }
  
      /*
       * we check the dump-on-exit in cleanup method .
       */
      virtual void cleanup() {
          stats_test::cleanup();
          std::string data = get_data();
          size_t pos = 0;
          for (unsigned i = 0; i < NUM_DATA_NODES; ++i) {
              std::string node_name = " data-" + ucs::to_string(i) + ":";
              pos = data.find(node_name, pos);
              EXPECT_NE(pos, std::string::npos) << node_name << " not found";
              for (unsigned j = 0; j < NUM_COUNTERS; ++j) {
                  std::string value = "counter" +
                                  ucs::to_string(j) +
                                  ": " +
                                  ucs::to_string((j + 1) * 10);
                  pos = data.find(value, pos);
                  EXPECT_NE(pos, std::string::npos) << value << " not found";
              }
          }
          close_pipes();
      }
  
      virtual std::string stats_trigger_config() {
          return "exit";
      }
  };
  
  UCS_TEST_F(stats_on_demand_test, null_root) {
      ucs_stats_node_t       *cat_node;
  
      static ucs_stats_class_t category_stats_class = {
          "category", 0
      };
      ucs_status_t status = UCS_STATS_NODE_ALLOC(&cat_node, &category_stats_class,
                                                 NULL);
  
      EXPECT_GE(status, UCS_ERR_INVALID_PARAM);
  }
  
  UCS_TEST_F(stats_udp_test, report) {
      ucs_stats_node_t       *cat_node;
      ucs_stats_node_t       *data_nodes[NUM_DATA_NODES] = {NULL};
  
      prepare_nodes(&cat_node, data_nodes);
      wait_for_stats();
      read_and_check_stats(data_nodes);
      free_nodes(cat_node, data_nodes);
  }
  
  UCS_TEST_F(stats_file_test, report) {
      ucs_stats_node_t       *cat_node;
      ucs_stats_node_t       *data_nodes[NUM_DATA_NODES] = {NULL};
  
      prepare_nodes(&cat_node, data_nodes);
      ucs_stats_dump();
      free_nodes(cat_node, data_nodes);
  
      std::string data = get_data();
      FILE *f = fmemopen(&data[0], data.size(), "rb");
      ucs_stats_node_t *root;
      ucs_status_t status = ucs_stats_deserialize(f, &root);
      ASSERT_UCS_OK(status);
      fclose(f);
  
      check_tree(root, data_nodes);
      ucs_stats_free(root);
  }
  
  UCS_TEST_F(stats_on_demand_test, report) {
      ucs_stats_node_t       *cat_node;
      ucs_stats_node_t       *data_nodes[NUM_DATA_NODES] = {NULL};
  
      prepare_nodes(&cat_node, data_nodes);
      ucs_stats_dump();
      wait_for_stats();
      read_and_check_stats(data_nodes);
      free_nodes(cat_node, data_nodes);
  }
  
  UCS_TEST_F(stats_on_signal_test, report) {
      ucs_stats_node_t       *cat_node;
      ucs_stats_node_t       *data_nodes[NUM_DATA_NODES] = {NULL};
  
      prepare_nodes(&cat_node, data_nodes);
      kill(getpid(), SIGUSR1);
      wait_for_stats();
      read_and_check_stats(data_nodes);
      free_nodes(cat_node, data_nodes);
  }
  
  UCS_TEST_F(stats_on_exit_test, dump) {
      ucs_stats_node_t       *cat_node;
      ucs_stats_node_t       *data_nodes[NUM_DATA_NODES] = {NULL};
  
      prepare_nodes(&cat_node, data_nodes);
      free_nodes(cat_node, data_nodes);
  }
  
  UCS_MT_TEST_F(stats_file_test, mt_add_remove, 10) {
      ucs_stats_node_t       *cat_node;
      ucs_stats_node_t       *data_nodes[NUM_DATA_NODES] = {NULL};
      unsigned i;
  
      for (i = 0; i < 100; i++) {
          prepare_nodes(&cat_node, data_nodes);
          free_nodes(cat_node, data_nodes);
      }
  }
  
  #endif
  