Skip to main content

hyperi_rustlib/worker/engine/
metrics.rs

1// Project:   hyperi-rustlib
2// File:      src/worker/engine/metrics.rs
3// Purpose:   Metric registration and threshold gauge emission for BatchEngine
4// Language:  Rust
5//
6// License:   FSL-1.1-ALv2
7// Copyright: (c) 2026 HYPERI PTY LIMITED
8
9use crate::metrics::MetricsManager;
10
11use super::config::BatchProcessingConfig;
12
13/// Register all `BatchEngine` metrics with the `MetricsManager`.
14///
15/// Called by [`BatchEngine::auto_wire`](super::BatchEngine::auto_wire). Registers
16/// descriptors for all operational metrics and immediately emits the current
17/// config thresholds as gauges (for Grafana overlay of scaling decision lines).
18pub fn register(manager: &MetricsManager, config: &BatchProcessingConfig) {
19    // Counters
20    let _ = manager.counter(
21        "batch_engine_messages_received_total",
22        "Messages received from transport",
23    );
24    let _ = manager.counter(
25        "batch_engine_messages_parsed_total",
26        "Messages successfully SIMD-parsed",
27    );
28    let _ = manager.counter(
29        "batch_engine_messages_filtered_total",
30        "Messages filtered at pre-route",
31    );
32    let _ = manager.counter("batch_engine_messages_dlq_total", "Messages routed to DLQ");
33    let _ = manager.counter("batch_engine_parse_errors_total", "Parse failures");
34    let _ = manager.counter(
35        "batch_engine_memory_pressure_pauses_total",
36        "MemoryGuard pause events between chunks",
37    );
38
39    // Histograms
40    let _ = manager.histogram(
41        "batch_engine_parse_duration_seconds",
42        "SIMD parse time per chunk",
43    );
44    let _ = manager.histogram(
45        "batch_engine_transform_duration_seconds",
46        "App transform time per chunk",
47    );
48    let _ = manager.histogram("batch_engine_chunk_size", "Actual items per chunk");
49    let _ = manager.histogram(
50        "batch_engine_pre_route_duration_seconds",
51        "Pre-route extraction time per chunk",
52    );
53
54    // Gauges
55    let _ = manager.gauge(
56        "batch_engine_intern_table_size",
57        "Interned field name count",
58    );
59
60    // Config thresholds as gauges (emitted immediately).
61    emit_thresholds(config);
62}
63
64/// Emit config threshold values as gauge metrics.
65///
66/// Called at startup (via `register`) and optionally on config reload.
67/// Metric names are mechanically derived from config field names so that
68/// Grafana dashboards can overlay config changes on operational graphs.
69pub fn emit_thresholds(config: &BatchProcessingConfig) {
70    metrics::gauge!("batch_engine_max_chunk_size").set(config.max_chunk_size as f64);
71    metrics::gauge!("batch_engine_memory_pressure_pause_ms")
72        .set(config.memory_pressure_pause_ms as f64);
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn register_does_not_panic() {
81        let manager = MetricsManager::new_for_test("test_engine_metrics");
82        let config = BatchProcessingConfig::default();
83        // Should complete without panic even with no recorder installed.
84        register(&manager, &config);
85    }
86
87    #[test]
88    fn emit_thresholds_does_not_panic() {
89        let config = BatchProcessingConfig::default();
90        // metrics macros are no-ops when no recorder is installed.
91        emit_thresholds(&config);
92    }
93
94    #[test]
95    fn register_returns_handles() {
96        let manager = MetricsManager::new_for_test("test_engine_metrics_handles");
97        let config = BatchProcessingConfig::default();
98        // Calling twice should be idempotent.
99        register(&manager, &config);
100        register(&manager, &config);
101    }
102}