Skip to content
Open
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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ After releasing `memtrack` or `exec-harness`, you **must** update the version re
const MEMTRACK_CODSPEED_VERSION: &str = "X.Y.Z"; // Update to new version
```

2. **For exec-harness**: Update `EXEC_HARNESS_VERSION` in `src/exec/mod.rs`:
2. **For exec-harness**: Update `EXEC_HARNESS_VERSION` in `src/executor/orchestrator.rs`:
```rust
const EXEC_HARNESS_VERSION: &str = "X.Y.Z"; // Update to new version
```
Expand All @@ -57,7 +57,7 @@ The main runner (`codspeed-runner`) should be released after ensuring all depend
**Verify binary version references**: Check that version constants in the runner code match the released versions:

- `MEMTRACK_CODSPEED_VERSION` in `src/executor/memory/executor.rs`
- `EXEC_HARNESS_VERSION` in `src/exec/mod.rs`
- `EXEC_HARNESS_VERSION` in `src/executor/orchestrator.rs`

#### Release Command

Expand Down
17 changes: 4 additions & 13 deletions src/cli/exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use crate::instruments::Instruments;
use crate::prelude::*;
use crate::project_config::ProjectConfig;
use crate::project_config::merger::ConfigMerger;
use crate::upload::UploadResult;
use crate::upload::poll_results::{PollResultsOptions, poll_results};
use crate::upload::poll_results::PollResultsOptions;
use clap::Args;
use std::path::Path;
use url::Url;
Expand All @@ -18,9 +17,6 @@ pub mod multi_targets;
/// We temporarily force this name for all exec runs
pub const DEFAULT_REPOSITORY_NAME: &str = "local-runs";

pub const EXEC_HARNESS_COMMAND: &str = "exec-harness";
pub const EXEC_HARNESS_VERSION: &str = "1.2.0";

#[derive(Args, Debug)]
pub struct ExecArgs {
#[command(flatten)]
Expand Down Expand Up @@ -92,6 +88,8 @@ fn build_orchestrator_config(
skip_setup: args.shared.skip_setup,
allow_empty: args.shared.allow_empty,
go_runner_version: args.shared.go_runner_version,
show_full_output: args.shared.show_full_output,
poll_results_options: PollResultsOptions::for_exec(),
})
}

Expand Down Expand Up @@ -131,14 +129,7 @@ pub async fn execute_config(

debug!("config: {:#?}", orchestrator.config);

let poll_opts = PollResultsOptions::for_exec();
let poll_results_fn = async |upload_result: &UploadResult| {
poll_results(api_client, upload_result, &poll_opts).await
};

orchestrator
.execute(setup_cache_dir, poll_results_fn)
.await?;
orchestrator.execute(setup_cache_dir, api_client).await?;

Ok(())
}
2 changes: 1 addition & 1 deletion src/cli/exec/multi_targets.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::EXEC_HARNESS_COMMAND;
use crate::executor::config::BenchmarkTarget;
use crate::executor::orchestrator::EXEC_HARNESS_COMMAND;
use crate::prelude::*;
use crate::project_config::{Target, TargetCommand, WalltimeOptions};
use exec_harness::BenchmarkCommand;
Expand Down
140 changes: 95 additions & 45 deletions src/cli/run/helpers/benchmark_display.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::api_client::FetchLocalRunBenchmarkResult;
use crate::cli::run::helpers;
use crate::executor::ExecutorName;
use console::style;
use std::collections::HashMap;
use tabled::settings::object::{Columns, Rows};
use tabled::settings::panel::Panel;
use tabled::settings::style::HorizontalLine;
use tabled::settings::{Alignment, Color, Modify, Style};
use tabled::settings::{Alignment, Color, Modify, Padding, Style};
use tabled::{Table, Tabled};

fn format_with_thousands_sep(n: u64) -> String {
Expand All @@ -20,6 +21,30 @@ fn format_with_thousands_sep(n: u64) -> String {
result.chars().rev().collect()
}

/// Format StdDev with color coding based on value
fn format_stdev_colored(stdev_pct: f64) -> String {
let formatted = format!("{stdev_pct:.2}%");
if stdev_pct <= 2.0 {
format!("{}", style(&formatted).green())
} else if stdev_pct <= 5.0 {
format!("{}", style(&formatted).yellow())
} else {
format!("{}", style(&formatted).red())
}
}

/// Format a percentage distribution value with color intensity
fn format_distribution_pct(pct: f64) -> String {
let formatted = format!("{pct:.1}%");
if pct >= 70.0 {
format!("{}", style(&formatted).white().bold())
} else if pct >= 20.0 {
format!("{}", style(&formatted).white())
} else {
format!("{}", style(&formatted).dim())
}
}

#[derive(Tabled)]
struct SimulationRow {
#[tabled(rename = "Benchmark")]
Expand Down Expand Up @@ -62,7 +87,7 @@ struct MemoryRow {
alloc_calls: String,
}

fn build_table_with_style<T: Tabled>(rows: &[T], instrument: &str) -> String {
fn build_table_with_style<T: Tabled>(rows: &[T], instrument: &str, icon: &str) -> String {
// Line after panel header: use ┬ to connect with columns below
let header_line = HorizontalLine::full('─', '┬', '├', '┤');
// Line after column headers: keep intersection
Expand All @@ -71,7 +96,7 @@ fn build_table_with_style<T: Tabled>(rows: &[T], instrument: &str) -> String {
// Format title in bold CodSpeed orange (#FF8700)
let codspeed_orange = Color::rgb_fg(255, 135, 0);
let title_style = Color::BOLD | codspeed_orange;
let title = title_style.colorize(format!("{instrument} Instrument"));
let title = title_style.colorize(format!("{icon} {instrument}"));

let mut table = Table::new(rows);
table
Expand All @@ -83,10 +108,12 @@ fn build_table_with_style<T: Tabled>(rows: &[T], instrument: &str) -> String {
.horizontals([(1, header_line), (2, column_line)]),
)
.with(Modify::new(Rows::first()).with(Alignment::center()))
// Make column headers bold
// Make column headers bold and dimmed for visual hierarchy
.with(Modify::new(Rows::new(1..2)).with(Color::BOLD))
// Right-align numeric columns (all except first column)
.with(Modify::new(Columns::new(1..)).with(Alignment::right()));
.with(Modify::new(Columns::new(1..)).with(Alignment::right()))
// Add some padding for breathing room
.with(Modify::new(Columns::new(0..)).with(Padding::new(1, 1, 0, 0)));
table.to_string()
}

Expand All @@ -100,10 +127,13 @@ fn build_simulation_table(results: &[&FetchLocalRunBenchmarkResult]) -> String {
.and_then(|v| v.time_distribution.as_ref())
.map(|td| {
let total = result.value;
let ir_pct = (td.ir / total) * 100.0;
let l1m_pct = (td.l1m / total) * 100.0;
let llm_pct = (td.llm / total) * 100.0;
(
format!("{:.1}%", (td.ir / total) * 100.0),
format!("{:.1}%", (td.l1m / total) * 100.0),
format!("{:.1}%", (td.llm / total) * 100.0),
format_distribution_pct(ir_pct),
format_distribution_pct(l1m_pct),
format_distribution_pct(llm_pct),
helpers::format_duration(td.sys, Some(2)),
)
})
Expand All @@ -118,31 +148,45 @@ fn build_simulation_table(results: &[&FetchLocalRunBenchmarkResult]) -> String {

SimulationRow {
name: result.benchmark.name.clone(),
time: helpers::format_duration(result.value, Some(2)),
time: format!(
"{}",
style(helpers::format_duration(result.value, Some(2))).cyan()
),
instructions,
cache,
memory,
sys_time,
}
})
.collect();
build_table_with_style(&rows, "CPU Simulation")
build_table_with_style(
&rows,
ExecutorName::Valgrind.label(),
ExecutorName::Valgrind.icon(),
)
}

fn build_walltime_table(results: &[&FetchLocalRunBenchmarkResult]) -> String {
let rows: Vec<WalltimeRow> = results
.iter()
.map(|result| {
let (time_best, iterations, rel_stdev, run_time) = if let Some(wt) = &result.walltime {
let stdev_pct = (wt.stdev / result.value) * 100.0;
(
helpers::format_duration(result.value, Some(2)),
format!(
"{}",
style(helpers::format_duration(result.value, Some(2))).cyan()
),
format_with_thousands_sep(wt.iterations as u64),
format!("{:.2}%", (wt.stdev / result.value) * 100.0),
format_stdev_colored(stdev_pct),
helpers::format_duration(wt.total_time, Some(2)),
)
} else {
(
helpers::format_duration(result.value, Some(2)),
format!(
"{}",
style(helpers::format_duration(result.value, Some(2))).cyan()
),
"-".to_string(),
"-".to_string(),
"-".to_string(),
Expand All @@ -157,7 +201,11 @@ fn build_walltime_table(results: &[&FetchLocalRunBenchmarkResult]) -> String {
}
})
.collect();
build_table_with_style(&rows, "Walltime")
build_table_with_style(
&rows,
ExecutorName::WallTime.label(),
ExecutorName::WallTime.icon(),
)
}

fn build_memory_table(results: &[&FetchLocalRunBenchmarkResult]) -> String {
Expand All @@ -166,13 +214,19 @@ fn build_memory_table(results: &[&FetchLocalRunBenchmarkResult]) -> String {
.map(|result| {
let (peak_memory, total_allocated, alloc_calls) = if let Some(mem) = &result.memory {
(
helpers::format_memory(mem.peak_memory as f64, Some(1)),
format!(
"{}",
style(helpers::format_memory(mem.peak_memory as f64, Some(1))).cyan()
),
helpers::format_memory(mem.total_allocated as f64, Some(1)),
format_with_thousands_sep(mem.alloc_calls as u64),
)
} else {
(
helpers::format_memory(result.value, Some(1)),
format!(
"{}",
style(helpers::format_memory(result.value, Some(1))).cyan()
),
"-".to_string(),
"-".to_string(),
)
Expand All @@ -185,7 +239,11 @@ fn build_memory_table(results: &[&FetchLocalRunBenchmarkResult]) -> String {
}
})
.collect();
build_table_with_style(&rows, "Memory")
build_table_with_style(
&rows,
ExecutorName::Memory.label(),
ExecutorName::Memory.icon(),
)
}

pub fn build_benchmark_table(results: &[FetchLocalRunBenchmarkResult]) -> String {
Expand Down Expand Up @@ -224,47 +282,36 @@ pub fn build_benchmark_table(results: &[FetchLocalRunBenchmarkResult]) -> String
}

pub fn build_detailed_summary(result: &FetchLocalRunBenchmarkResult) -> String {
let name = &result.benchmark.name;
match result.benchmark.executor {
ExecutorName::Valgrind => {
format!(
"{}: {}",
result.benchmark.name,
helpers::format_duration(result.value, Some(2))
)
let time = style(helpers::format_duration(result.value, Some(2))).cyan();
format!("{name}: {time}")
}
ExecutorName::WallTime => {
if let Some(wt) = &result.walltime {
let time = style(helpers::format_duration(result.value, Some(2))).cyan();
let iters = format_with_thousands_sep(wt.iterations as u64);
let stdev_pct = (wt.stdev / result.value) * 100.0;
let stdev = format_stdev_colored(stdev_pct);
let total = helpers::format_duration(wt.total_time, Some(2));
format!(
"{}: best {} ({} iterations, rel. stddev: {:.2}%, total {})",
result.benchmark.name,
helpers::format_duration(result.value, Some(2)),
format_with_thousands_sep(wt.iterations as u64),
(wt.stdev / result.value) * 100.0,
helpers::format_duration(wt.total_time, Some(2))
"{name}: best {time} ({iters} iterations, rel. stddev: {stdev}, total {total})"
)
} else {
format!(
"{}: {}",
result.benchmark.name,
helpers::format_duration(result.value, Some(2))
)
let time = style(helpers::format_duration(result.value, Some(2))).cyan();
format!("{name}: {time}")
}
}
ExecutorName::Memory => {
if let Some(mem) = &result.memory {
format!(
"{}: peak {} (total allocated: {}, {} allocations)",
result.benchmark.name,
helpers::format_memory(mem.peak_memory as f64, Some(1)),
helpers::format_memory(mem.total_allocated as f64, Some(1)),
format_with_thousands_sep(mem.alloc_calls as u64)
)
let peak = style(helpers::format_memory(mem.peak_memory as f64, Some(1))).cyan();
let total = helpers::format_memory(mem.total_allocated as f64, Some(1));
let allocs = format_with_thousands_sep(mem.alloc_calls as u64);
format!("{name}: peak {peak} (total allocated: {total}, {allocs} allocations)")
} else {
format!(
"{}: {}",
result.benchmark.name,
helpers::format_memory(result.value, Some(1))
)
let mem = style(helpers::format_memory(result.value, Some(1))).cyan();
format!("{name}: {mem}")
}
}
}
Expand Down Expand Up @@ -396,6 +443,7 @@ mod tests {
};

let summary = build_detailed_summary(&result);
let summary = console::strip_ansi_codes(&summary).to_string();
insta::assert_snapshot!(summary, @"benchmark_fast: 1.23 ms");
}

Expand All @@ -417,6 +465,7 @@ mod tests {
};

let summary = build_detailed_summary(&result);
let summary = console::strip_ansi_codes(&summary).to_string();
insta::assert_snapshot!(summary, @"benchmark_wt: best 1.50 s (50 iterations, rel. stddev: 1.67%, total 1.50 s)");
}

Expand All @@ -438,6 +487,7 @@ mod tests {
};

let summary = build_detailed_summary(&result);
let summary = console::strip_ansi_codes(&summary).to_string();
insta::assert_snapshot!(summary, @"benchmark_mem: peak 1 MB (total allocated: 5 MB, 500 allocations)");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ source: src/cli/run/helpers/benchmark_display.rs
expression: table
---
╭─────────────────────────────────────────────────────────────────╮
│ CPU Simulation Instrument
CPU Simulation
├─────────────────┬─────────┬────────┬───────┬────────┬───────────┤
│ Benchmark │ Time │ Instr. │ Cache │ Memory │ Sys. Time │
├─────────────────┼─────────┼────────┼───────┼────────┼───────────┤
│ bench_parse │ 1.23 ms │ 85.0% │ 10.0% │ 4.0% │ 12.34 µs │
│ bench_serialize │ 2.57 ms │ 70.0% │ 20.0% │ 8.0% │ 51.34 µs │
╰─────────────────┴─────────┴────────┴───────┴────────┴───────────╯
╭─────────────────────────────────────────────────────────────────────╮
│ Walltime Instrument
Walltime
├────────────────────┬─────────────┬────────────┬────────┬────────────┤
│ Benchmark │ Time (best) │ Iterations │ StdDev │ Total time │
├────────────────────┼─────────────┼────────────┼────────┼────────────┤
│ bench_http_request │ 150.00 ms │ 100 │ 5.00% │ 150.00 ms │
│ bench_db_query │ 25.00 ms │ 500 │ 2.00% │ 25.00 ms │
╰────────────────────┴─────────────┴────────────┴────────┴────────────╯
╭─────────────────────────────────────────────────────────────────╮
│ Memory Instrument
Memory
├───────────────────┬─────────────┬─────────────────┬─────────────┤
│ Benchmark │ Peak memory │ Total allocated │ Allocations │
├───────────────────┼─────────────┼─────────────────┼─────────────┤
Expand Down
Loading
Loading