partner_chains_db_sync_data_sources/
metrics.rs

1//! Substrate Prometheus metrics client for Db-Sync-based Partner Chain data sources
2use log::warn;
3use substrate_prometheus_endpoint::{
4	CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, register,
5};
6
7/// Substrate Prometheus metrics client used by Partner Chain data sources
8#[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
47/// Registers new metrics with Substrate Prometheus metrics service and returns a client instance
48pub 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/// Logs each method invocation and each returned result.
61/// Has to be made at the level of trait, because otherwise #[async_trait] is expanded first.
62/// '&self' matching yields "__self" identifier not found error, so "&$self:tt" is required.
63/// Works only if return type is Result.
64macro_rules! observed_async_trait {
65	(impl $(<$($type_param:tt),+>)? $trait_name:ident $(<$($type_arg:ident),+>)? for $target_type:ty
66		$(where $($where_type:ident : $where_bound:tt ,)+)?
67
68		{
69		$(type $type_name:ident = $type:ty;)*
70		$(async fn $method:ident(&$self:tt $(,$param_name:ident: $param_type:ty)* $(,)?) -> $res:ty $body:block)*
71	})=> {
72		#[async_trait::async_trait]
73		impl $(<$($type_param),+>)? $trait_name $(<$($type_arg),+>)? for $target_type
74		$(where $($where_type : $where_bound ,)+)?
75
76		{
77		$(type $type_name = $type;)*
78		$(
79			async fn $method(&$self $(,$param_name: $param_type)*,) -> $res {
80				let method_name = stringify!($method);
81				let _timer = if let Some(metrics) = &$self.metrics_opt {
82					metrics.call_count().with_label_values(&[method_name]).inc();
83					Some(metrics.time_elapsed().with_label_values(&[method_name]).start_timer())
84				} else { None };
85				let params: Vec<String> = vec![$(format!("{:?}", $param_name.clone()),)*];
86				log::debug!("{} called with parameters: {:?}", method_name, params);
87				let result = $body;
88				match &result {
89					Ok(value) => {
90						log::debug!("{} returns {:?}", method_name, value);
91					},
92					Err(error) => {
93						log::error!("{} failed with {:?}", method_name, error);
94					},
95				};
96				result
97			}
98		)*
99		}
100	};
101}
102
103pub(crate) use observed_async_trait;
104
105#[cfg(test)]
106pub(crate) mod mock {
107	use crate::metrics::McFollowerMetrics;
108	use substrate_prometheus_endpoint::{CounterVec, HistogramOpts, HistogramVec, Opts};
109
110	pub(crate) fn test_metrics() -> McFollowerMetrics {
111		McFollowerMetrics {
112			time_elapsed: HistogramVec::new(HistogramOpts::new("test", "test"), &["method_name"])
113				.unwrap(),
114			call_count: CounterVec::new(Opts::new("test", "test"), &["method_name"]).unwrap(),
115		}
116	}
117}
118
119#[cfg(test)]
120mod tests {
121	use crate::metrics::{McFollowerMetrics, mock::test_metrics};
122	use async_trait::async_trait;
123	use std::convert::Infallible;
124	use substrate_prometheus_endpoint::prometheus::core::Metric;
125
126	struct MetricsMacroTestStruct {
127		metrics_opt: Option<McFollowerMetrics>,
128	}
129
130	#[async_trait]
131	trait MetricMacroTestTrait {
132		async fn test_method_one(&self) -> Result<(), Infallible>;
133		async fn test_method_two(&self) -> Result<(), Infallible>;
134	}
135
136	observed_async_trait!(
137	impl MetricMacroTestTrait for MetricsMacroTestStruct {
138		async fn test_method_one(&self) -> Result<(), Infallible> {
139			tokio::time::sleep(core::time::Duration::from_millis(10)).await;
140			Ok(())
141		}
142
143		async fn test_method_two(&self) -> Result<(), Infallible> {
144			Ok(())
145		}
146	});
147
148	#[tokio::test]
149	async fn calculate_metrics_correctly() {
150		let metrics = test_metrics();
151		let histogram_method_one = metrics.time_elapsed().with_label_values(&["test_method_one"]);
152		let histogram_method_two = metrics.time_elapsed().with_label_values(&["test_method_two"]);
153		let histogram_method_random = metrics.time_elapsed().with_label_values(&["random"]);
154		let counter_method_one = metrics.call_count().with_label_values(&["test_method_one"]);
155		let counter_method_two = metrics.call_count().with_label_values(&["test_method_two"]);
156		let counter_method_random = metrics.call_count().with_label_values(&["random"]);
157
158		let metrics_struct = MetricsMacroTestStruct { metrics_opt: Some(metrics.clone()) };
159		metrics_struct.test_method_one().await.unwrap();
160		metrics_struct.test_method_two().await.unwrap();
161
162		for bucket in histogram_method_one.metric().get_histogram().get_bucket().iter().take(2) {
163			assert_eq!(bucket.get_cumulative_count(), 0);
164		}
165
166		// Assert below has a tiny potential to be flaky - if it is, please increase sleep time in MetricMacroTestTrait implementation or
167		// remove the Assert completely
168		assert!(histogram_method_one.get_sample_sum() > histogram_method_two.get_sample_sum());
169		assert_eq!(histogram_method_one.get_sample_count(), 1);
170		assert_eq!(histogram_method_two.get_sample_count(), 1);
171		assert_eq!(histogram_method_random.get_sample_count(), 0);
172
173		assert_eq!(counter_method_one.get(), 1);
174		assert_eq!(counter_method_two.get(), 1);
175		assert_eq!(counter_method_random.get(), 0);
176	}
177}