// See the file "COPYING" in the main distribution directory for copyright. #pragma once #include #include #include #include "zeek/Span.h" #include "zeek/telemetry/MetricFamily.h" #include "zeek/telemetry/telemetry.bif.h" #include "prometheus/family.h" #include "prometheus/histogram.h" namespace zeek::telemetry { template class BaseHistogram { public: using Handle = prometheus::Histogram; using FamilyType = prometheus::Family; /** * Increments all buckets with an upper bound less than or equal to @p value * by one and adds @p value to the total sum of all observed values. */ void Observe(BaseType value) noexcept { handle.Observe(value); } /// @return The sum of all observed values. BaseType Sum() const noexcept { auto metric = handle.Collect(); return static_cast(metric.histogram.sample_sum); } bool operator==(const BaseHistogram& rhs) const noexcept { return &handle == &rhs.handle; } bool operator!=(const BaseHistogram& rhs) const noexcept { return &handle != &rhs.handle; } bool CompareLabels(const prometheus::Labels& lbls) const { return labels == lbls; } protected: explicit BaseHistogram(FamilyType& family, const prometheus::Labels& labels, prometheus::Histogram::BucketBoundaries bounds) noexcept : handle(family.Add(labels, std::move(bounds))), labels(labels) {} Handle& handle; prometheus::Labels labels; }; /** * A handle to a metric that represents an aggregable distribution of observed * measurements with integer precision. Sorts individual measurements into * configurable buckets. */ class IntHistogram : public BaseHistogram { public: static inline const char* OpaqueName = "IntHistogramMetricVal"; explicit IntHistogram(FamilyType& family, const prometheus::Labels& labels, prometheus::Histogram::BucketBoundaries bounds) noexcept : BaseHistogram(family, labels, std::move(bounds)) {} IntHistogram() = delete; IntHistogram(const IntHistogram&) noexcept = delete; IntHistogram& operator=(const IntHistogram&) noexcept = delete; }; /** * A handle to a metric that represents an aggregable distribution of observed * measurements with integer precision. Sorts individual measurements into * configurable buckets. */ class DblHistogram : public BaseHistogram { public: static inline const char* OpaqueName = "DblHistogramMetricVal"; explicit DblHistogram(FamilyType& family, const prometheus::Labels& labels, prometheus::Histogram::BucketBoundaries bounds) noexcept : BaseHistogram(family, labels, std::move(bounds)) {} DblHistogram() = delete; DblHistogram(const DblHistogram&) noexcept = delete; DblHistogram& operator=(const DblHistogram&) noexcept = delete; }; template class BaseHistogramFamily : public MetricFamily, public std::enable_shared_from_this> { public: /** * Returns the metrics handle for given labels, creating a new instance * lazily if necessary. */ std::shared_ptr GetOrAdd(Span labels) { prometheus::Labels p_labels = BuildPrometheusLabels(labels); auto check = [&](const std::shared_ptr& histo) { return histo->CompareLabels(p_labels); }; if ( auto it = std::find_if(histograms.begin(), histograms.end(), check); it != histograms.end() ) return *it; auto histogram = std::make_shared(family, p_labels, boundaries); histograms.push_back(histogram); return histogram; } /** * @copydoc GetOrAdd */ std::shared_ptr GetOrAdd(std::initializer_list labels) { return GetOrAdd(Span{labels.begin(), labels.size()}); } std::vector Collect() const override { static auto string_vec_type = zeek::id::find_type("string_vec"); static auto double_vec_type = zeek::id::find_type("double_vec"); static auto count_vec_type = zeek::id::find_type("index_vec"); static auto histogram_metric_type = zeek::id::find_type("Telemetry::HistogramMetric"); static auto labels_idx = histogram_metric_type->FieldOffset("labels"); static auto values_idx = histogram_metric_type->FieldOffset("values"); static auto count_values_idx = histogram_metric_type->FieldOffset("count_values"); static auto observations_idx = histogram_metric_type->FieldOffset("observations"); static auto count_observations_idx = histogram_metric_type->FieldOffset("count_observations"); static auto sum_idx = histogram_metric_type->FieldOffset("sum"); static auto count_sum_idx = histogram_metric_type->FieldOffset("count_sum"); static auto opts_idx = histogram_metric_type->FieldOffset("opts"); static auto opts_rt = zeek::id::find_type("Telemetry::MetricOpts"); static auto bounds_idx = opts_rt->FieldOffset("bounds"); static auto count_bounds_idx = opts_rt->FieldOffset("count_bounds"); RecordValPtr opts_record = GetMetricOptsRecord(); std::vector records; for ( const auto& h : histograms ) { auto label_values_vec = make_intrusive(string_vec_type); for ( const auto& [label_key, label] : h->Labels() ) { // We don't include the endpoint key/value unless it's a prometheus request if ( label_key != "endpoint" ) label_values_vec->Append(make_intrusive(label)); } auto r = make_intrusive(histogram_metric_type); r->Assign(labels_idx, label_values_vec); r->Assign(opts_idx, opts_record); auto histo_data = h->Collect(); auto counts_double_vec = make_intrusive(double_vec_type); auto counts_count_vec = make_intrusive(count_vec_type); uint64_t last = 0.0; for ( const auto& b : histo_data.bucket ) { counts_double_vec->Append( zeek::make_intrusive(static_cast(b.cumulative_count - last))); counts_count_vec->Append(val_mgr->Count(b.cumulative_count - last)); last = b.cumulative_count; } // TODO: these could be generated at creation time instead of repeatedly here auto bounds_vec = make_intrusive(double_vec_type); auto count_bounds_vec = make_intrusive(count_vec_type); for ( auto b : boundaries ) { bounds_vec->Append(zeek::make_intrusive(b)); count_bounds_vec->Append(val_mgr->Count(static_cast(b))); } bounds_vec->Append(zeek::make_intrusive(std::numeric_limits::infinity())); count_bounds_vec->Append(val_mgr->Count(std::numeric_limits::infinity())); r->Assign(values_idx, counts_double_vec); r->Assign(observations_idx, zeek::make_intrusive(static_cast(histo_data.sample_count))); r->Assign(sum_idx, zeek::make_intrusive(histo_data.sample_sum)); RecordValPtr local_opts_record = r->GetField(opts_idx); local_opts_record->Assign(bounds_idx, bounds_vec); if constexpr ( ! std::is_same_v ) { r->Assign(count_values_idx, counts_count_vec); r->Assign(count_observations_idx, val_mgr->Count(histo_data.sample_count)); r->Assign(count_sum_idx, val_mgr->Count(static_cast(histo_data.sample_sum))); r->Assign(count_bounds_idx, count_bounds_vec); } records.push_back(std::move(r)); } return records; } protected: BaseHistogramFamily(std::string_view prefix, std::string_view name, Span labels, Span default_upper_bounds, std::string_view helptext, std::shared_ptr registry, std::string_view unit = "") : MetricFamily(prefix, name, labels, helptext, unit, false), family(prometheus::BuildHistogram().Name(full_name).Help(std::string{helptext}).Register(*registry)) { std::copy(default_upper_bounds.begin(), default_upper_bounds.end(), std::back_inserter(boundaries)); } prometheus::Family& family; prometheus::Histogram::BucketBoundaries boundaries; std::vector> histograms; }; /** * Manages a collection of IntHistogram metrics. */ class IntHistogramFamily : public BaseHistogramFamily { public: static inline const char* OpaqueName = "IntHistogramMetricFamilyVal"; IntHistogramFamily(std::string_view prefix, std::string_view name, Span labels, Span default_upper_bounds, std::string_view helptext, std::shared_ptr registry, std::string_view unit = "") : BaseHistogramFamily(prefix, name, labels, default_upper_bounds, helptext, std::move(registry), unit) {} IntHistogramFamily(const IntHistogramFamily&) noexcept = delete; IntHistogramFamily& operator=(const IntHistogramFamily&) noexcept = delete; zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::INT_HISTOGRAM; } }; /** * Manages a collection of DblHistogram metrics. */ class DblHistogramFamily : public BaseHistogramFamily { public: static inline const char* OpaqueName = "DblHistogramMetricFamilyVal"; DblHistogramFamily(std::string_view prefix, std::string_view name, Span labels, Span default_upper_bounds, std::string_view helptext, std::shared_ptr registry, std::string_view unit = "") : BaseHistogramFamily(prefix, name, labels, default_upper_bounds, helptext, std::move(registry), unit) {} DblHistogramFamily(const DblHistogramFamily&) noexcept = delete; DblHistogramFamily& operator=(const DblHistogramFamily&) noexcept = delete; zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM; } }; namespace detail { template struct HistogramOracle { static_assert(std::is_same::value, "Histogram only supports int64_t and double"); using type = IntHistogram; }; template<> struct HistogramOracle { using type = DblHistogram; }; } // namespace detail template using Histogram = typename detail::HistogramOracle::type; } // namespace zeek::telemetry