photon_telemetry/
recording.rs1use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
4
5use serde_json::Value;
6
7use super::OpsLog;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct RecordedCounter {
12 pub name: String,
14 pub labels: Vec<(String, String)>,
16 pub value: f64,
18}
19
20#[derive(Debug, Clone, PartialEq)]
22pub struct RecordedGauge {
23 pub name: String,
25 pub labels: Vec<(String, String)>,
27 pub value: f64,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct RecordedEvent {
34 pub name: String,
36 pub payload: Value,
38}
39
40#[derive(Debug, Default)]
41struct Inner {
42 counters: Vec<RecordedCounter>,
43 gauges: Vec<RecordedGauge>,
44 events: Vec<RecordedEvent>,
45}
46
47#[derive(Debug, Clone)]
49pub struct RecordingOpsLog {
50 inner: Arc<Mutex<Inner>>,
51}
52
53fn lock_inner(inner: &Mutex<Inner>) -> MutexGuard<'_, Inner> {
54 inner.lock().unwrap_or_else(PoisonError::into_inner)
55}
56
57impl RecordingOpsLog {
58 #[must_use]
60 pub fn new() -> Self {
61 Self {
62 inner: Arc::new(Mutex::new(Inner::default())),
63 }
64 }
65
66 pub fn clear(&self) {
68 let mut g = lock_inner(&self.inner);
69 g.counters.clear();
70 g.gauges.clear();
71 g.events.clear();
72 }
73
74 #[must_use]
76 pub fn counters(&self) -> Vec<RecordedCounter> {
77 lock_inner(&self.inner).counters.clone()
78 }
79
80 #[must_use]
82 pub fn gauges(&self) -> Vec<RecordedGauge> {
83 lock_inner(&self.inner).gauges.clone()
84 }
85
86 #[must_use]
88 pub fn events(&self) -> Vec<RecordedEvent> {
89 lock_inner(&self.inner).events.clone()
90 }
91
92 #[must_use]
94 pub fn recorded_counters_matching(
95 &self,
96 name: &str,
97 label_subset: &[(&str, &str)],
98 ) -> Vec<RecordedCounter> {
99 self.counters()
100 .into_iter()
101 .filter(|c| c.name == name && labels_contain(&c.labels, label_subset))
102 .collect()
103 }
104
105 #[must_use]
107 pub fn recorded_events_for(&self, event_name: &str) -> Vec<RecordedEvent> {
108 self.events()
109 .into_iter()
110 .filter(|e| e.name == event_name)
111 .collect()
112 }
113}
114
115fn labels_contain(labels: &[(String, String)], subset: &[(&str, &str)]) -> bool {
116 subset.iter().all(|(k, v)| {
117 labels
118 .iter()
119 .any(|(lk, lv)| lk.as_str() == *k && lv.as_str() == *v)
120 })
121}
122
123impl OpsLog for RecordingOpsLog {
124 fn record_counter(&self, name: &str, labels: &[(&str, &str)], value: f64) {
125 let labels: Vec<(String, String)> = labels
126 .iter()
127 .map(|(k, v)| (k.to_string(), v.to_string()))
128 .collect();
129 lock_inner(&self.inner).counters.push(RecordedCounter {
130 name: name.to_string(),
131 labels,
132 value,
133 });
134 }
135
136 fn record_gauge(&self, name: &str, labels: &[(&str, &str)], value: f64) {
137 let labels: Vec<(String, String)> = labels
138 .iter()
139 .map(|(k, v)| (k.to_string(), v.to_string()))
140 .collect();
141 lock_inner(&self.inner).gauges.push(RecordedGauge {
142 name: name.to_string(),
143 labels,
144 value,
145 });
146 }
147
148 fn log_event(&self, name: &str, payload: &Value) {
149 lock_inner(&self.inner).events.push(RecordedEvent {
150 name: name.to_string(),
151 payload: payload.clone(),
152 });
153 }
154}
155
156impl Default for RecordingOpsLog {
157 fn default() -> Self {
158 Self::new()
159 }
160}