1use std::{collections::BTreeMap, mem};
2
3use backtrace::Backtrace;
4use cycle_tracker::CycleTracker;
5use itertools::Itertools;
6use metrics::counter;
7use openvm_instructions::{
8 exe::{FnBound, FnBounds},
9 program::ProgramDebugInfo,
10};
11use openvm_stark_backend::prover::{hal::ProverBackend, types::DeviceMultiStarkProvingKey};
12
13use crate::{
14 arch::{
15 execution_mode::PreflightCtx, interpreter_preflight::PcEntry, Arena, PreflightExecutor,
16 VmExecState,
17 },
18 system::memory::online::TracingMemory,
19};
20
21pub mod cycle_tracker;
22
23#[derive(Clone, Debug, Default)]
24pub struct VmMetrics {
25 pub air_names: Vec<String>,
27 pub debug_infos: ProgramDebugInfo,
28 #[cfg(feature = "perf-metrics")]
29 pub(crate) num_sys_airs: usize,
30 #[cfg(feature = "perf-metrics")]
31 pub(crate) access_adapter_offset: usize,
32 pub(crate) main_widths: Vec<usize>,
33 pub(crate) total_widths: Vec<usize>,
34
35 pub counts: BTreeMap<(Option<String>, String), usize>,
38 pub trace_cells: BTreeMap<(Option<String>, String, String), usize>,
40 pub cycle_tracker: CycleTracker,
42
43 pub(crate) current_trace_cells: Vec<usize>,
44
45 pub prev_backtrace: Option<Backtrace>,
47 #[allow(dead_code)]
48 pub(crate) fn_bounds: FnBounds,
49 #[allow(dead_code)]
51 pub(crate) current_fn: FnBound,
52}
53
54#[allow(unused_variables)]
56#[inline(always)]
57pub fn update_instruction_metrics<F, RA, Executor>(
58 state: &mut VmExecState<F, TracingMemory, PreflightCtx<RA>>,
59 executor: &Executor,
60 prev_pc: u32, pc_entry: &PcEntry<F>,
62) where
63 F: Clone + Send + Sync,
64 RA: Arena,
65 Executor: PreflightExecutor<F, RA>,
66{
67 #[cfg(any(debug_assertions, feature = "perf-metrics"))]
68 {
69 let pc = state.pc;
70 state.metrics.update_backtrace(pc);
71 }
72
73 #[cfg(feature = "perf-metrics")]
74 {
75 use std::iter::zip;
76
77 let pc = state.pc;
78 let opcode = pc_entry.insn.opcode;
79 let opcode_name = executor.get_opcode_name(opcode.as_usize());
80
81 let debug_info = state.metrics.debug_infos.get(prev_pc);
82 let dsl_instr = debug_info.as_ref().map(|info| info.dsl_instruction.clone());
83
84 let now_trace_heights: Vec<usize> = state
85 .ctx
86 .arenas
87 .iter()
88 .map(|arena| arena.current_trace_height())
89 .collect();
90 let now_trace_cells = zip(&state.metrics.main_widths, &now_trace_heights)
91 .map(|(main_width, h)| main_width * h)
92 .collect_vec();
93 state
94 .metrics
95 .update_trace_cells(now_trace_cells, opcode_name, dsl_instr);
96
97 state.metrics.update_current_fn(pc);
98 }
99}
100
101#[cfg(feature = "perf-metrics")]
106pub fn end_segment_metrics<F, RA>(state: &mut VmExecState<F, TracingMemory, PreflightCtx<RA>>)
107where
108 F: Clone + Send + Sync,
109 RA: Arena,
110{
111 use std::iter::zip;
112
113 use crate::system::memory::adapter::AccessAdapterInventory;
114
115 let access_adapter_offset = state.metrics.access_adapter_offset;
116 let num_sys_airs = state.metrics.num_sys_airs;
117 let mut now_heights = vec![0; num_sys_airs - access_adapter_offset];
118 AccessAdapterInventory::<F>::compute_heights_from_arena(
119 &state.memory.access_adapter_records,
120 &mut now_heights,
121 );
122 let now_trace_cells = zip(
123 &state.metrics.main_widths[access_adapter_offset..],
124 &now_heights,
125 )
126 .map(|(main_width, h)| main_width * h)
127 .collect_vec();
128 for (air_name, &now_value) in itertools::izip!(
129 &state.metrics.air_names[access_adapter_offset..],
130 &now_trace_cells,
131 ) {
132 if now_value != 0 {
133 let labels = [
134 ("air_name", air_name.clone()),
135 ("opcode", String::default()),
136 ("dsl_ir", String::default()),
137 ("cycle_tracker_span", "memory_access_adapters".to_owned()),
138 ];
139 counter!("cells_used", &labels).increment(now_value as u64);
140 }
141 }
142 state.metrics.current_trace_cells.fill(0);
143}
144
145impl VmMetrics {
146 pub fn set_pk_info<PB: ProverBackend>(&mut self, pk: &DeviceMultiStarkProvingKey<PB>) {
147 let (air_names, main_widths, total_widths): (Vec<_>, Vec<_>, Vec<_>) = pk
148 .per_air
149 .iter()
150 .map(|pk| {
151 let air_names = pk.air_name.clone();
152 let width = &pk.vk.params.width;
153 let main_width = width.main_width();
154 let total_width = width.total_width(PB::CHALLENGE_EXT_DEGREE as usize);
155 (air_names, main_width, total_width)
156 })
157 .multiunzip();
158 self.air_names = air_names;
159 self.main_widths = main_widths;
160 self.total_widths = total_widths;
161 self.current_trace_cells = vec![0; self.air_names.len()];
162 }
163
164 pub fn update_trace_cells(
165 &mut self,
166 now_trace_cells: Vec<usize>,
167 opcode_name: String,
168 dsl_instr: Option<String>,
169 ) {
170 let key = (dsl_instr, opcode_name);
171 self.cycle_tracker.increment_opcode(&key);
172 *self.counts.entry(key.clone()).or_insert(0) += 1;
173
174 for (air_name, now_value, prev_value) in
175 itertools::izip!(&self.air_names, &now_trace_cells, &self.current_trace_cells)
176 {
177 if prev_value != now_value {
178 let key = (key.0.clone(), key.1.clone(), air_name.to_owned());
179 self.cycle_tracker
180 .increment_cells_used(&key, now_value - prev_value);
181 *self.trace_cells.entry(key).or_insert(0) += now_value - prev_value;
182 }
183 }
184 self.current_trace_cells = now_trace_cells;
185 }
186
187 pub fn partial_take(&mut self) -> Self {
190 Self {
191 cycle_tracker: mem::take(&mut self.cycle_tracker),
192 fn_bounds: mem::take(&mut self.fn_bounds),
193 current_fn: mem::take(&mut self.current_fn),
194 ..Default::default()
195 }
196 }
197
198 pub fn clear(&mut self) {
202 *self = self.partial_take();
203 }
204
205 #[cfg(any(debug_assertions, feature = "perf-metrics"))]
206 pub fn update_backtrace(&mut self, pc: u32) {
207 if let Some(info) = self.debug_infos.get(pc) {
208 if let Some(trace) = &info.trace {
209 self.prev_backtrace = Some(trace.clone());
210 }
211 }
212 }
213
214 #[cfg(feature = "perf-metrics")]
215 pub(super) fn update_current_fn(&mut self, pc: u32) {
216 if self.fn_bounds.is_empty() {
217 return;
218 }
219 if pc < self.current_fn.start || pc > self.current_fn.end {
220 self.current_fn = self
221 .fn_bounds
222 .range(..=pc)
223 .next_back()
224 .map(|(_, func)| (*func).clone())
225 .unwrap();
226 if pc == self.current_fn.start {
227 self.cycle_tracker.start(self.current_fn.name.clone());
228 } else {
229 while let Some(name) = self.cycle_tracker.top() {
230 if name == &self.current_fn.name {
231 break;
232 }
233 self.cycle_tracker.force_end();
234 }
235 }
236 };
237 }
238
239 pub fn emit(&self) {
240 for ((dsl_ir, opcode), value) in self.counts.iter() {
241 let labels = [
242 ("dsl_ir", dsl_ir.clone().unwrap_or_else(String::new)),
243 ("opcode", opcode.clone()),
244 ];
245 counter!("frequency", &labels).absolute(*value as u64);
246 }
247
248 for ((dsl_ir, opcode, air_name), value) in self.trace_cells.iter() {
249 let labels = [
250 ("dsl_ir", dsl_ir.clone().unwrap_or_else(String::new)),
251 ("opcode", opcode.clone()),
252 ("air_name", air_name.clone()),
253 ];
254 counter!("cells_used", &labels).absolute(*value as u64);
255 }
256 }
257}