bothan_lib/metrics/
websocket.rs

1//! Metrics collection for websocket operations.
2//!
3//! This module provides the [`Metrics`] struct and related types for tracking websocket connection and message statistics
4//! such as the number of activity messages, connection attempts, and their durations. It leverages OpenTelemetry for metrics
5//! instrumentation, supporting monitoring and observability.
6
7use opentelemetry::metrics::{Counter, Histogram};
8use opentelemetry::{KeyValue, global};
9use strum_macros::Display;
10
11/// Holds counters and histograms for websocket metrics.
12#[derive(Clone, Debug)]
13pub struct Metrics {
14    /// The worker identifier for labeling metrics.
15    worker: String,
16    /// Counter tracking total activity messages sent.
17    activity_messages_total: Counter<u64>,
18    /// Histogram recording connection durations in milliseconds.
19    connection_duration: Histogram<u64>,
20    /// Counter tracking total websocket connections established.
21    connections_total: Counter<u64>,
22}
23
24impl Metrics {
25    /// Creates a new [`Metrics`] instance configured for a specified source and worker.
26    ///
27    /// # Arguments
28    ///
29    /// * `source` - A static string identifying the source whose metrics are being recorded.
30    /// * `worker` - The worker identifier for labeling metrics.
31    ///
32    /// # Examples
33    ///
34    /// ```rust
35    /// use bothan_lib::metrics::websocket::{Metrics, MessageType, ConnectionResult};
36    ///
37    /// let metrics = Metrics::new("example_source", "worker-1".to_string());
38    /// metrics.increment_activity_messages_total(MessageType::Ping);
39    /// metrics.update_websocket_connection(200, ConnectionResult::Success);
40    /// ```
41    pub fn new(source: &'static str, worker: String) -> Self {
42        let meter = global::meter(source);
43
44        let activity_messages_total = meter
45            .u64_counter("websocket_activity_messages")
46            .with_description("Total number of messages sent by the source to indicate whether the source is active or not")
47            .build();
48        let connection_duration = meter
49            .u64_histogram("websocket_connection_duration_milliseconds")
50            .with_description(
51                "Time taken for worker to establish a websocket connection to the source",
52            )
53            .with_unit("milliseconds")
54            .build();
55        let connections_total = meter
56            .u64_counter("websocket_connection")
57            .with_description("Total number of connections established by a worker to the source")
58            .build();
59
60        Self {
61            worker,
62            activity_messages_total,
63            connection_duration,
64            connections_total,
65        }
66    }
67
68    /// Increments the activity message counter for a given message type.
69    ///
70    /// # Arguments
71    ///
72    /// * `message` - The type of activity message sent.
73    pub fn increment_activity_messages_total(&self, message: MessageType) {
74        self.activity_messages_total.add(
75            1,
76            &[
77                KeyValue::new("worker", self.worker.clone()),
78                KeyValue::new("message_type", message.to_string()),
79            ],
80        );
81    }
82
83    /// Records a websocket connection attempt and its duration.
84    ///
85    /// # Arguments
86    ///
87    /// * `elapsed_time` - Duration of the connection attempt in milliseconds.
88    /// * `status` - The result of the connection attempt.
89    pub fn update_websocket_connection(&self, elapsed_time: u128, status: ConnectionResult) {
90        let labels = &[
91            KeyValue::new("worker", self.worker.clone()),
92            KeyValue::new("status", status.to_string()),
93        ];
94        self.connections_total.add(1, labels);
95        // `elapsed_time` is u128, but it will never exceed u64::MAX in practice
96        self.connection_duration.record(elapsed_time as u64, labels);
97    }
98}
99
100/// Possible types of websocket activity messages.
101#[derive(Display)]
102#[strum(serialize_all = "snake_case")]
103pub enum MessageType {
104    /// Asset information message.
105    AssetInfo,
106    /// Ping message.
107    Ping,
108    /// Unused message type.
109    Unused,
110    /// Error message.
111    Error,
112}
113
114/// Possible results for a websocket connection attempt.
115#[derive(Display)]
116#[strum(serialize_all = "snake_case")]
117pub enum ConnectionResult {
118    /// The connection was successful.
119    Success,
120    /// The connection failed.
121    Failed,
122}