Files
rippled/src/libxrpl/beast/insight/NullCollector.cpp
Pratik Mankawde 76c9051203 feat(insight): let an Event declare what it measures
beast::insight::Event documents itself as carrying "a millisecond time, or
other integral value", but both backends assumed the first case: the OTel
bridge declared every instrument with unit `ms` and StatsD tagged every
sample `|ms`. One Event does not measure time -- ServerHandler's "size"
records the serialized RPC response length -- so it exported as
rpc_size_milliseconds and inherited the millisecond bucket ladder. A quarter
of its samples landed above that ladder's top edge, and since Prometheus
returns the second-highest edge for a quantile in the `+Inf` bucket, its p95
panel showed a flat 5.00 kB rather than a measurement.

Adds beast::insight::Unit (Millis, Bytes) plus otelUnitCode(), carried on
EventImpl and selectable at makeEvent(). Naming the unit at creation is what
lets a backend pick the export unit and, through it, the bucket ladder.

- Collector gains a virtual makeEvent(name, Unit) whose default delegates to
  the millisecond overload, so a collector that cannot act on a unit keeps
  working unchanged. NullCollector and the Groups wrapper override it.
- The Groups override matters most: call sites reach a collector through a
  Group, so forwarding only the prefixed name would silently drop the unit.
  A test covers that hop specifically.
- Event gains notify(std::uint64_t) for non-duration samples, replacing
  ServerHandler's `Event::value_type{response.size()}` -- wrapping a byte
  count in a std::chrono::milliseconds compiles but reads as a duration to
  everything downstream.
- EventImpl::value_type stays std::chrono::milliseconds. Widening it would
  change the wire value of every existing StatsD timer, and metrics needing
  finer resolution use the OTel-native microsecond instruments.

The StatsD collector deliberately keeps emitting `|ms`: that path is retired
here (its UDP port is commented out of the compose file and the integration
test fails if anything listens on 8125), so changing its wire format would
alter a legacy contract with no consumer and no way to verify it.

The exported name does not change yet -- OTelEventImpl still hardcodes its
unit. That follows with the unit-keyed histogram views.
2026-08-21 12:11:32 +01:00

169 lines
3.6 KiB
C++

#include <xrpl/beast/insight/NullCollector.h>
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/beast/insight/Counter.h>
#include <xrpl/beast/insight/CounterImpl.h>
#include <xrpl/beast/insight/Event.h>
#include <xrpl/beast/insight/EventImpl.h>
#include <xrpl/beast/insight/Gauge.h>
#include <xrpl/beast/insight/GaugeImpl.h>
#include <xrpl/beast/insight/Hook.h>
#include <xrpl/beast/insight/HookImpl.h>
#include <xrpl/beast/insight/Meter.h>
#include <xrpl/beast/insight/MeterImpl.h>
#include <xrpl/beast/insight/Unit.h>
#include <memory>
#include <string>
namespace beast::insight {
namespace detail {
class NullHookImpl : public HookImpl
{
public:
explicit NullHookImpl() = default;
NullHookImpl&
operator=(NullHookImpl const&) = delete;
};
//------------------------------------------------------------------------------
class NullCounterImpl : public CounterImpl
{
public:
explicit NullCounterImpl() = default;
void
increment(value_type) override
{
}
NullCounterImpl&
operator=(NullCounterImpl const&) = delete;
};
//------------------------------------------------------------------------------
class NullEventImpl : public EventImpl
{
public:
/**
* @param unit What the samples would measure. Recorded even though
* nothing is collected, so a caller can still read back the
* unit it asked for -- which is what makes the null collector
* usable for testing the unit plumbing.
*/
explicit NullEventImpl(Unit unit = Unit::Millis) : EventImpl(unit)
{
}
void
notify(value_type const&) override
{
}
NullEventImpl&
operator=(NullEventImpl const&) = delete;
};
//------------------------------------------------------------------------------
class NullGaugeImpl : public GaugeImpl
{
public:
explicit NullGaugeImpl() = default;
void
set(value_type) override
{
}
void
increment(difference_type) override
{
}
NullGaugeImpl&
operator=(NullGaugeImpl const&) = delete;
};
//------------------------------------------------------------------------------
class NullMeterImpl : public MeterImpl
{
public:
explicit NullMeterImpl() = default;
void
increment(value_type) override
{
}
NullMeterImpl&
operator=(NullMeterImpl const&) = delete;
};
//------------------------------------------------------------------------------
class NullCollectorImp : public NullCollector
{
private:
public:
NullCollectorImp() = default;
~NullCollectorImp() override = default;
Hook
makeHook(HookImpl::HandlerType const&) override
{
return Hook(std::make_shared<detail::NullHookImpl>());
}
Counter
makeCounter(std::string const&) override
{
return Counter(std::make_shared<detail::NullCounterImpl>());
}
using Collector::makeEvent;
Event
makeEvent(std::string const&) override
{
return Event(std::make_shared<detail::NullEventImpl>());
}
Event
makeEvent(std::string const&, Unit unit) override
{
return Event(std::make_shared<detail::NullEventImpl>(unit));
}
Gauge
makeGauge(std::string const&) override
{
return Gauge(std::make_shared<detail::NullGaugeImpl>());
}
Meter
makeMeter(std::string const&) override
{
return Meter(std::make_shared<detail::NullMeterImpl>());
}
};
} // namespace detail
//------------------------------------------------------------------------------
std::shared_ptr<Collector>
NullCollector::make()
{
return std::make_shared<detail::NullCollectorImp>();
}
} // namespace beast::insight