Malachi. Histogram
(malachi v0.22.7)
View Source
A lock-free latency histogram: an :atomics array of log-spaced buckets that many processes write to
concurrently, with no per-op allocation and no shared GenServer. The load generator records request
latency in it, and Malachi.Metrics records storage flush latency in it.
Latencies are recorded in microseconds. Bucket b covers (2^((b-1)/scale), 2^(b/scale)] us, so the
resolution is 2^(1/scale) - 1 (about 4.4% at scale = 16), fine enough for tail percentiles while
keeping the array tiny. Percentiles return the representative us of the bucket the rank falls in.
Writers pay two :atomics.add/3 calls on the caller (the bucket and the running sum), which is why this
is safe on a hot path: routing the samples through a GenServer would serialize every writer behind one
process instead.
cumulative/1 exports the counts at a coarser, fixed set of edges (edges/0, four per octave), which is
what a Prometheus histogram needs: a stable le set whose counts can be subtracted between two scrapes.
Every edge is the upper bound of an internal bucket, so those counts are exact rather than interpolated.
A bucket includes its upper bound, so a count is of samples at or below its edge, which is what a
Prometheus le means; the only integer samples that can equal an edge are exact powers of two.
Summary
Functions
Total number of samples recorded.
The number of samples at or below each of edges/0, as [{edge_us, count}] in ascending order,
and the total count, all read in one pass so they agree with each other while writers keep adding (the
total is never below the last edge's count). Samples above the last edge are only in the total.
The exported edges in microseconds, ascending: four per octave from 8us to about 16.8s.
The p-th percentile in microseconds (p in 0..100), or 0.0 if empty. Walks the cumulative counts
and returns the representative us of the bucket where the rank lands.
Records one latency sample (microseconds).
Sum of every recorded sample, in microseconds (non-positive samples add 0, fractions truncate).
Types
Functions
@spec count(t()) :: non_neg_integer()
Total number of samples recorded.
@spec cumulative(t()) :: {[{float(), non_neg_integer()}], non_neg_integer()}
The number of samples at or below each of edges/0, as [{edge_us, count}] in ascending order,
and the total count, all read in one pass so they agree with each other while writers keep adding (the
total is never below the last edge's count). Samples above the last edge are only in the total.
@spec edges() :: [float()]
The exported edges in microseconds, ascending: four per octave from 8us to about 16.8s.
@spec new() :: t()
The p-th percentile in microseconds (p in 0..100), or 0.0 if empty. Walks the cumulative counts
and returns the representative us of the bucket where the rank lands.
Records one latency sample (microseconds).
@spec sum(t()) :: non_neg_integer()
Sum of every recorded sample, in microseconds (non-positive samples add 0, fractions truncate).