feat: freeze typed metrics registry and bounded exemplars

This commit is contained in:
2026-08-14 11:52:00 +03:00
parent 17c8e3a8f0
commit 8e709acea9
29 changed files with 1690 additions and 103 deletions
+39
View File
@@ -0,0 +1,39 @@
use std::{collections::BTreeSet, time::Duration};
use crank_metrics::{
HttpMethod, HttpRoute, HttpStatusClass, McpMethod, McpOutcome, McpResponseMode,
record_http_request, record_mcp_request,
};
use metrics_util::debugging::DebuggingRecorder;
#[test]
fn one_million_hostile_values_collapse_to_the_same_closed_series() {
let recorder = DebuggingRecorder::new();
let snapshotter = recorder.snapshotter();
metrics::with_local_recorder(&recorder, || {
for index in 0..1_000_000_u32 {
let canary = format!("workspace-{index}-request-trace-url-secret");
record_http_request(
HttpRoute::from_matched_path(&canary),
HttpMethod::classify(&canary),
HttpStatusClass::from_status(999),
Duration::ZERO,
);
record_mcp_request(
McpMethod::Invalid,
McpResponseMode::Unknown,
McpOutcome::Other,
Duration::ZERO,
);
}
});
let series = snapshotter
.snapshot()
.into_vec()
.into_iter()
.map(|(key, _, _, _)| format!("{:?}", key.key()))
.collect::<BTreeSet<_>>();
assert_eq!(series.len(), 4);
assert!(series.iter().all(|value| !value.contains("workspace-")));
}
+25 -2
View File
@@ -8,7 +8,7 @@ use crank_metrics::{
metric_schema, record_cache_outcome, record_confirmation_outcome, record_export_failure,
record_http_request, record_idempotency_outcome, record_invocation_history_lost,
record_limit_rejection, record_mcp_request, record_tool_invocation, record_upstream_request,
set_catalog, set_db_pool_connections, set_mcp_active_sessions,
set_catalog, set_db_pool_connections, set_mcp_active_sessions, set_mcp_session_metrics_fresh,
};
use metrics_util::debugging::DebuggingRecorder;
@@ -80,6 +80,26 @@ fn application_outcomes_are_distinct_from_transport_success() {
assert_eq!(ConfirmationOutcome::Required.as_str(), "required");
}
#[test]
fn impossible_tool_outcome_pairs_are_normalized() {
let recorder = DebuggingRecorder::new();
let snapshotter = recorder.snapshotter();
metrics::with_local_recorder(&recorder, || {
record_tool_invocation(
InvocationSource::AgentToolCall,
ToolOutcome::Success,
ToolErrorKind::Mapping,
Duration::ZERO,
);
});
let snapshot = snapshotter.snapshot().into_vec();
assert!(snapshot.iter().any(|(key, _, _, _)| {
key.key().name() == "crank_tool_invocations_total"
&& has_label(key.key(), "outcome", "success")
&& has_label(key.key(), "error_kind", "none")
}));
}
#[test]
fn dropped_product_guards_record_aborted_outcomes() {
let recorder = DebuggingRecorder::new();
@@ -128,6 +148,7 @@ fn diverse_calls_cannot_create_unbounded_series() {
McpMethod::ToolsCall,
McpResponseMode::Json,
McpOutcome::ToolError,
Duration::from_millis(1),
);
record_tool_invocation(
InvocationSource::AgentToolCall,
@@ -147,7 +168,7 @@ fn diverse_calls_cannot_create_unbounded_series() {
.map(|(key, _, _, _)| format!("{:?}", key.key()))
.collect::<BTreeSet<_>>();
assert_eq!(series.len(), 8);
assert_eq!(series.len(), 9);
assert!(
series
.iter()
@@ -172,8 +193,10 @@ fn every_recording_api_matches_the_declared_schema() {
McpMethod::ToolsCall,
McpResponseMode::Json,
McpOutcome::Success,
Duration::from_millis(1),
);
set_mcp_active_sessions(1);
set_mcp_session_metrics_fresh(true);
let _stream = InFlightGuard::mcp_stream();
let _runtime = InFlightGuard::runtime();
record_tool_invocation(
+40
View File
@@ -0,0 +1,40 @@
use std::time::Duration;
use crank_metrics::{
ExemplarTraceId, HttpMethod, HttpRoute, HttpStatusClass, exemplar_snapshot,
record_http_request_with_exemplar, reset_exemplars_for_test,
};
use metrics_util::debugging::DebuggingRecorder;
#[test]
fn trace_id_is_validated_and_never_becomes_an_ordinary_label() {
assert!(ExemplarTraceId::parse("0123456789abcdef0123456789abcdef").is_some());
assert!(ExemplarTraceId::parse("0123456789ABCDEF0123456789ABCDEF").is_none());
assert!(ExemplarTraceId::parse("short").is_none());
reset_exemplars_for_test();
let recorder = DebuggingRecorder::new();
let snapshotter = recorder.snapshotter();
metrics::with_local_recorder(&recorder, || {
record_http_request_with_exemplar(
HttpRoute::from_matched_path("/health"),
HttpMethod::Get,
HttpStatusClass::Success,
Duration::from_millis(10),
ExemplarTraceId::parse("0123456789abcdef0123456789abcdef"),
);
});
let aggregate = snapshotter.snapshot().into_vec();
assert!(
aggregate
.iter()
.all(|(key, _, _, _)| { key.key().labels().all(|label| label.key() != "trace_id") })
);
let exemplars = exemplar_snapshot();
assert_eq!(exemplars.len(), 1);
assert_eq!(
exemplars[0].trace_id.as_str(),
"0123456789abcdef0123456789abcdef"
);
}
+62
View File
@@ -0,0 +1,62 @@
use std::{
hint::black_box,
time::{Duration, Instant},
};
use crank_metrics::{CacheOutcome, record_cache_outcome};
use metrics_util::debugging::DebuggingRecorder;
const SAMPLES: usize = 40;
const WORK_UNITS: u64 = 500_000;
#[test]
fn reproducible_foundation_profile_stays_inside_latency_and_cpu_proxy_budgets() {
let recorder = DebuggingRecorder::new();
let snapshotter = recorder.snapshotter();
let mut baseline = Vec::with_capacity(SAMPLES);
let mut enabled = Vec::with_capacity(SAMPLES);
metrics::with_local_recorder(&recorder, || {
for index in 0..SAMPLES {
if index % 2 == 0 {
baseline.push(sample(false));
enabled.push(sample(true));
} else {
enabled.push(sample(true));
baseline.push(sample(false));
}
}
});
baseline.sort_unstable();
enabled.sort_unstable();
let baseline_p95 = baseline[SAMPLES * 95 / 100];
let enabled_p95 = enabled[SAMPLES * 95 / 100];
let baseline_cpu: Duration = baseline.iter().copied().sum();
let enabled_cpu: Duration = enabled.iter().copied().sum();
let series = snapshotter.snapshot().into_vec().len();
println!(
"metrics_profile_v1 samples={SAMPLES} work_units={WORK_UNITS} baseline_p95_ns={} enabled_p95_ns={} baseline_cpu_ns={} enabled_cpu_ns={} logical_series={series}",
baseline_p95.as_nanos(),
enabled_p95.as_nanos(),
baseline_cpu.as_nanos(),
enabled_cpu.as_nanos()
);
assert!(enabled_p95.as_nanos() * 100 <= baseline_p95.as_nanos() * 105);
assert!(enabled_cpu.as_nanos() * 100 <= baseline_cpu.as_nanos() * 110);
assert_eq!(series, 1);
}
fn sample(with_metrics: bool) -> Duration {
let started = Instant::now();
let mut value = 0x9e37_79b9_u64;
for index in 0..WORK_UNITS {
value = value.rotate_left(7) ^ index.wrapping_mul(0x100_0000_01b3);
}
black_box(value);
if with_metrics {
record_cache_outcome(CacheOutcome::Hit);
}
started.elapsed()
}
+100
View File
@@ -0,0 +1,100 @@
use std::collections::BTreeSet;
use crank_metrics::{
HTTP_ROUTE_DOMAIN, HttpRoute, MAX_LOGICAL_SERIES_PER_PROCESS, MAX_PRODUCT_LABELS_PER_FAMILY,
MAX_RENDERED_SERIES_PER_PROCESS, METRIC_SCHEMA_VERSION, metric_schema,
render_metric_schema_json, schema_budget, validate_budget,
};
#[test]
fn schema_v1_is_complete_deterministic_and_inside_the_frozen_budget() {
assert_eq!(METRIC_SCHEMA_VERSION, 1);
assert_eq!(metric_schema().len(), 23);
let budget = schema_budget().expect("canonical schema must be valid");
assert!(budget.logical_series <= MAX_LOGICAL_SERIES_PER_PROCESS);
assert!(budget.rendered_series <= MAX_RENDERED_SERIES_PER_PROCESS);
for definition in metric_schema() {
assert!(!definition.description.trim().is_empty());
assert!(definition.labels.len() <= MAX_PRODUCT_LABELS_PER_FAMILY);
assert!(!definition.processes.is_empty());
assert_eq!(definition.labels.len(), definition.label_domains.len());
assert!(definition.label_domains.iter().all(|domain| {
!domain.values.is_empty()
&& domain.values.iter().all(|value| !value.is_empty())
&& domain.values.iter().copied().collect::<BTreeSet<_>>().len()
== domain.values.len()
}));
if definition.kind.as_str() == "histogram" {
assert!(!definition.buckets.is_empty());
assert!(definition.buckets.windows(2).all(|pair| pair[0] < pair[1]));
} else {
assert!(definition.buckets.is_empty());
}
}
let first = render_metric_schema_json().expect("schema renders");
let second = render_metric_schema_json().expect("schema renders deterministically");
assert_eq!(first, second);
assert!(first.len() < 65_536);
assert!(!first.contains("trace_id"));
assert!(!first.contains("request_id"));
}
#[test]
fn frozen_budget_edges_are_exact() {
assert!(validate_budget(4_999, 14_999).is_ok());
assert!(validate_budget(5_000, 15_000).is_ok());
assert!(validate_budget(5_001, 15_000).is_err());
assert!(validate_budget(5_000, 15_001).is_err());
assert_eq!(crank_metrics::max_exemplar_slots(), 9_170);
}
#[test]
fn every_registered_route_round_trips_without_dynamic_segments() {
assert_eq!(HTTP_ROUTE_DOMAIN.len(), 57);
for route in HTTP_ROUTE_DOMAIN {
assert_eq!(HttpRoute::from_matched_path(route).as_str(), *route);
assert!(!route.contains("trace_id"));
assert!(!route.contains("request_id"));
}
}
#[test]
fn canonical_schema_validation_reports_its_category() {
assert_eq!(
schema_budget(),
Ok(crank_metrics::SchemaBudget {
logical_series: 4_513,
rendered_series: 14_338
})
);
}
#[test]
fn exact_budget_math_counts_histogram_bucket_sum_and_count_series() {
for definition in metric_schema() {
let logical = definition
.label_domains
.iter()
.map(|domain| domain.values.len())
.product::<usize>();
let logical = logical.max(1);
let rendered = if definition.kind.as_str() == "histogram" {
logical * (definition.buckets.len() + 3)
} else {
logical
};
assert_eq!(
definition.max_logical_series, logical,
"{}",
definition.name
);
assert_eq!(
definition.max_rendered_series, rendered,
"{}",
definition.name
);
}
}