Skip to content

Commit 7485037

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 830f631 commit 7485037

11 files changed

Lines changed: 491 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
@@ -55,6 +55,7 @@ impl ExecArgs {
5555
fn build_orchestrator_config(
5656
args: ExecArgs,
5757
target: executor::BenchmarkTarget,
58+
poll_results_options: PollResultsOptions,
5859
) -> Result<OrchestratorConfig> {
5960
let modes = args.shared.resolve_modes()?;
6061
let raw_upload_url = args
@@ -86,7 +87,7 @@ fn build_orchestrator_config(
8687
allow_empty: args.shared.allow_empty,
8788
go_runner_version: args.shared.go_runner_version,
8889
show_full_output: args.shared.show_full_output,
89-
poll_results_options: PollResultsOptions::for_exec(),
90+
poll_results_options,
9091
extra_env: HashMap::new(),
9192
})
9293
}
@@ -99,12 +100,17 @@ pub async fn run(
99100
setup_cache_dir: Option<&Path>,
100101
) -> Result<()> {
101102
let merged_args = args.merge_with_project_config(project_config);
103+
let base_run_id = merged_args.shared.base.clone();
102104
let target = executor::BenchmarkTarget::Exec {
103105
command: merged_args.command.clone(),
104106
name: merged_args.name.clone(),
105107
walltime_args: merged_args.walltime_args.clone(),
106108
};
107-
let config = build_orchestrator_config(merged_args, target)?;
109+
let config = build_orchestrator_config(
110+
merged_args,
111+
target,
112+
PollResultsOptions::new(false, base_run_id),
113+
)?;
108114

109115
execute_config(config, api_client, codspeed_config, setup_cache_dir).await
110116
}

0 commit comments

Comments
 (0)