referrerpolicy=no-referrer-when-downgrade

polkadot_node_metrics/runtime/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Runtime Metrics helpers.
18//!
19//! A runtime metric provider implementation that builds on top of Substrate wasm
20//! tracing support. This requires that the custom profiler (`TraceHandler`) to be
21//! registered in substrate via a `logger_hook()`. Events emitted from runtime are
22//! then captured/processed by the `TraceHandler` implementation.
23//!
24//! Don't add logs in this file because it gets executed before the logger is
25//! initialized and they won't be delivered. Add println! statements if you need
26//! to debug this code.
27
28#![cfg(feature = "runtime-metrics")]
29
30use codec::Decode;
31use polkadot_primitives::{
32	metric_definitions::{CounterDefinition, CounterVecDefinition, HistogramDefinition},
33	RuntimeMetricLabelValues, RuntimeMetricOp, RuntimeMetricUpdate,
34};
35use prometheus_endpoint::{
36	register, Counter, CounterVec, Histogram, HistogramOpts, Opts, PrometheusError, Registry, U64,
37};
38use std::{
39	collections::hash_map::HashMap,
40	sync::{Arc, Mutex, MutexGuard},
41};
42mod parachain;
43
44/// Holds the registered Prometheus metric collections.
45#[derive(Clone, Default)]
46pub struct Metrics {
47	counter_vecs: Arc<Mutex<HashMap<String, CounterVec<U64>>>>,
48	counters: Arc<Mutex<HashMap<String, Counter<U64>>>>,
49	histograms: Arc<Mutex<HashMap<String, Histogram>>>,
50}
51
52/// Runtime metrics wrapper.
53#[derive(Clone)]
54pub struct RuntimeMetricsProvider(Registry, Metrics);
55
56impl RuntimeMetricsProvider {
57	/// Creates new instance.
58	pub fn new(metrics_registry: Registry) -> Self {
59		Self(metrics_registry, Metrics::default())
60	}
61
62	/// Register a counter vec metric.
63	pub fn register_countervec(&self, countervec: CounterVecDefinition) {
64		self.with_counter_vecs_lock_held(|mut hashmap| {
65			hashmap.entry(countervec.name.to_owned()).or_insert(register(
66				CounterVec::new(
67					Opts::new(countervec.name, countervec.description),
68					countervec.labels,
69				)?,
70				&self.0,
71			)?);
72			Ok(())
73		})
74	}
75
76	/// Register a counter metric.
77	pub fn register_counter(&self, counter: CounterDefinition) {
78		self.with_counters_lock_held(|mut hashmap| {
79			hashmap
80				.entry(counter.name.to_owned())
81				.or_insert(register(Counter::new(counter.name, counter.description)?, &self.0)?);
82			return Ok(())
83		})
84	}
85
86	/// Register a histogram metric
87	pub fn register_histogram(&self, hist: HistogramDefinition) {
88		self.with_histograms_lock_held(|mut hashmap| {
89			hashmap.entry(hist.name.to_owned()).or_insert(register(
90				Histogram::with_opts(
91					HistogramOpts::new(hist.name, hist.description).buckets(hist.buckets.to_vec()),
92				)?,
93				&self.0,
94			)?);
95			return Ok(())
96		})
97	}
98
99	/// Increment a counter with labels by a value
100	pub fn inc_counter_vec_by(&self, name: &str, value: u64, labels: &RuntimeMetricLabelValues) {
101		self.with_counter_vecs_lock_held(|mut hashmap| {
102			hashmap.entry(name.to_owned()).and_modify(|counter_vec| {
103				counter_vec.with_label_values(&labels.as_str_vec()).inc_by(value)
104			});
105
106			Ok(())
107		});
108	}
109
110	/// Increment a counter by a value.
111	pub fn inc_counter_by(&self, name: &str, value: u64) {
112		self.with_counters_lock_held(|mut hashmap| {
113			hashmap
114				.entry(name.to_owned())
115				.and_modify(|counter_vec| counter_vec.inc_by(value));
116			Ok(())
117		})
118	}
119
120	/// Observe a histogram. `value` should be in `ns`.
121	pub fn observe_histogram(&self, name: &str, value: u128) {
122		self.with_histograms_lock_held(|mut hashmap| {
123			hashmap
124				.entry(name.to_owned())
125				.and_modify(|histogram| histogram.observe(value as f64 / 1_000_000_000.0)); // ns to sec
126			Ok(())
127		})
128	}
129
130	fn with_counters_lock_held<F>(&self, do_something: F)
131	where
132		F: FnOnce(MutexGuard<'_, HashMap<String, Counter<U64>>>) -> Result<(), PrometheusError>,
133	{
134		let _ = self.1.counters.lock().map(do_something).or_else(|error| Err(error));
135	}
136
137	fn with_counter_vecs_lock_held<F>(&self, do_something: F)
138	where
139		F: FnOnce(MutexGuard<'_, HashMap<String, CounterVec<U64>>>) -> Result<(), PrometheusError>,
140	{
141		let _ = self.1.counter_vecs.lock().map(do_something).or_else(|error| Err(error));
142	}
143
144	fn with_histograms_lock_held<F>(&self, do_something: F)
145	where
146		F: FnOnce(MutexGuard<'_, HashMap<String, Histogram>>) -> Result<(), PrometheusError>,
147	{
148		let _ = self.1.histograms.lock().map(do_something).or_else(|error| Err(error));
149	}
150}
151
152impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
153	fn handle_span(&self, _span: &sc_tracing::SpanDatum) {}
154	fn handle_event(&self, event: &sc_tracing::TraceEvent) {
155		if event
156			.values
157			.string_values
158			.get("target")
159			.unwrap_or(&String::default())
160			.ne("metrics")
161		{
162			return
163		}
164
165		if let Some(update_op_bs58) = event.values.string_values.get("params") {
166			// Deserialize the metric update struct.
167			match RuntimeMetricUpdate::decode(
168				&mut RuntimeMetricsProvider::parse_event_params(&update_op_bs58)
169					.unwrap_or_default()
170					.as_slice(),
171			) {
172				Ok(update_op) => {
173					self.parse_metric_update(update_op);
174				},
175				Err(_) => {
176					// do nothing
177				},
178			}
179		}
180	}
181}
182
183impl RuntimeMetricsProvider {
184	// Parse end execute the update operation.
185	fn parse_metric_update(&self, update: RuntimeMetricUpdate) {
186		match update.op {
187			RuntimeMetricOp::IncrementCounterVec(value, ref labels) =>
188				self.inc_counter_vec_by(update.metric_name(), value, labels),
189			RuntimeMetricOp::IncrementCounter(value) =>
190				self.inc_counter_by(update.metric_name(), value),
191			RuntimeMetricOp::ObserveHistogram(value) =>
192				self.observe_histogram(update.metric_name(), value),
193		}
194	}
195
196	// Returns the `bs58` encoded metric update operation.
197	fn parse_event_params(event_params: &str) -> Option<Vec<u8>> {
198		// Shave " }" suffix.
199		let new_len = event_params.len().saturating_sub(2);
200		let event_params = &event_params[..new_len];
201
202		// Shave " { update_op: " prefix.
203		const SKIP_CHARS: &'static str = " { update_op: ";
204		if SKIP_CHARS.len() < event_params.len() {
205			if SKIP_CHARS.eq_ignore_ascii_case(&event_params[..SKIP_CHARS.len()]) {
206				return bs58::decode(&event_params[SKIP_CHARS.len()..].as_bytes()).into_vec().ok()
207			}
208		}
209
210		// No event was parsed
211		None
212	}
213}
214
215/// Returns the custom profiling closure that we'll apply to the `LoggerBuilder`.
216pub fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
217	|logger_builder, config| {
218		if config.prometheus_registry().is_none() {
219			return
220		}
221		let registry = config.prometheus_registry().cloned().unwrap();
222		let metrics_provider = RuntimeMetricsProvider::new(registry);
223		parachain::register_metrics(&metrics_provider);
224		logger_builder.with_custom_profiling(Box::new(metrics_provider));
225	}
226}