///|
pub(all) struct BenchmarkSummary {
rule_count : Int
edge_count : Int
node_count : Int
dependency_depth : Int
wave_count : Int
max_wave_width : Int
critical_path : Int
fan_in : Int
fanout : Int
command_count : Int
} derive(Debug, Eq)
///|
pub(all) struct BenchmarkReport {
summary : BenchmarkSummary
rendered_commands : Array[String]
wave_sizes : Array[Int]
} derive(Debug)
///|
pub fn BenchmarkSummary::to_text(self : BenchmarkSummary) -> String {
"rules=" +
self.rule_count.to_string() +
" edges=" +
self.edge_count.to_string() +
" nodes=" +
self.node_count.to_string() +
" depth=" +
self.dependency_depth.to_string() +
" waves=" +
self.wave_count.to_string() +
" max_wave_width=" +
self.max_wave_width.to_string() +
" critical_path=" +
self.critical_path.to_string() +
" fan_in=" +
self.fan_in.to_string() +
" fanout=" +
self.fanout.to_string() +
" commands=" +
self.command_count.to_string()
}
///|
fn benchmark_nodes(edges : Array[BuildEdge]) -> Map[String, Bool] {
let nodes : Map[String, Bool] = Map([])
for edge in edges {
for input in edge.inputs {
nodes[input] = true
}
for output in edge.outputs {
nodes[output] = true
}
}
nodes
}
///|
fn benchmark_fan_in(edges : Array[BuildEdge]) -> Int {
let mut total = 0
for edge in edges {
total += edge.inputs.length()
}
if edges.is_empty() {
0
} else {
total / edges.length()
}
}
///|
fn benchmark_fanout(edges : Array[BuildEdge]) -> Int {
let producers : Map[String, Int] = Map([])
for edge in edges {
for input in edge.inputs {
producers[input] = match producers.get(input) {
Some(value) => value + 1
None => 1
}
}
}
let mut highest = 0
for _, count in producers {
if count > highest {
highest = count
}
}
highest
}
///|
/// Measure a target's real parsed graph, not a synthetic line-count metric.
pub fn Manifest::benchmark(
self : Manifest,
target : String,
) -> Result[BenchmarkReport, String] {
let mut target_is_output = false
for edge in self.builds {
if edge.outputs.contains(target) {
target_is_output = true
}
}
if !target_is_output {
return Err("benchmark target is not produced: " + target)
}
match self.validate() {
Err(error) => Err(error)
Ok(_) =>
match DepGraph::build(self).parallel_waves(target) {
Err(error) => Err(error)
Ok(waves) => {
let edges = match DepGraph::build(self).traverse(target) {
Ok(value) => value
Err(error) => return Err(error)
}
let rendered_commands : Array[String] = []
for edge in edges {
match edge.render_command(self.rules) {
Ok(command) => rendered_commands.push(command)
Err(error) => return Err(error)
}
}
let wave_sizes : Array[Int] = []
let mut max_wave_width = 0
for wave in waves {
wave_sizes.push(wave.length())
if wave.length() > max_wave_width {
max_wave_width = wave.length()
}
}
let summary : BenchmarkSummary = {
rule_count: self.rules.length(),
edge_count: edges.length(),
node_count: benchmark_nodes(edges).length(),
dependency_depth: waves.length(),
wave_count: waves.length(),
max_wave_width,
critical_path: waves.length(),
fan_in: benchmark_fan_in(edges),
fanout: benchmark_fanout(edges),
command_count: rendered_commands.length(),
}
Ok({ summary, rendered_commands, wave_sizes })
}
}
}
}
///|
pub fn benchmark_from_ninja(
input : String,
target : String,
) -> Result[BenchmarkReport, String] {
try {
let manifest = Parser::new(input).parse()
manifest.benchmark(target)
} catch {
ParseError::SyntaxError(message, line~, col~) =>
Err(
"parse error at " +
line.to_string() +
":" +
col.to_string() +
": " +
message,
)
ParseError::UnexpectedToken(token, expected~) =>
Err("unexpected token " + token.to_string() + ", expected " + expected)
}
}
///|
/// Build a repeatable multi-layer workload for performance and boundary tests.
pub fn synthetic_fixture(layer_count~ : Int, fanout~ : Int) -> Manifest {
let layers = if layer_count < 1 { 1 } else { layer_count }
let width = if fanout < 1 { 1 } else { fanout }
let builds : Array[BuildEdge] = []
for layer in 0..