referrerpolicy=no-referrer-when-downgrade

sc_rpc_server/middleware/
metrics.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! RPC middleware to collect prometheus metrics on RPC calls.
20
21use std::time::Instant;
22
23use jsonrpsee::{types::Request, MethodResponse};
24use prometheus_endpoint::{
25	register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry,
26	U64,
27};
28
29/// Histogram time buckets in microseconds.
30const HISTOGRAM_BUCKETS: [f64; 11] = [
31	5.0,
32	25.0,
33	100.0,
34	500.0,
35	1_000.0,
36	2_500.0,
37	10_000.0,
38	25_000.0,
39	100_000.0,
40	1_000_000.0,
41	10_000_000.0,
42];
43
44/// Metrics for RPC middleware storing information about the number of requests started/completed,
45/// calls started/completed and their timings.
46#[derive(Debug, Clone)]
47pub struct RpcMetrics {
48	/// Histogram over RPC execution times.
49	calls_time: HistogramVec,
50	/// Number of calls started.
51	calls_started: CounterVec<U64>,
52	/// Number of calls completed.
53	calls_finished: CounterVec<U64>,
54	/// Number of Websocket sessions opened.
55	ws_sessions_opened: Option<Counter<U64>>,
56	/// Number of Websocket sessions closed.
57	ws_sessions_closed: Option<Counter<U64>>,
58	/// Histogram over RPC websocket sessions.
59	ws_sessions_time: HistogramVec,
60}
61
62impl RpcMetrics {
63	/// Create an instance of metrics
64	pub fn new(metrics_registry: Option<&Registry>) -> Result<Option<Self>, PrometheusError> {
65		if let Some(metrics_registry) = metrics_registry {
66			Ok(Some(Self {
67				calls_time: register(
68					HistogramVec::new(
69						HistogramOpts::new(
70							"substrate_rpc_calls_time",
71							"Total time [μs] of processed RPC calls",
72						)
73						.buckets(HISTOGRAM_BUCKETS.to_vec()),
74						&["protocol", "method", "is_rate_limited"],
75					)?,
76					metrics_registry,
77				)?,
78				calls_started: register(
79					CounterVec::new(
80						Opts::new(
81							"substrate_rpc_calls_started",
82							"Number of received RPC calls (unique un-batched requests)",
83						),
84						&["protocol", "method"],
85					)?,
86					metrics_registry,
87				)?,
88				calls_finished: register(
89					CounterVec::new(
90						Opts::new(
91							"substrate_rpc_calls_finished",
92							"Number of processed RPC calls (unique un-batched requests)",
93						),
94						&["protocol", "method", "is_error", "is_rate_limited"],
95					)?,
96					metrics_registry,
97				)?,
98				ws_sessions_opened: register(
99					Counter::new(
100						"substrate_rpc_sessions_opened",
101						"Number of persistent RPC sessions opened",
102					)?,
103					metrics_registry,
104				)?
105				.into(),
106				ws_sessions_closed: register(
107					Counter::new(
108						"substrate_rpc_sessions_closed",
109						"Number of persistent RPC sessions closed",
110					)?,
111					metrics_registry,
112				)?
113				.into(),
114				ws_sessions_time: register(
115					HistogramVec::new(
116						HistogramOpts::new(
117							"substrate_rpc_sessions_time",
118							"Total time [s] for each websocket session",
119						)
120						.buckets(HISTOGRAM_BUCKETS.to_vec()),
121						&["protocol"],
122					)?,
123					metrics_registry,
124				)?,
125			}))
126		} else {
127			Ok(None)
128		}
129	}
130
131	pub(crate) fn ws_connect(&self) {
132		self.ws_sessions_opened.as_ref().map(|counter| counter.inc());
133	}
134
135	pub(crate) fn ws_disconnect(&self, now: Instant) {
136		let micros = now.elapsed().as_secs();
137
138		self.ws_sessions_closed.as_ref().map(|counter| counter.inc());
139		self.ws_sessions_time.with_label_values(&["ws"]).observe(micros as _);
140	}
141
142	pub(crate) fn on_call(&self, req: &Request, transport_label: &'static str) {
143		log::trace!(
144			target: "rpc_metrics",
145			"[{transport_label}] on_call name={} params={:?}",
146			req.method_name(),
147			req.params(),
148		);
149
150		self.calls_started
151			.with_label_values(&[transport_label, req.method_name()])
152			.inc();
153	}
154
155	pub(crate) fn on_response(
156		&self,
157		req: &Request,
158		rp: &MethodResponse,
159		is_rate_limited: bool,
160		transport_label: &'static str,
161		now: Instant,
162	) {
163		log::trace!(target: "rpc_metrics", "[{transport_label}] on_response started_at={:?}", now);
164		log::trace!(target: "rpc_metrics::extra", "[{transport_label}] result={}", rp.as_result());
165
166		let micros = now.elapsed().as_micros();
167		log::debug!(
168			target: "rpc_metrics",
169			"[{transport_label}] {} call took {} μs",
170			req.method_name(),
171			micros,
172		);
173		self.calls_time
174			.with_label_values(&[
175				transport_label,
176				req.method_name(),
177				if is_rate_limited { "true" } else { "false" },
178			])
179			.observe(micros as _);
180		self.calls_finished
181			.with_label_values(&[
182				transport_label,
183				req.method_name(),
184				// the label "is_error", so `success` should be regarded as false
185				// and vice-versa to be registered correctly.
186				if rp.is_success() { "false" } else { "true" },
187				if is_rate_limited { "true" } else { "false" },
188			])
189			.inc();
190	}
191}
192
193/// Metrics with transport label.
194#[derive(Clone, Debug)]
195pub struct Metrics {
196	pub(crate) inner: RpcMetrics,
197	pub(crate) transport_label: &'static str,
198}
199
200impl Metrics {
201	/// Create a new [`Metrics`].
202	pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self {
203		Self { inner: metrics, transport_label }
204	}
205
206	pub(crate) fn ws_connect(&self) {
207		self.inner.ws_connect();
208	}
209
210	pub(crate) fn ws_disconnect(&self, now: Instant) {
211		self.inner.ws_disconnect(now)
212	}
213
214	pub(crate) fn on_call(&self, req: &Request) {
215		self.inner.on_call(req, self.transport_label)
216	}
217
218	pub(crate) fn on_response(
219		&self,
220		req: &Request,
221		rp: &MethodResponse,
222		is_rate_limited: bool,
223		now: Instant,
224	) {
225		self.inner.on_response(req, rp, is_rate_limited, self.transport_label, now)
226	}
227}