diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2618740f290..2452a56e7bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -292,6 +292,36 @@ jobs: - name: Cargo Test sway-lib-std run: cargo test --locked --manifest-path ./test/src/sdk-harness/Cargo.toml -- --nocapture + forc-run-benchmarks: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + - uses: Swatinem/rust-cache@v2 + - name: Install Forc + run: cargo install --locked --debug --path ./forc + - name: Run benchmarks + run: ./benchmark.sh + - name: Checkout benchmark data + uses: actions/checkout@v3 + with: + repository: FuelLabs/sway-performance-data + path: performance-data + token: ${{ secrets.CI_ACCOUNT_PAT }} + - name: Prepare benchmarks data for commit + if: github.event_name == 'push' + run: ./benchmark.sh --prepare-for-commit + - uses: EndBug/add-and-commit@v9 + with: + cwd: './performance-data' + message: 'Updated benchmark data' + default_author: github_actions + forc-unit-tests: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 367af64d2ed..06522634c92 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ sway-lib-std/Forc.lock # Forc's build directory out + +# Benchmarks data output directory +benchmarks/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 24e140c604f..1136dce5fbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1797,6 +1797,7 @@ dependencies = [ "sway-error", "sway-types", "sway-utils", + "sysinfo", "toml", "tracing", "url", @@ -3426,6 +3427,15 @@ dependencies = [ "notify", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -5181,6 +5191,7 @@ dependencies = [ "sway-parse", "sway-types", "sway-utils", + "sysinfo", "thiserror", "tracing", "uint", @@ -5306,6 +5317,9 @@ dependencies = [ [[package]] name = "sway-utils" version = "0.39.1" +dependencies = [ + "serde", +] [[package]] name = "swayfmt" @@ -5375,6 +5389,21 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "sysinfo" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02f1dc6930a439cc5d154221b5387d153f8183529b07c19aca24ea31e0a167e1" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "tai64" version = "4.0.0" @@ -5499,6 +5528,7 @@ dependencies = [ "serde_json", "sway-core", "sway-ir", + "sway-utils", "textwrap 0.16.0", "tokio", "toml", diff --git a/benchmark.sh b/benchmark.sh new file mode 100755 index 00000000000..5ea8698aecc --- /dev/null +++ b/benchmark.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +PREPARE_FOR_COMMIT=false +while [[ $# -gt 0 ]] +do + key="$1" + case $key in + --prepare-for-commit) + PREPARE_FOR_COMMIT=true + shift # past argument + ;; + *) + shift # past argument + ;; + esac +done + +benchmarks_dir="benchmarks" +if [ ! -d "$benchmarks_dir" ]; then + mkdir -p "$benchmarks_dir" +fi + +if [ -d "$SCRIPT_DIR/target/release" ]; then + build_type="release" +elif [ -d "$SCRIPT_DIR/target/debug" ]; then + build_type="debug" +else + echo "Neither target/release nor target/debug directories found. Exiting..." + exit 1 +fi + +forc_path="$SCRIPT_DIR/target/$build_type/forc" + +# prepare the benchmark data for commit if requested +if $PREPARE_FOR_COMMIT; then + sway_peformance_data_dir=performance-data + sway_performance_data_repo_url=git@github.com:FuelLabs/sway-performance-data.git + + if [ ! -d "$SCRIPT_DIR/$sway_peformance_data_dir" ]; then + echo "Directory $sway_peformance_data_dir not found. Cloning the repository..." + git clone "$sway_performance_data_repo_url" "$sway_peformance_data_dir" + echo "Repository cloned into $sway_peformance_data_dir." + else + echo "Updating sway-performance-data repository..." + git -C "$SCRIPT_DIR/$sway_peformance_data_dir" pull + fi + + mkdir -p "$SCRIPT_DIR/$sway_peformance_data_dir/$GITHUB_SHA" + cp -r $benchmarks_dir/* "$SCRIPT_DIR/$sway_peformance_data_dir/$GITHUB_SHA" +else + sway_libs_dir=sway-libs + sway_libs_repo_url=https://github.com/FuelLabs/sway-libs.git + sway_libs_branch_name="benchmarks" + + if [ ! -d "$SCRIPT_DIR/$sway_libs_dir" ]; then + echo "Directory $sway_libs_dir not found. Cloning the repository..." + git clone -b "$sway_libs_branch_name" "$sway_libs_repo_url" "$sway_libs_dir" + echo "Repository cloned with branch $sway_libs_branch_name into $sway_libs_dir." + fi + + libs=( + "fixed_point" + "merkle_proof" + "nft" + "ownership" + "reentrancy" + "signed_integers" + "storagemapvec" + "strings/storage_string" + "strings/string" + ) + + sway_apps_dir=sway-applications + sway_apps_repo_url=https://github.com/FuelLabs/sway-applications.git + sway_apps_branch_name="master" + + if [ ! -d "$SCRIPT_DIR/$sway_apps_dir" ]; then + echo "Directory $sway_apps_dir not found. Cloning the repository..." + git clone -b "$sway_apps_branch_name" "$sway_apps_repo_url" "$sway_apps_dir" + echo "Repository cloned with branch $sway_apps_branch_name into $sway_apps_dir." + fi + + sway_libs_revision=$(git -C $SCRIPT_DIR/$sway_libs_dir rev-parse HEAD) + sway_apps_revision=$(git -C $SCRIPT_DIR/$sway_apps_dir rev-parse HEAD) + sway_git_revision=$(git rev-parse HEAD) + + for lib in "${libs[@]}"; do + echo "Benchmarking $lib..." + project_name=$(basename "$lib") + metrics_json_file="$benchmarks_dir/$project_name.json" + output=$(/usr/bin/time -f '{"elapsed": "%e", "cpu_usage": "%P", "memory": "%MKB"}' \ + $forc_path build --path "$SCRIPT_DIR/sway-libs/libs/$lib" \ + --metrics-outfile="$metrics_json_file" 2>&1) + + exit_status=$? + if [ $exit_status -ne 0 ]; then + echo " Failed, ignoring." + continue + fi + + # filter out forc warnings by only matching on the JSON metrics data + json_stats=$(echo "$output" | grep -Eo '^\s*{[^}]*}') + + metrics_json=$(cat "$metrics_json_file" | jq '{phases: .}') + merged_json=$(jq -s '.[0] * .[1]' <(echo "$metrics_json") <(echo "$json_stats")) + merged_json=$(jq --arg bt "$build_type" '. + {build_type: $bt}' <<< "$merged_json") + merged_json=$(jq --arg gr "$sway_apps_revision" '. + {sway_apps_revision: $gr}' <<< "$merged_json") + merged_json=$(jq --arg gr "$sway_libs_revision" '. + {sway_libs_revision: $gr}' <<< "$merged_json") + merged_json=$(jq --arg gr "$sway_git_revision" '. + {sway_git_revision: $gr}' <<< "$merged_json") + + echo "$merged_json" > $metrics_json_file + done +fi diff --git a/forc-pkg/Cargo.toml b/forc-pkg/Cargo.toml index a10b765bad8..42d4909400a 100644 --- a/forc-pkg/Cargo.toml +++ b/forc-pkg/Cargo.toml @@ -27,6 +27,7 @@ sway-core = { version = "0.39.1", path = "../sway-core" } sway-error = { version = "0.39.1", path = "../sway-error" } sway-types = { version = "0.39.1", path = "../sway-types" } sway-utils = { version = "0.39.1", path = "../sway-utils" } +sysinfo = "0.29.0" toml = "0.5" tracing = "0.1" url = { version = "2.2", features = ["serde"] } diff --git a/forc-pkg/src/manifest.rs b/forc-pkg/src/manifest.rs index 9328e9952ca..ff83a1629e3 100644 --- a/forc-pkg/src/manifest.rs +++ b/forc-pkg/src/manifest.rs @@ -218,6 +218,8 @@ pub struct BuildProfile { #[serde(default)] pub time_phases: bool, #[serde(default)] + pub metrics_outfile: Option, + #[serde(default)] pub include_tests: bool, #[serde(default)] pub json_abi_with_callpaths: bool, @@ -663,6 +665,7 @@ impl BuildProfile { print_intermediate_asm: false, terse: false, time_phases: false, + metrics_outfile: None, include_tests: false, json_abi_with_callpaths: false, error_on_warnings: false, @@ -679,6 +682,7 @@ impl BuildProfile { print_intermediate_asm: false, terse: false, time_phases: false, + metrics_outfile: None, include_tests: false, json_abi_with_callpaths: false, error_on_warnings: false, diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 33ba8d9d0ad..2599799cf48 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -49,7 +49,8 @@ use sway_core::{ }; use sway_error::{error::CompileError, warning::CompileWarning}; use sway_types::{Ident, Span, Spanned}; -use sway_utils::constants; +use sway_utils::{constants, time_expr, PerformanceData, PerformanceMetric}; +use sysinfo::{System, SystemExt}; use tracing::{info, warn}; type GraphIx = u32; @@ -180,6 +181,7 @@ pub struct CompiledPackage { pub bytecode: BuiltPackageBytecode, pub namespace: namespace::Root, pub warnings: Vec, + pub metrics: PerformanceData, } /// Compiled contract dependency parts relevant to calculating a contract's ID. @@ -297,6 +299,8 @@ pub struct BuildOpts { pub release: bool, /// Output the time elapsed over each part of the compilation process. pub time_phases: bool, + /// If set, outputs compilation metrics info in JSON format. + pub metrics_outfile: Option, /// Warnings must be treated as compiler errors. pub error_on_warnings: bool, /// Include all test functions within the build. @@ -1545,7 +1549,9 @@ pub fn sway_build_config( .print_finalized_asm(build_profile.print_finalized_asm) .print_intermediate_asm(build_profile.print_intermediate_asm) .print_ir(build_profile.print_ir) - .include_tests(build_profile.include_tests); + .include_tests(build_profile.include_tests) + .time_phases(build_profile.time_phases) + .metrics(build_profile.metrics_outfile.clone()); Ok(build_config) } @@ -1708,6 +1714,7 @@ pub fn compile_ast( engines: Engines<'_>, namespace: namespace::Module, package_name: &str, + metrics: &mut PerformanceData, ) -> Result> { let source = pkg.manifest_file.entry_string()?; let sway_build_config = sway_build_config( @@ -1722,6 +1729,7 @@ pub fn compile_ast( namespace, Some(&sway_build_config), package_name, + metrics, ); Ok(ast_res) } @@ -1751,29 +1759,11 @@ pub fn compile( namespace: namespace::Module, source_map: &mut SourceMap, ) -> Result { - // Time the given expression and print the result if `build_config.time_phases` is true. - macro_rules! time_expr { - ($description:expr, $expression:expr) => {{ - if profile.time_phases { - let expr_start = std::time::Instant::now(); - let output = { $expression }; - println!( - " Time elapsed to {}: {:?}", - $description, - expr_start.elapsed() - ); - output - } else { - $expression - } - }}; - } + let mut metrics = PerformanceData::default(); let entry_path = pkg.manifest_file.entry_path(); - let sway_build_config = time_expr!( - "produce `sway_core::BuildConfig`", - sway_build_config(pkg.manifest_file.dir(), &entry_path, pkg.target, profile)? - ); + let sway_build_config = + sway_build_config(pkg.manifest_file.dir(), &entry_path, pkg.target, profile)?; let terse_mode = profile.terse; let fail = |warnings, errors| { print_on_failure(terse_mode, warnings, errors); @@ -1783,7 +1773,10 @@ pub fn compile( // First, compile to an AST. We'll update the namespace and check for JSON ABI output. let ast_res = time_expr!( "compile to ast", - compile_ast(pkg, profile, engines, namespace, &pkg.name)? + "compile_to_ast", + compile_ast(pkg, profile, engines, namespace, &pkg.name, &mut metrics)?, + Some(sway_build_config.clone()), + metrics ); let typed_program = match ast_res.value.as_ref() { None => return fail(&ast_res.warnings, &ast_res.errors), @@ -1805,7 +1798,10 @@ pub fn compile( let asm_res = time_expr!( "compile ast to asm", - sway_core::ast_to_asm(engines, &ast_res, &sway_build_config) + "compile_ast_to_asm", + sway_core::ast_to_asm(engines, &ast_res, &sway_build_config), + Some(sway_build_config.clone()), + metrics ); let mut program_abi = match pkg.target { @@ -1813,6 +1809,7 @@ pub fn compile( let mut types = vec![]; ProgramABI::Fuel(time_expr!( "generate JSON ABI program", + "generate_json_abi", fuel_json_abi::generate_json_abi_program( &mut JsonAbiContext { program: typed_program, @@ -1821,7 +1818,9 @@ pub fn compile( engines.te(), engines.de(), &mut types - ) + ), + Some(sway_build_config.clone()), + metrics )) } BuildTarget::EVM => { @@ -1837,7 +1836,10 @@ pub fn compile( let abi = time_expr!( "generate JSON ABI program", - evm_json_abi::generate_json_abi_program(typed_program, &engines) + "generate_json_abi", + evm_json_abi::generate_json_abi_program(typed_program, &engines), + Some(sway_build_config.clone()), + metrics ); ops.extend(abi.into_iter()); @@ -1860,7 +1862,10 @@ pub fn compile( .collect::>()?; let bc_res = time_expr!( "compile asm to bytecode", - sway_core::asm_to_bytecode(asm_res, source_map) + "compile_asm_to_bytecode", + sway_core::asm_to_bytecode(asm_res, source_map), + Some(sway_build_config), + metrics ); let errored = @@ -1888,6 +1893,7 @@ pub fn compile( } } + metrics.bytecode_size = compiled.bytecode.len(); let bytecode = BuiltPackageBytecode { bytes: compiled.bytecode, entries, @@ -1900,6 +1906,7 @@ pub fn compile( bytecode, namespace, warnings: bc_res.warnings, + metrics, }; Ok(compiled_package) } @@ -2004,6 +2011,7 @@ fn build_profile_from_opts( build_profile, release, time_phases, + metrics_outfile, tests, error_on_warnings, .. @@ -2053,6 +2061,9 @@ fn build_profile_from_opts( profile.print_intermediate_asm |= print.intermediate_asm; profile.terse |= pkg.terse; profile.time_phases |= time_phases; + if profile.metrics_outfile.is_none() { + profile.metrics_outfile = metrics_outfile.clone(); + } profile.include_tests |= tests; profile.json_abi_with_callpaths |= pkg.json_abi_with_callpaths; profile.error_on_warnings |= error_on_warnings; @@ -2293,6 +2304,7 @@ pub fn build( Ok(o) => o, Err(errs) => return fail(&[], &errs), }; + let compiled_without_tests = compile( &descriptor, &profile, @@ -2301,6 +2313,13 @@ pub fn build( &mut source_map, )?; + if let Some(outfile) = profile.metrics_outfile { + let path = Path::new(&outfile); + let metrics_json = serde_json::to_string(&compiled_without_tests.metrics) + .expect("JSON serialization failed"); + fs::write(path, metrics_json)?; + } + // If this contract is built because: // 1) it is a contract dependency, or // 2) tests are enabled, @@ -2357,6 +2376,13 @@ pub fn build( &mut source_map, )?; + if let Some(outfile) = profile.metrics_outfile { + let path = Path::new(&outfile); + let metrics_json = + serde_json::to_string(&compiled.metrics).expect("JSON serialization failed"); + fs::write(path, metrics_json)?; + } + if let TreeType::Library = compiled.tree_type { let mut namespace = namespace::Module::from(compiled.namespace); namespace.name = Some(Ident::new_no_span(pkg.name.clone())); diff --git a/forc-plugins/forc-client/src/op/deploy.rs b/forc-plugins/forc-client/src/op/deploy.rs index 0037820044d..e167f453973 100644 --- a/forc-plugins/forc-client/src/op/deploy.rs +++ b/forc-plugins/forc-client/src/op/deploy.rs @@ -243,6 +243,7 @@ fn build_opts_from_cmd(cmd: &cmd::Deploy) -> pkg::BuildOpts { ir: cmd.print.ir, }, time_phases: cmd.print.time_phases, + metrics_outfile: cmd.print.metrics_outfile.clone(), minify: pkg::MinifyOpts { json_abi: cmd.minify.json_abi, json_storage_slots: cmd.minify.json_storage_slots, diff --git a/forc-plugins/forc-client/src/op/run.rs b/forc-plugins/forc-client/src/op/run.rs index fd7e828d372..06a7b24c5d0 100644 --- a/forc-plugins/forc-client/src/op/run.rs +++ b/forc-plugins/forc-client/src/op/run.rs @@ -172,6 +172,7 @@ fn build_opts_from_cmd(cmd: &cmd::Run) -> pkg::BuildOpts { release: cmd.build_profile.release, error_on_warnings: cmd.build_profile.error_on_warnings, time_phases: cmd.print.time_phases, + metrics_outfile: cmd.print.metrics_outfile.clone(), binary_outfile: cmd.build_output.bin_file.clone(), debug_outfile: cmd.build_output.debug_file.clone(), tests: false, diff --git a/forc-test/src/lib.rs b/forc-test/src/lib.rs index b2a7a8934de..843a671a830 100644 --- a/forc-test/src/lib.rs +++ b/forc-test/src/lib.rs @@ -160,6 +160,8 @@ pub struct Opts { pub error_on_warnings: bool, /// Output the time elapsed over each part of the compilation process. pub time_phases: bool, + /// Output compilation metrics into file. + pub metrics_outfile: Option, } /// The set of options provided for controlling logs printed for each test. @@ -496,6 +498,7 @@ impl Opts { release: self.release, error_on_warnings: self.error_on_warnings, time_phases: self.time_phases, + metrics_outfile: self.metrics_outfile, tests: true, member_filter: Default::default(), } diff --git a/forc/src/cli/commands/test.rs b/forc/src/cli/commands/test.rs index 2dd8d02a693..f362452d86a 100644 --- a/forc/src/cli/commands/test.rs +++ b/forc/src/cli/commands/test.rs @@ -202,6 +202,7 @@ fn opts_from_cmd(cmd: Command) -> forc_test::Opts { ir: cmd.build.print.ir, }, time_phases: cmd.build.print.time_phases, + metrics_outfile: cmd.build.print.metrics_outfile, minify: pkg::MinifyOpts { json_abi: cmd.build.minify.json_abi, json_storage_slots: cmd.build.minify.json_storage_slots, diff --git a/forc/src/cli/shared.rs b/forc/src/cli/shared.rs index ab8acd2b064..2e0f595a382 100644 --- a/forc/src/cli/shared.rs +++ b/forc/src/cli/shared.rs @@ -85,6 +85,9 @@ pub struct Print { /// Output the time elapsed over each part of the compilation process. #[clap(long)] pub time_phases: bool, + /// Output compilation metrics into file. + #[clap(long)] + pub metrics_outfile: Option, } /// Package-related options. diff --git a/forc/src/ops/forc_build.rs b/forc/src/ops/forc_build.rs index bde935b2b7f..4480f3796c4 100644 --- a/forc/src/ops/forc_build.rs +++ b/forc/src/ops/forc_build.rs @@ -27,6 +27,7 @@ fn opts_from_cmd(cmd: BuildCommand) -> pkg::BuildOpts { ir: cmd.build.print.ir, }, time_phases: cmd.build.print.time_phases, + metrics_outfile: cmd.build.print.metrics_outfile, minify: pkg::MinifyOpts { json_abi: cmd.build.minify.json_abi, json_storage_slots: cmd.build.minify.json_storage_slots, diff --git a/forc/src/ops/forc_contract_id.rs b/forc/src/ops/forc_contract_id.rs index 3a7ffed0049..03f50c0ab37 100644 --- a/forc/src/ops/forc_contract_id.rs +++ b/forc/src/ops/forc_contract_id.rs @@ -63,6 +63,7 @@ fn build_opts_from_cmd(cmd: &ContractIdCommand) -> pkg::BuildOpts { ir: cmd.print.ir, }, time_phases: cmd.print.time_phases, + metrics_outfile: cmd.print.metrics_outfile.clone(), minify: pkg::MinifyOpts { json_abi: cmd.minify.json_abi, json_storage_slots: cmd.minify.json_storage_slots, diff --git a/forc/src/ops/forc_predicate_root.rs b/forc/src/ops/forc_predicate_root.rs index 166ade58970..de4abad9c91 100644 --- a/forc/src/ops/forc_predicate_root.rs +++ b/forc/src/ops/forc_predicate_root.rs @@ -31,6 +31,7 @@ fn build_opts_from_cmd(cmd: PredicateRootCommand) -> pkg::BuildOpts { ir: cmd.print.ir, }, time_phases: cmd.print.time_phases, + metrics_outfile: cmd.print.metrics_outfile, minify: pkg::MinifyOpts { json_abi: cmd.minify.json_abi, json_storage_slots: cmd.minify.json_storage_slots, diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index 204a8ff932e..acd740ef975 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -40,6 +40,7 @@ sway-ir = { version = "0.39.1", path = "../sway-ir" } sway-parse = { version = "0.39.1", path = "../sway-parse" } sway-types = { version = "0.39.1", path = "../sway-types" } sway-utils = { version = "0.39.1", path = "../sway-utils" } +sysinfo = "0.29.0" thiserror = "1.0" tracing = "0.1" uint = "0.9" diff --git a/sway-core/src/build_config.rs b/sway-core/src/build_config.rs index a387ad839a4..5dd49b661c5 100644 --- a/sway-core/src/build_config.rs +++ b/sway-core/src/build_config.rs @@ -46,6 +46,8 @@ pub struct BuildConfig { pub(crate) print_finalized_asm: bool, pub(crate) print_ir: bool, pub(crate) include_tests: bool, + pub time_phases: bool, + pub metrics_outfile: Option, } impl BuildConfig { @@ -88,6 +90,8 @@ impl BuildConfig { print_finalized_asm: false, print_ir: false, include_tests: false, + time_phases: false, + metrics_outfile: None, } } @@ -126,6 +130,20 @@ impl BuildConfig { } } + pub fn time_phases(self, a: bool) -> Self { + Self { + time_phases: a, + ..self + } + } + + pub fn metrics(self, a: Option) -> Self { + Self { + metrics_outfile: a, + ..self + } + } + /// Whether or not to include test functions in parsing, type-checking and codegen. /// /// This should be set to `true` by invocations like `forc test` or `forc check --tests`. diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index 6ce712f8575..67e70ab8c2d 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -40,6 +40,7 @@ use sway_ir::{ MODULEPRINTER_NAME, RETDEMOTION_NAME, }; use sway_types::constants::DOC_COMMENT_ATTRIBUTE_NAME; +use sway_utils::{time_expr, PerformanceData, PerformanceMetric}; use transform::{Attribute, AttributeArg, AttributeKind, AttributesMap}; use types::*; @@ -60,6 +61,7 @@ pub mod fuel_prelude { } pub use engine_threading::Engines; +use sysinfo::{System, SystemExt}; /// Given an input `Arc` and an optional [BuildConfig], parse the input into a [lexed::LexedProgram] and [parsed::ParseProgram]. /// @@ -449,13 +451,21 @@ pub fn compile_to_ast( initial_namespace: namespace::Module, build_config: Option<&BuildConfig>, package_name: &str, + metrics: &mut PerformanceData, ) -> CompileResult { // Parse the program to a concrete syntax tree (CST). let CompileResult { value: parse_program_opt, mut warnings, mut errors, - } = parse(input, engines, build_config); + } = time_expr!( + "parse the program to a concrete syntax tree (CST)", + "parse_cst", + parse(input, engines, build_config), + build_config, + metrics + ); + let (.., mut parse_program) = match parse_program_opt { Some(parse_program) => parse_program, None => return deduped_err(warnings, errors), @@ -470,13 +480,20 @@ pub fn compile_to_ast( } // Type check (+ other static analysis) the CST to a typed AST. - let typed_res = parsed_to_ast( - engines, - &parse_program, - initial_namespace, + let typed_res = time_expr!( + "parse the concrete syntax tree (CST) to a typed AST", + "parse_ast", + parsed_to_ast( + engines, + &parse_program, + initial_namespace, + build_config, + package_name, + ), build_config, - package_name, + metrics ); + errors.extend(typed_res.errors); warnings.extend(typed_res.warnings); let typed_program = match typed_res.value { @@ -500,6 +517,7 @@ pub fn compile_to_asm( initial_namespace: namespace::Module, build_config: BuildConfig, package_name: &str, + metrics: &mut PerformanceData, ) -> CompileResult { let ast_res = compile_to_ast( engines, @@ -507,6 +525,7 @@ pub fn compile_to_asm( initial_namespace, Some(&build_config), package_name, + metrics, ); ast_to_asm(engines, &ast_res, &build_config) } @@ -642,6 +661,7 @@ pub fn compile_to_bytecode( build_config: BuildConfig, source_map: &mut SourceMap, package_name: &str, + metrics: &mut PerformanceData, ) -> CompileResult { let asm_res = compile_to_asm( engines, @@ -649,6 +669,7 @@ pub fn compile_to_bytecode( initial_namespace, build_config, package_name, + metrics, ); asm_to_bytecode(asm_res, source_map) } diff --git a/sway-utils/Cargo.toml b/sway-utils/Cargo.toml index a9066dbccc4..4e9483f6f09 100644 --- a/sway-utils/Cargo.toml +++ b/sway-utils/Cargo.toml @@ -9,3 +9,4 @@ license.workspace = true repository.workspace = true [dependencies] +serde = { version = "1.0", features = ["derive"] } diff --git a/sway-utils/src/lib.rs b/sway-utils/src/lib.rs index 9d86788852e..2b4109ceaa9 100644 --- a/sway-utils/src/lib.rs +++ b/sway-utils/src/lib.rs @@ -1,6 +1,8 @@ pub mod constants; pub mod helpers; pub mod mapped_stack; +pub mod performance; pub use constants::*; pub use helpers::*; pub use mapped_stack::*; +pub use performance::*; diff --git a/sway-utils/src/performance.rs b/sway-utils/src/performance.rs new file mode 100644 index 00000000000..40a9ea70673 --- /dev/null +++ b/sway-utils/src/performance.rs @@ -0,0 +1,45 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct PerformanceMetric { + pub phase: String, + pub elapsed: f64, + pub memory_usage: u64, +} + +#[derive(Debug, Default, Serialize)] +pub struct PerformanceData { + pub bytecode_size: usize, + pub metrics: Vec, +} + +#[macro_export] +// Time the given expression and print/save the result. +macro_rules! time_expr { + ($description:expr, $key:expr, $expression:expr, $build_config:expr, $data:expr) => {{ + if let Some(cfg) = $build_config { + if cfg.time_phases || cfg.metrics_outfile.is_some() { + let expr_start = std::time::Instant::now(); + let output = { $expression }; + let elapsed = expr_start.elapsed(); + if cfg.time_phases { + println!(" Time elapsed to {}: {:?}", $description, elapsed); + } + if cfg.metrics_outfile.is_some() { + let mut sys = System::new(); + sys.refresh_system(); + $data.metrics.push(PerformanceMetric { + phase: $key.to_string(), + elapsed: elapsed.as_secs_f64(), + memory_usage: sys.used_memory(), + }); + } + output + } else { + $expression + } + } else { + $expression + } + }}; +} diff --git a/test/Cargo.toml b/test/Cargo.toml index ef44cd4a55d..06fcd445cda 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -29,6 +29,7 @@ revm = "2.3.1" serde_json = "1.0.73" sway-core = { path = "../sway-core" } sway-ir = { path = "../sway-ir" } +sway-utils = { path = "../sway-utils" } textwrap = "0.16.0" tokio = "1.12" toml = "0.5" diff --git a/test/src/ir_generation/mod.rs b/test/src/ir_generation/mod.rs index 805d3cb536f..a60619ede7f 100644 --- a/test/src/ir_generation/mod.rs +++ b/test/src/ir_generation/mod.rs @@ -14,6 +14,7 @@ use sway_ir::{ create_inline_in_module_pass, register_known_passes, PassGroup, PassManager, ARGDEMOTION_NAME, CONSTDEMOTION_NAME, DCE_NAME, MEMCPYOPT_NAME, MISCDEMOTION_NAME, RETDEMOTION_NAME, }; +use sway_utils::PerformanceData; pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { // Compile core library and reuse it when compiling tests. @@ -129,6 +130,7 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { // Include unit tests in the build. let bld_cfg = bld_cfg.include_tests(true); + let mut metrics = PerformanceData::default(); let sway_str = String::from_utf8_lossy(&sway_str); let typed_res = compile_to_ast( engines, @@ -136,6 +138,7 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { core_lib.clone(), Some(&bld_cfg), "test_lib", + &mut metrics, ); if !typed_res.errors.is_empty() { panic!(