cairo_lang_runner/
profiling.rs

1use std::fmt::{Debug, Display};
2
3use cairo_lang_lowering::ids::FunctionLongId;
4use cairo_lang_runnable_utils::builder::RunnableBuilder;
5use cairo_lang_sierra::extensions::core::CoreConcreteLibfunc;
6use cairo_lang_sierra::ids::ConcreteLibfuncId;
7use cairo_lang_sierra::program::{GenStatement, Program, StatementIdx};
8use cairo_lang_sierra_generator::db::SierraGenGroup;
9use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
10use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
11use cairo_lang_utils::{LookupIntern, require};
12use cairo_vm::vm::trace::trace_entry::RelocatedTraceEntry;
13use itertools::{Itertools, chain};
14use smol_str::SmolStr;
15
16use crate::ProfilingInfoCollectionConfig;
17
18#[cfg(test)]
19#[path = "profiling_test.rs"]
20mod test;
21
22/// Profiler configuration.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum ProfilerConfig {
25    /// Profiler with Cairo level debug information.
26    Cairo,
27    /// Sierra level profiling, no cairo level debug information.
28    Sierra,
29}
30
31/// Profiling into of a single run. This is the raw info - went through minimum processing, as this
32/// is done during the run. To enrich it before viewing/printing, use the `ProfilingInfoProcessor`.
33#[derive(Debug, Eq, PartialEq, Clone)]
34pub struct ProfilingInfo {
35    /// The number of steps in the trace that originated from each sierra statement.
36    pub sierra_statement_weights: UnorderedHashMap<StatementIdx, usize>,
37
38    /// A map of weights of each stack trace.
39    /// The key is a function stack trace of an executed function. The stack trace is represented
40    /// as a vector of indices of the functions in the stack (indices of the functions according to
41    /// the list in the sierra program).
42    /// The value is the weight of the stack trace.
43    /// The stack trace entries are sorted in the order they occur.
44    pub stack_trace_weights: OrderedHashMap<Vec<usize>, usize>,
45
46    /// The number of steps in the trace that originated from each sierra statement
47    /// combined with information about the user function call stack.
48    /// The call stack items are deduplicated to flatten and aggregate recursive calls
49    /// and loops (which are tail recursion).
50    /// The entries are sorted in the order they occur.
51    pub scoped_sierra_statement_weights: OrderedHashMap<(Vec<usize>, StatementIdx), usize>,
52}
53
54impl ProfilingInfo {
55    pub fn from_trace(
56        builder: &RunnableBuilder,
57        // The offset in memory where builder.casm_program() was loaded.
58        load_offset: usize,
59        profiling_config: &ProfilingInfoCollectionConfig,
60        trace: &[RelocatedTraceEntry],
61    ) -> Self {
62        let sierra_statement_info = &builder.casm_program().debug_info.sierra_statement_info;
63        let sierra_len = sierra_statement_info.len();
64        let bytecode_len = sierra_statement_info.last().unwrap().end_offset;
65
66        // The function stack trace of the current function, excluding the current function (that
67        // is, the stack of the caller). Represented as a vector of indices of the functions
68        // in the stack (indices of the functions according to the list in the sierra program).
69        // Limited to depth `max_stack_trace_depth`. Note `function_stack_depth` tracks the real
70        // depth, even if >= `max_stack_trace_depth`.
71        let mut function_stack = Vec::new();
72        // Tracks the depth of the function stack, without limit. This is usually equal to
73        // `function_stack.len()`, but if the actual stack is deeper than `max_stack_trace_depth`,
74        // this remains reliable while `function_stack` does not.
75        let mut function_stack_depth = 0;
76        let mut cur_weight = 0;
77        // The key is a function stack trace (see `function_stack`, but including the current
78        // function).
79        // The value is the weight of the stack trace so far, not including the pending weight being
80        // tracked at the time.
81        let mut stack_trace_weights = OrderedHashMap::default();
82        let mut end_of_program_reached = false;
83        // The total weight of each Sierra statement.
84        // Note the header and footer (CASM instructions added for running the program by the
85        // runner). The header is not counted, and the footer is, but then the relevant
86        // entry is removed.
87        let mut sierra_statement_weights = UnorderedHashMap::default();
88        // Total weight of Sierra statements grouped by the respective (collapsed) user function
89        // call stack.
90        let mut scoped_sierra_statement_weights = OrderedHashMap::default();
91        for step in trace {
92            // Skip the header.
93            let Some(real_pc) = step.pc.checked_sub(load_offset) else {
94                continue;
95            };
96
97            // Skip the footer.
98            // Also if pc is greater or equal the bytecode length it means that it is the outside
99            // ret used for e.g. getting pointer to builtins costs table, const segments
100            // etc.
101            if real_pc >= bytecode_len {
102                continue;
103            }
104
105            if end_of_program_reached {
106                unreachable!("End of program reached, but trace continues.");
107            }
108
109            cur_weight += 1;
110
111            // TODO(yuval): Maintain a map of pc to sierra statement index (only for PCs we saw), to
112            // save lookups.
113            let sierra_statement_idx = builder.casm_program().sierra_statement_index_by_pc(real_pc);
114            let user_function_idx = user_function_idx_by_sierra_statement_idx(
115                builder.sierra_program(),
116                sierra_statement_idx,
117            );
118
119            *sierra_statement_weights.entry(sierra_statement_idx).or_insert(0) += 1;
120
121            if profiling_config.collect_scoped_sierra_statement_weights {
122                // The current stack trace, including the current function (recursive calls
123                // collapsed).
124                let cur_stack: Vec<usize> =
125                    chain!(function_stack.iter().map(|&(idx, _)| idx), [user_function_idx])
126                        .dedup()
127                        .collect();
128
129                *scoped_sierra_statement_weights
130                    .entry((cur_stack, sierra_statement_idx))
131                    .or_insert(0) += 1;
132            }
133
134            let Some(gen_statement) =
135                builder.sierra_program().statements.get(sierra_statement_idx.0)
136            else {
137                panic!("Failed fetching statement index {}", sierra_statement_idx.0);
138            };
139
140            match gen_statement {
141                GenStatement::Invocation(invocation) => {
142                    if matches!(
143                        builder.registry().get_libfunc(&invocation.libfunc_id),
144                        Ok(CoreConcreteLibfunc::FunctionCall(_))
145                    ) {
146                        // Push to the stack.
147                        if function_stack_depth < profiling_config.max_stack_trace_depth {
148                            function_stack.push((user_function_idx, cur_weight));
149                            cur_weight = 0;
150                        }
151                        function_stack_depth += 1;
152                    }
153                }
154                GenStatement::Return(_) => {
155                    // Pop from the stack.
156                    if function_stack_depth <= profiling_config.max_stack_trace_depth {
157                        // The current stack trace, including the current function.
158                        let cur_stack: Vec<_> =
159                            chain!(function_stack.iter().map(|f| f.0), [user_function_idx])
160                                .collect();
161                        *stack_trace_weights.entry(cur_stack).or_insert(0) += cur_weight;
162
163                        let Some(popped) = function_stack.pop() else {
164                            // End of the program.
165                            end_of_program_reached = true;
166                            continue;
167                        };
168                        cur_weight += popped.1;
169                    }
170                    function_stack_depth -= 1;
171                }
172            }
173        }
174
175        // Remove the footer.
176        sierra_statement_weights.remove(&StatementIdx(sierra_len));
177
178        ProfilingInfo {
179            sierra_statement_weights,
180            stack_trace_weights,
181            scoped_sierra_statement_weights,
182        }
183    }
184}
185
186/// Weights per libfunc.
187#[derive(Default)]
188pub struct LibfuncWeights {
189    /// Weight (in steps in the relevant run) of each concrete libfunc.
190    pub concrete_libfunc_weights: Option<OrderedHashMap<SmolStr, usize>>,
191    /// Weight (in steps in the relevant run) of each generic libfunc.
192    pub generic_libfunc_weights: Option<OrderedHashMap<SmolStr, usize>>,
193    /// Weight (in steps in the relevant run) of return statements.
194    pub return_weight: Option<usize>,
195}
196impl Display for LibfuncWeights {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        if let Some(concrete_libfunc_weights) = &self.concrete_libfunc_weights {
199            writeln!(f, "Weight by concrete libfunc:")?;
200            for (concrete_name, weight) in concrete_libfunc_weights.iter() {
201                writeln!(f, "  libfunc {concrete_name}: {weight}")?;
202            }
203            writeln!(
204                f,
205                "  return: {}",
206                self.return_weight.expect(
207                    "return_weight should have a value if concrete_libfunc_weights has a value"
208                )
209            )?;
210        }
211        if let Some(generic_libfunc_weights) = &self.generic_libfunc_weights {
212            writeln!(f, "Weight by generic libfunc:")?;
213            for (generic_name, weight) in generic_libfunc_weights.iter() {
214                writeln!(f, "  libfunc {generic_name}: {weight}")?;
215            }
216            writeln!(
217                f,
218                "  return: {}",
219                self.return_weight.expect(
220                    "return_weight should have a value if generic_libfunc_weights has a value"
221                )
222            )?;
223        }
224        Ok(())
225    }
226}
227
228/// Weights per user function.
229#[derive(Default)]
230pub struct UserFunctionWeights {
231    /// Weight (in steps in the relevant run) of each user function (including generated
232    /// functions).
233    pub user_function_weights: Option<OrderedHashMap<SmolStr, usize>>,
234    /// Weight (in steps in the relevant run) of each original user function.
235    pub original_user_function_weights: Option<OrderedHashMap<String, usize>>,
236}
237impl Display for UserFunctionWeights {
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239        if let Some(user_function_weights) = &self.user_function_weights {
240            writeln!(f, "Weight by user function (inc. generated):")?;
241            for (name, weight) in user_function_weights.iter() {
242                writeln!(f, "  function {name}: {weight}")?;
243            }
244        }
245        if let Some(original_user_function_weights) = &self.original_user_function_weights {
246            writeln!(f, "Weight by original user function (exc. generated):")?;
247            for (name, weight) in original_user_function_weights.iter() {
248                writeln!(f, "  function {name}: {weight}")?;
249            }
250        }
251        Ok(())
252    }
253}
254
255/// Weights per stack trace.
256#[derive(Default)]
257pub struct StackTraceWeights {
258    /// A map of weights of each Sierra stack trace.
259    /// The key is a function stack trace of an executed function. The stack trace is represented
260    /// as a vector of the function names.
261    /// The value is the weight of the stack trace.
262    pub sierra_stack_trace_weights: Option<OrderedHashMap<Vec<String>, usize>>,
263    /// A map of weights of each stack trace, only for stack traces that are fully semantic
264    /// (equivalent to Cairo traces). That is, none of the trace components is generated.
265    /// This is a filtered map of `sierra_stack_trace_weights`.
266    pub cairo_stack_trace_weights: Option<OrderedHashMap<Vec<String>, usize>>,
267}
268impl Display for StackTraceWeights {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        if let Some(sierra_stack_trace_weights) = &self.sierra_stack_trace_weights {
271            writeln!(f, "Weight by Sierra stack trace:")?;
272            for (stack_trace, weight) in sierra_stack_trace_weights.iter() {
273                let stack_trace_str = stack_trace.join(" -> ");
274                writeln!(f, "  {stack_trace_str}: {weight}")?;
275            }
276        }
277
278        if let Some(cairo_stack_trace_weights) = &self.cairo_stack_trace_weights {
279            writeln!(f, "Weight by Cairo stack trace:")?;
280            for (stack_trace, weight) in cairo_stack_trace_weights.iter() {
281                let stack_trace_str = stack_trace.join(" -> ");
282                writeln!(f, "  {stack_trace_str}: {weight}")?;
283            }
284        }
285        Ok(())
286    }
287}
288
289/// Full profiling info of a single run. This is the processed info which went through additional
290/// processing after collecting the raw data during the run itself.
291pub struct ProcessedProfilingInfo {
292    /// For each sierra statement: the number of steps in the trace that originated from it, and
293    /// the relevant GenStatement.
294    pub sierra_statement_weights:
295        Option<OrderedHashMap<StatementIdx, (usize, GenStatement<StatementIdx>)>>,
296
297    /// Weights per stack trace.
298    pub stack_trace_weights: StackTraceWeights,
299
300    /// Weights per libfunc.
301    pub libfunc_weights: LibfuncWeights,
302
303    /// Weights per user function.
304    pub user_function_weights: UserFunctionWeights,
305
306    /// Weight (in steps in the relevant run) of each Cairo function.
307    pub cairo_function_weights: Option<OrderedHashMap<String, usize>>,
308
309    /// For each Sierra statement in the scope of a particular call stack with deduplicated frames
310    /// (collapsed recursion): the number of steps in the trace that originated from it.
311    pub scoped_sierra_statement_weights: Option<OrderedHashMap<Vec<String>, usize>>,
312}
313impl Display for ProcessedProfilingInfo {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        if let Some(sierra_statement_weights) = &self.sierra_statement_weights {
316            writeln!(f, "Weight by sierra statement:")?;
317            for (statement_idx, (weight, gen_statement)) in sierra_statement_weights.iter() {
318                writeln!(f, "  statement {statement_idx}: {weight} ({gen_statement})")?;
319            }
320        }
321
322        // libfunc weights.
323        self.libfunc_weights.fmt(f)?;
324
325        // user functions.
326        self.user_function_weights.fmt(f)?;
327
328        if let Some(cairo_function_weights) = &self.cairo_function_weights {
329            writeln!(f, "Weight by Cairo function:")?;
330            for (function_identifier, weight) in cairo_function_weights.iter() {
331                writeln!(f, "  function {function_identifier}: {weight}")?;
332            }
333        }
334
335        self.stack_trace_weights.fmt(f)?;
336
337        if let Some(weights) = &self.scoped_sierra_statement_weights {
338            format_scoped_sierra_statement_weights(weights, f)?;
339        }
340
341        Ok(())
342    }
343}
344
345/// Parameters controlling what profiling info is processed and how, by the
346/// `ProfilingInfoProcessor`.
347pub struct ProfilingInfoProcessorParams {
348    /// The minimal weight to include in the output. Used for all collected stats. That is - the
349    /// sum of the weights per statement may be smaller than the sum of the weights per concrete
350    /// libfunc, that may be smaller than the sum of the weights per generic libfunc.
351    pub min_weight: usize,
352    /// Whether to process the profiling info by Sierra statement.
353    pub process_by_statement: bool,
354    /// Whether to process the profiling info by concrete libfunc.
355    pub process_by_concrete_libfunc: bool,
356    /// Whether to process the profiling info by generic libfunc.
357    pub process_by_generic_libfunc: bool,
358    /// Whether to process the profiling info by user function, including generated functions.
359    pub process_by_user_function: bool,
360    /// Whether to process the profiling info by original user function only.
361    pub process_by_original_user_function: bool,
362    /// Whether to process the profiling info by Cairo function (computed from the compiler
363    /// StableLocation).
364    pub process_by_cairo_function: bool,
365    /// Whether to process the profiling info by Sierra stack trace (including generated
366    /// functions in the traces).
367    pub process_by_stack_trace: bool,
368    /// Whether to process the profiling info by Cairo stack trace (that is, no generated
369    /// functions in the traces).
370    pub process_by_cairo_stack_trace: bool,
371    /// Process the profiling info by Sierra statement in the scope of a particular
372    /// call stack (recursion collapsed) and output in a format compatible with Flamegraph.
373    pub process_by_scoped_statement: bool,
374}
375impl Default for ProfilingInfoProcessorParams {
376    fn default() -> Self {
377        Self {
378            min_weight: 1,
379            process_by_statement: true,
380            process_by_concrete_libfunc: true,
381            process_by_generic_libfunc: true,
382            process_by_user_function: true,
383            process_by_original_user_function: true,
384            process_by_cairo_function: true,
385            process_by_stack_trace: true,
386            process_by_cairo_stack_trace: true,
387            process_by_scoped_statement: false,
388        }
389    }
390}
391
392impl ProfilingInfoProcessorParams {
393    pub fn from_profiler_config(config: &ProfilerConfig) -> Self {
394        match config {
395            ProfilerConfig::Cairo => Default::default(),
396            ProfilerConfig::Sierra => Self {
397                process_by_generic_libfunc: false,
398                process_by_cairo_stack_trace: false,
399                process_by_original_user_function: false,
400                process_by_cairo_function: false,
401                ..ProfilingInfoProcessorParams::default()
402            },
403        }
404    }
405}
406
407/// A processor for profiling info. Used to process the raw profiling info (basic info collected
408/// during the run) into a more detailed profiling info that can also be formatted.
409pub struct ProfilingInfoProcessor<'a> {
410    db: Option<&'a dyn SierraGenGroup>,
411    sierra_program: &'a Program,
412    /// A map between sierra statement index and the string representation of the Cairo function
413    /// that generated it. The function representation is composed of the function name and the
414    /// path (modules and impls) to the function in the file.
415    statements_functions: &'a UnorderedHashMap<StatementIdx, String>,
416}
417impl<'a> ProfilingInfoProcessor<'a> {
418    pub fn new(
419        db: Option<&'a dyn SierraGenGroup>,
420        sierra_program: &'a Program,
421        statements_functions: &'a UnorderedHashMap<StatementIdx, String>,
422    ) -> Self {
423        Self { db, sierra_program, statements_functions }
424    }
425
426    /// Processes the raw profiling info according to the given params.
427    pub fn process(
428        &self,
429        raw_profiling_info: &ProfilingInfo,
430        params: &ProfilingInfoProcessorParams,
431    ) -> ProcessedProfilingInfo {
432        let sierra_statement_weights_iter = raw_profiling_info
433            .sierra_statement_weights
434            .iter_sorted_by_key(|(pc, count)| (usize::MAX - **count, **pc));
435
436        let sierra_statement_weights =
437            self.process_sierra_statement_weights(sierra_statement_weights_iter.clone(), params);
438
439        let stack_trace_weights = self.process_stack_trace_weights(raw_profiling_info, params);
440
441        let libfunc_weights =
442            self.process_libfunc_weights(sierra_statement_weights_iter.clone(), params);
443
444        let user_function_weights =
445            self.process_user_function_weights(sierra_statement_weights_iter.clone(), params);
446
447        let cairo_function_weights =
448            self.process_cairo_function_weights(sierra_statement_weights_iter, params);
449
450        let scoped_sierra_statement_weights =
451            self.process_scoped_sierra_statement_weights(raw_profiling_info, params);
452
453        ProcessedProfilingInfo {
454            sierra_statement_weights,
455            stack_trace_weights,
456            libfunc_weights,
457            user_function_weights,
458            cairo_function_weights,
459            scoped_sierra_statement_weights,
460        }
461    }
462
463    /// Process the weights per Sierra statement.
464    fn process_sierra_statement_weights(
465        &self,
466        sierra_statement_weights_iter: std::vec::IntoIter<(&StatementIdx, &usize)>,
467        params: &ProfilingInfoProcessorParams,
468    ) -> Option<OrderedHashMap<StatementIdx, (usize, GenStatement<StatementIdx>)>> {
469        require(params.process_by_statement)?;
470
471        Some(
472            sierra_statement_weights_iter
473                .filter(|&(_, weight)| *weight >= params.min_weight)
474                .map(|(statement_idx, weight)| {
475                    (*statement_idx, (*weight, self.statement_idx_to_gen_statement(statement_idx)))
476                })
477                .collect(),
478        )
479    }
480
481    /// Process the weights per stack trace.
482    fn process_stack_trace_weights(
483        &self,
484        raw_profiling_info: &ProfilingInfo,
485        params: &ProfilingInfoProcessorParams,
486    ) -> StackTraceWeights {
487        let resolve_names = |(idx_stack_trace, weight): (&Vec<usize>, &usize)| {
488            (index_stack_trace_to_name_stack_trace(self.sierra_program, idx_stack_trace), *weight)
489        };
490
491        let sierra_stack_trace_weights = params.process_by_stack_trace.then(|| {
492            raw_profiling_info
493                .stack_trace_weights
494                .iter()
495                .sorted_by_key(|&(trace, weight)| (usize::MAX - *weight, trace.clone()))
496                .map(resolve_names)
497                .collect()
498        });
499
500        let cairo_stack_trace_weights = params.process_by_cairo_stack_trace.then(|| {
501            let db = self.db.expect("DB must be set with `process_by_cairo_stack_trace=true`.");
502            raw_profiling_info
503                .stack_trace_weights
504                .iter()
505                .filter(|(trace, _)| is_cairo_trace(db, self.sierra_program, trace))
506                .sorted_by_key(|&(trace, weight)| (usize::MAX - *weight, trace.clone()))
507                .map(resolve_names)
508                .collect()
509        });
510
511        StackTraceWeights { sierra_stack_trace_weights, cairo_stack_trace_weights }
512    }
513
514    /// Process the weights per libfunc.
515    fn process_libfunc_weights(
516        &self,
517        sierra_statement_weights: std::vec::IntoIter<(&StatementIdx, &usize)>,
518        params: &ProfilingInfoProcessorParams,
519    ) -> LibfuncWeights {
520        if !params.process_by_concrete_libfunc && !params.process_by_generic_libfunc {
521            return LibfuncWeights::default();
522        }
523
524        let mut return_weight = 0;
525        let mut libfunc_weights = UnorderedHashMap::<ConcreteLibfuncId, usize>::default();
526        for (statement_idx, weight) in sierra_statement_weights {
527            match self.statement_idx_to_gen_statement(statement_idx) {
528                GenStatement::Invocation(invocation) => {
529                    *(libfunc_weights.entry(invocation.libfunc_id.clone()).or_insert(0)) += weight;
530                }
531                GenStatement::Return(_) => {
532                    return_weight += weight;
533                }
534            }
535        }
536
537        let generic_libfunc_weights = params.process_by_generic_libfunc.then(|| {
538            let db: &dyn SierraGenGroup =
539                self.db.expect("DB must be set with `process_by_generic_libfunc=true`.");
540            libfunc_weights
541                .aggregate_by(
542                    |k| -> SmolStr {
543                        db.lookup_intern_concrete_lib_func(k.clone()).generic_id.to_string().into()
544                    },
545                    |v1: &usize, v2| v1 + v2,
546                    &0,
547                )
548                .filter(|_, weight| *weight >= params.min_weight)
549                .iter_sorted_by_key(|(generic_name, weight)| {
550                    (usize::MAX - **weight, (*generic_name).clone())
551                })
552                .map(|(generic_name, weight)| (generic_name.clone(), *weight))
553                .collect()
554        });
555
556        // This is done second as .filter() is consuming and to avoid cloning.
557        let concrete_libfunc_weights = params.process_by_concrete_libfunc.then(|| {
558            libfunc_weights
559                .filter(|_, weight| *weight >= params.min_weight)
560                .iter_sorted_by_key(|(libfunc_id, weight)| {
561                    (usize::MAX - **weight, (*libfunc_id).to_string())
562                })
563                .map(|(libfunc_id, weight)| (SmolStr::from(libfunc_id.to_string()), *weight))
564                .collect()
565        });
566
567        LibfuncWeights {
568            concrete_libfunc_weights,
569            generic_libfunc_weights,
570            return_weight: Some(return_weight),
571        }
572    }
573
574    /// Process the weights per user function.
575    fn process_user_function_weights(
576        &self,
577        sierra_statement_weights: std::vec::IntoIter<(&StatementIdx, &usize)>,
578        params: &ProfilingInfoProcessorParams,
579    ) -> UserFunctionWeights {
580        if !params.process_by_user_function && !params.process_by_original_user_function {
581            return UserFunctionWeights::default();
582        }
583
584        let mut user_functions = UnorderedHashMap::<usize, usize>::default();
585        for (statement_idx, weight) in sierra_statement_weights {
586            let function_idx: usize =
587                user_function_idx_by_sierra_statement_idx(self.sierra_program, *statement_idx);
588            *(user_functions.entry(function_idx).or_insert(0)) += weight;
589        }
590
591        let original_user_function_weights = params.process_by_original_user_function.then(|| {
592            let db: &dyn SierraGenGroup =
593                self.db.expect("DB must be set with `process_by_original_user_function=true`.");
594            user_functions
595                .aggregate_by(
596                    |idx| {
597                        let lowering_function_id =
598                            self.sierra_program.funcs[*idx].id.clone().lookup_intern(db);
599                        lowering_function_id.semantic_full_path(db.upcast())
600                    },
601                    |x, y| x + y,
602                    &0,
603                )
604                .filter(|_, weight| *weight >= params.min_weight)
605                .iter_sorted_by_key(|(orig_name, weight)| {
606                    (usize::MAX - **weight, (*orig_name).clone())
607                })
608                .map(|(orig_name, weight)| (orig_name.clone(), *weight))
609                .collect()
610        });
611
612        // This is done second as .filter() is consuming and to avoid cloning.
613        let user_function_weights = params.process_by_user_function.then(|| {
614            user_functions
615                .filter(|_, weight| *weight >= params.min_weight)
616                .iter_sorted_by_key(|(idx, weight)| {
617                    (usize::MAX - **weight, self.sierra_program.funcs[**idx].id.to_string())
618                })
619                .map(|(idx, weight)| {
620                    let func: &cairo_lang_sierra::program::GenFunction<StatementIdx> =
621                        &self.sierra_program.funcs[*idx];
622                    (func.id.to_string().into(), *weight)
623                })
624                .collect()
625        });
626
627        UserFunctionWeights { user_function_weights, original_user_function_weights }
628    }
629
630    /// Process the weights per Cairo function.
631    fn process_cairo_function_weights(
632        &self,
633        sierra_statement_weights: std::vec::IntoIter<(&StatementIdx, &usize)>,
634        params: &ProfilingInfoProcessorParams,
635    ) -> Option<OrderedHashMap<String, usize>> {
636        require(params.process_by_cairo_function)?;
637
638        let mut cairo_functions = UnorderedHashMap::<_, _>::default();
639        for (statement_idx, weight) in sierra_statement_weights {
640            // TODO(Gil): Fill all the `Unknown functions` in the cairo functions profiling.
641            let function_identifier = self
642                .statements_functions
643                .get(statement_idx)
644                .unwrap_or(&"unknown".to_string())
645                .clone();
646            *(cairo_functions.entry(function_identifier).or_insert(0)) += weight;
647        }
648
649        Some(
650            cairo_functions
651                .filter(|_, weight| *weight >= params.min_weight)
652                .iter_sorted_by_key(|(function_identifier, weight)| {
653                    (usize::MAX - **weight, (*function_identifier).clone())
654                })
655                .map(|(function_identifier, weight)| (function_identifier.clone(), *weight))
656                .collect(),
657        )
658    }
659
660    /// Process Sierra statement weights in the scope of a particular call stack
661    fn process_scoped_sierra_statement_weights(
662        &self,
663        raw_profiling_info: &ProfilingInfo,
664        params: &ProfilingInfoProcessorParams,
665    ) -> Option<OrderedHashMap<Vec<String>, usize>> {
666        if params.process_by_scoped_statement {
667            let mut scoped_sierra_statement_weights: OrderedHashMap<Vec<String>, usize> =
668                Default::default();
669            for ((idx_stack_trace, statement_idx), weight) in
670                raw_profiling_info.scoped_sierra_statement_weights.iter()
671            {
672                let statement_name = match self.statement_idx_to_gen_statement(statement_idx) {
673                    GenStatement::Invocation(invocation) => invocation.libfunc_id.to_string(),
674                    GenStatement::Return(_) => "return".into(),
675                };
676                let key: Vec<String> = chain!(
677                    index_stack_trace_to_name_stack_trace(self.sierra_program, idx_stack_trace),
678                    [statement_name]
679                )
680                .collect();
681                // Accumulating statements with the same name.
682                *scoped_sierra_statement_weights.entry(key).or_default() += *weight;
683            }
684            return Some(scoped_sierra_statement_weights);
685        }
686        None
687    }
688
689    /// Translates the given Sierra statement index into the actual statement.
690    fn statement_idx_to_gen_statement(
691        &self,
692        statement_idx: &StatementIdx,
693    ) -> GenStatement<StatementIdx> {
694        self.sierra_program
695            .statements
696            .get(statement_idx.0)
697            .unwrap_or_else(|| panic!("Failed fetching statement index {}", statement_idx.0))
698            .clone()
699    }
700}
701
702/// Checks if the given stack trace is fully semantic (so it is equivalent to a Cairo trace). That
703/// is, none of the trace components is generated.
704fn is_cairo_trace(
705    db: &dyn SierraGenGroup,
706    sierra_program: &Program,
707    sierra_trace: &[usize],
708) -> bool {
709    sierra_trace.iter().all(|sierra_function_idx| {
710        let sierra_function = &sierra_program.funcs[*sierra_function_idx];
711        let lowering_function_id = sierra_function.id.lookup_intern(db);
712        matches!(lowering_function_id.lookup_intern(db), FunctionLongId::Semantic(_))
713    })
714}
715
716/// Converts a sierra statement index to the index of the function that contains it (the index in
717/// the list in the sierra program).
718///
719/// Assumes that the given `statement_idx` is valid (that is within range of the given
720/// `sierra_program`) and that the given `sierra_program` is valid, specifically that the first
721/// function's entry point is 0.
722pub fn user_function_idx_by_sierra_statement_idx(
723    sierra_program: &Program,
724    statement_idx: StatementIdx,
725) -> usize {
726    // The `-1` here can't cause an underflow as the first function's entry point is
727    // always 0, so it is always on the left side of the partition, and thus the
728    // partition index is >0.
729    sierra_program.funcs.partition_point(|f| f.entry_point.0 <= statement_idx.0) - 1
730}
731
732/// Converts a stack trace represented as a vector of indices of functions in the sierra program to
733/// a stack trace represented as a vector of function names.
734/// Assumes that the given `idx_stack_trace` is valid with respect to the given `sierra_program`.
735/// That is, each index in the stack trace is within range of the sierra program.
736fn index_stack_trace_to_name_stack_trace(
737    sierra_program: &Program,
738    idx_stack_trace: &[usize],
739) -> Vec<String> {
740    idx_stack_trace.iter().map(|idx| sierra_program.funcs[*idx].id.to_string()).collect()
741}
742
743/// Writes scoped Sierra statement weights data in a FlameGraph compatible format.
744fn format_scoped_sierra_statement_weights(
745    weights: &OrderedHashMap<Vec<String>, usize>,
746    f: &mut std::fmt::Formatter<'_>,
747) -> std::fmt::Result {
748    for (key, weight) in weights.iter() {
749        f.write_fmt(format_args!("{} {weight}\n", key.join(";")))?;
750    }
751    Ok(())
752}