Skip to content

Commit 4d202c4

Browse files
feat: add --base argument to compare local run to another run
Add --base cli argument and simplify polling code 1. If a base run id is provided, we now use the `compareRunsPaginated` resolver and display results from there 2. If a base run id is not provided or if the base run id is not correct (missing, executor mismatch...), we display the single mode results This overall simplifies the polling code and gets rid of the artificial "for_run" and "for_exec" distinctions.
1 parent 39b6fe4 commit 4d202c4

12 files changed

Lines changed: 484 additions & 198 deletions

File tree

src/api_client.rs

Lines changed: 135 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -81,39 +81,12 @@ nest! {
8181

8282
#[derive(Serialize, Clone)]
8383
#[serde(rename_all = "camelCase")]
84-
pub struct FetchLocalRunReportVars {
84+
pub struct FetchLocalRunVars {
8585
pub owner: String,
8686
pub name: String,
8787
pub run_id: String,
8888
}
8989

90-
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
91-
pub enum ReportConclusion {
92-
AcknowledgedFailure,
93-
Failure,
94-
MissingBaseRun,
95-
NoBenchmarks,
96-
Success,
97-
}
98-
99-
impl Display for ReportConclusion {
100-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101-
match self {
102-
ReportConclusion::AcknowledgedFailure => {
103-
write!(f, "{}", style("Acknowledged Failure").yellow().bold())
104-
}
105-
ReportConclusion::Failure => write!(f, "{}", style("Failure").red().bold()),
106-
ReportConclusion::MissingBaseRun => {
107-
write!(f, "{}", style("Missing Base Run").yellow().bold())
108-
}
109-
ReportConclusion::NoBenchmarks => {
110-
write!(f, "{}", style("No Benchmarks").yellow().bold())
111-
}
112-
ReportConclusion::Success => write!(f, "{}", style("Success").green().bold()),
113-
}
114-
}
115-
}
116-
11790
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
11891
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
11992
pub enum RunStatus {
@@ -123,18 +96,23 @@ pub enum RunStatus {
12396
Processing,
12497
}
12598

99+
// Custom deserializer to convert string values to i64
100+
fn deserialize_i64_from_string<'de, D>(deserializer: D) -> Result<i64, D::Error>
101+
where
102+
D: serde::Deserializer<'de>,
103+
{
104+
use serde::de;
105+
let s = String::deserialize(deserializer)?;
106+
s.parse().map_err(de::Error::custom)
107+
}
108+
126109
nest! {
127110
#[derive(Debug, Deserialize, Serialize)]*
128111
#[serde(rename_all = "camelCase")]*
129-
pub struct FetchLocalRunReportRun {
112+
pub struct FetchLocalRunRun {
130113
pub id: String,
131114
pub status: RunStatus,
132115
pub url: String,
133-
pub head_reports: Vec<pub struct FetchLocalRunReportHeadReport {
134-
pub id: String,
135-
pub impact: Option<f64>,
136-
pub conclusion: ReportConclusion,
137-
}>,
138116
pub results: Vec<pub struct FetchLocalRunBenchmarkResult {
139117
pub value: f64,
140118
pub benchmark: pub struct FetchLocalRunBenchmark {
@@ -166,42 +144,113 @@ nest! {
166144
}
167145
}
168146

169-
// Custom deserializer to convert string values to i64
170-
fn deserialize_i64_from_string<'de, D>(deserializer: D) -> Result<i64, D::Error>
171-
where
172-
D: serde::Deserializer<'de>,
173-
{
174-
use serde::de;
175-
let s = String::deserialize(deserializer)?;
176-
s.parse().map_err(de::Error::custom)
147+
nest! {
148+
#[derive(Debug, Deserialize, Serialize)]*
149+
#[serde(rename_all = "camelCase")]*
150+
struct FetchLocalRunData {
151+
repository: struct FetchLocalRunRepository {
152+
run: FetchLocalRunRun,
153+
}
154+
}
155+
}
156+
157+
pub struct FetchLocalRunResponse {
158+
pub run: FetchLocalRunRun,
159+
}
160+
161+
#[derive(Serialize, Clone)]
162+
#[serde(rename_all = "camelCase")]
163+
pub struct CompareRunsVars {
164+
pub owner: String,
165+
pub name: String,
166+
pub base_run_id: String,
167+
pub head_run_id: String,
168+
}
169+
170+
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
171+
#[serde(rename_all = "PascalCase")]
172+
pub enum ResultComparisonCategory {
173+
Acknowledged,
174+
Archived,
175+
Ignored,
176+
Improvement,
177+
New,
178+
Regression,
179+
Skipped,
180+
Untouched,
181+
}
182+
183+
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
184+
pub enum BenchmarkReportStatus {
185+
Improvement,
186+
Missing,
187+
New,
188+
NoChange,
189+
Regression,
190+
}
191+
192+
impl Display for BenchmarkReportStatus {
193+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194+
match self {
195+
BenchmarkReportStatus::Improvement => {
196+
write!(f, "{}", style("Improvement").green().bold())
197+
}
198+
BenchmarkReportStatus::Missing => write!(f, "{}", style("Missing").yellow().bold()),
199+
BenchmarkReportStatus::New => write!(f, "{}", style("New").cyan().bold()),
200+
BenchmarkReportStatus::NoChange => write!(f, "{}", style("No Change").dim()),
201+
BenchmarkReportStatus::Regression => write!(f, "{}", style("Regression").red().bold()),
202+
}
203+
}
177204
}
178205

179206
nest! {
180207
#[derive(Debug, Deserialize, Serialize)]*
181208
#[serde(rename_all = "camelCase")]*
182-
struct FetchLocalRunReportData {
183-
repository: pub struct FetchLocalRunReportRepository {
184-
settings: struct FetchLocalRunReportSettings {
185-
allowed_regression: f64,
186-
},
187-
run: FetchLocalRunReportRun,
188-
}
209+
pub struct CompareRunsBenchmarkResult {
210+
pub value: Option<f64>,
211+
pub base_value: Option<f64>,
212+
pub change: Option<f64>,
213+
pub category: ResultComparisonCategory,
214+
pub status: BenchmarkReportStatus,
215+
pub benchmark: pub struct CompareRunsBenchmark {
216+
pub name: String,
217+
pub executor: ExecutorName,
218+
},
219+
}
220+
}
221+
222+
nest! {
223+
#[derive(Debug, Deserialize, Serialize)]*
224+
#[serde(rename_all = "camelCase")]*
225+
pub struct CompareRunsHeadRun {
226+
pub id: String,
227+
pub status: RunStatus,
189228
}
190229
}
191230

192231
nest! {
193232
#[derive(Debug, Deserialize, Serialize)]*
194233
#[serde(rename_all = "camelCase")]*
195-
struct FetchLocalExecReportData {
196-
project: pub struct FetchLocalExecReportProject {
197-
run: FetchLocalRunReportRun,
234+
struct CompareRunsData {
235+
repository: struct CompareRunsRepository {
236+
paginated_compare_runs: pub struct CompareRunsComparison {
237+
pub impact: Option<f64>,
238+
pub url: String,
239+
pub head_run: CompareRunsHeadRun,
240+
pub result_comparisons: Vec<CompareRunsBenchmarkResult>,
241+
},
198242
}
199243
}
200244
}
201245

202-
pub struct FetchLocalRunReportResponse {
203-
pub allowed_regression: f64,
204-
pub run: FetchLocalRunReportRun,
246+
pub struct CompareRunsResponse {
247+
pub comparison: CompareRunsComparison,
248+
}
249+
250+
pub enum CompareRunsOutcome {
251+
Success(CompareRunsResponse),
252+
BaseRunNotFound,
253+
ExecutorMismatch,
205254
}
206255

207256
#[derive(Serialize, Clone)]
@@ -274,26 +323,47 @@ impl CodSpeedAPIClient {
274323
}
275324
}
276325

277-
pub async fn fetch_local_run_report(
278-
&self,
279-
vars: FetchLocalRunReportVars,
280-
) -> Result<FetchLocalRunReportResponse> {
326+
pub async fn compare_runs(&self, vars: CompareRunsVars) -> Result<CompareRunsOutcome> {
281327
let response = self
282328
.gql_client
283-
.query_with_vars_unwrap::<FetchLocalRunReportData, FetchLocalRunReportVars>(
284-
include_str!("queries/FetchLocalRunReport.gql"),
285-
vars.clone(),
329+
.query_with_vars_unwrap::<CompareRunsData, CompareRunsVars>(
330+
include_str!("queries/CompareRuns.gql"),
331+
vars,
332+
)
333+
.await;
334+
match response {
335+
Ok(response) => Ok(CompareRunsOutcome::Success(CompareRunsResponse {
336+
comparison: response.repository.paginated_compare_runs,
337+
})),
338+
Err(err) if err.contains_error_code("UNAUTHENTICATED") => {
339+
bail!("Your session has expired, please login again using `codspeed auth login`")
340+
}
341+
Err(err) if err.contains_error_code("RUN_NOT_FOUND") => {
342+
Ok(CompareRunsOutcome::BaseRunNotFound)
343+
}
344+
Err(err) if err.contains_error_code("NOT_FOUND") => {
345+
Ok(CompareRunsOutcome::ExecutorMismatch)
346+
}
347+
Err(err) => bail!("Failed to compare runs: {err:?}"),
348+
}
349+
}
350+
351+
pub async fn fetch_local_run(&self, vars: FetchLocalRunVars) -> Result<FetchLocalRunResponse> {
352+
let response = self
353+
.gql_client
354+
.query_with_vars_unwrap::<FetchLocalRunData, FetchLocalRunVars>(
355+
include_str!("queries/FetchLocalRun.gql"),
356+
vars,
286357
)
287358
.await;
288359
match response {
289-
Ok(response) => Ok(FetchLocalRunReportResponse {
290-
allowed_regression: response.repository.settings.allowed_regression,
360+
Ok(response) => Ok(FetchLocalRunResponse {
291361
run: response.repository.run,
292362
}),
293363
Err(err) if err.contains_error_code("UNAUTHENTICATED") => {
294364
bail!("Your session has expired, please login again using `codspeed auth login`")
295365
}
296-
Err(err) => bail!("Failed to fetch local run report: {err}"),
366+
Err(err) => bail!("Failed to fetch local run: {err}"),
297367
}
298368
}
299369

src/cli/exec/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ impl ExecArgs {
5959
fn build_orchestrator_config(
6060
args: ExecArgs,
6161
target: executor::BenchmarkTarget,
62+
poll_results_options: PollResultsOptions,
6263
) -> Result<OrchestratorConfig> {
6364
let modes = args.shared.resolve_modes()?;
6465
let raw_upload_url = args
@@ -90,7 +91,7 @@ fn build_orchestrator_config(
9091
allow_empty: args.shared.allow_empty,
9192
go_runner_version: args.shared.go_runner_version,
9293
show_full_output: args.shared.show_full_output,
93-
poll_results_options: PollResultsOptions::for_exec(),
94+
poll_results_options,
9495
extra_env: HashMap::new(),
9596
})
9697
}
@@ -103,12 +104,17 @@ pub async fn run(
103104
setup_cache_dir: Option<&Path>,
104105
) -> Result<()> {
105106
let merged_args = args.merge_with_project_config(project_config);
107+
let base_run_id = merged_args.shared.base.clone();
106108
let target = executor::BenchmarkTarget::Exec {
107109
command: merged_args.command.clone(),
108110
name: merged_args.name.clone(),
109111
walltime_args: merged_args.walltime_args.clone(),
110112
};
111-
let config = build_orchestrator_config(merged_args, target)?;
113+
let config = build_orchestrator_config(
114+
merged_args,
115+
target,
116+
PollResultsOptions::new(false, base_run_id),
117+
)?;
112118

113119
execute_config(config, api_client, codspeed_config, setup_cache_dir).await
114120
}

0 commit comments

Comments
 (0)