Skip to main content

miden_note_transport_node/
metrics.rs

1use opentelemetry::KeyValue;
2use opentelemetry::metrics::{Counter, Histogram, Meter};
3
4/// Transport metrics using OpenTelemetry metrics
5///
6/// If [`Metrics`] needs to be shared, cloning is recommended.
7#[derive(Debug, Clone)]
8pub struct Metrics {
9    /// [`crate::node::grpc::GrpcServer`] metrics
10    pub grpc: MetricsGrpc,
11    /// [`crate::database::Database`] metrics
12    pub db: MetricsDatabase,
13}
14
15/// [`crate::node::grpc::GrpcServer`] metrics
16#[derive(Debug, Clone)]
17pub struct MetricsGrpc {
18    // -- gRPC
19    // send_note()
20    send_note_count: Counter<u64>,
21    send_note_duration: Histogram<f64>,
22    send_note_note_size: Histogram<u64>,
23    // fetch_notes()
24    fetch_notes_count: Counter<u64>,
25    fetch_notes_duration: Histogram<f64>,
26    fetch_notes_replied_notes_number: Histogram<u64>,
27    fetch_notes_replied_notes_size: Histogram<u64>,
28}
29
30/// [`crate::database::Database`] metrics
31#[derive(Debug, Clone)]
32pub struct MetricsDatabase {
33    // -- DB
34    // store_note()
35    store_note_count: Counter<u64>,
36    store_note_duration: Histogram<f64>,
37    // fetch_notes()
38    fetch_notes_count: Counter<u64>,
39    fetch_notes_duration: Histogram<f64>,
40    // legacy cursor reset (pre-seq-migration clients)
41    fetch_notes_legacy_cursor_reset_count: Counter<u64>,
42    // Maintenance
43    maintenance_cleanup_notes_count: Counter<u64>,
44    maintenance_cleanup_notes_duration: Histogram<f64>,
45}
46
47impl Metrics {
48    /// Create a new instance of `Metrics`
49    pub fn new(meter: &Meter) -> Self {
50        let grpc = MetricsGrpc::new(meter);
51        let db = MetricsDatabase::new(meter);
52        Self { grpc, db }
53    }
54}
55
56impl MetricsGrpc {
57    /// Create a new instance of `MetricsGrpc`
58    pub fn new(meter: &Meter) -> Self {
59        let send_note_count = meter
60            .u64_counter("grpc_send_note_count")
61            .with_description("Total number of gRPC send_note() requests")
62            .build();
63
64        let send_note_duration = meter
65            .f64_histogram("grpc_send_note_duration")
66            .with_description("Duration of gRPC send_note() requests in seconds")
67            .with_unit("s")
68            .build();
69
70        let send_note_note_size = meter
71            .u64_histogram("grpc_send_note_note_size")
72            .with_description("Size of incoming note in send_note() requests in bytes")
73            .with_unit("B")
74            .build();
75
76        let fetch_notes_count = meter
77            .u64_counter("grpc_fetch_notes_count")
78            .with_description("Total number of gRPC fetch_notes() requests")
79            .build();
80
81        let fetch_notes_duration = meter
82            .f64_histogram("grpc_fetch_notes_duration")
83            .with_description("Duration of gRPC fetch_notes() requests in seconds")
84            .with_unit("s")
85            .build();
86
87        let fetch_notes_replied_notes_number = meter
88            .u64_histogram("grpc_fetch_notes_replied_notes_number")
89            .with_description("Number of replied notes per gRPC fetch_notes() request")
90            .build();
91
92        let fetch_notes_replied_notes_size = meter
93            .u64_histogram("grpc_fetch_notes_replied_notes_size")
94            .with_description("Total size of replied notes per gRPC fetch_notes() request in bytes")
95            .with_unit("B")
96            .build();
97
98        Self {
99            send_note_count,
100            send_note_duration,
101            send_note_note_size,
102            fetch_notes_count,
103            fetch_notes_duration,
104            fetch_notes_replied_notes_number,
105            fetch_notes_replied_notes_size,
106        }
107    }
108
109    /// Measure a send-note request
110    ///
111    /// Increases the request counter, records note size, and measures request duration.
112    pub fn grpc_send_note_request(&self, size_b: u64) -> RequestTimer<'_> {
113        let operation = "grpc.send_note.request";
114
115        self.send_note_note_size
116            .record(size_b, &[KeyValue::new("operation", operation.to_string())]);
117
118        let counter = &self.send_note_count;
119        let histogram = &self.send_note_duration;
120        request_count_measure(operation, counter, histogram)
121    }
122
123    /// Measure a fetch-notes request
124    ///
125    /// Increases the request counter and measures request duration.
126    pub fn grpc_fetch_notes_request(&self) -> RequestTimer<'_> {
127        let operation = "grpc.fetch_notes";
128        let counter = &self.fetch_notes_count;
129        let histogram = &self.fetch_notes_duration;
130
131        request_count_measure(operation, counter, histogram)
132    }
133
134    /// Measure a fetch-notes response
135    ///
136    /// Records number and size of replied notes.
137    pub fn grpc_fetch_notes_response(&self, number: u64, size_b: u64) {
138        let operation = "grpc.fetch_notes.response";
139
140        self.fetch_notes_replied_notes_number
141            .record(number, &[KeyValue::new("operation", operation.to_string())]);
142        self.fetch_notes_replied_notes_size
143            .record(size_b, &[KeyValue::new("operation", operation.to_string())]);
144    }
145}
146
147impl MetricsDatabase {
148    /// Create a new instance of `MetricsDatabase`
149    pub fn new(meter: &Meter) -> Self {
150        let store_note_count = meter
151            .u64_counter("db_store_note_count")
152            .with_description("Total number of DB store_note() requests")
153            .build();
154
155        let store_note_duration = meter
156            .f64_histogram("db_store_note_duration")
157            .with_description("Duration of DB store_note() requests in seconds")
158            .with_unit("s")
159            .build();
160
161        let fetch_notes_count = meter
162            .u64_counter("db_fetch_notes_count")
163            .with_description("Total number of DB fetch_notes() requests")
164            .build();
165
166        let fetch_notes_duration = meter
167            .f64_histogram("db_fetch_notes_duration")
168            .with_description("Duration of dB fetch_notes() requests in seconds")
169            .with_unit("s")
170            .build();
171
172        let fetch_notes_legacy_cursor_reset_count = meter
173            .u64_counter("db_fetch_notes_legacy_cursor_reset_count")
174            .with_description(
175                "Number of fetch_notes() requests where the client's cursor was \
176                 above the legacy-cursor threshold and reset to 0 (pre-seq-migration \
177                 clients)",
178            )
179            .build();
180
181        let maintenance_cleanup_notes_count = meter
182            .u64_counter("db_maintenance_cleanup_notes_count")
183            .with_description("Total number of DB maintenance cleanup_old_notes() requests")
184            .build();
185
186        let maintenance_cleanup_notes_duration = meter
187            .f64_histogram("db_maintenance_cleanup_notes_duration")
188            .with_description("Duration of DB maintenance cleanup_old_notes() requests in seconds")
189            .with_unit("s")
190            .build();
191
192        Self {
193            store_note_count,
194            store_note_duration,
195            fetch_notes_count,
196            fetch_notes_duration,
197            fetch_notes_legacy_cursor_reset_count,
198            maintenance_cleanup_notes_count,
199            maintenance_cleanup_notes_duration,
200        }
201    }
202
203    /// Measure a DB store-note request
204    ///
205    /// Increases the request counter and measures request duration.
206    pub fn db_store_note(&self) -> RequestTimer<'_> {
207        let operation = "db.store_note";
208        let counter = &self.store_note_count;
209        let histogram = &self.store_note_duration;
210
211        request_count_measure(operation, counter, histogram)
212    }
213
214    /// Measure a DB fetch-notes request
215    ///
216    /// Increases the request counter and measures request duration.
217    pub fn db_fetch_notes(&self) -> RequestTimer<'_> {
218        let operation = "db.fetch_notes";
219        let counter = &self.fetch_notes_count;
220        let histogram = &self.fetch_notes_duration;
221
222        request_count_measure(operation, counter, histogram)
223    }
224
225    /// Record a legacy-cursor reset (pre-seq-migration client).
226    pub fn db_fetch_notes_legacy_cursor_reset(&self) {
227        self.fetch_notes_legacy_cursor_reset_count.add(1, &[]);
228    }
229
230    /// Measure a DB maintenance cleanup-old-notes procedure
231    ///
232    /// Increases the request counter and measures request duration.
233    pub fn db_maintenance_cleanup_notes(&self) -> RequestTimer<'_> {
234        let operation = "db.maintenance.cleanup_old_notes";
235        let counter = &self.maintenance_cleanup_notes_count;
236        let histogram = &self.maintenance_cleanup_notes_duration;
237
238        request_count_measure(operation, counter, histogram)
239    }
240}
241
242/// Measure a request
243///
244/// Increases the request counter and measures request duration.
245fn request_count_measure<'a>(
246    operation: &str,
247    counter: &Counter<u64>,
248    histogram: &'a Histogram<f64>,
249) -> RequestTimer<'a> {
250    let start = std::time::Instant::now();
251
252    // Increment request counter
253    counter.add(1, &[KeyValue::new("operation", operation.to_string())]);
254
255    RequestTimer {
256        operation: operation.to_string(),
257        start,
258        histogram,
259    }
260}
261
262impl Default for Metrics {
263    fn default() -> Self {
264        let meter = opentelemetry::global::meter("miden-note-transport-node");
265        Self::new(&meter)
266    }
267}
268
269/// Timer for measuring request duration
270pub struct RequestTimer<'a> {
271    operation: String,
272    start: std::time::Instant,
273    histogram: &'a Histogram<f64>,
274}
275
276impl RequestTimer<'_> {
277    /// Finish the request and record the duration
278    pub fn finish(&self, status: &str) {
279        let duration = self.start.elapsed();
280        let duration_s = duration.as_secs_f64();
281
282        // Record request duration
283        self.histogram.record(
284            duration_s,
285            &[
286                KeyValue::new("operation", self.operation.clone()),
287                KeyValue::new("status", status.to_string()),
288            ],
289        );
290    }
291}
292
293impl Drop for RequestTimer<'_> {
294    fn drop(&mut self) {
295        self.finish("dropped");
296    }
297}