text: //  Copyright (c) 2007-2021 Hartmut Kaiser
  //
  //  SPDX-License-Identifier: BSL-1.0
  //  Distributed under the Boost Software License, Version 1.0. (See accompanying
  //  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  
  #include <hpx/config.hpp>
  #include <hpx/assert.hpp>
  #include <hpx/async_base/launch_policy.hpp>
  #include <hpx/async_combinators/wait_all.hpp>
  #include <hpx/async_distributed/continuation.hpp>
  #include <hpx/components_base/agas_interface.hpp>
  #include <hpx/functional/bind_front.hpp>
  #include <hpx/modules/format.hpp>
  #include <hpx/performance_counters/apex_sample_value.hpp>
  #include <hpx/performance_counters/counters.hpp>
  #include <hpx/performance_counters/performance_counter.hpp>
  #include <hpx/performance_counters/query_counters.hpp>
  #include <hpx/runtime_local/config_entry.hpp>
  #include <hpx/runtime_local/get_thread_name.hpp>
  #include <hpx/thread_support/unlock_guard.hpp>
  #include <hpx/threading_base/external_timer.hpp>
  #include <hpx/threading_base/thread_helpers.hpp>
  #include <hpx/timing/high_resolution_clock.hpp>
  #include <hpx/type_support/unused.hpp>
  
  #include <cstddef>
  #include <cstdint>
  #include <fstream>
  #include <iostream>
  #include <mutex>
  #include <sstream>
  #include <string>
  #include <utility>
  #include <vector>
  
  #if HPX_HAVE_ITTNOTIFY != 0 && !defined(HPX_HAVE_APEX)
  #include <ittnotify.h>
  #include <map>
  #endif
  
  namespace hpx { namespace util {
  
      query_counters::query_counters(std::vector<std::string> const& names,
          std::vector<std::string> const& reset_names, std::int64_t interval,
          std::string const& dest, std::string const& form,
          std::vector<std::string> const& shortnames, bool csv_header,
          bool print_counters_locally, bool counter_types)
        : names_(names)
        , reset_names_(reset_names)
        , counters_(print_counters_locally)
        , destination_(dest)
        , format_(form)
        , counter_shortnames_(shortnames)
        , csv_header_(csv_header)
        , print_counters_locally_(print_counters_locally)
        , counter_types_(counter_types)
        , timer_(hpx::bind_front(&query_counters::evaluate, this_(), false),
              hpx::bind_front(&query_counters::terminate, this_()),
              interval * 1000, "query_counters", true)
      {
          // add counter prefix, if necessary
          for (std::string& name : names_)
          {
              performance_counters::ensure_counter_prefix(name);
          }
          for (std::string& name : reset_names_)
          {
              performance_counters::ensure_counter_prefix(name);
          }
      }
  
      query_counters::~query_counters()
      {
          counters_.release();
      }
  
      void query_counters::find_counters()
      {
          if (!names_.empty())
              counters_.add_counters(names_);
          if (!reset_names_.empty())
              counters_.add_counters(reset_names_, true);
  
  #if HPX_HAVE_ITTNOTIFY != 0 && !defined(HPX_HAVE_APEX)
          if (use_ittnotify_api)
          {
              typedef std::map<std::string, util::itt::counter>::value_type
                  value_type;
  
              for (auto const& info : counters_.get_counter_infos())
              {
                  std::string real_name =
                      performance_counters::remove_counter_prefix(info.fullname_);
                  itt_counters_.insert(value_type(info.fullname_,
                      util::itt::counter(real_name.c_str(),
                          hpx::get_thread_name().c_str(),
                          __itt_metadata_double)));
              }
          }
  #endif
      }
  
      void query_counters::start()
      {
          if (print_counters_locally_ && destination_ != "cout")
              destination_ += "." + std::to_string(hpx::get_locality_id());
  
          find_counters();
  
          counters_.start(launch::sync);
  
          // this will invoke the evaluate function for the first time
          timer_.start();
      }
  
      void query_counters::stop_evaluating_counters(bool terminate)
      {
          timer_.stop(terminate);
          counters_.stop(launch::sync);
      }
  
      ///////////////////////////////////////////////////////////////////////////
      namespace strings {
  
          char const* const counter_type_short_names[] = {
              "counter_type::text",
              "counter_type::raw",
              "counter_type::monotonically_increasing",
              "counter_type::average_base",
              "counter_type::average_count",
              "counter_type::aggregated",
              "counter_type::average_timer",
              "counter_type::elapsed_time",
              "counter_type::histogram",
              "counter_type::raw_values",
          };
      }
  
      char const* get_counter_short_type_name(
          performance_counters::counter_type type)
      {
          if (type < performance_counters::counter_type::text ||
              type > performance_counters::counter_type::raw_values)
          {
              return "unknown";
          }
          return strings::counter_type_short_names[static_cast<int>(type)];
      }
  
      template <typename Stream>
      void query_counters::print_name_csv(Stream& out, std::string const& name)
      {
          std::string s = performance_counters::remove_counter_prefix(name);
          if (s.find_first_of(',') != std::string::npos)
              out << "\"" << s << "\"";
          else
              out << s;
      }
  
      template <typename Stream>
      void query_counters::print_value(Stream* out,
          performance_counters::counter_info const& info,
          performance_counters::counter_value const& value)
      {
          std::string const& name = info.fullname_;
          std::string const& uom = info.unit_of_measure_;
  
          error_code ec(throwmode::lightweight);    // do not throw
          double val = value.get_value<double>(ec);
  
          if (!ec)
          {
  #ifdef HPX_HAVE_APEX
              external_timer::sample_value(info, val);
  #elif HPX_HAVE_ITTNOTIFY != 0
              if (use_ittnotify_api)
              {
                  auto it = itt_counters_.find(name);
                  if (it != itt_counters_.end())
                  {
                      (*it).second.set_value(val);
                  }
              }
  #endif
  
              if (out == nullptr)
                  return;
  
              print_name_csv(*out, name);
              *out << "," << value.count_ << ",";
  
              double elapsed = static_cast<double>(value.time_) * 1e-9;
              *out << hpx::util::format("{:.6}", elapsed) << ",[s]," << val;
              if (!uom.empty())
                  *out << ",[" << uom << "]";
  
              if (counter_types_)
              {
                  if (uom.empty())
                      *out << ",[]";
                  *out << "," << get_counter_short_type_name(info.type_);
              }
              *out << "\n";
          }
          else
          {
              if (out != nullptr)
                  *out << "invalid\n";
          }
      }
  
      template <typename Stream>
      void query_counters::print_value(Stream* out,
          performance_counters::counter_info const& info,
          performance_counters::counter_values_array const& value)
      {
          if (out == nullptr)
              return;
  
          std::string const& name = info.fullname_;
          std::string const& uom = info.unit_of_measure_;
  
          error_code ec(throwmode::lightweight);    // do not throw
  
          print_name_csv(*out, name);
          *out << "," << value.count_ << ",";
  
          double elapsed = static_cast<double>(value.time_) * 1e-9;
          *out << hpx::util::format("{:.6}", elapsed) << ",[s],";
  
          bool first = true;
          for (std::int64_t val : value.values_)
          {
              if (!first)
                  *out << ':';
              first = false;
              *out << val;
          }
  
          if (!uom.empty())
              *out << ",[" << uom << "]";
  
          if (counter_types_)
          {
              if (uom.empty())
                  *out << ",[]";
              *out << "," << get_counter_short_type_name(info.type_);
          }
          *out << "\n";
      }
  
      template <typename Stream>
      void query_counters::print_value_csv(Stream* out,
          performance_counters::counter_info const& info,
          performance_counters::counter_value const& value)
      {
          error_code ec(throwmode::lightweight);
          double val = value.get_value<double>(ec);
  
          if (!ec)
          {
  #ifdef HPX_HAVE_APEX
              external_timer::sample_value(info, val);
  #elif HPX_HAVE_ITTNOTIFY != 0
              if (use_ittnotify_api)
              {
                  auto it = itt_counters_.find(info.fullname_);
                  if (it != itt_counters_.end())
                  {
                      (*it).second.set_value(val);
                  }
              }
  #else
              HPX_UNUSED(info);
  #endif
              if (out == nullptr)
                  return;
  
              *out << val;
          }
          else
          {
              if (out != nullptr)
                  *out << "invalid";
          }
      }
  
      template <typename Stream>
      void query_counters::print_value_csv(Stream* out,
          performance_counters::counter_info const& /* info */,
          performance_counters::counter_values_array const& value)
      {
          if (out == nullptr)
              return;
  
          bool first = true;
          for (std::int64_t val : value.values_)
          {
              if (!first)
                  *out << ':';
              first = false;
              *out << val;
          }
      }
  
      template <typename Stream>
      void query_counters::print_name_csv_short(
          Stream& out, std::string const& name)
      {
          out << name;
      }
  
      template <typename Stream>
      void query_counters::print_headers(Stream& output,
          std::vector<performance_counters::counter_info> const& infos)
      {
          if (csv_header_)
          {
              if (format_ == "csv")
              {
                  // first print raw value counters
                  bool first = true;
                  for (std::size_t i = 0; i != infos.size(); ++i)
                  {
                      using namespace performance_counters;
                      if (infos[i].type_ != counter_type::raw &&
                          infos[i].type_ !=
                              counter_type::monotonically_increasing &&
                          infos[i].type_ != counter_type::aggregating &&
                          infos[i].type_ != counter_type::elapsed_time &&
                          infos[i].type_ != counter_type::average_count &&
                          infos[i].type_ != counter_type::average_timer)
                      {
                          continue;
                      }
                      if (!first)
                          output << ",";
                      first = false;
                      print_name_csv(output, infos[i].fullname_);
                  }
  
                  // now print array value counters
                  for (std::size_t i = 0; i != infos.size(); ++i)
                  {
                      if (infos[i].type_ !=
                              performance_counters::counter_type::histogram &&
                          infos[i].type_ !=
                              performance_counters::counter_type::raw_values)
                      {
                          continue;
                      }
  
                      if (!first)
                          output << ",";
                      first = false;
                      print_name_csv(output, infos[i].fullname_);
                  }
  
                  output << "\n";
              }
              else if (format_ == "csv-short")
              {
                  // first print raw value counters
                  bool first = true;
                  for (std::size_t i = 0; i != counter_shortnames_.size(); ++i)
                  {
                      using namespace performance_counters;
                      if (infos[i].type_ != counter_type::raw &&
                          infos[i].type_ !=
                              counter_type::monotonically_increasing &&
                          infos[i].type_ != counter_type::aggregating &&
                          infos[i].type_ != counter_type::elapsed_time &&
                          infos[i].type_ != counter_type::average_count &&
                          infos[i].type_ != counter_type::average_timer)
                      {
                          continue;
                      }
                      if (!first)
                          output << ",";
                      first = false;
                      print_name_csv_short(output, counter_shortnames_[i]);
                  }
  
                  // now print array value counters
                  for (std::size_t i = 0; i != counter_shortnames_.size(); ++i)
                  {
                      if (infos[i].type_ !=
                              performance_counters::counter_type::histogram &&
                          infos[i].type_ !=
                              performance_counters::counter_type::raw_values)
                      {
                          continue;
                      }
  
                      if (!first)
                          output << ",";
                      first = false;
                      print_name_csv_short(output, counter_shortnames_[i]);
                  }
  
                  output << "\n";
              }
              csv_header_ = false;
          }
      }
  
      template <typename Stream, typename Value>
      void query_counters::print_values(Stream* output,
          std::vector<Value>&& values, std::vector<std::size_t>&& indices,
          std::vector<performance_counters::counter_info> const& infos)
      {
          if (format_ == "csv" || format_ == "csv-short")
          {
              bool first = true;
              for (std::size_t i = 0; i != values.size(); ++i)
              {
                  if (!first && output != nullptr)
                      *output << ",";
                  first = false;
                  print_value_csv(output, infos[i], values[i]);
              }
              if (output != nullptr)
                  *output << "\n";
          }
          else
          {
              std::size_t idx = 0;
              for (std::size_t i : indices)
              {
                  print_value(output, infos[i], values[idx]);
                  ++idx;
              }
          }
      }
  
      ///////////////////////////////////////////////////////////////////////////
      bool query_counters::evaluate(bool force)
      {
          bool reset = false;
          if (get_config_entry("hpx.print_counter.reset", "0") == "1")
              reset = true;
  
          return evaluate_counters(reset, nullptr, force);
      }
  
      void query_counters::terminate() {}
  
      ///////////////////////////////////////////////////////////////////////////
      void query_counters::start_counters(error_code& ec)
      {
          if (counters_.size() == 0)
          {
              // start has not been called yet
              HPX_THROWS_IF(ec, invalid_status, "query_counters::start_counters",
                  "The counters to be evaluated have not been initialized yet");
              return;
          }
  
          // Start the performance counters.
          counters_.start(launch::sync, ec);
      }
  
      void query_counters::stop_counters(error_code& ec)
      {
          if (counters_.size() == 0)
          {
              // start has not been called yet
              HPX_THROWS_IF(ec, invalid_status, "query_counters::stop_counters",
                  "The counters to be evaluated have not been initialized yet");
              return;
          }
  
          // Stop the performance counters.
          counters_.stop(launch::sync, ec);
      }
  
      void query_counters::reset_counters(error_code& ec)
      {
          if (counters_.size() == 0)
          {
              // start has not been called yet
              HPX_THROWS_IF(ec, invalid_status, "query_counters::reset_counters",
                  "The counters to be evaluated have not been initialized yet");
              return;
          }
  
          // Reset the performance counters.
          counters_.reset(launch::sync, ec);
      }
  
      void query_counters::reinit_counters(bool reset, error_code& ec)
      {
          if (counters_.size() == 0)
          {
              // start has not been called yet
              HPX_THROWS_IF(ec, invalid_status, "query_counters::reinit_counters",
                  "The counters to be evaluated have not been initialized yet");
              return;
          }
  
          // Reset the performance counters.
          counters_.reinit(launch::sync, reset, ec);
      }
  
      ///////////////////////////////////////////////////////////////////////////
      bool query_counters::print_raw_counters(bool destination_is_cout,
          bool reset, bool no_output, char const* description,
          std::vector<performance_counters::counter_info> const& infos,
          error_code& ec)
      {
          // Query the performance counters.
          std::vector<std::size_t> indices;
          indices.reserve(infos.size());
  
          for (std::size_t i = 0; i != infos.size(); ++i)
          {
              if (infos[i].type_ ==
                      performance_counters::counter_type::histogram ||
                  infos[i].type_ ==
                      performance_counters::counter_type::raw_values)
              {
                  continue;
              }
  
              indices.push_back(i);
          }
  
          if (indices.empty())
              return false;
  
          std::ostringstream output;
          if (description && !no_output)
              output << description << std::endl;
  
          std::vector<performance_counters::counter_value> values =
              counters_.get_counter_values(launch::sync, reset, ec);
  
          HPX_ASSERT(values.size() == indices.size());
  
          // Output the performance counter value.
          if (!no_output)
              print_headers(output, infos);
          print_values(no_output ? nullptr : &output, HPX_MOVE(values),
              HPX_MOVE(indices), infos);
  
          if (!no_output)
          {
              if (destination_is_cout)
              {
                  std::cout << output.str() << std::flush;
              }
              else
              {
                  std::ofstream out(destination_.c_str(), std::ofstream::app);
                  out << output.str();
              }
          }
          return true;
      }
  
      ///////////////////////////////////////////////////////////////////////////
      bool query_counters::print_array_counters(bool destination_is_cout,
          bool reset, bool no_output, char const* description,
          std::vector<performance_counters::counter_info> const& infos,
          error_code& ec)
      {
          // Query the performance counters.
          std::vector<std::size_t> indices;
          indices.reserve(infos.size());
  
          for (std::size_t i = 0; i != infos.size(); ++i)
          {
              if (infos[i].type_ !=
                      performance_counters::counter_type::histogram &&
                  infos[i].type_ !=
                      performance_counters::counter_type::raw_values)
              {
                  continue;
              }
  
              indices.push_back(i);
          }
  
          if (indices.empty())
              return false;
  
          std::ostringstream output;
          if (description && !no_output)
              output << description << std::endl;
  
          std::vector<performance_counters::counter_values_array> values =
              counters_.get_counter_values_array(launch::sync, reset, ec);
  
          HPX_ASSERT(values.size() == indices.size());
  
          // Output the performance counter value.
          if (!no_output)
              print_headers(output, infos);
          print_values(no_output ? nullptr : &output, HPX_MOVE(values),
              HPX_MOVE(indices), infos);
  
          if (!no_output)
          {
              if (destination_is_cout)
              {
                  std::cout << output.str() << std::flush;
              }
              else
              {
                  std::ofstream out(destination_.c_str(), std::ofstream::app);
                  out << output.str();
              }
          }
          return true;
      }
  
      bool query_counters::evaluate_counters(
          bool reset, char const* description, bool force, error_code& ec)
      {
          if (!force && timer_.is_terminated())
          {
              // just do nothing as we're about to terminate the application
              return false;
          }
  
          bool destination_is_cout = false;
          bool no_output = false;
  
          {
              std::lock_guard<mutex_type> l(mtx_);
              destination_is_cout = destination_ == "cout";
              no_output = destination_ == "none";
          }
  
  #if HPX_HAVE_ITTNOTIFY != 0 && !defined(HPX_HAVE_APEX)
          // don't generate any console-output if the ITTNotify API is used
          if (!no_output && destination_is_cout && use_ittnotify_api)
              no_output = true;
  #endif
  
          if (counters_.size() == 0)
          {
              // start has not been called yet
              HPX_THROWS_IF(ec, invalid_status, "query_counters::evaluate",
                  "The counters to be evaluated have not been initialized yet");
              return false;
          }
  
          bool result = false;
          std::vector<performance_counters::counter_info> infos =
              counters_.get_counter_infos();
  
          result = print_raw_counters(
              destination_is_cout, reset, no_output, description, infos, ec);
          if (ec)
              return false;
  
          result = print_array_counters(destination_is_cout, reset, no_output,
                       description, infos, ec) ||
              result;
          if (ec)
              return false;
  
          if (&ec != &throws)
              ec = make_success_code();
  
          return result;
      }
  }}    // namespace hpx::util
  