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