openvm_circuit/metrics/
mod.rs
1use std::{collections::BTreeMap, mem};
2
3use cycle_tracker::CycleTracker;
4use metrics::counter;
5use openvm_instructions::{
6 exe::{FnBound, FnBounds},
7 VmOpcode,
8};
9use openvm_stark_backend::p3_field::PrimeField32;
10
11use crate::arch::{ExecutionSegment, InstructionExecutor, VmConfig};
12
13pub mod cycle_tracker;
14
15#[derive(Clone, Debug, Default)]
16pub struct VmMetrics {
17 pub cycle_count: usize,
18 pub chip_heights: Vec<(String, usize)>,
19 pub counts: BTreeMap<(Option<String>, String), usize>,
21 pub trace_cells: BTreeMap<(Option<String>, String, String), usize>,
23 pub cycle_tracker: CycleTracker,
25 #[allow(dead_code)]
26 pub(crate) fn_bounds: FnBounds,
27 #[allow(dead_code)]
29 pub(crate) current_fn: FnBound,
30 pub(crate) current_trace_cells: Vec<usize>,
31}
32
33impl<F, VC> ExecutionSegment<F, VC>
34where
35 F: PrimeField32,
36 VC: VmConfig<F>,
37{
38 #[allow(unused_variables)]
40 pub fn update_instruction_metrics(
41 &mut self,
42 pc: u32,
43 opcode: VmOpcode,
44 dsl_instr: Option<String>,
45 ) {
46 self.metrics.cycle_count += 1;
47
48 if self.system_config().profiling {
49 let executor = self.chip_complex.inventory.get_executor(opcode).unwrap();
50 let opcode_name = executor.get_opcode_name(opcode.as_usize());
51 self.metrics.update_trace_cells(
52 &self.air_names,
53 self.current_trace_cells(),
54 opcode_name,
55 dsl_instr,
56 );
57
58 #[cfg(feature = "function-span")]
59 self.metrics.update_current_fn(pc);
60 }
61 }
62}
63
64impl VmMetrics {
65 fn update_trace_cells(
66 &mut self,
67 air_names: &[String],
68 now_trace_cells: Vec<usize>,
69 opcode_name: String,
70 dsl_instr: Option<String>,
71 ) {
72 let key = (dsl_instr, opcode_name);
73 self.cycle_tracker.increment_opcode(&key);
74 *self.counts.entry(key.clone()).or_insert(0) += 1;
75
76 for (air_name, now_value, prev_value) in
77 itertools::izip!(air_names, &now_trace_cells, &self.current_trace_cells)
78 {
79 if prev_value != now_value {
80 let key = (key.0.clone(), key.1.clone(), air_name.to_owned());
81 self.cycle_tracker
82 .increment_cells_used(&key, now_value - prev_value);
83 *self.trace_cells.entry(key).or_insert(0) += now_value - prev_value;
84 }
85 }
86 self.current_trace_cells = now_trace_cells;
87 }
88
89 pub fn partial_take(&mut self) -> Self {
92 Self {
93 cycle_tracker: mem::take(&mut self.cycle_tracker),
94 fn_bounds: mem::take(&mut self.fn_bounds),
95 current_fn: mem::take(&mut self.current_fn),
96 ..Default::default()
97 }
98 }
99
100 pub fn clear(&mut self) {
104 *self = self.partial_take();
105 }
106
107 #[cfg(feature = "function-span")]
108 fn update_current_fn(&mut self, pc: u32) {
109 if !self.fn_bounds.is_empty() && (pc < self.current_fn.start || pc > self.current_fn.end) {
110 self.current_fn = self
111 .fn_bounds
112 .range(..=pc)
113 .next_back()
114 .map(|(_, func)| (*func).clone())
115 .unwrap();
116 if pc == self.current_fn.start {
117 self.cycle_tracker.start(self.current_fn.name.clone());
118 } else {
119 self.cycle_tracker.force_end();
120 }
121 };
122 }
123 pub fn emit(&self) {
124 for (name, value) in self.chip_heights.iter() {
125 let labels = [("chip_name", name.clone())];
126 counter!("rows_used", &labels).absolute(*value as u64);
127 }
128
129 for ((dsl_ir, opcode), value) in self.counts.iter() {
130 let labels = [
131 ("dsl_ir", dsl_ir.clone().unwrap_or_else(String::new)),
132 ("opcode", opcode.clone()),
133 ];
134 counter!("frequency", &labels).absolute(*value as u64);
135 }
136
137 for ((dsl_ir, opcode, air_name), value) in self.trace_cells.iter() {
138 let labels = [
139 ("dsl_ir", dsl_ir.clone().unwrap_or_else(String::new)),
140 ("opcode", opcode.clone()),
141 ("air_name", air_name.clone()),
142 ];
143 counter!("cells_used", &labels).absolute(*value as u64);
144 }
145 }
146}