Four programs,
as they ran.
The source on the left is the whole file. The output on the right is what this library printed when that file was run — not a sketch of what it might print.
- Programs
- five
- Output
- captured, not written
- Run
- moon run src/examples/…
A counter.
src/examples/04-counter/main.mbt
moon run src/examples/04-counter
// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A counter only goes up, so the only way to read it is to scrape it twice. This
/// program prints one scrape.
fn main raise {
let registry = @metric.Registry::new()
let jobs = registry.counter("jobs_processed", "Jobs finished.")
jobs.inc()
jobs.inc(by=11.0)
println(@expo.text(registry.collect()))
}
output
The registered name was jobs_processed; the wire name
carries _total, because that is what a counter is called
in the exposition format.
A gauge.
src/examples/05-gauge/main.mbt
moon run src/examples/05-gauge
// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A gauge is the number at the moment of the scrape: it can go down, and it can be
/// taken from a function instead of stored.
fn main raise {
let registry = @metric.Registry::new()
let running = registry.gauge("jobs_running", "Jobs running now.")
running.set(3.0)
running.dec()
println(@expo.text(registry.collect()))
}
output
One gauge, one line. A gauge is the only kind whose name is written exactly as it was registered — no suffix, no rewrite.
A histogram.
src/examples/06-histogram/main.mbt
moon run src/examples/06-histogram
// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Two observations, counted into the fourteen bounds the official clients share.
/// The buckets are cumulative on the wire: `le="0.5"` counts everything at or below
/// half a second.
fn main raise {
let registry = @metric.Registry::new()
let latency = registry.histogram(
"request_duration_seconds",
"Request latency.",
)
latency.observe(0.041)
latency.observe(0.317)
println(@expo.text(registry.collect()))
}
output
The first observation, 41 ms, crosses the first four steps at once, so
the count jumps at le="0.05". The second, 317 ms, only
moves the steps above 0.25.
Both formats.
src/examples/07-formats/main.mbt
moon run src/examples/07-formats
// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// One snapshot, two formats. The counts are the same object; only the spelling of
/// the timestamp and the ending differ.
fn main raise {
let registry = @metric.Registry::new()
let latency = registry.histogram(
"request_duration_seconds",
"Request latency.",
)
latency.observe(0.041)
latency.observe(0.317)
let families = registry.collect(at=1_700_000_015.0)
println("=== 0.0.4 ===")
println(@expo.text(families))
println("=== 1.0 ===")
println(@expo.openmetrics(families))
}
output · the tail of each, where they differ
Same three numbers, twice. The first format counts in milliseconds and
says nothing at the end; the second counts in seconds and closes with
# EOF, which is how a reader knows the body is whole.
Three workers.
src/examples/09-multiprocess/main.mbt
moon run src/examples/09-multiprocess
///|
/// The work one worker did. The numbers differ per process so the merge has
/// something to add up.
fn registry_for(pid : Int) -> @metric.Registry raise @metric.Refused {
let registry = @metric.Registry::new(
prefix="p8s",
labels=[("service", "checkout")],
)
let orders = registry.counter("orders", "Orders placed.")
// ... a labelled counter, a histogram, a summary, a gauge
orders.inc(by=(pid * 10).to_double())
registry
}
///|
async fn flush(dir : String, pid : Int) -> Unit raise @metric.Refused {
let registry = registry_for(pid)
let written = @directory.write(
dir, pid, registry.collect(),
modes=[("p8s_queue_depth", @multiproc.Mode::Sum)],
)
println("worker " + pid.to_string() + ": "
+ written.files.to_string() + " files")
}
output · three files each, then one scrape
Ten orders, twenty, thirty: the counter adds them up. The queue depth
was three, six and nine, and Mode::Sum answers eighteen,
which is what the pool as a whole is holding. Six requests went
through three processes and the histogram kept one set of edges.