moonbitstack/moontel/meter does not have a README file

    Refused

    pub(all) suberror Refused {
    EmptyName
    InvalidName(name~ : String)
    LongName(name~ : String, got~ : Int, limit~ : Int)
    Bounds(name~ : String, at~ : Int)
    Conflict(name~ : String, got~ : InstrumentKind, want~ : InstrumentKind)
    } derive(Eq,
    Debug
    )

    Why a name or a bucket list was refused.

    Refused::equal

    fn Refused::equal(Refused, Refused) -> Bool

    Refused::not_equal

    fn Refused::not_equal(x : Refused, y : Refused) -> Bool

    Refused::to_repr

    Aggregation

    pub(all) enum Aggregation {
    Sum
    LastValue
    ExplicitBuckets(bounds~ : Array[Double])
    ExponentialBuckets(scale~ : Int, max_buckets~ : Int)
    Drop
    } derive(Eq,
    Debug
    )

    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.

    Aggregation::equal

    fn Aggregation::equal(Aggregation, Aggregation) -> Bool

    Aggregation::not_equal

    fn Aggregation::not_equal(x : Aggregation, y : Aggregation) -> Bool

    Attributes

    pub(all) struct Attributes {
    entries : Array[(String,
    Value
    )]
    } derive(Eq,
    Debug
    )

    The attributes a measurement carries, kept as a set rather than a list.

    A series is identified by its attribute set, so two writes that name the same facts in a different order must land in one series. Keys are sorted, a key written twice keeps the last value, and the identity string is length-prefixed so a key containing a separator cannot make two different sets collide.

    Dots in a key are ordinary characters: gen_ai.request.model travels unchanged, as the semantic conventions spell it. Nothing here rewrites a name into the underscores an exposition format would need.

    Attributes::equal

    fn Attributes::equal(Attributes, Attributes) -> Bool

    Attributes::get

    The value under key.

    Attributes::identity

    fn Attributes::identity(self : Attributes) -> String

    A string that is equal for equal attribute sets and different otherwise.

    Used as the key of a series, so it has to be unambiguous: each key and value carries its length, and the type of the value is part of the text — a string "12" and the integer 12 are two different dimensions.

    Attributes::len

    fn Attributes::len(self : Attributes) -> Int

    How many attributes the set holds.

    Attributes::new

    An attribute set built from these entries.

    Attributes::not_equal

    fn Attributes::not_equal(x : Attributes, y : Attributes) -> Bool

    Attributes::to_array

    The entries, sorted by key.

    Attributes::with_attribute

    fn Attributes::with_attribute(self : Attributes, key : String, value :
    Value
    ) -> Attributes

    The same set with key set to value, replacing an earlier value under that key. Returns a new value, so a set a caller shares with several instruments cannot change underneath one of them.

    Counter

    pub struct Counter {
    provider : MeterProvider
    index : Int
    }

    A monotonic sum: add may only go up.

    Counter::add

    fn Counter::add(self : Counter, value : Double, attributes? : Attributes, at? : Int64, context? :
    TraceContext
    ) -> Unit

    Add a non-negative amount.

    A negative or non-finite amount is ignored: the specification says a counter's measurements are non-negative, and a value that cannot be written to the wire is not a value to accumulate. No error is raised on a hot measurement path.

    Counter::inc

    fn Counter::inc(self : Counter, attributes? : Attributes, at? : Int64, context? :
    TraceContext
    ) -> Unit

    Add one.

    ExemplarFilter

    pub(all) enum ExemplarFilter {
    AlwaysOn
    AlwaysOff
    TraceBased
    } derive(Eq,
    Debug
    )

    Which measurements are kept as exemplars.

    An exemplar is a sample of what a number was made of: the trace id and span id of one request that landed in that bucket, so a reader can jump from "the ninth bucket doubled" to the request behind it.

    The default is TraceBased, which is the metrics SDK's default and the only filter that keeps the promise the jump makes: an exemplar from a trace that was not sampled names a span no backend holds, and one with no context at all names nothing.

    ExemplarFilter::equal

    ExemplarFilter::not_equal

    fn ExemplarFilter::not_equal(x : ExemplarFilter, y : ExemplarFilter) -> Bool

    ExponentialHistogram

    pub struct ExponentialHistogram {
    provider : MeterProvider
    index : Int
    }

    A distribution whose buckets adapt to the measurements.

    ExponentialHistogram::record

    fn ExponentialHistogram::record(self : ExponentialHistogram, value : Double, attributes? : Attributes, at? : Int64, context? :
    TraceContext
    ) -> Unit

    Record one measurement into the exponential bucket that contains it.

    A bucket grows with the value it holds, so a millisecond and a minute are both resolved finely with one set of buckets; when the measurements spread out enough to need more than max_buckets, neighbouring buckets merge and the scale drops. The scale a point was written at is on the point, so a reader never has to guess it.

    Gauge

    pub struct Gauge {
    provider : MeterProvider
    index : Int
    }

    The last value set, rather than a sum of values.

    Gauge::set

    fn Gauge::set(self : Gauge, value : Double, attributes? : Attributes, at? : Int64, context? :
    TraceContext
    ) -> Unit

    Replace the value of this series. A gauge keeps the most recent measurement; two writes to the same attributes do not add up.

    Histogram

    pub struct Histogram {
    provider : MeterProvider
    index : Int
    }

    A distribution over explicit buckets.

    Histogram::record

    fn Histogram::record(self : Histogram, value : Double, attributes? : Attributes, at? : Int64, context? :
    TraceContext
    ) -> Unit

    Record one measurement into the bucket whose upper bound contains it.

    The point also keeps sum, count, min and max, which the OTLP histogram data point carries so a backend can compute a mean or show the range without guessing from the buckets.

    Instrument

    pub struct Instrument {
    name : String
    description : String
    unit : String
    kind : InstrumentKind
    bounds : Array[Double]
    max_buckets : Int
    limits : MeterLimits
    temporality :
    Temporality

    exemplar : ExemplarFilter
    current : () ->
    TraceContext
    ?
    now : () -> Int64
    start : Int64
    observe : (Observer) -> Unit?
    streams : Array[Stream]
    dropped :
    Ref
    [Int]
    }

    What every stream of one name shares.

    InstrumentKind

    pub(all) enum InstrumentKind {
    Counter
    UpDownCounter
    Histogram
    Gauge
    ObservableCounter
    ObservableUpDownCounter
    ObservableGauge
    ExponentialHistogram
    } derive(Eq,
    Debug
    )

    Which instrument a name belongs to.

    The first four are written to as the program runs. The three Observable ones are read when the metrics are collected: the program registers a callback, and the callback reports the values that are true at that moment. An exponential histogram is a histogram whose buckets adapt to the measurements rather than being fixed.

    The OTLP exporter has its own kinds because a reader sees a sum, a gauge, a histogram or a summary; this one says what the producer is allowed to do. A counter and an up-down counter are both sums on the wire, but only one of them may go down.

    InstrumentKind::equal

    InstrumentKind::not_equal

    fn InstrumentKind::not_equal(x : InstrumentKind, y : InstrumentKind) -> Bool

    Meter

    pub struct Meter {
    provider : MeterProvider
    scope :
    Scope

    }

    A meter naming the instrumentation scope its instruments belong to.

    Several parts of a program can each have a meter. The scope is a local identity — scope reads it back — while the name an exporter puts on the wire is the exporter's own scope~; a program that needs two scope names on the wire makes two providers and two exporters.

    Meter::counter

    fn Meter::counter(self : Meter, name : String, description? : String, unit? : String) -> Counter raise Refused

    A monotonic sum, written as the program runs. add with a negative amount is ignored.

    Meter::exponential_histogram

    fn Meter::exponential_histogram(self : Meter, name : String, description? : String, unit? : String, scale? : Int, max_buckets? : Int) -> ExponentialHistogram raise Refused

    A distribution whose buckets follow the measurements.

    scale is where it starts — 20 is the finest the SDK defines, and each step down halves the resolution and doubles what one bucket covers — and max_buckets is how many buckets it may use before the scale drops, 160 by default. The point carries the scale it ended at, so a reader never has to guess what a bucket index means.

    Meter::gauge

    fn Meter::gauge(self : Meter, name : String, description? : String, unit? : String) -> Gauge raise Refused

    The most recent value of a series, written as the program runs.

    Meter::histogram

    fn Meter::histogram(self : Meter, name : String, description? : String, unit? : String, bounds? : Array[Double]) -> Histogram raise Refused

    A distribution over bounds, which default to the OpenTelemetry SDK's explicit bucket boundaries. The unit on the instrument is the unit of every boundary and of every recorded value.

    Meter::observable_counter

    fn Meter::observable_counter(self : Meter, name : String, description? : String, unit? : String, observe~ : (Observer) -> Unit) -> ObservableCounter raise Refused

    A monotonic sum whose value is reported when the metrics are collected.

    The callback runs once per collection and reports the totals that are true at that moment. A cumulative reader sees the total; a delta reader sees what changed since the last export.

    Meter::observable_gauge

    fn Meter::observable_gauge(self : Meter, name : String, description? : String, unit? : String, observe~ : (Observer) -> Unit) -> ObservableGauge raise Refused

    A value reported when the metrics are collected, such as a queue depth or a temperature: a number that is what it is rather than a total of anything.

    Meter::observable_up_down_counter

    fn Meter::observable_up_down_counter(self : Meter, name : String, description? : String, unit? : String, observe~ : (Observer) -> Unit) -> ObservableUpDownCounter raise Refused

    A sum either sign of which is reported when the metrics are collected.

    Meter::scope

    The scope this meter names.

    Meter::up_down_counter

    fn Meter::up_down_counter(self : Meter, name : String, description? : String, unit? : String) -> UpDownCounter raise Refused

    A sum that may go up or down, written as the program runs.

    MeterLimits

    pub(all) struct MeterLimits {
    attributes : Int
    attribute_value_length : Int?
    series : Int
    exemplars : Int
    } derive(Eq,
    Debug
    )

    How much one instrument keeps.

    The attribute limits are OpenTelemetry's own: 128 attributes on a point, and no limit on the length of a string value. series is the SDK's default cardinality limit of 2000 per instrument, and exemplars is how many sampled traces are kept beside a series. All four exist so a loop over user data cannot make one instrument unbounded.

    MeterLimits::equal

    fn MeterLimits::equal(MeterLimits, MeterLimits) -> Bool

    MeterLimits::new

    fn MeterLimits::new(attributes? : Int, attribute_value_length? : Int?, series? : Int, exemplars? : Int) -> MeterLimits

    The same limits, with any of them changed.

    MeterLimits::not_equal

    fn MeterLimits::not_equal(x : MeterLimits, y : MeterLimits) -> Bool

    MeterProvider

    pub struct MeterProvider {
    sink : &
    MetricSink

    now : () -> Int64
    temporality :
    Temporality

    limits : MeterLimits
    exemplar : ExemplarFilter
    current : () ->
    TraceContext
    ?
    views : Array[View]
    start : Int64
    instruments :
    Ref
    [Array[Instrument]]
    }

    Where instruments are registered and where their accumulations are collected.

    The provider holds no resource and no scope of its own: those belong to the exporter, which is the same object a program already hands its spans and logs. Metrics therefore travel under the same resource, the same transport, the same batching and the same retry policy as the other two signals.

    It reads no clock. now is the caller's, and collect is given the instant the points are dated with, so a replay can produce the timeline it chooses.

    MeterProvider::collect

    The metrics as they stand, dated at, in registration order.

    An observable instrument's callback is run first, because its value is defined as what the callback says at that moment; every instrument then contributes one metric per stream. Collection does not clear a cumulative reader's numbers, so the same points can be read again or handed to more than one sink. emit is the call that also writes them to the sink.

    MeterProvider::dropped

    fn MeterProvider::dropped(self : MeterProvider) -> Int

    How many measurements were dropped because a cardinality limit was reached.

    MeterProvider::emit

    Collect and hand every metric to the sink.

    With delta temporality the accumulations are cleared afterwards, so one measurement is never counted in two consecutive exports. With cumulative temporality they are kept, and a restart of the process is what resets them.

    MeterProvider::exemplar_filter

    fn MeterProvider::exemplar_filter(self : MeterProvider) -> ExemplarFilter

    Which measurements this provider keeps as exemplars.

    MeterProvider::len

    fn MeterProvider::len(self : MeterProvider) -> Int

    How many instruments are registered.

    MeterProvider::limits

    The limits this provider's instruments were built with.

    MeterProvider::meter

    fn MeterProvider::meter(self : MeterProvider, name~ : String, version? : String, attributes? : Array[(String,
    Value
    )]) -> Meter

    The meter for one library or one part of a program.

    MeterProvider::new

    A provider writing finished metrics to sink, timed by now.

    temporality is the reader's choice: cumulative is what the OTLP exporters of most backends want, delta is what a Prometheus-style scraper wants. start is when the process began accumulating, in nanoseconds; it defaults to the first reading of now.

    views is how a program decides what the metrics look like without touching the call sites — a name, a description, which attributes survive, how the measurements are aggregated, or that an instrument is not wanted at all. exemplar says which measurements are worth keeping beside a bucket, and current says which span a measurement belongs to when the call site does not name one: hand it the span the request is in, and an exemplar points at that request.

    MeterProvider::temporality

    Which temporality this provider exports.

    MeterProvider::views

    fn MeterProvider::views(self : MeterProvider) -> Array[View]

    The views this provider applies to the instruments registered with it.

    ObservableCounter

    pub struct ObservableCounter {
    provider : MeterProvider
    index : Int
    }

    A monotonic sum whose value is reported when the metrics are collected.

    ObservableGauge

    pub struct ObservableGauge {
    provider : MeterProvider
    index : Int
    }

    A value reported when the metrics are collected.

    ObservableUpDownCounter

    pub struct ObservableUpDownCounter {
    provider : MeterProvider
    index : Int
    }

    A sum either sign of which is reported when the metrics are collected.

    Observer

    pub struct Observer {
    provider : MeterProvider
    index : Int
    }

    What an observable instrument's callback writes through.

    The observer is handed to the callback at the moment of collection and is only valid then: it writes into the series that collection is reading, so a value reported while nothing is being collected would have nowhere to go. Keeping it a value passed in — rather than a global the callback has to find — is what makes two providers in one process independent.

    Observer::observe

    fn Observer::observe(self : Observer, value : Double, attributes? : Attributes, at? : Int64, context? :
    TraceContext
    ) -> Unit

    Report the value of this series as it stands now.

    The callback of an observable counter runs at each collection, and what it observes replaces what was observed before: the value is a total, not an increment. A delta reader is given the difference between two collections, which is what makes an observed total usable by a scraper that wants per-interval numbers.

    Series

    pub struct Series {
    attributes : Attributes
    key : String
    start : Int64
    point :
    Point

    exemplars : Array[
    Exemplar
    ]
    reported : Double?
    }

    One series of one stream: its attribute set, when it began, and the accumulated point that will be exported.

    The point is the OTLP shape itself. Accumulating it in place means collection has nothing to translate, and the JSON and protobuf encoders see the same numbers a hand-built metric would carry.

    Stream

    pub struct Stream {
    name : String
    description : String
    aggregation : Aggregation
    attribute_keys : Array[String]?
    series : Array[Series]
    }

    One way of seeing an instrument: its name, its aggregation, which attributes survive, and the series written under it.

    An instrument with no view over it has one stream, which is the instrument as it was registered. A view that renames it, aggregates it differently or drops some attributes adds a stream of its own, so the same measurement can reach a backend twice — once coarse and once fine — without being recorded twice.

    UpDownCounter

    pub struct UpDownCounter {
    provider : MeterProvider
    index : Int
    }

    A sum that may go up or down.

    UpDownCounter::add

    fn UpDownCounter::add(self : UpDownCounter, value : Double, attributes? : Attributes, at? : Int64, context? :
    TraceContext
    ) -> Unit

    Add a positive or negative amount. Only non-finite values are ignored.

    View

    pub(all) struct View {
    instrument : String
    kind : InstrumentKind?
    name : String?
    description : String?
    attribute_keys : Array[String]?
    aggregation : Aggregation?
    } derive(Eq,
    Debug
    )

    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.

    View::equal

    fn View::equal(View, View) -> Bool

    View::new

    fn View::new(instrument? : String, kind? : InstrumentKind, name? : String, description? : String, attribute_keys? : Array[String], aggregation? : Aggregation) -> View

    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.

    View::not_equal

    fn View::not_equal(x : View, y : View) -> Bool

    View::to_repr

    bucket_index

    fn bucket_index(bounds : Array[Double], value : Double) -> Int

    Which bucket value falls into.

    A bucket is inclusive of its upper bound, so the answer is the first boundary greater than or equal to the measurement. When none is, the answer is one past the last boundary: OTLP wants one more count than there are bounds, and that last count is the +Inf bucket.

    default_buckets

    let default_buckets : Array[Double]

    The default explicit bucket boundaries of the OpenTelemetry Metrics SDK: [0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000].

    Source: the Metrics SDK specification, "Explicit Bucket Histogram Aggregation", the Boundaries row of the aggregation's parameter table: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#explicit-bucket-histogram-aggregation

    The specification gives the intervals as (-Inf, 0], (0, 5], (5, 10], ... up to (10000, +Inf), notes that the values were chosen from the default buckets of the Prometheus clients, and says an SDK SHOULD use them when the caller does not supply boundaries. These are plain numbers: the unit is the instrument's own, so a histogram registered with s measures seconds and one registered with ms measures milliseconds.

    empty_attributes

    let empty_attributes : Attributes

    The empty attribute set: the series with no dimensions.

    empty_counts

    fn empty_counts(bounds : Array[Double]) -> Array[Int64]

    An empty set of bucket counts: one per finite boundary, plus the overflow.

    exponential_index

    fn exponential_index(value : Double, scale : Int) -> Int

    The bucket a positive measurement belongs to, at this scale.

    Bucket i covers (2^(i/2^scale), 2^((i+1)/2^scale)]: the upper bound is part of the bucket, as it is for an explicit boundary, so a value that lands exactly on a boundary is counted in the lower bucket rather than the upper one.

    gen_ai_client_attributes

    fn gen_ai_client_attributes(provider~ : String, operation~ : String, request_model~ : String, response_model? : String, agent_name? : String, conversation_id? : String) -> Attributes

    The attribute group for one GenAI client operation.

    The three required names come from the metric's attribute group; the optional ones are the parts a caller knows only after the response arrives. Every key is a constant from semconv, so a rename upstream moves the pin and the generated constants rather than a string in instrumented code.

    gen_ai_metric_keys

    let gen_ai_metric_keys : Array[String]

    The attribute keys the GenAI metric conventions attach to a client operation. A dashboard matches on these spellings; writing them by hand is how a near miss becomes a value no dashboard shows.

    instrument_name_limit

    let instrument_name_limit : Int

    The longest instrument name the metrics specification allows.

    is_valid_name

    fn is_valid_name(name : String) -> Bool

    Whether name is an instrument name the specification allows.

    max_exponential_buckets

    let max_exponential_buckets : Int

    The most buckets one exponential histogram may use.

    Source: the OpenTelemetry Metrics SDK specification, "Exponential Histogram Aggregations", the MaxSize row of the aggregation's parameter table, which states the default as 160: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exponential-bucket-histogram-aggregation

    A measurement whose bucket is not among the ones in use takes a new one; when the count would pass this limit the buckets are merged in pairs and scale drops by one, which is what keeps the memory one instrument may take bounded without giving up resolution where the measurements actually are.

    max_exponential_scale

    let max_exponential_scale : Int

    The finest scale the SDK defines: a bucket spans a ratio of 2^(2^-20), about 1.00000095.

    meter_limits

    let meter_limits : MeterLimits

    The OpenTelemetry defaults, from the metrics SDK specification's attribute limits and cardinality limit sections.

    min_exponential_scale

    let min_exponential_scale : Int

    The coarsest scale the SDK defines. At -10 a bucket spans a ratio of 2^1024, which is wider than the range of a double, so the whole positive range is one bucket.