From 4756fc9fa49844caa76e1bedf2a8bc575d97f23a Mon Sep 17 00:00:00 2001 From: Keef Aragon Date: Thu, 9 Aug 2018 19:52:27 -0700 Subject: [PATCH] With all the publisher stuff in place --- CMakeLists.txt | 2 +- {src => cxxmetrics}/CMakeLists.txt | 4 +- {src => cxxmetrics}/conanfile.txt | 0 {src => cxxmetrics}/counter.hpp | 0 {src => cxxmetrics}/ewma.hpp | 67 +-- {src => cxxmetrics}/exponential_decay.hpp | 0 {src => cxxmetrics}/gauge.hpp | 2 +- {src => cxxmetrics}/histogram.hpp | 0 {src => cxxmetrics}/internal/atomic_lifo.hpp | 0 {src => cxxmetrics}/internal/hazard_ptr.hpp | 0 cxxmetrics/meta.hpp | 144 +++++ {src => cxxmetrics}/meter.hpp | 2 +- {src => cxxmetrics}/metric.hpp | 0 {src => cxxmetrics}/metric_path.hpp | 6 +- {src => cxxmetrics}/metric_value.hpp | 0 {src => cxxmetrics}/metrics_registry.hpp | 162 +++++- {src => cxxmetrics}/pool.hpp | 0 cxxmetrics/publisher.hpp | 533 +++++++++++++++++++ cxxmetrics/publisher_impl.hpp | 160 ++++++ {src => cxxmetrics}/ringbuf.hpp | 0 {src => cxxmetrics}/simple_reservoir.hpp | 0 {src => cxxmetrics}/skiplist.hpp | 0 {src => cxxmetrics}/sliding_window.hpp | 0 {src => cxxmetrics}/snapshots.hpp | 17 +- {src => cxxmetrics}/tag_collection.hpp | 4 +- {src => cxxmetrics}/time.hpp | 10 +- {src => cxxmetrics}/timer.hpp | 0 {src => cxxmetrics}/uniform_reservoir.hpp | 0 src/meta.hpp | 144 ----- src/publisher.hpp | 102 ---- test/CMakeLists.txt | 1 + test/counter_test.cpp | 2 +- test/ewma_test.cpp | 10 +- test/gauge_test.cpp | 2 +- test/helpers.hpp | 4 - test/histogram_test.cpp | 4 +- test/internal/atomic_lifo_test.cpp | 2 +- test/meter_test.cpp | 11 +- test/metrics_registry_test.cpp | 13 +- test/pool_test.cpp | 2 +- test/publisher_tests.cpp | 353 ++++++++++++ test/reservoir_test.cpp | 6 +- test/ringbuf_test.cpp | 2 +- test/skiplist_test.cpp | 2 +- test/timer_test.cpp | 4 +- 45 files changed, 1438 insertions(+), 339 deletions(-) rename {src => cxxmetrics}/CMakeLists.txt (95%) rename {src => cxxmetrics}/conanfile.txt (100%) rename {src => cxxmetrics}/counter.hpp (100%) rename {src => cxxmetrics}/ewma.hpp (88%) rename {src => cxxmetrics}/exponential_decay.hpp (100%) rename {src => cxxmetrics}/gauge.hpp (99%) rename {src => cxxmetrics}/histogram.hpp (100%) rename {src => cxxmetrics}/internal/atomic_lifo.hpp (100%) rename {src => cxxmetrics}/internal/hazard_ptr.hpp (100%) create mode 100644 cxxmetrics/meta.hpp rename {src => cxxmetrics}/meter.hpp (98%) rename {src => cxxmetrics}/metric.hpp (100%) rename {src => cxxmetrics}/metric_path.hpp (93%) rename {src => cxxmetrics}/metric_value.hpp (100%) rename {src => cxxmetrics}/metrics_registry.hpp (81%) rename {src => cxxmetrics}/pool.hpp (100%) create mode 100644 cxxmetrics/publisher.hpp create mode 100644 cxxmetrics/publisher_impl.hpp rename {src => cxxmetrics}/ringbuf.hpp (100%) rename {src => cxxmetrics}/simple_reservoir.hpp (100%) rename {src => cxxmetrics}/skiplist.hpp (100%) rename {src => cxxmetrics}/sliding_window.hpp (100%) rename {src => cxxmetrics}/snapshots.hpp (96%) rename {src => cxxmetrics}/tag_collection.hpp (90%) rename {src => cxxmetrics}/time.hpp (92%) rename {src => cxxmetrics}/timer.hpp (100%) rename {src => cxxmetrics}/uniform_reservoir.hpp (100%) delete mode 100644 src/meta.hpp delete mode 100644 src/publisher.hpp create mode 100644 test/publisher_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 61983fa..b655d7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,5 +16,5 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage") link_libraries(gcov) endif() -add_subdirectory(src) +add_subdirectory(cxxmetrics) add_subdirectory(test) diff --git a/src/CMakeLists.txt b/cxxmetrics/CMakeLists.txt similarity index 95% rename from src/CMakeLists.txt rename to cxxmetrics/CMakeLists.txt index 6767cc0..0537629 100644 --- a/src/CMakeLists.txt +++ b/cxxmetrics/CMakeLists.txt @@ -30,6 +30,8 @@ set(HEADERS metric_value.hpp metrics_registry.hpp pool.hpp + publisher.hpp + publisher_impl.hpp ringbuf.hpp simple_reservoir.hpp skiplist.hpp @@ -41,7 +43,7 @@ set(HEADERS ) add_library(cxxmetrics INTERFACE) -target_include_directories(cxxmetrics INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") +target_include_directories(cxxmetrics INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/../") target_sources_local(cxxmetrics INTERFACE ${HEADERS}) target_link_libraries(cxxmetrics INTERFACE atomic CONAN_PKG::ctti) diff --git a/src/conanfile.txt b/cxxmetrics/conanfile.txt similarity index 100% rename from src/conanfile.txt rename to cxxmetrics/conanfile.txt diff --git a/src/counter.hpp b/cxxmetrics/counter.hpp similarity index 100% rename from src/counter.hpp rename to cxxmetrics/counter.hpp diff --git a/src/ewma.hpp b/cxxmetrics/ewma.hpp similarity index 88% rename from src/ewma.hpp rename to cxxmetrics/ewma.hpp index b57d508..725870c 100644 --- a/src/ewma.hpp +++ b/cxxmetrics/ewma.hpp @@ -76,18 +76,20 @@ class ewma using clock_diff = typename clock_traits::clock_diff; private: - TClockGet clk_; static long double alpha_; + + TClockGet clk_; std::atomic rate_; clock_point last_; std::atomic pending_; static constexpr double get_alpha() { - return 1 - exp((TInterval * -1.0l) / (TWindow * 1.0l)); + return 1 - exp((TInterval * -1.0l) / (TWindow * 2.0l)); } - void tick(const clock_point &at) noexcept; + template + TValue tick(const clock_point &at) noexcept; public: ewma(const TClockGet &clock = TClockGet()) noexcept; @@ -139,20 +141,16 @@ void ewma::mark(TMark amount) noexcept if (now < last_) return; - // See if we crossed the interval threshold. If so we need to tick - if (last_ != clock_point{}) + if (last_ == clock_point{}) { - if ((now - last_) >= period(TInterval)) - tick(now); atomic_add(pending_, amount); + tick(now); } else { - // the clock hasn't started yet - add the item to pending and attempt to tick - atomic_add(pending_, amount); tick(now); + atomic_add(pending_, amount); } - } template @@ -165,49 +163,47 @@ template::rate() noexcept { auto now = clk_(); - - // See if we crossed the interval threshold. If so we need to tick - if (last_ != clock_point{} && now > last_ && (now - last_) >= period(TInterval)) - tick(now); - - return rate_.load(); + return tick(now); } template TValue ewma::rate() const noexcept { - auto rate = rate_.load(); - if (rate < 0) - return 0; - - return rate; + auto now = clk_(); + // the const_cast is safe with the false template parameter + return const_cast(this)->tick(now); } template -void ewma::tick(const clock_point &at) noexcept +template +TValue ewma::tick(const clock_point &at) noexcept { int missed_intervals; clock_point last; - auto pending = pending_.load(); + last = last_; + auto pending = pending_.load(); auto nrate = rate_.load(); - last = last_; if (last == clock_point{}) { - // one thread sets the last timestamp - if (!pending_.compare_exchange_strong(pending, 0)) - return; // someone else ticked from under us + // use constexpr if with 17 + if (TWrite) + { + // one thread sets the last timestamp + if (!pending_.compare_exchange_strong(pending, 0)) + return pending; // someone else ticked from under us - if (rate_.compare_exchange_weak(nrate, pending)) - last_ = at; + if (rate_.compare_exchange_weak(nrate, pending)) + last_ = at; + } - return; + return pending; } // make sure that last_ didn't catch up with us - if ((at - last) < period(TInterval)) - return; + if ((at < last) || (at - last) < period(TInterval)) + return nrate; // apply the pending value to our current rate // if someone else already snagged the pending value, start over @@ -234,12 +230,17 @@ void ewma::tick(const clock_point &at) no rate = rate + (alpha_ * -rate); } + if (!TWrite) + return rate; + if (!pending_.compare_exchange_strong(pending, 0)) - return; // someone else already either ticked or added a pending value + return rate; // someone else already either ticked or added a pending value rate_.store(rate); if (last_ < at) last_ = at; + + return rate; } template diff --git a/src/exponential_decay.hpp b/cxxmetrics/exponential_decay.hpp similarity index 100% rename from src/exponential_decay.hpp rename to cxxmetrics/exponential_decay.hpp diff --git a/src/gauge.hpp b/cxxmetrics/gauge.hpp similarity index 99% rename from src/gauge.hpp rename to cxxmetrics/gauge.hpp index a612bd0..fe1c6d4 100644 --- a/src/gauge.hpp +++ b/cxxmetrics/gauge.hpp @@ -293,7 +293,7 @@ struct default_metric_builder> cxxmetrics::gauge operator()() const { static TGaugeType *r = nullptr; - return cxxmetrics::gauge(r); + return cxxmetrics::gauge(r); } }; diff --git a/src/histogram.hpp b/cxxmetrics/histogram.hpp similarity index 100% rename from src/histogram.hpp rename to cxxmetrics/histogram.hpp diff --git a/src/internal/atomic_lifo.hpp b/cxxmetrics/internal/atomic_lifo.hpp similarity index 100% rename from src/internal/atomic_lifo.hpp rename to cxxmetrics/internal/atomic_lifo.hpp diff --git a/src/internal/hazard_ptr.hpp b/cxxmetrics/internal/hazard_ptr.hpp similarity index 100% rename from src/internal/hazard_ptr.hpp rename to cxxmetrics/internal/hazard_ptr.hpp diff --git a/cxxmetrics/meta.hpp b/cxxmetrics/meta.hpp new file mode 100644 index 0000000..31d5f1d --- /dev/null +++ b/cxxmetrics/meta.hpp @@ -0,0 +1,144 @@ +#ifndef CXXMETRICS_META_HPP +#define CXXMETRICS_META_HPP + +namespace cxxmetrics +{ + +namespace templates +{ + +using sortable_template_type = unsigned long long; + +template +struct sortable_template_collection; + +template +struct static_concat; + +template +struct static_concat, sortable_template_collection, TTail...> +{ + using type = typename static_concat, TTail...>::type; +}; + +template +struct static_concat> +{ + using type = sortable_template_collection; +}; + +template +struct static_if_else; + +template +struct static_if_else +{ + using type = TTrue; +}; + +template +struct static_if_else +{ + using type = TFalse; +}; + +template +struct is_less +{ + static constexpr bool value = (TLeft < TRight); +}; + +template +struct is_greater +{ + static constexpr bool value = (TLeft > TRight); +}; + +template +struct are_equal +{ + static constexpr bool value = (TLeft == TRight); +}; + +template +struct static_partition; + +template +struct static_partition> +{ +private: + using left_ = typename static_partition>::left; + using middle_ = typename static_partition>::middle; + using right_ = typename static_partition>::right; +public: + using left = typename static_if_else::value, typename static_concat, left_>::type, left_>::type; + using middle = typename static_if_else::value, typename static_concat, middle_>::type, middle_>::type; + using right = typename static_if_else::value, typename static_concat, right_>::type, right_>::type; +}; + +template +struct static_partition> +{ + using left = sortable_template_collection<>; + using middle = sortable_template_collection; + using right = sortable_template_collection<>; +}; + +template +struct static_sorter; + +template +struct static_sorter> +{ +private: + using partitioned_ = static_partition>; + + using left_ = typename static_sorter::type; + using middle_ = typename partitioned_::middle; + using right_ = typename static_sorter::type; +public: + using type = typename static_concat::type; +}; + +template<> +struct static_sorter> +{ + using type = sortable_template_collection<>; +}; + +template +struct static_uniq; + +template +struct static_uniq> +{ + using type = typename static_if_else::value, typename static_uniq>::type, typename static_concat, typename static_uniq>::type>::type>::type; +}; + +template +struct static_uniq> +{ + using type = sortable_template_collection; +}; + +template<> +struct static_uniq> +{ + using type = sortable_template_collection<>; +}; + +template +struct sort_unique +{ +private: + using list_ = sortable_template_collection; + using sorted_ = typename static_sorter::type; +public: + using type = typename static_uniq::type; +}; + +} // templates + +} // cxxmetrics + +#endif //CXXMETRICS_META_HPP diff --git a/src/meter.hpp b/cxxmetrics/meter.hpp similarity index 98% rename from src/meter.hpp rename to cxxmetrics/meter.hpp index 507ff1b..0d61788 100644 --- a/src/meter.hpp +++ b/cxxmetrics/meter.hpp @@ -415,7 +415,7 @@ template class meter_builder; template -class meter_builder> : public meter +class meter_builder> : public meter { public: meter_builder() = default; diff --git a/src/metric.hpp b/cxxmetrics/metric.hpp similarity index 100% rename from src/metric.hpp rename to cxxmetrics/metric.hpp diff --git a/src/metric_path.hpp b/cxxmetrics/metric_path.hpp similarity index 93% rename from src/metric_path.hpp rename to cxxmetrics/metric_path.hpp index db2c454..376366c 100644 --- a/src/metric_path.hpp +++ b/cxxmetrics/metric_path.hpp @@ -76,7 +76,7 @@ class metric_path } }; -std::string metric_path::join(const std::string &delim) const +inline std::string metric_path::join(const std::string &delim) const { if (paths_.empty()) return ""; @@ -100,7 +100,7 @@ std::string metric_path::join(const std::string &delim) const return result; } -bool metric_path::operator==(const cxxmetrics::metric_path &other) const +inline bool metric_path::operator==(const cxxmetrics::metric_path &other) const { if (paths_.size() != other.paths_.size()) return false; @@ -114,7 +114,7 @@ bool metric_path::operator==(const cxxmetrics::metric_path &other) const return true; } -bool metric_path::operator!=(const cxxmetrics::metric_path &other) const +inline bool metric_path::operator!=(const cxxmetrics::metric_path &other) const { return !(operator==(other)); } diff --git a/src/metric_value.hpp b/cxxmetrics/metric_value.hpp similarity index 100% rename from src/metric_value.hpp rename to cxxmetrics/metric_value.hpp diff --git a/src/metrics_registry.hpp b/cxxmetrics/metrics_registry.hpp similarity index 81% rename from src/metrics_registry.hpp rename to cxxmetrics/metrics_registry.hpp index e6c1f44..82edf38 100644 --- a/src/metrics_registry.hpp +++ b/cxxmetrics/metrics_registry.hpp @@ -1,9 +1,10 @@ #ifndef CXXMETRICS_METRICS_REGISTRY_HPP #define CXXMETRICS_METRICS_REGISTRY_HPP +// TODO: use shared_mutexes with C++17 #include #include -#include "metric_path.hpp" +#include "publisher.hpp" #include "tag_collection.hpp" #include "counter.hpp" #include "ewma.hpp" @@ -99,6 +100,9 @@ class basic_registered_metric { std::string type_; + std::unordered_map> pubdata_; + mutable std::mutex pubdatalock_; + template std::shared_ptr tagged(const tag_collection& tags, TConstructorArgs&&... args) { @@ -125,8 +129,36 @@ class basic_registered_metric return added; } + template + typename std::enable_if::value, TDataType>::type& + get_or_create_publish_data(TConstructArgs&&... args) + { + std::lock_guard lock(pubdatalock_); + auto key = ctti::nameof().str(); + auto& ptr = pubdata_[key]; + + if (!ptr) + ptr = std::make_unique(std::forward(args)...); + + return static_cast(*ptr); + } + + template + typename std::enable_if::value, TDataType>::type* + try_get_publish_data() const + { + std::lock_guard lock(pubdatalock_); + auto fnd = pubdata_.find(ctti::nameof().str()); + if (fnd == pubdata_.end()) + return nullptr; + + return static_cast(fnd->second.get()); + } + template friend class metrics_registry; + template + friend class metrics_publisher; protected: template struct basic_metric_builder @@ -280,31 +312,39 @@ std::shared_ptr registered_metric::child(const cx /** * \brief The default metric repository that registers metrics in a standard unordered map with a mutex lock */ -template +template> class basic_default_repository { std::unordered_map, std::hash, std::equal_to, TAlloc> metrics_; - std::mutex lock_; + std::unordered_map, std::hash, std::equal_to, TAlloc> data_; + + mutable std::mutex metriclock_; + mutable std::mutex datalock_; + public: basic_default_repository() = default; template basic_registered_metric& get_or_add(const metric_path& name, const TMetricPtrBuilder& builder); + basic_registered_metric* get(const metric_path& name); template void visit(THandler&& handler); - const tag_collection& tags(const tag_collection& tags) const - { - return tags; - } + constexpr const tag_collection& tags(const tag_collection& tags) const noexcept { return tags; } + + template + typename std::enable_if::value, TDataType>::type& get_publish_data(TConstructArgs&&... args); + + template + typename std::enable_if::value, TDataType>::type* get_publish_data() const; }; template template basic_registered_metric& basic_default_repository::get_or_add(const metric_path& name, const TMetricPtrBuilder& builder) { - std::lock_guard lock(lock_); + std::lock_guard lock(metriclock_); auto existing = metrics_.find(name); if (existing == metrics_.end()) @@ -316,15 +356,56 @@ basic_registered_metric& basic_default_repository::get_or_add(const metr return *existing->second; } +template +basic_registered_metric* basic_default_repository::get(const metric_path& name) +{ + std::lock_guard lock(metriclock_); + auto existing = metrics_.find(name); + + if (existing == metrics_.end()) + return nullptr; + + return existing->second.get(); +} + template template void basic_default_repository::visit(THandler&& handler) { - std::lock_guard lock(lock_); + std::lock_guard lock(metriclock_); for (auto& pair : metrics_) handler(pair.first, *pair.second); } +template +template +typename std::enable_if::value, TDataType>::type& +basic_default_repository::get_publish_data(TConstructArgs&&... args) +{ + std::lock_guard lock(datalock_); + auto key = ctti::nameof().str(); + auto& ptr = data_[key]; + + if (!ptr) + ptr = std::make_unique(std::forward(args)...); + + return static_cast(*ptr); +} + +template +template +typename std::enable_if::value, TDataType>::type* +basic_default_repository::get_publish_data() const +{ + std::lock_guard lock(datalock_); + auto key = ctti::nameof().str(); + auto fnd = data_.find(ctti::nameof().str()); + if (fnd == data_.end()) + return nullptr; + + return static_cast(fnd->second.get()); +} + using default_repository = basic_default_repository>>; /** @@ -343,6 +424,12 @@ class metrics_registry template std::shared_ptr get(const metric_path& path, const tag_collection& tags, TConstructorArgs&&... args); + template + TDataType& get_publish_data(TConstructArgs&&... args) { return repo_.template get_publish_data(std::forward(args)...); } + + auto* try_get(const metric_path& name) { return repo_.get(name); } + + friend class metrics_publisher; public: /** * \brief Construct the registry with the arguments being passed to the underlying repository @@ -356,6 +443,30 @@ class metrics_registry { } ~metrics_registry() = default; + /** + * \brief Get the repository wide publish options + */ + const cxxmetrics::publish_options& publish_options() const; + + /** + * \brief Set the repository wide publish options + * + * \note individual metrics may override these + * + * \param options the new options for the repository + */ + void publish_options(cxxmetrics::publish_options&& options); + + /** + * \brief Set the publish option overrides for a single metric + * + * if the metric provided isn't registered, this method does nothing + * + * \param name the metric on which to set the options + * \param options the new options for the metric + */ + void publish_options(const metric_path& name, cxxmetrics::publish_options&& options); + /** * \brief Run a visitor on all of the registered metrics * @@ -509,6 +620,8 @@ class metrics_registry TReservoir&& reservoir = TReservoir(), const tag_collection& tags = tag_collection()); +#if __cplusplus >= 201700 + /** * A wrapper for timer with some sensible defaults * @@ -525,6 +638,8 @@ class metrics_registry { return this->template timer, TRateWindows...>(name, TReservoir(), tags); } +#endif + }; template @@ -533,6 +648,33 @@ metrics_registry::metrics_registry(TRepoArgs &&... args) : repo_(std::forward(args)...) { } +template +const cxxmetrics::publish_options& metrics_registry::publish_options() const +{ + static const cxxmetrics::publish_options defaultopts; + auto existing = repo_.template get_publish_data(); + if (existing == nullptr) + return defaultopts; + + return *existing; +} + +template +void metrics_registry::publish_options(cxxmetrics::publish_options&& options) +{ + repo_.template get_publish_data() = std::move(options); +} + +template +void metrics_registry::publish_options(const metric_path& path, cxxmetrics::publish_options&& options) +{ + auto l = try_get(path); + if (l == nullptr) + return; + + l->template get_or_create_publish_data() = std::move(options); +} + template template registered_metric& metrics_registry::get(const metric_path& path) @@ -632,4 +774,6 @@ std::shared_ptr +#include "meta.hpp" +#include "snapshots.hpp" +#include "metric_path.hpp" + +namespace cxxmetrics +{ + +// fwd declaration +template +class metrics_registry; +class basic_registered_metric; + +class scale_factor +{ + double factor_; + bool apply_; +public: + constexpr scale_factor() noexcept : factor_(1), apply_(false) { } + constexpr scale_factor(double factor) noexcept : factor_(factor), apply_(true) { } + constexpr operator bool() const noexcept { return apply_; } + constexpr double factor() const noexcept { return factor_; } +}; + +/** + * \brief the base type that all publish options need to implement + */ +class basic_publish_options +{ +public: + virtual ~basic_publish_options() = default; +}; + +/** + * \brief Options to apply to value types while publishing + */ +class value_publish_options +{ + scale_factor scale_; +public: + constexpr value_publish_options(const value_publish_options&) noexcept = default; + constexpr value_publish_options& operator=(const value_publish_options&) noexcept = default; + + constexpr value_publish_options(const scale_factor& sf = scale_factor()) noexcept : + scale_(sf) + { } + + /** + * \brief a scaling factor to the metric, if set + */ + constexpr scale_factor scale() const noexcept { return scale_; } +}; + +/** + * \brief Options to apply to meter types while publishing + */ +class meter_publish_options : public virtual value_publish_options +{ + bool mean_; +public: + meter_publish_options(const scale_factor& sf = scale_factor()) : + value_publish_options(sf), + mean_(true) + { } + meter_publish_options(bool with_mean, const scale_factor& sf = scale_factor()) : + value_publish_options(sf), mean_(with_mean) + { } + + meter_publish_options(const meter_publish_options& other) noexcept : + value_publish_options(other), + mean_(other.mean_) + { } + + meter_publish_options& operator=(const meter_publish_options& other) noexcept = default; + + /** + * \brief whether or not the publisher should publish the mean + */ + bool include_mean() const noexcept { return mean_; } +}; + +/** + * \brief a visitor that can be used with quantile_options if honoring custom quantiles for histogram publishing + */ +class quantile_visitor +{ +public: + virtual void visit(const quantile& q, const metric_value& value) const = 0; + virtual ~quantile_visitor() = default; +}; + +class basic_quantile_options +{ + template + class invokable_quantile_visitor : public quantile_visitor + { + THandler handler_; + public: + invokable_quantile_visitor(THandler&& handler) : + handler_(std::forward(handler)) + { } + + void visit(const quantile& q, const metric_value& value) const override + { + handler_(q, value); + } + }; + +public: + template()(std::declval(), std::declval()))> + void visit(const histogram_snapshot& snapshot, THandler&& hnd) const + { + invokable_quantile_visitor visitor(std::forward(hnd)); + visit(snapshot, visitor); + } + + virtual void visit(const histogram_snapshot& snapshot, const quantile_visitor& visitor) const = 0; + virtual ~basic_quantile_options() = default; +}; + +namespace quantiles +{ + +/** + * \brief Quantile options that allow one to specify quantiles from a histogram + * + * \tparam TQuantiles the quantiles that should be published + */ +template +class processed_quantile_options : public basic_quantile_options +{ + template + struct visit_one; + + template + struct visit_one + { + void operator()(const histogram_snapshot& snapshot, const quantile_visitor& visitor) const + { + visitor.visit(TVisit, snapshot.value()); + visit_one next; + next(snapshot, visitor); + } + }; + + template + struct visit_one<_Quantile> + { + void operator()(const histogram_snapshot& snapshot, const quantile_visitor& visitor) const + { + visitor.visit(_Quantile, snapshot.value<_Quantile>()); + } + }; +public: + /** + * \brief collect the requested quantiles in the snapshot, calling the visitor for each + */ + void visit(const histogram_snapshot &snapshot, const quantile_visitor &visitor) const override + { + visit_one fn; + fn(snapshot, visitor); + } +}; + +template +struct quantile_options_builder; + +template +struct quantile_options_builder> : public processed_quantile_options +{ }; + +} + +/** + * \brief The options for the quantiles that should be published + * + * As with all options, this may or may not be supported by the publisher + * + * \tparam TQuantiles the quantiles to of the histogram that should be reported + */ +template +class quantile_options : public quantiles::quantile_options_builder::type> +{ }; + +/** + * \brief Options to apply to histogram types while publishing + */ +class histogram_publish_options : public virtual value_publish_options +{ + static basic_quantile_options& default_quantiles() + { + static auto def = std::make_unique>(); + return *def; + } + std::unique_ptr quantiles_; + bool count_; +public: + histogram_publish_options(histogram_publish_options&& other) noexcept : + value_publish_options(std::move(other)), + quantiles_(std::move(other.quantiles_)), + count_(other.count_) + { } + + histogram_publish_options(const scale_factor& sf = scale_factor()) noexcept : + value_publish_options(sf), + count_(true) + { } + + histogram_publish_options(bool publish_count, const scale_factor& sf = scale_factor()) noexcept : + value_publish_options(sf), + count_(publish_count) + { } + + template::value, void>::type> + histogram_publish_options(TQuantileOptions&& quantile_options, bool publish_count = true, const scale_factor& sf = scale_factor()) : + value_publish_options(sf), + quantiles_(std::make_unique(std::forward(quantile_options))), + count_(publish_count) + { } + + histogram_publish_options& operator=(histogram_publish_options&& other) noexcept + { + value_publish_options::operator=(std::move(other)); + quantiles_ = std::move(other.quantiles_); + count_ = other.count_; + return *this; + } + + /** + * \brief Get the quantiles that should be published + * + * \return the quantile options for the publisher, will either be options specific or universal default + */ + basic_quantile_options& quantiles() const noexcept + { + if (quantiles_) + return *quantiles_; + return default_quantiles(); + } + + /** + * \brief whether or not the histogram total should be published + */ + bool include_count() const noexcept { + return count_; + } +}; + +/** + * \brief Options to apply to timer types while publishing + */ +class timer_publish_options : public histogram_publish_options, public meter_publish_options +{ +public: + timer_publish_options(timer_publish_options&& other) noexcept : + histogram_publish_options(std::move(other)), + meter_publish_options(std::move(other)) + { } + + timer_publish_options(const scale_factor& sf = scale_factor()) noexcept : + histogram_publish_options(sf), + meter_publish_options(sf) + { } + + timer_publish_options(bool publish_count, const scale_factor& sf = scale_factor()) noexcept : + histogram_publish_options(publish_count, sf), + meter_publish_options(sf) + { } + + timer_publish_options(bool publish_count, bool publish_mean, const scale_factor& sf = scale_factor()) noexcept : + histogram_publish_options(publish_count, sf), + meter_publish_options(publish_mean, sf) + { } + + template::value, void>::type> + timer_publish_options(TQuantileOptions&& quantile_options, bool publish_count = true, const scale_factor& sf = scale_factor()) : + histogram_publish_options(quantile_options, publish_count, sf), + meter_publish_options(sf) + { } + + + template::value, void>::type> + timer_publish_options(TQuantileOptions&& quantile_options, bool publish_count, bool publish_mean, const scale_factor& sf = scale_factor()) : + histogram_publish_options(quantile_options, publish_count, sf), + meter_publish_options(publish_mean, sf) + { } + + timer_publish_options& operator=(timer_publish_options&& other) noexcept + { + histogram_publish_options::operator=(std::move(other)); + meter_publish_options::operator=(std::move(other)); + return *this; + } +}; + +class publish_options : public basic_publish_options +{ + value_publish_options values_; + meter_publish_options meters_; + histogram_publish_options histograms_; + timer_publish_options timers_; +public: + publish_options(publish_options&& other) noexcept : + values_(std::move(other.values_)), + meters_(std::move(other.meters_)), + histograms_(std::move(other.histograms_)), + timers_(std::move(other.timers_)) + { } + + publish_options( + value_publish_options&& value_options = value_publish_options(), + meter_publish_options&& meter_options = meter_publish_options(), + histogram_publish_options&& histogram_options = histogram_publish_options(), + timer_publish_options&& timer_options = timer_publish_options()) : + values_(std::move(value_options)), + meters_(std::move(meter_options)), + histograms_(std::move(histogram_options)), + timers_(std::move(timer_options)) + { } + + publish_options( + meter_publish_options&& meter_options, + histogram_publish_options&& histogram_options = histogram_publish_options(), + timer_publish_options&& timer_options = timer_publish_options()) : + meters_(std::move(meter_options)), + histograms_(std::move(histogram_options)), + timers_(std::move(timer_options)) + { } + + publish_options( + histogram_publish_options&& histogram_options, + timer_publish_options&& timer_options = timer_publish_options()) : + histograms_(std::move(histogram_options)), + timers_(std::move(timer_options)) + { } + + publish_options( + timer_publish_options&& timer_options) : + timers_(std::move(timer_options)) + { } + + publish_options& operator=(publish_options&& other) noexcept + { + values_ = std::move(other.values_); + meters_ = std::move(other.meters_); + histograms_ = std::move(other.histograms_); + timers_ = std::move(other.timers_); + + return *this; + } + + const value_publish_options& value_options() const noexcept { return values_; }; + const meter_publish_options& meter_options() const noexcept { return meters_; }; + const histogram_publish_options& histogram_options() const noexcept { return histograms_; }; + const timer_publish_options& timer_options() const noexcept { return timers_; }; + +}; + +/** + * \brief The base class for the metrics publisher + * + * A publisher simply uses the standard visit pattern to access metrics and publish. + * However, inheriting from this class allows the publisher to gain additional insight into + * metrics that are registered in the underlying registry as well as inject it's own data + * at various levels + * + * \tparam TMetricRepo the repository being used by the registry + */ +template +class metrics_publisher +{ + metrics_registry& registry_; + +protected: + + /** + * \brief Get the publish options for a metric found in the repository + * + * Publish options are a special global set of options that tell publishers how to publish metrics + * + * They are effectively the same thing as any other custom data but are codified into cxxmetrics as + * a way to set some semblance of a standard across publishers for obvious things. + * + * \return The publish options for the repository + */ + const publish_options& effective_options(basic_registered_metric& metric) const; + + /** + * \brief Get a piece of data from the registry. Generally, this will be a type specific to the publisher + * + * This allows the publisher to store data in the registry that is specific to the publisher. If the data is + * already registered, the returned value will be the registered data. Otherwise, the object is constructed + * and stored in the registry using the arguments specified. The data, however, must be a subclass of + * \refitem basic_publish_options + * + * \tparam TDataType the type of data to get from the registry + * \tparam TBuildArgs the types of arguments that will be used to construct the data if it doesn't exist + * + * \return the registered data of the requested type + */ + template + typename std::enable_if::value, TDataType>::type& + get_data(TBuildArgs&&... args); + + /** + * \brief Get a piece of data from the registry for a specific metric by name. + * + * \warning Don't use this in a visitor - use the overload that operates directly on the metric instead on the metric the visitor receives. Ignoring this warning will result in deadlock + * + * This returns a pointer which has the potential for being null. This can happen when the metric_path being + * requested isn't registered in the registry (thus there's nothing to attach data to). + * + * \tparam TDataType the type of data to attach to the metric + * \tparam TBuildArgs The types of arguments that will be used to construct the data if it doesn't exist but the path does + * + * \param path the path of the metric to attach the data to + * + * \return the attached data if the path existed + */ + template + typename std::enable_if::value, TDataType>::type* + get_data_for(const metric_path& path, TBuildArgs&&... args); + + /** + * \brief Get a piece of data from the registry for a specific metric by reference. + * + * \tparam TDataType the type of data to attach to the metric + * \tparam TBuildArgs The types of arguments that will be used to construct the data if it doesn't exist but the path does + * + * \param metric the metric to attach the data to or get the attached data + * + * \return the attached data + */ + template + typename std::enable_if::value, TDataType>::type& + get_data_for(basic_registered_metric& metric, TBuildArgs&&... args); + + /** + * \brief Get whether or not the custom data exists on the specified metric path + * + * \warning Don't use this in a visitor - use the overload that operates directly on the metric instead which the visitor receives. Ignoring this warning will result in deadlock + * + * \tparam TDataType the type of data to look for being attached to the metric + * + * \param path the path of the metric to query for the custom data + * + * \return whether or not the metric has the requested custom data + */ + template + typename std::enable_if::value, bool>::type + has_data_for(const metric_path& path) const; + + /** + * \brief Get whether or not the custom data exists on the specified metric + * + * \tparam TDataType the type of data to look for being attached to the metric + * + * \param metric the metric to query for the existence of custom data + * + * \return whether or not the metric has the requested data + */ + template + typename std::enable_if::value, bool>::type + has_data_for(const basic_registered_metric& metric) const; + + /** + * \brief Get whether or not there is a metric at the specified path + * + * \warning Figure this out while you're not in a visitor, otherwise, you'll deadlock + * + * \return whether or not there is a metric at the specified path + */ + bool has_metric(const metric_path& path) const; + + /** + * \brief Get a string representation of the type of metric stored at the specified path + * + * \warning Don't use this in a visitor - use the overload that operates directly on the metric instead which the visitor receives. Ignoring this warning will result in deadlock + * + * If there is no metric stored at the path, this will return an empty string + * + * \param path the path of the metric to query for the type + * + * \return the type of metric at the specified path + */ + std::string metric_type(const metric_path& path) const; + + /** + * \brief Get a string representation of the type of metric + * + * \param metric the metric to get the type for + * + * \return the type of metric at the specified path + */ + std::string metric_type(const basic_registered_metric& metric) const; + + /** + * \brief Visit just a single metric in the registry + * + * The handler follows the same signature as the visitor on the registry at large or + * visit_all but it will only be called on the metric requested, if it exists + * + * \note this method may hold a lock on the registry data at some level. It is therefore advised not to use any other registry access routines inside of the handler + * + * \tparam THandler the handler to call if the registry is found at the specified path, the first argument will be the path + * \param path the path of the metric to visit + */ + template + void visit_one(const metric_path& path, THandler&& handler) const; + + /** + * \brief a convenience wrapper around \refitem metrics_registry::visit_registered_metrics + */ + template + void visit_all(THandler&& handler) const; +public: + /** + * \brief Construct a publisher that will publish from the specified registry + * + * \param registry the registry from which the publisher will publish + */ + constexpr metrics_publisher(metrics_registry& registry) noexcept; +}; + +} + +// ensures there's no undefined values - will pull in impl after the registry is defined if we didn't include it first +#include "metrics_registry.hpp" + +#endif //CXXMETRICS_PUBLISHER_HPP diff --git a/cxxmetrics/publisher_impl.hpp b/cxxmetrics/publisher_impl.hpp new file mode 100644 index 0000000..d184c3e --- /dev/null +++ b/cxxmetrics/publisher_impl.hpp @@ -0,0 +1,160 @@ +#ifndef CXXMETRICS_PUBLISHER_IMPL_HPP +#define CXXMETRICS_PUBLISHER_IMPL_HPP + +#include "publisher.hpp" +#include "metrics_registry.hpp" + +namespace cxxmetrics +{ + +template +constexpr metrics_publisher::metrics_publisher(metrics_registry ®istry) noexcept : + registry_(registry) +{} + +template +const publish_options& metrics_publisher::effective_options(basic_registered_metric& metric) const +{ + if (!has_data_for(metric)) + return registry_.publish_options(); + + // In the absence of data being removed in other threads, (removing data isn't supported) this is safe + return const_cast*>(this)->get_data_for(metric); +} + +template +template +typename std::enable_if::value, TDataType>::type& +metrics_publisher::get_data(TBuildArgs&&... args) +{ + return registry_.template get_publish_data(std::forward(args)...); +} + +template +template +typename std::enable_if::value, TDataType>::type* +metrics_publisher::get_data_for(const metric_path& path, TBuildArgs&&... args) +{ + auto res = registry_.try_get(path); + if (res == nullptr) + return nullptr; + + return &get_data_for(*res, std::forward(args)...); +} + +template +template +typename std::enable_if::value, TDataType>::type& +metrics_publisher::get_data_for(basic_registered_metric& metric, TBuildArgs&&... args) +{ + return metric.template get_or_create_publish_data(std::forward(args)...); +} + +template +template +typename std::enable_if::value, bool>::type +metrics_publisher::has_data_for(const metric_path& path) const +{ + auto res = registry_.try_get(path); + if (res == nullptr) + return false; + + return has_data_for(*res); +} + +template +template +typename std::enable_if::value, bool>::type +metrics_publisher::has_data_for(const basic_registered_metric& metric) const +{ + return metric.template try_get_publish_data() != nullptr; +} + +template +bool metrics_publisher::has_metric(const metric_path& path) const +{ + return registry_.try_get(path) != nullptr; +} + +template +std::string metrics_publisher::metric_type(const basic_registered_metric& metric) const +{ + auto result = metric.type(); + + // try to parse this string + int templdepth = 0; + std::size_t start = 0; + std::size_t end = 0; + + bool hadcolon = false; + for (std::size_t i = 0; i < result.length(); i++) + { + char c = result[i]; + if (c == ':') + { + if (templdepth) + continue; + + if (hadcolon) + { + start = i+1; + end = 0; + } + else + hadcolon = true; + + continue; + } + + hadcolon = false; + switch (c) + { + case '<': + if (++templdepth == 1) + end = i; + continue; + case '>': + --templdepth; + continue; + } + } + + if (end == 0) + return result.substr(start); + if (end <= start) + return {}; + + return result.substr(start, end - start); +} + +template +std::string metrics_publisher::metric_type(const metric_path& path) const +{ + auto res = registry_.try_get(path); + if (res == nullptr) + return {}; + + return metric_type(*res); +} + +template +template +void metrics_publisher::visit_one(const metric_path& path, THandler&& handler) const +{ + auto res = registry_.try_get(path); + if (res == nullptr) + return; + + handler(path, *res); +} + +template +template +void metrics_publisher::visit_all(THandler&& handler) const +{ + registry_.visit_registered_metrics(std::forward(handler)); +} + +} + +#endif //CXXMETRICS_PUBLISHER_IMPL_HPP diff --git a/src/ringbuf.hpp b/cxxmetrics/ringbuf.hpp similarity index 100% rename from src/ringbuf.hpp rename to cxxmetrics/ringbuf.hpp diff --git a/src/simple_reservoir.hpp b/cxxmetrics/simple_reservoir.hpp similarity index 100% rename from src/simple_reservoir.hpp rename to cxxmetrics/simple_reservoir.hpp diff --git a/src/skiplist.hpp b/cxxmetrics/skiplist.hpp similarity index 100% rename from src/skiplist.hpp rename to cxxmetrics/skiplist.hpp diff --git a/src/sliding_window.hpp b/cxxmetrics/sliding_window.hpp similarity index 100% rename from src/sliding_window.hpp rename to cxxmetrics/sliding_window.hpp diff --git a/src/snapshots.hpp b/cxxmetrics/snapshots.hpp similarity index 96% rename from src/snapshots.hpp rename to cxxmetrics/snapshots.hpp index 263f707..b4da166 100644 --- a/src/snapshots.hpp +++ b/cxxmetrics/snapshots.hpp @@ -4,6 +4,7 @@ #include #include #include +#include "meta.hpp" #include "metric_value.hpp" namespace cxxmetrics @@ -185,9 +186,9 @@ class meter_snapshot : public average_value_snapshot class quantile { long double value_; - static constexpr uint64_t max = static_cast(~(static_cast(0))); + static constexpr templates::sortable_template_type max = static_cast(~(static_cast(0))); public: - using value = uint64_t; + using value = templates::sortable_template_type; constexpr quantile(value v) : value_((v * 100.0) / max) @@ -199,16 +200,26 @@ class quantile { } + /** + * \brief probably best not to use this. It exists for the template parameters to work until C++ supports user type template params + */ constexpr operator value() { return (max / 100.0) * value_; } - constexpr operator long double() + /** + * \brief Get the percentile that this quantile represents + */ + constexpr long double percentile() const { return value_; } + constexpr operator long double() + { + return percentile(); + } }; /** diff --git a/src/tag_collection.hpp b/cxxmetrics/tag_collection.hpp similarity index 90% rename from src/tag_collection.hpp rename to cxxmetrics/tag_collection.hpp index 43de7f2..bc5f4ce 100644 --- a/src/tag_collection.hpp +++ b/cxxmetrics/tag_collection.hpp @@ -36,7 +36,7 @@ class tag_collection bool operator!=(const tag_collection& other) const; }; -bool tag_collection::operator==(const cxxmetrics::tag_collection &other) const +inline bool tag_collection::operator==(const cxxmetrics::tag_collection &other) const { if (tags_.size() != other.tags_.size()) return false; @@ -51,7 +51,7 @@ bool tag_collection::operator==(const cxxmetrics::tag_collection &other) const return true; } -bool tag_collection::operator!=(const cxxmetrics::tag_collection &other) const +inline bool tag_collection::operator!=(const cxxmetrics::tag_collection &other) const { return !operator==(other); } diff --git a/src/time.hpp b/cxxmetrics/time.hpp similarity index 92% rename from src/time.hpp rename to cxxmetrics/time.hpp index b61cc3e..5672202 100644 --- a/src/time.hpp +++ b/cxxmetrics/time.hpp @@ -15,9 +15,9 @@ class time_window constexpr time_window(unsigned long long value) : value_(value) {} - constexpr operator templates::duration_type() + constexpr operator templates::sortable_template_type() { - return (templates::duration_type) value_; + return (templates::sortable_template_type) value_; } }; @@ -28,7 +28,7 @@ class time_window class period { public: - using value = templates::duration_type; + using value = templates::sortable_template_type; private: const value value_; @@ -37,9 +37,9 @@ class period constexpr period(value v) : value_(v) {} - constexpr operator templates::duration_type() const + constexpr operator templates::sortable_template_type() const { - return (templates::duration_type) value_; + return (templates::sortable_template_type) value_; } constexpr std::chrono::steady_clock::duration to_duration() const diff --git a/src/timer.hpp b/cxxmetrics/timer.hpp similarity index 100% rename from src/timer.hpp rename to cxxmetrics/timer.hpp diff --git a/src/uniform_reservoir.hpp b/cxxmetrics/uniform_reservoir.hpp similarity index 100% rename from src/uniform_reservoir.hpp rename to cxxmetrics/uniform_reservoir.hpp diff --git a/src/meta.hpp b/src/meta.hpp deleted file mode 100644 index db19430..0000000 --- a/src/meta.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef CXXMETRICS_META_HPP -#define CXXMETRICS_META_HPP - -namespace cxxmetrics -{ - -namespace templates -{ - -using duration_type = unsigned long long; - -template -struct duration_collection; - -template -struct static_concat; - -template -struct static_concat, duration_collection, TTail...> -{ - using type = typename static_concat, TTail...>::type; -}; - -template -struct static_concat> -{ - using type = duration_collection; -}; - -template -struct static_if_else; - -template -struct static_if_else -{ - using type = TTrue; -}; - -template -struct static_if_else -{ - using type = TFalse; -}; - -template -struct is_less -{ - static constexpr bool value = (TLeft < TRight); -}; - -template -struct is_greater -{ - static constexpr bool value = (TLeft > TRight); -}; - -template -struct are_equal -{ - static constexpr bool value = (TLeft == TRight); -}; - -template -struct static_partition; - -template -struct static_partition> -{ -private: - using left_ = typename static_partition>::left; - using middle_ = typename static_partition>::middle; - using right_ = typename static_partition>::right; -public: - using left = typename static_if_else::value, typename static_concat, left_>::type, left_>::type; - using middle = typename static_if_else::value, typename static_concat, middle_>::type, middle_>::type; - using right = typename static_if_else::value, typename static_concat, right_>::type, right_>::type; -}; - -template -struct static_partition> -{ - using left = duration_collection<>; - using middle = duration_collection; - using right = duration_collection<>; -}; - -template -struct static_sorter; - -template -struct static_sorter> -{ -private: - using partitioned_ = static_partition>; - - using left_ = typename static_sorter::type; - using middle_ = typename partitioned_::middle; - using right_ = typename static_sorter::type; -public: - using type = typename static_concat::type; -}; - -template<> -struct static_sorter> -{ - using type = duration_collection<>; -}; - -template -struct static_uniq; - -template -struct static_uniq> -{ - using type = typename static_if_else::value, typename static_uniq>::type, typename static_concat, typename static_uniq>::type>::type>::type; -}; - -template -struct static_uniq> -{ - using type = duration_collection; -}; - -template<> -struct static_uniq> -{ - using type = duration_collection<>; -}; - -template -struct sort_unique -{ -private: - using list_ = duration_collection; - using sorted_ = typename static_sorter::type; -public: - using type = typename static_uniq::type; -}; - -} // templates - -} // cxxmetrics - -#endif //CXXMETRICS_META_HPP diff --git a/src/publisher.hpp b/src/publisher.hpp deleted file mode 100644 index 0651078..0000000 --- a/src/publisher.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef CXXMETRICS_PUBLISHER_HPP -#define CXXMETRICS_PUBLISHER_HPP - -#include "metrics_registry.hpp" - -namespace cxxmetrics -{ - -/** - * \brief The base class for the metrics publisher - * - * The publisher simply uses the standard visit pattern to access metrics and publish. - * However, inheriting from this class allows the publisher to gain additional insight into - * metrics that are registered in the underlying registry. - * - * \tparam TMetricRepo the repository being used by the registry - */ -template -class metric_publisher -{ - metrics_registry& registry_; - -protected: - /** - * \brief Get a piece of data from the registry. Generally, this will be a type specific to the publisher - * - * This allows the publisher to store data in the registry that is specific to the publisher. If the data is - * already registered, the returned value will be the registered data. Otherwise, the object is constructed - * and stored in the registry using the arguments specified. - * - * \tparam TDataType the type of data to get from the registry - * \tparam TBuildArgs the types of arguments that will be used to construct the data if it doesn't exist - * - * \return the registered data of the requested type - */ - template - TDataType& get_data(TBuildArgs&&... args); - - /** - * \brief Get a piece of data from the registry for a specific metric. - * - * This returns a pointer which has the potential for being null. This can happen when the metric_path being - * requested isn't registered in the registry (thus there's nothing to attach data to). - * - * \tparam TDataType the type of data to attach to the metric - * \tparam TBuildArgs The types of arguments that will be used to construct the data if it doesn't exist but the path does - * - * \param path the path of the metric to attach the data to - * - * \return the attached data if the path existed - */ - template - TDataType* get_data_for(const metric_path& path); - - /** - * \brief Get whether or not there is a metric at the specified path - * - * \return whether or not there is a metric at the specified path - */ - bool has_metric(const metric_path& path) const; - - /** - * \brief Get a string representation of the type of metric stored at the specified path - * - * If there is no metric stored at the path, this will return an empty string - * - * \param path the path of the metric to query for the type - * - * \return the type of metric at the specified path - */ - std::string metric_type(const metric_path& path) const; - - /** - * \brief Visit just a single metric in the registry - * - * The handler follows the same signature as the visitor on the registry at large - * - * \note this method may hold a lock on the registry data at some level. It is therefore advised not to use any other registry access routines inside of the handler - * - * \tparam THandler the handler to call if the registry is found at the specified path, the first argument will be the path - * \param path the path of the metric to visit - */ - template - void visit_one(const metric_path& path, THandler&& handler) const; - - /** - * \brief a convenience wrapper around \refitem metrics_registry::visit_registered_metrics - */ - template - void visit_all(THandler&& handler); -public: - /** - * \brief Construct a publisher that will publish from the specified registry - * - * \param registry the registry from which the publisher will publish - */ - metric_publisher(metrics_registry& registry); -}; - -} - -#endif //CXXMETRICS_PUBLISHER_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 122d18d..d51f8e2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES meter_test.cpp metrics_registry_test.cpp #pool_test.cpp + publisher_tests.cpp reservoir_test.cpp ringbuf_test.cpp #skiplist_test.cpp diff --git a/test/counter_test.cpp b/test/counter_test.cpp index e3c4fe5..ad14613 100644 --- a/test/counter_test.cpp +++ b/test/counter_test.cpp @@ -1,5 +1,5 @@ #include -#include +#include using namespace cxxmetrics; diff --git a/test/ewma_test.cpp b/test/ewma_test.cpp index c46c038..83e132c 100644 --- a/test/ewma_test.cpp +++ b/test/ewma_test.cpp @@ -1,6 +1,6 @@ #include -#include #include +#include #include "helpers.hpp" using namespace std::chrono_literals; @@ -33,13 +33,13 @@ TEST_CASE("EWMA backwards clock skips", "[ewma]") TEST_CASE("EMWA calculates fixed rate", "[ewma]") { - unsigned clock = 1; + unsigned clock = 0; mock_ewma<10, 1> e(clock); for (int i = 0; i <= 10; i++) { - e.mark(7); clock++; + e.mark(7); } REQUIRE(round(e.rate()) == 7); @@ -85,13 +85,13 @@ TEST_CASE("EWMA calculates fixed rate threads", "[ewma]") TEST_CASE("EWMA calculates after jump past window", "[ewma]") { - unsigned clock = 1; + unsigned clock = 0; mock_ewma<10, 1> e(clock); for (int i = 0; i <= 10; i++) { - e.mark(7); clock++; + e.mark(7); } REQUIRE(round(e.rate()) == 7); diff --git a/test/gauge_test.cpp b/test/gauge_test.cpp index 0487763..0c8bbe1 100644 --- a/test/gauge_test.cpp +++ b/test/gauge_test.cpp @@ -1,5 +1,5 @@ #include -#include +#include using namespace std; using namespace cxxmetrics; diff --git a/test/helpers.hpp b/test/helpers.hpp index 4e1adb6..a13d884 100644 --- a/test/helpers.hpp +++ b/test/helpers.hpp @@ -1,7 +1,3 @@ -// -// Created by keef on 6/16/17. -// - #ifndef CXXMETRICS_HELPERS_HPP #define CXXMETRICS_HELPERS_HPP diff --git a/test/histogram_test.cpp b/test/histogram_test.cpp index cacc7bc..2d67aeb 100644 --- a/test/histogram_test.cpp +++ b/test/histogram_test.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include using namespace cxxmetrics; using namespace cxxmetrics_literals; diff --git a/test/internal/atomic_lifo_test.cpp b/test/internal/atomic_lifo_test.cpp index fa62998..200625b 100644 --- a/test/internal/atomic_lifo_test.cpp +++ b/test/internal/atomic_lifo_test.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/test/meter_test.cpp b/test/meter_test.cpp index 8823759..d15363c 100644 --- a/test/meter_test.cpp +++ b/test/meter_test.cpp @@ -1,6 +1,6 @@ #include -#include #include +#include #include #include "helpers.hpp" @@ -44,7 +44,7 @@ TEST_CASE("Meter rates are passed on", "[meter]") internal::_meter_impl m(clk); - for(int i = 0; i < 100; i++) + for(int i = 0; i < 10; i++) { m.mark(10); clock++; @@ -56,14 +56,15 @@ TEST_CASE("Meter rates are passed on", "[meter]") REQUIRE(round(m.get_rate<50>()) == 10); REQUIRE(round(m.mean()) == 10); - clock += 10; - m.mark(100); + clock += 100; + m.mark(1000); + clock += 1; + REQUIRE_THAT(m.mean(), Catch::WithinULP(1100.0 / 111.0, 1)); REQUIRE(m.get_rate<1>() > m.get_rate<8>()); REQUIRE(m.get_rate<8>() > m.get_rate<20>()); REQUIRE(m.get_rate<20>() > m.get_rate<50>()); - REQUIRE_THAT(m.mean(), Catch::WithinULP(1100.0 / 111.0, 1)); } TEST_CASE("Meter snapshot", "[meter]") diff --git a/test/metrics_registry_test.cpp b/test/metrics_registry_test.cpp index 551ae6b..5cd4ad9 100644 --- a/test/metrics_registry_test.cpp +++ b/test/metrics_registry_test.cpp @@ -1,9 +1,9 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include using namespace std::chrono_literals; using namespace cxxmetrics; @@ -94,7 +94,6 @@ TEST_CASE("Registry supports all the types", "[metrics_registry]") subject.histogram("H"_m/"istogramW", sliding_window_reservoir(100s)); subject.meter<1_sec, 1_min, 1_sec, 5_min>("Meter"); subject.timer<1_sec, std::chrono::system_clock, simple_reservoir, 1_min, 5_min>("TimerVerbose"); - subject.timer<1_sec, uniform_reservoir, 1024, 1_min, 5_min>("TimerSimpler"); REQUIRE(subject.register_existing("MyCounter"_m/"Alias", myCounter)); subject.visit_registered_metrics([&instances, &names](const metric_path& path, basic_registered_metric& metric) { @@ -104,7 +103,7 @@ TEST_CASE("Registry supports all the types", "[metrics_registry]") }); }); - REQUIRE(names == 10); + REQUIRE(names == 9); REQUIRE(instances == names); names = 0; @@ -198,7 +197,7 @@ TEST_CASE("Registry meter aggregation", "[metrics_registry]") metric_value m100(0); metric_value m200(0); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 50; i++) { std::this_thread::sleep_for(interval.to_duration()); m1.mark(1000); diff --git a/test/pool_test.cpp b/test/pool_test.cpp index 1892cd8..b8760a7 100644 --- a/test/pool_test.cpp +++ b/test/pool_test.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include using namespace cxxmetrics::internal; using namespace std; diff --git a/test/publisher_tests.cpp b/test/publisher_tests.cpp new file mode 100644 index 0000000..d8aa6a4 --- /dev/null +++ b/test/publisher_tests.cpp @@ -0,0 +1,353 @@ +#include +#include +#include +#include +#include + +using namespace cxxmetrics; +using namespace cxxmetrics_literals; +using namespace std::chrono_literals; + +template +class test_publisher : public metrics_publisher +{ +public: + constexpr test_publisher(metrics_registry& registry) noexcept : + metrics_publisher(registry) + { } + + template + T& data(TArgs&&... args) + { + return this->template get_data(std::forward(args)...); + } + + template + T* metric_data(const metric_path& name, TArgs&&... args) + { + return this->template get_data_for(name, std::forward(args)...); + } + + template + bool has_metric_data(const metric_path& name) + { + return this->template has_data_for(name); + } + + bool exists(const metric_path& name) const + { + return this->has_metric(name); + } + + std::string type_of(const metric_path& name) const + { + return this->metric_type(name); + } + + std::string type_of(const basic_registered_metric& metric) const + { + return this->metric_type(metric); + } + + template + void visit_metric(const metric_path& path, THandler&& handler) const + { + return this->visit_one(path, std::forward(handler)); + } + + template + void visit_registry(THandler&& handler) const + { + return this->visit_all(std::forward(handler)); + } + + const auto& opts(basic_registered_metric& metric) + { + return this->effective_options(metric); + } +}; + +struct test_pubdata : public basic_publish_options +{ + int value_; +public: + test_pubdata() : + value_(0) + { } + + test_pubdata(int v) : + value_(v) + { } + + int value() const + { + return value_; + } +}; + +TEST_CASE("Publisher can get custom publish data", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + + auto& v = subject.data(10); + REQUIRE(v.value() == 10); + + auto& e = subject.data(2000); + REQUIRE(e.value() == 10); + + REQUIRE(&e == &v); +} + +TEST_CASE("Publisher can get metric-attached custom publish data", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + + auto path = "Test"_m/"Counter"/"blah"; + + REQUIRE(!subject.exists(path)); + r.counter(path); + REQUIRE(subject.exists(path)); + + REQUIRE(!subject.has_metric_data(path)); + auto v = subject.metric_data(path, 10); + REQUIRE(v != nullptr); + REQUIRE(subject.has_metric_data(path)); + REQUIRE(v->value() == 10); + + auto e = subject.metric_data(path, 2000); + REQUIRE(e->value() == 10); + REQUIRE(e == v); + + REQUIRE(subject.metric_data(path/"other") == nullptr); + REQUIRE(!subject.has_metric_data(path/"other")); +} + +TEST_CASE("Publisher metric types are correctly resolved", "[publisher]") +{ + int gaugeProvider = 7; + int gp2 = 8; + + metrics_registry<> r; + test_publisher<> subject(r); + auto myCounter = r.counter("MyCounter"); + REQUIRE(subject.type_of("MyCounter") == "counter"); + + r.ewma<1_min>("MyEWMA"); + REQUIRE(subject.type_of("MyEWMA") == "ewma"); + + r.gauge("Gauge"/"Ref"_m, gaugeProvider); + r.gauge("Gauge"/"Ptr"_m, &gp2); + REQUIRE(subject.type_of("Gauge"/"Ref"_m) == "gauge"); + REQUIRE(subject.type_of("Gauge"/"Ptr"_m) == "gauge"); + + r.histogram("HistogramS", simple_reservoir()); + r.histogram("HistogramU", uniform_reservoir()); + r.histogram("HistogramW", sliding_window_reservoir(100s)); + REQUIRE(subject.type_of("HistogramS") == "histogram"); + REQUIRE(subject.type_of("HistogramU") == "histogram"); + REQUIRE(subject.type_of("HistogramW") == "histogram"); + + r.meter<1_sec, 1_min, 1_sec, 5_min>("Meter"); + REQUIRE(subject.type_of("Meter") == "meter"); + + r.timer<1_sec, std::chrono::system_clock, simple_reservoir, 1_min, 5_min>("TimerVerbose"); + REQUIRE(subject.type_of("TimerVerbose") == "timer"); + + r.register_existing("MyCounter"/"Alias"_m, myCounter); + REQUIRE(subject.type_of("MyCounter"/"Alias"_m) == "counter"); + + REQUIRE(subject.type_of("NonExistent"/"Metric"_m).empty()); +} + +TEST_CASE("Publisher visiting one metric only visits one metric", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + *r.counter("MyCounter") += 99; + r.ewma<1_min>("MyEWMA"); + r.meter<1_sec, 1_min, 1_sec, 5_min>("Meter"); + + int count = 0; + metric_value value(0); + auto visitor = [&count, &value](const metric_path& path, basic_registered_metric& metric) { + ++count; + metric.aggregate([&value](const value_snapshot& ss) { value = ss.value(); }); + }; + + subject.visit_metric("MyCounter", visitor); + REQUIRE(count == 1); + REQUIRE(value == metric_value(99)); + + subject.visit_metric("Nonexistent"/"Metric"_m, visitor); + REQUIRE(count == 1); +} + +TEST_CASE("Publisher visit_all sanity check", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + r.counter("MyCounter"); + r.ewma<1_min>("MyEWMA"); + r.meter<1_sec, 1_min, 1_sec, 5_min>("Meter"); + + int count = 0; + auto visitor = [&count](const metric_path& path, basic_registered_metric& metric) { + ++count; + }; + + subject.visit_registry(visitor); + REQUIRE(count == 3); +} + +TEST_CASE("Publisher can get value publish options", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + *r.counter("MyCounter") += 100; + r.ewma<1_min>("MyEWMA"); + r.meter<1_sec, 1_min, 1_sec, 5_min>("Meter"); + + int count = 0; + metric_value value(0); + auto visitor = [&](const metric_path& path, basic_registered_metric& metric) { + ++count; + metric.aggregate([&](const value_snapshot& ss) { + if (subject.type_of(metric) != "counter") + return; + + auto& opts = subject.opts(metric); + if (opts.value_options().scale()) + value = ss.value() * metric_value(opts.value_options().scale().factor()); + else + value = ss.value(); + }); + }; + + subject.visit_registry(visitor); + REQUIRE(value == metric_value(100)); + REQUIRE(count == 3); + + r.publish_options(publish_options(value_publish_options(0.5))); + subject.visit_registry(visitor); + REQUIRE(value == metric_value(50)); + REQUIRE(count == 6); +} + +TEST_CASE("Publisher can handle metric overrides on publish options", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + auto ctr = r.counter("MyCounter1"); + *ctr += 1000; + r.register_existing("MyCounter2", ctr); + + int count = 0; + metric_value value(0); + auto visitor = [&](const metric_path& path, basic_registered_metric& metric) { + ++count; + metric.aggregate([&](const value_snapshot& ss) { + if (subject.type_of(metric) != "counter") + return; + + auto& opts = subject.opts(metric); + if (opts.value_options().scale()) + value += (ss.value() * metric_value(opts.value_options().scale().factor())); + else + value += ss.value(); + }); + }; + + r.publish_options("MyCounter2", publish_options(value_publish_options(0.5))); + + subject.visit_registry(visitor); + REQUIRE(value == metric_value(1500)); + REQUIRE(count == 2); +} + +TEST_CASE("Publisher can get meter publish options", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + *r.counter("MyCounter") += 100; + auto& m = *r.meter<1_sec, 1_min, 1_sec, 5_min>("Meter"); + + for (int i = 0; i < 50; i++) + m.mark(10000); + + int count = 0; + metric_value value(0); + auto visitor = [&](const metric_path& path, basic_registered_metric& metric) { + ++count; + metric.aggregate([&](const meter_snapshot& ss) { + auto& opts = subject.opts(metric); + if (opts.meter_options().include_mean()) + value = ss.value(); + else + value = 0; + }); + }; + + subject.visit_registry(visitor); + REQUIRE(round(value) >= 1); + REQUIRE(count == 2); + + r.publish_options(publish_options(meter_publish_options(false))); + subject.visit_registry(visitor); + REQUIRE(value == metric_value(0)); + REQUIRE(count == 4); +} + +TEST_CASE("Publisher can get histogram options", "[publisher]") +{ + metrics_registry<> r; + test_publisher<> subject(r); + *r.counter("MyCounter") += 100; + auto& h1 = *r.histogram("histogram", simple_reservoir()); + + for (int i = 0; i < 50; i++) + h1.update(i * 2000); + + int count = 0; + metric_value value(0); + std::unordered_map values; + auto visitor = [&](const metric_path& path, basic_registered_metric& metric) { + ++count; + metric.aggregate([&](const histogram_snapshot& ss) { + auto& opts = subject.opts(metric); + if (opts.histogram_options().include_count()) + value = ss.count(); + else + value = 0; + + auto& qvisitors = opts.histogram_options().quantiles(); + qvisitors.visit(ss, [&](quantile q, const metric_value& v) + { + values.emplace(q, round(v)); + }); + }); + }; + + auto print_percentiles = [&](const auto& dict) { + INFO("Percentiles: "); + for (const auto &pair : dict) + INFO(" " << pair.first << ": " << pair.second); + }; + + subject.visit_registry(visitor); + REQUIRE(value == metric_value(50)); + REQUIRE(values.size() == 3); // defaults + REQUIRE(count == 2); + + print_percentiles(values); + values.clear(); + + r.publish_options(publish_options(histogram_publish_options(quantile_options<99_p>(), false))); + subject.visit_registry(visitor); + + print_percentiles(values); + REQUIRE(value == metric_value(0)); + REQUIRE(values.size() == 1); + REQUIRE(count == 4); +} diff --git a/test/reservoir_test.cpp b/test/reservoir_test.cpp index 9d604ce..fb2d2da 100644 --- a/test/reservoir_test.cpp +++ b/test/reservoir_test.cpp @@ -1,7 +1,7 @@ #include -#include -#include -#include +#include +#include +#include #include "helpers.hpp" using namespace std; diff --git a/test/ringbuf_test.cpp b/test/ringbuf_test.cpp index efa4011..0c181d5 100644 --- a/test/ringbuf_test.cpp +++ b/test/ringbuf_test.cpp @@ -1,5 +1,5 @@ #include -#include +#include using namespace std; using namespace cxxmetrics::internal; diff --git a/test/skiplist_test.cpp b/test/skiplist_test.cpp index b64b01a..0185635 100644 --- a/test/skiplist_test.cpp +++ b/test/skiplist_test.cpp @@ -5,7 +5,7 @@ #include -#include "skiplist.hpp" +#include "cxxmetrics/skiplist.hpp" using namespace std; using namespace cxxmetrics; diff --git a/test/timer_test.cpp b/test/timer_test.cpp index d0f71a2..66d5762 100644 --- a/test/timer_test.cpp +++ b/test/timer_test.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include using namespace cxxmetrics; using namespace cxxmetrics_literals;