use std::fmt::Display;
use cairo_lang_lowering::ids::FunctionLongId;
use cairo_lang_sierra::ids::ConcreteLibfuncId;
use cairo_lang_sierra::program::{GenStatement, Program, StatementIdx};
use cairo_lang_sierra_generator::db::SierraGenGroup;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use cairo_lang_utils::{require, LookupIntern};
use smol_str::SmolStr;
#[cfg(test)]
#[path = "profiling_test.rs"]
mod test;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ProfilingInfo {
pub sierra_statement_weights: UnorderedHashMap<StatementIdx, usize>,
pub stack_trace_weights: UnorderedHashMap<Vec<usize>, usize>,
}
#[derive(Default)]
pub struct LibfuncWeights {
pub concrete_libfunc_weights: Option<OrderedHashMap<SmolStr, usize>>,
pub generic_libfunc_weights: Option<OrderedHashMap<SmolStr, usize>>,
pub return_weight: Option<usize>,
}
impl Display for LibfuncWeights {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(concrete_libfunc_weights) = &self.concrete_libfunc_weights {
writeln!(f, "Weight by concrete libfunc:")?;
for (concrete_name, weight) in concrete_libfunc_weights.iter() {
writeln!(f, " libfunc {concrete_name}: {weight}")?;
}
writeln!(
f,
" return: {}",
self.return_weight.expect(
"return_weight should have a value if concrete_libfunc_weights has a value"
)
)?;
}
if let Some(generic_libfunc_weights) = &self.generic_libfunc_weights {
writeln!(f, "Weight by generic libfunc:")?;
for (generic_name, weight) in generic_libfunc_weights.iter() {
writeln!(f, " libfunc {generic_name}: {weight}")?;
}
writeln!(
f,
" return: {}",
self.return_weight.expect(
"return_weight should have a value if generic_libfunc_weights has a value"
)
)?;
}
Ok(())
}
}
#[derive(Default)]
pub struct UserFunctionWeights {
pub user_function_weights: Option<OrderedHashMap<SmolStr, usize>>,
pub original_user_function_weights: Option<OrderedHashMap<String, usize>>,
}
impl Display for UserFunctionWeights {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(user_function_weights) = &self.user_function_weights {
writeln!(f, "Weight by user function (inc. generated):")?;
for (name, weight) in user_function_weights.iter() {
writeln!(f, " function {name}: {weight}")?;
}
}
if let Some(original_user_function_weights) = &self.original_user_function_weights {
writeln!(f, "Weight by original user function (exc. generated):")?;
for (name, weight) in original_user_function_weights.iter() {
writeln!(f, " function {name}: {weight}")?;
}
}
Ok(())
}
}
#[derive(Default)]
pub struct StackTraceWeights {
pub sierra_stack_trace_weights: Option<OrderedHashMap<Vec<String>, usize>>,
pub cairo_stack_trace_weights: Option<OrderedHashMap<Vec<String>, usize>>,
}
impl Display for StackTraceWeights {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(sierra_stack_trace_weights) = &self.sierra_stack_trace_weights {
writeln!(f, "Weight by Sierra stack trace:")?;
for (stack_trace, weight) in sierra_stack_trace_weights.iter() {
let stack_trace_str = stack_trace.join(" -> ");
writeln!(f, " {stack_trace_str}: {weight}")?;
}
}
if let Some(cairo_stack_trace_weights) = &self.cairo_stack_trace_weights {
writeln!(f, "Weight by Cairo stack trace:")?;
for (stack_trace, weight) in cairo_stack_trace_weights.iter() {
let stack_trace_str = stack_trace.join(" -> ");
writeln!(f, " {stack_trace_str}: {weight}")?;
}
}
Ok(())
}
}
pub struct ProcessedProfilingInfo {
pub sierra_statement_weights:
Option<OrderedHashMap<StatementIdx, (usize, GenStatement<StatementIdx>)>>,
pub stack_trace_weights: StackTraceWeights,
pub libfunc_weights: LibfuncWeights,
pub user_function_weights: UserFunctionWeights,
pub cairo_function_weights: Option<OrderedHashMap<String, usize>>,
}
impl Display for ProcessedProfilingInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(sierra_statement_weights) = &self.sierra_statement_weights {
writeln!(f, "Weight by sierra statement:")?;
for (statement_idx, (weight, gen_statement)) in sierra_statement_weights.iter() {
writeln!(f, " statement {statement_idx}: {weight} ({gen_statement})")?;
}
}
self.libfunc_weights.fmt(f)?;
self.user_function_weights.fmt(f)?;
if let Some(cairo_function_weights) = &self.cairo_function_weights {
writeln!(f, "Weight by Cairo function:")?;
for (function_identifier, weight) in cairo_function_weights.iter() {
writeln!(f, " function {function_identifier}: {weight}")?;
}
}
self.stack_trace_weights.fmt(f)?;
Ok(())
}
}
pub struct ProfilingInfoProcessorParams {
pub min_weight: usize,
pub process_by_statement: bool,
pub process_by_concrete_libfunc: bool,
pub process_by_generic_libfunc: bool,
pub process_by_user_function: bool,
pub process_by_original_user_function: bool,
pub process_by_cairo_function: bool,
pub process_by_stack_trace: bool,
pub process_by_cairo_stack_trace: bool,
}
impl Default for ProfilingInfoProcessorParams {
fn default() -> Self {
Self {
min_weight: 1,
process_by_statement: true,
process_by_concrete_libfunc: true,
process_by_generic_libfunc: true,
process_by_user_function: true,
process_by_original_user_function: true,
process_by_cairo_function: true,
process_by_stack_trace: true,
process_by_cairo_stack_trace: true,
}
}
}
pub struct ProfilingInfoProcessor<'a> {
db: Option<&'a dyn SierraGenGroup>,
sierra_program: Program,
statements_functions: UnorderedHashMap<StatementIdx, String>,
params: ProfilingInfoProcessorParams,
}
impl<'a> ProfilingInfoProcessor<'a> {
pub fn new(
db: Option<&'a dyn SierraGenGroup>,
sierra_program: Program,
statements_functions: UnorderedHashMap<StatementIdx, String>,
) -> Self {
Self {
db,
sierra_program,
statements_functions,
params: ProfilingInfoProcessorParams::default(),
}
}
pub fn process(&self, raw_profiling_info: &ProfilingInfo) -> ProcessedProfilingInfo {
self.process_ex(raw_profiling_info, &self.params)
}
pub fn process_ex(
&self,
raw_profiling_info: &ProfilingInfo,
params: &ProfilingInfoProcessorParams,
) -> ProcessedProfilingInfo {
let sierra_statement_weights_iter = raw_profiling_info
.sierra_statement_weights
.iter_sorted_by_key(|(pc, count)| (usize::MAX - **count, **pc));
let sierra_statement_weights =
self.process_sierra_statement_weights(sierra_statement_weights_iter.clone(), params);
let stack_trace_weights = self.process_stack_trace_weights(raw_profiling_info, params);
let libfunc_weights =
self.process_libfunc_weights(sierra_statement_weights_iter.clone(), params);
let user_function_weights =
self.process_user_function_weights(sierra_statement_weights_iter.clone(), params);
let cairo_function_weights =
self.process_cairo_function_weights(sierra_statement_weights_iter, params);
ProcessedProfilingInfo {
sierra_statement_weights,
stack_trace_weights,
libfunc_weights,
user_function_weights,
cairo_function_weights,
}
}
fn process_sierra_statement_weights(
&self,
sierra_statement_weights_iter: std::vec::IntoIter<(&StatementIdx, &usize)>,
params: &ProfilingInfoProcessorParams,
) -> Option<OrderedHashMap<StatementIdx, (usize, GenStatement<StatementIdx>)>> {
require(params.process_by_statement)?;
Some(
sierra_statement_weights_iter
.filter(|&(_, weight)| (*weight >= params.min_weight))
.map(|(statement_idx, weight)| {
(*statement_idx, (*weight, self.statement_idx_to_gen_statement(statement_idx)))
})
.collect(),
)
}
fn process_stack_trace_weights(
&self,
raw_profiling_info: &ProfilingInfo,
params: &ProfilingInfoProcessorParams,
) -> StackTraceWeights {
let sierra_stack_trace_weights = params.process_by_stack_trace.then(|| {
raw_profiling_info
.stack_trace_weights
.iter_sorted_by_key(|(idx_stack_trace, weight)| {
(usize::MAX - **weight, (*idx_stack_trace).clone())
})
.map(|(idx_stack_trace, weight)| {
(
index_stack_trace_to_name_stack_trace(
&self.sierra_program,
idx_stack_trace,
),
*weight,
)
})
.collect()
});
let cairo_stack_trace_weights = params.process_by_cairo_stack_trace.then(|| {
let db = self.db.expect("DB must be set with `process_by_cairo_stack_trace=true`.");
raw_profiling_info
.stack_trace_weights
.filter_cloned(|trace, _| is_cairo_trace(db, &self.sierra_program, trace))
.into_iter_sorted_by_key(|(trace, weight)| (usize::MAX - *weight, trace.clone()))
.map(|(idx_stack_trace, weight)| {
(
index_stack_trace_to_name_stack_trace(
&self.sierra_program,
&idx_stack_trace,
),
weight,
)
})
.collect()
});
StackTraceWeights { sierra_stack_trace_weights, cairo_stack_trace_weights }
}
fn process_libfunc_weights(
&self,
sierra_statement_weights: std::vec::IntoIter<(&StatementIdx, &usize)>,
params: &ProfilingInfoProcessorParams,
) -> LibfuncWeights {
if !params.process_by_concrete_libfunc && !params.process_by_generic_libfunc {
return LibfuncWeights::default();
}
let mut return_weight = 0;
let mut libfunc_weights = UnorderedHashMap::<ConcreteLibfuncId, usize>::default();
for (statement_idx, weight) in sierra_statement_weights {
match self.statement_idx_to_gen_statement(statement_idx) {
GenStatement::Invocation(invocation) => {
*(libfunc_weights.entry(invocation.libfunc_id.clone()).or_insert(0)) += weight;
}
GenStatement::Return(_) => {
return_weight += weight;
}
}
}
let generic_libfunc_weights = params.process_by_generic_libfunc.then(|| {
let db: &dyn SierraGenGroup =
self.db.expect("DB must be set with `process_by_generic_libfunc=true`.");
libfunc_weights
.aggregate_by(
|k| -> SmolStr {
db.lookup_intern_concrete_lib_func(k.clone()).generic_id.to_string().into()
},
|v1: &usize, v2| v1 + v2,
&0,
)
.filter(|_, weight| *weight >= params.min_weight)
.iter_sorted_by_key(|(generic_name, weight)| {
(usize::MAX - **weight, (*generic_name).clone())
})
.map(|(generic_name, weight)| (generic_name.clone(), *weight))
.collect()
});
let concrete_libfunc_weights = params.process_by_concrete_libfunc.then(|| {
libfunc_weights
.filter(|_, weight| *weight >= params.min_weight)
.iter_sorted_by_key(|(libfunc_id, weight)| {
(usize::MAX - **weight, (*libfunc_id).to_string())
})
.map(|(libfunc_id, weight)| (SmolStr::from(libfunc_id.to_string()), *weight))
.collect()
});
LibfuncWeights {
concrete_libfunc_weights,
generic_libfunc_weights,
return_weight: Some(return_weight),
}
}
fn process_user_function_weights(
&self,
sierra_statement_weights: std::vec::IntoIter<(&StatementIdx, &usize)>,
params: &ProfilingInfoProcessorParams,
) -> UserFunctionWeights {
if !params.process_by_user_function && !params.process_by_original_user_function {
return UserFunctionWeights::default();
}
let mut user_functions = UnorderedHashMap::<usize, usize>::default();
for (statement_idx, weight) in sierra_statement_weights {
let function_idx: usize =
user_function_idx_by_sierra_statement_idx(&self.sierra_program, *statement_idx);
*(user_functions.entry(function_idx).or_insert(0)) += weight;
}
let original_user_function_weights = params.process_by_original_user_function.then(|| {
let db: &dyn SierraGenGroup =
self.db.expect("DB must be set with `process_by_original_user_function=true`.");
user_functions
.aggregate_by(
|idx| {
let lowering_function_id =
self.sierra_program.funcs[*idx].id.clone().lookup_intern(db);
lowering_function_id.semantic_full_path(db.upcast())
},
|x, y| x + y,
&0,
)
.filter(|_, weight| *weight >= params.min_weight)
.iter_sorted_by_key(|(orig_name, weight)| {
(usize::MAX - **weight, (*orig_name).clone())
})
.map(|(orig_name, weight)| (orig_name.clone(), *weight))
.collect()
});
let user_function_weights = params.process_by_user_function.then(|| {
user_functions
.filter(|_, weight| *weight >= params.min_weight)
.iter_sorted_by_key(|(idx, weight)| {
(usize::MAX - **weight, self.sierra_program.funcs[**idx].id.to_string())
})
.map(|(idx, weight)| {
let func: &cairo_lang_sierra::program::GenFunction<StatementIdx> =
&self.sierra_program.funcs[*idx];
(func.id.to_string().into(), *weight)
})
.collect()
});
UserFunctionWeights { user_function_weights, original_user_function_weights }
}
fn process_cairo_function_weights(
&self,
sierra_statement_weights: std::vec::IntoIter<(&StatementIdx, &usize)>,
params: &ProfilingInfoProcessorParams,
) -> Option<OrderedHashMap<String, usize>> {
require(params.process_by_cairo_function)?;
let mut cairo_functions = UnorderedHashMap::<_, _>::default();
for (statement_idx, weight) in sierra_statement_weights {
let function_identifier = self
.statements_functions
.get(statement_idx)
.unwrap_or(&"unknown".to_string())
.clone();
*(cairo_functions.entry(function_identifier).or_insert(0)) += weight;
}
Some(
cairo_functions
.filter(|_, weight| *weight >= params.min_weight)
.iter_sorted_by_key(|(function_identifier, weight)| {
(usize::MAX - **weight, (*function_identifier).clone())
})
.map(|(function_identifier, weight)| (function_identifier.clone(), *weight))
.collect(),
)
}
fn statement_idx_to_gen_statement(
&self,
statement_idx: &StatementIdx,
) -> GenStatement<StatementIdx> {
self.sierra_program
.statements
.get(statement_idx.0)
.unwrap_or_else(|| panic!("Failed fetching statement index {}", statement_idx.0))
.clone()
}
}
fn is_cairo_trace(
db: &dyn SierraGenGroup,
sierra_program: &Program,
sierra_trace: &[usize],
) -> bool {
sierra_trace.iter().all(|sierra_function_idx| {
let sierra_function = &sierra_program.funcs[*sierra_function_idx];
let lowering_function_id = sierra_function.id.lookup_intern(db);
matches!(lowering_function_id.lookup_intern(db), FunctionLongId::Semantic(_))
})
}
pub fn user_function_idx_by_sierra_statement_idx(
sierra_program: &Program,
statement_idx: StatementIdx,
) -> usize {
sierra_program.funcs.partition_point(|f| f.entry_point.0 <= statement_idx.0) - 1
}
fn index_stack_trace_to_name_stack_trace(
sierra_program: &Program,
idx_stack_trace: &[usize],
) -> Vec<String> {
idx_stack_trace.iter().map(|idx| sierra_program.funcs[*idx].id.to_string()).collect()
}