sc_rpc_server/middleware/
metrics.rs
1use 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
29const 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#[derive(Debug, Clone)]
47pub struct RpcMetrics {
48 calls_time: HistogramVec,
50 calls_started: CounterVec<U64>,
52 calls_finished: CounterVec<U64>,
54 ws_sessions_opened: Option<Counter<U64>>,
56 ws_sessions_closed: Option<Counter<U64>>,
58 ws_sessions_time: HistogramVec,
60}
61
62impl RpcMetrics {
63 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 if rp.is_success() { "false" } else { "true" },
187 if is_rate_limited { "true" } else { "false" },
188 ])
189 .inc();
190 }
191}
192
193#[derive(Clone, Debug)]
195pub struct Metrics {
196 pub(crate) inner: RpcMetrics,
197 pub(crate) transport_label: &'static str,
198}
199
200impl Metrics {
201 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}