use std::time::{Duration, Instant};
use opentelemetry::metrics::{Histogram, Meter};
const SCHEDULER_PROBE_INTERVAL: Duration = Duration::from_secs(5);
const METER_NAME: &str = "otel-bootstrap.runtime";
pub(crate) fn install() {
let meter = opentelemetry::global::meter(METER_NAME);
let started = Instant::now();
meter
.f64_observable_gauge("process.uptime")
.with_unit("s")
.with_description(
"Seconds since telemetry init. Lets any other series be correlated \
against process age — the shape that distinguishes a leak that a \
restart resets from a genuine load change.",
)
.with_callback(move |o| o.observe(uptime_seconds(started), &[]))
.build();
meter
.u64_observable_gauge("process.memory.resident")
.with_unit("By")
.with_description(
"Resident set size. Read from /proc/self/statm on Linux; not \
reported on other platforms. Useful where no container-level \
metrics agent is deployed.",
)
.with_callback(|o| {
if let Some(rss) = resident_memory_bytes() {
o.observe(rss, &[]);
}
})
.build();
install_tokio_gauges(&meter);
spawn_scheduler_probe(&meter);
}
fn install_tokio_gauges(meter: &Meter) {
let Ok(handle) = tokio::runtime::Handle::try_current() else {
return;
};
let h = handle.clone();
meter
.u64_observable_gauge("runtime.tokio.workers")
.with_description(
"Tokio worker threads. Derived from available_parallelism, which \
honours the cgroup CPU quota — a sub-1-core limit yields a single \
worker, so any blocking call serialises the whole process.",
)
.with_callback(move |o| o.observe(tokio_workers(&h), &[]))
.build();
let h = handle.clone();
meter
.u64_observable_gauge("runtime.tokio.alive_tasks")
.with_description(
"Live Tokio tasks. A monotonic climb over a process's lifetime is \
a task leak; flat rules tasks out as the thing that is growing.",
)
.with_callback(move |o| o.observe(tokio_alive_tasks(&h), &[]))
.build();
meter
.u64_observable_gauge("runtime.tokio.global_queue_depth")
.with_description(
"Tasks waiting in the runtime's global queue. Sustained non-zero \
depth under light load means the runtime is starved, which delays \
every in-flight future including I/O.",
)
.with_callback(move |o| o.observe(tokio_global_queue_depth(&handle), &[]))
.build();
}
fn spawn_scheduler_probe(meter: &Meter) {
if tokio::runtime::Handle::try_current().is_err() {
return;
}
let scheduler_delay = meter
.f64_histogram("runtime.tokio.scheduler_delay")
.with_unit("ms")
.with_description(
"Wall time for a bare yield_now() round-trip. Touches no I/O, no \
locks and nothing downstream, so it measures scheduling delay \
alone. Microseconds on a healthy runtime; seconds means every \
await point in the process is equally late.",
)
.build();
tokio::spawn(run_scheduler_probe(scheduler_delay));
}
async fn run_scheduler_probe(scheduler_delay: Histogram<f64>) {
loop {
tokio::time::sleep(SCHEDULER_PROBE_INTERVAL).await;
record_scheduler_delay(&scheduler_delay).await;
}
}
async fn record_scheduler_delay(scheduler_delay: &Histogram<f64>) {
let started = Instant::now();
tokio::task::yield_now().await;
scheduler_delay.record(started.elapsed().as_secs_f64() * 1000.0, &[]);
}
fn uptime_seconds(started: Instant) -> f64 {
started.elapsed().as_secs_f64()
}
fn tokio_workers(handle: &tokio::runtime::Handle) -> u64 {
handle.metrics().num_workers() as u64
}
fn tokio_alive_tasks(handle: &tokio::runtime::Handle) -> u64 {
handle.metrics().num_alive_tasks() as u64
}
fn tokio_global_queue_depth(handle: &tokio::runtime::Handle) -> u64 {
handle.metrics().global_queue_depth() as u64
}
fn resident_memory_bytes() -> Option<u64> {
resident_memory_from(std::path::Path::new("/proc/self/statm"))
}
fn resident_memory_from(path: &std::path::Path) -> Option<u64> {
parse_statm_resident(&std::fs::read_to_string(path).ok()?)
}
fn parse_statm_resident(statm: &str) -> Option<u64> {
let resident_pages: u64 = statm.split_whitespace().nth(1)?.parse().ok()?;
Some(resident_pages * 4096)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_the_resident_field_not_the_first() {
assert_eq!(parse_statm_resident("100 7 3 1 0 2 0"), Some(7 * 4096));
}
#[test]
fn rejects_malformed_statm() {
assert_eq!(parse_statm_resident(""), None, "empty");
assert_eq!(parse_statm_resident("100"), None, "no second field");
assert_eq!(parse_statm_resident("100 abc"), None, "unparsable");
assert_eq!(parse_statm_resident("100 -3"), None, "negative page count");
}
#[test]
fn missing_statm_file_reads_as_absent() {
assert_eq!(
resident_memory_from(std::path::Path::new("/nonexistent/otel-bootstrap/statm")),
None
);
}
#[cfg(target_os = "linux")]
#[test]
fn resident_memory_is_plausible() {
let rss = resident_memory_bytes().expect("/proc/self/statm readable on Linux");
assert!(rss >= 4096, "implausibly small RSS: {rss}");
assert!(
rss < 64 * 1024 * 1024 * 1024,
"implausibly large RSS: {rss}"
);
}
#[cfg(not(target_os = "linux"))]
#[test]
fn resident_memory_absent_without_proc() {
assert!(
resident_memory_bytes().is_none(),
"expected no /proc/self/statm off Linux"
);
}
#[test]
fn install_outside_a_runtime_does_not_panic() {
install();
}
#[tokio::test]
async fn install_inside_a_runtime_spawns_the_probe() {
install();
tokio::time::sleep(Duration::from_millis(20)).await;
}
#[tokio::test]
async fn tokio_gauges_report_inside_a_runtime() {
let handle = tokio::runtime::Handle::current();
assert!(tokio_workers(&handle) >= 1);
let before = tokio_alive_tasks(&handle);
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let held = tokio::spawn(async move {
let _ = rx.await;
});
tokio::task::yield_now().await;
assert!(
tokio_alive_tasks(&handle) > before,
"spawning a task should raise the live-task count"
);
let _ = tx.send(());
let _ = held.await;
let _ = tokio_global_queue_depth(&handle);
}
#[tokio::test]
async fn captured_handle_still_reports_off_runtime() {
let handle = tokio::runtime::Handle::current();
let workers = std::thread::spawn(move || {
assert!(
tokio::runtime::Handle::try_current().is_err(),
"this thread must be outside the runtime for the test to mean anything"
);
tokio_workers(&handle)
})
.join()
.expect("thread joins");
assert!(workers >= 1, "captured handle reports from another thread");
}
#[test]
fn uptime_advances() {
let started = Instant::now();
std::thread::sleep(Duration::from_millis(5));
assert!(uptime_seconds(started) > 0.0);
}
#[tokio::test]
async fn scheduler_delay_sample_is_recorded() {
let meter = opentelemetry::global::meter("test");
let histogram = meter.f64_histogram("test.scheduler_delay").build();
record_scheduler_delay(&histogram).await;
}
#[tokio::test]
async fn scheduler_probe_spawns_and_survives_abort() {
let meter = opentelemetry::global::meter("test");
let histogram = meter.f64_histogram("test.scheduler_delay").build();
let probe = tokio::spawn(run_scheduler_probe(histogram));
tokio::time::sleep(Duration::from_millis(20)).await;
probe.abort();
}
}