1use polkadot_node_core_pvf_common::prepare::MemoryStats;
20use polkadot_node_metrics::metrics::{self, prometheus};
21use polkadot_node_subsystem::messages::PvfExecKind;
22
23#[derive(Default, Clone)]
25pub struct Metrics(Option<MetricsInner>);
26
27impl Metrics {
28 pub(crate) fn prepare_worker(&'_ self) -> WorkerRelatedMetrics<'_> {
30 WorkerRelatedMetrics { metrics: self, flavor: WorkerFlavor::Prepare }
31 }
32
33 pub(crate) fn execute_worker(&'_ self) -> WorkerRelatedMetrics<'_> {
35 WorkerRelatedMetrics { metrics: self, flavor: WorkerFlavor::Execute }
36 }
37
38 pub(crate) fn prepare_enqueued(&self) {
40 if let Some(metrics) = &self.0 {
41 metrics.prepare_enqueued.inc();
42 }
43 }
44
45 pub(crate) fn prepare_concluded(&self) {
47 if let Some(metrics) = &self.0 {
48 metrics.prepare_concluded.inc();
49 }
50 }
51
52 pub(crate) fn execute_enqueued(&self) {
54 if let Some(metrics) = &self.0 {
55 metrics.execute_enqueued.inc();
56 }
57 }
58
59 pub(crate) fn execute_finished(&self) {
61 if let Some(metrics) = &self.0 {
62 metrics.execute_finished.inc();
63 }
64 }
65
66 pub(crate) fn time_preparation(
68 &self,
69 ) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
70 self.0.as_ref().map(|metrics| metrics.preparation_time.start_timer())
71 }
72
73 pub(crate) fn time_execution(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
75 self.0.as_ref().map(|metrics| metrics.execution_time.start_timer())
76 }
77
78 pub(crate) fn observe_execution_queued_time(&self, queued_for_millis: u32) {
79 self.0.as_ref().map(|metrics| {
80 metrics.execution_queued_time.observe(queued_for_millis as f64 / 1000 as f64)
81 });
82 }
83
84 #[allow(unused_variables)]
86 pub(crate) fn observe_preparation_memory_metrics(&self, memory_stats: MemoryStats) {
87 if let Some(metrics) = &self.0 {
88 #[cfg(target_os = "linux")]
89 if let Some(max_rss) = memory_stats.max_rss {
90 metrics.preparation_max_rss.observe(max_rss as f64);
91 }
92
93 #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
94 if let Some(tracker_stats) = memory_stats.memory_tracker_stats {
95 let max_resident_kb = (tracker_stats.resident / 1024) as f64;
98 let max_allocated_kb = (tracker_stats.allocated / 1024) as f64;
99
100 metrics.preparation_max_resident.observe(max_resident_kb);
101 metrics.preparation_max_allocated.observe(max_allocated_kb);
102 }
103
104 metrics
105 .preparation_peak_tracked_allocation
106 .observe((memory_stats.peak_tracked_alloc / 1024) as f64);
107 }
108 }
109
110 pub(crate) fn observe_code_size(&self, code_size: usize) {
111 if let Some(metrics) = &self.0 {
112 metrics.code_size.observe(code_size as f64);
113 }
114 }
115
116 pub(crate) fn observe_pov_size(&self, pov_size: usize, compressed: bool) {
117 if let Some(metrics) = &self.0 {
118 metrics
119 .pov_size
120 .with_label_values(&[if compressed { "true" } else { "false" }])
121 .observe(pov_size as f64);
122 }
123 }
124
125 pub(crate) fn on_execute_kind(&self, kind: PvfExecKind) {
127 if let Some(metrics) = &self.0 {
128 metrics.exec_kind_selected.with_label_values(&[kind.as_str()]).inc();
129 }
130 }
131}
132
133#[derive(Clone)]
134struct MetricsInner {
135 worker_spawning: prometheus::CounterVec<prometheus::U64>,
136 worker_spawned: prometheus::CounterVec<prometheus::U64>,
137 worker_retired: prometheus::CounterVec<prometheus::U64>,
138 prepare_enqueued: prometheus::Counter<prometheus::U64>,
139 prepare_concluded: prometheus::Counter<prometheus::U64>,
140 execute_enqueued: prometheus::Counter<prometheus::U64>,
141 execute_finished: prometheus::Counter<prometheus::U64>,
142 preparation_time: prometheus::Histogram,
143 execution_time: prometheus::Histogram,
144 execution_queued_time: prometheus::Histogram,
145 #[cfg(target_os = "linux")]
146 preparation_max_rss: prometheus::Histogram,
147 #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
149 preparation_max_allocated: prometheus::Histogram,
150 #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
152 preparation_max_resident: prometheus::Histogram,
153 preparation_peak_tracked_allocation: prometheus::Histogram,
155 pov_size: prometheus::HistogramVec,
156 code_size: prometheus::Histogram,
157 exec_kind_selected: prometheus::CounterVec<prometheus::U64>,
158}
159
160impl metrics::Metrics for Metrics {
161 fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError> {
162 let inner = MetricsInner {
163 worker_spawning: prometheus::register(
164 prometheus::CounterVec::new(
165 prometheus::Opts::new(
166 "polkadot_pvf_worker_spawning",
167 "The total number of workers began to spawn",
168 ),
169 &["flavor"],
170 )?,
171 registry,
172 )?,
173 worker_spawned: prometheus::register(
174 prometheus::CounterVec::new(
175 prometheus::Opts::new(
176 "polkadot_pvf_worker_spawned",
177 "The total number of workers spawned successfully",
178 ),
179 &["flavor"],
180 )?,
181 registry,
182 )?,
183 worker_retired: prometheus::register(
184 prometheus::CounterVec::new(
185 prometheus::Opts::new(
186 "polkadot_pvf_worker_retired",
187 "The total number of workers retired, either killed by the host or died on duty",
188 ),
189 &["flavor"],
190 )?,
191 registry,
192 )?,
193 prepare_enqueued: prometheus::register(
194 prometheus::Counter::new(
195 "polkadot_pvf_prepare_enqueued",
196 "The total number of jobs enqueued into the preparation pipeline"
197 )?,
198 registry,
199 )?,
200 prepare_concluded: prometheus::register(
201 prometheus::Counter::new(
202 "polkadot_pvf_prepare_concluded",
203 "The total number of jobs concluded in the preparation pipeline"
204 )?,
205 registry,
206 )?,
207 execute_enqueued: prometheus::register(
208 prometheus::Counter::new(
209 "polkadot_pvf_execute_enqueued",
210 "The total number of jobs enqueued into the execution pipeline"
211 )?,
212 registry,
213 )?,
214 execute_finished: prometheus::register(
215 prometheus::Counter::new(
216 "polkadot_pvf_execute_finished",
217 "The total number of jobs done in the execution pipeline"
218 )?,
219 registry,
220 )?,
221 preparation_time: prometheus::register(
222 prometheus::Histogram::with_opts(
223 prometheus::HistogramOpts::new(
224 "polkadot_pvf_preparation_time",
225 "Time spent in preparing PVF artifacts in seconds",
226 )
227 .buckets(vec![
228 0.1,
232 0.5,
233 1.0,
234 2.0,
235 3.0,
236 10.0,
237 20.0,
238 30.0,
239 60.0,
240 120.0,
241 240.0,
242 360.0,
243 480.0,
244 ]),
245 )?,
246 registry,
247 )?,
248 execution_time: prometheus::register(
249 prometheus::Histogram::with_opts(
250 prometheus::HistogramOpts::new(
251 "polkadot_pvf_execution_time",
252 "Time spent in executing PVFs",
253 ).buckets(vec![
254 0.01,
258 0.025,
259 0.05,
260 0.1,
261 0.25,
262 0.5,
263 1.0,
264 2.0,
265 3.0,
266 4.0,
267 5.0,
268 6.0,
269 8.0,
270 10.0,
271 12.0,
272 ]),
273 )?,
274 registry,
275 )?,
276 execution_queued_time: prometheus::register(
277 prometheus::Histogram::with_opts(
278 prometheus::HistogramOpts::new(
279 "polkadot_pvf_execution_queued_time",
280 "Time spent in queue waiting for PVFs execution job to be assigned",
281 ).buckets(vec![
282 0.01,
283 0.025,
284 0.05,
285 0.1,
286 0.25,
287 0.5,
288 1.0,
289 2.0,
290 3.0,
291 4.0,
292 5.0,
293 6.0,
294 12.0,
295 24.0,
296 48.0,
297 ]),
298 )?,
299 registry,
300 )?,
301 #[cfg(target_os = "linux")]
302 preparation_max_rss: prometheus::register(
303 prometheus::Histogram::with_opts(
304 prometheus::HistogramOpts::new(
305 "polkadot_pvf_preparation_max_rss",
306 "ru_maxrss (maximum resident set size) observed for preparation (in kilobytes)",
307 ).buckets(
308 prometheus::exponential_buckets(8192.0, 2.0, 10)
309 .expect("arguments are always valid; qed"),
310 ),
311 )?,
312 registry,
313 )?,
314 #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
315 preparation_max_resident: prometheus::register(
316 prometheus::Histogram::with_opts(
317 prometheus::HistogramOpts::new(
318 "polkadot_pvf_preparation_max_resident",
319 "max resident memory observed for preparation (in kilobytes)",
320 ).buckets(
321 prometheus::exponential_buckets(8192.0, 2.0, 10)
322 .expect("arguments are always valid; qed"),
323 ),
324 )?,
325 registry,
326 )?,
327 #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
328 preparation_max_allocated: prometheus::register(
329 prometheus::Histogram::with_opts(
330 prometheus::HistogramOpts::new(
331 "polkadot_pvf_preparation_max_allocated",
332 "max allocated memory observed for preparation (in kilobytes)",
333 ).buckets(
334 prometheus::exponential_buckets(8192.0, 2.0, 10)
335 .expect("arguments are always valid; qed"),
336 ),
337 )?,
338 registry,
339 )?,
340 preparation_peak_tracked_allocation: prometheus::register(
341 prometheus::Histogram::with_opts(
342 prometheus::HistogramOpts::new(
343 "polkadot_pvf_preparation_peak_tracked_allocation",
344 "peak allocation observed for preparation (in kilobytes)",
345 ).buckets(
346 prometheus::exponential_buckets(8192.0, 2.0, 10)
347 .expect("arguments are always valid; qed"),
348 ),
349 )?,
350 registry,
351 )?,
352 pov_size: prometheus::register(
355 prometheus::HistogramVec::new(
356 prometheus::HistogramOpts::new(
357 "polkadot_parachain_candidate_validation_pov_size",
358 "The compressed and decompressed size of the proof of validity of a candidate",
359 )
360 .buckets(
361 prometheus::exponential_buckets(16384.0, 2.0, 10)
362 .expect("arguments are always valid; qed"),
363 ),
364 &["compressed"],
365 )?,
366 registry,
367 )?,
368 code_size: prometheus::register(
369 prometheus::Histogram::with_opts(
370 prometheus::HistogramOpts::new(
371 "polkadot_parachain_candidate_validation_code_size",
372 "The size of the decompressed WASM validation blob used for checking a candidate",
373 )
374 .buckets(
375 prometheus::exponential_buckets(16384.0, 2.0, 10)
376 .expect("arguments are always valid; qed"),
377 ),
378 )?,
379 registry,
380 )?,
381 exec_kind_selected: prometheus::register(
382 prometheus::CounterVec::new(
383 prometheus::Opts::new(
384 "polkadot_pvf_exec_kind_selected",
385 "The total number of selected execute kinds",
386 ),
387 &["priority"],
388 )?,
389 registry,
390 )?,
391 };
392 Ok(Metrics(Some(inner)))
393 }
394}
395
396enum WorkerFlavor {
397 Prepare,
398 Execute,
399}
400
401impl WorkerFlavor {
402 fn as_label(&self) -> &'static str {
403 match *self {
404 WorkerFlavor::Prepare => "prepare",
405 WorkerFlavor::Execute => "execute",
406 }
407 }
408}
409
410pub(crate) struct WorkerRelatedMetrics<'a> {
411 metrics: &'a Metrics,
412 flavor: WorkerFlavor,
413}
414
415impl<'a> WorkerRelatedMetrics<'a> {
416 pub(crate) fn on_begin_spawn(&self) {
418 if let Some(metrics) = &self.metrics.0 {
419 metrics.worker_spawning.with_label_values(&[self.flavor.as_label()]).inc();
420 }
421 }
422
423 pub(crate) fn on_spawned(&self) {
425 if let Some(metrics) = &self.metrics.0 {
426 metrics.worker_spawned.with_label_values(&[self.flavor.as_label()]).inc();
427 }
428 }
429
430 pub(crate) fn on_retired(&self) {
432 if let Some(metrics) = &self.metrics.0 {
433 metrics.worker_retired.with_label_values(&[self.flavor.as_label()]).inc();
434 }
435 }
436}