Skip to content

Commit

Permalink
fix: flamegraph script (and enable > 1 circuit) (#10065)
Browse files Browse the repository at this point in the history
The flamegraph script was broken. 
Then I added some extra features:
- The ability to specify >1 circuit name.
- `-l` list the names of all the available, compiled circuits, because I can't be bothered typing them.
- `-a` create flamegraphs for all the circuits at once.
- `-n` don't try to create flamegraphs, just act as a proxy to the server and serve me my flamegraphs.

---------

Co-authored-by: sirasistant <[email protected]>
  • Loading branch information
iAmMichaelConnor and sirasistant authored Nov 27, 2024
1 parent 7d5c33d commit 0c3b7ef
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 49 deletions.
158 changes: 113 additions & 45 deletions noir-projects/noir-protocol-circuits/scripts/flamegraph.sh
Original file line number Diff line number Diff line change
@@ -1,34 +1,85 @@
#!/usr/bin/env bash
set -eu

EXAMPLE_CMD="$0 private_kernel_init"
EXAMPLE_CMD="$0 private_kernel_init rollup_merge"

# First arg is the circuit name.
if [[ $# -eq 0 || ($1 == -* && $1 != "-h") ]]; then
echo "Please specify the name of the circuit."
echo "e.g.: $EXAMPLE_CMD"
exit 1
fi

CIRCUIT_NAME=$1
# Parse global options.
CIRCUIT_NAMES=()
SERVE=false
PORT=5000
ALLOW_NO_CIRCUIT_NAMES=false

# Get the directory of the script.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# and of the artifact
ARTIFACT_DIR="$SCRIPT_DIR/../target"

# Function to get filenames from a directory
get_filenames() {
local dir="$1"
# Return filenames (without extensions) from the directory
for file in "$dir"/*; do
if [[ -f "$file" ]]; then
filename="$(basename "$file" .${file##*.})"
echo "$filename"
fi
done
}

NAUGHTY_LIST=("empty_nested") # files with no opcodes, which break the flamegraph tool.

get_valid_circuit_names() {
# Capture the output of function call in an array:
ALL_CIRCUIT_NAMES=($(get_filenames "$ARTIFACT_DIR"))
for circuit_name in "${ALL_CIRCUIT_NAMES[@]}"; do
# Skip files that include the substring "simulated"
if [[ "$circuit_name" == *"simulated"* ]]; then
continue
fi
# Skip the file if it's on the naughty list:
if [[ " ${NAUGHTY_LIST[@]} " =~ " ${circuit_name} " ]]; then
continue
fi
CIRCUIT_NAMES+=("$circuit_name")
done
}

while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
echo "Generates a flamegraph for the specified protocol circuit."
echo "Generates flamegraphs for the specified protocol circuits."
echo ""
echo "Usage:"
echo " $0 <CIRCUIT_NAME>"
echo " $0 <CIRCUIT_NAME> [<CIRCUIT_NAME> ...] [options]"
echo ""
echo " e.g.: $EXAMPLE_CMD"
echo " e.g.: $EXAMPLE_CMD -s -p 8080"
echo ""
echo "Arguments:"
echo " -s Serve the file over http"
echo "Options:"
echo " -s Serve the file(s) over http"
echo " -p Specify custom port. Default: ${PORT}"
echo ""
echo "If you're feeling lazy, you can also just list available (compiled) circuit names with:"
echo " $0 -l"
exit 0
;;
-l|--list)
echo "Available circuits (that have been compiled):"
get_valid_circuit_names
for circuit_name in "${CIRCUIT_NAMES[@]}"; do
echo "$circuit_name"
done
exit 0
;;
-a|--all)
echo "This will probably take a while..."
get_valid_circuit_names
shift
;;
-n|--allow-no-circuit-names)
# Enables the existing flamegraphs to be served quickly.
ALLOW_NO_CIRCUIT_NAMES=true
shift
;;
-s|--serve)
SERVE=true
shift
Expand All @@ -43,22 +94,23 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
*)
# Treat any argument not matching an option as a CIRCUIT_NAME.
CIRCUIT_NAMES+=("$1")
shift
;;
;;
esac
done

# Get the directory of the script.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Check if the artifact exists.
ARTIFACT="$SCRIPT_DIR/../target/$CIRCUIT_NAME.json"
if [[ ! -f $ARTIFACT ]]; then
echo "Cannot find artifact: ${ARTIFACT}"
exit 1
# Ensure at least one CIRCUIT_NAME was specified.
if [[ ! $ALLOW_NO_CIRCUIT_NAMES ]]; then
if [[ ${#CIRCUIT_NAMES[@]} -eq 0 ]]; then
echo "Please specify at least one circuit name."
echo "e.g.: $EXAMPLE_CMD"
exit 1
fi
fi

# Build profier if it's not available.
# Build profiler if it's not available.
PROFILER="$SCRIPT_DIR/../../../noir/noir-repo/target/release/noir-profiler"
if [ ! -f $PROFILER ]; then
echo "Profiler not found, building profiler"
Expand All @@ -67,33 +119,49 @@ if [ ! -f $PROFILER ]; then
cd "$SCRIPT_DIR"
fi

# We create dest directory and use it as an output for the generated main.svg file.
# Create the output directory.
DEST="$SCRIPT_DIR/../dest"
mkdir -p $DEST

MEGA_HONK_CIRCUIT_PATTERNS=$(jq -r '.[]' "$SCRIPT_DIR/../../mega_honk_circuits.json")

# Check if the target circuit is a mega honk circuit.
ARTIFACT_FILE_NAME=$(basename -s .json "$ARTIFACT")
# Process each CIRCUIT_NAME.
for CIRCUIT_NAME in "${CIRCUIT_NAMES[@]}"; do
(
echo ""
echo "Doing $CIRCUIT_NAME..."
# Check if the artifact exists.
ARTIFACT="$ARTIFACT_DIR/$CIRCUIT_NAME.json"
if [[ ! -f $ARTIFACT ]]; then
artifact_error="Cannot find artifact: ${ARTIFACT}"
echo "$artifact_error"
fi

IS_MEGA_HONK_CIRCUIT="false"
for pattern in $MEGA_HONK_CIRCUIT_PATTERNS; do
if echo "$ARTIFACT_FILE_NAME" | grep -qE "$pattern"; then
IS_MEGA_HONK_CIRCUIT="true"
break
fi
done
ARTIFACT_FILE_NAME=$(basename -s .json "$ARTIFACT")

# At last, generate the flamegraph.
# If it's a mega honk circuit, we need to set the backend_gates_command argument to "gates_mega_honk".
if [ "$IS_MEGA_HONK_CIRCUIT" = "true" ]; then
$PROFILER gates-flamegraph --artifact-path "${ARTIFACT}" --backend-path "$SCRIPT_DIR/../../../barretenberg/cpp/build/bin/bb" --output "$DEST" --backend-gates-command "gates_mega_honk" -- -h
else
$PROFILER gates-flamegraph --artifact-path "${ARTIFACT}" --backend-path "$SCRIPT_DIR/../../../barretenberg/cpp/build/bin/bb" --output "$DEST" -- -h
fi
# Determine if the circuit is a mega honk circuit.
IS_MEGA_HONK_CIRCUIT="false"
for pattern in $MEGA_HONK_CIRCUIT_PATTERNS; do
if echo "$ARTIFACT_FILE_NAME" | grep -qE "$pattern"; then
IS_MEGA_HONK_CIRCUIT="true"
break
fi
done

# Generate the flamegraph.
if [ "$IS_MEGA_HONK_CIRCUIT" = "true" ]; then
$PROFILER gates --artifact-path "${ARTIFACT}" --backend-path "$SCRIPT_DIR/../../../barretenberg/cpp/build/bin/bb" --output "$DEST" --output-filename "$CIRCUIT_NAME" --backend-gates-command "gates_mega_honk" -- -h
else
$PROFILER gates --artifact-path "${ARTIFACT}" --backend-path "$SCRIPT_DIR/../../../barretenberg/cpp/build/bin/bb" --output "$DEST" --output-filename "$CIRCUIT_NAME" -- -h
fi

# Serve the file over http if -s is set.
echo "Flamegraph generated for circuit: $CIRCUIT_NAME"
) & # These parenthesis `( stuff ) &` mean "do all this in parallel"
done
wait # wait for parallel processes to finish

# Serve the files over HTTP if -s is set.
if $SERVE; then
echo "Serving flamegraph at http://0.0.0.0:${PORT}/main.svg"
python3 -m http.server --directory "$SCRIPT_DIR/../dest" $PORT
fi
echo "Serving flamegraphs at http://0.0.0.0:${PORT}/"
python3 -m http.server --directory "$DEST" $PORT
fi
25 changes: 21 additions & 4 deletions noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub(crate) struct GatesFlamegraphCommand {
/// The output folder for the flamegraph svg files
#[clap(long, short)]
output: String,

/// The output name for the flamegraph svg files
#[clap(long, short = 'f')]
output_filename: Option<String>,
}

pub(crate) fn run(args: GatesFlamegraphCommand) -> eyre::Result<()> {
Expand All @@ -43,6 +47,7 @@ pub(crate) fn run(args: GatesFlamegraphCommand) -> eyre::Result<()> {
},
&InfernoFlamegraphGenerator { count_name: "gates".to_string() },
&PathBuf::from(args.output),
args.output_filename,
)
}

Expand All @@ -51,6 +56,7 @@ fn run_with_provider<Provider: GatesProvider, Generator: FlamegraphGenerator>(
gates_provider: &Provider,
flamegraph_generator: &Generator,
output_path: &Path,
output_filename: Option<String>,
) -> eyre::Result<()> {
let mut program =
read_program_from_file(artifact_path).context("Error reading program from file")?;
Expand Down Expand Up @@ -91,13 +97,18 @@ fn run_with_provider<Provider: GatesProvider, Generator: FlamegraphGenerator>(
})
.collect();

let output_filename = if let Some(output_filename) = &output_filename {
format!("{}::{}::gates.svg", output_filename, func_name)
} else {
format!("{}::gates.svg", func_name)
};
flamegraph_generator.generate_flamegraph(
samples,
&debug_artifact.debug_symbols[func_idx],
&debug_artifact,
artifact_path.to_str().unwrap(),
&func_name,
&Path::new(&output_path).join(Path::new(&format!("{}_gates.svg", &func_name))),
&Path::new(&output_path).join(Path::new(&output_filename)),
)?;
}

Expand Down Expand Up @@ -189,11 +200,17 @@ mod tests {
};
let flamegraph_generator = TestFlamegraphGenerator::default();

super::run_with_provider(&artifact_path, &provider, &flamegraph_generator, temp_dir.path())
.expect("should run without errors");
super::run_with_provider(
&artifact_path,
&provider,
&flamegraph_generator,
temp_dir.path(),
Some(String::from("test_filename")),
)
.expect("should run without errors");

// Check that the output file was written to
let output_file = temp_dir.path().join("main_gates.svg");
let output_file = temp_dir.path().join("test_filename::main::gates.svg");
assert!(output_file.exists());
}
}

0 comments on commit 0c3b7ef

Please sign in to comment.