Convert telemetry code to use prometheus-cpp

This commit is contained in:
Tim Wojtulewicz 2024-03-01 13:43:37 -07:00
parent 97a35011a7
commit a0ae06b3cd
22 changed files with 1517 additions and 1195 deletions

View file

@ -236,6 +236,11 @@ if (ZEEK_STANDALONE)
set(zeek_exe_access PRIVATE) set(zeek_exe_access PRIVATE)
else () else ()
add_library(zeek_lib STATIC) add_library(zeek_lib STATIC)
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_link_libraries(zeek_exe PRIVATE util)
target_link_libraries(zeek_exe PRIVATE procstat)
endif ()
endif () endif ()
if (TARGET zeek_lib) if (TARGET zeek_lib)
@ -248,6 +253,11 @@ if (TARGET zeek_lib)
install(TARGETS zeek_lib LIBRARY DESTINATION lib) install(TARGETS zeek_lib LIBRARY DESTINATION lib)
# Tell zeek_target_link_libraries to add library dependencies as PRIVATE. # Tell zeek_target_link_libraries to add library dependencies as PRIVATE.
set(zeek_lib_access PRIVATE) 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)
endif ()
endif () endif ()
# When building our fuzzers, we also need one extra top-level target that # When building our fuzzers, we also need one extra top-level target that

View file

@ -49,7 +49,7 @@ void EventHandler::Call(Args* vl, bool no_remote, double ts) {
telemetry_mgr->CounterFamily("zeek", "event-handler-invocations", {"name"}, 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", "1", true);
call_count = eh_invocations_family.GetOrAdd({{"name", name}}); call_count = eh_invocations_family->GetOrAdd({{"name", name}});
} }
call_count->Inc(); call_count->Inc();
@ -113,4 +113,6 @@ void EventHandler::NewEvent(Args* vl) {
event_mgr.Dispatch(ev); event_mgr.Dispatch(ev);
} }
uint64_t EventHandler::CallCount() const { return call_count ? call_count->Value() : 0; }
} // namespace zeek } // namespace zeek

View file

@ -9,7 +9,6 @@
#include "zeek/Type.h" #include "zeek/Type.h"
#include "zeek/ZeekArgs.h" #include "zeek/ZeekArgs.h"
#include "zeek/ZeekList.h" #include "zeek/ZeekList.h"
#include "zeek/telemetry/Counter.h"
namespace zeek { namespace zeek {
@ -17,6 +16,10 @@ namespace run_state {
extern double network_time; extern double network_time;
} // namespace run_state } // namespace run_state
namespace telemetry {
class IntCounter;
}
class Func; class Func;
using FuncPtr = IntrusivePtr<Func>; using FuncPtr = IntrusivePtr<Func>;
@ -60,7 +63,8 @@ public:
void SetGenerateAlways(bool arg_generate_always = true) { generate_always = arg_generate_always; } void SetGenerateAlways(bool arg_generate_always = true) { generate_always = arg_generate_always; }
bool GenerateAlways() const { return generate_always; } bool GenerateAlways() const { return generate_always; }
uint64_t CallCount() const { return call_count ? call_count->Value() : 0; } // Returns the number of times this EventHandler has been called since startup.
uint64_t CallCount() const;
private: private:
void NewEvent(zeek::Args* vl); // Raise new_event() meta event. void NewEvent(zeek::Args* vl); // Raise new_event() meta event.
@ -74,7 +78,7 @@ private:
bool generate_always; bool generate_always;
// Initialize this lazy, so we don't expose metrics for 0 values. // Initialize this lazy, so we don't expose metrics for 0 values.
std::optional<zeek::telemetry::IntCounter> call_count; std::shared_ptr<zeek::telemetry::IntCounter> call_count;
std::unordered_set<std::string> auto_publish; std::unordered_set<std::string> auto_publish;
}; };

View file

@ -1096,29 +1096,31 @@ std::optional<BrokerData> TelemetryVal::DoSerializeData() const { return std::nu
bool TelemetryVal::DoUnserializeData(BrokerDataView) { return false; } bool TelemetryVal::DoUnserializeData(BrokerDataView) { return false; }
TelemetryVal::TelemetryVal(telemetry::IntCounter) : OpaqueVal(int_counter_metric_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::IntCounter>) : OpaqueVal(int_counter_metric_type) {}
TelemetryVal::TelemetryVal(telemetry::IntCounterFamily) : OpaqueVal(int_counter_metric_family_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::IntCounterFamily>) : OpaqueVal(int_counter_metric_family_type) {}
TelemetryVal::TelemetryVal(telemetry::DblCounter) : OpaqueVal(dbl_counter_metric_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::DblCounter>) : OpaqueVal(dbl_counter_metric_type) {}
TelemetryVal::TelemetryVal(telemetry::DblCounterFamily) : OpaqueVal(dbl_counter_metric_family_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::DblCounterFamily>) : OpaqueVal(dbl_counter_metric_family_type) {}
TelemetryVal::TelemetryVal(telemetry::IntGauge) : OpaqueVal(int_gauge_metric_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::IntGauge>) : OpaqueVal(int_gauge_metric_type) {}
TelemetryVal::TelemetryVal(telemetry::IntGaugeFamily) : OpaqueVal(int_gauge_metric_family_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::IntGaugeFamily>) : OpaqueVal(int_gauge_metric_family_type) {}
TelemetryVal::TelemetryVal(telemetry::DblGauge) : OpaqueVal(dbl_gauge_metric_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::DblGauge>) : OpaqueVal(dbl_gauge_metric_type) {}
TelemetryVal::TelemetryVal(telemetry::DblGaugeFamily) : OpaqueVal(dbl_gauge_metric_family_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::DblGaugeFamily>) : OpaqueVal(dbl_gauge_metric_family_type) {}
TelemetryVal::TelemetryVal(telemetry::IntHistogram) : OpaqueVal(int_histogram_metric_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::IntHistogram>) : OpaqueVal(int_histogram_metric_type) {}
TelemetryVal::TelemetryVal(telemetry::IntHistogramFamily) : OpaqueVal(int_histogram_metric_family_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::IntHistogramFamily>)
: OpaqueVal(int_histogram_metric_family_type) {}
TelemetryVal::TelemetryVal(telemetry::DblHistogram) : OpaqueVal(dbl_histogram_metric_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::DblHistogram>) : OpaqueVal(dbl_histogram_metric_type) {}
TelemetryVal::TelemetryVal(telemetry::DblHistogramFamily) : OpaqueVal(dbl_histogram_metric_family_type) {} TelemetryVal::TelemetryVal(std::shared_ptr<telemetry::DblHistogramFamily>)
: OpaqueVal(dbl_histogram_metric_family_type) {}
IMPLEMENT_OPAQUE_VALUE(IntCounterMetricVal) IMPLEMENT_OPAQUE_VALUE(IntCounterMetricVal)
IMPLEMENT_OPAQUE_VALUE(IntCounterMetricFamilyVal) IMPLEMENT_OPAQUE_VALUE(IntCounterMetricFamilyVal)

View file

@ -448,18 +448,18 @@ private:
*/ */
class TelemetryVal : public OpaqueVal { class TelemetryVal : public OpaqueVal {
protected: protected:
explicit TelemetryVal(telemetry::IntCounter); explicit TelemetryVal(std::shared_ptr<telemetry::IntCounter>);
explicit TelemetryVal(telemetry::IntCounterFamily); explicit TelemetryVal(std::shared_ptr<telemetry::IntCounterFamily>);
explicit TelemetryVal(telemetry::DblCounter); explicit TelemetryVal(std::shared_ptr<telemetry::DblCounter>);
explicit TelemetryVal(telemetry::DblCounterFamily); explicit TelemetryVal(std::shared_ptr<telemetry::DblCounterFamily>);
explicit TelemetryVal(telemetry::IntGauge); explicit TelemetryVal(std::shared_ptr<telemetry::IntGauge>);
explicit TelemetryVal(telemetry::IntGaugeFamily); explicit TelemetryVal(std::shared_ptr<telemetry::IntGaugeFamily>);
explicit TelemetryVal(telemetry::DblGauge); explicit TelemetryVal(std::shared_ptr<telemetry::DblGauge>);
explicit TelemetryVal(telemetry::DblGaugeFamily); explicit TelemetryVal(std::shared_ptr<telemetry::DblGaugeFamily>);
explicit TelemetryVal(telemetry::IntHistogram); explicit TelemetryVal(std::shared_ptr<telemetry::IntHistogram>);
explicit TelemetryVal(telemetry::IntHistogramFamily); explicit TelemetryVal(std::shared_ptr<telemetry::IntHistogramFamily>);
explicit TelemetryVal(telemetry::DblHistogram); explicit TelemetryVal(std::shared_ptr<telemetry::DblHistogram>);
explicit TelemetryVal(telemetry::DblHistogramFamily); explicit TelemetryVal(std::shared_ptr<telemetry::DblHistogramFamily>);
std::optional<BrokerData> DoSerializeData() const override; std::optional<BrokerData> DoSerializeData() const override;
bool DoUnserializeData(BrokerDataView data) override; bool DoUnserializeData(BrokerDataView data) override;
@ -468,11 +468,11 @@ protected:
template<class Handle> template<class Handle>
class TelemetryValImpl : public TelemetryVal { class TelemetryValImpl : public TelemetryVal {
public: public:
using HandleType = Handle; using HandleType = std::shared_ptr<Handle>;
explicit TelemetryValImpl(Handle hdl) : TelemetryVal(hdl), hdl(hdl) {} explicit TelemetryValImpl(HandleType hdl) : TelemetryVal(hdl), hdl(hdl) {}
Handle GetHandle() const noexcept { return hdl; } HandleType GetHandle() const noexcept { return hdl; }
static zeek::OpaqueValPtr OpaqueInstantiate() { static zeek::OpaqueValPtr OpaqueInstantiate() {
reporter->Error("TelemetryValImpl::OpaqueInstantiate is unsupported"); reporter->Error("TelemetryValImpl::OpaqueInstantiate is unsupported");
@ -485,7 +485,7 @@ protected:
const char* OpaqueName() const override { return Handle::OpaqueName; } const char* OpaqueName() const override { return Handle::OpaqueName; }
private: private:
Handle hdl; HandleType hdl;
}; };
using IntCounterMetricVal = TelemetryValImpl<telemetry::IntCounter>; using IntCounterMetricVal = TelemetryValImpl<telemetry::IntCounter>;

View file

@ -415,8 +415,6 @@ void Manager::InitPostScript() {
bstate->subscriber.add_topic(broker::topic::store_events(), true); bstate->subscriber.add_topic(broker::topic::store_events(), true);
telemetry_mgr->InitPostBrokerSetup(bstate->endpoint);
InitializeBrokerStoreForwarding(); InitializeBrokerStoreForwarding();
} }

View file

@ -223,9 +223,9 @@ struct Manager::WriterInfo {
bool hook_initialized = false; bool hook_initialized = false;
string instantiating_filter; string instantiating_filter;
telemetry::IntCounter total_writes; std::shared_ptr<telemetry::IntCounter> total_writes;
WriterInfo(telemetry::IntCounter total_writes) : total_writes(total_writes) {} WriterInfo(std::shared_ptr<telemetry::IntCounter> total_writes) : total_writes(std::move(total_writes)) {}
}; };
struct Manager::Stream { struct Manager::Stream {
@ -244,7 +244,7 @@ struct Manager::Stream {
bool enable_remote = false; bool enable_remote = false;
std::optional<telemetry::IntCounter> total_writes; // Initialized on first write. std::shared_ptr<telemetry::IntCounter> total_writes; // Initialized on first write.
// State about delayed writes for this Stream. // State about delayed writes for this Stream.
detail::DelayQueue delay_queue; detail::DelayQueue delay_queue;
@ -947,7 +947,7 @@ bool Manager::Write(EnumVal* id, RecordVal* columns_arg) {
if ( ! stream->total_writes ) { if ( ! stream->total_writes ) {
std::string module_name = zeek::detail::extract_module_name(stream->name.c_str()); std::string module_name = zeek::detail::extract_module_name(stream->name.c_str());
std::initializer_list<telemetry::LabelView> labels{{"module", module_name}, {"stream", stream->name}}; std::initializer_list<telemetry::LabelView> labels{{"module", module_name}, {"stream", stream->name}};
stream->total_writes = total_log_stream_writes_family.GetOrAdd(labels); stream->total_writes = total_log_stream_writes_family->GetOrAdd(labels);
} }
stream->total_writes->Inc(); stream->total_writes->Inc();
@ -1173,7 +1173,7 @@ bool Manager::WriteToFilters(const Manager::Stream* stream, zeek::RecordValPtr c
} }
assert(w != stream->writers.end()); assert(w != stream->writers.end());
w->second->total_writes.Inc(); w->second->total_writes->Inc();
// Write takes ownership of vals. // Write takes ownership of vals.
assert(writer); assert(writer);
@ -1616,7 +1616,7 @@ WriterFrontend* Manager::CreateWriter(EnumVal* id, EnumVal* writer, WriterBacken
{"filter-name", instantiating_filter}, {"filter-name", instantiating_filter},
{"path", info->path}}; {"path", info->path}};
WriterInfo* winfo = new WriterInfo(zeek::log_mgr->total_log_writer_writes_family.GetOrAdd(labels)); WriterInfo* winfo = new WriterInfo(zeek::log_mgr->total_log_writer_writes_family->GetOrAdd(labels));
winfo->type = writer->Ref()->AsEnumVal(); winfo->type = writer->Ref()->AsEnumVal();
winfo->writer = nullptr; winfo->writer = nullptr;
winfo->open_time = run_state::network_time; winfo->open_time = run_state::network_time;

View file

@ -403,8 +403,8 @@ private:
FuncPtr rotation_format_func; FuncPtr rotation_format_func;
FuncPtr log_stream_policy_hook; FuncPtr log_stream_policy_hook;
telemetry::IntCounterFamily total_log_stream_writes_family; std::shared_ptr<telemetry::IntCounterFamily> total_log_stream_writes_family;
telemetry::IntCounterFamily total_log_writer_writes_family; std::shared_ptr<telemetry::IntCounterFamily> total_log_writer_writes_family;
zeek_uint_t last_delay_token = 0; zeek_uint_t last_delay_token = 0;
std::vector<detail::WriteContext> active_writes; std::vector<detail::WriteContext> active_writes;

View file

@ -32,22 +32,22 @@ namespace detail {
class ProtocolStats { class ProtocolStats {
public: public:
struct Protocol { struct Protocol {
telemetry::IntGauge active; std::shared_ptr<telemetry::IntGauge> active;
telemetry::IntCounter total; std::shared_ptr<telemetry::IntCounter> total;
ssize_t max = 0; ssize_t max = 0;
Protocol(telemetry::IntGaugeFamily active_family, telemetry::IntCounterFamily total_family, Protocol(const std::shared_ptr<telemetry::IntGaugeFamily>& active_family,
std::string protocol) const std::shared_ptr<telemetry::IntCounterFamily>& total_family, std::string protocol)
: active(active_family.GetOrAdd({{"protocol", protocol}})), : active(active_family->GetOrAdd({{"protocol", protocol}})),
total(total_family.GetOrAdd({{"protocol", protocol}})) {} total(total_family->GetOrAdd({{"protocol", protocol}})) {}
}; };
using ProtocolMap = std::map<std::string, Protocol>; using ProtocolMap = std::map<std::string, Protocol>;
ProtocolMap::iterator InitCounters(const std::string& protocol) { ProtocolMap::iterator InitCounters(const std::string& protocol) {
telemetry::IntGaugeFamily active_family = auto active_family =
telemetry_mgr->GaugeFamily("zeek", "active-sessions", {"protocol"}, "Active Zeek Sessions"); telemetry_mgr->GaugeFamily("zeek", "active-sessions", {"protocol"}, "Active Zeek Sessions");
telemetry::IntCounterFamily total_family = 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", "1", true);
auto [it, inserted] = entries.insert({protocol, Protocol{active_family, total_family, protocol}}); auto [it, inserted] = entries.insert({protocol, Protocol{active_family, total_family, protocol}});
@ -116,7 +116,7 @@ void Manager::Remove(Session* s) {
else { else {
Connection* c = static_cast<Connection*>(s); Connection* c = static_cast<Connection*>(s);
if ( auto* stat_block = stats->GetCounters(c->TransportIdentifier()) ) if ( auto* stat_block = stats->GetCounters(c->TransportIdentifier()) )
stat_block->active.Dec(); stat_block->active->Dec();
} }
// Mark that the session isn't in the table so that in case the // Mark that the session isn't in the table so that in case the
@ -190,18 +190,18 @@ void Manager::Clear() {
void Manager::GetStats(Stats& s) { void Manager::GetStats(Stats& s) {
auto* tcp_stats = stats->GetCounters("tcp"); auto* tcp_stats = stats->GetCounters("tcp");
s.max_TCP_conns = tcp_stats->max; s.max_TCP_conns = tcp_stats->max;
s.num_TCP_conns = tcp_stats->active.Value(); s.num_TCP_conns = tcp_stats->active->Value();
s.cumulative_TCP_conns = tcp_stats->total.Value(); s.cumulative_TCP_conns = tcp_stats->total->Value();
auto* udp_stats = stats->GetCounters("udp"); auto* udp_stats = stats->GetCounters("udp");
s.max_UDP_conns = udp_stats->max; s.max_UDP_conns = udp_stats->max;
s.num_UDP_conns = udp_stats->active.Value(); s.num_UDP_conns = udp_stats->active->Value();
s.cumulative_UDP_conns = udp_stats->total.Value(); s.cumulative_UDP_conns = udp_stats->total->Value();
auto* icmp_stats = stats->GetCounters("icmp"); auto* icmp_stats = stats->GetCounters("icmp");
s.max_ICMP_conns = icmp_stats->max; s.max_ICMP_conns = icmp_stats->max;
s.num_ICMP_conns = icmp_stats->active.Value(); s.num_ICMP_conns = icmp_stats->active->Value();
s.cumulative_ICMP_conns = icmp_stats->total.Value(); s.cumulative_ICMP_conns = icmp_stats->total->Value();
s.num_fragments = zeek::detail::fragment_mgr->Size(); s.num_fragments = zeek::detail::fragment_mgr->Size();
s.max_fragments = zeek::detail::fragment_mgr->MaxFragments(); s.max_fragments = zeek::detail::fragment_mgr->MaxFragments();
@ -238,10 +238,10 @@ void Manager::InsertSession(detail::Key key, Session* session) {
std::string protocol = session->TransportIdentifier(); std::string protocol = session->TransportIdentifier();
if ( auto* stat_block = stats->GetCounters(protocol) ) { if ( auto* stat_block = stats->GetCounters(protocol) ) {
stat_block->active.Inc(); stat_block->active->Inc();
stat_block->total.Inc(); stat_block->total->Inc();
if ( stat_block->active.Value() > stat_block->max ) if ( stat_block->active->Value() > stat_block->max )
stat_block->max++; stat_block->max++;
} }
} }

View file

@ -1 +1,8 @@
zeek_add_subdir_library(telemetry SOURCES Manager.cc BIFS telemetry.bif) zeek_add_subdir_library(
telemetry
SOURCES
Manager.cc
MetricFamily.cc
ProcessStats.cc
BIFS
telemetry.bif)

View file

@ -8,190 +8,182 @@
#include "zeek/Span.h" #include "zeek/Span.h"
#include "zeek/telemetry/MetricFamily.h" #include "zeek/telemetry/MetricFamily.h"
#include "zeek/telemetry/telemetry.bif.h"
#include "broker/telemetry/fwd.hh" #include "prometheus/counter.h"
#include "prometheus/family.h"
namespace zeek::telemetry { namespace zeek::telemetry {
class DblCounterFamily; template<typename BaseType>
class IntCounterFamily; class BaseCounter {
class Manager;
/**
* A handle to a metric that represents an integer value that can only go up.
*/
class IntCounter {
public: public:
friend class IntCounterFamily; using Handle = prometheus::Counter;
using FamilyType = prometheus::Family<Handle>;
static inline const char* OpaqueName = "IntCounterMetricVal";
IntCounter() = delete;
IntCounter(const IntCounter&) noexcept = default;
IntCounter& operator=(const IntCounter&) noexcept = default;
/** /**
* Increments the value by 1. * Increments the value by 1.
*/ */
void Inc() noexcept { broker::telemetry::inc(hdl); } void Inc() noexcept { Inc(1); }
/** /**
* Increments the value by @p amount. * Increments the value by @p amount.
* @pre `amount >= 0` * @pre `amount >= 0`
*/ */
void Inc(int64_t amount) noexcept { broker::telemetry::inc(hdl, amount); } void Inc(BaseType amount) noexcept { handle.Increment(amount); }
/** /**
* Increments the value by 1. * Increments the value by 1.
* @return The new value. * @return The new value.
*/ */
int64_t operator++() noexcept { return broker::telemetry::inc(hdl); } BaseType operator++() noexcept {
Inc(1);
return Value();
}
/** BaseType Value() const noexcept { return static_cast<BaseType>(handle.Value()); }
* @return The current value.
*/
int64_t Value() const noexcept { return broker::telemetry::value(hdl); }
/** bool operator==(const BaseCounter<BaseType>& rhs) const noexcept { return &handle == &rhs.handle; }
* @return Whether @c this and @p other refer to the same counter. bool operator!=(const BaseCounter<BaseType>& rhs) const noexcept { return &handle != &rhs.handle; }
*/
constexpr bool IsSameAs(const IntCounter& other) const noexcept { return hdl == other.hdl; }
private: bool CompareLabels(const prometheus::Labels& lbls) const { return labels == lbls; }
using Handle = broker::telemetry::int_counter_hdl*;
explicit IntCounter(Handle hdl) noexcept : hdl(hdl) {} protected:
explicit BaseCounter(FamilyType& family, const prometheus::Labels& labels) noexcept
: handle(family.Add(labels)), labels(labels) {}
Handle hdl; Handle& handle;
prometheus::Labels labels;
BaseType last_value = 0;
}; };
/** /**
* Checks whether two @ref IntCounter handles are identical. * A handle to a metric that represents an integer value that can only go up.
* @return Whether @p lhs and @p rhs refer to the same object.
* @note compare their @c value instead to check for equality.
*/ */
constexpr bool operator==(const IntCounter& lhs, const IntCounter& rhs) noexcept { return lhs.IsSameAs(rhs); } class IntCounter : public BaseCounter<uint64_t> {
public:
static inline const char* OpaqueName = "IntCounterMetricVal";
explicit IntCounter(FamilyType& family, const prometheus::Labels& labels) noexcept : BaseCounter(family, labels) {}
};
/// @relates IntCounter /**
constexpr bool operator!=(const IntCounter& lhs, const IntCounter& rhs) noexcept { return ! (lhs == rhs); } * A handle to a metric that represents a double value that can only go up.
*/
class DblCounter : public BaseCounter<double> {
public:
static inline const char* OpaqueName = "DblCounterMetricVal";
explicit DblCounter(FamilyType& family, const prometheus::Labels& labels) noexcept : BaseCounter(family, labels) {}
};
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::Labels p_labels = BuildPrometheusLabels(labels);
auto check = [&](const std::shared_ptr<CounterType>& counter) { return counter->CompareLabels(p_labels); };
if ( auto it = std::find_if(counters.begin(), counters.end(), check); it != counters.end() )
return *it;
auto counter = std::make_shared<CounterType>(family, p_labels);
counters.push_back(counter);
return counter;
}
/**
* @copydoc GetOrAdd
*/
std::shared_ptr<CounterType> GetOrAdd(std::initializer_list<LabelView> labels) {
return GetOrAdd(Span{labels.begin(), labels.size()});
}
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() )
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;
std::vector<std::shared_ptr<CounterType>> counters;
};
/** /**
* Manages a collection of IntCounter metrics. * Manages a collection of IntCounter metrics.
*/ */
class IntCounterFamily : public MetricFamily { class IntCounterFamily : public BaseCounterFamily<IntCounter, uint64_t> {
public: public:
friend class Manager;
static inline const char* OpaqueName = "IntCounterMetricFamilyVal"; static inline const char* OpaqueName = "IntCounterMetricFamilyVal";
using InstanceType = IntCounter; 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(const IntCounterFamily&) noexcept = default;
IntCounterFamily& operator=(const IntCounterFamily&) noexcept = default; IntCounterFamily& operator=(const IntCounterFamily&) noexcept = delete;
/** zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::INT_COUNTER; }
* Returns the metrics handle for given labels, creating a new instance
* lazily if necessary.
*/
IntCounter GetOrAdd(Span<const LabelView> labels) { return IntCounter{int_counter_get_or_add(hdl, labels)}; }
/**
* @copydoc GetOrAdd
*/
IntCounter GetOrAdd(std::initializer_list<LabelView> labels) {
return GetOrAdd(Span{labels.begin(), labels.size()});
}
private:
using Handle = broker::telemetry::int_counter_family_hdl*;
explicit IntCounterFamily(Handle hdl) : MetricFamily(upcast(hdl)) {}
}; };
/**
* A handle to a metric that represents a floating point value that can only go
* up.
*/
class DblCounter {
public:
friend class DblCounterFamily;
static inline const char* OpaqueName = "DblCounterMetricVal";
DblCounter() = delete;
DblCounter(const DblCounter&) noexcept = default;
DblCounter& operator=(const DblCounter&) noexcept = default;
/**
* Increments the value by 1.
*/
void Inc() noexcept { broker::telemetry::inc(hdl); }
/**
* Increments the value by @p amount.
* @pre `amount >= 0`
*/
void Inc(double amount) noexcept { broker::telemetry::inc(hdl, amount); }
/**
* @return The current value.
*/
double Value() const noexcept { return broker::telemetry::value(hdl); }
/**
* @return Whether @c this and @p other refer to the same counter.
*/
constexpr bool IsSameAs(const DblCounter& other) const noexcept { return hdl == other.hdl; }
private:
using Handle = broker::telemetry::dbl_counter_hdl*;
explicit DblCounter(Handle hdl) noexcept : hdl(hdl) {}
Handle hdl;
};
/**
* Checks whether two @ref DblCounter handles are identical.
* @return Whether @p lhs and @p rhs refer to the same object.
* @note compare their @c value instead to check for equality.
*/
constexpr bool operator==(const DblCounter& lhs, const DblCounter& rhs) noexcept { return lhs.IsSameAs(rhs); }
/// @relates DblCounter
constexpr bool operator!=(const DblCounter& lhs, const DblCounter& rhs) noexcept { return ! (lhs == rhs); }
/** /**
* Manages a collection of DblCounter metrics. * Manages a collection of DblCounter metrics.
*/ */
class DblCounterFamily : public MetricFamily { class DblCounterFamily : public BaseCounterFamily<DblCounter, double> {
public: public:
friend class Manager;
static inline const char* OpaqueName = "DblCounterMetricFamilyVal"; static inline const char* OpaqueName = "DblCounterMetricFamilyVal";
using InstanceType = DblCounter; 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(const DblCounterFamily&) noexcept = default;
DblCounterFamily& operator=(const DblCounterFamily&) noexcept = default; DblCounterFamily& operator=(const DblCounterFamily&) noexcept = delete;
/** zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::DOUBLE_COUNTER; }
* Returns the metrics handle for given labels, creating a new instance
* lazily if necessary.
*/
DblCounter GetOrAdd(Span<const LabelView> labels) { return DblCounter{dbl_counter_get_or_add(hdl, labels)}; }
/**
* @copydoc GetOrAdd
*/
DblCounter GetOrAdd(std::initializer_list<LabelView> labels) {
return GetOrAdd(Span{labels.begin(), labels.size()});
}
private:
using Handle = broker::telemetry::dbl_counter_family_hdl*;
explicit DblCounterFamily(Handle hdl) : MetricFamily(upcast(hdl)) {}
}; };
namespace detail { namespace detail {

View file

@ -8,211 +8,216 @@
#include "zeek/Span.h" #include "zeek/Span.h"
#include "zeek/telemetry/MetricFamily.h" #include "zeek/telemetry/MetricFamily.h"
#include "zeek/telemetry/telemetry.bif.h"
#include "broker/telemetry/fwd.hh" #include "prometheus/family.h"
#include "prometheus/gauge.h"
namespace zeek::telemetry { namespace zeek::telemetry {
class DblGaugeFamily; template<typename BaseType>
class IntGaugeFamily; class BaseGauge {
class Manager; public:
using Handle = prometheus::Gauge;
using FamilyType = prometheus::Family<Handle>;
/**
* Increments the value by 1.
*/
void Inc() noexcept { Inc(1); }
/**
* Increments the value by @p amount.
*/
void Inc(BaseType amount) noexcept { handle.Increment(amount); }
/**
* Increments the value by 1.
* @return The new value.
*/
BaseType operator++() noexcept {
Inc(1);
return Value();
}
/**
* Decrements the value by 1.
*/
void Dec() noexcept { Dec(1); }
/**
* Decrements the value by @p amount.
*/
void Dec(BaseType amount) noexcept { handle.Decrement(amount); }
/**
* Decrements the value by 1.
* @return The new value.
*/
BaseType operator--() noexcept {
Dec(1);
return Value();
}
BaseType Value() const noexcept { return static_cast<BaseType>(handle.Value()); }
/**
* Directly sets the value of the gauge.
*/
void Set(BaseType v) { handle.Set(v); }
bool operator==(const BaseGauge<BaseType>& rhs) const noexcept { return &handle == &rhs.handle; }
bool operator!=(const BaseGauge<BaseType>& rhs) const noexcept { return &handle != &rhs.handle; }
bool CompareLabels(const prometheus::Labels& lbls) const { return labels == lbls; }
protected:
explicit BaseGauge(FamilyType& family, const prometheus::Labels& labels) noexcept
: handle(family.Add(labels)), labels(labels) {}
Handle& handle;
prometheus::Labels labels;
BaseType last_value = 0;
};
/** /**
* A handle to a metric that represents an integer value. Gauges are more * A handle to a metric that represents an integer value. Gauges are more
* permissive than counters and also allow decrementing the value. * permissive than counters and also allow decrementing the value.
*/ */
class IntGauge { class IntGauge : public BaseGauge<int64_t> {
public: public:
friend class IntGaugeFamily;
static inline const char* OpaqueName = "IntGaugeMetricVal"; static inline const char* OpaqueName = "IntGaugeMetricVal";
IntGauge() = delete; explicit IntGauge(FamilyType& family, const prometheus::Labels& labels) noexcept : BaseGauge(family, labels) {}
IntGauge(const IntGauge&) noexcept = default;
IntGauge& operator=(const IntGauge&) noexcept = default;
/** IntGauge(const IntGauge&) = delete;
* Increments the value by 1. IntGauge& operator=(const IntGauge&) = delete;
*/
void Inc() noexcept { broker::telemetry::inc(hdl); }
/**
* Increments the value by @p amount.
*/
void Inc(int64_t amount) noexcept { broker::telemetry::inc(hdl, amount); }
/**
* Increments the value by 1.
* @return The new value.
*/
int64_t operator++() noexcept { return broker::telemetry::inc(hdl); }
/**
* Decrements the value by 1.
*/
void Dec() noexcept { broker::telemetry::dec(hdl); }
/**
* Decrements the value by @p amount.
*/
void Dec(int64_t amount) noexcept { broker::telemetry::dec(hdl, amount); }
/**
* Decrements the value by 1.
* @return The new value.
*/
int64_t operator--() noexcept { return broker::telemetry::dec(hdl); }
/**
* @return The current value.
*/
int64_t Value() const noexcept { return broker::telemetry::value(hdl); }
/**
* @return Whether @c this and @p other refer to the same counter.
*/
constexpr bool IsSameAs(const IntGauge& other) const noexcept { return hdl == other.hdl; }
private:
using Handle = broker::telemetry::int_gauge_hdl*;
explicit IntGauge(Handle hdl) noexcept : hdl(hdl) {}
Handle hdl;
}; };
/** /**
* Checks whether two @ref IntGauge handles are identical. * A handle to a metric that represents a double value. Gauges are more
* @return Whether @p lhs and @p rhs refer to the same object. * permissive than counters and also allow decrementing the value.
* @note compare their @c value instead to check for equality.
*/ */
constexpr bool operator==(const IntGauge& lhs, const IntGauge& rhs) noexcept { return lhs.IsSameAs(rhs); } class DblGauge : public BaseGauge<double> {
public:
static inline const char* OpaqueName = "DblGaugeMetricVal";
/// @relates IntGauge explicit DblGauge(FamilyType& family, const prometheus::Labels& labels) noexcept : BaseGauge(family, labels) {}
constexpr bool operator!=(const IntGauge& lhs, const IntGauge& rhs) noexcept { return ! (lhs == rhs); }
DblGauge(const DblGauge&) = delete;
DblGauge& operator=(const DblGauge&) = delete;
};
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::Labels p_labels = BuildPrometheusLabels(labels);
auto check = [&](const std::shared_ptr<GaugeType>& gauge) { return gauge->CompareLabels(p_labels); };
if ( auto it = std::find_if(gauges.begin(), gauges.end(), check); it != gauges.end() )
return *it;
auto gauge = std::make_shared<GaugeType>(family, p_labels);
gauges.push_back(gauge);
return gauge;
}
/**
* @copydoc GetOrAdd
*/
std::shared_ptr<GaugeType> GetOrAdd(std::initializer_list<LabelView> labels) {
return GetOrAdd(Span{labels.begin(), labels.size()});
}
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() )
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;
std::vector<std::shared_ptr<GaugeType>> gauges;
};
/** /**
* Manages a collection of IntGauge metrics. * Manages a collection of IntGauge metrics.
*/ */
class IntGaugeFamily : public MetricFamily { class IntGaugeFamily : public BaseGaugeFamily<IntGauge, int64_t> {
public: public:
friend class Manager;
static inline const char* OpaqueName = "IntGaugeMetricFamilyVal"; static inline const char* OpaqueName = "IntGaugeMetricFamilyVal";
using InstanceType = IntGauge; 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(const IntGaugeFamily&) noexcept = default;
IntGaugeFamily& operator=(const IntGaugeFamily&) noexcept = default; IntGaugeFamily& operator=(const IntGaugeFamily&) noexcept = delete;
/** zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::INT_GAUGE; }
* Returns the metrics handle for given labels, creating a new instance
* lazily if necessary.
*/
IntGauge GetOrAdd(Span<const LabelView> labels) { return IntGauge{int_gauge_get_or_add(hdl, labels)}; }
/**
* @copydoc GetOrAdd
*/
IntGauge GetOrAdd(std::initializer_list<LabelView> labels) { return GetOrAdd(Span{labels.begin(), labels.size()}); }
private:
using Handle = broker::telemetry::int_gauge_family_hdl*;
explicit IntGaugeFamily(Handle hdl) : MetricFamily(upcast(hdl)) {}
}; };
/**
* A handle to a metric that represents a floating point value. Gauges are more
* permissive than counters and also allow decrementing the value.
*/
class DblGauge {
public:
friend class DblGaugeFamily;
static inline const char* OpaqueName = "DblGaugeMetricVal";
DblGauge() = delete;
DblGauge(const DblGauge&) noexcept = default;
DblGauge& operator=(const DblGauge&) noexcept = default;
/**
* Increments the value by 1.
*/
void Inc() noexcept { broker::telemetry::inc(hdl); }
/**
* Increments the value by @p amount.
*/
void Inc(double amount) noexcept { broker::telemetry::inc(hdl, amount); }
/**
* Increments the value by 1.
*/
void Dec() noexcept { broker::telemetry::dec(hdl); }
/**
* Increments the value by @p amount.
*/
void Dec(double amount) noexcept { broker::telemetry::dec(hdl, amount); }
/**
* @return The current value.
*/
double Value() const noexcept { return broker::telemetry::value(hdl); }
/**
* @return Whether @c this and @p other refer to the same counter.
*/
constexpr bool IsSameAs(const DblGauge& other) const noexcept { return hdl == other.hdl; }
private:
using Handle = broker::telemetry::dbl_gauge_hdl*;
explicit DblGauge(Handle hdl) noexcept : hdl(hdl) {}
Handle hdl;
};
/**
* Checks whether two @ref DblGauge handles are identical.
* @return Whether @p lhs and @p rhs refer to the same object.
* @note compare their @c value instead to check for equality.
*/
constexpr bool operator==(const DblGauge& lhs, const DblGauge& rhs) noexcept { return lhs.IsSameAs(rhs); }
/// @relates DblGauge
constexpr bool operator!=(const DblGauge& lhs, const DblGauge& rhs) noexcept { return ! (lhs == rhs); }
/** /**
* Manages a collection of DblGauge metrics. * Manages a collection of DblGauge metrics.
*/ */
class DblGaugeFamily : public MetricFamily { class DblGaugeFamily : public BaseGaugeFamily<DblGauge, double> {
public: public:
friend class Manager;
static inline const char* OpaqueName = "DblGaugeMetricFamilyVal"; static inline const char* OpaqueName = "DblGaugeMetricFamilyVal";
using InstanceType = DblGauge; 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(const DblGaugeFamily&) noexcept = default;
DblGaugeFamily& operator=(const DblGaugeFamily&) noexcept = default; DblGaugeFamily& operator=(const DblGaugeFamily&) noexcept = delete;
/** zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::DOUBLE_GAUGE; }
* Returns the metrics handle for given labels, creating a new instance
* lazily if necessary.
*/
DblGauge GetOrAdd(Span<const LabelView> labels) { return DblGauge{dbl_gauge_get_or_add(hdl, labels)}; }
/**
* @copydoc GetOrAdd
*/
DblGauge GetOrAdd(std::initializer_list<LabelView> labels) { return GetOrAdd(Span{labels.begin(), labels.size()}); }
private:
using Handle = broker::telemetry::dbl_gauge_family_hdl*;
explicit DblGaugeFamily(Handle hdl) : MetricFamily(upcast(hdl)) {}
}; };
namespace detail { namespace detail {

View file

@ -8,193 +8,234 @@
#include "zeek/Span.h" #include "zeek/Span.h"
#include "zeek/telemetry/MetricFamily.h" #include "zeek/telemetry/MetricFamily.h"
#include "zeek/telemetry/telemetry.bif.h"
#include "broker/telemetry/fwd.hh" #include "prometheus/family.h"
#include "prometheus/histogram.h"
namespace zeek::telemetry { namespace zeek::telemetry {
class DblHistogramFamily; template<typename BaseType>
class IntHistogramFamily; class BaseHistogram {
class Manager; public:
using Handle = prometheus::Histogram;
using FamilyType = prometheus::Family<Handle>;
/**
* 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.
// TODO
BaseType Sum() const noexcept {
auto metric = handle.Collect();
return static_cast<BaseType>(metric.histogram.sample_sum);
}
bool operator==(const BaseHistogram<BaseType>& rhs) const noexcept { return &handle == &rhs.handle; }
bool operator!=(const BaseHistogram<BaseType>& 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 * A handle to a metric that represents an aggregable distribution of observed
* measurements with integer precision. Sorts individual measurements into * measurements with integer precision. Sorts individual measurements into
* configurable buckets. * configurable buckets.
*/ */
class IntHistogram { class IntHistogram : public BaseHistogram<int64_t> {
public: public:
friend class IntHistogramFamily;
static inline const char* OpaqueName = "IntHistogramMetricVal"; 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() = delete;
IntHistogram(const IntHistogram&) noexcept = default; IntHistogram(const IntHistogram&) noexcept = delete;
IntHistogram& operator=(const IntHistogram&) noexcept = default; IntHistogram& operator=(const IntHistogram&) noexcept = delete;
/**
* 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(int64_t value) noexcept { return broker::telemetry::observe(hdl, value); }
/// @return The sum of all observed values.
int64_t Sum() const noexcept { return broker::telemetry::sum(hdl); }
/// @return The number of buckets, including the implicit "infinite" bucket.
size_t NumBuckets() const noexcept { return broker::telemetry::num_buckets(hdl); }
/// @return The number of observations in the bucket at @p index.
/// @pre index < NumBuckets()
int64_t CountAt(size_t index) const noexcept { return broker::telemetry::count_at(hdl, index); }
/// @return The upper bound of the bucket at @p index.
/// @pre index < NumBuckets()
int64_t UpperBoundAt(size_t index) const noexcept { return broker::telemetry::upper_bound_at(hdl, index); }
/**
* @return Whether @c this and @p other refer to the same histogram.
*/
constexpr bool IsSameAs(const IntHistogram& other) const noexcept { return hdl == other.hdl; }
private:
using Handle = broker::telemetry::int_histogram_hdl*;
explicit IntHistogram(Handle hdl) noexcept : hdl(hdl) {}
Handle hdl;
};
/**
* Checks whether two @ref IntHistogram handles are identical.
* @return Whether @p lhs and @p rhs refer to the same object.
*/
constexpr bool operator==(const IntHistogram& lhs, const IntHistogram& rhs) noexcept { return lhs.IsSameAs(rhs); }
/// @relates IntHistogram
constexpr bool operator!=(const IntHistogram& lhs, const IntHistogram& rhs) noexcept { return ! (lhs == rhs); }
/**
* Manages a collection of IntHistogram metrics.
*/
class IntHistogramFamily : public MetricFamily {
public:
friend class Manager;
static inline const char* OpaqueName = "IntHistogramMetricFamilyVal";
using InstanceType = IntHistogram;
IntHistogramFamily(const IntHistogramFamily&) noexcept = default;
IntHistogramFamily& operator=(const IntHistogramFamily&) noexcept = default;
/**
* Returns the metrics handle for given labels, creating a new instance
* lazily if necessary.
*/
IntHistogram GetOrAdd(Span<const LabelView> labels) { return IntHistogram{int_histogram_get_or_add(hdl, labels)}; }
/**
* @copydoc GetOrAdd
*/
IntHistogram GetOrAdd(std::initializer_list<LabelView> labels) {
return GetOrAdd(Span{labels.begin(), labels.size()});
}
private:
using Handle = broker::telemetry::int_histogram_family_hdl*;
explicit IntHistogramFamily(Handle hdl) : MetricFamily(upcast(hdl)) {}
}; };
/** /**
* A handle to a metric that represents an aggregable distribution of observed * A handle to a metric that represents an aggregable distribution of observed
* measurements with floating point precision. Sorts individual measurements * measurements with integer precision. Sorts individual measurements into
* into configurable buckets. * configurable buckets.
*/ */
class DblHistogram { class DblHistogram : public BaseHistogram<double> {
public: public:
friend class DblHistogramFamily;
static inline const char* OpaqueName = "DblHistogramMetricVal"; 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() = delete;
DblHistogram(const DblHistogram&) noexcept = default; DblHistogram(const DblHistogram&) noexcept = delete;
DblHistogram& operator=(const DblHistogram&) noexcept = default; DblHistogram& operator=(const DblHistogram&) noexcept = delete;
/**
* 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(double value) noexcept { broker::telemetry::observe(hdl, value); }
/// @return The sum of all observed values.
double Sum() const noexcept { return broker::telemetry::sum(hdl); }
/// @return The number of buckets, including the implicit "infinite" bucket.
size_t NumBuckets() const noexcept { return broker::telemetry::num_buckets(hdl); }
/// @return The number of observations in the bucket at @p index.
/// @pre index < NumBuckets()
int64_t CountAt(size_t index) const noexcept { return broker::telemetry::count_at(hdl, index); }
/// @return The upper bound of the bucket at @p index.
/// @pre index < NumBuckets()
double UpperBoundAt(size_t index) const noexcept { return broker::telemetry::upper_bound_at(hdl, index); }
/**
* @return Whether @c this and @p other refer to the same histogram.
*/
constexpr bool IsSameAs(const DblHistogram& other) const noexcept { return hdl == other.hdl; }
private:
using Handle = broker::telemetry::dbl_histogram_hdl*;
explicit DblHistogram(Handle hdl) noexcept : hdl(hdl) {}
Handle hdl;
}; };
/** template<class HistogramType, typename BaseType>
* Checks whether two @ref DblHistogram handles are identical. class BaseHistogramFamily : public MetricFamily,
* @return Whether @p lhs and @p rhs refer to the same object. public std::enable_shared_from_this<BaseHistogramFamily<HistogramType, BaseType>> {
*/
constexpr bool operator==(const DblHistogram& lhs, const DblHistogram& rhs) noexcept { return lhs.IsSameAs(rhs); }
/// @relates DblHistogram
constexpr bool operator!=(const DblHistogram& lhs, const DblHistogram& rhs) noexcept { return ! (lhs == rhs); }
/**
* Manages a collection of DblHistogram metrics.
*/
class DblHistogramFamily : public MetricFamily {
public: public:
friend class Manager;
static inline const char* OpaqueName = "DblHistogramMetricFamilyVal";
using InstanceType = DblHistogram;
DblHistogramFamily(const DblHistogramFamily&) noexcept = default;
DblHistogramFamily& operator=(const DblHistogramFamily&) noexcept = default;
/** /**
* Returns the metrics handle for given labels, creating a new instance * Returns the metrics handle for given labels, creating a new instance
* lazily if necessary. * lazily if necessary.
*/ */
DblHistogram GetOrAdd(Span<const LabelView> labels) { return DblHistogram{dbl_histogram_get_or_add(hdl, labels)}; } std::shared_ptr<HistogramType> GetOrAdd(Span<const LabelView> labels) {
prometheus::Labels p_labels = BuildPrometheusLabels(labels);
auto check = [&](const std::shared_ptr<HistogramType>& 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<HistogramType>(family, p_labels, boundaries);
histograms.push_back(histogram);
return histogram;
}
/** /**
* @copydoc GetOrAdd * @copydoc GetOrAdd
*/ */
DblHistogram GetOrAdd(std::initializer_list<LabelView> labels) { std::shared_ptr<HistogramType> GetOrAdd(std::initializer_list<LabelView> labels) {
return GetOrAdd(Span{labels.begin(), labels.size()}); return GetOrAdd(Span{labels.begin(), labels.size()});
} }
private: std::vector<RecordValPtr> Collect() const override {
using Handle = broker::telemetry::dbl_histogram_family_hdl*; 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");
explicit DblHistogramFamily(Handle hdl) : MetricFamily(upcast(hdl)) {} 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() )
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));
}
prometheus::Family<prometheus::Histogram>& family;
prometheus::Histogram::BucketBoundaries boundaries;
std::vector<std::shared_ptr<HistogramType>> histograms;
};
/**
* Manages a collection of IntHistogram metrics.
*/
class IntHistogramFamily : 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;
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::INT_HISTOGRAM; }
};
/**
* Manages a collection of DblHistogram metrics.
*/
class DblHistogramFamily : 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;
zeek_int_t MetricType() const noexcept override { return BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM; }
}; };
namespace detail { namespace detail {

View file

@ -2,422 +2,291 @@
#include "zeek/telemetry/Manager.h" #include "zeek/telemetry/Manager.h"
#include <fnmatch.h> #include <algorithm>
#include <thread> #include <thread>
#include <variant> #include <variant>
#include "zeek/3rdparty/doctest.h" #include "zeek/3rdparty/doctest.h"
#include "zeek/ID.h" #include "zeek/ID.h"
#include "zeek/ZeekString.h"
#include "zeek/broker/Manager.h" #include "zeek/broker/Manager.h"
#include "zeek/telemetry/ProcessStats.h"
#include "zeek/telemetry/Timer.h" #include "zeek/telemetry/Timer.h"
#include "zeek/telemetry/telemetry.bif.h" #include "zeek/telemetry/telemetry.bif.h"
#include "broker/telemetry/metric_registry.hh"
namespace {
using NativeManager = broker::telemetry::metric_registry;
using NativeManagerImpl = broker::telemetry::metric_registry_impl;
using NativeManagerImplPtr = zeek::IntrusivePtr<NativeManagerImpl>;
using DoubleValPtr = zeek::IntrusivePtr<zeek::DoubleVal>;
std::vector<std::string_view> extract_label_values(broker::telemetry::const_label_list labels) {
auto get_value = [](const auto& label) { return label.second; };
std::vector<std::string_view> v;
std::transform(labels.begin(), labels.end(), std::back_inserter(v), get_value);
return v;
}
// Convert an int64_t or double to a DoubleValPtr. int64_t is casted.
template<typename T>
DoubleValPtr as_double_val(T val) {
if constexpr ( std::is_same_v<T, int64_t> ) {
return zeek::make_intrusive<zeek::DoubleVal>(static_cast<double>(val));
}
else {
static_assert(std::is_same_v<T, double>);
return zeek::make_intrusive<zeek::DoubleVal>(val);
}
};
} // namespace
namespace zeek::telemetry { namespace zeek::telemetry {
Manager::Manager() { Manager::Manager() { prometheus_registry = std::make_shared<prometheus::Registry>(); }
auto reg = NativeManager::pre_init_instance();
NativeManagerImplPtr ptr{NewRef{}, reg.pimpl()}; void Manager::InitPostScript() {
pimpl.swap(ptr); std::string prometheus_url;
if ( auto env = getenv("ZEEK_METRICS_PORT") )
prometheus_url = util::fmt("localhost:%s", env);
else if ( auto env = getenv("BROKER_METRICS_PORT") ) {
// Remove this in v7.1 when the Broker variables are removed
reporter->Warning("BROKER_METRICS_PORT is deprecated, use ZEEK_METRICS_PORT.");
prometheus_url = util::fmt("localhost:%s", env);
}
else {
auto metrics_port = id::find_val("Telemetry::metrics_port")->AsPortVal();
if ( metrics_port->Port() == 0 )
// Remove this in v7.1 when the Broker variables are removed
metrics_port = id::find_val("Broker::metrics_port")->AsPortVal();
if ( metrics_port->Port() != 0 )
prometheus_url = util::fmt("localhost:%u", metrics_port->Port());
}
if ( ! prometheus_url.empty() ) {
printf("prometheus configured\n");
prometheus_exposer = std::make_unique<prometheus::Exposer>(prometheus_url);
prometheus_exposer->RegisterCollectable(prometheus_registry);
// Import topics are only enabled if Prometheus is enabled, because we don't care
// to get imported metrics if we're just going to drop them on the floor.
auto topics = import_topics;
if ( auto env = getenv("ZEEK_METRICS_IMPORT_TOPICS") ) {
topics = util::split(std::string{env}, ":");
}
else if ( auto env = getenv("BROKER_METRICS_IMPORT_TOPICS") ) {
// Remove this in v7.1 when the Broker variables are removed
reporter->Warning("BROKER_METRICS_IMPORT_TOPICS is deprecated, use ZEEK_METRICS_IMPORT_TOPICS.");
topics = util::split(std::string{env}, ":");
}
else {
auto script_topics = id::find_val("Telemetry::metrics_import_topics")->AsVectorVal();
if ( script_topics->Size() == 0 )
// Remove this in v7.1 when the Broker variables are removed
script_topics = id::find_val("Broker::metrics_import_topics")->AsVectorVal();
for ( int i = 0; i < script_topics->Size(); i++ )
topics.push_back(script_topics->StringValAt(i)->ToStdString());
}
for ( const auto& topic : topics ) {
broker_mgr->Subscribe(topic);
}
}
if ( export_topic.empty() ) {
if ( auto env = getenv("ZEEK_METRICS_EXPORT_TOPIC") )
export_topic = env;
else if ( auto env = getenv("BROKER_METRICS_EXPORT_TOPIC") ) {
// Remove this in v7.1 when the Broker variables are removed
reporter->Warning("BROKER_METRICS_EXPORT_TOPIC is deprecated, use ZEEK_METRICS_EXPORT_TOPIC.");
export_topic = env;
}
else {
auto script_topic = id::find_val("Telemetry::metrics_export_topic")->AsStringVal();
if ( script_topic->Len() == 0 )
// Remove this in v7.1 when the Broker variables are removed
script_topic = id::find_val("Broker::metrics_export_topic")->AsStringVal();
export_topic = script_topic->ToStdString();
}
}
if ( export_endpoint.empty() ) {
if ( auto env = getenv("ZEEK_METRICS_ENDPOINT_NAME") )
export_endpoint = env;
else if ( auto env = getenv("BROKER_METRICS_ENDPOINT_NAME") ) {
// Remove this in v7.1 when the Broker variables are removed
reporter->Warning("BROKER_METRICS_ENDPOINT_NAME is deprecated, use ZEEK_METRICS_ENDPOINT_NAME.");
export_endpoint = env;
}
else {
auto script_endpoint = id::find_val("Telemetry::metrics_export_endpoint_name")->AsStringVal();
if ( script_endpoint->Len() == 0 )
// Remove this in v7.1 when the Broker variables are removed
script_endpoint = id::find_val("Broker::metrics_export_endpoint_name")->AsStringVal();
export_endpoint = script_endpoint->ToStdString();
}
}
if ( export_interval == 0 ) {
if ( auto env = getenv("ZEEK_METRICS_EXPORT_INTERVAL") )
export_interval = std::strtod(env, nullptr);
else if ( auto env = getenv("BROKER_METRICS_EXPORT_INTERVAL") ) {
reporter->Warning("BROKER_METRICS_EXPORT_INTERVAL is deprecated, use ZEEK_METRICS_EXPORT_INTERVAL.");
export_interval = std::strtod(env, nullptr);
}
else {
export_interval = id::find_val("Telemetry::metrics_export_interval")->AsInterval();
if ( export_interval == 0 )
// Remove this in v7.1 when the Broker variables are removed
export_interval = id::find_val("Broker::metrics_export_interval")->AsInterval();
}
}
if ( export_prefixes.empty() ) {
if ( auto env = getenv("ZEEK_METRICS_EXPORT_PREFIXES") ) {
export_prefixes = util::split(std::string{env}, ":");
}
else if ( auto env = getenv("BROKER_METRICS_EXPORT_PREFIXES") ) {
reporter->Warning("BROKER_METRICS_EXPORT_PREFIXES is deprecated, use ZEEK_METRICS_EXPORT_PREFIXES.");
export_prefixes = util::split(std::string{env}, ":");
}
else {
auto script_topics = id::find_val("Telemetry::metrics_export_prefixes")->AsVectorVal();
if ( script_topics->Size() == 0 )
// Remove this in v7.1 when the Broker variables are removed
script_topics = id::find_val("Broker::metrics_export_prefixes")->AsVectorVal();
for ( int i = 0; i < script_topics->Size(); i++ )
export_prefixes.push_back(script_topics->StringValAt(i)->ToStdString());
}
}
// printf("topic: %s\n", export_topic.c_str());
// printf("endpoint: %s\n", export_endpoint.c_str());
// printf("interval: %f\n", export_interval);
// printf("prefixes: %zu\n", export_prefixes.size());
if ( ! export_topic.empty() && ! export_endpoint.empty() && export_interval > 0 ) {
printf("topic exporter configured\n");
}
#ifdef HAVE_PROCESS_STAT_METRICS
static auto get_stats = [this]() -> const detail::process_stats* {
double now = util::current_time();
if ( this->process_stats_last_updated < now - 0.01 ) {
this->current_process_stats = detail::get_process_stats();
this->process_stats_last_updated = now;
}
return &this->current_process_stats;
};
/*
rss_gauge =
GaugeInstance<int64_t>("process", "resident_memory", {}, "Resident memory size", "bytes", false,
[](metrics_api::ObserverResult r, void* state) {
auto* s = get_stats();
opentelemetry::nostd::get<
opentelemetry::nostd::shared_ptr<metrics_api::ObserverResultT<int64_t>>>(r)
->Observe(s->rss);
});
vms_gauge =
GaugeInstance<int64_t>("process", "virtual_memory", {}, "Virtual memory size", "bytes", false,
[](metrics_api::ObserverResult r, void* state) {
auto* s = get_stats();
opentelemetry::nostd::get<
opentelemetry::nostd::shared_ptr<metrics_api::ObserverResultT<int64_t>>>(r)
->Observe(s->vms);
});
cpu_gauge = GaugeInstance<double>("process", "cpu", {}, "Total user and system CPU time spent", "seconds", false,
[](metrics_api::ObserverResult r, void* state) {
auto* s = get_stats();
opentelemetry::nostd::get<
opentelemetry::nostd::shared_ptr<metrics_api::ObserverResultT<double>>>(r)
->Observe(s->cpu);
});
fds_gauge =
GaugeInstance<int64_t>("process", "open_fds", {}, "Number of open file descriptors", "", false,
[](metrics_api::ObserverResult r, void* state) {
auto* s = get_stats();
opentelemetry::nostd::get<
opentelemetry::nostd::shared_ptr<metrics_api::ObserverResultT<int64_t>>>(r)
->Observe(s->fds);
});
*/
#endif
} }
void Manager::InitPostScript() {} 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; };
void Manager::InitPostBrokerSetup(broker::endpoint& ep) { if ( auto it = std::find_if(families.begin(), families.end(), check); it != families.end() )
auto reg = NativeManager::merge(NativeManager{pimpl.get()}, ep); return it->second;
NativeManagerImplPtr ptr{NewRef{}, reg.pimpl()};
pimpl.swap(ptr); return nullptr;
} }
// -- collect metric stuff ----------------------------------------------------- // -- collect metric stuff -----------------------------------------------------
template<typename T> ValPtr Manager::CollectMetrics(std::string_view prefix_pattern, std::string_view name_pattern) {
zeek::RecordValPtr Manager::GetMetricOptsRecord(Manager::MetricType metric_type, static auto metrics_vector_type = zeek::id::find_type<VectorType>("any_vec");
const broker::telemetry::metric_family_hdl* family) { VectorValPtr ret_val = make_intrusive<VectorVal>(metrics_vector_type);
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 metric_opts_type = zeek::id::find_type<zeek::RecordType>("Telemetry::MetricOpts");
static auto prefix_idx = metric_opts_type->FieldOffset("prefix"); // Build a map of all of the families that match the patterns based on their full prefixed
static auto name_idx = metric_opts_type->FieldOffset("name"); // name. This will let us match those families against the items returned from the otel reader.
static auto help_text_idx = metric_opts_type->FieldOffset("help_text"); for ( const auto& [name, family] : families ) {
static auto unit_idx = metric_opts_type->FieldOffset("unit"); // Histograms are handled by CollectHistogramMetrics and should be ignored here.
static auto is_total_idx = metric_opts_type->FieldOffset("is_total"); if ( family->MetricType() == BifEnum::Telemetry::MetricType::INT_HISTOGRAM ||
static auto labels_idx = metric_opts_type->FieldOffset("labels"); family->MetricType() == BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM )
static auto bounds_idx = metric_opts_type->FieldOffset("bounds"); continue;
static auto metric_type_idx = metric_opts_type->FieldOffset("metric_type");
if ( const auto& it = metric_opts_cache.find(family); it != metric_opts_cache.end() ) if ( family->Matches(prefix_pattern, name_pattern) ) {
return it->second; auto records = family->Collect();
for ( const auto& r : records )
auto r = make_intrusive<zeek::RecordVal>(metric_opts_type); ret_val->Append(r);
r->Assign(prefix_idx, make_intrusive<zeek::StringVal>(broker::telemetry::prefix(family)));
r->Assign(name_idx, make_intrusive<zeek::StringVal>(broker::telemetry::name(family)));
r->Assign(help_text_idx, make_intrusive<zeek::StringVal>(broker::telemetry::helptext(family)));
r->Assign(unit_idx, make_intrusive<zeek::StringVal>(broker::telemetry::unit(family)));
r->Assign(is_total_idx, val_mgr->Bool(broker::telemetry::is_sum(family)));
auto label_names_vec = make_intrusive<zeek::VectorVal>(string_vec_type);
for ( const auto& l : broker::telemetry::label_names(family) )
label_names_vec->Append(make_intrusive<StringVal>(l));
r->Assign(labels_idx, label_names_vec);
// This is mapping Manager.h enums to bif values depending on
// the template type and whether this is a counter, gauge or
// histogram.
zeek_int_t metric_type_int = -1;
if constexpr ( std::is_same_v<T, double> ) {
switch ( metric_type ) {
case MetricType::Counter: metric_type_int = BifEnum::Telemetry::MetricType::DOUBLE_COUNTER; break;
case MetricType::Gauge: metric_type_int = BifEnum::Telemetry::MetricType::DOUBLE_GAUGE; break;
case MetricType::Histogram: metric_type_int = BifEnum::Telemetry::MetricType::DOUBLE_HISTOGRAM; break;
}
}
else {
switch ( metric_type ) {
case MetricType::Counter: metric_type_int = BifEnum::Telemetry::MetricType::INT_COUNTER; break;
case MetricType::Gauge: metric_type_int = BifEnum::Telemetry::MetricType::INT_GAUGE; break;
case MetricType::Histogram: metric_type_int = BifEnum::Telemetry::MetricType::INT_HISTOGRAM; break;
} }
} }
if ( metric_type_int < 0 ) return ret_val;
reporter->FatalError("Unable to lookup metric type %d", int(metric_type));
r->Assign(metric_type_idx, zeek::BifType::Enum::Telemetry::MetricType->GetEnumVal(metric_type_int));
// Add bounds and optionally count_bounds into the MetricOpts record.
static auto opts_rt = zeek::id::find_type<zeek::RecordType>("Telemetry::MetricOpts");
static auto opts_rt_idx_bounds = opts_rt->FieldOffset("bounds");
static auto opts_rt_idx_count_bounds = opts_rt->FieldOffset("count_bounds");
if ( metric_type == MetricType::Histogram ) {
auto add_double_bounds = [](auto& r, const auto* histogram_family) {
size_t buckets = broker::telemetry::num_buckets(histogram_family);
auto bounds_vec = make_intrusive<zeek::VectorVal>(double_vec_type);
for ( size_t i = 0; i < buckets; i++ )
bounds_vec->Append(as_double_val(broker::telemetry::upper_bound_at(histogram_family, i)));
r->Assign(opts_rt_idx_bounds, bounds_vec);
};
if constexpr ( std::is_same_v<T, int64_t> ) {
auto histogram_family = broker::telemetry::as_int_histogram_family(family);
add_double_bounds(r, histogram_family);
// Add count_bounds to int64_t histograms
size_t buckets = broker::telemetry::num_buckets(histogram_family);
auto count_bounds_vec = make_intrusive<zeek::VectorVal>(count_vec_type);
for ( size_t i = 0; i < buckets; i++ )
count_bounds_vec->Append(val_mgr->Count(broker::telemetry::upper_bound_at(histogram_family, i)));
r->Assign(opts_rt_idx_count_bounds, count_bounds_vec);
}
else {
static_assert(std::is_same_v<T, double>);
add_double_bounds(r, broker::telemetry::as_dbl_histogram_family(family));
}
}
metric_opts_cache.insert({family, r});
return r;
} }
zeek::RecordValPtr Manager::CollectedValueMetric::AsMetricRecord() const { ValPtr Manager::CollectHistogramMetrics(std::string_view prefix_pattern, std::string_view name_pattern) {
static auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec"); static auto metrics_vector_type = zeek::id::find_type<VectorType>("any_vec");
static auto metric_record_type = zeek::id::find_type<zeek::RecordType>("Telemetry::Metric"); VectorValPtr ret_val = make_intrusive<VectorVal>(metrics_vector_type);
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");
auto r = make_intrusive<zeek::RecordVal>(metric_record_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 )
continue;
auto label_values_vec = make_intrusive<zeek::VectorVal>(string_vec_type); if ( family->Matches(prefix_pattern, name_pattern) ) {
for ( const auto& l : label_values ) auto records = family->Collect();
label_values_vec->Append(make_intrusive<StringVal>(l)); for ( const auto& r : records )
ret_val->Append(r);
r->Assign(labels_idx, label_values_vec);
auto fn = [&](auto val) {
using val_t = decltype(val);
auto opts_record = telemetry_mgr->GetMetricOptsRecord<val_t>(metric_type, family);
r->Assign(opts_idx, opts_record);
r->Assign(value_idx, as_double_val(val));
if constexpr ( std::is_same_v<val_t, int64_t> )
r->Assign(count_value_idx, val_mgr->Count(val));
};
std::visit(fn, value);
return r;
}
zeek::RecordValPtr Manager::CollectedHistogramMetric::AsHistogramMetricRecord() const {
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 opts_idx = histogram_metric_type->FieldOffset("opts");
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 sum_idx = histogram_metric_type->FieldOffset("sum");
static auto count_observations_idx = histogram_metric_type->FieldOffset("count_observations");
static auto count_sum_idx = histogram_metric_type->FieldOffset("count_sum");
auto r = make_intrusive<zeek::RecordVal>(histogram_metric_type);
auto label_values_vec = make_intrusive<zeek::VectorVal>(string_vec_type);
for ( const auto& l : label_values )
label_values_vec->Append(make_intrusive<StringVal>(l));
r->Assign(labels_idx, label_values_vec);
auto fn = [&](const auto& histogram_data) {
using val_t = std::decay_t<decltype(histogram_data.sum)>;
auto opts_record = telemetry_mgr->GetMetricOptsRecord<val_t>(MetricType::Histogram, family);
r->Assign(opts_idx, opts_record);
val_t observations = 0;
auto values_vec = make_intrusive<zeek::VectorVal>(double_vec_type);
auto count_values_vec = make_intrusive<zeek::VectorVal>(count_vec_type);
for ( const auto& b : histogram_data.buckets ) {
observations += b.count;
values_vec->Append(as_double_val(b.count));
if constexpr ( std::is_same_v<val_t, int64_t> )
count_values_vec->Append(val_mgr->Count(b.count));
} }
}
r->Assign(values_idx, values_vec); return ret_val;
r->Assign(sum_idx, as_double_val(histogram_data.sum));
r->Assign(observations_idx, as_double_val(observations));
// Add extra fields just for int64_t based histograms with type count
if constexpr ( std::is_same_v<val_t, int64_t> ) {
r->Assign(count_values_idx, count_values_vec);
r->Assign(count_sum_idx, val_mgr->Count(histogram_data.sum));
r->Assign(count_observations_idx, val_mgr->Count(observations));
}
};
std::visit(fn, histogram);
return r;
} }
/** /**
* Encapsulate matching of prefix and name against a broker::telemetry::metric_family_hdl * Changes the frequency for publishing scraped metrics to the target topic.
* Passing a zero-length interval has no effect.
* @param value Interval between two scrapes in seconds.
*/ */
class MetricFamilyMatcher { void Manager::SetMetricsExportInterval(double value) { export_interval = value; }
public:
MetricFamilyMatcher(std::string_view prefix, std::string_view name) : prefix_pattern(prefix), name_pattern(name) {}
/**
* @return true if the given family's prefix and name match, else false;
*/
bool operator()(const broker::telemetry::metric_family_hdl* family) {
auto prefix = std::string{broker::telemetry::prefix(family)};
auto name = std::string{broker::telemetry::name(family)};
return fnmatch(prefix_pattern.c_str(), prefix.c_str(), 0) != FNM_NOMATCH &&
fnmatch(name_pattern.c_str(), name.c_str(), 0) != FNM_NOMATCH;
}
private:
std::string prefix_pattern;
std::string name_pattern;
};
/** /**
* A collector implementation for counters and gauges. * Sets a new target topic for the metrics. Passing an empty string has no
* effect.
* @param value The new topic for publishing local metrics to.
*/ */
class MetricsCollector : public broker::telemetry::metrics_collector { void Manager::SetMetricsExportTopic(std::string value) { export_topic = std::move(value); }
using MetricType = Manager::MetricType;
public:
MetricsCollector(std::string_view prefix, std::string_view name) : matches(prefix, name) {}
void operator()(const broker::telemetry::metric_family_hdl* family,
const broker::telemetry::dbl_counter_hdl* counter,
broker::telemetry::const_label_list labels) override {
if ( matches(family) )
metrics.emplace_back(MetricType::Counter, family, extract_label_values(labels),
broker::telemetry::value(counter));
}
void operator()(const broker::telemetry::metric_family_hdl* family,
const broker::telemetry::int_counter_hdl* counter,
broker::telemetry::const_label_list labels) override {
if ( matches(family) )
metrics.emplace_back(MetricType::Counter, family, extract_label_values(labels),
broker::telemetry::value(counter));
}
void operator()(const broker::telemetry::metric_family_hdl* family, const broker::telemetry::dbl_gauge_hdl* gauge,
broker::telemetry::const_label_list labels) override {
if ( matches(family) )
metrics.emplace_back(MetricType::Gauge, family, extract_label_values(labels),
broker::telemetry::value(gauge));
}
void operator()(const broker::telemetry::metric_family_hdl* family, const broker::telemetry::int_gauge_hdl* gauge,
broker::telemetry::const_label_list labels) override {
if ( matches(family) )
metrics.emplace_back(MetricType::Gauge, family, extract_label_values(labels),
broker::telemetry::value(gauge));
}
void operator()(const broker::telemetry::metric_family_hdl* family,
const broker::telemetry::dbl_histogram_hdl* histogram,
broker::telemetry::const_label_list labels) override {
// Ignored
}
void operator()(const broker::telemetry::metric_family_hdl* family,
const broker::telemetry::int_histogram_hdl* histogram,
broker::telemetry::const_label_list labels) override {
// Ignored
}
std::vector<Manager::CollectedValueMetric>& GetResult() { return metrics; }
private:
MetricFamilyMatcher matches;
std::vector<Manager::CollectedValueMetric> metrics;
};
std::vector<Manager::CollectedValueMetric> Manager::CollectMetrics(std::string_view prefix, std::string_view name) {
auto collector = MetricsCollector(prefix, name);
pimpl->collect(collector);
return std::move(collector.GetResult());
}
/** /**
* A collector implementation for histograms. * Sets the import topics for a node importing metrics.
*
* @param topics List of topics from which to import metrics.
*/ */
class HistogramMetricsCollector : public broker::telemetry::metrics_collector { void Manager::SetMetricsImportTopics(std::vector<std::string> topics) { import_topics = std::move(topics); }
using MetricType = Manager::MetricType;
public: /**
HistogramMetricsCollector(std::string_view prefix, std::string_view name) : matches(prefix, name) {} * Sets a new ID for the metrics exporter. Passing an empty string has no
* effect.
* @param value The new ID of the exporter in published metrics.
*/
void Manager::SetMetricsExportEndpointName(std::string value) { export_endpoint = std::move(value); }
void operator()(const broker::telemetry::metric_family_hdl* family, /**
const broker::telemetry::dbl_counter_hdl* counter, * Sets a prefix selection for the metrics exporter. An empty vector selects
broker::telemetry::const_label_list labels) override { * *all* metrics.
// Ignored * @param filter List of selected metric prefixes or an empty vector for
} * selecting all metrics.
*/
void Manager::SetMetricsExportPrefixes(std::vector<std::string> filter) { export_prefixes = std::move(filter); }
void operator()(const broker::telemetry::metric_family_hdl* family,
const broker::telemetry::int_counter_hdl* counter,
broker::telemetry::const_label_list labels) override {
// Ignored
}
void operator()(const broker::telemetry::metric_family_hdl* family, const broker::telemetry::dbl_gauge_hdl* gauge,
broker::telemetry::const_label_list labels) override {
// Ignored
}
void operator()(const broker::telemetry::metric_family_hdl* family, const broker::telemetry::int_gauge_hdl* gauge,
broker::telemetry::const_label_list labels) override {
// Ignored
}
void operator()(const broker::telemetry::metric_family_hdl* family,
const broker::telemetry::dbl_histogram_hdl* histogram,
broker::telemetry::const_label_list labels) override {
if ( ! matches(family) )
return;
size_t num_buckets = broker::telemetry::num_buckets(histogram);
Manager::CollectedHistogramMetric::DblHistogramData histogram_data;
histogram_data.buckets.reserve(num_buckets);
for ( size_t i = 0; i < num_buckets; i++ ) {
double c = broker::telemetry::count_at(histogram, i);
double ub = broker::telemetry::upper_bound_at(histogram, i);
histogram_data.buckets.emplace_back(c, ub);
}
histogram_data.sum = broker::telemetry::sum(histogram);
metrics.emplace_back(family, extract_label_values(labels), std::move(histogram_data));
}
void operator()(const broker::telemetry::metric_family_hdl* family,
const broker::telemetry::int_histogram_hdl* histogram,
broker::telemetry::const_label_list labels) override {
if ( ! matches(family) )
return;
size_t num_buckets = broker::telemetry::num_buckets(histogram);
Manager::CollectedHistogramMetric::IntHistogramData histogram_data;
histogram_data.buckets.reserve(num_buckets);
for ( size_t i = 0; i < num_buckets; i++ ) {
int64_t c = broker::telemetry::count_at(histogram, i);
int64_t ub = broker::telemetry::upper_bound_at(histogram, i);
histogram_data.buckets.emplace_back(c, ub);
}
histogram_data.sum = broker::telemetry::sum(histogram);
metrics.emplace_back(family, extract_label_values(labels), std::move(histogram_data));
}
std::vector<Manager::CollectedHistogramMetric>& GetResult() { return metrics; }
private:
MetricFamilyMatcher matches;
std::vector<Manager::CollectedHistogramMetric> metrics;
};
std::vector<Manager::CollectedHistogramMetric> Manager::CollectHistogramMetrics(std::string_view prefix,
std::string_view name) {
auto collector = HistogramMetricsCollector(prefix, name);
pimpl->collect(collector);
return std::move(collector.GetResult());
}
} // namespace zeek::telemetry } // namespace zeek::telemetry
@ -444,42 +313,42 @@ SCENARIO("telemetry managers provide access to counter families") {
WHEN("retrieving an IntCounter family") { WHEN("retrieving an IntCounter family") {
auto family = mgr.CounterFamily("zeek", "requests", {"method"}, "test", "1", true); auto family = mgr.CounterFamily("zeek", "requests", {"method"}, "test", "1", true);
THEN("the family object stores the parameters") { THEN("the family object stores the parameters") {
CHECK_EQ(family.Prefix(), "zeek"sv); CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "requests"sv); CHECK_EQ(family->Name(), "requests"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"method"s}); CHECK_EQ(toVector(family->LabelNames()), std::vector{"method"s});
CHECK_EQ(family.Helptext(), "test"sv); CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "1"sv); CHECK_EQ(family->Unit(), "1"sv);
CHECK_EQ(family.IsSum(), true); CHECK_EQ(family->IsSum(), true);
} }
AND_THEN("GetOrAdd returns the same metric for the same labels") { AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family.GetOrAdd({{"method", "get"}}); auto first = family->GetOrAdd({{"method", "get"}});
auto second = family.GetOrAdd({{"method", "get"}}); auto second = family->GetOrAdd({{"method", "get"}});
CHECK_EQ(first, second); CHECK_EQ(first, second);
} }
AND_THEN("GetOrAdd returns different metric for the disjoint labels") { AND_THEN("GetOrAdd returns different metric for the disjoint labels") {
auto first = family.GetOrAdd({{"method", "get"}}); auto first = family->GetOrAdd({{"method", "get"}});
auto second = family.GetOrAdd({{"method", "put"}}); auto second = family->GetOrAdd({{"method", "put"}});
CHECK_NE(first, second); CHECK_NE(first, second);
} }
} }
WHEN("retrieving a DblCounter family") { WHEN("retrieving a DblCounter family") {
auto family = mgr.CounterFamily<double>("zeek", "runtime", {"query"}, "test", "seconds", true); auto family = mgr.CounterFamily<double>("zeek", "runtime", {"query"}, "test", "seconds", true);
THEN("the family object stores the parameters") { THEN("the family object stores the parameters") {
CHECK_EQ(family.Prefix(), "zeek"sv); CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "runtime"sv); CHECK_EQ(family->Name(), "runtime"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"query"s}); CHECK_EQ(toVector(family->LabelNames()), std::vector{"query"s});
CHECK_EQ(family.Helptext(), "test"sv); CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "seconds"sv); CHECK_EQ(family->Unit(), "seconds"sv);
CHECK_EQ(family.IsSum(), true); CHECK_EQ(family->IsSum(), true);
} }
AND_THEN("GetOrAdd returns the same metric for the same labels") { AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family.GetOrAdd({{"query", "foo"}}); auto first = family->GetOrAdd({{"query", "foo"}});
auto second = family.GetOrAdd({{"query", "foo"}}); auto second = family->GetOrAdd({{"query", "foo"}});
CHECK_EQ(first, second); CHECK_EQ(first, second);
} }
AND_THEN("GetOrAdd returns different metric for the disjoint labels") { AND_THEN("GetOrAdd returns different metric for the disjoint labels") {
auto first = family.GetOrAdd({{"query", "foo"}}); auto first = family->GetOrAdd({{"query", "foo"}});
auto second = family.GetOrAdd({{"query", "bar"}}); auto second = family->GetOrAdd({{"query", "bar"}});
CHECK_NE(first, second); CHECK_NE(first, second);
} }
} }
@ -490,44 +359,44 @@ SCENARIO("telemetry managers provide access to gauge families") {
GIVEN("a telemetry manager") { GIVEN("a telemetry manager") {
Manager mgr; Manager mgr;
WHEN("retrieving an IntGauge family") { WHEN("retrieving an IntGauge family") {
auto family = mgr.GaugeFamily("zeek", "open-connections", {"protocol"}, "test"); auto family = mgr.GaugeFamily("zeek", "open-connections", {"protocol"}, "test", "1");
THEN("the family object stores the parameters") { THEN("the family object stores the parameters") {
CHECK_EQ(family.Prefix(), "zeek"sv); CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "open-connections"sv); CHECK_EQ(family->Name(), "open_connections"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"protocol"s}); CHECK_EQ(toVector(family->LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family.Helptext(), "test"sv); CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "1"sv); CHECK_EQ(family->Unit(), "1"sv);
CHECK_EQ(family.IsSum(), false); CHECK_EQ(family->IsSum(), false);
} }
AND_THEN("GetOrAdd returns the same metric for the same labels") { AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family.GetOrAdd({{"protocol", "tcp"}}); auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "tcp"}}); auto second = family->GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second); CHECK_EQ(first, second);
} }
AND_THEN("GetOrAdd returns different metric for the disjoint labels") { AND_THEN("GetOrAdd returns different metric for the disjoint labels") {
auto first = family.GetOrAdd({{"protocol", "tcp"}}); auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "quic"}}); auto second = family->GetOrAdd({{"protocol", "quic"}});
CHECK_NE(first, second); CHECK_NE(first, second);
} }
} }
WHEN("retrieving a DblGauge family") { WHEN("retrieving a DblGauge family") {
auto family = mgr.GaugeFamily<double>("zeek", "water-level", {"river"}, "test", "meters"); auto family = mgr.GaugeFamily<double>("zeek", "water-level", {"river"}, "test", "meters");
THEN("the family object stores the parameters") { THEN("the family object stores the parameters") {
CHECK_EQ(family.Prefix(), "zeek"sv); CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "water-level"sv); CHECK_EQ(family->Name(), "water_level"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"river"s}); CHECK_EQ(toVector(family->LabelNames()), std::vector{"river"s});
CHECK_EQ(family.Helptext(), "test"sv); CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "meters"sv); CHECK_EQ(family->Unit(), "meters"sv);
CHECK_EQ(family.IsSum(), false); CHECK_EQ(family->IsSum(), false);
} }
AND_THEN("GetOrAdd returns the same metric for the same labels") { AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family.GetOrAdd({{"river", "Sacramento"}}); auto first = family->GetOrAdd({{"river", "Sacramento"}});
auto second = family.GetOrAdd({{"river", "Sacramento"}}); auto second = family->GetOrAdd({{"river", "Sacramento"}});
CHECK_EQ(first, second); CHECK_EQ(first, second);
} }
AND_THEN("GetOrAdd returns different metric for the disjoint labels") { AND_THEN("GetOrAdd returns different metric for the disjoint labels") {
auto first = family.GetOrAdd({{"query", "Sacramento"}}); auto first = family->GetOrAdd({{"query", "Sacramento"}});
auto second = family.GetOrAdd({{"query", "San Joaquin"}}); auto second = family->GetOrAdd({{"query", "San Joaquin"}});
CHECK_NE(first, second); CHECK_NE(first, second);
} }
} }
@ -541,21 +410,21 @@ SCENARIO("telemetry managers provide access to histogram families") {
int64_t buckets[] = {10, 20}; int64_t buckets[] = {10, 20};
auto family = mgr.HistogramFamily("zeek", "payload-size", {"protocol"}, buckets, "test", "bytes"); auto family = mgr.HistogramFamily("zeek", "payload-size", {"protocol"}, buckets, "test", "bytes");
THEN("the family object stores the parameters") { THEN("the family object stores the parameters") {
CHECK_EQ(family.Prefix(), "zeek"sv); CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "payload-size"sv); CHECK_EQ(family->Name(), "payload_size"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"protocol"s}); CHECK_EQ(toVector(family->LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family.Helptext(), "test"sv); CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "bytes"sv); CHECK_EQ(family->Unit(), "bytes"sv);
CHECK_EQ(family.IsSum(), false); CHECK_EQ(family->IsSum(), false);
} }
AND_THEN("GetOrAdd returns the same metric for the same labels") { AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family.GetOrAdd({{"protocol", "tcp"}}); auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "tcp"}}); auto second = family->GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second); CHECK_EQ(first, second);
} }
AND_THEN("GetOrAdd returns different metric for the disjoint labels") { AND_THEN("GetOrAdd returns different metric for the disjoint labels") {
auto first = family.GetOrAdd({{"protocol", "tcp"}}); auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "udp"}}); auto second = family->GetOrAdd({{"protocol", "udp"}});
CHECK_NE(first, second); CHECK_NE(first, second);
} }
} }
@ -563,31 +432,31 @@ SCENARIO("telemetry managers provide access to histogram families") {
double buckets[] = {10.0, 20.0}; double buckets[] = {10.0, 20.0};
auto family = mgr.HistogramFamily<double>("zeek", "parse-time", {"protocol"}, buckets, "test", "seconds"); auto family = mgr.HistogramFamily<double>("zeek", "parse-time", {"protocol"}, buckets, "test", "seconds");
THEN("the family object stores the parameters") { THEN("the family object stores the parameters") {
CHECK_EQ(family.Prefix(), "zeek"sv); CHECK_EQ(family->Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "parse-time"sv); CHECK_EQ(family->Name(), "parse_time"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"protocol"s}); CHECK_EQ(toVector(family->LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family.Helptext(), "test"sv); CHECK_EQ(family->Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "seconds"sv); CHECK_EQ(family->Unit(), "seconds"sv);
CHECK_EQ(family.IsSum(), false); CHECK_EQ(family->IsSum(), false);
} }
AND_THEN("GetOrAdd returns the same metric for the same labels") { AND_THEN("GetOrAdd returns the same metric for the same labels") {
auto first = family.GetOrAdd({{"protocol", "tcp"}}); auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "tcp"}}); auto second = family->GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second); CHECK_EQ(first, second);
} }
AND_THEN("GetOrAdd returns different metric for the disjoint labels") { AND_THEN("GetOrAdd returns different metric for the disjoint labels") {
auto first = family.GetOrAdd({{"protocol", "tcp"}}); auto first = family->GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "udp"}}); auto second = family->GetOrAdd({{"protocol", "udp"}});
CHECK_NE(first, second); CHECK_NE(first, second);
} }
AND_THEN("Timers add observations to histograms") { AND_THEN("Timers add observations to histograms") {
auto hg = family.GetOrAdd({{"protocol", "tst"}}); auto hg = family->GetOrAdd({{"protocol", "tst"}});
CHECK_EQ(hg.Sum(), 0.0); CHECK_EQ(hg->Sum(), 0.0);
{ {
Timer observer{hg}; Timer observer{hg};
std::this_thread::sleep_for(1ms); std::this_thread::sleep_for(1ms);
} }
CHECK_NE(hg.Sum(), 0.0); CHECK_NE(hg->Sum(), 0.0);
} }
} }
} }

View file

@ -2,11 +2,11 @@
#pragma once #pragma once
#include <condition_variable>
#include <cstdint> #include <cstdint>
#include <initializer_list> #include <initializer_list>
#include <memory>
#include <string_view> #include <string_view>
#include <unordered_map>
#include <variant>
#include <vector> #include <vector>
#include "zeek/IntrusivePtr.h" #include "zeek/IntrusivePtr.h"
@ -14,149 +14,52 @@
#include "zeek/telemetry/Counter.h" #include "zeek/telemetry/Counter.h"
#include "zeek/telemetry/Gauge.h" #include "zeek/telemetry/Gauge.h"
#include "zeek/telemetry/Histogram.h" #include "zeek/telemetry/Histogram.h"
#include "zeek/telemetry/ProcessStats.h"
#include "broker/telemetry/fwd.hh" #include "prometheus/exposer.h"
#include "prometheus/registry.h"
namespace broker {
class endpoint;
}
namespace zeek { namespace zeek {
class RecordVal; class RecordVal;
using RecordValPtr = IntrusivePtr<RecordVal>; using RecordValPtr = IntrusivePtr<RecordVal>;
} // namespace zeek } // namespace zeek
namespace zeek::Broker {
class Manager;
}
namespace zeek::telemetry { namespace zeek::telemetry {
class OtelReader;
/** /**
* Manages a collection of metric families. * Manages a collection of metric families.
*/ */
class Manager { class Manager final {
public: public:
friend class Broker::Manager;
Manager(); Manager();
Manager(const Manager&) = delete; Manager(const Manager&) = delete;
Manager& operator=(const Manager&) = delete; Manager& operator=(const Manager&) = delete;
virtual ~Manager() = default; ~Manager() = default;
/** /**
* Initialization of the manager. This is called late during Zeek's * Initialization of the manager. This is called late during Zeek's
* initialization after any scripts are processed. * initialization after any scripts are processed.
*/ */
virtual void InitPostScript(); void InitPostScript();
/** /**
* Supported metric types. * @return A VectorVal containing all counter and gauge metrics and their values matching prefix and name.
*/
enum class MetricType { Counter, Gauge, Histogram };
/**
* Captures information about counter and gauge metrics.
*/
struct CollectedValueMetric {
/**
* Constructor.
* @param metric_type The type of this metric.
* @param family Broker layer family handle for this metric.
* @param label_values The string values for each of the metric's labels.
* @param value The metric's current value.
*/
CollectedValueMetric(MetricType metric_type, const broker::telemetry::metric_family_hdl* family,
std::vector<std::string_view> label_values, std::variant<double, int64_t> value)
: metric_type(metric_type), family(family), label_values(std::move(label_values)), value(value) {}
/**
* @return A script layer Telemetry::Metric record for this metric.
*/
zeek::RecordValPtr AsMetricRecord() const;
enum MetricType metric_type;
const broker::telemetry::metric_family_hdl* family;
std::vector<std::string_view> label_values;
std::variant<double, int64_t> value;
};
/**
* Captures information about histogram metrics.
*/
struct CollectedHistogramMetric {
/**
* Helper struct representing a single bucket of a histogram.
* @tparam T The data type used by the histogram (double or int64_t).
*/
template<class T>
struct Bucket {
Bucket(T count, T upper_bound) : count(count), upper_bound(upper_bound) {}
T count;
T upper_bound;
};
/**
* Helper struct representing a histogram as sum and buckets.
* @tparam T The data type used by the histogram (double or int64_t).
*/
template<class T>
struct HistogramData {
T sum;
std::vector<Bucket<T>> buckets;
};
using DblHistogramData = HistogramData<double>;
using IntHistogramData = HistogramData<int64_t>;
/**
* Constructor.
* @param family Broker layer family handle for this metric.
* @param label_values The string values for each of the metric's labels.
* @param histogram The histogram's data (sum and individual buckets).
*/
CollectedHistogramMetric(const broker::telemetry::metric_family_hdl* family,
std::vector<std::string_view> label_values,
std::variant<DblHistogramData, IntHistogramData> histogram)
: family(family), label_values(std::move(label_values)), histogram(std::move(histogram)) {}
const broker::telemetry::metric_family_hdl* family;
std::vector<std::string_view> label_values;
std::variant<DblHistogramData, IntHistogramData> histogram;
/**
* @return A script layer Telemetry::HistogramMetric record for this histogram.
*/
zeek::RecordValPtr AsHistogramMetricRecord() const;
};
/**
* @return A script layer Telemetry::MetricOpts record for the given metric family.
* @param metric_typ The type of metric.
* @param family Broker layer family handle for the family.
* @tparam T The underlying data type (double or int64_t)
*/
template<typename T>
zeek::RecordValPtr GetMetricOptsRecord(MetricType metric_type, const broker::telemetry::metric_family_hdl* family);
/**
* @return All counter and gauge metrics and their values matching prefix and name.
* @param prefix The prefix pattern to use for filtering. Supports globbing. * @param prefix The prefix pattern to use for filtering. Supports globbing.
* @param name The name pattern to use for filtering. Supports globbing. * @param name The name pattern to use for filtering. Supports globbing.
*/ */
std::vector<CollectedValueMetric> CollectMetrics(std::string_view prefix, std::string_view name); ValPtr CollectMetrics(std::string_view prefix, std::string_view name);
/** /**
* @return All histogram metrics and their data matching prefix and name. * @return A VectorVal containing all histogram metrics and their values matching prefix and name.
* @param prefix The prefix pattern to use for filtering. Supports globbing. * @param prefix The prefix pattern to use for filtering. Supports globbing.
* @param name The name pattern to use for filtering. Supports globbing. * @param name The name pattern to use for filtering. Supports globbing.
*/ */
std::vector<CollectedHistogramMetric> CollectHistogramMetrics(std::string_view prefix, std::string_view name); ValPtr CollectHistogramMetrics(std::string_view prefix, std::string_view name);
/** /**
* @return A counter metric family. Creates the family lazily if necessary. * @return A counter metric family. Creates the family lazily if necessary.
@ -165,27 +68,41 @@ public:
* @param labels Names for all label dimensions of the metric. * @param labels Names for all label dimensions of the metric.
* @param helptext Short explanation of the metric. * @param helptext Short explanation of the metric.
* @param unit Unit of measurement. * @param unit Unit of measurement.
* @param is_sum Indicates whether this metric accumulates something, where * @param is_sum Indicates whether this metric accumulates something, where only the total value is of interest.
* 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> template<class ValueType = int64_t>
auto CounterFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels, auto CounterFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::string_view unit = "1", bool is_sum = false) { std::string_view helptext, std::string_view unit = "", bool is_sum = false) {
auto fam = LookupFamily(prefix, name);
if constexpr ( std::is_same<ValueType, int64_t>::value ) { if constexpr ( std::is_same<ValueType, int64_t>::value ) {
auto fam = int_counter_fam(Ptr(), prefix, name, labels, helptext, unit, is_sum); if ( fam )
return IntCounterFamily{fam}; return std::static_pointer_cast<IntCounterFamily>(fam);
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;
} }
else { else {
static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values"); static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values");
auto fam = dbl_counter_fam(Ptr(), prefix, name, labels, helptext, unit, is_sum);
return DblCounterFamily{fam}; 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;
} }
} }
/// @copydoc CounterFamily /// @copydoc CounterFamily
template<class ValueType = int64_t> template<class ValueType = int64_t>
auto CounterFamily(std::string_view prefix, std::string_view name, std::initializer_list<std::string_view> labels, auto CounterFamily(std::string_view prefix, std::string_view name, std::initializer_list<std::string_view> labels,
std::string_view helptext, std::string_view unit = "1", bool is_sum = false) { std::string_view helptext, std::string_view unit = "", bool is_sum = false) {
auto lbl_span = Span{labels.begin(), labels.size()}; auto lbl_span = Span{labels.begin(), labels.size()};
return CounterFamily<ValueType>(prefix, name, lbl_span, helptext, unit, is_sum); return CounterFamily<ValueType>(prefix, name, lbl_span, helptext, unit, is_sum);
} }
@ -198,25 +115,28 @@ public:
* @param labels Values for all label dimensions of the metric. * @param labels Values for all label dimensions of the metric.
* @param helptext Short explanation of the metric. * @param helptext Short explanation of the metric.
* @param unit Unit of measurement. * @param unit Unit of measurement.
* @param is_sum Indicates whether this metric accumulates something, where * @param is_sum Indicates whether this metric accumulates something, where only the total value is of interest.
* 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> template<class ValueType = int64_t>
Counter<ValueType> CounterInstance(std::string_view prefix, std::string_view name, Span<const LabelView> labels, std::shared_ptr<Counter<ValueType>> CounterInstance(std::string_view prefix, std::string_view name,
std::string_view helptext, std::string_view unit = "1", bool is_sum = false) { Span<const LabelView> labels, std::string_view helptext,
std::string_view unit = "", bool is_sum = false) {
return WithLabelNames(labels, [&, this](auto labelNames) { return WithLabelNames(labels, [&, this](auto labelNames) {
auto family = CounterFamily<ValueType>(prefix, name, labelNames, helptext, unit, is_sum); auto family = CounterFamily<ValueType>(prefix, name, labelNames, helptext, unit, is_sum);
return family.getOrAdd(labels); return family->GetOrAdd(labels);
}); });
} }
/// @copydoc counterInstance /// @copydoc counterInstance
template<class ValueType = int64_t> template<class ValueType = int64_t>
Counter<ValueType> CounterInstance(std::string_view prefix, std::string_view name, std::shared_ptr<Counter<ValueType>> CounterInstance(std::string_view prefix, std::string_view name,
std::initializer_list<LabelView> labels, std::string_view helptext, std::initializer_list<LabelView> labels,
std::string_view unit = "1", bool is_sum = false) { std::string_view helptext, std::string_view unit = "",
bool is_sum = false) {
auto lbl_span = Span{labels.begin(), labels.size()}; auto lbl_span = Span{labels.begin(), labels.size()};
return CounterInstance(prefix, name, lbl_span, helptext, unit, is_sum); return CounterInstance<ValueType>(prefix, name, lbl_span, helptext, unit, is_sum);
} }
/** /**
@ -226,27 +146,40 @@ public:
* @param labels Names for all label dimensions of the metric. * @param labels Names for all label dimensions of the metric.
* @param helptext Short explanation of the metric. * @param helptext Short explanation of the metric.
* @param unit Unit of measurement. * @param unit Unit of measurement.
* @param is_sum Indicates whether this metric accumulates something, where * @param is_sum Indicates whether this metric accumulates something, where only the total value is of interest.
* 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> template<class ValueType = int64_t>
auto GaugeFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels, auto GaugeFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels,
std::string_view helptext, std::string_view unit = "1", bool is_sum = false) { std::string_view helptext, std::string_view unit = "", bool is_sum = false) {
auto fam = LookupFamily(prefix, name);
if constexpr ( std::is_same<ValueType, int64_t>::value ) { if constexpr ( std::is_same<ValueType, int64_t>::value ) {
auto fam = int_gauge_fam(Ptr(), prefix, name, labels, helptext, unit, is_sum); if ( fam )
return IntGaugeFamily{fam}; return std::static_pointer_cast<IntGaugeFamily>(fam);
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;
} }
else { else {
static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values"); static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values");
auto fam = dbl_gauge_fam(Ptr(), prefix, name, labels, helptext, unit, is_sum); if ( fam )
return DblGaugeFamily{fam}; return std::static_pointer_cast<DblGaugeFamily>(fam);
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;
} }
} }
/// @copydoc GaugeFamily /// @copydoc GaugeFamily
template<class ValueType = int64_t> template<class ValueType = int64_t>
auto GaugeFamily(std::string_view prefix, std::string_view name, std::initializer_list<std::string_view> labels, auto GaugeFamily(std::string_view prefix, std::string_view name, std::initializer_list<std::string_view> labels,
std::string_view helptext, std::string_view unit = "1", bool is_sum = false) { std::string_view helptext, std::string_view unit = "", bool is_sum = false) {
auto lbl_span = Span{labels.begin(), labels.size()}; auto lbl_span = Span{labels.begin(), labels.size()};
return GaugeFamily<ValueType>(prefix, name, lbl_span, helptext, unit, is_sum); return GaugeFamily<ValueType>(prefix, name, lbl_span, helptext, unit, is_sum);
} }
@ -259,25 +192,27 @@ public:
* @param labels Values for all label dimensions of the metric. * @param labels Values for all label dimensions of the metric.
* @param helptext Short explanation of the metric. * @param helptext Short explanation of the metric.
* @param unit Unit of measurement. * @param unit Unit of measurement.
* @param is_sum Indicates whether this metric accumulates something, where * @param is_sum Indicates whether this metric accumulates something, where only the total value is of interest.
* 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> template<class ValueType = int64_t>
Gauge<ValueType> GaugeInstance(std::string_view prefix, std::string_view name, Span<const LabelView> labels, std::shared_ptr<Gauge<ValueType>> GaugeInstance(std::string_view prefix, std::string_view name,
std::string_view helptext, std::string_view unit = "1", bool is_sum = false) { Span<const LabelView> labels, std::string_view helptext,
std::string_view unit = "", bool is_sum = false) {
return WithLabelNames(labels, [&, this](auto labelNames) { return WithLabelNames(labels, [&, this](auto labelNames) {
auto family = GaugeFamily<ValueType>(prefix, name, labelNames, helptext, unit, is_sum); auto family = GaugeFamily<ValueType>(prefix, name, labelNames, helptext, unit, is_sum);
return family.getOrAdd(labels); return family->GetOrAdd(labels);
}); });
} }
/// @copydoc GaugeInstance /// @copydoc GaugeInstance
template<class ValueType = int64_t> template<class ValueType = int64_t>
Gauge<ValueType> GaugeInstance(std::string_view prefix, std::string_view name, std::shared_ptr<Gauge<ValueType>> GaugeInstance(std::string_view prefix, std::string_view name,
std::initializer_list<LabelView> labels, std::string_view helptext, std::initializer_list<LabelView> labels, std::string_view helptext,
std::string_view unit = "1", bool is_sum = false) { std::string_view unit = "", bool is_sum = false) {
auto lbl_span = Span{labels.begin(), labels.size()}; auto lbl_span = Span{labels.begin(), labels.size()};
return GaugeInstance(prefix, name, lbl_span, helptext, unit, is_sum); return GaugeInstance<ValueType>(prefix, name, lbl_span, helptext, unit, is_sum);
} }
// Forces the compiler to use the type `Span<const T>` instead of trying to // Forces the compiler to use the type `Span<const T>` instead of trying to
@ -315,15 +250,27 @@ public:
template<class ValueType = int64_t> template<class ValueType = int64_t>
auto HistogramFamily(std::string_view prefix, std::string_view name, Span<const std::string_view> labels, 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, ConstSpan<ValueType> default_upper_bounds, std::string_view helptext,
std::string_view unit = "1", bool is_sum = false) { std::string_view unit = "") {
auto fam = LookupFamily(prefix, name);
if constexpr ( std::is_same<ValueType, int64_t>::value ) { if constexpr ( std::is_same<ValueType, int64_t>::value ) {
auto fam = int_histogram_fam(Ptr(), prefix, name, labels, default_upper_bounds, helptext, unit, is_sum); if ( fam )
return IntHistogramFamily{fam}; return std::static_pointer_cast<IntHistogramFamily>(fam);
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;
} }
else { else {
static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values"); static_assert(std::is_same<ValueType, double>::value, "metrics only support int64_t and double values");
auto fam = dbl_histogram_fam(Ptr(), prefix, name, labels, default_upper_bounds, helptext, unit, is_sum); if ( fam )
return DblHistogramFamily{fam}; return std::static_pointer_cast<DblHistogramFamily>(fam);
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;
} }
} }
@ -331,9 +278,9 @@ public:
template<class ValueType = int64_t> template<class ValueType = int64_t>
auto HistogramFamily(std::string_view prefix, std::string_view name, std::initializer_list<std::string_view> labels, 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, ConstSpan<ValueType> default_upper_bounds, std::string_view helptext,
std::string_view unit = "1", bool is_sum = false) { std::string_view unit = "") {
auto lbl_span = Span{labels.begin(), labels.size()}; auto lbl_span = Span{labels.begin(), labels.size()};
return HistogramFamily<ValueType>(prefix, name, lbl_span, default_upper_bounds, helptext, unit, is_sum); return HistogramFamily<ValueType>(prefix, name, lbl_span, default_upper_bounds, helptext, unit);
} }
/** /**
@ -357,29 +304,76 @@ public:
* @p default_upper_bounds via run-time configuration. * @p default_upper_bounds via run-time configuration.
*/ */
template<class ValueType = int64_t> template<class ValueType = int64_t>
Histogram<ValueType> HistogramInstance(std::string_view prefix, std::string_view name, Span<const LabelView> labels, std::shared_ptr<Histogram<ValueType>> HistogramInstance(std::string_view prefix, std::string_view name,
ConstSpan<ValueType> default_upper_bounds, std::string_view helptext, Span<const LabelView> labels,
std::string_view unit = "1", bool is_sum = false) { ConstSpan<ValueType> default_upper_bounds,
std::string_view helptext, std::string_view unit = "") {
return WithLabelNames(labels, [&, this](auto labelNames) { return WithLabelNames(labels, [&, this](auto labelNames) {
auto family = auto family = HistogramFamily<ValueType>(prefix, name, labelNames, default_upper_bounds, helptext, unit);
HistogramFamily<ValueType>(prefix, name, labelNames, default_upper_bounds, helptext, unit, is_sum); return family->GetOrAdd(labels);
return family.getOrAdd(labels);
}); });
} }
/// @copdoc HistogramInstance /// @copdoc HistogramInstance
template<class ValueType = int64_t> template<class ValueType = int64_t>
Histogram<ValueType> HistogramInstance(std::string_view prefix, std::string_view name, std::shared_ptr<Histogram<ValueType>> HistogramInstance(std::string_view prefix, std::string_view name,
std::initializer_list<LabelView> labels, std::initializer_list<LabelView> labels,
ConstSpan<ValueType> default_upper_bounds, std::string_view helptext, std::initializer_list<ValueType> default_upper_bounds,
std::string_view unit = "1", bool is_sum = false) { std::string_view helptext, std::string_view unit = "") {
auto lbls = Span{labels.begin(), labels.size()}; auto lbls = Span{labels.begin(), labels.size()};
return HistogramInstance(prefix, name, lbls, default_upper_bounds, helptext, unit, is_sum); auto bounds = Span{default_upper_bounds.begin(), default_upper_bounds.size()};
return HistogramInstance<ValueType>(prefix, name, lbls, bounds, helptext, unit);
}
/**
* Changes the frequency for publishing scraped metrics to the target topic.
* Passing a zero-length interval has no effect.
* @param value Interval between two scrapes in seconds.
*/
void SetMetricsExportInterval(double value);
/**
* Sets a new target topic for the metrics. Passing an empty string has no
* effect.
* @param value The new topic for publishing local metrics to.
*/
void SetMetricsExportTopic(std::string value);
/**
* Sets the import topics for a node importing metrics.
*
* @param topics List of topics from which to import metrics.
*/
void SetMetricsImportTopics(std::vector<std::string> topics);
/**
* Sets a new ID for the metrics exporter. Passing an empty string has no
* effect.
* @param value The new ID of the exporter in published metrics.
*/
void SetMetricsExportEndpointName(std::string value);
/**
* Sets a prefix selection for the metrics exporter. An empty vector selects
* *all* metrics.
* @param filter List of selected metric prefixes or an empty vector for
* selecting all metrics.
*/
void SetMetricsExportPrefixes(std::vector<std::string> filter);
bool IsExporting() const { return ! export_topic.empty() && ! export_endpoint.empty(); }
const std::string& MetricsSchema() const { return metrics_schema; }
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;
} }
protected: protected:
template<class F> template<class F>
static void WithLabelNames(Span<const LabelView> xs, F continuation) { static auto WithLabelNames(Span<const LabelView> xs, F continuation) {
if ( xs.size() <= 10 ) { if ( xs.size() <= 10 ) {
std::string_view buf[10]; std::string_view buf[10];
for ( size_t index = 0; index < xs.size(); ++index ) for ( size_t index = 0; index < xs.size(); ++index )
@ -390,29 +384,41 @@ protected:
else { else {
std::vector<std::string_view> buf; std::vector<std::string_view> buf;
for ( auto x : xs ) for ( auto x : xs )
buf.emplace_back(x.first, x.second); buf.emplace_back(x.first);
return continuation(Span{buf}); return continuation(Span{buf});
} }
} }
broker::telemetry::metric_registry_impl* Ptr() { return pimpl.get(); }
// Connects all the dots after the Broker Manager constructed the endpoint
// for this Zeek instance. Called from Broker::Manager::InitPostScript().
void InitPostBrokerSetup(broker::endpoint&);
IntrusivePtr<broker::telemetry::metric_registry_impl> pimpl;
private: private:
// Caching of metric_family_hdl instances to their Zeek record representation. std::shared_ptr<MetricFamily> LookupFamily(std::string_view prefix, std::string_view name) const;
std::unordered_map<const broker::telemetry::metric_family_hdl*, zeek::RecordValPtr> metric_opts_cache;
std::string metrics_schema;
std::shared_ptr<OtelReader> otel_reader;
std::map<std::string, std::shared_ptr<MetricFamily>> families;
detail::process_stats current_process_stats;
double process_stats_last_updated = 0.0;
std::shared_ptr<IntGauge> rss_gauge;
std::shared_ptr<IntGauge> vms_gauge;
std::shared_ptr<DblGauge> cpu_gauge;
std::shared_ptr<IntGauge> fds_gauge;
std::string export_topic;
std::vector<std::string> import_topics;
std::string export_endpoint;
std::vector<std::string> export_prefixes;
double export_interval = 0.0;
std::shared_ptr<prometheus::Registry> prometheus_registry;
std::unique_ptr<prometheus::Exposer> prometheus_exposer;
}; };
} // namespace zeek::telemetry } // namespace zeek::telemetry
namespace zeek { namespace zeek {
extern telemetry::Manager* telemetry_mgr; extern telemetry::Manager* telemetry_mgr;
} // namespace zeek } // namespace zeek

View file

@ -0,0 +1,78 @@
#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_export_endpoint_name")->AsStringVal();
p_labels.emplace("endpoint", endpoint->ToStdString());
}
return p_labels;
}
} // namespace zeek::telemetry

View file

@ -7,8 +7,9 @@
#include <utility> #include <utility>
#include "zeek/Span.h" #include "zeek/Span.h"
#include "zeek/Val.h"
#include "broker/telemetry/metric_family.hh" #include "prometheus/labels.h"
namespace zeek::telemetry { namespace zeek::telemetry {
@ -27,50 +28,92 @@ public:
MetricFamily(const MetricFamily&) noexcept = default; MetricFamily(const MetricFamily&) noexcept = default;
MetricFamily& operator=(const MetricFamily&) noexcept = default; MetricFamily& operator=(const MetricFamily&) noexcept = default;
virtual ~MetricFamily() = default;
/** /**
* @return The prefix (namespace) this family belongs to. Builtin metrics * @return The prefix (namespace) this family belongs to. Builtin metrics
* of Zeek return @c zeek. Custom metrics, e.g., created in a * of Zeek return @c zeek. Custom metrics, e.g., created in a
* script, may use a prefix that represents the application/script * script, may use a prefix that represents the application/script
* or protocol (e.g. @c http) name. * or protocol (e.g. @c http) name.
*/ */
std::string_view Prefix() const noexcept { return broker::telemetry::prefix(hdl); } std::string_view Prefix() const noexcept { return prefix; }
/** /**
* @return The human-readable name of the metric, e.g., * @return The human-readable name of the metric, e.g.,
* @p open-connections. * @p open-connections.
*/ */
std::string_view Name() const noexcept { return broker::telemetry::name(hdl); } 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. * @return The names for all label dimensions.
*/ */
Span<const std::string> LabelNames() const noexcept { return broker::telemetry::label_names(hdl); } Span<const std::string> LabelNames() const noexcept { return labels; }
/** /**
* @return A short explanation of the metric. * @return A short explanation of the metric.
*/ */
std::string_view Helptext() const noexcept { return broker::telemetry::helptext(hdl); } std::string_view Helptext() const noexcept { return helptext; }
/** /**
* @return The unit of measurement, preferably a base unit such as * @return The unit of measurement, preferably a base unit such as @c bytes
* @c bytes or @c seconds. Dimensionless counts return the * or @c seconds.
* pseudo-unit @c 1.
*/ */
std::string_view Unit() const noexcept { return broker::telemetry::unit(hdl); } std::string_view Unit() const noexcept { return unit; }
/** /**
* @return Whether metrics of this family accumulate values, where only the * @return Whether metrics of this family accumulate values, where only the
* total value is of interest. For example, the total number of * total value is of interest. For example, the total number of
* HTTP requests. * HTTP requests.
*/ */
bool IsSum() const noexcept { return broker::telemetry::is_sum(hdl); } 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;
protected: protected:
using Handle = broker::telemetry::metric_family_hdl*; 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);
explicit MetricFamily(Handle hdl) : hdl(hdl) {} /**
* 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);
Handle hdl; 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;
}; };
} // namespace zeek::telemetry } // namespace zeek::telemetry

View file

@ -0,0 +1,204 @@
#include "zeek/telemetry/ProcessStats.h"
#include "zeek/util.h"
#ifdef __APPLE__
#include <libproc.h>
#include <mach/mach.h>
#include <mach/task.h>
#include <math.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <unistd.h>
namespace zeek::telemetry::detail {
process_stats get_process_stats() {
process_stats result;
// Fetch memory usage.
{
mach_task_basic_info info;
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
if ( task_info(mach_task_self(), MACH_TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&info), &count) ==
KERN_SUCCESS ) {
result.rss = static_cast<int64_t>(info.resident_size);
result.vms = static_cast<int64_t>(info.virtual_size);
}
}
// Fetch CPU time.
{
task_thread_times_info info;
mach_msg_type_number_t count = TASK_THREAD_TIMES_INFO_COUNT;
if ( task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, reinterpret_cast<task_info_t>(&info), &count) ==
KERN_SUCCESS ) {
// Round to milliseconds.
result.cpu += info.user_time.seconds;
result.cpu += ceil(info.user_time.microseconds / 1000.0) / 1000.0;
result.cpu += info.system_time.seconds;
result.cpu += ceil(info.system_time.microseconds / 1000.0) / 1000.0;
}
}
// Fetch open file handles.
{
// proc_pidinfo is undocumented, but this is what lsof also uses.
auto suggested_buf_size = proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, nullptr, 0);
if ( suggested_buf_size > 0 ) {
auto buf_size = suggested_buf_size;
auto buf = malloc(buf_size); // TODO: could be thread-local
auto res = proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, buf, buf_size);
free(buf);
if ( res > 0 )
result.fds = static_cast<int64_t>(res / sizeof(proc_fdinfo));
}
}
return result;
}
} // namespace zeek::telemetry::detail
#elif defined(HAVE_LINUX)
#include <atomic>
std::atomic<long> global_ticks_per_second;
std::atomic<long> global_page_size;
namespace zeek::telemetry::detail {
/// Caches the result from a `sysconf` call in a cache variable to avoid
/// frequent syscalls. Sets `cache_var` to -1 in case of an error. Initially,
/// `cache_var` must be 0 and we assume a successful syscall would always return
/// some value > 0. If `cache_var` is > 0 then this function simply returns the
/// cached value directly.
bool load_system_setting(std::atomic<long>& cache_var, long& var, int name, [[maybe_unused]] const char* pretty_name) {
var = cache_var.load();
switch ( var ) {
case -1: return false;
case 0:
var = sysconf(name);
if ( var <= 0 ) {
var = -1;
cache_var = var;
return false;
}
else {
cache_var = var;
return true;
}
default: return true;
}
}
#define TRY_LOAD(varname, confname) load_system_setting(global_##varname, varname, confname, #confname)
process_stats get_process_stats() {
process_stats result;
long ticks_per_second = 0;
long page_size = 0;
if ( ! TRY_LOAD(ticks_per_second, _SC_CLK_TCK) || ! TRY_LOAD(page_size, _SC_PAGE_SIZE) )
return result;
if ( auto f = fopen("/proc/self/stat", "r") ) {
unsigned long utime_ticks = 0;
unsigned long stime_ticks = 0;
unsigned long vmsize_bytes = 0;
unsigned long rss_pages = 0;
auto rd = fscanf(f,
"%*d " // 1. PID
"%*s " // 2. Executable
"%*c " // 3. State
"%*d " // 4. Parent PID
"%*d " // 5. Process group ID
"%*d " // 6. Session ID
"%*d " // 7. Controlling terminal
"%*d " // 8. Foreground process group ID
"%*u " // 9. Flags
"%*u " // 10. Number of minor faults
"%*u " // 11. Number of minor faults of waited-for children
"%*u " // 12. Number of major faults
"%*u " // 13. Number of major faults of waited-for children
"%lu " // 14. CPU user time in ticks
"%lu " // 15. CPU kernel time in ticks
"%*d " // 16. CPU user time of waited-for children
"%*d " // 17. CPU kernel time of waited-for children
"%*d " // 18. Priority
"%*d " // 19. Nice value
"%*d " // 20. Num threads
"%*d " // 21. Obsolete since 2.6
"%*u " // 22. Time the process started after system boot
"%lu " // 23. Virtual memory size in bytes
"%ld", // 24. Resident set size in pages
&utime_ticks, &stime_ticks, &vmsize_bytes, &rss_pages);
fclose(f);
if ( rd != 4 )
return result;
result.rss = rss_pages * page_size;
result.vms = vmsize_bytes;
result.cpu = static_cast<double>(utime_ticks + stime_ticks) / ticks_per_second;
zeek::filesystem::path fd_path{"/proc/self/fd"};
result.fds =
std::distance(zeek::filesystem::directory_iterator{fd_path}, zeek::filesystem::directory_iterator{});
}
return result;
}
} // namespace zeek::telemetry::detail
#elif defined(__FreeBSD__)
// Force these includes into a specific order so that the libraries can find
// all of the required types.
// clang-format off
#include <sys/queue.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/user.h>
#include <unistd.h>
#include <libprocstat.h>
#include <libutil.h>
// clang-format on
namespace zeek::telemetry::detail {
process_stats get_process_stats() {
process_stats result;
struct kinfo_proc* kp = kinfo_getproc(getpid());
result.vms = kp->ki_size;
result.rss = kp->ki_rssize * getpagesize();
result.cpu = static_cast<double>(kp->ki_runtime) / 1000000.0;
struct procstat* procstat = procstat_open_sysctl();
struct filestat_list* files = procstat_getfiles(procstat, kp, 0);
struct filestat* file = nullptr;
// Use one of the looping methods from sys/queue.h instead of
// implementing this by hand.
STAILQ_FOREACH(file, files, next)
result.fds++;
procstat_freeprocs(procstat, kp);
procstat_close(procstat);
return result;
}
#else
process_stats get_process_stats() {
process_stats result = {0};
return result;
}
} // namespace zeek::telemetry::detail
#endif

View file

@ -0,0 +1,23 @@
#pragma once
#include "zeek/zeek-config.h"
#include <cstdint>
namespace zeek::telemetry::detail {
struct process_stats {
int64_t rss = 0;
int64_t vms = 0;
double cpu = 0.0;
int64_t fds = 0;
};
#if defined(__APPLE__) || defined(HAVE_LINUX) || defined(__FreeBSD__)
#define HAVE_PROCESS_STAT_METRICS
process_stats get_process_stats();
#endif
} // namespace zeek::telemetry::detail

View file

@ -15,7 +15,7 @@ class [[nodiscard]] Timer {
public: public:
using Clock = std::chrono::steady_clock; using Clock = std::chrono::steady_clock;
explicit Timer(DblHistogram h) : h_(h) { start_ = Clock::now(); } explicit Timer(std::shared_ptr<DblHistogram> h) : h_(std::move(h)) { start_ = Clock::now(); }
Timer(const Timer&) = delete; Timer(const Timer&) = delete;
@ -30,14 +30,14 @@ public:
auto Started() const noexcept { return start_; } auto Started() const noexcept { return start_; }
/// Calls `h.Observe` with the time passed since `start`. /// Calls `h.Observe` with the time passed since `start`.
static void Observe(DblHistogram h, Clock::time_point start) { static void Observe(const std::shared_ptr<DblHistogram>& h, Clock::time_point start) {
using DblSec = std::chrono::duration<double>; using DblSec = std::chrono::duration<double>;
if ( auto end = Clock::now(); end > start ) if ( auto end = Clock::now(); end > start )
h.Observe(std::chrono::duration_cast<DblSec>(end - start).count()); h->Observe(std::chrono::duration_cast<DblSec>(end - start).count());
} }
private: private:
DblHistogram h_; std::shared_ptr<DblHistogram> h_;
Clock::time_point start_; Clock::time_point start_;
}; };

View file

@ -12,6 +12,7 @@ enum MetricType %{
%} %}
%%{ %%{
#include "zeek/telemetry/Counter.h" #include "zeek/telemetry/Counter.h"
#include "zeek/telemetry/Gauge.h" #include "zeek/telemetry/Gauge.h"
#include "zeek/telemetry/Histogram.h" #include "zeek/telemetry/Histogram.h"
@ -112,7 +113,7 @@ function Telemetry::__int_counter_family%(prefix: string,
name: string, name: string,
labels: string_vec, labels: string_vec,
helptext: string &default = "Zeek Script Metric", helptext: string &default = "Zeek Script Metric",
unit: string &default = "1", unit: string &default = "",
is_sum: bool &default = F%): opaque of int_counter_metric_family is_sum: bool &default = F%): opaque of int_counter_metric_family
%{ %{
auto lbl_vec = sv_vec(labels->AsVectorVal()); auto lbl_vec = sv_vec(labels->AsVectorVal());
@ -129,9 +130,9 @@ function Telemetry::__int_counter_metric_get_or_add%(family: opaque of int_count
{ {
auto hdl = ptr->GetHandle(); auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal()); auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl.LabelNames()) ) if ( is_valid(lbl_map, hdl->LabelNames()) )
{ {
auto res = hdl.GetOrAdd(lbl_map); auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<IntCounterMetricVal>(res); return zeek::make_intrusive<IntCounterMetricVal>(res);
} }
else else
@ -150,14 +151,14 @@ function Telemetry::__int_counter_metric_get_or_add%(family: opaque of int_count
function Telemetry::__int_counter_inc%(val: opaque of int_counter_metric, function Telemetry::__int_counter_inc%(val: opaque of int_counter_metric,
amount: int &default = 1%): bool amount: int &default = 1%): bool
%{ %{
return with<IntCounterMetricVal>(val, "Telemetry::int_counter_inc: invalid handle.", [amount](auto hdl) { hdl.Inc(amount); }); return with<IntCounterMetricVal>(val, "Telemetry::int_counter_inc: invalid handle.", [amount](auto hdl) { hdl->Inc(amount); });
%} %}
function Telemetry::__int_counter_value%(val: opaque of int_counter_metric%): int function Telemetry::__int_counter_value%(val: opaque of int_counter_metric%): int
%{ %{
if ( auto ptr = dynamic_cast<zeek::IntCounterMetricVal*>(val) ) if ( auto ptr = dynamic_cast<zeek::IntCounterMetricVal*>(val) )
{ {
return zeek::val_mgr->Int(ptr->GetHandle().Value()); return zeek::val_mgr->Int(ptr->GetHandle()->Value());
} }
else else
{ {
@ -172,7 +173,7 @@ function Telemetry::__dbl_counter_family%(prefix: string,
name: string, name: string,
labels: string_vec, labels: string_vec,
helptext: string &default = "Zeek Script Metric", helptext: string &default = "Zeek Script Metric",
unit: string &default = "1", unit: string &default = "",
is_sum: bool &default = F%): opaque of dbl_counter_metric_family is_sum: bool &default = F%): opaque of dbl_counter_metric_family
%{ %{
auto lbl_vec = sv_vec(labels->AsVectorVal()); auto lbl_vec = sv_vec(labels->AsVectorVal());
@ -189,9 +190,9 @@ function Telemetry::__dbl_counter_metric_get_or_add%(family: opaque of dbl_count
{ {
auto hdl = ptr->GetHandle(); auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal()); auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl.LabelNames()) ) if ( is_valid(lbl_map, hdl->LabelNames()) )
{ {
auto res = hdl.GetOrAdd(lbl_map); auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<DblCounterMetricVal>(res); return zeek::make_intrusive<DblCounterMetricVal>(res);
} }
else else
@ -210,14 +211,14 @@ function Telemetry::__dbl_counter_metric_get_or_add%(family: opaque of dbl_count
function Telemetry::__dbl_counter_inc%(val: opaque of dbl_counter_metric, function Telemetry::__dbl_counter_inc%(val: opaque of dbl_counter_metric,
amount: double &default = 1.0%): bool amount: double &default = 1.0%): bool
%{ %{
return with<DblCounterMetricVal>(val, "Telemetry::dbl_counter_inc: invalid handle.", [amount](auto hdl) { hdl.Inc(amount); }); return with<DblCounterMetricVal>(val, "Telemetry::dbl_counter_inc: invalid handle.", [amount](auto hdl) { hdl->Inc(amount); });
%} %}
function Telemetry::__dbl_counter_value%(val: opaque of dbl_counter_metric%): double function Telemetry::__dbl_counter_value%(val: opaque of dbl_counter_metric%): double
%{ %{
if ( auto ptr = dynamic_cast<zeek::DblCounterMetricVal*>(val) ) if ( auto ptr = dynamic_cast<zeek::DblCounterMetricVal*>(val) )
{ {
return zeek::make_intrusive<DoubleVal>(ptr->GetHandle().Value()); return zeek::make_intrusive<DoubleVal>(ptr->GetHandle()->Value());
} }
else else
{ {
@ -232,7 +233,7 @@ function Telemetry::__int_gauge_family%(prefix: string,
name: string, name: string,
labels: string_vec, labels: string_vec,
helptext: string &default = "Zeek Script Metric", helptext: string &default = "Zeek Script Metric",
unit: string &default = "1", unit: string &default = "",
is_sum: bool &default = F%): opaque of int_gauge_metric_family is_sum: bool &default = F%): opaque of int_gauge_metric_family
%{ %{
auto lbl_vec = sv_vec(labels->AsVectorVal()); auto lbl_vec = sv_vec(labels->AsVectorVal());
@ -249,9 +250,9 @@ function Telemetry::__int_gauge_metric_get_or_add%(family: opaque of int_gauge_m
{ {
auto hdl = ptr->GetHandle(); auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal()); auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl.LabelNames()) ) if ( is_valid(lbl_map, hdl->LabelNames()) )
{ {
auto res = hdl.GetOrAdd(lbl_map); auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<IntGaugeMetricVal>(res); return zeek::make_intrusive<IntGaugeMetricVal>(res);
} }
else else
@ -270,20 +271,20 @@ function Telemetry::__int_gauge_metric_get_or_add%(family: opaque of int_gauge_m
function Telemetry::__int_gauge_inc%(val: opaque of int_gauge_metric, function Telemetry::__int_gauge_inc%(val: opaque of int_gauge_metric,
amount: int &default = 1%): bool amount: int &default = 1%): bool
%{ %{
return with<IntGaugeMetricVal>(val, "Telemetry::int_gauge_inc: invalid handle.", [amount](auto hdl) { hdl.Inc(amount); }); return with<IntGaugeMetricVal>(val, "Telemetry::int_gauge_inc: invalid handle.", [amount](auto hdl) { hdl->Inc(amount); });
%} %}
function Telemetry::__int_gauge_dec%(val: opaque of int_gauge_metric, function Telemetry::__int_gauge_dec%(val: opaque of int_gauge_metric,
amount: int &default = 1%): bool amount: int &default = 1%): bool
%{ %{
return with<IntGaugeMetricVal>(val, "Telemetry::int_gauge_dec: invalid handle.", [amount](auto hdl) { hdl.Dec(amount); }); return with<IntGaugeMetricVal>(val, "Telemetry::int_gauge_dec: invalid handle.", [amount](auto hdl) { hdl->Dec(amount); });
%} %}
function Telemetry::__int_gauge_value%(val: opaque of int_gauge_metric%): int function Telemetry::__int_gauge_value%(val: opaque of int_gauge_metric%): int
%{ %{
if ( auto ptr = dynamic_cast<zeek::IntGaugeMetricVal*>(val) ) if ( auto ptr = dynamic_cast<zeek::IntGaugeMetricVal*>(val) )
{ {
return zeek::val_mgr->Int(ptr->GetHandle().Value()); return zeek::val_mgr->Int(ptr->GetHandle()->Value());
} }
else else
{ {
@ -298,7 +299,7 @@ function Telemetry::__dbl_gauge_family%(prefix: string,
name: string, name: string,
labels: string_vec, labels: string_vec,
helptext: string &default = "Zeek Script Metric", helptext: string &default = "Zeek Script Metric",
unit: string &default = "1", unit: string &default = "",
is_sum: bool &default = F%): opaque of dbl_gauge_metric_family is_sum: bool &default = F%): opaque of dbl_gauge_metric_family
%{ %{
auto lbl_vec = sv_vec(labels->AsVectorVal()); auto lbl_vec = sv_vec(labels->AsVectorVal());
@ -315,9 +316,9 @@ function Telemetry::__dbl_gauge_metric_get_or_add%(family: opaque of dbl_gauge_m
{ {
auto hdl = ptr->GetHandle(); auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal()); auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl.LabelNames()) ) if ( is_valid(lbl_map, hdl->LabelNames()) )
{ {
auto res = hdl.GetOrAdd(lbl_map); auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<DblGaugeMetricVal>(res); return zeek::make_intrusive<DblGaugeMetricVal>(res);
} }
else else
@ -336,20 +337,20 @@ function Telemetry::__dbl_gauge_metric_get_or_add%(family: opaque of dbl_gauge_m
function Telemetry::__dbl_gauge_inc%(val: opaque of dbl_gauge_metric, function Telemetry::__dbl_gauge_inc%(val: opaque of dbl_gauge_metric,
amount: double &default = 1.0%): bool amount: double &default = 1.0%): bool
%{ %{
return with<DblGaugeMetricVal>(val, "Telemetry::dbl_gauge_inc: invalid handle.", [amount](auto hdl) { hdl.Inc(amount); }); return with<DblGaugeMetricVal>(val, "Telemetry::dbl_gauge_inc: invalid handle.", [amount](auto hdl) { hdl->Inc(amount); });
%} %}
function Telemetry::__dbl_gauge_dec%(val: opaque of dbl_gauge_metric, function Telemetry::__dbl_gauge_dec%(val: opaque of dbl_gauge_metric,
amount: double &default = 1.0%): bool amount: double &default = 1.0%): bool
%{ %{
return with<DblGaugeMetricVal>(val, "Telemetry::dbl_gauge_dec: invalid handle.", [amount](auto hdl) { hdl.Dec(amount); }); return with<DblGaugeMetricVal>(val, "Telemetry::dbl_gauge_dec: invalid handle.", [amount](auto hdl) { hdl->Dec(amount); });
%} %}
function Telemetry::__dbl_gauge_value%(val: opaque of dbl_gauge_metric%): double function Telemetry::__dbl_gauge_value%(val: opaque of dbl_gauge_metric%): double
%{ %{
if ( auto ptr = dynamic_cast<zeek::DblGaugeMetricVal*>(val) ) if ( auto ptr = dynamic_cast<zeek::DblGaugeMetricVal*>(val) )
{ {
return zeek::make_intrusive<DoubleVal>(ptr->GetHandle().Value()); return zeek::make_intrusive<DoubleVal>(ptr->GetHandle()->Value());
} }
else else
{ {
@ -365,14 +366,14 @@ function Telemetry::__int_histogram_family%(prefix: string,
labels: string_vec, labels: string_vec,
bounds: int_vec, bounds: int_vec,
helptext: string &default = "Zeek Script Metric", helptext: string &default = "Zeek Script Metric",
unit: string &default = "1", unit: string &default = "",
is_sum: bool &default = F%): opaque of int_histogram_metric_family is_sum: bool &default = F%): opaque of int_histogram_metric_family
%{ %{
auto lbl_vec = sv_vec(labels->AsVectorVal()); auto lbl_vec = sv_vec(labels->AsVectorVal());
auto std_bounds = to_std_vec<int64_t>(bounds); auto std_bounds = to_std_vec<int64_t>(bounds);
auto hdl = telemetry_mgr->HistogramFamily(sv(prefix), sv(name), lbl_vec, auto hdl = telemetry_mgr->HistogramFamily(sv(prefix), sv(name), lbl_vec,
std_bounds, sv(helptext), std_bounds, sv(helptext),
sv(unit), is_sum); sv(unit));
return zeek::make_intrusive<IntHistogramMetricFamilyVal>(hdl); return zeek::make_intrusive<IntHistogramMetricFamilyVal>(hdl);
%} %}
@ -384,9 +385,9 @@ function Telemetry::__int_histogram_metric_get_or_add%(family: opaque of int_his
{ {
auto hdl = ptr->GetHandle(); auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal()); auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl.LabelNames()) ) if ( is_valid(lbl_map, hdl->LabelNames()) )
{ {
auto res = hdl.GetOrAdd(lbl_map); auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<IntHistogramMetricVal>(res); return zeek::make_intrusive<IntHistogramMetricVal>(res);
} }
else else
@ -405,14 +406,14 @@ function Telemetry::__int_histogram_metric_get_or_add%(family: opaque of int_his
function Telemetry::__int_histogram_observe%(val: opaque of int_histogram_metric, function Telemetry::__int_histogram_observe%(val: opaque of int_histogram_metric,
measurement: int%): bool measurement: int%): bool
%{ %{
return with<IntHistogramMetricVal>(val, "Telemetry::int_histogram_inc: invalid handle.", [measurement](auto hdl) { hdl.Observe(measurement); }); return with<IntHistogramMetricVal>(val, "Telemetry::int_histogram_inc: invalid handle.", [measurement](auto hdl) { hdl->Observe(measurement); });
%} %}
function Telemetry::__int_histogram_sum%(val: opaque of int_histogram_metric%): int function Telemetry::__int_histogram_sum%(val: opaque of int_histogram_metric%): int
%{ %{
if ( auto ptr = dynamic_cast<zeek::IntHistogramMetricVal*>(val) ) if ( auto ptr = dynamic_cast<zeek::IntHistogramMetricVal*>(val) )
{ {
return zeek::val_mgr->Int(ptr->GetHandle().Sum()); return zeek::val_mgr->Int(ptr->GetHandle()->Sum());
} }
else else
{ {
@ -428,15 +429,14 @@ function Telemetry::__dbl_histogram_family%(prefix: string,
labels: string_vec, labels: string_vec,
bounds: double_vec, bounds: double_vec,
helptext: string &default = "Zeek Script Metric", helptext: string &default = "Zeek Script Metric",
unit: string &default = "1", unit: string &default = "",
is_sum: bool &default = F%): opaque of dbl_histogram_metric_family is_sum: bool &default = F%): opaque of dbl_histogram_metric_family
%{ %{
auto lbl_vec = sv_vec(labels->AsVectorVal()); auto lbl_vec = sv_vec(labels->AsVectorVal());
auto std_bounds = to_std_vec<double>(bounds); auto std_bounds = to_std_vec<double>(bounds);
auto hdl = telemetry_mgr->HistogramFamily<double>(sv(prefix), sv(name), auto hdl = telemetry_mgr->HistogramFamily<double>(sv(prefix), sv(name),
lbl_vec, std_bounds, lbl_vec, std_bounds,
sv(helptext), sv(unit), sv(helptext), sv(unit));
is_sum);
return zeek::make_intrusive<DblHistogramMetricFamilyVal>(hdl); return zeek::make_intrusive<DblHistogramMetricFamilyVal>(hdl);
%} %}
@ -448,9 +448,9 @@ function Telemetry::__dbl_histogram_metric_get_or_add%(family: opaque of dbl_his
{ {
auto hdl = ptr->GetHandle(); auto hdl = ptr->GetHandle();
auto lbl_map = sv_tbl(labels->AsTableVal()); auto lbl_map = sv_tbl(labels->AsTableVal());
if ( is_valid(lbl_map, hdl.LabelNames()) ) if ( is_valid(lbl_map, hdl->LabelNames()) )
{ {
auto res = hdl.GetOrAdd(lbl_map); auto res = hdl->GetOrAdd(lbl_map);
return zeek::make_intrusive<DblHistogramMetricVal>(res); return zeek::make_intrusive<DblHistogramMetricVal>(res);
} }
else else
@ -469,14 +469,14 @@ function Telemetry::__dbl_histogram_metric_get_or_add%(family: opaque of dbl_his
function Telemetry::__dbl_histogram_observe%(val: opaque of dbl_histogram_metric, function Telemetry::__dbl_histogram_observe%(val: opaque of dbl_histogram_metric,
measurement: double%): bool measurement: double%): bool
%{ %{
return with<DblHistogramMetricVal>(val, "Telemetry::dbl_histogram_inc: invalid handle.", [measurement](auto hdl) { hdl.Observe(measurement); }); return with<DblHistogramMetricVal>(val, "Telemetry::dbl_histogram_inc: invalid handle.", [measurement](auto hdl) { hdl->Observe(measurement); });
%} %}
function Telemetry::__dbl_histogram_sum%(val: opaque of dbl_histogram_metric%): double function Telemetry::__dbl_histogram_sum%(val: opaque of dbl_histogram_metric%): double
%{ %{
if ( auto ptr = dynamic_cast<zeek::DblHistogramMetricVal*>(val) ) if ( auto ptr = dynamic_cast<zeek::DblHistogramMetricVal*>(val) )
{ {
return zeek::make_intrusive<DoubleVal>(ptr->GetHandle().Sum()); return zeek::make_intrusive<DoubleVal>(ptr->GetHandle()->Sum());
} }
else else
{ {
@ -487,26 +487,61 @@ function Telemetry::__dbl_histogram_sum%(val: opaque of dbl_histogram_metric%):
function Telemetry::__collect_metrics%(prefix: string, name: string%): any_vec function Telemetry::__collect_metrics%(prefix: string, name: string%): any_vec
%{ %{
auto metrics = telemetry_mgr->CollectMetrics(sv(prefix), sv(name)); return telemetry_mgr->CollectMetrics(sv(prefix), sv(name));
static auto metrics_vector_type = zeek::id::find_type<VectorType>("any_vec");
auto vec = zeek::make_intrusive<VectorVal>(metrics_vector_type);
for ( const auto& m : metrics )
vec->Append(m.AsMetricRecord());
return vec;
%} %}
function Telemetry::__collect_histogram_metrics%(prefix: string, name: string%): any_vec function Telemetry::__collect_histogram_metrics%(prefix: string, name: string%): any_vec
%{ %{
auto metrics = telemetry_mgr->CollectHistogramMetrics(sv(prefix), sv(name)); return telemetry_mgr->CollectHistogramMetrics(sv(prefix), sv(name));
static auto metrics_vector_type = zeek::id::find_type<VectorType>("any_vec");
auto vec = zeek::make_intrusive<VectorVal>(metrics_vector_type);
for ( const auto& m : metrics )
vec->Append(m.AsHistogramMetricRecord());
return vec;
%} %}
function Telemetry::__set_metrics_export_interval%(value: interval%): bool
%{
// This BIF may run prior to telemetry::Manager::InitPostScript. In this case,
// telemetry_mgr is still null but we can safely ignore this event because the
// Manager is going to initialize Telemetry using the most recent value of the
// corresponding option.
if ( telemetry_mgr )
telemetry_mgr->SetMetricsExportInterval(value);
return zeek::val_mgr->True();
%}
function Telemetry::__set_metrics_export_topic%(value: string%): bool
%{
if ( telemetry_mgr )
telemetry_mgr->SetMetricsExportTopic(value->ToStdString());
return zeek::val_mgr->True();
%}
function Telemetry::__set_metrics_import_topics%(filter: string_vec%): bool
%{
if ( telemetry_mgr )
{
std::vector<std::string> slist;
auto* vval = filter->AsVectorVal();
for ( unsigned index = 0; index < vval->Size(); ++index )
slist.emplace_back(vval->StringValAt(index)->ToStdString());
telemetry_mgr->SetMetricsImportTopics(std::move(slist));
}
return zeek::val_mgr->True();
%}
function Telemetry::__set_metrics_export_endpoint_name%(value: string%): bool
%{
if ( telemetry_mgr )
telemetry_mgr->SetMetricsExportEndpointName(value->ToStdString());
return zeek::val_mgr->True();
%}
function Telemetry::__set_metrics_export_prefixes%(filter: string_vec%): bool
%{
if ( telemetry_mgr )
{
std::vector<std::string> slist;
auto* vval = filter->AsVectorVal();
for ( unsigned index = 0; index < vval->Size(); ++index )
slist.emplace_back(vval->StringValAt(index)->ToStdString());
telemetry_mgr->SetMetricsExportPrefixes(std::move(slist));
}
return zeek::val_mgr->True();
%}

View file

@ -811,7 +811,6 @@ SetupResult setup(int argc, char** argv, Options* zopts) {
RecordType::InitPostScript(); RecordType::InitPostScript();
telemetry_mgr->InitPostScript();
iosource_mgr->InitPostScript(); iosource_mgr->InitPostScript();
log_mgr->InitPostScript(); log_mgr->InitPostScript();
plugin_mgr->InitPostScript(); plugin_mgr->InitPostScript();
@ -820,6 +819,10 @@ SetupResult setup(int argc, char** argv, Options* zopts) {
timer_mgr->InitPostScript(); timer_mgr->InitPostScript();
event_mgr.InitPostScript(); event_mgr.InitPostScript();
// telemetry_mgr has be initialized after broker manager since it might
// register for a topic and would fail to do so otherwise.
telemetry_mgr->InitPostScript();
if ( supervisor_mgr ) if ( supervisor_mgr )
supervisor_mgr->InitPostScript(); supervisor_mgr->InitPostScript();