Rework everything to access the prometheus-cpp objects more directly

This commit is contained in:
Tim Wojtulewicz 2024-03-26 11:19:25 -07:00
parent ec3e68928f
commit 84aa308527
17 changed files with 469 additions and 606 deletions

View file

@ -236,7 +236,7 @@ if (ZEEK_STANDALONE)
set(zeek_exe_access PRIVATE)
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_link_libraries(zeek_exe PRIVATE util)
target_link_libraries(zeek_exe PRIVATE /usr/lib/libutil.so)
target_link_libraries(zeek_exe PRIVATE procstat)
endif ()
else ()
@ -255,8 +255,8 @@ if (TARGET zeek_lib)
set(zeek_lib_access PRIVATE)
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_link_libraries(zeek_exe PRIVATE util)
target_link_libraries(zeek_exe PRIVATE procstat)
target_link_libraries(zeek_lib PRIVATE /usr/lib/libutil.so)
target_link_libraries(zeek_lib PRIVATE procstat)
endif ()
endif ()

View file

@ -5790,11 +5790,17 @@ export {
## for any metrics involving process state (CPU, memory, etc).
prefix: string;
## The human-readable name of the metric.
## The human-readable name of the metric. This is set to the
## full prefixed name including the unit when returned from
## :zeek:see:`Telemetry::collect_metrics` or
## :zeek:see:`Telemetry::collect_histogram_metrics`.
name: string;
## The unit of the metric. Set to a blank string if this is a unit-less metric.
unit: string;
## The unit of the metric. Leave this unset for a unit-less
## metric. Will be unset when returned from
## :zeek:see:`Telemetry::collect_metrics` or
## :zeek:see:`Telemetry::collect_histogram_metrics`.
unit: string &optional;
## Documentation for this metric.
help_text: string &optional;

View file

@ -91,35 +91,35 @@ export {
global bytes_received_cf = Telemetry::register_counter_family([
$prefix="zeek",
$name="net-received-bytes",
$unit="1",
$unit="",
$help_text="Total number of bytes received",
]);
global packets_received_cf = Telemetry::register_counter_family([
$prefix="zeek",
$name="net-received-packets",
$unit="1",
$unit="",
$help_text="Total number of packets received",
]);
global packets_dropped_cf = Telemetry::register_counter_family([
$prefix="zeek",
$name="net-dropped-packets",
$unit="1",
$unit="",
$help_text="Total number of packets dropped",
]);
global link_packets_cf = Telemetry::register_counter_family([
$prefix="zeek",
$name="net-link-packets",
$unit="1",
$unit="",
$help_text="Total number of packets on the packet source link before filtering",
]);
global packets_filtered_cf = Telemetry::register_counter_family([
$prefix="zeek",
$name="net-filtered-packets",
$unit="1",
$unit="",
$help_text="Total number of packets filtered",
]);

View file

@ -47,7 +47,7 @@ void EventHandler::Call(Args* vl, bool no_remote, double ts) {
if ( ! call_count ) {
static auto eh_invocations_family =
telemetry_mgr->CounterFamily("zeek", "event-handler-invocations", {"name"},
"Number of times the given event handler was called", "1", true);
"Number of times the given event handler was called", "", true);
call_count = eh_invocations_family->GetOrAdd({{"name", name}});
}

View file

@ -419,13 +419,13 @@ Manager::Manager()
: plugin::ComponentManager<logging::Component>("Log", "Writer"),
total_log_stream_writes_family(telemetry_mgr->CounterFamily("zeek", "log-stream-writes", {"module", "stream"},
"Total number of log writes for the given stream.",
"1", true)),
"", true)),
total_log_writer_writes_family(
telemetry_mgr
->CounterFamily("zeek", "log-writer-writes", {"writer", "module", "stream", "filter-name", "path"},
"Total number of log writes passed to a concrete log writer not vetoed by stream or "
"filter policies.",
"1", true)) {
"", true)) {
rotations_pending = 0;
}

View file

@ -48,7 +48,7 @@ public:
auto active_family =
telemetry_mgr->GaugeFamily("zeek", "active-sessions", {"protocol"}, "Active Zeek Sessions");
auto total_family =
telemetry_mgr->CounterFamily("zeek", "total-sessions", {"protocol"}, "Total number of sessions", "1", true);
telemetry_mgr->CounterFamily("zeek", "total-sessions", {"protocol"}, "Total number of sessions", "", true);
auto [it, inserted] = entries.insert({protocol, Protocol{active_family, total_family, protocol}});

View file

@ -2,9 +2,9 @@ zeek_add_subdir_library(
telemetry
SOURCES
Manager.cc
MetricFamily.cc
Opaques.cc
ProcessStats.cc
Utils.cc
BIFS
telemetry.bif)

View file

@ -4,10 +4,11 @@
#include <cstdint>
#include <initializer_list>
#include <type_traits>
#include <memory>
#include "zeek/Span.h"
#include "zeek/telemetry/MetricFamily.h"
#include "zeek/telemetry/Utils.h"
#include "zeek/telemetry/telemetry.bif.h"
#include "prometheus/counter.h"
@ -30,7 +31,7 @@ public:
* Increments the value by @p amount.
* @pre `amount >= 0`
*/
void Inc(BaseType amount) noexcept { handle.Increment(amount); }
void Inc(BaseType amount) noexcept { handle->Increment(amount); }
/**
* Increments the value by 1.
@ -44,7 +45,7 @@ public:
BaseType Value() const noexcept {
// Use Collect() here instead of Value() to correctly handle metrics with
// callbacks.
auto metric = handle.Collect();
auto metric = handle->Collect();
return static_cast<BaseType>(metric.counter.value);
}
@ -54,25 +55,24 @@ public:
bool CompareLabels(const prometheus::Labels& lbls) const { return labels == lbls; }
protected:
explicit BaseCounter(FamilyType& family, const prometheus::Labels& labels,
explicit BaseCounter(FamilyType* family, const prometheus::Labels& labels,
prometheus::CollectCallbackPtr callback = nullptr) noexcept
: handle(family.Add(labels)), labels(labels) {
: handle(&(family->Add(labels))), labels(labels) {
if ( callback )
handle.AddCollectCallback(callback);
handle->AddCollectCallback(callback);
}
Handle& handle;
Handle* handle;
prometheus::Labels labels;
BaseType last_value = 0;
};
/**
* A handle to a metric that represents an integer value that can only go up.
*/
class IntCounter : public BaseCounter<uint64_t> {
class IntCounter final : public BaseCounter<uint64_t> {
public:
static inline const char* OpaqueName = "IntCounterMetricVal";
explicit IntCounter(FamilyType& family, const prometheus::Labels& labels,
explicit IntCounter(FamilyType* family, const prometheus::Labels& labels,
prometheus::CollectCallbackPtr callback = nullptr) noexcept
: BaseCounter(family, labels, callback) {}
};
@ -80,10 +80,10 @@ public:
/**
* A handle to a metric that represents a double value that can only go up.
*/
class DblCounter : public BaseCounter<double> {
class DblCounter final : public BaseCounter<double> {
public:
static inline const char* OpaqueName = "DblCounterMetricVal";
explicit DblCounter(FamilyType& family, const prometheus::Labels& labels,
explicit DblCounter(FamilyType* family, const prometheus::Labels& labels,
prometheus::CollectCallbackPtr callback = nullptr) noexcept
: BaseCounter(family, labels, callback) {}
};
@ -92,19 +92,13 @@ template<class CounterType, typename BaseType>
class BaseCounterFamily : public MetricFamily,
public std::enable_shared_from_this<BaseCounterFamily<CounterType, BaseType>> {
public:
BaseCounterFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::shared_ptr<prometheus::Registry> registry,
std::string_view unit = "", bool is_sum = false)
: MetricFamily(prefix, name, labels, helptext, unit, is_sum),
family(prometheus::BuildCounter().Name(full_name).Help(std::string{helptext}).Register(*registry)) {}
/**
* Returns the metrics handle for given labels, creating a new instance
* lazily if necessary.
*/
std::shared_ptr<CounterType> GetOrAdd(Span<const LabelView> labels,
prometheus::CollectCallbackPtr callback = nullptr) {
prometheus::Labels p_labels = BuildPrometheusLabels(labels);
prometheus::Labels p_labels = detail::BuildPrometheusLabels(labels);
auto check = [&](const std::shared_ptr<CounterType>& counter) { return counter->CompareLabels(p_labels); };
@ -124,63 +118,23 @@ public:
return GetOrAdd(Span{labels.begin(), labels.size()}, callback);
}
std::vector<std::shared_ptr<CounterType>>& GetAllCounters() { return counters; }
std::vector<RecordValPtr> Collect() const override {
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
static auto metric_record_type = zeek::id::find_type<zeek::RecordType>("Telemetry::Metric");
static auto opts_idx = metric_record_type->FieldOffset("opts");
static auto labels_idx = metric_record_type->FieldOffset("labels");
static auto value_idx = metric_record_type->FieldOffset("value");
static auto count_value_idx = metric_record_type->FieldOffset("count_value");
RecordValPtr opts_record = GetMetricOptsRecord();
std::vector<RecordValPtr> records;
for ( const auto& ctr : counters ) {
auto label_values_vec = make_intrusive<VectorVal>(string_vec_type);
for ( const auto& [label_key, label] : ctr->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<StringVal>(label));
}
auto r = make_intrusive<zeek::RecordVal>(metric_record_type);
r->Assign(labels_idx, label_values_vec);
r->Assign(opts_idx, opts_record);
if constexpr ( std::is_same_v<BaseType, double> )
r->Assign(value_idx, zeek::make_intrusive<DoubleVal>(ctr->Value()));
else {
r->Assign(value_idx, zeek::make_intrusive<DoubleVal>(static_cast<double>(ctr->Value())));
r->Assign(count_value_idx, val_mgr->Count(ctr->Value()));
}
records.push_back(std::move(r));
}
return records;
}
protected:
prometheus::Family<prometheus::Counter>& family;
BaseCounterFamily(prometheus::Family<prometheus::Counter>* family, Span<const std::string_view> labels)
: MetricFamily(labels), family(family) {}
prometheus::Family<prometheus::Counter>* family;
std::vector<std::shared_ptr<CounterType>> counters;
};
/**
* Manages a collection of IntCounter metrics.
*/
class IntCounterFamily : public BaseCounterFamily<IntCounter, uint64_t> {
class IntCounterFamily final : public BaseCounterFamily<IntCounter, uint64_t> {
public:
static inline const char* OpaqueName = "IntCounterMetricFamilyVal";
explicit IntCounterFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::shared_ptr<prometheus::Registry> registry,
std::string_view unit = "", bool is_sum = false)
: BaseCounterFamily(prefix, name, labels, helptext, std::move(registry), unit, is_sum) {}
IntCounterFamily(const IntCounterFamily&) noexcept = default;
IntCounterFamily& operator=(const IntCounterFamily&) noexcept = delete;
explicit IntCounterFamily(prometheus::Family<prometheus::Counter>* family, Span<const std::string_view> labels)
: BaseCounterFamily(family, labels) {}
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::INT_COUNTER; }
};
@ -188,17 +142,12 @@ public:
/**
* Manages a collection of DblCounter metrics.
*/
class DblCounterFamily : public BaseCounterFamily<DblCounter, double> {
class DblCounterFamily final : public BaseCounterFamily<DblCounter, double> {
public:
static inline const char* OpaqueName = "DblCounterMetricFamilyVal";
explicit DblCounterFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::shared_ptr<prometheus::Registry> registry,
std::string_view unit = "", bool is_sum = false)
: BaseCounterFamily(prefix, name, labels, helptext, std::move(registry), unit, is_sum) {}
DblCounterFamily(const DblCounterFamily&) noexcept = default;
DblCounterFamily& operator=(const DblCounterFamily&) noexcept = delete;
explicit DblCounterFamily(prometheus::Family<prometheus::Counter>* family, Span<const std::string_view> labels)
: BaseCounterFamily(family, labels) {}
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::DOUBLE_COUNTER; }
};

View file

@ -4,10 +4,11 @@
#include <cstdint>
#include <initializer_list>
#include <type_traits>
#include <memory>
#include "zeek/Span.h"
#include "zeek/telemetry/MetricFamily.h"
#include "zeek/telemetry/Utils.h"
#include "zeek/telemetry/telemetry.bif.h"
#include "prometheus/family.h"
@ -77,27 +78,26 @@ public:
bool CompareLabels(const prometheus::Labels& lbls) const { return labels == lbls; }
protected:
explicit BaseGauge(FamilyType& family, const prometheus::Labels& labels,
explicit BaseGauge(FamilyType* family, const prometheus::Labels& labels,
prometheus::CollectCallbackPtr callback = nullptr) noexcept
: handle(family.Add(labels)), labels(labels) {
: handle(family->Add(labels)), labels(labels) {
if ( callback )
handle.AddCollectCallback(callback);
}
Handle& handle;
prometheus::Labels labels;
BaseType last_value = 0;
};
/**
* A handle to a metric that represents an integer value. Gauges are more
* permissive than counters and also allow decrementing the value.
*/
class IntGauge : public BaseGauge<int64_t> {
class IntGauge final : public BaseGauge<int64_t> {
public:
static inline const char* OpaqueName = "IntGaugeMetricVal";
explicit IntGauge(FamilyType& family, const prometheus::Labels& labels,
explicit IntGauge(FamilyType* family, const prometheus::Labels& labels,
prometheus::CollectCallbackPtr callback = nullptr) noexcept
: BaseGauge(family, labels, callback) {}
@ -109,11 +109,11 @@ public:
* A handle to a metric that represents a double value. Gauges are more
* permissive than counters and also allow decrementing the value.
*/
class DblGauge : public BaseGauge<double> {
class DblGauge final : public BaseGauge<double> {
public:
static inline const char* OpaqueName = "DblGaugeMetricVal";
explicit DblGauge(FamilyType& family, const prometheus::Labels& labels,
explicit DblGauge(FamilyType* family, const prometheus::Labels& labels,
prometheus::CollectCallbackPtr callback = nullptr) noexcept
: BaseGauge(family, labels, callback) {}
@ -124,19 +124,13 @@ public:
template<class GaugeType, typename BaseType>
class BaseGaugeFamily : public MetricFamily, public std::enable_shared_from_this<BaseGaugeFamily<GaugeType, BaseType>> {
public:
BaseGaugeFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::shared_ptr<prometheus::Registry> registry,
std::string_view unit = "", bool is_sum = false)
: MetricFamily(prefix, name, labels, helptext, unit, is_sum),
family(prometheus::BuildGauge().Name(full_name).Help(std::string{helptext}).Register(*registry)) {}
/**
* Returns the metrics handle for given labels, creating a new instance
* lazily if necessary.
*/
std::shared_ptr<GaugeType> GetOrAdd(Span<const LabelView> labels,
prometheus::CollectCallbackPtr callback = nullptr) {
prometheus::Labels p_labels = BuildPrometheusLabels(labels);
prometheus::Labels p_labels = detail::BuildPrometheusLabels(labels);
auto check = [&](const std::shared_ptr<GaugeType>& gauge) { return gauge->CompareLabels(p_labels); };
@ -156,63 +150,23 @@ public:
return GetOrAdd(Span{labels.begin(), labels.size()}, callback);
}
std::vector<std::shared_ptr<GaugeType>>& GetAllGauges() { return gauges; }
std::vector<RecordValPtr> Collect() const override {
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
static auto metric_record_type = zeek::id::find_type<zeek::RecordType>("Telemetry::Metric");
static auto opts_idx = metric_record_type->FieldOffset("opts");
static auto labels_idx = metric_record_type->FieldOffset("labels");
static auto value_idx = metric_record_type->FieldOffset("value");
static auto count_value_idx = metric_record_type->FieldOffset("count_value");
RecordValPtr opts_record = GetMetricOptsRecord();
std::vector<RecordValPtr> records;
for ( const auto& g : gauges ) {
auto label_values_vec = make_intrusive<VectorVal>(string_vec_type);
for ( const auto& [label_key, label] : g->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<StringVal>(label));
}
auto r = make_intrusive<zeek::RecordVal>(metric_record_type);
r->Assign(labels_idx, label_values_vec);
r->Assign(opts_idx, opts_record);
if constexpr ( std::is_same_v<BaseType, double> )
r->Assign(value_idx, zeek::make_intrusive<DoubleVal>(g->Value()));
else {
r->Assign(value_idx, zeek::make_intrusive<DoubleVal>(static_cast<double>(g->Value())));
r->Assign(count_value_idx, val_mgr->Count(g->Value()));
}
records.push_back(std::move(r));
}
return records;
}
protected:
prometheus::Family<prometheus::Gauge>& family;
BaseGaugeFamily(prometheus::Family<prometheus::Gauge>* family, Span<const std::string_view> labels)
: MetricFamily(labels), family(family) {}
prometheus::Family<prometheus::Gauge>* family;
std::vector<std::shared_ptr<GaugeType>> gauges;
};
/**
* Manages a collection of IntGauge metrics.
*/
class IntGaugeFamily : public BaseGaugeFamily<IntGauge, int64_t> {
class IntGaugeFamily final : public BaseGaugeFamily<IntGauge, int64_t> {
public:
static inline const char* OpaqueName = "IntGaugeMetricFamilyVal";
IntGaugeFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::shared_ptr<prometheus::Registry> registry,
std::string_view unit = "", bool is_sum = false)
: BaseGaugeFamily(prefix, name, labels, helptext, std::move(registry), unit, is_sum) {}
IntGaugeFamily(const IntGaugeFamily&) noexcept = default;
IntGaugeFamily& operator=(const IntGaugeFamily&) noexcept = delete;
explicit IntGaugeFamily(prometheus::Family<prometheus::Gauge>* family, Span<const std::string_view> labels)
: BaseGaugeFamily(family, labels) {}
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::INT_GAUGE; }
};
@ -220,17 +174,12 @@ public:
/**
* Manages a collection of DblGauge metrics.
*/
class DblGaugeFamily : public BaseGaugeFamily<DblGauge, double> {
class DblGaugeFamily final : public BaseGaugeFamily<DblGauge, double> {
public:
static inline const char* OpaqueName = "DblGaugeMetricFamilyVal";
DblGaugeFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::shared_ptr<prometheus::Registry> registry,
std::string_view unit = "", bool is_sum = false)
: BaseGaugeFamily(prefix, name, labels, helptext, std::move(registry), unit, is_sum) {}
DblGaugeFamily(const DblGaugeFamily&) noexcept = default;
DblGaugeFamily& operator=(const DblGaugeFamily&) noexcept = delete;
explicit DblGaugeFamily(prometheus::Family<prometheus::Gauge>* family, Span<const std::string_view> labels)
: BaseGaugeFamily(family, labels) {}
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::DOUBLE_GAUGE; }
};
@ -240,7 +189,6 @@ namespace detail {
template<class T>
struct GaugeOracle {
static_assert(std::is_same<T, int64_t>::value, "Gauge<T> only supports int64_t and double");
using type = IntGauge;
};

View file

@ -4,10 +4,11 @@
#include <cstdint>
#include <initializer_list>
#include <type_traits>
#include <memory>
#include "zeek/Span.h"
#include "zeek/telemetry/MetricFamily.h"
#include "zeek/telemetry/Utils.h"
#include "zeek/telemetry/telemetry.bif.h"
#include "prometheus/family.h"
@ -39,9 +40,9 @@ public:
bool CompareLabels(const prometheus::Labels& lbls) const { return labels == lbls; }
protected:
explicit BaseHistogram(FamilyType& family, const prometheus::Labels& labels,
explicit BaseHistogram(FamilyType* family, const prometheus::Labels& labels,
prometheus::Histogram::BucketBoundaries bounds) noexcept
: handle(family.Add(labels, std::move(bounds))), labels(labels) {}
: handle(family->Add(labels, std::move(bounds))), labels(labels) {}
Handle& handle;
prometheus::Labels labels;
@ -52,11 +53,11 @@ protected:
* measurements with integer precision. Sorts individual measurements into
* configurable buckets.
*/
class IntHistogram : public BaseHistogram<int64_t> {
class IntHistogram final : public BaseHistogram<int64_t> {
public:
static inline const char* OpaqueName = "IntHistogramMetricVal";
explicit IntHistogram(FamilyType& family, const prometheus::Labels& labels,
explicit IntHistogram(FamilyType* family, const prometheus::Labels& labels,
prometheus::Histogram::BucketBoundaries bounds) noexcept
: BaseHistogram(family, labels, std::move(bounds)) {}
@ -70,11 +71,11 @@ public:
* measurements with integer precision. Sorts individual measurements into
* configurable buckets.
*/
class DblHistogram : public BaseHistogram<double> {
class DblHistogram final : public BaseHistogram<double> {
public:
static inline const char* OpaqueName = "DblHistogramMetricVal";
explicit DblHistogram(FamilyType& family, const prometheus::Labels& labels,
explicit DblHistogram(FamilyType* family, const prometheus::Labels& labels,
prometheus::Histogram::BucketBoundaries bounds) noexcept
: BaseHistogram(family, labels, std::move(bounds)) {}
@ -92,7 +93,7 @@ public:
* lazily if necessary.
*/
std::shared_ptr<HistogramType> GetOrAdd(Span<const LabelView> labels) {
prometheus::Labels p_labels = BuildPrometheusLabels(labels);
prometheus::Labels p_labels = detail::BuildPrometheusLabels(labels);
auto check = [&](const std::shared_ptr<HistogramType>& histo) { return histo->CompareLabels(p_labels); };
@ -111,94 +112,14 @@ public:
return GetOrAdd(Span{labels.begin(), labels.size()});
}
std::vector<RecordValPtr> Collect() const override {
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
static auto double_vec_type = zeek::id::find_type<zeek::VectorType>("double_vec");
static auto count_vec_type = zeek::id::find_type<zeek::VectorType>("index_vec");
static auto histogram_metric_type = zeek::id::find_type<zeek::RecordType>("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<zeek::RecordType>("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<RecordValPtr> records;
for ( const auto& h : histograms ) {
auto label_values_vec = make_intrusive<VectorVal>(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<StringVal>(label));
}
auto r = make_intrusive<zeek::RecordVal>(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<zeek::VectorVal>(double_vec_type);
auto counts_count_vec = make_intrusive<zeek::VectorVal>(count_vec_type);
uint64_t last = 0.0;
for ( const auto& b : histo_data.bucket ) {
counts_double_vec->Append(
zeek::make_intrusive<DoubleVal>(static_cast<double>(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<zeek::VectorVal>(double_vec_type);
auto count_bounds_vec = make_intrusive<zeek::VectorVal>(count_vec_type);
for ( auto b : boundaries ) {
bounds_vec->Append(zeek::make_intrusive<DoubleVal>(b));
count_bounds_vec->Append(val_mgr->Count(static_cast<BaseType>(b)));
}
bounds_vec->Append(zeek::make_intrusive<DoubleVal>(std::numeric_limits<double>::infinity()));
count_bounds_vec->Append(val_mgr->Count(std::numeric_limits<int64_t>::infinity()));
r->Assign(values_idx, counts_double_vec);
r->Assign(observations_idx, zeek::make_intrusive<DoubleVal>(static_cast<double>(histo_data.sample_count)));
r->Assign(sum_idx, zeek::make_intrusive<DoubleVal>(histo_data.sample_sum));
RecordValPtr local_opts_record = r->GetField<RecordVal>(opts_idx);
local_opts_record->Assign(bounds_idx, bounds_vec);
if constexpr ( ! std::is_same_v<BaseType, double> ) {
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<BaseType>(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<const std::string_view> labels,
Span<const BaseType> default_upper_bounds, std::string_view helptext,
std::shared_ptr<prometheus::Registry> 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));
BaseHistogramFamily(prometheus::Family<prometheus::Histogram>* family, Span<const BaseType> bounds,
Span<const std::string_view> labels)
: MetricFamily(labels), family(family) {
std::copy(bounds.begin(), bounds.end(), std::back_inserter(boundaries));
}
prometheus::Family<prometheus::Histogram>& family;
prometheus::Family<prometheus::Histogram>* family;
prometheus::Histogram::BucketBoundaries boundaries;
std::vector<std::shared_ptr<HistogramType>> histograms;
};
@ -206,17 +127,13 @@ protected:
/**
* Manages a collection of IntHistogram metrics.
*/
class IntHistogramFamily : public BaseHistogramFamily<IntHistogram, int64_t> {
class IntHistogramFamily final : public BaseHistogramFamily<IntHistogram, int64_t> {
public:
static inline const char* OpaqueName = "IntHistogramMetricFamilyVal";
IntHistogramFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
Span<const int64_t> default_upper_bounds, std::string_view helptext,
std::shared_ptr<prometheus::Registry> 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;
explicit IntHistogramFamily(prometheus::Family<prometheus::Histogram>* family, Span<const int64_t> bounds,
Span<const std::string_view> labels)
: BaseHistogramFamily(family, bounds, labels) {}
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::INT_HISTOGRAM; }
};
@ -224,17 +141,13 @@ public:
/**
* Manages a collection of DblHistogram metrics.
*/
class DblHistogramFamily : public BaseHistogramFamily<DblHistogram, double> {
class DblHistogramFamily final : public BaseHistogramFamily<DblHistogram, double> {
public:
static inline const char* OpaqueName = "DblHistogramMetricFamilyVal";
DblHistogramFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
Span<const double> default_upper_bounds, std::string_view helptext,
std::shared_ptr<prometheus::Registry> 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;
explicit DblHistogramFamily(prometheus::Family<prometheus::Histogram>* family, Span<const double> bounds,
Span<const std::string_view> labels)
: BaseHistogramFamily(family, bounds, labels) {}
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM; }
};

View file

@ -119,32 +119,140 @@ void Manager::InitPostScript() {
#endif
}
std::shared_ptr<MetricFamily> Manager::LookupFamily(std::string_view prefix, std::string_view name) const {
auto check = [&](const auto& fam) { return fam.second->Prefix() == prefix && fam.second->Name() == name; };
// -- collect metric stuff -----------------------------------------------------
if ( auto it = std::find_if(families.begin(), families.end(), check); it != families.end() )
RecordValPtr Manager::GetMetricOptsRecord(const prometheus::MetricFamily& metric_family) {
// Avoid recreating this repeatedly
if ( auto it = opts_records.find(metric_family.name); it != opts_records.end() )
return it->second;
return nullptr;
// Get the opt record
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
static auto metric_opts_type = zeek::id::find_type<zeek::RecordType>("Telemetry::MetricOpts");
static auto prefix_idx = metric_opts_type->FieldOffset("prefix");
static auto name_idx = metric_opts_type->FieldOffset("name");
static auto help_text_idx = metric_opts_type->FieldOffset("help_text");
static auto unit_idx = metric_opts_type->FieldOffset("unit");
static auto labels_idx = metric_opts_type->FieldOffset("labels");
static auto is_total_idx = metric_opts_type->FieldOffset("is_total");
static auto metric_type_idx = metric_opts_type->FieldOffset("metric_type");
auto record_val = make_intrusive<zeek::RecordVal>(metric_opts_type);
record_val->Assign(name_idx, make_intrusive<zeek::StringVal>(metric_family.name));
record_val->Assign(help_text_idx, make_intrusive<zeek::StringVal>(metric_family.help));
// prometheus-cpp doesn't store the prefix information separately. we pull the word
// before the first underscore as the prefix instead. The Prometheus docs state
// that the prefix "should exist" not "must exist" so it's possible this could result
// in incorrect data, but it should be correct for all of our uses.
std::string prefix;
auto first_underscore = metric_family.name.find('_');
if ( first_underscore != std::string::npos )
prefix = metric_family.name.substr(0, first_underscore);
record_val->Assign(prefix_idx, make_intrusive<zeek::StringVal>(prefix));
// Assume that a metric ending with _total is always a summed metric so we can set that.
record_val->Assign(is_total_idx, val_mgr->Bool(util::ends_with(metric_family.name, "_total")));
auto label_names_vec = make_intrusive<zeek::VectorVal>(string_vec_type);
// Check if this is a Zeek-internal metric. We keep a little more information about a metric
// for these than we do for ones that were inserted into prom-cpp directly.
if ( auto it = families.find(metric_family.name); it != families.end() ) {
record_val->Assign(metric_type_idx,
zeek::BifType::Enum::Telemetry::MetricType->GetEnumVal(it->second->MetricType()));
for ( const auto& lbl : it->second->LabelNames() )
label_names_vec->Append(make_intrusive<StringVal>(lbl));
}
else {
// prom-cpp stores everything internally as doubles
if ( metric_family.type == prometheus::MetricType::Counter )
record_val->Assign(metric_type_idx, zeek::BifType::Enum::Telemetry::MetricType->GetEnumVal(
BifEnum::Telemetry::MetricType::DOUBLE_COUNTER));
if ( metric_family.type == prometheus::MetricType::Gauge )
record_val->Assign(metric_type_idx, zeek::BifType::Enum::Telemetry::MetricType->GetEnumVal(
BifEnum::Telemetry::MetricType::DOUBLE_GAUGE));
if ( metric_family.type == prometheus::MetricType::Histogram )
record_val->Assign(metric_type_idx, zeek::BifType::Enum::Telemetry::MetricType->GetEnumVal(
BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM));
// prom-cpp doesn't store label names anywhere other than in each instrument. just assume
// they're always going to be the same across all of the instruments and use the names from
// the first one.
// TODO: is this check here ever false?
if ( ! metric_family.metric.empty() )
for ( const auto& lbl : metric_family.metric[0].label )
label_names_vec->Append(make_intrusive<StringVal>(lbl.name));
}
// -- collect metric stuff -----------------------------------------------------
record_val->Assign(labels_idx, label_names_vec);
opts_records.insert({metric_family.name, record_val});
return record_val;
}
ValPtr Manager::CollectMetrics(std::string_view prefix_pattern, std::string_view name_pattern) {
static auto metrics_vector_type = zeek::id::find_type<VectorType>("any_vec");
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
static auto metric_record_type = zeek::id::find_type<zeek::RecordType>("Telemetry::Metric");
static auto opts_idx = metric_record_type->FieldOffset("opts");
static auto labels_idx = metric_record_type->FieldOffset("labels");
static auto value_idx = metric_record_type->FieldOffset("value");
static auto count_value_idx = metric_record_type->FieldOffset("count_value");
static auto metric_opts_type = zeek::id::find_type<zeek::RecordType>("Telemetry::MetricOpts");
static auto metric_type_idx = metric_opts_type->FieldOffset("metric_type");
VectorValPtr ret_val = make_intrusive<VectorVal>(metrics_vector_type);
// Build a map of all of the families that match the patterns based on their full prefixed
// name. This will let us match those families against the items returned from the otel reader.
for ( const auto& [name, family] : families ) {
// Histograms are handled by CollectHistogramMetrics and should be ignored here.
if ( family->MetricType() == BifEnum::Telemetry::MetricType::INT_HISTOGRAM ||
family->MetricType() == BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM )
// Due to the name containing the full information about a metric including a potential unit add an
// asterisk to the end of the full pattern so matches work correctly.
std::string full_pattern = util::fmt("%s_%s", prefix_pattern.data(), name_pattern.data());
if ( full_pattern[full_pattern.size() - 1] != '*' )
full_pattern.append("*");
auto collected = prometheus_registry->Collect();
for ( const auto& fam : collected ) {
if ( fam.type == prometheus::MetricType::Histogram )
continue;
if ( family->Matches(prefix_pattern, name_pattern) ) {
auto records = family->Collect();
for ( const auto& r : records )
if ( fnmatch(full_pattern.c_str(), fam.name.c_str(), 0) == FNM_NOMATCH )
continue;
RecordValPtr opts_record = GetMetricOptsRecord(fam);
for ( const auto& inst : fam.metric ) {
auto label_values_vec = make_intrusive<VectorVal>(string_vec_type);
for ( const auto& label : inst.label ) {
// We don't include the endpoint key/value unless it's a prometheus request
if ( label.name != "endpoint" )
label_values_vec->Append(make_intrusive<StringVal>(label.value));
}
auto r = make_intrusive<zeek::RecordVal>(metric_record_type);
r->Assign(labels_idx, label_values_vec);
r->Assign(opts_idx, opts_record);
if ( fam.type == prometheus::MetricType::Counter )
r->Assign(value_idx, zeek::make_intrusive<DoubleVal>(inst.counter.value));
else if ( fam.type == prometheus::MetricType::Gauge )
r->Assign(value_idx, zeek::make_intrusive<DoubleVal>(inst.gauge.value));
// Use the information from GetMetaricOptsRecord to check whether we need to add the integer
// fields, or if this is a double.
if ( opts_record->GetField<EnumVal>(metric_type_idx)->Get() ==
BifEnum::Telemetry::MetricType::INT_COUNTER ) {
r->Assign(count_value_idx, val_mgr->Count(static_cast<int64_t>(inst.counter.value)));
}
else if ( opts_record->GetField<EnumVal>(metric_type_idx)->Get() ==
BifEnum::Telemetry::MetricType::INT_GAUGE ) {
r->Assign(count_value_idx, val_mgr->Count(static_cast<int64_t>(inst.gauge.value)));
}
ret_val->Append(r);
}
}
@ -154,18 +262,103 @@ ValPtr Manager::CollectMetrics(std::string_view prefix_pattern, std::string_view
ValPtr Manager::CollectHistogramMetrics(std::string_view prefix_pattern, std::string_view name_pattern) {
static auto metrics_vector_type = zeek::id::find_type<VectorType>("any_vec");
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
static auto double_vec_type = zeek::id::find_type<zeek::VectorType>("double_vec");
static auto count_vec_type = zeek::id::find_type<zeek::VectorType>("index_vec");
static auto histogram_metric_type = zeek::id::find_type<zeek::RecordType>("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<zeek::RecordType>("Telemetry::MetricOpts");
static auto bounds_idx = opts_rt->FieldOffset("bounds");
static auto count_bounds_idx = opts_rt->FieldOffset("count_bounds");
static auto metric_opts_type = zeek::id::find_type<zeek::RecordType>("Telemetry::MetricOpts");
static auto metric_type_idx = metric_opts_type->FieldOffset("metric_type");
VectorValPtr ret_val = make_intrusive<VectorVal>(metrics_vector_type);
// Build a map of all of the families that match the patterns based on their full prefixed
// name. This will let us match those families against the items returned from the otel reader.
for ( const auto& [name, family] : families ) {
if ( family->MetricType() != BifEnum::Telemetry::MetricType::INT_HISTOGRAM &&
family->MetricType() != BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM )
// Due to the name containing the full information about a metric including a potential unit add an
// asterisk to the end of the full pattern so matches work correctly.
std::string full_pattern = util::fmt("%s_%s", prefix_pattern.data(), name_pattern.data());
if ( full_pattern[full_pattern.size() - 1] != '*' )
full_pattern.append("*");
auto collected = prometheus_registry->Collect();
for ( const auto& fam : collected ) {
if ( fam.type != prometheus::MetricType::Histogram )
continue;
if ( family->Matches(prefix_pattern, name_pattern) ) {
auto records = family->Collect();
for ( const auto& r : records )
if ( fnmatch(full_pattern.c_str(), fam.name.c_str(), 0) == FNM_NOMATCH )
continue;
RecordValPtr opts_record = GetMetricOptsRecord(fam);
for ( const auto& inst : fam.metric ) {
auto label_values_vec = make_intrusive<VectorVal>(string_vec_type);
for ( const auto& label : inst.label ) {
// We don't include the endpoint key/value unless it's a prometheus request
if ( label.name != "endpoint" )
label_values_vec->Append(make_intrusive<StringVal>(label.value));
}
auto r = make_intrusive<zeek::RecordVal>(histogram_metric_type);
r->Assign(labels_idx, label_values_vec);
r->Assign(opts_idx, opts_record);
auto double_values_vec = make_intrusive<zeek::VectorVal>(double_vec_type);
auto count_values_vec = make_intrusive<zeek::VectorVal>(count_vec_type);
std::vector<double> boundaries;
uint64_t last = 0.0;
for ( const auto& b : inst.histogram.bucket ) {
double_values_vec->Append(
zeek::make_intrusive<DoubleVal>(static_cast<double>(b.cumulative_count - last)));
count_values_vec->Append(val_mgr->Count(b.cumulative_count - last));
last = b.cumulative_count;
boundaries.push_back(b.upper_bound);
}
// TODO: these could be stored somehow to avoid recreating them repeatedly
auto bounds_vec = make_intrusive<zeek::VectorVal>(double_vec_type);
auto count_bounds_vec = make_intrusive<zeek::VectorVal>(count_vec_type);
for ( auto b : boundaries ) {
bounds_vec->Append(zeek::make_intrusive<DoubleVal>(b));
// The boundaries from prom-cpp include the infinite boundary in double.
// This can't be converted safely to int64_t, so check for that case and
// set the int64_t version.
if ( b != std::numeric_limits<double>::infinity() )
count_bounds_vec->Append(val_mgr->Count(static_cast<int64_t>(b)));
else
count_bounds_vec->Append(val_mgr->Count(std::numeric_limits<int64_t>::infinity()));
}
r->Assign(values_idx, double_values_vec);
r->Assign(observations_idx,
zeek::make_intrusive<DoubleVal>(static_cast<double>(inst.histogram.sample_count)));
r->Assign(sum_idx, zeek::make_intrusive<DoubleVal>(inst.histogram.sample_sum));
RecordValPtr local_opts_record = r->GetField<RecordVal>(opts_idx);
local_opts_record->Assign(bounds_idx, bounds_vec);
// Use the information from GetMetaricOptsRecord to check whether we need to add the integer
// fields, or if this is a double.
if ( opts_record->GetField<EnumVal>(metric_type_idx)->Get() ==
BifEnum::Telemetry::MetricType::INT_HISTOGRAM ) {
r->Assign(count_values_idx, count_values_vec);
r->Assign(count_observations_idx, val_mgr->Count(inst.histogram.sample_count));
r->Assign(count_sum_idx, val_mgr->Count(static_cast<int64_t>(inst.histogram.sample_sum)));
local_opts_record->Assign(count_bounds_idx, count_bounds_vec);
}
ret_val->Append(r);
}
}
@ -225,16 +418,8 @@ SCENARIO("telemetry managers provide access to counter families") {
GIVEN("a telemetry manager") {
Manager mgr;
WHEN("retrieving an IntCounter family") {
auto family = mgr.CounterFamily("zeek", "requests", {"method"}, "test", "1", true);
THEN("the family object stores the parameters") {
CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family->Name(), "requests"sv);
CHECK_EQ(toVector(family->LabelNames()), std::vector{"method"s});
CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family->Unit(), "1"sv);
CHECK_EQ(family->IsSum(), true);
}
AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto family = mgr.CounterFamily("zeek", "requests", {"method"}, "test", "", true);
THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family->GetOrAdd({{"method", "get"}});
auto second = family->GetOrAdd({{"method", "get"}});
CHECK_EQ(first, second);
@ -247,15 +432,7 @@ SCENARIO("telemetry managers provide access to counter families") {
}
WHEN("retrieving a DblCounter family") {
auto family = mgr.CounterFamily<double>("zeek", "runtime", {"query"}, "test", "seconds", true);
THEN("the family object stores the parameters") {
CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family->Name(), "runtime"sv);
CHECK_EQ(toVector(family->LabelNames()), std::vector{"query"s});
CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family->Unit(), "seconds"sv);
CHECK_EQ(family->IsSum(), true);
}
AND_THEN("GetOrAdd returns the same metric for the same labels") {
THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family->GetOrAdd({{"query", "foo"}});
auto second = family->GetOrAdd({{"query", "foo"}});
CHECK_EQ(first, second);
@ -273,16 +450,8 @@ SCENARIO("telemetry managers provide access to gauge families") {
GIVEN("a telemetry manager") {
Manager mgr;
WHEN("retrieving an IntGauge family") {
auto family = mgr.GaugeFamily("zeek", "open-connections", {"protocol"}, "test", "1");
THEN("the family object stores the parameters") {
CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family->Name(), "open_connections"sv);
CHECK_EQ(toVector(family->LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family->Unit(), "1"sv);
CHECK_EQ(family->IsSum(), false);
}
AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto family = mgr.GaugeFamily("zeek", "open-connections", {"protocol"}, "test", "");
THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family->GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second);
@ -295,15 +464,7 @@ SCENARIO("telemetry managers provide access to gauge families") {
}
WHEN("retrieving a DblGauge family") {
auto family = mgr.GaugeFamily<double>("zeek", "water-level", {"river"}, "test", "meters");
THEN("the family object stores the parameters") {
CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family->Name(), "water_level"sv);
CHECK_EQ(toVector(family->LabelNames()), std::vector{"river"s});
CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family->Unit(), "meters"sv);
CHECK_EQ(family->IsSum(), false);
}
AND_THEN("GetOrAdd returns the same metric for the same labels") {
THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family->GetOrAdd({{"river", "Sacramento"}});
auto second = family->GetOrAdd({{"river", "Sacramento"}});
CHECK_EQ(first, second);
@ -323,15 +484,7 @@ SCENARIO("telemetry managers provide access to histogram families") {
WHEN("retrieving an IntHistogram family") {
int64_t buckets[] = {10, 20};
auto family = mgr.HistogramFamily("zeek", "payload-size", {"protocol"}, buckets, "test", "bytes");
THEN("the family object stores the parameters") {
CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family->Name(), "payload_size"sv);
CHECK_EQ(toVector(family->LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family->Unit(), "bytes"sv);
CHECK_EQ(family->IsSum(), false);
}
AND_THEN("GetOrAdd returns the same metric for the same labels") {
THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family->GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second);
@ -345,15 +498,7 @@ SCENARIO("telemetry managers provide access to histogram families") {
WHEN("retrieving a DblHistogram family") {
double buckets[] = {10.0, 20.0};
auto family = mgr.HistogramFamily<double>("zeek", "parse-time", {"protocol"}, buckets, "test", "seconds");
THEN("the family object stores the parameters") {
CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family->Name(), "parse_time"sv);
CHECK_EQ(toVector(family->LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family->Unit(), "seconds"sv);
CHECK_EQ(family->IsSum(), false);
}
AND_THEN("GetOrAdd returns the same metric for the same labels") {
THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family->GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second);

View file

@ -15,6 +15,7 @@
#include "zeek/telemetry/Gauge.h"
#include "zeek/telemetry/Histogram.h"
#include "zeek/telemetry/ProcessStats.h"
#include "zeek/telemetry/Utils.h"
#include "prometheus/exposer.h"
#include "prometheus/registry.h"
@ -26,8 +27,6 @@ using RecordValPtr = IntrusivePtr<RecordVal>;
namespace zeek::telemetry {
class OtelReader;
/**
* Manages a collection of metric families.
*/
@ -69,33 +68,31 @@ public:
* @param helptext Short explanation of the metric.
* @param unit Unit of measurement.
* @param is_sum Indicates whether this metric accumulates something, where only the total value is of interest.
* @param callback Passing a callback method will enable asynchronous mode. The callback method will be called by
* the metrics subsystem whenever data is requested.
*/
template<class ValueType = int64_t>
auto CounterFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::string_view unit = "", bool is_sum = false) {
auto fam = LookupFamily(prefix, name);
auto full_name = detail::BuildFullPrometheusName(prefix, name, unit, is_sum);
auto& prom_fam =
prometheus::BuildCounter().Name(full_name).Help(std::string{helptext}).Register(*prometheus_registry);
if constexpr ( std::is_same<ValueType, int64_t>::value ) {
if ( fam )
return std::static_pointer_cast<IntCounterFamily>(fam);
if ( auto it = families.find(prom_fam.GetName()); it != families.end() )
return std::static_pointer_cast<IntCounterFamily>(it->second);
auto int_fam =
std::make_shared<IntCounterFamily>(prefix, name, labels, helptext, prometheus_registry, unit, is_sum);
families.insert_or_assign(int_fam->FullName(), int_fam);
return int_fam;
auto fam = std::make_shared<IntCounterFamily>(&prom_fam, labels);
families.insert({prom_fam.GetName(), fam});
return fam;
}
else {
static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values");
if ( auto it = families.find(prom_fam.GetName()); it != families.end() )
return std::static_pointer_cast<DblCounterFamily>(it->second);
if ( fam )
return std::static_pointer_cast<DblCounterFamily>(fam);
auto dbl_fam =
std::make_shared<DblCounterFamily>(prefix, name, labels, helptext, prometheus_registry, unit, is_sum);
families.insert_or_assign(dbl_fam->FullName(), dbl_fam);
return dbl_fam;
auto fam = std::make_shared<DblCounterFamily>(&prom_fam, labels);
families.insert({prom_fam.GetName(), fam});
return fam;
}
}
@ -149,32 +146,31 @@ public:
* @param helptext Short explanation of the metric.
* @param unit Unit of measurement.
* @param is_sum Indicates whether this metric accumulates something, where only the total value is of interest.
* @param callback Passing a callback method will enable asynchronous mode. The callback method will be called by
* the metrics subsystem whenever data is requested.
*/
template<class ValueType = int64_t>
auto GaugeFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::string_view unit = "", bool is_sum = false) {
auto fam = LookupFamily(prefix, name);
auto full_name = detail::BuildFullPrometheusName(prefix, name, unit, is_sum);
auto& prom_fam =
prometheus::BuildGauge().Name(full_name).Help(std::string{helptext}).Register(*prometheus_registry);
if constexpr ( std::is_same<ValueType, int64_t>::value ) {
if ( fam )
return std::static_pointer_cast<IntGaugeFamily>(fam);
if ( auto it = families.find(prom_fam.GetName()); it != families.end() )
return std::static_pointer_cast<IntGaugeFamily>(it->second);
auto int_fam =
std::make_shared<IntGaugeFamily>(prefix, name, labels, helptext, prometheus_registry, unit, is_sum);
families.insert_or_assign(int_fam->FullName(), int_fam);
return int_fam;
auto fam = std::make_shared<IntGaugeFamily>(&prom_fam, labels);
families.insert({prom_fam.GetName(), fam});
return fam;
}
else {
static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values");
if ( fam )
return std::static_pointer_cast<DblGaugeFamily>(fam);
if ( auto it = families.find(prom_fam.GetName()); it != families.end() )
return std::static_pointer_cast<DblGaugeFamily>(it->second);
auto dbl_fam =
std::make_shared<DblGaugeFamily>(prefix, name, labels, helptext, prometheus_registry, unit, is_sum);
families.insert_or_assign(dbl_fam->FullName(), dbl_fam);
return dbl_fam;
auto fam = std::make_shared<DblGaugeFamily>(&prom_fam, labels);
families.insert({prom_fam.GetName(), fam});
return fam;
}
}
@ -239,52 +235,48 @@ public:
* reserved.
* @param name The human-readable name of the metric, e.g., `requests`.
* @param labels Names for all label dimensions of the metric.
* @param default_upper_bounds Upper bounds for the metric buckets.
* @param bounds Upper bounds for the metric buckets.
* @param helptext Short explanation of the metric.
* @param unit Unit of measurement. Please use base units such as `bytes` or
* `seconds` (prefer lowercase). The pseudo-unit `1` identifies
* dimensionless counts.
* @param is_sum Setting this to `true` indicates that this metric adds
* something up to a total, where only the total value is of
* interest. For example, the total number of HTTP requests.
* @note The first call wins when calling this function multiple times with
* different bucket settings. Users may also override
* @p default_upper_bounds via run-time configuration.
* @p bounds via run-time configuration.
*/
template<class ValueType = int64_t>
auto HistogramFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
ConstSpan<ValueType> default_upper_bounds, std::string_view helptext,
std::string_view unit = "") {
auto fam = LookupFamily(prefix, name);
ConstSpan<ValueType> bounds, std::string_view helptext, std::string_view unit = "") {
auto full_name = detail::BuildFullPrometheusName(prefix, name, unit);
auto& prom_fam =
prometheus::BuildHistogram().Name(full_name).Help(std::string{helptext}).Register(*prometheus_registry);
if constexpr ( std::is_same<ValueType, int64_t>::value ) {
if ( fam )
return std::static_pointer_cast<IntHistogramFamily>(fam);
if ( auto it = families.find(prom_fam.GetName()); it != families.end() )
return std::static_pointer_cast<IntHistogramFamily>(it->second);
auto int_fam = std::make_shared<IntHistogramFamily>(prefix, name, labels, default_upper_bounds, helptext,
prometheus_registry, unit);
families.insert_or_assign(int_fam->FullName(), int_fam);
return int_fam;
auto fam = std::make_shared<IntHistogramFamily>(&prom_fam, bounds, labels);
families.insert({prom_fam.GetName(), fam});
return fam;
}
else {
static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values");
if ( fam )
return std::static_pointer_cast<DblHistogramFamily>(fam);
if ( auto it = families.find(prom_fam.GetName()); it != families.end() )
return std::static_pointer_cast<DblHistogramFamily>(it->second);
auto dbl_fam = std::make_shared<DblHistogramFamily>(prefix, name, labels, default_upper_bounds, helptext,
prometheus_registry, unit);
families.insert_or_assign(dbl_fam->FullName(), dbl_fam);
return dbl_fam;
auto fam = std::make_shared<DblHistogramFamily>(&prom_fam, bounds, labels);
families.insert({prom_fam.GetName(), fam});
return fam;
}
}
/// @copydoc HistogramFamily
template<class ValueType = int64_t>
auto HistogramFamily(std::string_view prefix, std::string_view name, std::initializer_list<std::string_view> labels,
ConstSpan<ValueType> default_upper_bounds, std::string_view helptext,
std::string_view unit = "") {
ConstSpan<ValueType> bounds, std::string_view helptext, std::string_view unit = "") {
auto lbl_span = Span{labels.begin(), labels.size()};
return HistogramFamily<ValueType>(prefix, name, lbl_span, default_upper_bounds, helptext, unit);
return HistogramFamily<ValueType>(prefix, name, lbl_span, bounds, helptext, unit);
}
/**
@ -295,25 +287,21 @@ public:
* reserved.
* @param name The human-readable name of the metric, e.g., `requests`.
* @param labels Names for all label dimensions of the metric.
* @param default_upper_bounds Upper bounds for the metric buckets.
* @param bounds Upper bounds for the metric buckets.
* @param helptext Short explanation of the metric.
* @param unit Unit of measurement. Please use base units such as `bytes` or
* `seconds` (prefer lowercase). The pseudo-unit `1` identifies
* dimensionless counts.
* @param is_sum Setting this to `true` indicates that this metric adds
* something up to a total, where only the total value is of
* interest. For example, the total number of HTTP requests.
* @note The first call wins when calling this function multiple times with
* different bucket settings. Users may also override
* @p default_upper_bounds via run-time configuration.
* @p bounds via run-time configuration.
*/
template<class ValueType = int64_t>
std::shared_ptr<Histogram<ValueType>> HistogramInstance(std::string_view prefix, std::string_view name,
Span<const LabelView> labels,
ConstSpan<ValueType> default_upper_bounds,
Span<const LabelView> labels, ConstSpan<ValueType> bounds,
std::string_view helptext, std::string_view unit = "") {
return WithLabelNames(labels, [&, this](auto labelNames) {
auto family = HistogramFamily<ValueType>(prefix, name, labelNames, default_upper_bounds, helptext, unit);
auto family = HistogramFamily<ValueType>(prefix, name, labelNames, bounds, helptext, unit);
return family->GetOrAdd(labels);
});
}
@ -322,17 +310,11 @@ public:
template<class ValueType = int64_t>
std::shared_ptr<Histogram<ValueType>> HistogramInstance(std::string_view prefix, std::string_view name,
std::initializer_list<LabelView> labels,
std::initializer_list<ValueType> default_upper_bounds,
std::initializer_list<ValueType> bounds,
std::string_view helptext, std::string_view unit = "") {
auto lbls = Span{labels.begin(), labels.size()};
auto bounds = Span{default_upper_bounds.begin(), default_upper_bounds.size()};
return HistogramInstance<ValueType>(prefix, name, lbls, bounds, helptext, unit);
}
std::shared_ptr<MetricFamily> GetFamilyByFullName(const std::string& full_name) const {
if ( auto it = families.find(full_name); it != families.end() )
return it->second;
return nullptr;
auto bounds_span = Span{bounds.begin(), bounds.size()};
return HistogramInstance<ValueType>(prefix, name, lbls, bounds_span, helptext, unit);
}
/**
@ -368,10 +350,10 @@ protected:
}
private:
std::shared_ptr<MetricFamily> LookupFamily(std::string_view prefix, std::string_view name) const;
RecordValPtr GetMetricOptsRecord(const prometheus::MetricFamily& metric_family);
std::shared_ptr<OtelReader> otel_reader;
std::map<std::string, std::shared_ptr<MetricFamily>> families;
std::map<std::string, RecordValPtr> opts_records;
detail::process_stats current_process_stats;
double process_stats_last_updated = 0.0;

View file

@ -1,79 +0,0 @@
#include "zeek/telemetry/MetricFamily.h"
#include <fnmatch.h>
#include "zeek/Val.h"
#include "zeek/telemetry/telemetry.bif.h"
namespace zeek::telemetry {
MetricFamily::MetricFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> lbls,
std::string_view helptext, std::string_view unit, bool is_sum)
: prefix(prefix), helptext(helptext), unit(unit), is_sum(is_sum) {
this->name = util::strreplace(std::string{name}, "-", "_");
for ( const auto& lbl : lbls ) {
labels.emplace_back(lbl);
}
full_name = util::fmt("%s_%s", this->prefix.c_str(), this->name.c_str());
}
RecordValPtr MetricFamily::GetMetricOptsRecord() const {
if ( record_val )
return record_val;
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
static auto metric_opts_type = zeek::id::find_type<zeek::RecordType>("Telemetry::MetricOpts");
static auto prefix_idx = metric_opts_type->FieldOffset("prefix");
static auto name_idx = metric_opts_type->FieldOffset("name");
static auto help_text_idx = metric_opts_type->FieldOffset("help_text");
static auto unit_idx = metric_opts_type->FieldOffset("unit");
static auto is_total_idx = metric_opts_type->FieldOffset("is_total");
static auto labels_idx = metric_opts_type->FieldOffset("labels");
static auto bounds_idx = metric_opts_type->FieldOffset("bounds");
static auto metric_type_idx = metric_opts_type->FieldOffset("metric_type");
record_val = make_intrusive<zeek::RecordVal>(metric_opts_type);
record_val->Assign(prefix_idx, make_intrusive<zeek::StringVal>(prefix));
record_val->Assign(name_idx, make_intrusive<zeek::StringVal>(name));
record_val->Assign(help_text_idx, make_intrusive<zeek::StringVal>(helptext));
record_val->Assign(unit_idx, make_intrusive<zeek::StringVal>(unit));
record_val->Assign(is_total_idx, val_mgr->Bool(is_sum));
auto label_names_vec = make_intrusive<zeek::VectorVal>(string_vec_type);
for ( const auto& lbl : labels )
label_names_vec->Append(make_intrusive<StringVal>(lbl));
record_val->Assign(labels_idx, label_names_vec);
record_val->Assign(metric_type_idx, zeek::BifType::Enum::Telemetry::MetricType->GetEnumVal(MetricType()));
return record_val;
}
bool MetricFamily::Matches(std::string_view prefix_pattern, std::string_view name_pattern) const noexcept {
return fnmatch(prefix_pattern.data(), prefix.c_str(), 0) != FNM_NOMATCH &&
fnmatch(name_pattern.data(), name.c_str(), 0) != FNM_NOMATCH;
}
prometheus::Labels MetricFamily::BuildPrometheusLabels(Span<const LabelView> labels) {
prometheus::Labels p_labels;
bool found_endpoint = false;
for ( const auto& lbl : labels ) {
p_labels.emplace(util::strreplace(std::string{lbl.first}, "-", "_"), lbl.second);
if ( lbl.first == "endpoint" )
found_endpoint = true;
}
if ( ! found_endpoint ) {
auto endpoint = id::find_val("Telemetry::metrics_endpoint_name")->AsStringVal();
if ( endpoint && endpoint->Len() > 0 )
p_labels.emplace("endpoint", endpoint->ToStdString());
}
return p_labels;
}
} // namespace zeek::telemetry

View file

@ -3,117 +3,32 @@
#pragma once
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "zeek/Span.h"
#include "zeek/Val.h"
#include "prometheus/labels.h"
#include "zeek/util.h"
namespace zeek::telemetry {
/**
* A key-value pair for a single label dimension.
*/
using LabelView = std::pair<std::string_view, std::string_view>;
/**
* Manages a collection (family) of metrics. All members of the family share
* the same prefix (namespace), name, and label dimensions.
*/
class MetricFamily {
public:
MetricFamily() = delete;
MetricFamily(const MetricFamily&) noexcept = default;
MetricFamily& operator=(const MetricFamily&) noexcept = default;
virtual ~MetricFamily() = default;
/**
* @return The prefix (namespace) this family belongs to. Builtin metrics
* of Zeek return @c zeek. Custom metrics, e.g., created in a
* script, may use a prefix that represents the application/script
* or protocol (e.g. @c http) name.
*/
std::string_view Prefix() const noexcept { return prefix; }
virtual zeek_int_t MetricType() const = 0;
/**
* @return The human-readable name of the metric, e.g.,
* @p open-connections.
*/
std::string_view Name() const noexcept { return name; }
/**
* @return The complete name for the family including prefix.
*/
std::string FullName() const noexcept { return full_name; }
/**
* @return The names for all label dimensions.
*/
Span<const std::string> LabelNames() const noexcept { return labels; }
/**
* @return A short explanation of the metric.
*/
std::string_view Helptext() const noexcept { return helptext; }
/**
* @return The unit of measurement, preferably a base unit such as @c bytes
* or @c seconds.
*/
std::string_view Unit() const noexcept { return unit; }
/**
* @return Whether metrics of this family accumulate values, where only the
* total value is of interest. For example, the total number of
* HTTP requests.
*/
bool IsSum() const noexcept { return is_sum; }
/**
* Converts the family data into script layer record. This record
* lazily-allocated and reused for each instrument associated with this
* family.
*
* @return A script layer Telemetry::Metric record for this family.
*/
RecordValPtr GetMetricOptsRecord() const;
/**
* @return The type of this metric, defined as one of the values in the
* script-layer Telemetry::MetricType enum.
*/
virtual zeek_int_t MetricType() const noexcept = 0;
/**
* @return Whether the prefix and name of this family matches the patterns
* provided.
*/
bool Matches(std::string_view prefix_pattern, std::string_view name_pattern) const noexcept;
virtual std::vector<RecordValPtr> Collect() const = 0;
std::vector<std::string> LabelNames() const { return label_names; }
protected:
MetricFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> lbls,
std::string_view helptext, std::string_view unit, bool is_sum = false);
MetricFamily(Span<const std::string_view> labels) {
for ( const auto& lbl : labels )
label_names.emplace_back(lbl);
}
/**
* Builds a set of labels for prometheus based on a set of labels from
* Zeek. This adds an 'endpoint' label if it's missing from the set.
*/
static prometheus::Labels BuildPrometheusLabels(Span<const LabelView> labels);
std::string prefix;
std::string name;
std::string full_name;
std::vector<std::string> labels;
std::string helptext;
std::string unit;
bool is_sum = false;
mutable RecordValPtr record_val;
std::vector<std::string> label_names;
};
} // namespace zeek::telemetry

54
src/telemetry/Utils.cc Normal file
View file

@ -0,0 +1,54 @@
#include "Utils.h"
#include "zeek/ID.h"
#include "zeek/Reporter.h"
#include "zeek/Val.h"
#include "zeek/telemetry/telemetry.bif.h"
#include "zeek/util.h"
using namespace zeek;
namespace zeek::telemetry::detail {
std::string BuildFullPrometheusName(std::string_view prefix, std::string_view name, std::string_view unit,
bool is_sum) {
if ( prefix.empty() || name.empty() )
reporter->FatalError("Telemetry metric families must have a non-zero-length prefix and name");
std::string fn = util::fmt("%s_%s", prefix.data(), name.data());
std::for_each(fn.begin(), fn.end(), [](char& c) {
if ( ! std::isalnum(c) )
c = '_';
});
// We were previously using "1" to mean "no unit value" for whatever reason, so we have to handle that now
// to mean the same thing.
if ( ! unit.empty() && unit != "1" )
fn.append("_").append(unit);
if ( is_sum )
fn.append("_total");
return fn;
}
prometheus::Labels BuildPrometheusLabels(Span<const LabelView> labels) {
prometheus::Labels p_labels;
bool found_endpoint = false;
for ( const auto& lbl : labels ) {
p_labels.emplace(util::strreplace(std::string{lbl.first}, "-", "_"), lbl.second);
if ( lbl.first == "endpoint" )
found_endpoint = true;
}
if ( ! found_endpoint ) {
auto endpoint = id::find_val("Telemetry::metrics_endpoint_name")->AsStringVal();
if ( endpoint && endpoint->Len() > 0 )
p_labels.emplace("endpoint", endpoint->ToStdString());
}
return p_labels;
}
} // namespace zeek::telemetry::detail

30
src/telemetry/Utils.h Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <string_view>
#include "zeek/Span.h"
#include "zeek/Val.h"
#include "prometheus/family.h"
#include "prometheus/labels.h"
namespace zeek::telemetry {
using LabelView = std::pair<std::string_view, std::string_view>;
namespace detail {
/**
* Builds a set of labels for prometheus based on a set of labels from
* Zeek. This adds an 'endpoint' label if it's missing from the set.
*/
prometheus::Labels BuildPrometheusLabels(Span<const LabelView> labels);
/**
* Builds a full metric name for Prometheus from prefix, name, and unit values.
*/
std::string BuildFullPrometheusName(std::string_view prefix, std::string_view name, std::string_view unit,
bool is_sum = false);
} // namespace detail
} // namespace zeek::telemetry

View file

@ -73,7 +73,7 @@ std::vector<zeek::telemetry::LabelView> sv_tbl(zeek::TableVal* xs)
return result;
}
bool is_valid(zeek::Span<const zeek::telemetry::LabelView> labels,
bool labels_valid(zeek::Span<const zeek::telemetry::LabelView> labels,
zeek::Span<const std::string> label_names)
{
auto key_in_label_names = [keys{label_names}](auto x)
@ -131,7 +131,7 @@ function Telemetry::__int_counter_metric_get_or_add%(family: opaque of int_count
{
auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl->LabelNames()) )
if ( labels_valid(lbl_map, hdl->LabelNames()) )
{
auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<IntCounterMetricVal>(res);
@ -191,7 +191,7 @@ function Telemetry::__dbl_counter_metric_get_or_add%(family: opaque of dbl_count
{
auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl->LabelNames()) )
if ( labels_valid(lbl_map, hdl->LabelNames()) )
{
auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<DblCounterMetricVal>(res);
@ -251,7 +251,7 @@ function Telemetry::__int_gauge_metric_get_or_add%(family: opaque of int_gauge_m
{
auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl->LabelNames()) )
if ( labels_valid(lbl_map, hdl->LabelNames()) )
{
auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<IntGaugeMetricVal>(res);
@ -317,7 +317,7 @@ function Telemetry::__dbl_gauge_metric_get_or_add%(family: opaque of dbl_gauge_m
{
auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl->LabelNames()) )
if ( labels_valid(lbl_map, hdl->LabelNames()) )
{
auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<DblGaugeMetricVal>(res);
@ -386,7 +386,7 @@ function Telemetry::__int_histogram_metric_get_or_add%(family: opaque of int_his
{
auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl->LabelNames()) )
if ( labels_valid(lbl_map, hdl->LabelNames()) )
{
auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<IntHistogramMetricVal>(res);
@ -449,7 +449,7 @@ function Telemetry::__dbl_histogram_metric_get_or_add%(family: opaque of dbl_his
{
auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl->LabelNames()) )
if ( labels_valid(lbl_map, hdl->LabelNames()) )
{
auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<DblHistogramMetricVal>(res);