Skip to content

Commit cc7af31

Browse files
committed
Label the perf image per grid point
The image endpoint takes `parameters`, the same comma separated list of URL encoded parameter sets the query endpoint takes, spelled the same way and read by the same code path. The image query struct mirrors the query struct field for field, doc comment included, so the image is the plot of the query it mirrors. A line is a grid point, so the key names the grid point when the benchmark name no longer says which line is which. A benchmark whose every line in the image plots the empty parameter set keeps the bare benchmark name it has always had. One non empty set among a benchmark's lines names them all, the empty set among them included, which reads `{}`. The set is spelled in its canonical form and follows the benchmark name on the same row, so the wrapping the key text already does for a long benchmark name loses the tail of the set spelling first and the name never. A project that never reported a parameter set is drawn from the inputs it was always drawn from: every line of every such benchmark is labeled exactly as before, which the fixtures pin, and nothing else on the draw path moved. A point whose measure named no `value` has no point estimate to place on the axis, so the plot leaves it out of the line. That already held; a fixture now pins it.
1 parent 76b54e1 commit cc7af31

7 files changed

Lines changed: 1893 additions & 10 deletions

File tree

lib/api_projects/src/perf/img.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub async fn proj_perf_img_options(
3333
/// The query results are every permutation of each branch, testbed, benchmark, and measure.
3434
/// There is a limit of 8 permutations for a single image.
3535
/// Therefore, only the first 8 permutations are plotted.
36+
/// Each permutation plots one line per grid point of its benchmark,
37+
/// narrowed by the `parameters` filter when one is given.
3638
/// If the project is public, then the user does not need to be authenticated.
3739
/// If the project is private, then the user must be authenticated and have `view` permissions for the project,
3840
/// or provide a valid project key for the project.

lib/api_projects/tests/perf.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3887,3 +3887,101 @@ async fn perf_point_without_a_value_name_keeps_its_line() {
38873887
assert_eq!(p99.value, 99.5);
38883888
assert!(p99.boundaries.is_none());
38893889
}
3890+
3891+
// =============================================================================
3892+
// Section: The perf image
3893+
// =============================================================================
3894+
3895+
/// The perf image path for a fully built query, through the encoder a client uses.
3896+
fn perf_img_url(project_slug: &str, query: &JsonPerfQuery) -> String {
3897+
format!(
3898+
"/v0/projects/{project_slug}/perf/img?{}",
3899+
query.to_query_string(&[]).expect("build query string")
3900+
)
3901+
}
3902+
3903+
/// GET a perf image path and return the JPEG.
3904+
async fn get_perf_img(server: &TestServer, token: &str, url: &str) -> Vec<u8> {
3905+
let resp = server
3906+
.client
3907+
.get(server.api_url(url))
3908+
.header(
3909+
bencher_json::AUTHORIZATION,
3910+
bencher_json::bearer_header(token),
3911+
)
3912+
.send()
3913+
.await
3914+
.expect("Request failed");
3915+
assert_eq!(resp.status(), StatusCode::OK, "GET {url}");
3916+
assert_eq!(
3917+
resp.headers()
3918+
.get(http::header::CONTENT_TYPE)
3919+
.expect("content type"),
3920+
"image/jpeg",
3921+
"GET {url} is a JPEG"
3922+
);
3923+
resp.bytes().await.expect("read image").to_vec()
3924+
}
3925+
3926+
// The image takes the same `parameters` filter the query takes, and plots exactly
3927+
// the lines the query answers with: the image of a filtered query is the plot of
3928+
// that query's response, byte for byte.
3929+
#[tokio::test]
3930+
async fn perf_img_parameters_filter_plots_the_queried_grid_points() {
3931+
let server = TestServer::new().await;
3932+
let user = server
3933+
.signup("Test User", "perfimgfilter@example.com")
3934+
.await;
3935+
let org = server.create_org(&user, "Perf Img Filter Org").await;
3936+
let project = server
3937+
.create_project(&user, &org, "Perf Img Filter Project")
3938+
.await;
3939+
3940+
let project_id = get_project_id(&server, project.slug.as_ref());
3941+
let data = create_perf_data(&server, project_id);
3942+
create_grid_point(&server, &data, data.benchmark_id, r#"{"size_mb": 16}"#, 1.0);
3943+
create_grid_point(&server, &data, data.benchmark_id, r#"{"size_mb": 32}"#, 2.0);
3944+
3945+
let filtered = fixture_query(
3946+
&data,
3947+
vec![data.benchmark_uuid],
3948+
&[r#"{"size_mb": 16}"#, r#"{"size_mb": 32}"#],
3949+
);
3950+
let perf = get_perf(
3951+
&server,
3952+
&user.token,
3953+
&perf_query_url(project.slug.as_ref(), &filtered),
3954+
)
3955+
.await;
3956+
assert_eq!(
3957+
line_parameters(&perf),
3958+
vec![
3959+
r#"{"size_mb":16}"#.to_owned(),
3960+
r#"{"size_mb":32}"#.to_owned()
3961+
],
3962+
"the filter leaves the empty grid point out"
3963+
);
3964+
3965+
// The image endpoint takes no title here, so both sides fall back to the
3966+
// project name and the plot is handed the very same inputs.
3967+
let jpeg = get_perf_img(
3968+
&server,
3969+
&user.token,
3970+
&perf_img_url(project.slug.as_ref(), &filtered),
3971+
)
3972+
.await;
3973+
let plotted = bencher_plot::LinePlot::new()
3974+
.draw(None, &perf)
3975+
.expect("draw the queried lines");
3976+
assert_eq!(jpeg, plotted, "the image plots the query's own lines");
3977+
3978+
// Without the filter the empty grid point is a third line, so the image differs.
3979+
let unfiltered = fixture_query(&data, vec![data.benchmark_uuid], &[]);
3980+
let all_jpeg = get_perf_img(
3981+
&server,
3982+
&user.token,
3983+
&perf_img_url(project.slug.as_ref(), &unfiltered),
3984+
)
3985+
.await;
3986+
assert_ne!(jpeg, all_jpeg, "the filter reaches the image query");
3987+
}

lib/bencher_json/src/project/perf.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ pub struct JsonPerfImgQueryParams {
8080
pub specs: Option<String>,
8181
/// A comma separated list of benchmark UUIDs to query.
8282
pub benchmarks: String,
83+
/// An optional comma separated list of URL encoded parameter sets to filter on.
84+
/// A grid point is queried when at least one of them is a subset of its
85+
/// parameter set: every key the filter names, with the same value.
86+
/// Leaving this off queries every grid point.
87+
pub parameters: Option<String>,
8388
/// A comma separated list of measure UUIDs to query.
8489
pub measures: String,
8590
/// Search for metrics after the given date time in milliseconds.
@@ -97,6 +102,7 @@ impl From<JsonPerfImgQueryParams> for JsonPerfQueryParams {
97102
testbeds,
98103
specs,
99104
benchmarks,
105+
parameters,
100106
measures,
101107
start_time,
102108
end_time,
@@ -107,9 +113,7 @@ impl From<JsonPerfImgQueryParams> for JsonPerfQueryParams {
107113
testbeds,
108114
specs,
109115
benchmarks,
110-
// The perf image has no parameters filter of its own yet, so it plots
111-
// every grid point its dimensions name.
112-
parameters: None,
116+
parameters,
113117
measures,
114118
start_time,
115119
end_time,

0 commit comments

Comments
 (0)