diff --git a/docs/manual/existing_performance_counters.qbk b/docs/manual/existing_performance_counters.qbk index 514029b06842..e2f4a13956b6 100644 --- a/docs/manual/existing_performance_counters.qbk +++ b/docs/manual/existing_performance_counters.qbk @@ -1575,6 +1575,46 @@ system and application performance. ] ] +[/////////////////////////////////////////////////////////////////////////////] +[table for Generic Histogram Performance Counter + [[Counter Type] [Counter Instance Formatting] [Description] [Parameters]] + [ [`Histogram-Performance-Counter`] + [`locality#*/total` + + where:[br] `*` is the locality id of the locality the average time + between parcels for the given action should be queried for. The + locality id is a (zero based) number identifying the locality.] + [Returns a histogram representing the data collected for the + base counter which has been queried. + + This counter returns an array of values, where the first three values + represent the three parameters used for the histogram followed by + one value for each of the histogram buckets. + + The first unit of measure displayed for this counter (`[ns]`) refers to + the lower and upper boundary values in the returned histogram data only. + The second unit of measure displayed (`[0.1%]`) refers to the actual + histogram data. + + For each bucket the counter shows a value between `0` and `1000`, + which corresponds to a percentage value between `0%` and `100%`. + ] + [The action type and optional histogram parameters. The action type is + the string which has been used while registering the action with + __hpx__, e.g. which has been passed as the second parameter to the macro + [macroref HPX_REGISTER_ACTION `HPX_REGISTER_ACTION`] or + [macroref HPX_REGISTER_ACTION_ID `HPX_REGISTER_ACTION_ID`]. + + The action type may be followed by a comma separated list of up-to + three numbers: the lower and upper boundaries for the collected + histogram, and the number of buckets for the histogram to generate. + By default these three numbers will be assumed to be `0` (`[ns]`, + lower bound), `1000000` (`[ns]`, upper bound), and `20` (number of + buckets to generate). + ] + ] +] + [/////////////////////////////////////////////////////////////////////////////] [table Performance Counters for Elementary Arithmetic Operations [[Counter Type] [Counter Instance Formatting] [Description] [Parameters]] diff --git a/hpx/performance_counters/counters_fwd.hpp b/hpx/performance_counters/counters_fwd.hpp index 73e3a30f68b1..01b6353a06ef 100644 --- a/hpx/performance_counters/counters_fwd.hpp +++ b/hpx/performance_counters/counters_fwd.hpp @@ -553,6 +553,11 @@ namespace hpx { namespace performance_counters // be registered with the counter types. HPX_EXPORT naming::gid_type arithmetics_counter_extended_creator( counter_info const&, error_code&); + + // Creation function for histogram performance counters; to + // be registered with the counter types. + HPX_EXPORT naming::gid_type get_histogram_counter_creator( + counter_info const&, error_code&); // Creation function for uptime counters. HPX_EXPORT naming::gid_type uptime_counter_creator( @@ -583,6 +588,11 @@ namespace hpx { namespace performance_counters counter_info const& info, std::vector const& base_counter_names, error_code& ec = throws); + + // \brief Create a new histogram performance counter instance + HPX_EXPORT naming::gid_type get_histogram_counter( + std::string const& name, std::int64_t min_boundary, + std::int64_t max_boundary, std::int64_t num_buckets); // \brief Create a new performance counter instance based on given // counter info diff --git a/hpx/performance_counters/registry.hpp b/hpx/performance_counters/registry.hpp index f38a414cfe17..b7f421153b44 100644 --- a/hpx/performance_counters/registry.hpp +++ b/hpx/performance_counters/registry.hpp @@ -41,6 +41,39 @@ namespace hpx { namespace performance_counters public: registry() {} + + typedef util::function_nonser(bool)> + get_histogram_values_type; + typedef util::function_nonser< + void(std::int64_t, std::int64_t, std::int64_t, + get_histogram_values_type&) + > get_histogram_creator_type; + + typedef std::unordered_map< + std::string, counter_functions, hpx::util::jenkins_hash + > map_type; + + static registry& instance(); + + void register_action(std::string const& name, + get_histogram_creator_type + histogram_counter_creator); + + struct counter_functions + { + get_histogram_creator_type histogram_counter_creator; + std::int64_t min_boundary, max_boundary, num_buckets; + }; + /// \brief Create a new histogram performance counter instance + get_histogram_creator_type get_histogram_counter( + std::string const& name, std::int64_t min_boundary, + std::int64_t max_boundary, naming::gid_type& id, std::int64_t num_buckets); + + static std::vector empty_histogram(bool) + { + std::vector result = { 0, 0, 1, 0 }; + return result; + } /// \brief Add a new performance counter type to the (local) registry counter_status add_counter_type(counter_info const& info, @@ -165,6 +198,15 @@ namespace hpx { namespace performance_counters private: counter_type_map_type countertypes_; + + struct tag {}; + + friend struct hpx::util::static_< + registry, tag + >; + + mutable mutex_type mtx_; + map_type map_; }; }} diff --git a/hpx/performance_counters/server/histogram_performance_counters.hpp b/hpx/performance_counters/server/histogram_performance_counters.hpp new file mode 100644 index 000000000000..0d02ad20c11e --- /dev/null +++ b/hpx/performance_counters/server/histogram_performance_counters.hpp @@ -0,0 +1,121 @@ +// Copyright (c) 2018 Surya Priy +// +// 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) + +#if !defined(HPX_PERFORMANCE_COUNTERS_SERVER_HISTOGRAM_PERFORMANCE_COUNTER) +#define HPX_PERFORMANCE_COUNTERS_SERVER_HISTOGRAM_PERFORMANCE_COUNTER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +/////////////////////////////////////////////////////////////////////////////// +namespace hpx { namespace performance_counters { namespace server +{ + + /////////////////////////////////////////////////////////////////////////// + // This counter exposes the histogram for counters processed during the + // given base time interval. The counter relies on querying a steadily + // growing counter value. + class histogram_performance_counter + { + typedef hpx::lcos::local::spinlock mutex_type; + + // avoid warnings about using this in member initializer list + histogram_performance_counter* this_() { return this; } + + public: + histogram_counter() {} + typedef histogram_performance_counter type_holder; + typedef base_performance_counter base_type_holder; + + std::vector + get_histogram(bool reset); + void get_histogram_creator( + std::int64_t min_boundary, std::int64_t max_boundary, + std::int64_t num_buckets, + util::function_nonser(bool)>& result); + + // register the given action + static void register_action(char const* action, error_code& ec); + + get_histogram_values_type get_histogram_counter( + std::string const& name, std::int64_t min_boundary, + std::int64_t max_boundary, naming::gid_type& gid, + std::int64_t num_buckets); + + histogram_performance_counter(counter_info const& info, + std::string const& base_counter_name, + std::int64_t min_boundary, std::int64_t max_boundary, + std::int64_t num_buckets, bool reset_base_counter); + + bool start(); + + bool stop(); + + void reset_counter_value(); + + void on_terminate() {} + + /// \brief finalize() will be called just before the instance gets + /// destructed + void finalize() + { + base_performance_counter::finalize(); + base_type::finalize(); + } + + protected: + bool evaluate_base_counter(counter_value& value); + bool evaluate(); + bool ensure_base_counter(); + + private: + typedef lcos::local::spinlock mutex_type; + mutable mutex_type mtx_; + + hpx::util::interval_timer timer_; ///< base time interval in milliseconds + std::string base_counter_name_; ///< name of base counter to be queried + naming::id_type base_counter_id_; + + boost::scoped_ptr value_; + counter_value prev_value_; + bool has_prev_value_; + + bool reset_base_counter_; + + typedef boost::accumulators::accumulator_set< + double, // collects percentiles + boost::accumulators::features + > histogram_collector_type; + + std::unique_ptr data_histogram; + std::int64_t histogram_min_boundary_; + std::int64_t histogram_max_boundary_; + std::int64_t histogram_num_buckets_; + + // base counters to be queried + performance_counter_set counters_; + + }; +}}} + +#endif diff --git a/src/performance_counters/counters.cpp b/src/performance_counters/counters.cpp index ea8db7661085..4f15734a3b68 100644 --- a/src/performance_counters/counters.cpp +++ b/src/performance_counters/counters.cpp @@ -827,6 +827,20 @@ namespace hpx { namespace performance_counters gid, ec); return gid; } + + // \brief Create a new aggregating histogram performance counter instance + // based on given base counter name and given base time interval + // (milliseconds). + naming::gid_type get_histogram_counter( + std::string const& name, std::int64_t min_boundary, + std::int64_t max_boundary, std::int64_t num_buckets) + { + naming::gid_type gid; + get_runtime().get_counter_registry(). + get_histogram_counter(name, min_boundary, + max_boundary, gid, num_buckets); + return gid; + } /////////////////////////////////////////////////////////////////////// counter_status add_counter(naming::id_type const& id, diff --git a/src/performance_counters/registry.cpp b/src/performance_counters/registry.cpp index 4c2725d28f0a..9551c8573e78 100644 --- a/src/performance_counters/registry.cpp +++ b/src/performance_counters/registry.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include /////////////////////////////////////////////////////////////////////////////// namespace hpx { namespace performance_counters @@ -55,6 +57,104 @@ namespace hpx { namespace performance_counters } return it; } + + registry& registry::instance() + { + hpx::util::static_ registry; + return registry.get(); + } + + void registry::register_action( + std::string const& name, + get_histogram_creator_type histogram_counter_creator) + { + if (name.empty()) + { + HPX_THROW_EXCEPTION(bad_parameter, + "registry::register_action", + "Cannot register an action with an empty name"); + } + + std::lock_guard l(mtx_); + + auto it = map_.find(name); + if (it == map_.end()) + { + counter_functions data = + { + histogram_counter_creator, + 0, 0, 1 + }; + + map_.emplace(name, std::move(data)); + } + else + { + (*it).second.histogram_counter_creator = + histogram_counter_creator; + + if ((*it).second.min_boundary != (*it).second.max_boundary) + { + // instantiate actual histogram collection + registry::get_histogram_values_type result; + histogram_counter_creator( + (*it).second.min_boundary, (*it).second.max_boundary, + (*it).second.num_buckets, result); + } + } + } + + void registry::register_action(std::string const& name) + { + if (name.empty()) + { + HPX_THROW_EXCEPTION(bad_parameter, + "registry::register_action", + "Cannot register an action with an empty name"); + } + + std::lock_guard l(mtx_); + + auto it = map_.find(name); + if (it == map_.end()) + { + map_.emplace(name, counter_functions()); + } + } + + registry::get_histogram_values_type + registry::get_histogram_counter( + std::string const& name, std::int64_t min_boundary, + std::int64_t max_boundary, naming::gid_type& gid, + std::int64_t num_buckets) + { + std::unique_lock l(mtx_); + + map_type::iterator it = map_.find(name); + if (it == map_.end()) + { + l.unlock(); + HPX_THROW_EXCEPTION(bad_parameter, + "registry::" + "get_histogram_counter", + "unknown action type"); + return ®istry::empty_histogram; + } + + if ((*it).second.histogram_counter_creator.empty()) + { + // no parcel of this type has been sent yet + (*it).second.min_boundary = min_boundary; + (*it).second.max_boundary = max_boundary; + (*it).second.num_buckets = num_buckets; + return registry::get_histogram_values_type(); + } + + registry::get_histogram_values_type result; + (*it).second.histogram_counter_creator( + min_boundary, max_boundary, num_buckets, result); + return result; + } registry::counter_type_map_type::const_iterator registry::locate_counter_type(std::string const& type_name) const diff --git a/src/performance_counters/server/histogram_performance_counter.cpp b/src/performance_counters/server/histogram_performance_counter.cpp new file mode 100644 index 000000000000..1a42004c1510 --- /dev/null +++ b/src/performance_counters/server/histogram_performance_counter.cpp @@ -0,0 +1,445 @@ +// Copyright (c) 2018 Surya Priy +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(HPX_MSVC) +# pragma warning(push) +# pragma warning(disable: 4244) +#endif +#include +#if defined(HPX_MSVC) +# pragma warning(pop) +#endif + +#include + +#define BOOST_SPIRIT_USE_PHOENIX_V3 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +namespace hpx { namespace performance_counters { namespace server +{ + + histogram_performance_counter::histogram_performance_counter( + counter_info const& info, std::string const& base_counter_name, + std::int64_t histogram_min_boundary, std::int64_t histogram_max_boundary, + std::int64_t histogram_num_buckets, bool reset_base_counter) + : base_type_holder(info), + timer_(util::bind(&histogram_performance_counter::evaluate, this_()), + util::bind(&histogram_performance_counter::on_terminate, this_()), + std::string(action_name) + "_timer", info.fullname_, true), + base_counter_name_(ensure_counter_prefix(base_counter_name)), + has_prev_value_(false), + histogram_min_boundary_(-1), + histogram_max_boundary_(-1), + histogram_num_buckets_(-1), + reset_base_counter_(reset_base_counter) + { + registry::instance().register_action(action_name, + util::bind_front(&histogram_performance_counter:: + get_histogram_creator, this)); + + if (info.type_ != counter_aggregating) { + HPX_THROW_EXCEPTION(bad_parameter, + "histogram_performance_counter::histogram_performance_counter", + "unexpected counter type specified"); + } + + // make sure this counter starts collecting data + start(); + } + + bool histogram_performance_counter::evaluate() + { + // gather current base value + counter_value base_value; + if (!evaluate_base_counter(base_value)) + return false; + + // simply average the measured base counter values since it got queried + // for the last time + counter_value value; + if (base_value.scaling_ != prev_value_.scaling_ || + base_value.scale_inverse_ != prev_value_.scale_inverse_) + { + // not supported right now + HPX_THROW_EXCEPTION(not_implemented, + "statistics_counter::evaluate", + "base counter should keep scaling constant over time"); + return false; + } + else { + // accumulate new value + std::lock_guard l(mtx_); + // value_->add_value(static_cast(base_value.value_)); + + // collecting data for histogram + if(data_histogram) + (*data_histogram)(static_cast(base_value.value_)); + } + return true; + } + + bool histogram_performance_counter::ensure_base_counter() + { + // lock here to avoid checking out multiple reference counted GIDs + // from AGAS. This + std::unique_lock l(mtx_); + + if (!base_counter_id_) { + // get or create the base counter + error_code ec(lightweight); + hpx::id_type base_counter_id; + { + // We need to unlock the lock here since get_counter might suspend + util::unlock_guard > unlock(l); + base_counter_id = get_counter(base_counter_name_, ec); + } + + // After reacquiring the lock, we need to check again if base_counter_id_ + // hasn't been set yet + if (!base_counter_id_) + { + base_counter_id_ = base_counter_id; + } + else + { + // If it was set already by a different thread, return true. + return true; + } + + if (HPX_UNLIKELY(ec || !base_counter_id_)) + { + // base counter could not be retrieved + HPX_THROW_EXCEPTION(bad_parameter, + "histogram_performance_counter::evaluate_base_counter", + hpx::util::format( + "could not get or create performance counter: '%s'", + base_counter_name_)); + return false; + } + } + + return true; + } + + bool histogram_performance_counter::evaluate_base_counter( + counter_value& value) + { + // query the actual value + if (!base_counter_id_ && !ensure_base_counter()) + return false; + + value = stubs::performance_counter::get_value( + launch::sync, base_counter_id_, reset_base_counter_); + + if (!has_prev_value_) + { + has_prev_value_ = true; + prev_value_ = value; + } + + return true; + } + + /////////////////////////////////////////////////////////////////////////// + // Start and stop this counter. We dispatch the calls to the base counter + // and control our own interval_timer. + bool histogram_performance_counter::start() + { + if (!timer_.is_started()) { + // start base counter + if (!base_counter_id_ && !ensure_base_counter()) + return false; + + bool result = stubs::performance_counter::start( + launch::sync, base_counter_id_); + if (result) { + // acquire the current value of the base counter + counter_value base_value; + if (evaluate_base_counter(base_value)) + { + std::lock_guard l(mtx_); + // value_->add_value(static_cast(base_value.value_)); + prev_value_ = base_value; + + // collecting data for histogram + if(data_histogram) + (*data_histogram)(static_cast(base_value.value_)); + } + + // start timer + timer_.start(); + } + else { + // start timer even if base counter does not support being + // start/stop operations + timer_.start(true); + } + return result; + } + return false; + } + + bool histogram_performance_counter::stop() + { + if (timer_.is_started()) { + timer_.stop(); + + if (!base_counter_id_ && !ensure_base_counter()) + return false; + return stubs::performance_counter::stop( + launch::sync, base_counter_id_); + } + return false; + } + + void histogram_performance_counter::reset_counter_value() + { + std::lock_guard l(mtx_); + + // reset accumulator + // value_.reset(new detail::counter_type_from_histogram( + // parameter2_)); + + // start off with last base value + // value_->add_value(static_cast(prev_value_.value_)); + + // collecting data for histogram + if(data_histogram) + (*data_histogram)(static_cast(base_value.value_)); + } + +}}} + +namespace hpx { namespace performance_counters { namespace detail +{ + std::vector + histogram_performance_counter::get_histogram(bool reset) + { + std::vector result; + + std::unique_lock l(mtx_); + if (!data_histogram) + { + l.unlock(); + HPX_THROW_EXCEPTION(bad_parameter, + "histogram_performance_counter::" + "get_histogram", + "histogram counter was not initialized for " + "action type: " + action_name_); + return result; + } + + // first add histogram parameters + result.push_back(histogram_min_boundary_); + result.push_back(histogram_max_boundary_); + result.push_back(histogram_num_buckets_); + + auto data = hpx::util::histogram(*data_histogram); + for (auto const& item : data) + { + result.push_back(std::int64_t(item.second * 1000)); + } + + return result; + } + + void + histogram_performance_counter::get_histogram_creator( + std::int64_t min_boundary, std::int64_t max_boundary, + std::int64_t num_buckets, + util::function_nonser(bool)>& result) + { + std::lock_guard l(mtx_); + if (data_histogram) + { + result = util::bind_front(&histogram_performance_counter:: + get_histogram, this); + return; + } + + histogram_min_boundary_ = min_boundary; + histogram_max_boundary_ = max_boundary; + histogram_num_buckets_ = num_buckets; + + time_between_parcels_.reset(new histogram_collector_type( + hpx::util::tag::histogram::num_bins = double(num_buckets), + hpx::util::tag::histogram::min_range = double(min_boundary), + hpx::util::tag::histogram::max_range = double(max_boundary))); + last_parcel_time_ = util::high_resolution_clock::now(); + + result = util::bind_front(&histogram_performance_counter:: + get_histogram, this); + } + + struct get_histogram_surrogate + { + get_histogram_surrogate( + std::string const& action_name, std::int64_t min_boundary, + std::int64_t max_boundary, std::int64_t num_buckets) + : action_name_(action_name), min_boundary_(min_boundary), + max_boundary_(max_boundary), num_buckets_(num_buckets) + {} + + get_histogram_surrogate( + get_histogram_surrogate const& rhs) + : action_name_(rhs.action_name_), min_boundary_(rhs.min_boundary_), + max_boundary_(rhs.max_boundary_), num_buckets_(rhs.num_buckets_) + {} + + std::vector operator()(bool reset) + { + { + std::lock_guard l(mtx_); + if (counter_.empty()) + { + counter_ = registry::instance(). + get_histogram_counter(action_name_, + min_boundary_, max_boundary_, num_buckets_); + + // no counter available yet + if (counter_.empty()) + return registry::empty_histogram(reset); + } + } + + // dispatch to actual counter + return counter_(reset); + } + + hpx::lcos::local::spinlock mtx_; + hpx::util::function_nonser(bool)> counter_; + std::string action_name_; + std::int64_t min_boundary_; + std::int64_t max_boundary_; + std::int64_t num_buckets_; + }; + + hpx::naming::gid_type get_histogram_counter_creator( + hpx::performance_counters::counter_info const& info, hpx::error_code& ec) + { + switch (info.type_) { + case performance_counters::counter_histogram: + { + performance_counters::counter_path_elements paths; + performance_counters::get_counter_path_elements( + info.fullname_, paths, ec); + if (ec) return naming::invalid_gid; + + if (paths.parentinstance_is_basename_) { + HPX_THROWS_IF(ec, bad_parameter, + "get_histogram_counter_creator", + "invalid counter name for " + " histogram (instance " + "name must not be a valid base counter name)"); + return naming::invalid_gid; + } + + if (paths.parameters_.empty()) + { + HPX_THROWS_IF(ec, bad_parameter, + "get_histogram_counter_creator", + "invalid counter parameter for " + "histogram: must " + "specify an action type"); + return naming::invalid_gid; + } + + // split parameters, extract separate values + std::vector params; + boost::algorithm::split(params, paths.parameters_, + boost::algorithm::is_any_of(","), + boost::algorithm::token_compress_off); + + std::int64_t min_boundary = 0; + std::int64_t max_boundary = 1000000; // 1ms + std::int64_t num_buckets = 20; + + if (params.empty() || params[0].empty()) + { + HPX_THROWS_IF(ec, bad_parameter, + "get_histogram_counter_creator", + "invalid counter parameter for " + "histogram: " + "must specify an action type"); + return naming::invalid_gid; + } + + if (params.size() > 1 && !params[1].empty()) + min_boundary = util::safe_lexical_cast(params[1]); + if (params.size() > 2 && !params[2].empty()) + max_boundary = util::safe_lexical_cast(params[2]); + if (params.size() > 3 && !params[3].empty()) + num_buckets = util::safe_lexical_cast(params[3]); + + // ask registry + hpx::util::function_nonser(bool)> f = + registry::instance(). + get_histogram_counter(params[0], + min_boundary, max_boundary, num_buckets); + + if (!f.empty()) + { + return performance_counters::detail::create_raw_counter( + info, std::move(f), ec); + } + + // the counter is not available yet, create surrogate function + return performance_counters::detail::create_raw_counter(info, + get_histogram_surrogate( + params[0], min_boundary, max_boundary, num_buckets), ec); + } + break; + + default: + HPX_THROWS_IF(ec, bad_parameter, + "get_histogram_counter_creator", + "invalid counter type requested"); + return naming::invalid_gid; + } +}}} diff --git a/src/runtime.cpp b/src/runtime.cpp index 16ff4b311902..eece5f325d3a 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -556,6 +556,24 @@ namespace hpx performance_counters::install_counter_types( statistic_counter_types, sizeof(statistic_counter_types)/sizeof(statistic_counter_types[0])); + + performance_counters::generic_counter_type_data histogram_counter_types[] = + { + // histogram counter + + // histogram_performance_counter@action-name,min,max,buckets + { "histogram_performance_counter", counter_histogram, + "returns the histogram for the times between parcels for " + "the action which is given by the counter parameter", + HPX_PERFORMANCE_COUNTER_V1, + &performance_counters::get_histogram_counter_creator, + &performance_counters::counter_discoverer, + "ns/0.1%" + } + }; + performance_counters::install_counter_types( + histogram_counter_types, + sizeof(histogram_counter_types)/sizeof(histogram_counter_types[0])); performance_counters::generic_counter_type_data arithmetic_counter_types[] = {