partner_chains_data_source_metrics/
lib.rs1use log::warn;
3use substrate_prometheus_endpoint::{
4 CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, register,
5};
6
7pub type MetricsRegistry = Registry;
8
9#[derive(Clone)]
11pub struct McFollowerMetrics {
12 time_elapsed: HistogramVec,
13 call_count: CounterVec<U64>,
14}
15
16impl McFollowerMetrics {
17 pub fn time_elapsed(&self) -> &HistogramVec {
18 &self.time_elapsed
19 }
20 pub fn call_count(&self) -> &CounterVec<U64> {
21 &self.call_count
22 }
23 pub fn register(registry: &Registry) -> Result<Self, PrometheusError> {
24 Ok(Self {
25 time_elapsed: register(
26 HistogramVec::new(
27 HistogramOpts::new(
28 "partner_chains_data_source_method_time_elapsed",
29 "Time spent in a method call",
30 ),
31 &["method_name"],
32 )?,
33 registry,
34 )?,
35 call_count: register(
36 CounterVec::new(
37 Opts::new(
38 "partner_chains_data_source_method_call_count",
39 "Total number of data source method calls",
40 ),
41 &["method_name"],
42 )?,
43 registry,
44 )?,
45 })
46 }
47}
48
49pub fn register_metrics_warn_errors(
51 metrics_registry_opt: Option<&Registry>,
52) -> Option<McFollowerMetrics> {
53 metrics_registry_opt.and_then(|registry| match McFollowerMetrics::register(registry) {
54 Ok(metrics) => Some(metrics),
55 Err(err) => {
56 warn!("Failed registering data source metrics with err: {}", err);
57 None
58 },
59 })
60}
61
62pub use async_trait::async_trait;
63
64#[macro_export]
69macro_rules! observed_async_trait {
70 (impl $(<$($type_param:tt),+>)? $trait_name:ident $(<$($type_arg:ident),+>)? for $target_type:ty
71 $(where $($where_type:ident : $where_bound:tt ,)+)?
72
73 {
74 $(type $type_name:ident = $type:ty;)*
75 $(async fn $method:ident(&$self:tt $(,$param_name:ident: $param_type:ty)* $(,)?) -> $res:ty $body:block)*
76 })=> {
77 #[$crate::async_trait]
78 impl $(<$($type_param),+>)? $trait_name $(<$($type_arg),+>)? for $target_type
79 $(where $($where_type : $where_bound ,)+)?
80
81 {
82 $(type $type_name = $type;)*
83 $(
84 async fn $method(&$self $(,$param_name: $param_type)*,) -> $res {
85 let method_name = stringify!($method);
86 let _timer = if let Some(metrics) = &$self.metrics_opt {
87 metrics.call_count().with_label_values(&[method_name]).inc();
88 Some(metrics.time_elapsed().with_label_values(&[method_name]).start_timer())
89 } else { None };
90 let params: Vec<String> = vec![$(format!("{:?}", $param_name.clone()),)*];
91 log::debug!("{} called with parameters: {:?}", method_name, params);
92 let result = $body;
93 match &result {
94 Ok(value) => {
95 log::debug!("{} returns {:?}", method_name, value);
96 },
97 Err(error) => {
98 log::error!("{} failed with {:?}", method_name, error);
99 },
100 };
101 result
102 }
103 )*
104 }
105 };
106}
107
108pub mod mock {
109 use crate::McFollowerMetrics;
110 use substrate_prometheus_endpoint::{CounterVec, HistogramOpts, HistogramVec, Opts};
111
112 pub fn test_metrics() -> McFollowerMetrics {
113 McFollowerMetrics {
114 time_elapsed: HistogramVec::new(HistogramOpts::new("test", "test"), &["method_name"])
115 .unwrap(),
116 call_count: CounterVec::new(Opts::new("test", "test"), &["method_name"]).unwrap(),
117 }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use crate::{McFollowerMetrics, mock::test_metrics};
124 use async_trait::async_trait;
125 use std::convert::Infallible;
126 use substrate_prometheus_endpoint::prometheus::core::Metric;
127
128 struct MetricsMacroTestStruct {
129 metrics_opt: Option<McFollowerMetrics>,
130 }
131
132 #[async_trait]
133 trait MetricMacroTestTrait {
134 async fn test_method_one(&self) -> Result<(), Infallible>;
135 async fn test_method_two(&self) -> Result<(), Infallible>;
136 }
137
138 observed_async_trait!(
139 impl MetricMacroTestTrait for MetricsMacroTestStruct {
140 async fn test_method_one(&self) -> Result<(), Infallible> {
141 tokio::time::sleep(core::time::Duration::from_millis(10)).await;
142 Ok(())
143 }
144
145 async fn test_method_two(&self) -> Result<(), Infallible> {
146 Ok(())
147 }
148 });
149
150 #[tokio::test]
151 async fn calculate_metrics_correctly() {
152 let metrics = test_metrics();
153 let histogram_method_one = metrics.time_elapsed().with_label_values(&["test_method_one"]);
154 let histogram_method_two = metrics.time_elapsed().with_label_values(&["test_method_two"]);
155 let histogram_method_random = metrics.time_elapsed().with_label_values(&["random"]);
156 let counter_method_one = metrics.call_count().with_label_values(&["test_method_one"]);
157 let counter_method_two = metrics.call_count().with_label_values(&["test_method_two"]);
158 let counter_method_random = metrics.call_count().with_label_values(&["random"]);
159
160 let metrics_struct = MetricsMacroTestStruct { metrics_opt: Some(metrics.clone()) };
161 metrics_struct.test_method_one().await.unwrap();
162 metrics_struct.test_method_two().await.unwrap();
163
164 for bucket in histogram_method_one.metric().get_histogram().get_bucket().iter().take(2) {
165 assert_eq!(bucket.get_cumulative_count(), 0);
166 }
167
168 assert!(histogram_method_one.get_sample_sum() > histogram_method_two.get_sample_sum());
171 assert_eq!(histogram_method_one.get_sample_count(), 1);
172 assert_eq!(histogram_method_two.get_sample_count(), 1);
173 assert_eq!(histogram_method_random.get_sample_count(), 0);
174
175 assert_eq!(counter_method_one.get(), 1);
176 assert_eq!(counter_method_two.get(), 1);
177 assert_eq!(counter_method_random.get(), 0);
178 }
179}