referrerpolicy=no-referrer-when-downgrade

polkadot_subsystem_bench/
display.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//! Display implementations and helper methods for parsing prometheus metrics
18//! to a format that can be displayed in the CLI.
19//!
20//! Currently histogram buckets are skipped.
21
22use crate::configuration::TestConfiguration;
23use colored::Colorize;
24use prometheus::{
25	proto::{MetricFamily, MetricType},
26	Registry,
27};
28use std::fmt::Display;
29
30const LOG_TARGET: &str = "subsystem-bench::display";
31
32#[derive(Default, Debug)]
33pub struct MetricCollection(Vec<TestMetric>);
34
35impl From<Vec<TestMetric>> for MetricCollection {
36	fn from(metrics: Vec<TestMetric>) -> Self {
37		MetricCollection(metrics)
38	}
39}
40
41impl MetricCollection {
42	pub fn all(&self) -> &Vec<TestMetric> {
43		&self.0
44	}
45
46	/// Sums up all metrics with the given name in the collection
47	pub fn sum_by(&self, name: &str) -> f64 {
48		self.all()
49			.iter()
50			.filter(|metric| metric.name == name)
51			.map(|metric| metric.value)
52			.sum()
53	}
54
55	/// Tells if entries in bucket metric is lower than `value`
56	pub fn metric_lower_than(&self, metric_name: &str, value: f64) -> bool {
57		self.sum_by(metric_name) < value
58	}
59
60	pub fn subset_with_label_value(&self, label_name: &str, label_value: &str) -> MetricCollection {
61		self.0
62			.iter()
63			.filter_map(|metric| {
64				if let Some(index) = metric.label_names.iter().position(|label| label == label_name)
65				{
66					if Some(&String::from(label_value)) == metric.label_values.get(index) {
67						Some(metric.clone())
68					} else {
69						None
70					}
71				} else {
72					None
73				}
74			})
75			.collect::<Vec<_>>()
76			.into()
77	}
78}
79
80impl Display for MetricCollection {
81	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82		writeln!(f)?;
83		let metrics = self.all();
84		for metric in metrics {
85			writeln!(f, "{metric}")?;
86		}
87		Ok(())
88	}
89}
90
91#[derive(Debug, Clone)]
92pub struct TestMetric {
93	name: String,
94	label_names: Vec<String>,
95	label_values: Vec<String>,
96	value: f64,
97}
98
99impl TestMetric {
100	pub fn name(&self) -> &str {
101		&self.name
102	}
103
104	pub fn value(&self) -> f64 {
105		self.value
106	}
107
108	pub fn label_value(&self, label_name: &str) -> Option<&str> {
109		self.label_names
110			.iter()
111			.position(|name| name == label_name)
112			.and_then(|index| self.label_values.get(index).map(|s| s.as_str()))
113	}
114}
115
116impl Display for TestMetric {
117	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118		write!(
119			f,
120			"({} = {}) [{:?}, {:?}]",
121			self.name.cyan(),
122			format!("{}", self.value).white(),
123			self.label_names,
124			self.label_values
125		)
126	}
127}
128
129// Returns `false` if metric should be skipped.
130fn check_metric_family(mf: &MetricFamily) -> bool {
131	if mf.get_metric().is_empty() {
132		gum::error!(target: LOG_TARGET, "MetricFamily has no metrics: {:?}", mf);
133		return false
134	}
135	if mf.get_name().is_empty() {
136		gum::error!(target: LOG_TARGET, "MetricFamily has no name: {:?}", mf);
137		return false
138	}
139
140	true
141}
142
143pub fn parse_metrics(registry: &Registry) -> MetricCollection {
144	let metric_families = registry.gather();
145	let mut test_metrics = Vec::new();
146	for mf in metric_families {
147		if !check_metric_family(&mf) {
148			continue
149		}
150
151		let name: String = mf.get_name().into();
152		let metric_type = mf.get_field_type();
153		for m in mf.get_metric() {
154			let (label_names, label_values): (Vec<String>, Vec<String>) = m
155				.get_label()
156				.iter()
157				.map(|pair| (String::from(pair.get_name()), String::from(pair.get_value())))
158				.unzip();
159
160			match metric_type {
161				MetricType::COUNTER => {
162					test_metrics.push(TestMetric {
163						name: name.clone(),
164						label_names,
165						label_values,
166						value: m.get_counter().get_value(),
167					});
168				},
169				MetricType::GAUGE => {
170					test_metrics.push(TestMetric {
171						name: name.clone(),
172						label_names,
173						label_values,
174						value: m.get_gauge().get_value(),
175					});
176				},
177				MetricType::HISTOGRAM => {
178					let h = m.get_histogram();
179					let h_name = name.clone() + "_sum";
180					test_metrics.push(TestMetric {
181						name: h_name,
182						label_names: label_names.clone(),
183						label_values: label_values.clone(),
184						value: h.get_sample_sum(),
185					});
186
187					let h_name = name.clone() + "_count";
188					test_metrics.push(TestMetric {
189						name: h_name,
190						label_names,
191						label_values,
192						value: h.get_sample_count() as f64,
193					});
194				},
195				MetricType::SUMMARY => {
196					unimplemented!();
197				},
198				MetricType::UNTYPED => {
199					unimplemented!();
200				},
201			}
202		}
203	}
204	test_metrics.into()
205}
206
207impl Display for TestConfiguration {
208	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209		write!(
210			f,
211			"{}, {}, {}, {}, {}",
212			format!("n_validators = {}", self.n_validators).blue(),
213			format!("n_cores = {}", self.n_cores).blue(),
214			format!("pov_size = {} - {}", self.min_pov_size, self.max_pov_size).bright_black(),
215			format!("connectivity = {}", self.connectivity).bright_black(),
216			format!("latency = {:?}", self.latency).bright_black(),
217		)
218	}
219}