Skip to content

Commit 33e467b

Browse files
Add aggregated graph for instruction tracing
1 parent e4cdb16 commit 33e467b

4 files changed

Lines changed: 291 additions & 108 deletions

File tree

canbench-bin/src/instruction_tracing.rs

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -307,65 +307,16 @@ fn inject_tracing(
307307
/// Renders the tracing to a file. Adapted from
308308
/// https://github.com/dfinity/ic-repl/blob/master/src/tracing.rs
309309
pub(super) fn write_traces_to_file(
310-
input: Vec<(i32, i64)>,
310+
input: InstructionTraceGraphNode,
311311
names: &BTreeMap<i32, String>,
312312
bench_fn: &str,
313313
filename: PathBuf,
314314
) -> Result<(), String> {
315315
use inferno::flamegraph::{from_reader, Options};
316-
let mut stack = Vec::new();
317-
let mut prefix = Vec::new();
318-
let mut result = Vec::new();
319-
let mut prev = None;
320-
for (id, count) in input.into_iter() {
321-
if id >= 0 {
322-
stack.push((id, count, 0));
323-
let name = if id < i32::MAX {
324-
match names.get(&id) {
325-
Some(name) => name.clone(),
326-
None => "func_".to_string() + &id.to_string(),
327-
}
328-
} else {
329-
bench_fn.to_string()
330-
};
331-
prefix.push(name);
332-
} else {
333-
let end_id = reverse_func_id(id);
334-
match stack.pop() {
335-
None => return Err("pop empty stack".to_string()),
336-
Some((start_id, start, children)) => {
337-
if start_id != end_id {
338-
return Err("func id mismatch".to_string());
339-
}
340-
let cost = count - start;
341-
let frame = prefix.join(";");
342-
prefix.pop().unwrap();
343-
if let Some((parent, parent_cost, children_cost)) = stack.pop() {
344-
stack.push((parent, parent_cost, children_cost + cost));
345-
}
346-
match prev {
347-
Some(prev) if prev == frame => {
348-
// Add an empty spacer to avoid collapsing adjacent same-named calls
349-
// See https://github.com/jonhoo/inferno/issues/185#issuecomment-671393504
350-
result.push(format!("{};spacer 0", prefix.join(";")));
351-
}
352-
_ => (),
353-
}
354-
result.push(format!("{} {}", frame, cost - children));
355-
prev = Some(frame);
356-
}
357-
}
358-
}
359-
}
360-
let is_trace_incomplete = !stack.is_empty();
316+
let mut result = convert_trace_graph_to_flamegraph(input, names, bench_fn);
361317
let mut opt = Options::default();
362318
opt.count_name = "instructions".to_string();
363-
let bench_fn = if is_trace_incomplete {
364-
bench_fn.to_string() + " (incomplete)"
365-
} else {
366-
bench_fn.to_string()
367-
};
368-
opt.title = bench_fn;
319+
opt.title = bench_fn.to_string();
369320
opt.flame_chart = true;
370321
opt.no_sort = true;
371322
// Reserve result order to make flamegraph from left to right.
@@ -379,6 +330,49 @@ pub(super) fn write_traces_to_file(
379330
Ok(())
380331
}
381332

333+
fn convert_trace_graph_to_flamegraph(
334+
input: InstructionTraceGraphNode,
335+
names: &BTreeMap<i32, String>,
336+
bench_fn: &str,
337+
) -> Vec<String> {
338+
let mut flamegraph = Vec::new();
339+
let mut prefix = Vec::new();
340+
convert_trace_node_to_flamegraph(input, names, bench_fn, &mut prefix, &mut flamegraph);
341+
flamegraph
342+
}
343+
344+
fn convert_trace_node_to_flamegraph(
345+
input: InstructionTraceGraphNode,
346+
names: &BTreeMap<i32, String>,
347+
bench_fn: &str,
348+
prefix: &mut Vec<String>,
349+
flamegraph: &mut Vec<String>,
350+
) {
351+
let InstructionTraceGraphNode {
352+
func_id,
353+
cost,
354+
children,
355+
} = input;
356+
prefix.push(func_id_to_name(func_id, names, bench_fn));
357+
358+
for child in children {
359+
convert_trace_node_to_flamegraph(child, names, bench_fn, prefix, flamegraph);
360+
}
361+
flamegraph.push(format!("{} {}", prefix.join(";"), cost));
362+
prefix.pop();
363+
}
364+
365+
fn func_id_to_name(func_id: i32, names: &BTreeMap<i32, String>, bench_fn: &str) -> String {
366+
if func_id < i32::MAX {
367+
match names.get(&func_id) {
368+
Some(name) => name.clone(),
369+
None => "func_".to_string() + &func_id.to_string(),
370+
}
371+
} else {
372+
bench_fn.to_string()
373+
}
374+
}
375+
382376
/// Extracts function names from the module to be a map from function id to function name.
383377
fn extract_function_names(module: &Module) -> BTreeMap<i32, String> {
384378
module

canbench-bin/src/lib.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod results_file;
88
mod summary;
99
mod table;
1010

11-
use canbench_rs::{BenchResult, Measurement};
11+
use canbench_rs::{BenchResult, InstructionTraceGraphNode, Measurement};
1212
use candid::{Encode, Principal};
1313
use flate2::read::GzDecoder;
1414
use instruction_tracing::{prepare_instruction_tracing, write_traces_to_file};
@@ -117,14 +117,17 @@ pub fn run_benchmarks(
117117
}
118118

119119
if let Some(instruction_tracing_canister_id) = instruction_tracing_canister_id {
120-
run_instruction_tracing(
121-
&pocket_ic,
122-
instruction_tracing_canister_id,
123-
bench_fn,
124-
function_names_mapping.as_ref().unwrap(),
125-
results_file,
126-
result.total.instructions,
127-
);
120+
for aggregate in [true, false] {
121+
run_instruction_tracing(
122+
&pocket_ic,
123+
instruction_tracing_canister_id,
124+
bench_fn,
125+
function_names_mapping.as_ref().unwrap(),
126+
results_file,
127+
result.total.instructions,
128+
aggregate,
129+
);
130+
}
128131
}
129132

130133
new_results.insert(bench_fn.to_string(), result);
@@ -270,15 +273,16 @@ fn run_instruction_tracing(
270273
names_mapping: &BTreeMap<i32, String>,
271274
results_file: &Path,
272275
bench_instructions: u64,
276+
aggregate: bool,
273277
) {
274-
let traces: Result<Vec<(i32, i64)>, String> = match pocket_ic.query_call(
278+
let traces: Result<InstructionTraceGraphNode, String> = match pocket_ic.query_call(
275279
canister_id,
276280
Principal::anonymous(),
277281
&format!("__tracing__{bench_fn}"),
278-
Encode!(&bench_instructions).unwrap(),
282+
Encode!(&bench_instructions, &aggregate).unwrap(),
279283
) {
280284
Ok(reply) => {
281-
let res: Result<Vec<(i32, i64)>, String> =
285+
let res: Result<InstructionTraceGraphNode, String> =
282286
candid::decode_one(&reply).expect("error decoding tracing result");
283287
res
284288
}
@@ -295,7 +299,10 @@ fn run_instruction_tracing(
295299
traces,
296300
names_mapping,
297301
bench_fn,
298-
results_file.with_file_name(format!("{bench_fn}.svg")),
302+
results_file.with_file_name(format!(
303+
"{bench_fn}{}.svg",
304+
if aggregate { "_aggregated" } else { "" }
305+
)),
299306
)
300307
.expect("failed to write tracing results"),
301308
Err(e) => {

canbench-rs-macros/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ pub fn bench(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
6363

6464
#[ic_cdk::query]
6565
#[allow(non_snake_case)]
66-
fn #tracing_func_name(bench_instructions: u64) -> Result<Vec<(i32, i64)>, String> {
66+
fn #tracing_func_name(bench_instructions: u64, aggregate: bool) -> Result<canbench_rs::InstructionTraceGraphNode, String> {
6767
#func_name();
68-
canbench_rs::get_traces(bench_instructions)
68+
canbench_rs::get_traces(bench_instructions, aggregate)
6969
}
7070
}
7171
}
@@ -91,11 +91,11 @@ pub fn bench(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
9191

9292
#[ic_cdk::query]
9393
#[allow(non_snake_case)]
94-
fn #tracing_func_name(bench_instructions: u64) -> Result<Vec<(i32, i64)>, String> {
94+
fn #tracing_func_name(bench_instructions: u64, aggregate: bool) -> Result<canbench_rs::InstructionTraceGraphNode, String> {
9595
canbench_rs::bench_fn(|| {
9696
#func_name();
9797
});
98-
canbench_rs::get_traces(bench_instructions)
98+
canbench_rs::get_traces(bench_instructions, aggregate)
9999
}
100100
}
101101
}

0 commit comments

Comments
 (0)