#![allow(clippy::panic)]
use super::*;
use loonfs::metrics::LATENCY_SECONDS_BOUNDARIES;
#[test]
fn a_counter_renders_with_the_total_suffix_and_its_labels() {
let recorder = DefaultMetricsRecorder::new();
recorder
.register_counter(
"loonfs.object_store.operations",
"Object-store calls by operation and outcome",
&[("operation", "put"), ("result", "ok")],
)
.increment(3);
assert_eq!(
render_snapshot(&recorder.snapshot()),
"# HELP loonfs_object_store_operations_total Object-store calls by operation and outcome\n\
# TYPE loonfs_object_store_operations_total counter\n\
loonfs_object_store_operations_total{operation=\"put\",result=\"ok\"} 3\n"
);
}
#[test]
fn a_gauge_renders_without_a_suffix() {
let recorder = DefaultMetricsRecorder::new();
recorder
.register_gauge("loonfs.publisher.queue_depth", "Queued candidates", &[])
.set(7);
assert_eq!(
render_snapshot(&recorder.snapshot()),
"# HELP loonfs_publisher_queue_depth Queued candidates\n\
# TYPE loonfs_publisher_queue_depth gauge\n\
loonfs_publisher_queue_depth 7\n"
);
}
#[test]
fn a_histogram_renders_cumulative_buckets_ending_at_infinity() {
const BOUNDARIES: &[f64] = &[1.0, 2.0];
let recorder = DefaultMetricsRecorder::new();
let histogram = recorder.register_histogram(
"loonfs.server.request_seconds",
"Time to serve one request",
&[("route", "/health")],
BOUNDARIES,
);
histogram.record(0.5);
histogram.record(1.5);
histogram.record(9.0);
assert_eq!(
render_snapshot(&recorder.snapshot()),
"# HELP loonfs_server_request_seconds Time to serve one request\n\
# TYPE loonfs_server_request_seconds histogram\n\
loonfs_server_request_seconds_bucket{route=\"/health\",le=\"1.0\"} 1\n\
loonfs_server_request_seconds_bucket{route=\"/health\",le=\"2.0\"} 2\n\
loonfs_server_request_seconds_bucket{route=\"/health\",le=\"+Inf\"} 3\n\
loonfs_server_request_seconds_sum{route=\"/health\"} 11.0\n\
loonfs_server_request_seconds_count{route=\"/health\"} 3\n"
);
}
#[test]
fn label_sets_of_one_name_share_a_single_header_pair() {
let recorder = DefaultMetricsRecorder::new();
for kind in ["upload", "download"] {
recorder
.register_counter(
"loonfs.server.busy_rejections",
"Requests refused at a concurrency limit",
&[("kind", kind)],
)
.increment(1);
}
let rendered = render_snapshot(&recorder.snapshot());
assert_eq!(rendered.matches("# HELP").count(), 1);
assert_eq!(rendered.matches("# TYPE").count(), 1);
assert_eq!(
rendered
.lines()
.filter(|line| !line.starts_with('#'))
.count(),
2
);
}
#[test]
fn rendering_the_same_readings_twice_produces_the_same_bytes() {
let recorder = DefaultMetricsRecorder::new();
for (name, labels) in [
("loonfs.maintenance.steps", [("job", "gc")]),
("loonfs.maintenance.steps", [("job", "metadata")]),
("loonfs.gc.retained", [("category", "all")]),
] {
recorder
.register_counter(name, "Steps", &labels)
.increment(1);
}
recorder
.register_histogram(
"loonfs.maintenance.step_seconds",
"Step duration",
&[("job", "gc")],
LATENCY_SECONDS_BOUNDARIES,
)
.record(0.02);
let first = render_snapshot(&recorder.snapshot());
assert_eq!(first, render_snapshot(&recorder.snapshot()));
let names: Vec<&str> = first
.lines()
.filter(|line| line.starts_with("# TYPE"))
.map(|line| line.split(' ').nth(2).expect("type line names its metric"))
.collect();
let mut sorted = names.clone();
sorted.sort_unstable();
assert_eq!(names, sorted);
}
#[test]
fn scrape_time_gauges_carry_every_runtime_cache_counter() {
let mut rendered = String::new();
let cache = RuntimeCacheStats {
metadata_table_cache_hits: 12,
..RuntimeCacheStats::default()
};
render_scrape_gauges(&mut rendered, &cache, 4, 2);
assert!(rendered.contains("loonfs_cache_metadata_table_cache_hits 12\n"));
assert!(rendered.contains("loonfs_server_upload_permits_available 4\n"));
assert!(rendered.contains("loonfs_server_download_permits_available 2\n"));
assert_eq!(
rendered
.lines()
.filter(|line| line.starts_with("# TYPE"))
.count(),
20,
"eighteen cache counters and the two permit pools"
);
}
#[test]
fn route_labels_intern_once_and_refuse_to_grow_without_bound() {
let mut routes = RouteLabels::default();
let first = routes.intern("/v0/namespaces/{namespace}/commits");
let again = routes.intern("/v0/namespaces/{namespace}/commits");
assert_eq!(first, again);
assert_eq!(first.as_ptr(), again.as_ptr());
for index in 0..MAX_ROUTE_LABELS {
routes.intern(&format!("/synthetic/{index}"));
}
assert_eq!(routes.intern("/one/too/many"), UNMATCHED_ROUTE);
}
#[test]
fn status_classes_collapse_to_their_leading_digit() {
assert_eq!(status_class_label(StatusCode::OK), "2xx");
assert_eq!(status_class_label(StatusCode::UNAUTHORIZED), "4xx");
assert_eq!(status_class_label(StatusCode::SERVICE_UNAVAILABLE), "5xx");
}