@alejoamiras/aztec-benchmark 0.0.0-canary.gb8d1485

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ const core = require('@actions/core');
2
+ const exec = require('@actions/exec');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { runComparison } = require('./comparison.cjs');
6
+
7
+ /**
8
+ * Main function for the GitHub Action.
9
+ * This function is responsible for:
10
+ * - Reading action inputs (threshold, output path, base suffix, current suffix).
11
+ * - Setting up paths and suffixes for benchmark reports.
12
+ * - Executing the benchmark CLI to generate the 'latest' reports.
13
+ * - Running the comparison logic between 'base' and 'latest' reports.
14
+ * - Writing the comparison result to a markdown file.
15
+ * - Setting action outputs (markdown content and file path).
16
+ * It handles errors and sets the action to failed if any step fails.
17
+ */
18
+ async function run() {
19
+ try {
20
+ const threshold = parseFloat(core.getInput('threshold'));
21
+ const outputMarkdownPath = core.getInput('output_markdown_path');
22
+ const baseSuffix = core.getInput('base_suffix');
23
+ const currentSuffix = core.getInput('current_suffix');
24
+ const configPath = core.getInput('config_path');
25
+ const reportsDir = core.getInput('reports_dir');
26
+ const onlyReport = core.getInput('only_report') === 'true';
27
+ const circuitDetails = core.getInput('circuit_details') === 'true';
28
+
29
+ core.info('--- Inputs ---');
30
+ core.info(`Config Path: ${configPath}`);
31
+ core.info(`Threshold: ${threshold}%`);
32
+ core.info(`Output Markdown Path: ${outputMarkdownPath}`);
33
+ core.info(`Reports Directory: ${reportsDir}`);
34
+ core.info(`Base Suffix: ${baseSuffix}`);
35
+ core.info(`Current Suffix: ${currentSuffix}`);
36
+ core.info(`Only report: ${onlyReport}`);
37
+ core.info(`Circuit details: ${circuitDetails}`);
38
+
39
+ if (isNaN(threshold)) {
40
+ throw new Error('Invalid threshold value. Please provide a number.');
41
+ }
42
+
43
+ if (!onlyReport) {
44
+ core.startGroup('Generating latest benchmark reports (all contracts)');
45
+ const cliArgs = [];
46
+ cliArgs.push('--config', configPath);
47
+ cliArgs.push('--output-dir', reportsDir);
48
+ cliArgs.push('--suffix', currentSuffix);
49
+
50
+ core.info(`Executing: aztec-benchmark ${cliArgs.join(' ')}`);
51
+
52
+ const execOptions = {
53
+ cwd: process.cwd(),
54
+ };
55
+ // Scoped name — the unscoped `aztec-benchmark` registry name is not ours; invoking it
56
+ // via npx would be a dependency-confusion target on runners without a local install.
57
+ const exitCode = await exec.exec('npx', ['--yes', '@alejoamiras/aztec-benchmark', ...cliArgs], execOptions);
58
+ if (exitCode !== 0) {
59
+ throw new Error(`Benchmark CLI execution failed with exit code ${exitCode}`);
60
+ }
61
+ core.endGroup();
62
+ } else {
63
+ core.info('Skipping aztec-benchmark execution (per only_report input).');
64
+ }
65
+
66
+ core.startGroup('Comparing benchmark reports');
67
+ const comparisonInputs = {
68
+ reportsDir,
69
+ baseSuffix,
70
+ prSuffix: currentSuffix,
71
+ threshold,
72
+ circuitDetails,
73
+ };
74
+ const markdownResult = runComparison(comparisonInputs);
75
+ core.endGroup();
76
+
77
+ const resolvedOutputPath = path.resolve(outputMarkdownPath);
78
+ core.info(`Writing comparison report to: ${resolvedOutputPath}`);
79
+ fs.writeFileSync(resolvedOutputPath, markdownResult);
80
+
81
+ core.setOutput('comparison_markdown', markdownResult);
82
+ core.setOutput('markdown_file_path', resolvedOutputPath);
83
+
84
+ core.info('Benchmark generation and comparison action completed successfully.');
85
+ } catch (error) {
86
+ core.setFailed(`Action failed: ${error.message}`);
87
+ if (error.stack) {
88
+ core.debug(error.stack);
89
+ }
90
+ }
91
+ }
92
+
93
+ run();
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env sh
2
+
3
+ # Resolve the true path of the script, following symlinks
4
+ SOURCE="${0}"
5
+ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
6
+ DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
7
+ SOURCE="$(readlink "$SOURCE")"
8
+ case $SOURCE in
9
+ /*) ;;
10
+ *) SOURCE="$DIR/$SOURCE" ;;
11
+ esac
12
+ done
13
+ SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
14
+
15
+ # The actual CLI script is in dist/cli.js relative to the package root
16
+ # SCRIPT_DIR is currently aztec-benchmark/bin
17
+ PACKAGE_ROOT="$(dirname "$SCRIPT_DIR")"
18
+ CLI_SCRIPT="$PACKAGE_ROOT/dist/cli.js"
19
+
20
+ # Execute the CLI script with tsx, passing all arguments
21
+ exec tsx "$CLI_SCRIPT" "$@"
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import toml from '@iarna/toml';
5
+ import { Command } from 'commander';
6
+ import { Profiler } from './profiler.js';
7
+ const program = new Command();
8
+ program
9
+ .name('aztec-benchmark')
10
+ .description('Runs benchmarks defined in Nargo.toml and associated *.benchmark.ts files.')
11
+ .option('-c, --contracts <names...>', 'Specify contracts to benchmark by name (defined in Nargo.toml)')
12
+ .option('--config <path>', 'Path to the Nargo.toml file', './Nargo.toml')
13
+ .option('-o, --output-dir <path>', 'Directory to save benchmark reports', './benchmarks')
14
+ .option('-s, --suffix <suffix>', 'Optional suffix to append to the report filename (e.g., _pr)')
15
+ .option('--skip-proving', 'Skip proving transactions (only measure gate counts and gas)')
16
+ /**
17
+ * Main action for the CLI.
18
+ * Parses Nargo.toml, finds and runs specified benchmarks, and saves the reports.
19
+ * @param options - The command line options.
20
+ * @param options.contracts - Specific contracts to benchmark.
21
+ * @param options.config - Path to the Nargo.toml file.
22
+ * @param options.outputDir - Directory to save reports.
23
+ * @param options.suffix - Optional suffix for report filenames.
24
+ */
25
+ .action(async (options) => {
26
+ const nargoTomlPath = path.resolve(process.cwd(), options.config);
27
+ const outputDir = path.resolve(process.cwd(), options.outputDir);
28
+ const specifiedContractNames = options.contracts || [];
29
+ const suffix = options.suffix || '';
30
+ if (!fs.existsSync(nargoTomlPath)) {
31
+ console.error(`Error: Nargo.toml not found at ${nargoTomlPath}`);
32
+ process.exit(1);
33
+ }
34
+ if (!fs.existsSync(outputDir)) {
35
+ console.log(`Creating output directory: ${outputDir}`);
36
+ fs.mkdirSync(outputDir, { recursive: true });
37
+ }
38
+ let nargoConfig;
39
+ try {
40
+ const tomlContent = fs.readFileSync(nargoTomlPath, 'utf-8');
41
+ nargoConfig = toml.parse(tomlContent);
42
+ }
43
+ catch (error) {
44
+ console.error(`Error parsing Nargo.toml at ${nargoTomlPath}:`, error.message);
45
+ process.exit(1);
46
+ }
47
+ const availableBenchmarks = nargoConfig.benchmark || {};
48
+ const availableContractNames = Object.keys(availableBenchmarks);
49
+ if (availableContractNames.length === 0) {
50
+ console.error('No contracts found in the [benchmark] section of Nargo.toml.');
51
+ process.exit(1);
52
+ }
53
+ // Filter contracts to run based on CLI option
54
+ const contractsToRunNames = specifiedContractNames.length > 0
55
+ ? availableContractNames.filter((name) => specifiedContractNames.includes(name))
56
+ : availableContractNames;
57
+ if (contractsToRunNames.length === 0) {
58
+ if (specifiedContractNames.length > 0) {
59
+ console.error(`Error: None of the specified contracts found in the [benchmark] section: ${specifiedContractNames.join(', ')}`);
60
+ }
61
+ else {
62
+ console.error('Error: No benchmarks specified via --contracts flag or found in the [benchmark] section of Nargo.toml.');
63
+ }
64
+ program.outputHelp();
65
+ process.exit(1);
66
+ }
67
+ console.log(`Found ${contractsToRunNames.length} benchmark(s) to run: ${contractsToRunNames.join(', ')}`);
68
+ // Iterate over the filtered contract names
69
+ for (const contractName of contractsToRunNames) {
70
+ const benchmarkFileName = availableBenchmarks[contractName];
71
+ const benchmarkFilePath = path.resolve(path.dirname(nargoTomlPath), benchmarkFileName);
72
+ const outputFilename = `${contractName}${suffix}.benchmark.json`;
73
+ const outputJsonPath = path.join(outputDir, outputFilename);
74
+ console.log(`--- Running benchmark for ${contractName}${suffix ? ` (suffix: ${suffix})` : ''} ---`);
75
+ console.log(` -> Benchmark file: ${benchmarkFilePath}`);
76
+ console.log(` -> Output report: ${outputJsonPath}`);
77
+ if (!fs.existsSync(benchmarkFilePath)) {
78
+ console.error(`Error: Benchmark file not found: ${benchmarkFilePath}`);
79
+ process.exit(1);
80
+ }
81
+ try {
82
+ const module = await import(benchmarkFilePath);
83
+ const BenchmarkClass = module.default;
84
+ if (!BenchmarkClass ||
85
+ !(typeof BenchmarkClass === 'function') ||
86
+ !(typeof BenchmarkClass.prototype.getMethods === 'function')) {
87
+ console.error(`Error: ${benchmarkFilePath} does not export a default class with a getMethods method.`);
88
+ process.exit(1);
89
+ }
90
+ const benchmarkInstance = new BenchmarkClass();
91
+ let runContext = {};
92
+ if (typeof benchmarkInstance.setup === 'function') {
93
+ console.log(`Running setup for ${contractName}...`);
94
+ runContext = await benchmarkInstance.setup();
95
+ console.log(`Setup complete for ${contractName}.`);
96
+ }
97
+ if (!options.skipProving && !runContext.wallet) {
98
+ console.error(`Error: setup() must return a context with a 'wallet' property when proving is enabled.`);
99
+ process.exit(1);
100
+ }
101
+ const profiler = new Profiler(runContext.wallet, {
102
+ skipProving: options.skipProving,
103
+ feePaymentMethod: runContext.feePaymentMethod,
104
+ });
105
+ console.log(`Getting methods to benchmark for ${contractName}...`);
106
+ const interactionsToBenchmark = benchmarkInstance.getMethods(runContext);
107
+ if (!Array.isArray(interactionsToBenchmark) || interactionsToBenchmark.length === 0) {
108
+ console.warn(`No benchmark methods returned by getMethods for ${contractName}. Saving empty report.`);
109
+ await profiler.saveResults([], outputJsonPath);
110
+ }
111
+ else {
112
+ console.log(`Profiling ${interactionsToBenchmark.length} methods for ${contractName}...`);
113
+ const results = await profiler.profile(interactionsToBenchmark);
114
+ await profiler.saveResults(results, outputJsonPath);
115
+ }
116
+ if (typeof benchmarkInstance.teardown === 'function') {
117
+ console.log(`Running teardown for ${contractName}...`);
118
+ await benchmarkInstance.teardown(runContext);
119
+ console.log(`Teardown complete for ${contractName}.`);
120
+ }
121
+ // Workaround: PXE.stop() doesn't release all resources
122
+ // @see https://github.com/AztecProtocol/aztec-packages/issues/20446
123
+ if (runContext.wallet) {
124
+ const pxe = runContext.wallet.pxe;
125
+ if (pxe?.blockStateSynchronizer) {
126
+ await pxe.blockStateSynchronizer.blockStream?.stop();
127
+ await pxe.blockStateSynchronizer.store?.close();
128
+ }
129
+ await runContext.wallet.stop();
130
+ // Close leftover HTTP keep-alive sockets
131
+ for (const h of process._getActiveHandles()) {
132
+ if (h?.constructor?.name === 'Socket' && !h.destroyed)
133
+ h.destroy();
134
+ }
135
+ }
136
+ console.log(`--- Benchmark finished for ${contractName} ---`);
137
+ }
138
+ catch (error) {
139
+ console.error(`Failed to run benchmark for ${contractName} from ${benchmarkFilePath}:`, error);
140
+ process.exit(1);
141
+ }
142
+ }
143
+ console.log('All specified benchmarks completed successfully.');
144
+ });
145
+ program.parse(process.argv);
@@ -0,0 +1,35 @@
1
+ import type { AztecAddress } from '@aztec/aztec.js/addresses';
2
+ import type { ContractFunctionInteraction, GasSettingsOption, ProfileInteractionOptions, RequestInteractionOptions, SendInteractionOptions, SimulateInteractionOptions } from '@aztec/aztec.js/contracts';
3
+ import type { FeePaymentMethod } from '@aztec/aztec.js/fee';
4
+ import type { NamedBenchmarkedInteraction } from './types.js';
5
+ /** Partial gas settings that can be injected into every profiler call. */
6
+ export type FeeGasSettings = NonNullable<GasSettingsOption['gasSettings']>;
7
+ /** Fee configuration injected by FeeWrappedInteraction and namedMethod. */
8
+ export interface FeeOptions {
9
+ paymentMethod?: FeePaymentMethod;
10
+ gasSettings?: FeeGasSettings;
11
+ }
12
+ /**
13
+ * Wraps a ContractFunctionInteraction so the benchmark profiler always uses a
14
+ * per-interaction FeePaymentMethod and gas settings.
15
+ *
16
+ * The profiler only supports a single global feePaymentMethod, but benchmarks
17
+ * often require different methods per interaction. This class intercepts every
18
+ * profiler call and injects the configured method and settings.
19
+ */
20
+ export declare class FeeWrappedInteraction {
21
+ private readonly inner;
22
+ private readonly paymentMethod?;
23
+ private readonly gasSettings?;
24
+ constructor(inner: ContractFunctionInteraction, paymentMethod?: FeePaymentMethod | undefined, gasSettings?: FeeGasSettings | undefined);
25
+ request(options?: RequestInteractionOptions): ReturnType<ContractFunctionInteraction['request']>;
26
+ simulate(options?: SimulateInteractionOptions): Promise<import("@aztec/aztec.js/contracts").SimulationResult>;
27
+ profile(options?: ProfileInteractionOptions): ReturnType<ContractFunctionInteraction['profile']>;
28
+ send(options?: SendInteractionOptions): ReturnType<ContractFunctionInteraction['send']>;
29
+ private withFee;
30
+ }
31
+ /**
32
+ * Creates a NamedBenchmarkedInteraction that wraps `inner` with an optional
33
+ * FeePaymentMethod and gas settings.
34
+ */
35
+ export declare function namedMethod(name: string, caller: AztecAddress, inner: ContractFunctionInteraction, fee?: FeeOptions): NamedBenchmarkedInteraction;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Wraps a ContractFunctionInteraction so the benchmark profiler always uses a
3
+ * per-interaction FeePaymentMethod and gas settings.
4
+ *
5
+ * The profiler only supports a single global feePaymentMethod, but benchmarks
6
+ * often require different methods per interaction. This class intercepts every
7
+ * profiler call and injects the configured method and settings.
8
+ */
9
+ export class FeeWrappedInteraction {
10
+ constructor(inner, paymentMethod, gasSettings) {
11
+ this.inner = inner;
12
+ this.paymentMethod = paymentMethod;
13
+ this.gasSettings = gasSettings;
14
+ }
15
+ async request(options = {}) {
16
+ // `RequestInteractionOptions.fee` is `FeePaymentMethodOption` — it only carries
17
+ // `paymentMethod`, not `gasSettings`. Gas settings are injected by `withFee` in
18
+ // simulate/profile/send where the fee type supports them.
19
+ const paymentMethod = options.fee?.paymentMethod ?? this.paymentMethod;
20
+ return paymentMethod
21
+ ? this.inner.request({
22
+ ...options,
23
+ fee: { ...(options.fee ?? {}), paymentMethod },
24
+ })
25
+ : this.inner.request(options);
26
+ }
27
+ async simulate(options) {
28
+ return this.inner.simulate(this.withFee(options));
29
+ }
30
+ async profile(options) {
31
+ return this.inner.profile(this.withFee(options));
32
+ }
33
+ async send(options) {
34
+ return this.inner.send(this.withFee(options));
35
+ }
36
+ withFee(options) {
37
+ const paymentMethod = options?.fee?.paymentMethod ?? this.paymentMethod;
38
+ if (!paymentMethod)
39
+ return (options ?? {});
40
+ return {
41
+ ...(options ?? {}),
42
+ fee: {
43
+ ...(options?.fee ?? {}),
44
+ paymentMethod,
45
+ ...(this.gasSettings && { gasSettings: this.gasSettings }),
46
+ },
47
+ };
48
+ }
49
+ }
50
+ /**
51
+ * Creates a NamedBenchmarkedInteraction that wraps `inner` with an optional
52
+ * FeePaymentMethod and gas settings.
53
+ */
54
+ export function namedMethod(name, caller, inner, fee) {
55
+ return {
56
+ name,
57
+ interaction: {
58
+ caller,
59
+ action: new FeeWrappedInteraction(inner, fee?.paymentMethod, fee?.gasSettings),
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,6 @@
1
+ export type { FeeGasSettings, FeeOptions } from './feeWrappedInteraction.js';
2
+ export { FeeWrappedInteraction, namedMethod } from './feeWrappedInteraction.js';
3
+ export { Profiler } from './profiler.js';
4
+ export { getSystemInfo } from './systemInfo.js';
5
+ export type { GateCount, NamedBenchmarkedInteraction, ProfileReport, ProfileResult, SystemInfo } from './types.js';
6
+ export { BenchmarkBase as Benchmark, BenchmarkContext } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Export the base class and types for users
2
+ // Export fee payment helpers
3
+ export { FeeWrappedInteraction, namedMethod } from './feeWrappedInteraction.js';
4
+ // Also export the Profiler for potential advanced use (or internal use by CLI)
5
+ export { Profiler } from './profiler.js';
6
+ // Export system info utilities
7
+ export { getSystemInfo } from './systemInfo.js';
8
+ export { BenchmarkBase as Benchmark } from './types.js'; // Alias BenchmarkBase to Benchmark for user convenience
@@ -0,0 +1,32 @@
1
+ import type { ContractFunctionInteractionCallIntent } from '@aztec/aztec.js/authorization';
2
+ import type { FeePaymentMethod } from '@aztec/aztec.js/fee';
3
+ import type { EmbeddedWallet } from '@aztec/wallets/embedded';
4
+ import type { NamedBenchmarkedInteraction, ProfileResult } from './types.js';
5
+ interface ProfilerOptions {
6
+ skipProving?: boolean;
7
+ /** Fee payment method to use when sending transactions. */
8
+ feePaymentMethod?: FeePaymentMethod;
9
+ }
10
+ /**
11
+ * Profiles Aztec contract functions to measure gate counts, gas usage and proving time.
12
+ */
13
+ export declare class Profiler {
14
+ #private;
15
+ /** @param _wallet - Unused, kept for backward compatibility with existing callers. */
16
+ constructor(_wallet?: EmbeddedWallet, options?: ProfilerOptions);
17
+ /**
18
+ * Profiles a list of contract function interactions.
19
+ * Items can be plain interactions or objects with an interaction and a custom name.
20
+ * @param fsToProfile - An array of items to profile.
21
+ * @returns A promise that resolves to an array of profile results.
22
+ */
23
+ profile(fsToProfile: Array<ContractFunctionInteractionCallIntent | NamedBenchmarkedInteraction>): Promise<ProfileResult[]>;
24
+ /**
25
+ * Saves the profiling results to a JSON file.
26
+ * If no results are provided, an empty report is saved.
27
+ * @param results - An array of profile results to save.
28
+ * @param filename - The name of the file to save the results to.
29
+ */
30
+ saveResults(results: ProfileResult[], filename: string): Promise<void>;
31
+ }
32
+ export {};
@@ -0,0 +1,197 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _Profiler_instances, _Profiler_skipProving, _Profiler_feePaymentMethod, _Profiler_profileOne;
13
+ import fs from 'node:fs';
14
+ import { getSystemInfo } from './systemInfo.js';
15
+ /**
16
+ * Sums all numbers in an array.
17
+ * @param arr - The array of numbers to sum.
18
+ * @returns The sum of the numbers.
19
+ */
20
+ function sumArray(arr) {
21
+ return arr.reduce((a, b) => a + b, 0);
22
+ }
23
+ /**
24
+ * Sums DA and L2 gas components.
25
+ * @param gas - The gas object.
26
+ * @returns The total gas (DA + L2).
27
+ */
28
+ function sumGas(gas) {
29
+ return (gas?.daGas ?? 0) + (gas?.l2Gas ?? 0);
30
+ }
31
+ /**
32
+ * Profiles Aztec contract functions to measure gate counts, gas usage and proving time.
33
+ */
34
+ export class Profiler {
35
+ /** @param _wallet - Unused, kept for backward compatibility with existing callers. */
36
+ constructor(_wallet, options) {
37
+ _Profiler_instances.add(this);
38
+ _Profiler_skipProving.set(this, void 0);
39
+ _Profiler_feePaymentMethod.set(this, void 0);
40
+ __classPrivateFieldSet(this, _Profiler_skipProving, options?.skipProving ?? false, "f");
41
+ __classPrivateFieldSet(this, _Profiler_feePaymentMethod, options?.feePaymentMethod, "f");
42
+ }
43
+ /**
44
+ * Profiles a list of contract function interactions.
45
+ * Items can be plain interactions or objects with an interaction and a custom name.
46
+ * @param fsToProfile - An array of items to profile.
47
+ * @returns A promise that resolves to an array of profile results.
48
+ */
49
+ async profile(fsToProfile) {
50
+ const results = [];
51
+ for (const item of fsToProfile) {
52
+ if ('interaction' in item && 'name' in item) {
53
+ // This is a NamedBenchmarkedInteraction object
54
+ results.push(await __classPrivateFieldGet(this, _Profiler_instances, "m", _Profiler_profileOne).call(this, item.interaction, item.name, item.additionalScopes));
55
+ }
56
+ else {
57
+ // This is a plain ContractFunctionInteractionCallIntent
58
+ results.push(await __classPrivateFieldGet(this, _Profiler_instances, "m", _Profiler_profileOne).call(this, item)); // Pass undefined for customName
59
+ }
60
+ }
61
+ return results;
62
+ }
63
+ /**
64
+ * Saves the profiling results to a JSON file.
65
+ * If no results are provided, an empty report is saved.
66
+ * @param results - An array of profile results to save.
67
+ * @param filename - The name of the file to save the results to.
68
+ */
69
+ async saveResults(results, filename) {
70
+ const systemInfo = getSystemInfo();
71
+ if (!results.length) {
72
+ console.log(`No results to save for ${filename}. Saving empty report.`);
73
+ fs.writeFileSync(filename, JSON.stringify({ summary: {}, results: [], gasSummary: {}, provingTimeSummary: {}, systemInfo }, null, 2));
74
+ return;
75
+ }
76
+ const summary = results.reduce((acc, result) => ({
77
+ ...acc,
78
+ [result.name]: result.totalGateCount,
79
+ }), {});
80
+ const gasSummary = results.reduce((acc, result) => ({
81
+ ...acc,
82
+ [result.name]: result.gas ? sumGas(result.gas.gasLimits) + sumGas(result.gas.teardownGasLimits) : 0,
83
+ }), {});
84
+ const provingTimeSummary = results.reduce((acc, result) => ({
85
+ ...acc,
86
+ [result.name]: result.provingTime ?? 0,
87
+ }), {});
88
+ const report = {
89
+ summary,
90
+ results: results,
91
+ gasSummary,
92
+ provingTimeSummary,
93
+ systemInfo,
94
+ };
95
+ console.log(`Saving results for ${results.length} methods in ${filename}`);
96
+ try {
97
+ fs.writeFileSync(filename, JSON.stringify(report, null, 2));
98
+ }
99
+ catch (error) {
100
+ console.error(`Error writing results to ${filename}:`, error.message);
101
+ throw error;
102
+ }
103
+ }
104
+ }
105
+ _Profiler_skipProving = new WeakMap(), _Profiler_feePaymentMethod = new WeakMap(), _Profiler_instances = new WeakSet(), _Profiler_profileOne =
106
+ /**
107
+ * Profiles a single contract function interaction.
108
+ * @param f - The contract function interaction to profile.
109
+ * @param customName - Optional user-defined name for this benchmark entry. If not provided, name is derived.
110
+ * @returns A promise that resolves to a profile result for the function.
111
+ * Returns a result with FAILED in the name and zero counts/gas if profiling errors.
112
+ * @private
113
+ */
114
+ async function _Profiler_profileOne(f, customName, additionalScopes) {
115
+ let name;
116
+ if (customName) {
117
+ name = customName;
118
+ }
119
+ else {
120
+ // Name discovery logic (reinstated)
121
+ try {
122
+ const executionPayload = await f.action.request(); // Note: f.request() might be an issue if f is already a PxeSimoneResponse - check aztec.js docs
123
+ if (executionPayload.calls && executionPayload.calls.length > 0) {
124
+ const firstCall = executionPayload.calls[0];
125
+ // Attempt to get a meaningful name
126
+ name = firstCall?.name ?? firstCall?.selector?.toString() ?? 'unknown_function';
127
+ }
128
+ else {
129
+ name = 'unknown_function_no_calls';
130
+ console.warn('No calls found in execution payload for name discovery.');
131
+ }
132
+ }
133
+ catch (e) {
134
+ // Fallback if request() fails or doesn't yield a name
135
+ const potentialMethodName = f.methodName; // methodName is not a standard prop, but might exist on some wrapped objects
136
+ if (potentialMethodName) {
137
+ name = potentialMethodName;
138
+ console.warn(`Could not simulate request for name discovery (${e.message}), using interaction.methodName as fallback: ${name}`);
139
+ }
140
+ else {
141
+ name = 'unknown_function_request_failed';
142
+ console.warn(`Could not determine function name from request simulation: ${e.message}`);
143
+ }
144
+ }
145
+ }
146
+ console.log(`Profiling ${name}...`);
147
+ try {
148
+ const origin = f.caller;
149
+ const feeOpts = __classPrivateFieldGet(this, _Profiler_feePaymentMethod, "f") ? { paymentMethod: __classPrivateFieldGet(this, _Profiler_feePaymentMethod, "f") } : undefined;
150
+ // `includeMetadata` returns `gasUsed`: the raw gas the simulation consumed, with no padding.
151
+ const simResult = await f.action.simulate({
152
+ from: origin,
153
+ additionalScopes,
154
+ includeMetadata: true,
155
+ fee: feeOpts,
156
+ });
157
+ const gas = simResult.gasUsed
158
+ ? {
159
+ gasLimits: simResult.gasUsed.totalGas,
160
+ teardownGasLimits: simResult.gasUsed.teardownGas,
161
+ }
162
+ : undefined;
163
+ // Profile the tx to get gate counts and optionally proving time.
164
+ const profileResults = await f.action.profile({
165
+ profileMode: 'full',
166
+ from: origin,
167
+ additionalScopes,
168
+ skipProofGeneration: __classPrivateFieldGet(this, _Profiler_skipProving, "f"),
169
+ fee: feeOpts,
170
+ });
171
+ const provingTime = !__classPrivateFieldGet(this, _Profiler_skipProving, "f") ? profileResults.stats?.timings?.proving : undefined;
172
+ // Send the tx (this proves again internally).
173
+ await f.action.send({ from: origin, additionalScopes, fee: feeOpts });
174
+ const result = {
175
+ name,
176
+ totalGateCount: sumArray(profileResults.executionSteps
177
+ .map((step) => step.gateCount)
178
+ .filter((count) => count !== undefined)),
179
+ gateCounts: profileResults.executionSteps.map((step) => ({
180
+ circuitName: step.functionName,
181
+ gateCount: step.gateCount || 0,
182
+ witgenMs: step.timings?.witgen,
183
+ })),
184
+ gas,
185
+ provingTime,
186
+ };
187
+ const daGas = gas?.gasLimits?.daGas ?? 'N/A';
188
+ const l2Gas = gas?.gasLimits?.l2Gas ?? 'N/A';
189
+ const provingDisplay = provingTime !== undefined ? `${provingTime}ms` : 'skipped';
190
+ console.log(` -> ${name}: ${result.totalGateCount} gates, Gas (DA: ${daGas}, L2: ${l2Gas}), Proving: ${provingDisplay}`);
191
+ return result;
192
+ }
193
+ catch (error) {
194
+ console.error(`Error profiling ${name}:`, error.message);
195
+ throw error;
196
+ }
197
+ };
@@ -0,0 +1,15 @@
1
+ export interface SystemInfo {
2
+ /** CPU model name */
3
+ cpuModel: string;
4
+ /** Number of available CPU cores */
5
+ cpuCores: number;
6
+ /** Total system memory in GiB */
7
+ totalMemoryGiB: number;
8
+ /** CPU architecture (e.g., 'x64', 'arm64') */
9
+ arch: string;
10
+ }
11
+ /**
12
+ * Collects system information for benchmark reports.
13
+ * Returns N/A values if information cannot be determined.
14
+ */
15
+ export declare function getSystemInfo(): SystemInfo;