Skip to main content

soil_rpc/v2/transaction/
metrics.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Metrics for recording transaction events.
8
9use std::{collections::HashSet, time::Instant};
10
11use soil_prometheus::{
12	exponential_buckets, linear_buckets, register, Histogram, HistogramOpts, PrometheusError,
13	Registry,
14};
15
16use super::TransactionEvent;
17
18/// RPC layer metrics for transaction pool.
19#[derive(Debug, Clone)]
20pub struct Metrics {
21	validated: Histogram,
22	in_block: Histogram,
23	finalized: Histogram,
24	dropped: Histogram,
25	invalid: Histogram,
26	error: Histogram,
27}
28
29impl Metrics {
30	/// Creates a new [`Metrics`] instance.
31	pub fn new(registry: &Registry) -> Result<Self, PrometheusError> {
32		let validated = register(
33			Histogram::with_opts(
34				HistogramOpts::new(
35					"rpc_transaction_validation_time",
36					"RPC Transaction validation time in seconds",
37				)
38				.buckets(exponential_buckets(0.01, 2.0, 16).expect("Valid buckets; qed")),
39			)?,
40			registry,
41		)?;
42
43		let in_block = register(
44			Histogram::with_opts(
45				HistogramOpts::new(
46					"rpc_transaction_in_block_time",
47					"RPC Transaction in block time in seconds",
48				)
49				.buckets(linear_buckets(0.0, 3.0, 20).expect("Valid buckets; qed")),
50			)?,
51			registry,
52		)?;
53
54		let finalized = register(
55			Histogram::with_opts(
56				HistogramOpts::new(
57					"rpc_transaction_finalized_time",
58					"RPC Transaction finalized time in seconds",
59				)
60				.buckets(linear_buckets(0.01, 40.0, 20).expect("Valid buckets; qed")),
61			)?,
62			registry,
63		)?;
64
65		let dropped = register(
66			Histogram::with_opts(
67				HistogramOpts::new(
68					"rpc_transaction_dropped_time",
69					"RPC Transaction dropped time in seconds",
70				)
71				.buckets(linear_buckets(0.01, 3.0, 20).expect("Valid buckets; qed")),
72			)?,
73			registry,
74		)?;
75
76		let invalid = register(
77			Histogram::with_opts(
78				HistogramOpts::new(
79					"rpc_transaction_invalid_time",
80					"RPC Transaction invalid time in seconds",
81				)
82				.buckets(linear_buckets(0.01, 3.0, 20).expect("Valid buckets; qed")),
83			)?,
84			registry,
85		)?;
86
87		let error = register(
88			Histogram::with_opts(
89				HistogramOpts::new(
90					"rpc_transaction_error_time",
91					"RPC Transaction error time in seconds",
92				)
93				.buckets(linear_buckets(0.01, 3.0, 20).expect("Valid buckets; qed")),
94			)?,
95			registry,
96		)?;
97
98		Ok(Metrics { validated, in_block, finalized, dropped, invalid, error })
99	}
100}
101
102/// Transaction metrics for a single transaction instance.
103pub struct InstanceMetrics {
104	/// The metrics instance.
105	metrics: Option<Metrics>,
106	/// The time when the transaction was submitted.
107	submitted_at: Instant,
108	/// Ensure the states are reported once.
109	reported_states: HashSet<&'static str>,
110}
111
112impl InstanceMetrics {
113	/// Creates a new [`InstanceMetrics`] instance.
114	pub fn new(metrics: Option<Metrics>) -> Self {
115		Self { metrics, submitted_at: Instant::now(), reported_states: HashSet::new() }
116	}
117
118	/// Record the execution time of a transaction state.
119	///
120	/// This represents how long it took for the transaction to move to the next state.
121	///
122	/// The method must be called before the transaction event is provided to the user.
123	pub fn register_event<Hash>(&mut self, event: &TransactionEvent<Hash>) {
124		let Some(ref metrics) = self.metrics else {
125			return;
126		};
127
128		let (histogram, target_state) = match event {
129			TransactionEvent::Validated => (&metrics.validated, "validated"),
130			TransactionEvent::BestChainBlockIncluded(Some(_)) => (&metrics.in_block, "in_block"),
131			TransactionEvent::BestChainBlockIncluded(None) => (&metrics.in_block, "retracted"),
132			TransactionEvent::Finalized(..) => (&metrics.finalized, "finalized"),
133			TransactionEvent::Error(..) => (&metrics.error, "error"),
134			TransactionEvent::Dropped(..) => (&metrics.dropped, "dropped"),
135			TransactionEvent::Invalid(..) => (&metrics.invalid, "invalid"),
136		};
137
138		// Only record the state if it hasn't been reported before.
139		if self.reported_states.insert(target_state) {
140			histogram.observe(self.submitted_at.elapsed().as_secs_f64());
141		}
142	}
143}