diff --git a/crates/cairo-profiler/src/main.rs b/crates/cairo-profiler/src/main.rs
index aa0e0a8..9035e14 100644
--- a/crates/cairo-profiler/src/main.rs
+++ b/crates/cairo-profiler/src/main.rs
@@ -6,7 +6,7 @@ use std::{
use crate::profiler_config::ProfilerConfig;
use crate::sierra_loader::collect_and_compile_all_sierra_programs;
use crate::trace_reader::collect_samples_from_trace;
-use crate::trace_reader::syscall::read_and_parse_versioned_constants_file;
+use crate::versioned_constants_reader::read_and_parse_versioned_constants_file;
use anyhow::{Context, Result};
use bytes::{Buf, BytesMut};
use camino::Utf8PathBuf;
@@ -20,6 +20,7 @@ mod profile_builder;
mod profiler_config;
mod sierra_loader;
mod trace_reader;
+mod versioned_constants_reader;
#[derive(Parser, Debug)]
#[command(version)]
@@ -51,6 +52,10 @@ struct Cli {
#[arg(long)]
show_inlined_functions: bool,
+ /// Path to `versioned_constants.json` file, that includes resource cost map
+ /// If not provided, 0.13.2.1 version will be used.
+ /// Versioned files can be found in sequencer repo:
+ ///
#[arg(long)]
versioned_constants_path: Option,
}
@@ -60,6 +65,8 @@ fn main() -> Result<()> {
let data = fs::read_to_string(&cli.path_to_trace_data)
.context("Failed to read call trace from a file")?;
+ let os_resources_map = read_and_parse_versioned_constants_file(&cli.versioned_constants_path)
+ .context("Failed to parse versioned constants file")?;
let serialized_trace: CallTrace =
serde_json::from_str(&data).context("Failed to deserialize call trace")?;
@@ -76,9 +83,6 @@ fn main() -> Result<()> {
);
}
- let os_resources_map = read_and_parse_versioned_constants_file(&cli.versioned_constants_path)
- .expect("Failed to parse versioned constants file");
-
let samples = collect_samples_from_trace(
&serialized_trace,
&compiled_artifacts_cache,
diff --git a/crates/cairo-profiler/src/trace_reader.rs b/crates/cairo-profiler/src/trace_reader.rs
index e7d6b3a..ab8bc1a 100644
--- a/crates/cairo-profiler/src/trace_reader.rs
+++ b/crates/cairo-profiler/src/trace_reader.rs
@@ -7,14 +7,13 @@ use crate::trace_reader::function_name::FunctionName;
use crate::trace_reader::function_trace_builder::collect_function_level_profiling_info;
use crate::trace_reader::sample::{FunctionCall, Sample};
-use crate::trace_reader::syscall::collect_syscall_sample;
-use trace_data::{CallTrace, CallTraceNode, ExecutionResources, OsResources};
+use crate::versioned_constants_reader::OsResources;
+use trace_data::{CallTrace, CallTraceNode, ExecutionResources};
pub mod function_name;
mod function_trace_builder;
pub mod sample;
-pub mod syscall;
pub fn collect_samples_from_trace(
trace: &CallTrace,
@@ -76,6 +75,7 @@ fn collect_samples<'a>(
cairo_execution_info.casm_level_info.run_with_call_header,
&compiled_artifacts.statements_functions_map,
&FunctionLevelConfig::from(profiler_config),
+ os_resources_map,
);
let mut function_samples = function_level_profiling_info
@@ -125,19 +125,6 @@ fn collect_samples<'a>(
&trace.used_l1_resources,
));
- call_resources
- .syscall_counter
- .iter()
- .filter(|(_, count)| **count != 0)
- .for_each(|(syscall, count)| {
- samples.push(collect_syscall_sample(
- current_entrypoint_call_stack.clone(),
- *syscall,
- *count,
- os_resources_map,
- ));
- });
-
current_entrypoint_call_stack.pop();
Ok(&trace.cumulative_resources)
diff --git a/crates/cairo-profiler/src/trace_reader/function_trace_builder.rs b/crates/cairo-profiler/src/trace_reader/function_trace_builder.rs
index 55831d9..d113c1b 100644
--- a/crates/cairo-profiler/src/trace_reader/function_trace_builder.rs
+++ b/crates/cairo-profiler/src/trace_reader/function_trace_builder.rs
@@ -8,6 +8,7 @@ use crate::trace_reader::function_trace_builder::inlining::build_original_call_s
use crate::trace_reader::sample::{
FunctionCall, InternalFunctionCall, MeasurementUnit, MeasurementValue, Sample,
};
+use crate::versioned_constants_reader::{map_syscall_name_to_selector, OsResources};
use cairo_lang_sierra::extensions::core::{CoreConcreteLibfunc, CoreLibfunc, CoreType};
use cairo_lang_sierra::program::{GenStatement, Program, StatementIdx};
use cairo_lang_sierra::program_registry::ProgramRegistry;
@@ -53,6 +54,7 @@ pub fn collect_function_level_profiling_info(
run_with_call_header: bool,
statements_functions_map: &Option,
function_level_config: &FunctionLevelConfig,
+ os_resources_map: &OsResources,
) -> FunctionLevelProfilingInfo {
let sierra_program_registry = &ProgramRegistry::::new(program).unwrap();
@@ -81,6 +83,10 @@ pub fn collect_function_level_profiling_info(
// the number of total steps. The value is different from zero only for functions run with header.
let mut header_steps = Steps(0);
let mut end_of_program_reached = false;
+ // Syscalls cannot be mapped using pc offsets
+ // They can be recognised by GenStatement::Invocation but they do not have GenStatement::Return
+ // That's why we must track entry to a syscall, and leave as soon as we're out of given GenStatement::Invocation
+ let mut in_syscall = false;
for step in trace {
// Skip the header.
@@ -115,10 +121,11 @@ pub fn collect_function_level_profiling_info(
let current_call_stack = build_current_call_stack(
&call_stack,
- current_function_name,
+ current_function_name.clone(),
function_level_config.show_inlined_functions,
sierra_statement_idx,
statements_functions_map.as_ref(),
+ in_syscall,
);
*functions_stack_traces
@@ -131,11 +138,42 @@ pub fn collect_function_level_profiling_info(
match gen_statement {
GenStatement::Invocation(invocation) => {
- if matches!(
- sierra_program_registry.get_libfunc(&invocation.libfunc_id),
- Ok(CoreConcreteLibfunc::FunctionCall(_))
- ) {
- call_stack.enter_function_call(current_call_stack);
+ match sierra_program_registry.get_libfunc(&invocation.libfunc_id) {
+ Ok(CoreConcreteLibfunc::FunctionCall(_)) => {
+ call_stack.enter_function_call(current_call_stack);
+ }
+ Ok(CoreConcreteLibfunc::StarkNet(_)) => {
+ if invocation.libfunc_id.debug_name.is_none() {
+ // this libfunc is not included in the artifact file
+ // it is likely to be a libfunc from the test itself
+ continue;
+ }
+
+ if !in_syscall {
+ in_syscall = true;
+ let mut new_current_call_stack = current_call_stack.clone();
+ new_current_call_stack.push(FunctionCall::InternalFunctionCall(
+ InternalFunctionCall::Syscall(FunctionName(
+ invocation
+ .libfunc_id
+ .debug_name
+ .clone()
+ .unwrap()
+ .to_string(),
+ )),
+ ));
+
+ call_stack.enter_function_call(new_current_call_stack);
+ }
+ }
+ _ => {
+ // If we were in a syscall this is the time we go out of it, as pcs no longer
+ // belong to GenStatement::Invocation of CoreConcreteLibfunc::StarkNet
+ if in_syscall {
+ call_stack.exit_function_call();
+ in_syscall = false;
+ }
+ }
}
}
GenStatement::Return(_) => {
@@ -146,16 +184,7 @@ pub fn collect_function_level_profiling_info(
}
}
- let functions_samples = functions_stack_traces
- .into_iter()
- .map(|(call_stack, steps)| Sample {
- call_stack,
- measurements: HashMap::from([(
- MeasurementUnit::from("steps".to_string()),
- MeasurementValue(i64::try_from(steps.0).unwrap()),
- )]),
- })
- .collect_vec();
+ let functions_samples = stack_trace_to_samples(functions_stack_traces, os_resources_map);
FunctionLevelProfilingInfo {
functions_samples,
@@ -198,12 +227,14 @@ fn build_current_call_stack(
show_inlined_functions: bool,
sierra_statement_idx: StatementIdx,
statements_functions_map: Option<&StatementsFunctionsMap>,
+ in_syscall: bool,
) -> VecWithLimitedCapacity {
let mut current_call_stack = call_stack.current_call_stack().clone();
if current_call_stack.len() == 0
|| *current_call_stack[current_call_stack.len() - 1].function_name()
!= current_function_name
+ && !in_syscall
{
current_call_stack.push(FunctionCall::InternalFunctionCall(
InternalFunctionCall::NonInlined(current_function_name),
@@ -220,3 +251,62 @@ fn build_current_call_stack(
current_call_stack
}
}
+
+fn stack_trace_to_samples(
+ functions_stack_traces: HashMap, Steps>,
+ os_resources_map: &OsResources,
+) -> Vec {
+ functions_stack_traces
+ .into_iter()
+ .map(|(call_stack, steps)| {
+ let mut measurements: HashMap = vec![(
+ MeasurementUnit::from("steps".to_string()),
+ MeasurementValue(i64::try_from(steps.0).unwrap()),
+ )]
+ .into_iter()
+ .collect();
+
+ if let Some(FunctionCall::InternalFunctionCall(InternalFunctionCall::Syscall(
+ function_name,
+ ))) = call_stack.last()
+ {
+ let Ok(syscall) = map_syscall_name_to_selector(function_name.0.as_str()) else {
+ // todo: print the error in debug mode
+ return Sample {
+ call_stack,
+ measurements,
+ };
+ };
+ let resources = os_resources_map
+ .execute_syscalls
+ .get(&syscall)
+ .unwrap_or_else(|| {
+ panic!("Missing syscall {syscall:?} from versioned constants file")
+ });
+
+ if let Some(value) =
+ measurements.get_mut(&MeasurementUnit::from("steps".to_string()))
+ {
+ *value += MeasurementValue(i64::try_from(resources.n_steps).unwrap());
+ }
+
+ measurements.insert(
+ MeasurementUnit::from("memory_holes".to_string()),
+ MeasurementValue(i64::try_from(resources.n_memory_holes).unwrap()),
+ );
+
+ for (builtin, b_count) in &resources.builtin_instance_counter {
+ measurements.insert(
+ MeasurementUnit::from(builtin.to_string()),
+ MeasurementValue(i64::try_from(*b_count).unwrap()),
+ );
+ }
+ }
+
+ Sample {
+ call_stack,
+ measurements,
+ }
+ })
+ .collect_vec()
+}
diff --git a/crates/cairo-profiler/src/trace_reader/sample.rs b/crates/cairo-profiler/src/trace_reader/sample.rs
index ee0841a..516f817 100644
--- a/crates/cairo-profiler/src/trace_reader/sample.rs
+++ b/crates/cairo-profiler/src/trace_reader/sample.rs
@@ -1,5 +1,6 @@
use crate::trace_reader::function_name::FunctionName;
use std::collections::HashMap;
+use std::ops::AddAssign;
use trace_data::{ExecutionResources, L1Resources};
pub(crate) struct Sample {
@@ -53,6 +54,12 @@ impl From for MeasurementUnit {
#[derive(Debug, Clone)]
pub struct MeasurementValue(pub i64);
+impl AddAssign for MeasurementValue {
+ fn add_assign(&mut self, other: Self) {
+ self.0 += other.0;
+ }
+}
+
impl Sample {
pub fn from(
call_stack: Vec,
diff --git a/crates/cairo-profiler/src/trace_reader/syscall.rs b/crates/cairo-profiler/src/trace_reader/syscall.rs
deleted file mode 100644
index bcd9436..0000000
--- a/crates/cairo-profiler/src/trace_reader/syscall.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use crate::trace_reader::function_name::FunctionName;
-use crate::trace_reader::sample::InternalFunctionCall::Syscall;
-use crate::trace_reader::sample::{FunctionCall, MeasurementUnit, MeasurementValue, Sample};
-use anyhow::Result;
-use camino::Utf8PathBuf;
-use serde_json::{from_str, from_value, Value};
-use std::fs;
-use trace_data::{DeprecatedSyscallSelector, OsResources};
-
-pub fn collect_syscall_sample(
- mut call_stack: Vec,
- syscall: DeprecatedSyscallSelector,
- count: usize,
- os_resources_map: &OsResources,
-) -> Sample {
- call_stack.push(FunctionCall::InternalFunctionCall(Syscall(FunctionName(
- format!("syscall: {syscall:?}"),
- ))));
- let resources = os_resources_map
- .execute_syscalls
- .get(&syscall)
- .unwrap_or_else(|| panic!("Missing syscall {syscall:?} from versioned constants file"));
- Sample {
- call_stack,
- measurements: {
- let mut measurements = vec![
- (
- MeasurementUnit::from("steps".to_string()),
- MeasurementValue(
- resources
- .n_steps
- .checked_mul(count)
- .expect("Multiplication overflow")
- .try_into()
- .expect("Overflow while converting to i64"),
- ),
- ),
- (
- MeasurementUnit::from("memory_holes".to_string()),
- MeasurementValue(
- resources
- .n_memory_holes
- .checked_mul(count)
- .expect("Multiplication overflow")
- .try_into()
- .expect("Overflow while converting to i64"),
- ),
- ),
- ];
-
- for (builtin, b_count) in &resources.builtin_instance_counter {
- measurements.push((
- MeasurementUnit::from(builtin.to_string()),
- MeasurementValue(
- b_count
- .checked_mul(count)
- .expect("Multiplication overflow")
- .try_into()
- .expect("Overflow while converting to i64"),
- ),
- ));
- }
-
- measurements.into_iter().collect()
- },
- }
-}
-
-pub fn read_and_parse_versioned_constants_file(path: &Option) -> Result {
- let file_content = match path {
- Some(path) => fs::read_to_string(path)?,
- // include_str requires a string literal
- None => include_str!("../../resources/versioned_constants_0_13_2_1.json").to_string(),
- };
- let json_value: Value = from_str(&file_content)?;
- let parsed_resources = json_value
- .get("os_resources")
- .expect("Field 'os_resources' not found in versioned constants file");
- let os_resources: OsResources = from_value(parsed_resources.clone())?;
- Ok(os_resources)
-}
diff --git a/crates/cairo-profiler/src/versioned_constants_reader.rs b/crates/cairo-profiler/src/versioned_constants_reader.rs
new file mode 100644
index 0000000..ec25e71
--- /dev/null
+++ b/crates/cairo-profiler/src/versioned_constants_reader.rs
@@ -0,0 +1,48 @@
+use anyhow::{anyhow, Context, Result};
+use camino::Utf8PathBuf;
+use serde::{Deserialize, Serialize};
+use serde_json::{from_str, from_value, Value};
+use std::collections::HashMap;
+use std::fs;
+use trace_data::{DeprecatedSyscallSelector, VmExecutionResources};
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct OsResources {
+ pub execute_syscalls: HashMap,
+}
+
+pub fn read_and_parse_versioned_constants_file(path: &Option) -> Result {
+ let file_content = match path {
+ Some(path) => fs::read_to_string(path)?,
+ // include_str requires a string literal
+ None => include_str!("../resources/versioned_constants_0_13_2_1.json").to_string(),
+ };
+ let json_value: Value = from_str(&file_content)?;
+ let parsed_resources = json_value
+ .get("os_resources")
+ .context("Field 'os_resources' not found in versioned constants file")?;
+ let os_resources: OsResources = from_value(parsed_resources.clone())?;
+ Ok(os_resources)
+}
+
+pub fn map_syscall_name_to_selector(syscall: &str) -> Result {
+ match syscall {
+ "call_contract_syscall" => Ok(DeprecatedSyscallSelector::CallContract),
+ "deploy_syscall" => Ok(DeprecatedSyscallSelector::Deploy),
+ "emit_event_syscall" => Ok(DeprecatedSyscallSelector::EmitEvent),
+ "get_block_hash_syscall" => Ok(DeprecatedSyscallSelector::GetBlockHash),
+ "get_execution_info_syscall" | "get_execution_info_v2_syscall" => {
+ Ok(DeprecatedSyscallSelector::GetExecutionInfo)
+ }
+ "keccak_syscall" => Ok(DeprecatedSyscallSelector::Keccak),
+ "library_call_syscall" => Ok(DeprecatedSyscallSelector::LibraryCall),
+ "replace_class_syscall" => Ok(DeprecatedSyscallSelector::ReplaceClass),
+ "send_message_to_l1_syscall" => Ok(DeprecatedSyscallSelector::SendMessageToL1),
+ "storage_read_syscall" => Ok(DeprecatedSyscallSelector::StorageRead),
+ "storage_write_syscall" => Ok(DeprecatedSyscallSelector::StorageWrite),
+ "sha256_process_block_syscall" => Ok(DeprecatedSyscallSelector::Sha256ProcessBlock),
+ _ => Err(anyhow!(
+ "Missing mapping for {syscall:?} - used resources values may not be complete"
+ )),
+ }
+}
diff --git a/crates/trace-data/src/lib.rs b/crates/trace-data/src/lib.rs
index 9d83080..cdaa706 100644
--- a/crates/trace-data/src/lib.rs
+++ b/crates/trace-data/src/lib.rs
@@ -213,8 +213,3 @@ impl ExecutionResources {
pub struct L1Resources {
pub l2_l1_message_sizes: Vec,
}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct OsResources {
- pub execute_syscalls: HashMap,
-}