// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// How a stream turns measurements into a point.
///
/// The aggregation is chosen by the instrument unless a view overrides it, which is
/// what lets one program export a duration both as an explicit-bucket histogram and
/// as an exponential one without recording it twice.
#derive(Eq, Debug)
pub(all) enum Aggregation {
/// A running total. A monotonic sum only grows; a non-monotonic one takes either
/// sign.
Sum
/// The most recent measurement.
LastValue
/// A distribution over these boundaries, the last count being everything above
/// the last bound.
ExplicitBuckets(bounds~ : Array[Double])
/// A base-2 distribution whose buckets split and merge so that no more than
/// `max_buckets` are in use. `scale` is where it starts: the finer the scale, the
/// more buckets a given range of measurements takes.
ExponentialBuckets(scale~ : Int32, max_buckets~ : Int32)
/// Nothing: the instrument is registered and its measurements are thrown away.
Drop
}
///|
pub extend Aggregation with Eq::{equal, not_equal}
///|
pub extend Aggregation with Debug::{to_repr}
///|
/// What a view does to the instruments it selects.
///
/// A view is how a program decides what a metric looks like without touching the
/// call sites: the name on the wire, the description, which attributes survive, and
/// how the measurements are aggregated. One instrument can be seen through several
/// views, and each view that is not `Drop` is a stream of its own on the wire.
///
/// `instrument` is the instrument's name, or `*` for every instrument. Matching is
/// case-insensitive, as instrument names are.
#derive(Eq, Debug)
pub(all) struct View {
instrument : String
kind : InstrumentKind?
name : String?
description : String?
attribute_keys : Array[String]?
aggregation : Aggregation?
}
///|
pub extend View with Eq::{equal, not_equal}
///|
pub extend View with Debug::{to_repr}
///|
/// A view over one instrument, or over every instrument.
///
/// `kind` widens the selection to a type of instrument, so one view can catch the
/// four sums a library registers without naming each. `attribute_keys` is an
/// allowlist: the keys named survive, the rest are dropped before the series are
/// formed, which is how a program keeps a high-cardinality key such as a request id
/// off the wire. `Some([])` keeps no attribute at all.
pub fn View::new(
instrument~ : String = "*",
kind? : InstrumentKind,
name? : String,
description? : String,
attribute_keys? : Array[String],
aggregation? : Aggregation,
) -> View {
{ instrument, kind, name, description, attribute_keys, aggregation, }
}
///|
/// Whether this view selects an instrument of that name and type.
fn View::selects(self : View, name : String, kind : InstrumentKind) -> Bool {
let named = self.instrument == "*" ||
self.instrument.to_lower() == name.to_lower()
if !named {
return false
}
match self.kind {
Some(wanted) => wanted == kind
None => true
}
}
///|
/// The default aggregation of an instrument kind, which is what a stream uses when
/// no view says otherwise.
fn default_aggregation(
kind : InstrumentKind,
bounds : Array[Double],
max_buckets : Int32,
) -> Aggregation {
match kind {
Counter | UpDownCounter | ObservableCounter | ObservableUpDownCounter => Sum
Gauge | ObservableGauge => LastValue
Histogram => ExplicitBuckets(bounds~)
ExponentialHistogram =>
ExponentialBuckets(scale=max_exponential_scale, max_buckets~)
}
}