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