1use std::time::Duration;
6use std::{sync::Once, time::Instant};
7
8use dapi_grpc::tonic::Code;
9use metrics::{counter, describe_counter, describe_gauge, describe_histogram, histogram, Label};
10use metrics_exporter_prometheus::PrometheusBuilder;
11
12pub const DEFAULT_PROMETHEUS_PORT: u16 = 29090;
14const COUNTER_LAST_BLOCK_TIME: &str = "abci_last_block_time_seconds";
16const COUNTER_LAST_HEIGHT: &str = "abci_last_finalized_height";
17const COUNTER_LAST_CHECKPOINT_HEIGHT: &str = "abci_last_checkpoint_height";
18const COUNTER_CHECKPOINT_FAILURES: &str = "abci_checkpoint_failures";
19const HISTOGRAM_FINALIZED_ROUND: &str = "abci_finalized_round";
20const HISTOGRAM_ABCI_REQUEST_DURATION: &str = "abci_request_duration_seconds";
21const HISTOGRAM_STATE_TRANSITION_PROCESSING_DURATION: &str =
23 "state_transition_processing_duration_seconds";
24const LABEL_ENDPOINT: &str = "endpoint";
25pub const LABEL_ABCI_RESPONSE_CODE: &str = "response_code";
27const HISTOGRAM_QUERY_DURATION: &str = "abci_query_duration";
28pub const LABEL_STATE_TRANSITION_NAME: &str = "st_name";
30const LABEL_STATE_TRANSITION_EXECUTION_CODE: &str = "st_exec_code";
32pub const LABEL_CHECK_TX_MODE: &str = "check_tx_mode";
34pub const GAUGE_CREDIT_WITHDRAWAL_LIMIT_AVAILABLE: &str = "credit_withdrawal_limit_available";
36pub const GAUGE_CREDIT_WITHDRAWAL_LIMIT_TOTAL: &str = "credit_withdrawal_limit_total";
38pub const GAUGE_CREDIT_WITHDRAWAL_LIMIT_CORE_AVAILABLE: &str =
40 "credit_withdrawal_limit_core_available";
41
42#[derive(thiserror::Error, Debug)]
44pub enum Error {
45 #[error("prometheus server: {0}")]
47 ServerFailed(#[from] metrics_exporter_prometheus::BuildError),
48 #[error("invalid listen address {0}: {1}")]
50 InvalidListenAddress(url::Url, String),
51}
52
53pub struct HistogramTiming {
61 key: metrics::Key,
62 start: Instant,
63 skip: bool,
64}
65
66impl HistogramTiming {
67 #[inline]
77 fn new(metric: metrics::Key) -> Self {
78 Self {
79 key: metric,
80 start: Instant::now(),
81 skip: false,
82 }
83 }
84
85 pub fn elapsed(&self) -> std::time::Duration {
87 self.start.elapsed()
88 }
89
90 pub fn add_label(&mut self, label: Label) {
92 self.key = self.key.with_extra_labels(vec![label]);
93 }
94
95 pub fn cancel(mut self) {
97 self.skip = true;
98
99 drop(self);
100 }
101}
102
103impl Drop for HistogramTiming {
104 #[inline]
109 fn drop(&mut self) {
110 if self.skip {
111 return;
112 }
113
114 let stop = self.start.elapsed();
115 let key = self.key.name().to_string();
116
117 let labels: Vec<Label> = self.key.labels().cloned().collect();
118 histogram!(key, labels).record(stop.as_secs_f64());
119 }
120}
121
122pub struct Prometheus {}
135
136impl Prometheus {
137 pub fn new(listen_address: url::Url) -> Result<Self, Error> {
162 if listen_address.scheme() != "http" {
163 return Err(Error::InvalidListenAddress(
164 listen_address.clone(),
165 format!("unsupported scheme {}", listen_address.scheme()),
166 ));
167 }
168
169 let saddr = listen_address
170 .socket_addrs(|| Some(DEFAULT_PROMETHEUS_PORT))
171 .map_err(|e| Error::InvalidListenAddress(listen_address.clone(), e.to_string()))?;
172 if saddr.len() > 1 {
173 tracing::warn!(
174 "too many listen addresses resolved from {}: {:?}",
175 listen_address,
176 saddr
177 )
178 }
179 let saddr = saddr.first().ok_or(Error::InvalidListenAddress(
180 listen_address,
181 "failed to resolve listen address".to_string(),
182 ))?;
183
184 let builder = PrometheusBuilder::new().with_http_listener(*saddr);
185 builder.install()?;
186
187 Self::register_metrics();
188
189 Ok(Self {})
190 }
191
192 fn register_metrics() {
193 static START: Once = Once::new();
194
195 START.call_once(|| {
196 describe_counter!(
197 COUNTER_LAST_HEIGHT,
198 "Last finalized height of platform chain (eg. Tenderdash)"
199 );
200
201 describe_counter!(
202 COUNTER_LAST_BLOCK_TIME,
203 metrics::Unit::Seconds,
204 "Time of last finalized block, seconds since epoch"
205 );
206
207 describe_counter!(
208 COUNTER_LAST_CHECKPOINT_HEIGHT,
209 "Height of the last GroveDB checkpoint created after a finalized block"
210 );
211
212 describe_counter!(
213 COUNTER_CHECKPOINT_FAILURES,
214 "Number of GroveDB checkpoint attempts that failed after their block was committed"
215 );
216
217 describe_histogram!(
218 HISTOGRAM_FINALIZED_ROUND,
219 "Rounds at which blocks are finalized"
220 );
221
222 describe_histogram!(
223 HISTOGRAM_ABCI_REQUEST_DURATION,
224 metrics::Unit::Seconds,
225 "Duration of ABCI request execution inside Drive per endpoint, in seconds"
226 );
227
228 describe_histogram!(
229 HISTOGRAM_QUERY_DURATION,
230 metrics::Unit::Seconds,
231 "Duration of query request execution inside Drive per endpoint, in seconds"
232 );
233
234 describe_gauge!(
235 GAUGE_CREDIT_WITHDRAWAL_LIMIT_AVAILABLE,
236 "Available withdrawal limit for last 24 hours in credits"
237 );
238
239 describe_gauge!(
240 GAUGE_CREDIT_WITHDRAWAL_LIMIT_TOTAL,
241 "Total withdrawal limit for last 24 hours in credits"
242 );
243
244 describe_gauge!(
245 GAUGE_CREDIT_WITHDRAWAL_LIMIT_CORE_AVAILABLE,
246 "Credits withdrawals may still take from Core's credit pool, by the stricter copy of Core's unlock limit"
247 );
248 });
249 }
250}
251
252pub fn abci_last_platform_height(height: u64) {
263 counter!(COUNTER_LAST_HEIGHT).absolute(height);
264}
265
266pub fn abci_last_finalized_round(round: u32) {
268 histogram!(HISTOGRAM_FINALIZED_ROUND).record(round as f64);
269}
270
271pub fn abci_last_block_time(time: u64) {
273 counter!(COUNTER_LAST_BLOCK_TIME).absolute(time);
274}
275
276pub fn abci_last_checkpoint_height(height: u64) {
278 counter!(COUNTER_LAST_CHECKPOINT_HEIGHT).absolute(height);
279}
280
281pub fn abci_checkpoint_failed() {
283 counter!(COUNTER_CHECKPOINT_FAILURES).increment(1);
284}
285
286pub fn abci_request_duration(endpoint: &str) -> HistogramTiming {
305 let labels = vec![Label::new(LABEL_ENDPOINT, endpoint.to_string())];
306 HistogramTiming::new(
307 metrics::Key::from_name(HISTOGRAM_ABCI_REQUEST_DURATION).with_extra_labels(labels),
308 )
309}
310
311pub fn query_duration_metric(endpoint: &str) -> HistogramTiming {
330 let labels = vec![endpoint_metric_label(endpoint)];
331 HistogramTiming::new(
332 metrics::Key::from_name(HISTOGRAM_QUERY_DURATION).with_extra_labels(labels),
333 )
334}
335
336pub fn abci_response_code_metric_label(code: Code) -> Label {
338 Label::new(
339 LABEL_ABCI_RESPONSE_CODE,
340 format!("{:?}", code).to_lowercase(),
341 )
342}
343
344pub fn endpoint_metric_label(name: &str) -> Label {
346 Label::new(LABEL_ENDPOINT, name.to_string())
347}
348
349pub fn state_transition_execution_histogram(
351 elapsed_time: Duration,
352 state_transition_name: &str,
353 code: u32,
354) {
355 histogram!(
356 HISTOGRAM_STATE_TRANSITION_PROCESSING_DURATION,
357 vec![
358 Label::new(
359 LABEL_STATE_TRANSITION_NAME,
360 state_transition_name.to_string()
361 ),
362 Label::new(LABEL_STATE_TRANSITION_EXECUTION_CODE, code.to_string())
363 ],
364 )
365 .record(elapsed_time.as_secs_f64());
366}