Skip to main content

hyperi_rustlib/metrics/dfe_groups/
circuit_breaker.rs

1// Project:   hyperi-rustlib
2// File:      src/metrics/dfe_groups/circuit_breaker.rs
3// Purpose:   DFE circuit breaker metrics group
4// Language:  Rust
5//
6// License:   BUSL-1.1
7// Copyright: (c) 2026 HYPERI PTY LIMITED
8
9//! Circuit breaker metrics.
10
11use super::super::MetricsManager;
12use super::super::manifest::{MetricDescriptor, MetricType};
13
14/// Circuit breaker metrics for per-target failure tracking.
15///
16/// State values: 0=closed (healthy), 1=open (failing), 2=half-open (probing).
17#[derive(Clone)]
18pub struct CircuitBreakerMetrics {
19    namespace: String,
20}
21
22impl CircuitBreakerMetrics {
23    #[must_use]
24    pub fn new(manager: &MetricsManager) -> Self {
25        let ns = manager.namespace();
26
27        // circuit_breaker_state -- label-based, register descriptor manually
28        let state_key = if ns.is_empty() {
29            "circuit_breaker_state".to_string()
30        } else {
31            format!("{ns}_circuit_breaker_state")
32        };
33        metrics::describe_gauge!(
34            state_key.clone(),
35            "Circuit breaker state (0=closed, 1=open, 2=half-open)"
36        );
37        manager.registry().push(MetricDescriptor {
38            name: state_key,
39            metric_type: MetricType::Gauge,
40            description: "Circuit breaker state (0=closed, 1=open, 2=half-open)".into(),
41            unit: String::new(),
42            labels: vec!["target".into()],
43            group: "circuit_breaker".into(),
44            buckets: None,
45            use_cases: vec![],
46            dashboard_hint: None,
47        });
48
49        // circuit_breaker_transitions_total -- label-based
50        let trans_key = if ns.is_empty() {
51            "circuit_breaker_transitions_total".to_string()
52        } else {
53            format!("{ns}_circuit_breaker_transitions_total")
54        };
55        metrics::describe_counter!(trans_key.clone(), "Circuit breaker state transitions");
56        manager.registry().push(MetricDescriptor {
57            name: trans_key,
58            metric_type: MetricType::Counter,
59            description: "Circuit breaker state transitions".into(),
60            unit: String::new(),
61            labels: vec!["target".into(), "to_state".into()],
62            group: "circuit_breaker".into(),
63            buckets: None,
64            use_cases: vec![],
65            dashboard_hint: None,
66        });
67
68        Self {
69            namespace: ns.to_string(),
70        }
71    }
72
73    /// Set circuit breaker state for a target.
74    #[inline]
75    pub fn set_state(&self, target: &str, state: u8) {
76        let key = if self.namespace.is_empty() {
77            "circuit_breaker_state".to_string()
78        } else {
79            format!("{}_circuit_breaker_state", self.namespace)
80        };
81        metrics::gauge!(key, "target" => target.to_string()).set(f64::from(state));
82    }
83
84    /// Record a state transition.
85    #[inline]
86    pub fn record_transition(&self, target: &str, to_state: &str) {
87        let key = if self.namespace.is_empty() {
88            "circuit_breaker_transitions_total".to_string()
89        } else {
90            format!("{}_circuit_breaker_transitions_total", self.namespace)
91        };
92        metrics::counter!(
93            key,
94            "target" => target.to_string(),
95            "to_state" => to_state.to_string()
96        )
97        .increment(1);
98    }
99}