Add handle types for histogram metrics

This commit is contained in:
Dominik Charousset 2021-03-02 12:54:20 +01:00
parent 0b665ee130
commit d4466db5ce
8 changed files with 884 additions and 172 deletions

View file

@ -8,6 +8,7 @@ include_directories(BEFORE
set(telemetry_SRCS set(telemetry_SRCS
Counter.cc Counter.cc
Gauge.cc Gauge.cc
Histogram.cc
Manager.cc Manager.cc
MetricFamily.cc MetricFamily.cc
) )

View file

@ -12,44 +12,6 @@ namespace ct = caf::telemetry;
namespace zeek::telemetry { namespace zeek::telemetry {
// -- bindings to private utility functions ------------------------------------
template <>
struct PimplTrait<IntCounter::Impl>
{
using Native = ct::int_counter;
using Oqaque = IntCounter::Impl;
using NativeFamily = ct::metric_family_impl<Native>;
};
template <>
struct PimplTrait<ct::int_counter> : PimplTrait<IntCounter::Impl> { };
template <>
struct PimplTrait<IntCounterFamily::Impl>
{
using Native = typename PimplTrait<IntCounter::Impl>::NativeFamily;
using Oqaque = IntCounterFamily::Impl;
};
template <>
struct PimplTrait<DblCounter::Impl>
{
using Native = ct::dbl_counter;
using Oqaque = DblCounter::Impl;
using NativeFamily = ct::metric_family_impl<Native>;
};
template <>
struct PimplTrait<ct::dbl_counter> : PimplTrait<DblCounter::Impl> { };
template <>
struct PimplTrait<DblCounterFamily::Impl>
{
using Native = typename PimplTrait<DblCounter::Impl>::NativeFamily;
using Oqaque = DblCounterFamily::Impl;
};
// -- IntCounter --------------------------------------------------------------- // -- IntCounter ---------------------------------------------------------------
void IntCounter::Inc() noexcept void IntCounter::Inc() noexcept

View file

@ -10,6 +10,10 @@
#include <type_traits> #include <type_traits>
#include "zeek/Span.h" #include "zeek/Span.h"
#include "zeek/telemetry/Counter.h"
#include "zeek/telemetry/Gauge.h"
#include "zeek/telemetry/Histogram.h"
#include "zeek/telemetry/Manager.h"
#include "zeek/telemetry/MetricFamily.h" #include "zeek/telemetry/MetricFamily.h"
#include "caf/telemetry/label_view.hpp" #include "caf/telemetry/label_view.hpp"
@ -17,6 +21,8 @@
namespace zeek::telemetry { namespace zeek::telemetry {
// -- traits for converting between opaque handles and native pointers ---------
/** /**
* This trait must provide the member types @c Native for referring to the CAF * This trait must provide the member types @c Native for referring to the CAF
* type, @c Opaque for referring to the @c Impl type. For instance types such as * type, @c Opaque for referring to the @c Impl type. For instance types such as
@ -25,6 +31,155 @@ namespace zeek::telemetry {
template <class T> template <class T>
struct PimplTrait; struct PimplTrait;
template <>
struct PimplTrait<IntCounter::Impl>
{
using Native = caf::telemetry::int_counter;
using Oqaque = IntCounter::Impl;
using NativeFamily = caf::telemetry::metric_family_impl<Native>;
};
template <>
struct PimplTrait<caf::telemetry::int_counter>
: PimplTrait<IntCounter::Impl> { };
template <>
struct PimplTrait<IntCounterFamily::Impl>
{
using Native = typename PimplTrait<IntCounter::Impl>::NativeFamily;
using Oqaque = IntCounterFamily::Impl;
};
template <>
struct PimplTrait<typename PimplTrait<IntCounter::Impl>::NativeFamily>
: PimplTrait<IntCounterFamily::Impl> { };
template <>
struct PimplTrait<DblCounter::Impl>
{
using Native = caf::telemetry::dbl_counter;
using Oqaque = DblCounter::Impl;
using NativeFamily = caf::telemetry::metric_family_impl<Native>;
};
template <>
struct PimplTrait<caf::telemetry::dbl_counter>
: PimplTrait<DblCounter::Impl> { };
template <>
struct PimplTrait<DblCounterFamily::Impl>
{
using Native = typename PimplTrait<DblCounter::Impl>::NativeFamily;
using Oqaque = DblCounterFamily::Impl;
};
template <>
struct PimplTrait<typename PimplTrait<DblCounter::Impl>::NativeFamily>
: PimplTrait<DblCounterFamily::Impl> { };
template <>
struct PimplTrait<IntGauge::Impl>
{
using Native = caf::telemetry::int_gauge;
using Oqaque = IntGauge::Impl;
using NativeFamily = caf::telemetry::metric_family_impl<Native>;
};
template <>
struct PimplTrait<caf::telemetry::int_gauge> : PimplTrait<IntGauge::Impl> { };
template <>
struct PimplTrait<IntGaugeFamily::Impl>
{
using Native = typename PimplTrait<IntGauge::Impl>::NativeFamily;
using Oqaque = IntGaugeFamily::Impl;
};
template <>
struct PimplTrait<typename PimplTrait<IntGauge::Impl>::NativeFamily>
: PimplTrait<IntGaugeFamily::Impl> { };
template <>
struct PimplTrait<DblGauge::Impl>
{
using Native = caf::telemetry::dbl_gauge;
using Oqaque = DblGauge::Impl;
using NativeFamily = caf::telemetry::metric_family_impl<Native>;
};
template <>
struct PimplTrait<caf::telemetry::dbl_gauge> : PimplTrait<DblGauge::Impl> { };
template <>
struct PimplTrait<DblGaugeFamily::Impl>
{
using Native = typename PimplTrait<DblGauge::Impl>::NativeFamily;
using Oqaque = DblGaugeFamily::Impl;
};
template <>
struct PimplTrait<typename PimplTrait<DblGauge::Impl>::NativeFamily>
: PimplTrait<DblGaugeFamily::Impl> { };
template <>
struct PimplTrait<IntHistogram::Impl>
{
using Native = caf::telemetry::int_histogram;
using Oqaque = IntHistogram::Impl;
using NativeFamily = caf::telemetry::metric_family_impl<Native>;
};
template <>
struct PimplTrait<caf::telemetry::int_histogram>
: PimplTrait<IntHistogram::Impl> { };
template <>
struct PimplTrait<IntHistogramFamily::Impl>
{
using Native = typename PimplTrait<IntHistogram::Impl>::NativeFamily;
using Oqaque = IntHistogramFamily::Impl;
};
template <>
struct PimplTrait<typename PimplTrait<IntHistogram::Impl>::NativeFamily>
: PimplTrait<IntHistogramFamily::Impl> { };
template <>
struct PimplTrait<DblHistogram::Impl>
{
using Native = caf::telemetry::dbl_histogram;
using Oqaque = DblHistogram::Impl;
using NativeFamily = caf::telemetry::metric_family_impl<Native>;
};
template <>
struct PimplTrait<caf::telemetry::dbl_histogram>
: PimplTrait<DblHistogram::Impl> { };
template <>
struct PimplTrait<DblHistogramFamily::Impl>
{
using Native = typename PimplTrait<DblHistogram::Impl>::NativeFamily;
using Oqaque = DblHistogramFamily::Impl;
};
template <>
struct PimplTrait<typename PimplTrait<DblHistogram::Impl>::NativeFamily>
: PimplTrait<DblHistogramFamily::Impl> { };
template <>
struct PimplTrait<Manager::Impl>
{
using Native = caf::telemetry::metric_registry;
using Oqaque = Manager::Impl;
};
template <>
struct PimplTrait<typename PimplTrait<Manager::Impl>::Native>
: PimplTrait<Manager::Impl> { };
// -- free functions -----------------------------------------------------------
template <class T, class NativeType = typename PimplTrait<T>::Native> template <class T, class NativeType = typename PimplTrait<T>::Native>
auto& deref(T* ptr) auto& deref(T* ptr)
{ {
@ -87,4 +242,23 @@ auto with_native_labels(Span<const LabelView> xs, F continuation)
} }
} }
template <class F>
auto with_native_labels(Span<const std::string_view> xs, F continuation)
{
if ( xs.size() <= 10 )
{
caf::string_view buf[10];
for ( size_t index = 0; index < xs.size(); ++index )
buf[index] = xs[index];
return continuation(Span{buf, xs.size()});
}
else
{
std::vector<caf::string_view> buf;
for ( auto x : xs )
buf.emplace_back(x);
return continuation(Span{buf});
}
}
} // namespace zeek::telemetry } // namespace zeek::telemetry

View file

@ -8,48 +8,8 @@
#include "zeek/telemetry/Detail.h" #include "zeek/telemetry/Detail.h"
namespace ct = caf::telemetry;
namespace zeek::telemetry { namespace zeek::telemetry {
// -- bindings to private utility functions ------------------------------------
template <>
struct PimplTrait<IntGauge::Impl>
{
using Native = ct::int_gauge;
using Oqaque = IntGauge::Impl;
using NativeFamily = ct::metric_family_impl<Native>;
};
template <>
struct PimplTrait<ct::int_gauge> : PimplTrait<IntGauge::Impl> { };
template <>
struct PimplTrait<IntGaugeFamily::Impl>
{
using Native = typename PimplTrait<IntGauge::Impl>::NativeFamily;
using Oqaque = IntGaugeFamily::Impl;
};
template <>
struct PimplTrait<DblGauge::Impl>
{
using Native = ct::dbl_gauge;
using Oqaque = DblGauge::Impl;
using NativeFamily = ct::metric_family_impl<Native>;
};
template <>
struct PimplTrait<ct::dbl_gauge> : PimplTrait<DblGauge::Impl> { };
template <>
struct PimplTrait<DblGaugeFamily::Impl>
{
using Native = typename PimplTrait<DblGauge::Impl>::NativeFamily;
using Oqaque = DblGaugeFamily::Impl;
};
// -- IntGauge --------------------------------------------------------------- // -- IntGauge ---------------------------------------------------------------
void IntGauge::Inc() noexcept void IntGauge::Inc() noexcept

103
src/telemetry/Histogram.cc Normal file
View file

@ -0,0 +1,103 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "zeek/telemetry/Histogram.h"
#include "caf/telemetry/histogram.hpp"
#include "caf/telemetry/metric_family.hpp"
#include "caf/telemetry/metric_family_impl.hpp"
#include "zeek/telemetry/Detail.h"
#include <cassert>
namespace zeek::telemetry {
// -- IntHistogram ---------------------------------------------------------------
void IntHistogram::Observe(int64_t value) noexcept
{
deref(pimpl).observe(value);
}
int64_t IntHistogram::Sum() const noexcept
{
return deref(pimpl).sum();
}
size_t IntHistogram::NumBuckets() const noexcept
{
return deref(pimpl).buckets().size();
}
int64_t IntHistogram::CountAt(size_t index) const noexcept
{
auto xs = deref(pimpl).buckets();
assert(index < xs.size());
return xs[index].count.value();
}
int64_t IntHistogram::UpperBoundAt(size_t index) const noexcept
{
auto xs = deref(pimpl).buckets();
assert(index < xs.size());
return xs[index].upper_bound;
}
IntHistogramFamily::IntHistogramFamily(Impl* ptr) : MetricFamily(upcast(ptr))
{
}
IntHistogram IntHistogramFamily::GetOrAdd(Span<const LabelView> labels)
{
return with_native_labels(labels, [this](auto nativeLabels)
{
auto hdl = opaque(deref(this, pimpl).get_or_add(nativeLabels));
return IntHistogram{hdl};
});
}
// -- DblHistogram ---------------------------------------------------------------
void DblHistogram::Observe(double amount) noexcept
{
deref(pimpl).observe(amount);
}
double DblHistogram::Sum() const noexcept
{
return deref(pimpl).sum();
}
size_t DblHistogram::NumBuckets() const noexcept
{
return deref(pimpl).buckets().size();
}
int64_t DblHistogram::CountAt(size_t index) const noexcept
{
auto xs = deref(pimpl).buckets();
assert(index < xs.size());
return xs[index].count.value();
}
double DblHistogram::UpperBoundAt(size_t index) const noexcept
{
auto xs = deref(pimpl).buckets();
assert(index < xs.size());
return xs[index].upper_bound;
}
DblHistogramFamily::DblHistogramFamily(Impl* ptr) : MetricFamily(upcast(ptr))
{
}
DblHistogram DblHistogramFamily::GetOrAdd(Span<const LabelView> labels)
{
return with_native_labels(labels, [this](auto nativeLabels)
{
auto hdl = opaque(deref(this, pimpl).get_or_add(nativeLabels));
return DblHistogram{hdl};
});
}
} // namespace zeek::telemetry

234
src/telemetry/Histogram.h Normal file
View file

@ -0,0 +1,234 @@
// See the file "COPYING" in the main distribution directory for copyright.
#pragma once
#include <cstdint>
#include <initializer_list>
#include <type_traits>
#include "zeek/Span.h"
#include "zeek/telemetry/MetricFamily.h"
namespace zeek::telemetry {
class DblHistogramFamily;
class IntHistogramFamily;
class Manager;
/**
* A handle to a metric that represents an aggregatable distribution of observed
* measurements with integer precision. Sorts individual measurements into
* configurable buckets.
*/
class IntHistogram {
public:
friend class IntHistogramFamily;
struct Impl;
IntHistogram() = delete;
IntHistogram(const IntHistogram&) noexcept = default;
IntHistogram& operator=(const IntHistogram&) noexcept = default;
/**
* 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 The sum of all observed values.
int64_t Sum() const noexcept;
/// @return The number of buckets, including the implicit "infinite" bucket.
size_t NumBuckets() const noexcept;
/// @return The number of observations in the bucket at @p index.
/// @pre index < NumBuckets()
int64_t CountAt(size_t index) const noexcept;
/// @return The upper bound of the bucket at @p index.
/// @pre index < NumBuckets()
int64_t UpperBoundAt(size_t index) const noexcept;
/**
* @return Whether @c this and @p other refer to the same histogram.
*/
constexpr bool IsSameAs(IntHistogram other) const noexcept
{
return pimpl == other.pimpl;
}
private:
explicit IntHistogram(Impl* ptr) noexcept : pimpl(ptr)
{
}
Impl* pimpl;
};
/**
* Checks whether two @ref IntHistogram handles are identical.
* @return Whether @p lhs and @p rhs refer to the same object.
*/
constexpr bool operator==(IntHistogram lhs, IntHistogram rhs) noexcept
{
return lhs.IsSameAs(rhs);
}
/// @relates IntHistogram
constexpr bool operator!=(IntHistogram lhs, IntHistogram rhs) noexcept
{
return !(lhs == rhs);
}
/**
* Manages a collection of IntHistogram metrics.
*/
class IntHistogramFamily : public MetricFamily {
public:
friend class Manager;
class Impl;
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);
/**
* @copydoc GetOrAdd
*/
IntHistogram GetOrAdd(std::initializer_list<LabelView> labels)
{
return GetOrAdd(Span{labels.begin(), labels.size()});
}
private:
explicit IntHistogramFamily(Impl* ptr);
};
/**
* A handle to a metric that represents an aggregatable distribution of observed
* measurements with floating point precision. Sorts individual measurements
* into configurable buckets.
*/
class DblHistogram {
public:
friend class DblHistogramFamily;
struct Impl;
DblHistogram() = delete;
DblHistogram(const DblHistogram&) noexcept = default;
DblHistogram& operator=(const DblHistogram&) noexcept = default;
/**
* 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;
/// @return The sum of all observed values.
double Sum() const noexcept;
/// @return The number of buckets, including the implicit "infinite" bucket.
size_t NumBuckets() const noexcept;
/// @return The number of observations in the bucket at @p index.
/// @pre index < NumBuckets()
int64_t CountAt(size_t index) const noexcept;
/// @return The upper bound of the bucket at @p index.
/// @pre index < NumBuckets()
double UpperBoundAt(size_t index) const noexcept;
/**
* @return Whether @c this and @p other refer to the same histogram.
*/
constexpr bool IsSameAs(DblHistogram other) const noexcept
{
return pimpl == other.pimpl;
}
private:
explicit DblHistogram(Impl* ptr) noexcept : pimpl(ptr)
{
}
Impl* pimpl;
};
/**
* Checks whether two @ref DblHistogram handles are identical.
* @return Whether @p lhs and @p rhs refer to the same object.
*/
constexpr bool operator==(DblHistogram lhs, DblHistogram rhs) noexcept
{
return lhs.IsSameAs(rhs);
}
/// @relates DblHistogram
constexpr bool operator!=(DblHistogram lhs, DblHistogram rhs) noexcept
{
return !(lhs == rhs);
}
/**
* Manages a collection of DblHistogram metrics.
*/
class DblHistogramFamily : public MetricFamily {
public:
friend class Manager;
class Impl;
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
* lazily if necessary.
*/
DblHistogram GetOrAdd(Span<const LabelView> labels);
/**
* @copydoc GetOrAdd
*/
DblHistogram GetOrAdd(std::initializer_list<LabelView> labels)
{
return GetOrAdd(Span{labels.begin(), labels.size()});
}
private:
explicit DblHistogramFamily(Impl* ptr);
};
namespace detail {
template <class T>
struct HistogramOracle {
static_assert(std::is_same<T, int64_t>::value,
"Histogram<T> only supports int64_t and double");
using type = IntHistogram;
};
template <>
struct HistogramOracle<double> {
using type = DblHistogram;
};
} // namespace detail
template <class T>
using Histogram = typename detail::HistogramOracle<T>::type;
} // namespace zeek::telemetry

View file

@ -6,81 +6,10 @@
#include "zeek/3rdparty/doctest.h" #include "zeek/3rdparty/doctest.h"
#include "zeek/telemetry/Detail.h"
namespace zeek::telemetry { namespace zeek::telemetry {
namespace {
namespace ct = caf::telemetry;
using NativeManager = ct::metric_registry;
using NativeIntCounter = ct::int_counter;
using NativeIntCounterFamily = ct::metric_family_impl<NativeIntCounter>;
using NativeDblCounter = ct::dbl_counter;
using NativeDblCounterFamily = ct::metric_family_impl<NativeDblCounter>;
using NativeIntGauge = ct::int_gauge;
using NativeIntGaugeFamily = ct::metric_family_impl<NativeIntGauge>;
using NativeDblGauge = ct::dbl_gauge;
using NativeDblGaugeFamily = ct::metric_family_impl<NativeDblGauge>;
auto& deref(Manager::Impl* ptr)
{
return *reinterpret_cast<NativeManager*>(ptr);
}
auto opaque(NativeManager* ptr)
{
return reinterpret_cast<Manager::Impl*>(ptr);
}
auto opaque(NativeIntCounterFamily* ptr)
{
return reinterpret_cast<IntCounterFamily::Impl*>(ptr);
}
auto opaque(NativeDblCounterFamily* ptr)
{
return reinterpret_cast<DblCounterFamily::Impl*>(ptr);
}
auto opaque(NativeIntGaugeFamily* ptr)
{
return reinterpret_cast<IntGaugeFamily::Impl*>(ptr);
}
auto opaque(NativeDblGaugeFamily* ptr)
{
return reinterpret_cast<DblGaugeFamily::Impl*>(ptr);
}
template <class F>
auto with_native(Span<const std::string_view> xs, F continuation)
{
if ( xs.size() <= 10 )
{
caf::string_view buf[10];
for ( size_t index = 0; index < xs.size(); ++index )
buf[index] = xs[index];
return continuation(Span{buf, xs.size()});
}
else
{
std::vector<caf::string_view> buf;
for ( auto x : xs )
buf.emplace_back(x);
return continuation(Span{buf});
}
}
} // namespace
Manager::~Manager() Manager::~Manager()
{ {
} }
@ -91,7 +20,7 @@ IntCounterFamily Manager::IntCounterFam(std::string_view prefix,
std::string_view helptext, std::string_view helptext,
std::string_view unit, bool is_sum) std::string_view unit, bool is_sum)
{ {
return with_native(labels, [&, this](auto xs) return with_native_labels(labels, [&, this](auto xs)
{ {
auto ptr = deref(pimpl).counter_family(prefix, name, xs, auto ptr = deref(pimpl).counter_family(prefix, name, xs,
helptext, unit, is_sum); helptext, unit, is_sum);
@ -105,7 +34,7 @@ DblCounterFamily Manager::DblCounterFam(std::string_view prefix,
std::string_view helptext, std::string_view helptext,
std::string_view unit, bool is_sum) std::string_view unit, bool is_sum)
{ {
return with_native(labels, [&, this](auto xs) return with_native_labels(labels, [&, this](auto xs)
{ {
auto ptr = deref(pimpl).counter_family<double>(prefix, name, xs, auto ptr = deref(pimpl).counter_family<double>(prefix, name, xs,
helptext, unit, is_sum); helptext, unit, is_sum);
@ -119,7 +48,7 @@ IntGaugeFamily Manager::IntGaugeFam(std::string_view prefix,
std::string_view helptext, std::string_view helptext,
std::string_view unit, bool is_sum) std::string_view unit, bool is_sum)
{ {
return with_native(labels, [&, this](auto xs) return with_native_labels(labels, [&, this](auto xs)
{ {
auto ptr = deref(pimpl).gauge_family(prefix, name, xs, auto ptr = deref(pimpl).gauge_family(prefix, name, xs,
helptext, unit, is_sum); helptext, unit, is_sum);
@ -133,7 +62,7 @@ DblGaugeFamily Manager::DblGaugeFam(std::string_view prefix,
std::string_view helptext, std::string_view helptext,
std::string_view unit, bool is_sum) std::string_view unit, bool is_sum)
{ {
return with_native(labels, [&, this](auto xs) return with_native_labels(labels, [&, this](auto xs)
{ {
auto ptr = deref(pimpl).gauge_family<double>(prefix, name, xs, auto ptr = deref(pimpl).gauge_family<double>(prefix, name, xs,
helptext, unit, is_sum); helptext, unit, is_sum);
@ -141,6 +70,39 @@ DblGaugeFamily Manager::DblGaugeFam(std::string_view prefix,
}); });
} }
IntHistogramFamily Manager::IntHistoFam(std::string_view prefix,
std::string_view name,
Span<const std::string_view> labels,
Span<const int64_t> ubounds,
std::string_view helptext,
std::string_view unit, bool is_sum)
{
return with_native_labels(labels, [&, this](auto xs)
{
auto bounds = caf::span<const int64_t>{ubounds.data(), ubounds.size()};
auto ptr = deref(pimpl).histogram_family(prefix, name, xs, bounds,
helptext, unit, is_sum);
return IntHistogramFamily{opaque(ptr)};
});
}
DblHistogramFamily Manager::DblHistoFam(std::string_view prefix,
std::string_view name,
Span<const std::string_view> labels,
Span<const double> ubounds,
std::string_view helptext,
std::string_view unit, bool is_sum)
{
return with_native_labels(labels, [&, this](auto xs)
{
auto bounds = caf::span<const double>{ubounds.data(), ubounds.size()};
auto ptr = deref(pimpl).histogram_family<double>(prefix, name, xs,
bounds, helptext,
unit, is_sum);
return DblHistogramFamily{opaque(ptr)};
});
}
} // namespace zeek::telemetry } // namespace zeek::telemetry
// -- unit tests --------------------------------------------------------------- // -- unit tests ---------------------------------------------------------------
@ -148,6 +110,8 @@ DblGaugeFamily Manager::DblGaugeFam(std::string_view prefix,
using namespace std::literals; using namespace std::literals;
using namespace zeek::telemetry; using namespace zeek::telemetry;
using NativeManager = caf::telemetry::metric_registry;
namespace { namespace {
template <class T> template <class T>
@ -241,13 +205,13 @@ SCENARIO("telemetry managers provide access to counter families")
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"}});
@ -266,13 +230,13 @@ SCENARIO("telemetry managers provide access to counter families")
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"}});
@ -371,13 +335,13 @@ SCENARIO("telemetry managers provide access to gauge families")
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"}});
@ -396,13 +360,13 @@ SCENARIO("telemetry managers provide access to gauge families")
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"}});
@ -411,3 +375,151 @@ SCENARIO("telemetry managers provide access to gauge families")
} }
} }
} }
SCENARIO("telemetry managers provide access to histogram singletons")
{
GIVEN("a telemetry manager")
{
NativeManager native_mgr;
Manager mgr{opaque(&native_mgr)};
WHEN("retrieving an IntHistogram singleton")
{
const auto max_int = std::numeric_limits<int64_t>::max();
int64_t buckets [] = { 10, 20 };
auto first = mgr.HistogramSingleton("zeek", "int-hist", buckets, "test");
THEN("it initially has no observations")
{
REQUIRE_EQ(first.NumBuckets(), 3u);
CHECK_EQ(first.Sum(), 0);
CHECK_EQ(first.CountAt(0), 0);
CHECK_EQ(first.CountAt(1), 0);
CHECK_EQ(first.CountAt(2), 0);
CHECK_EQ(first.UpperBoundAt(0), 10);
CHECK_EQ(first.UpperBoundAt(1), 20);
CHECK_EQ(first.UpperBoundAt(2), max_int);
}
AND_THEN("calling Observe() increments bucket counters")
{
first.Observe(1);
first.Observe(9);
first.Observe(10);
first.Observe(11);
first.Observe(19);
first.Observe(20);
first.Observe(21);
CHECK_EQ(first.Sum(), 91);
CHECK_EQ(first.CountAt(0), 3);
CHECK_EQ(first.CountAt(1), 3);
CHECK_EQ(first.CountAt(2), 1);
}
AND_THEN("calling HistogramSingleton again for the same name returns the same handle")
{
auto second = mgr.HistogramSingleton("zeek", "int-hist", buckets, "test");
CHECK_EQ(first, second);
}
AND_THEN("calling HistogramSingleton for a different name returns another handle")
{
auto third = mgr.HistogramSingleton("zeek", "int-hist-2", buckets, "test");
CHECK_NE(first, third);
}
}
WHEN("retrieving a DblHistogram singleton")
{
double buckets [] = { 10.0, 20.0 };
auto first = mgr.HistogramSingleton<double>("zeek", "dbl-count", buckets, "test");
THEN("it initially has no observations")
{
REQUIRE_EQ(first.NumBuckets(), 3u);
CHECK_EQ(first.Sum(), 0.0);
CHECK_EQ(first.CountAt(0), 0);
CHECK_EQ(first.CountAt(1), 0);
CHECK_EQ(first.CountAt(2), 0);
CHECK_EQ(first.UpperBoundAt(0), 10.0);
CHECK_EQ(first.UpperBoundAt(1), 20.0);
}
AND_THEN("calling Observe() increments bucket counters")
{
first.Observe(2.0);
first.Observe(4.0);
first.Observe(8.0);
first.Observe(16.0);
first.Observe(32.0);
CHECK_EQ(first.Sum(), 62.0);
CHECK_EQ(first.CountAt(0), 3);
CHECK_EQ(first.CountAt(1), 1);
CHECK_EQ(first.CountAt(2), 1);
}
AND_THEN("calling histogramSingleton again for the same name returns the same handle")
{
auto second = mgr.HistogramSingleton<double>("zeek", "dbl-count", buckets, "test");
CHECK_EQ(first, second);
}
AND_THEN("calling histogramSingleton for a different name returns another handle")
{
auto third = mgr.HistogramSingleton<double>("zeek", "dbl-count-2", buckets, "test");
CHECK_NE(first, third);
}
}
}
}
SCENARIO("telemetry managers provide access to histogram families")
{
GIVEN("a telemetry manager")
{
NativeManager native_mgr;
Manager mgr{opaque(&native_mgr)};
WHEN("retrieving an IntHistogram family")
{
int64_t buckets [] = { 10, 20 };
auto family = mgr.HistogramFamily("zeek", "payload-size", {"protocol"}, buckets, "test", "bytes");
THEN("the family object stores the parameters")
{
CHECK_EQ(family.Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "payload-size"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family.Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "bytes"sv);
CHECK_EQ(family.IsSum(), false);
}
AND_THEN("GetOrAdd returns the same metric for the same labels")
{
auto first = family.GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second);
}
AND_THEN("GetOrAdd returns different metric for the disjoint labels")
{
auto first = family.GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "udp"}});
CHECK_NE(first, second);
}
}
WHEN("retrieving a DblHistogram family")
{
double buckets [] = { 10.0, 20.0 };
auto family = mgr.HistogramFamily<double>("zeek", "parse-time", {"protocol"}, buckets, "test", "seconds");
THEN("the family object stores the parameters")
{
CHECK_EQ(family.Prefix(), "zeek"sv);
CHECK_EQ(family.Name(), "parse-time"sv);
CHECK_EQ(toVector(family.LabelNames()), std::vector{"protocol"s});
CHECK_EQ(family.Helptext(), "test"sv);
CHECK_EQ(family.Unit(), "seconds"sv);
CHECK_EQ(family.IsSum(), false);
}
AND_THEN("GetOrAdd returns the same metric for the same labels")
{
auto first = family.GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "tcp"}});
CHECK_EQ(first, second);
}
AND_THEN("GetOrAdd returns different metric for the disjoint labels")
{
auto first = family.GetOrAdd({{"protocol", "tcp"}});
auto second = family.GetOrAdd({{"protocol", "udp"}});
CHECK_NE(first, second);
}
}
}
}

View file

@ -10,6 +10,7 @@
#include "zeek/Span.h" #include "zeek/Span.h"
#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"
namespace zeek::telemetry { namespace zeek::telemetry {
@ -143,7 +144,8 @@ public:
auto GaugeFamily(std::string_view prefix, std::string_view name, auto GaugeFamily(std::string_view prefix, std::string_view name,
Span<const std::string_view> labels, Span<const std::string_view> labels,
std::string_view helptext, std::string_view helptext,
std::string_view unit = "1", bool is_sum = false) { std::string_view unit = "1", bool is_sum = false)
{
if constexpr (std::is_same<ValueType, int64_t>::value) if constexpr (std::is_same<ValueType, int64_t>::value)
{ {
return IntGaugeFam(prefix, name, labels, helptext, unit, is_sum); return IntGaugeFam(prefix, name, labels, helptext, unit, is_sum);
@ -228,6 +230,160 @@ public:
return fam.GetOrAdd({}); return fam.GetOrAdd({});
} }
// Forces the compiler to use the type `Span<const T>` instead of trying to
// match paremeters to a `span`.
template <class T>
struct ConstSpanOracle
{
using Type = Span<const T>;
};
// Convenience alias to safe some typing.
template <class T>
using ConstSpan = typename ConstSpanOracle<T>::Type;
/**
* Returns a histogram metric family. Creates the family lazily if
* necessary.
* @param prefix The prefix (namespace) this family belongs to. Usually the
* application or protocol name, e.g., `http`. The prefix `caf`
* as well as prefixes starting with an underscore are
* reserved.
* @param name The human-readable name of the metric, e.g., `requests`.
* @param labels Names for all label dimensions of the metric.
* @param default_upper_bounds Upper bounds for the metric buckets.
* @param helptext Short explanation of the metric.
* @param unit Unit of measurement. Please use base units such as `bytes` or
* `seconds` (prefer lowercase). The pseudo-unit `1` identifies
* dimensionless counts.
* @param is_sum Setting this to `true` indicates that this metric adds
* something up to a total, where only the total value is of
* interest. For example, the total number of HTTP requests.
* @note The first call wins when calling this function multiple times with
* different bucket settings. Users may also override
* @p default_upper_bounds via run-time configuration.
*/
template <class ValueType = int64_t>
auto HistogramFamily(std::string_view prefix, std::string_view name,
Span<const std::string_view> labels,
ConstSpan<ValueType> default_upper_bounds,
std::string_view helptext, std::string_view unit = "1",
bool is_sum = false)
{
if constexpr (std::is_same<ValueType, int64_t>::value)
{
return IntHistoFam(prefix, name, labels, default_upper_bounds,
helptext, unit, is_sum);
}
else
{
static_assert(std::is_same<ValueType, double>::value,
"metrics only support int64_t and double values");
return DblHistoFam(prefix, name, labels, default_upper_bounds,
helptext, unit, is_sum);
}
}
/// @copydoc HistogramFamily
template <class ValueType = int64_t>
auto HistogramFamily(std::string_view prefix, std::string_view name,
std::initializer_list<std::string_view> labels,
ConstSpan<ValueType> default_upper_bounds,
std::string_view helptext, std::string_view unit = "1",
bool is_sum = false)
{
auto lbl_span = Span{labels.begin(), labels.size()};
return HistogramFamily<ValueType>(prefix, name, lbl_span,
default_upper_bounds, helptext,
unit, is_sum);
}
/**
* Returns a histogram. Creates the family lazily if necessary.
* @param prefix The prefix (namespace) this family belongs to. Usually the
* application or protocol name, e.g., `http`. The prefix `caf`
* as well as prefixes starting with an underscore are
* reserved.
* @param name The human-readable name of the metric, e.g., `requests`.
* @param labels Names for all label dimensions of the metric.
* @param default_upper_bounds Upper bounds for the metric buckets.
* @param helptext Short explanation of the metric.
* @param unit Unit of measurement. Please use base units such as `bytes` or
* `seconds` (prefer lowercase). The pseudo-unit `1` identifies
* dimensionless counts.
* @param is_sum Setting this to `true` indicates that this metric adds
* something up to a total, where only the total value is of
* interest. For example, the total number of HTTP requests.
* @note The first call wins when calling this function multiple times with
* different bucket settings. Users may also override
* @p default_upper_bounds via run-time configuration.
*/
template <class ValueType = int64_t>
Histogram<ValueType>
HistogramInstance(std::string_view prefix, std::string_view name,
Span<const LabelView> labels,
ConstSpan<ValueType> default_upper_bounds,
std::string_view helptext, std::string_view unit = "1",
bool is_sum = false)
{
return WithLabelNames(labels, [&, this](auto labelNames)
{
auto family = HistogramFamily<ValueType>(prefix, name, labelNames,
default_upper_bounds,
helptext, unit, is_sum);
return family.getOrAdd(labels);
});
}
/// @copdoc HistogramInstance
template <class ValueType = int64_t>
Histogram<ValueType>
HistogramInstance(std::string_view prefix, std::string_view name,
std::initializer_list<LabelView> labels,
ConstSpan<ValueType> default_upper_bounds,
std::string_view helptext, std::string_view unit = "1",
bool is_sum = false)
{
auto lbls = Span{labels.begin(), labels.size()};
return HistogramInstance(prefix, name, lbls, default_upper_bounds,
helptext, unit, is_sum);
}
/**
* Returns a histogram metric singleton, i.e., the single instance of a
* family without label dimensions. Creates all objects lazily if necessary,
* but fails if the full name already belongs to a different family.
* @param prefix The prefix (namespace) this family belongs to. Usually the
* application or protocol name, e.g., `http`. The prefix `caf`
* as well as prefixes starting with an underscore are
* reserved.
* @param name The human-readable name of the metric, e.g., `requests`.
* @param default_upper_bounds Upper bounds for the metric buckets.
* @param helptext Short explanation of the metric.
* @param unit Unit of measurement. Please use base units such as `bytes` or
* `seconds` (prefer lowercase). The pseudo-unit `1` identifies
* dimensionless counts.
* @param is_sum Setting this to `true` indicates that this metric adds
* something up to a total, where only the total value is of
* interest. For example, the total number of HTTP requests.
* @note The first call wins when calling this function multiple times with
* different bucket settings. Users may also override
* @p default_upper_bounds via run-time configuration.
*/
template <class ValueType = int64_t>
Histogram<ValueType>
HistogramSingleton(std::string_view prefix, std::string_view name,
ConstSpan<ValueType> default_upper_bounds,
std::string_view helptext, std::string_view unit = "1",
bool is_sum = false)
{
auto lbls = Span<const std::string_view>{};
auto fam = HistogramFamily<ValueType>(prefix, name, lbls,
default_upper_bounds, helptext,
unit, is_sum);
return fam.GetOrAdd({});
}
private: private:
IntCounterFamily IntCounterFamily
IntCounterFam(std::string_view prefix, std::string_view name, IntCounterFam(std::string_view prefix, std::string_view name,
@ -253,6 +409,18 @@ private:
std::string_view helptext, std::string_view unit, std::string_view helptext, std::string_view unit,
bool is_sum); bool is_sum);
IntHistogramFamily
IntHistoFam(std::string_view prefix, std::string_view name,
Span<const std::string_view> labels,
Span<const int64_t> ubounds, std::string_view helptext,
std::string_view unit, bool is_sum);
DblHistogramFamily
DblHistoFam(std::string_view prefix, std::string_view name,
Span<const std::string_view> labels,
Span<const double> ubounds, std::string_view helptext,
std::string_view unit, bool is_sum);
template <class F> template <class F>
static void WithLabelNames(Span<const LabelView> xs, F continuation) static void WithLabelNames(Span<const LabelView> xs, F continuation)
{ {
@ -276,8 +444,6 @@ private:
namespace zeek { namespace zeek {
// @note for technically reasons (CAF dependency), this variable gets
// initialized in broker/Manager.cc.
extern telemetry::Manager* telemetry_mgr; extern telemetry::Manager* telemetry_mgr;
} // namespace zeek } // namespace zeek