Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions yarn-project/aztec/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ source $(git rev-parse --show-toplevel)/ci3/source_bootstrap
repo_root=$(git rev-parse --show-toplevel)
export NARGO=${NARGO:-$repo_root/noir/noir-repo/target/release/nargo}
export BB=${BB:-$repo_root/barretenberg/cpp/build/bin/bb}
export PROFILER_PATH=${PROFILER_PATH:-$repo_root/noir/noir-repo/target/release/noir-profiler}

hash=$(../bootstrap.sh hash)

function test_cmds {
for test in src/cli/**/*.test.ts; do
echo "$hash:ISOLATE=1:NAME=aztec/$test NARGO=$NARGO BB=$BB yarn-project/scripts/run_test.sh aztec/$test"
done
# All CLI tests share test/mixed-workspace/target so they must run sequentially
# in a single jest invocation (--runInBand is set by run_test.sh).
echo "$hash:ISOLATE=1:NAME=aztec/cli NARGO=$NARGO BB=$BB PROFILER_PATH=$PROFILER_PATH yarn-project/scripts/run_test.sh aztec/src/cli"
}

case "$cmd" in
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/aztec/scripts/aztec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ case $cmd in

aztec start "$@"
;;
new|init|flamegraph)
new|init)
$script_dir/${cmd}.sh "$@"
;;
flamegraph)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are still keeping the old command, but we are adding a deprecation warning. Internally, it's just using the new command

echo "Warning: 'aztec flamegraph' is deprecated. Use 'aztec profile flamegraph' instead." >&2
aztec profile flamegraph "$@"
;;
*)
aztec $cmd "$@"
;;
Expand Down
47 changes: 0 additions & 47 deletions yarn-project/aztec/scripts/extract_function.js

This file was deleted.

59 changes: 0 additions & 59 deletions yarn-project/aztec/scripts/flamegraph.sh

This file was deleted.

18 changes: 2 additions & 16 deletions yarn-project/aztec/src/cli/cmds/compile.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import type { LogFn } from '@aztec/foundation/log';

import { execFileSync, spawn } from 'child_process';
import { execFileSync } from 'child_process';
import type { Command } from 'commander';
import { readFile, writeFile } from 'fs/promises';

import { readArtifactFiles } from './utils/artifacts.js';

/** Spawns a command with inherited stdio and rejects on non-zero exit. */
function run(cmd: string, args: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(cmd, args, { stdio: 'inherit' });
child.on('error', reject);
child.on('close', code => {
if (code !== 0) {
reject(new Error(`${cmd} exited with code ${code}`));
} else {
resolve();
}
});
});
}
import { run } from './utils/spawn.js';

/** Returns paths to contract artifacts in the target directory. */
async function collectContractArtifacts(): Promise<string[]> {
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/aztec/src/cli/cmds/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { LogFn } from '@aztec/foundation/log';

import type { Command } from 'commander';

import { profileFlamegraph } from './profile_flamegraph.js';
import { profileGates } from './profile_gates.js';

export function injectProfileCommand(program: Command, log: LogFn): Command {
Expand All @@ -13,5 +14,12 @@ export function injectProfileCommand(program: Command, log: LogFn): Command {
.description('Display gate counts for all compiled Aztec artifacts in a target directory.')
.action((targetDir: string) => profileGates(targetDir, log));

profile
.command('flamegraph')
.argument('<contract-artifact>', 'Path to the compiled contract artifact JSON')
.argument('<function>', 'Name of the contract function to profile')
.description('Generate a gate count flamegraph SVG for a contract function.')
.action((artifactPath: string, functionName: string) => profileFlamegraph(artifactPath, functionName, log));

return program;
}
51 changes: 51 additions & 0 deletions yarn-project/aztec/src/cli/cmds/profile_flamegraph.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
import { execFileSync } from 'child_process';
import { existsSync, readFileSync, rmSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';

const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../../..');
const CLI = join(PACKAGE_ROOT, 'dest/bin/index.js');
const WORKSPACE = join(PACKAGE_ROOT, 'test/mixed-workspace');
const TARGET = join(WORKSPACE, 'target');
const CONTRACT_ARTIFACT = join(TARGET, 'simple_contract-SimpleContract.json');

describe('aztec profile flamegraph', () => {
const svgPath = join(TARGET, 'simple_contract-SimpleContract-private_function-flamegraph.svg');

beforeAll(() => {
rmSync(TARGET, { recursive: true, force: true });
runCompile();
runFlamegraph(CONTRACT_ARTIFACT, 'private_function');
}, 300_000);

afterAll(() => {
rmSync(TARGET, { recursive: true, force: true });
});

it('generates a valid flamegraph SVG', () => {
expect(existsSync(svgPath)).toBe(true);
const content = readFileSync(svgPath, 'utf-8');
expect(content).toContain('<svg');
expect(content).toContain('</svg>');
});
});

function runCompile() {
try {
execFileSync('node', [CLI, 'compile'], { cwd: WORKSPACE, stdio: 'pipe' });
} catch (e: any) {
throw new Error(`compile failed:\n${e.stderr?.toString() ?? e.message}`);
}
}

function runFlamegraph(artifactPath: string, functionName: string) {
try {
execFileSync('node', [CLI, 'profile', 'flamegraph', artifactPath, functionName], {
encoding: 'utf-8',
stdio: 'pipe',
});
} catch (e: any) {
throw new Error(`profile flamegraph failed:\n${e.stderr?.toString() ?? e.message}`);
}
}
63 changes: 63 additions & 0 deletions yarn-project/aztec/src/cli/cmds/profile_flamegraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { LogFn } from '@aztec/foundation/log';

import { readFile, rename, rm, writeFile } from 'fs/promises';
import { basename, dirname, join } from 'path';

import { makeFunctionArtifact } from './profile_utils.js';
import type { CompiledArtifact } from './utils/artifacts.js';
import { run } from './utils/spawn.js';

/** Generates a gate count flamegraph SVG for a single contract function. */
export async function profileFlamegraph(artifactPath: string, functionName: string, log: LogFn): Promise<void> {
const raw = await readFile(artifactPath, 'utf-8');
const artifact: CompiledArtifact = JSON.parse(raw);

if (!Array.isArray(artifact.functions)) {
throw new Error(`${artifactPath} does not appear to be a contract artifact (no functions array)`);
}

const func = artifact.functions.find(f => f.name === functionName);
if (!func) {
const available = artifact.functions.map(f => f.name).join(', ');
throw new Error(`Function "${functionName}" not found in artifact. Available: ${available}`);
}
if (func.is_unconstrained) {
throw new Error(`Function "${functionName}" is unconstrained and cannot be profiled`);
}

const outputDir = dirname(artifactPath);
const contractName = basename(artifactPath, '.json');
const functionArtifact = join(outputDir, `${contractName}-${functionName}.json`);

try {
await writeFile(functionArtifact, makeFunctionArtifact(artifact, func));

const profiler = process.env.PROFILER_PATH ?? 'noir-profiler';
const bb = process.env.BB ?? 'bb';

await run(profiler, [
'gates',
'--artifact-path',
functionArtifact,
'--backend-path',
bb,
'--backend-gates-command',
'gates',
'--output',
outputDir,
'--scheme',
'chonk',
'--include_gates_per_opcode',
]);

// noir-profiler names the SVG using the internal function name which
// retains the __aztec_nr_internals__ prefix in the bytecode metadata.
const srcSvg = join(outputDir, `__aztec_nr_internals__${functionName}_gates.svg`);
const destSvg = join(outputDir, `${contractName}-${functionName}-flamegraph.svg`);
await rename(srcSvg, destSvg);

log(`Flamegraph written to ${destSvg}`);
} finally {
await rm(functionArtifact, { force: true });
}
}
2 changes: 1 addition & 1 deletion yarn-project/aztec/src/cli/cmds/profile_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export async function discoverArtifacts(
}

/** Extracts a contract function as a standalone program artifact JSON string. */
function makeFunctionArtifact(artifact: CompiledArtifact, func: ContractFunction) {
export function makeFunctionArtifact(artifact: CompiledArtifact, func: ContractFunction) {
/* eslint-disable camelcase */
return JSON.stringify({
noir_version: artifact.noir_version,
Expand Down
16 changes: 16 additions & 0 deletions yarn-project/aztec/src/cli/cmds/utils/spawn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { spawn } from 'child_process';

/** Spawns a command with inherited stdio and rejects on non-zero exit. */
export function run(cmd: string, args: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(cmd, args, { stdio: 'inherit' });
child.on('error', reject);
child.on('close', code => {
if (code !== 0) {
reject(new Error(`${cmd} exited with code ${code}`));
} else {
resolve();
}
});
});
}
Loading