referrerpolicy=no-referrer-when-downgrade

sc_network/service/
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
19use crate::{service::traits::BandwidthSink, ProtocolName};
20
21use prometheus_endpoint::{
22	self as prometheus, Counter, CounterVec, Gauge, GaugeVec, HistogramOpts, MetricSource, Opts,
23	PrometheusError, Registry, SourcedCounter, U64,
24};
25
26use std::{str, sync::Arc};
27
28pub use prometheus_endpoint::{Histogram, HistogramVec};
29
30/// Registers all networking metrics with the given registry.
31pub fn register(registry: &Registry, sources: MetricSources) -> Result<Metrics, PrometheusError> {
32	BandwidthCounters::register(registry, sources.bandwidth)?;
33	Metrics::register(registry)
34}
35
36// Register `sc-network` metrics without the bandwidth source.
37pub fn register_without_sources(registry: &Registry) -> Result<Metrics, PrometheusError> {
38	Metrics::register(registry)
39}
40
41/// Predefined metric sources that are fed directly into prometheus.
42pub struct MetricSources {
43	pub bandwidth: Arc<dyn BandwidthSink>,
44}
45
46impl MetricSources {
47	pub fn register(
48		registry: &Registry,
49		bandwidth: Arc<dyn BandwidthSink>,
50	) -> Result<(), PrometheusError> {
51		BandwidthCounters::register(registry, bandwidth)
52	}
53}
54
55/// Dedicated metrics.
56#[derive(Clone)]
57pub struct Metrics {
58	// This list is ordered alphabetically
59	pub connections_closed_total: CounterVec<U64>,
60	pub connections_opened_total: CounterVec<U64>,
61	pub distinct_peers_connections_closed_total: Counter<U64>,
62	pub distinct_peers_connections_opened_total: Counter<U64>,
63	pub incoming_connections_errors_total: CounterVec<U64>,
64	pub incoming_connections_total: Counter<U64>,
65	pub kademlia_query_duration: HistogramVec,
66	pub kademlia_random_queries_total: Counter<U64>,
67	pub kademlia_records_count: Gauge<U64>,
68	pub kademlia_records_sizes_total: Gauge<U64>,
69	pub kbuckets_num_nodes: GaugeVec<U64>,
70	pub listeners_local_addresses: Gauge<U64>,
71	pub listeners_errors_total: Counter<U64>,
72	pub pending_connections: Gauge<U64>,
73	pub pending_connections_errors_total: CounterVec<U64>,
74	pub requests_in_failure_total: CounterVec<U64>,
75	pub requests_in_success_total: HistogramVec,
76	pub requests_out_failure_total: CounterVec<U64>,
77	pub requests_out_success_total: HistogramVec,
78	pub requests_response_bytes_total: CounterVec<U64>,
79}
80
81impl Metrics {
82	fn register(registry: &Registry) -> Result<Self, PrometheusError> {
83		Ok(Self {
84			// This list is ordered alphabetically
85			connections_closed_total: prometheus::register(CounterVec::new(
86				Opts::new(
87					"substrate_sub_libp2p_connections_closed_total",
88					"Total number of connections closed, by direction and reason"
89				),
90				&["direction", "reason"]
91			)?, registry)?,
92			connections_opened_total: prometheus::register(CounterVec::new(
93				Opts::new(
94					"substrate_sub_libp2p_connections_opened_total",
95					"Total number of connections opened by direction"
96				),
97				&["direction"]
98			)?, registry)?,
99			distinct_peers_connections_closed_total: prometheus::register(Counter::new(
100					"substrate_sub_libp2p_distinct_peers_connections_closed_total",
101					"Total number of connections closed with distinct peers"
102			)?, registry)?,
103			distinct_peers_connections_opened_total: prometheus::register(Counter::new(
104					"substrate_sub_libp2p_distinct_peers_connections_opened_total",
105					"Total number of connections opened with distinct peers"
106			)?, registry)?,
107			incoming_connections_errors_total: prometheus::register(CounterVec::new(
108				Opts::new(
109					"substrate_sub_libp2p_incoming_connections_handshake_errors_total",
110					"Total number of incoming connections that have failed during the \
111					initial handshake"
112				),
113				&["reason"]
114			)?, registry)?,
115			incoming_connections_total: prometheus::register(Counter::new(
116				"substrate_sub_libp2p_incoming_connections_total",
117				"Total number of incoming connections on the listening sockets"
118			)?, registry)?,
119			kademlia_query_duration: prometheus::register(HistogramVec::new(
120				HistogramOpts {
121					common_opts: Opts::new(
122						"substrate_sub_libp2p_kademlia_query_duration",
123						"Duration of Kademlia queries per query type"
124					),
125					buckets: prometheus::exponential_buckets(0.5, 2.0, 10)
126						.expect("parameters are always valid values; qed"),
127				},
128				&["type"]
129			)?, registry)?,
130			kademlia_random_queries_total: prometheus::register(Counter::new(
131				"substrate_sub_libp2p_kademlia_random_queries_total",
132				"Number of random Kademlia queries started",
133			)?, registry)?,
134			kademlia_records_count: prometheus::register(Gauge::new(
135				"substrate_sub_libp2p_kademlia_records_count",
136				"Number of records in the Kademlia records store",
137			)?, registry)?,
138			kademlia_records_sizes_total: prometheus::register(Gauge::new(
139				"substrate_sub_libp2p_kademlia_records_sizes_total",
140				"Total size of all the records in the Kademlia records store",
141			)?, registry)?,
142			kbuckets_num_nodes: prometheus::register(GaugeVec::new(
143				Opts::new(
144					"substrate_sub_libp2p_kbuckets_num_nodes",
145					"Number of nodes per kbucket per Kademlia instance"
146				),
147				&["lower_ilog2_bucket_bound"]
148			)?, registry)?,
149			listeners_local_addresses: prometheus::register(Gauge::new(
150				"substrate_sub_libp2p_listeners_local_addresses",
151				"Number of local addresses we're listening on"
152			)?, registry)?,
153			listeners_errors_total: prometheus::register(Counter::new(
154				"substrate_sub_libp2p_listeners_errors_total",
155				"Total number of non-fatal errors reported by a listener"
156			)?, registry)?,
157			pending_connections: prometheus::register(Gauge::new(
158				"substrate_sub_libp2p_pending_connections",
159				"Number of connections in the process of being established",
160			)?, registry)?,
161			pending_connections_errors_total: prometheus::register(CounterVec::new(
162				Opts::new(
163					"substrate_sub_libp2p_pending_connections_errors_total",
164					"Total number of pending connection errors"
165				),
166				&["reason"]
167			)?, registry)?,
168			requests_in_failure_total: prometheus::register(CounterVec::new(
169				Opts::new(
170					"substrate_sub_libp2p_requests_in_failure_total",
171					"Total number of incoming requests that the node has failed to answer"
172				),
173				&["protocol", "reason"]
174			)?, registry)?,
175			requests_in_success_total: prometheus::register(HistogramVec::new(
176				HistogramOpts {
177					common_opts: Opts::new(
178						"substrate_sub_libp2p_requests_in_success_total",
179						"For successful incoming requests, time between receiving the request and \
180						 starting to send the response"
181					),
182					buckets: prometheus::exponential_buckets(0.001, 2.0, 16)
183						.expect("parameters are always valid values; qed"),
184				},
185				&["protocol"]
186			)?, registry)?,
187			requests_out_failure_total: prometheus::register(CounterVec::new(
188				Opts::new(
189					"substrate_sub_libp2p_requests_out_failure_total",
190					"Total number of requests that have failed"
191				),
192				&["protocol", "reason"]
193			)?, registry)?,
194			requests_out_success_total: prometheus::register(HistogramVec::new(
195				HistogramOpts {
196					common_opts: Opts::new(
197						"substrate_sub_libp2p_requests_out_success_total",
198						"For successful outgoing requests, time between a request's start and finish"
199					),
200					buckets: prometheus::exponential_buckets(0.001, 2.0, 16)
201						.expect("parameters are always valid values; qed"),
202				},
203				&["protocol"]
204			)?, registry)?,
205			requests_response_bytes_total: prometheus::register(CounterVec::new(
206				Opts::new(
207					"substrate_sub_libp2p_requests_response_bytes_total",
208					"Total bytes sent and received by request-response protocols"
209				),
210				&["direction", "protocol"]
211			)?, registry)?,
212		})
213	}
214}
215
216/// Peer store metrics.
217#[derive(Clone, Debug)]
218pub struct PeerStoreMetrics {
219	pub num_banned_peers: Gauge<U64>,
220	pub num_discovered: Gauge<U64>,
221}
222
223impl PeerStoreMetrics {
224	pub fn register(registry: &Registry) -> Result<Self, PrometheusError> {
225		Ok(Self {
226			num_banned_peers: prometheus::register(
227				Gauge::new(
228					"substrate_sub_libp2p_peerset_num_banned_peers",
229					"Number of banned peers stored in the peerset manager",
230				)?,
231				registry,
232			)?,
233			num_discovered: prometheus::register(
234				Gauge::new(
235					"substrate_sub_libp2p_peerset_num_discovered",
236					"Number of nodes stored in the peerset manager",
237				)?,
238				registry,
239			)?,
240		})
241	}
242}
243
244/// The bandwidth counter metric.
245#[derive(Clone)]
246pub struct BandwidthCounters(Arc<dyn BandwidthSink>);
247
248impl BandwidthCounters {
249	/// Registers the `BandwidthCounters` metric whose values are
250	/// obtained from the given sinks.
251	fn register(registry: &Registry, sinks: Arc<dyn BandwidthSink>) -> Result<(), PrometheusError> {
252		prometheus::register(
253			SourcedCounter::new(
254				&Opts::new("substrate_sub_libp2p_network_bytes_total", "Total bandwidth usage")
255					.variable_label("direction"),
256				BandwidthCounters(sinks),
257			)?,
258			registry,
259		)?;
260
261		Ok(())
262	}
263}
264
265impl MetricSource for BandwidthCounters {
266	type N = u64;
267
268	fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) {
269		set(&["in"], self.0.total_inbound());
270		set(&["out"], self.0.total_outbound());
271	}
272}
273
274/// Notification metrics.
275///
276/// Wrapper over `Option<InnerNotificationMetrics>` to make metrics reporting code cleaner.
277#[derive(Debug, Clone)]
278pub struct NotificationMetrics {
279	/// Metrics, if enabled.
280	metrics: Option<InnerNotificationMetrics>,
281}
282
283impl NotificationMetrics {
284	/// Create new [`NotificationMetrics`].
285	pub fn new(registry: Option<&Registry>) -> NotificationMetrics {
286		let metrics = match registry {
287			Some(registry) => InnerNotificationMetrics::register(registry).ok(),
288			None => None,
289		};
290
291		Self { metrics }
292	}
293
294	/// Register opened substream to Prometheus.
295	pub fn register_substream_opened(&self, protocol: &ProtocolName) {
296		if let Some(metrics) = &self.metrics {
297			metrics.notifications_streams_opened_total.with_label_values(&[&protocol]).inc();
298		}
299	}
300
301	/// Register closed substream to Prometheus.
302	pub fn register_substream_closed(&self, protocol: &ProtocolName) {
303		if let Some(metrics) = &self.metrics {
304			metrics
305				.notifications_streams_closed_total
306				.with_label_values(&[&protocol[..]])
307				.inc();
308		}
309	}
310
311	/// Register sent notification to Prometheus.
312	pub fn register_notification_sent(&self, protocol: &ProtocolName, size: usize) {
313		if let Some(metrics) = &self.metrics {
314			metrics
315				.notifications_sizes
316				.with_label_values(&["out", protocol])
317				.observe(size as f64);
318		}
319	}
320
321	/// Register received notification to Prometheus.
322	pub fn register_notification_received(&self, protocol: &ProtocolName, size: usize) {
323		if let Some(metrics) = &self.metrics {
324			metrics
325				.notifications_sizes
326				.with_label_values(&["in", protocol])
327				.observe(size as f64);
328		}
329	}
330
331	/// Update the number of connected peers per direction and reservation status.
332	pub fn set_peerset_num_connected(
333		&self,
334		protocol: &ProtocolName,
335		in_reserved: usize,
336		in_non_reserved: usize,
337		out_reserved: usize,
338		out_non_reserved: usize,
339		num_disconnected: usize,
340		num_backoff: usize,
341	) {
342		if let Some(metrics) = &self.metrics {
343			metrics
344				.peerset_num_connected
345				.with_label_values(&["in", "reserved", protocol])
346				.set(in_reserved as u64);
347			metrics
348				.peerset_num_connected
349				.with_label_values(&["in", "non-reserved", protocol])
350				.set(in_non_reserved as u64);
351			metrics
352				.peerset_num_connected
353				.with_label_values(&["out", "reserved", protocol])
354				.set(out_reserved as u64);
355			metrics
356				.peerset_num_connected
357				.with_label_values(&["out", "non-reserved", protocol])
358				.set(out_non_reserved as u64);
359
360			metrics
361				.peerset_num_state
362				.with_label_values(&["disconnected", protocol])
363				.set(num_disconnected as u64);
364			metrics
365				.peerset_num_state
366				.with_label_values(&["backoff", protocol])
367				.set(num_backoff as u64);
368		}
369	}
370}
371
372/// Notification metrics.
373#[derive(Debug, Clone)]
374struct InnerNotificationMetrics {
375	// Total number of opened substreams.
376	pub notifications_streams_opened_total: CounterVec<U64>,
377
378	/// Total number of closed substreams.
379	pub notifications_streams_closed_total: CounterVec<U64>,
380
381	/// In/outbound notification sizes.
382	pub notifications_sizes: HistogramVec,
383
384	/// Number of connected peers per direction, reservation status and protocol.
385	pub peerset_num_connected: GaugeVec<U64>,
386
387	/// Number of disconnected and backed off peers.
388	pub peerset_num_state: GaugeVec<U64>,
389}
390
391impl InnerNotificationMetrics {
392	fn register(registry: &Registry) -> Result<Self, PrometheusError> {
393		Ok(Self {
394			notifications_sizes: prometheus::register(
395				HistogramVec::new(
396					HistogramOpts {
397						common_opts: Opts::new(
398							"substrate_sub_libp2p_notifications_sizes",
399							"Sizes of the notifications send to and received from all nodes",
400						),
401						buckets: prometheus::exponential_buckets(64.0, 4.0, 8)
402							.expect("parameters are always valid values; qed"),
403					},
404					&["direction", "protocol"],
405				)?,
406				registry,
407			)?,
408			notifications_streams_closed_total: prometheus::register(
409				CounterVec::new(
410					Opts::new(
411						"substrate_sub_libp2p_notifications_streams_closed_total",
412						"Total number of notification substreams that have been closed",
413					),
414					&["protocol"],
415				)?,
416				registry,
417			)?,
418			notifications_streams_opened_total: prometheus::register(
419				CounterVec::new(
420					Opts::new(
421						"substrate_sub_libp2p_notifications_streams_opened_total",
422						"Total number of notification substreams that have been opened",
423					),
424					&["protocol"],
425				)?,
426				registry,
427			)?,
428			peerset_num_connected: prometheus::register(
429				GaugeVec::new(
430					Opts::new(
431						"substrate_sub_libp2p_peerset_num_connected",
432						"Number of connected peers per direction, reservation status and protocol",
433					),
434					&["direction", "kind", "protocol"],
435				)?,
436				registry,
437			)?,
438			peerset_num_state: prometheus::register(
439				GaugeVec::new(
440					Opts::new(
441						"substrate_sub_libp2p_peerset_num_state",
442						"Number of peers per state in the peerset manager",
443					),
444					&["state", "protocol"],
445				)?,
446				registry,
447			)?,
448		})
449	}
450}