Skip to content

Commit 90a4cb0

Browse files
authored
Merge pull request #1744 from Kobzol/benchmark-detail-api
Add compile benchmark detail endpoint
2 parents 40fb983 + b4b44f0 commit 90a4cb0

File tree

9 files changed

+244
-100
lines changed

9 files changed

+244
-100
lines changed

site/frontend/src/graph/data.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface Series {
1818

1919
// Graph data received from the server
2020
export interface GraphData {
21-
commits: [[number, string]];
21+
commits: Array<[number, string]>;
22+
// benchmark -> profile -> scenario -> series
2223
benchmarks: Dict<Dict<Dict<Series>>>;
2324
}

site/frontend/src/graph/resolver.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

site/frontend/src/pages/compare/compile/table/benchmark-detail.vue

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import {
55
CompileBenchmarkMetadata,
66
CompileTestCase,
77
} from "../common";
8-
import {computed, onMounted, Ref, ref} from "vue";
8+
import {capitalize, computed, onMounted, Ref, ref} from "vue";
99
import Tooltip from "../../tooltip.vue";
1010
import {ArtifactDescription} from "../../types";
1111
import {daysBetweenDates, getFutureDate, getPastDate} from "./utils";
1212
import {GraphRenderOpts, renderPlots} from "../../../../graph/render";
13-
import {GRAPH_RESOLVER} from "../../../../graph/resolver";
14-
import {GraphKind} from "../../../../graph/data";
13+
import {GraphData, GraphKind, GraphsSelector} from "../../../../graph/data";
1514
import uPlot from "uplot";
1615
import CachegrindCmd from "../../../../components/cachegrind-cmd.vue";
16+
import {COMPILE_DETAIL_RESOLVER} from "./detail-resolver";
1717
1818
const props = defineProps<{
1919
testCase: CompileTestCase;
@@ -98,16 +98,6 @@ function drawCurrentDate(opts: GraphRenderOpts, date: Date) {
9898
9999
// Render both relative and absolute graphs
100100
async function renderGraphs() {
101-
// We want to be able to see noise "blips" vs. a previous artifact.
102-
// The "percent relative from previous commit" graph should be the best to
103-
// see these kinds of changes.
104-
renderGraph("percentrelative" as GraphKind, relativeChartElement);
105-
// We also want to see whether a change maintained its value or whether it was noise and has since
106-
// returned to steady state. Here, an absolute graph ("raw") is best.
107-
renderGraph("raw" as GraphKind, absoluteChartElement);
108-
}
109-
110-
async function renderGraph(kind: GraphKind, chartRef: Ref<HTMLElement | null>) {
111101
const {start, end, date} = graphRange.value;
112102
const selector = {
113103
benchmark: props.testCase.benchmark,
@@ -116,9 +106,68 @@ async function renderGraph(kind: GraphKind, chartRef: Ref<HTMLElement | null>) {
116106
stat: props.metric,
117107
start,
118108
end,
119-
kind,
109+
kinds: ["percentrelative", "raw"] as GraphKind[],
120110
};
121-
const graphData = await GRAPH_RESOLVER.loadGraph(selector);
111+
const detail = await COMPILE_DETAIL_RESOLVER.loadDetail(selector);
112+
if (detail.commits.length === 0) {
113+
return;
114+
}
115+
116+
function buildGraph(
117+
index: number,
118+
kind: GraphKind
119+
): [GraphData, GraphsSelector] {
120+
const data = {
121+
commits: detail.commits,
122+
benchmarks: {
123+
[selector.benchmark]: {
124+
// The server returns profiles capitalized, so we need to match that
125+
// here, so that the graph code can find the expected profile.
126+
[capitalize(selector.profile)]: {
127+
[selector.scenario]: detail.graphs[index],
128+
},
129+
},
130+
},
131+
};
132+
const graphSelector = {
133+
benchmark: selector.benchmark,
134+
profile: selector.profile,
135+
scenario: selector.scenario,
136+
stat: selector.stat,
137+
start: selector.start,
138+
end: selector.end,
139+
kind,
140+
};
141+
142+
return [data, graphSelector];
143+
}
144+
145+
const [percentRelativeData, percentRelativeSelector] = buildGraph(
146+
0,
147+
"percentrelative"
148+
);
149+
const [rawData, rawSelector] = buildGraph(1, "raw");
150+
151+
// We want to be able to see noise "blips" vs. a previous artifact.
152+
// The "percent relative from previous commit" graph should be the best to
153+
// see these kinds of changes.
154+
renderGraph(
155+
percentRelativeData,
156+
percentRelativeSelector,
157+
date,
158+
relativeChartElement
159+
);
160+
// We also want to see whether a change maintained its value or whether it was noise and has since
161+
// returned to steady state. Here, an absolute graph ("raw") is best.
162+
renderGraph(rawData, rawSelector, date, absoluteChartElement);
163+
}
164+
165+
async function renderGraph(
166+
graphData: GraphData,
167+
selector: GraphsSelector,
168+
date: Date | null,
169+
chartRef: Ref<HTMLElement | null>
170+
) {
122171
const opts: GraphRenderOpts = {
123172
width: Math.min(window.innerWidth - 40, 465),
124173
renderTitle: false,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {GraphKind, Series} from "../../../../graph/data";
2+
import {getJson} from "../../../../utils/requests";
3+
import {COMPARE_COMPILE_DETAIL_DATA_URL} from "../../../../urls";
4+
5+
export interface CompileDetailSelector {
6+
start: string;
7+
end: string;
8+
stat: string;
9+
benchmark: string;
10+
scenario: string;
11+
profile: string;
12+
kinds: GraphKind[];
13+
}
14+
15+
// Compile benchmark detail received from the server
16+
export interface CompileDetail {
17+
commits: Array<[number, string]>;
18+
// One Series for each GraphKind in the CompileDetailSelector
19+
graphs: Series[];
20+
}
21+
22+
/**
23+
* Compile benchmark detail resolver that contains a cache of downloaded details.
24+
* This is important for Vue components that download the benchmark detail on mount.
25+
* Without a cache, they would download the detail each time they are destroyed
26+
* and recreated.
27+
*/
28+
export class CompileBenchmarkDetailResolver {
29+
private cache: Dict<CompileDetail> = {};
30+
31+
public async loadDetail(
32+
selector: CompileDetailSelector
33+
): Promise<CompileDetail> {
34+
const key = `${selector.benchmark};${selector.profile};${selector.scenario};${selector.start};${selector.end};${selector.stat};${selector.kinds}`;
35+
if (!this.cache.hasOwnProperty(key)) {
36+
this.cache[key] = await loadDetail(selector);
37+
}
38+
39+
return this.cache[key];
40+
}
41+
}
42+
43+
/**
44+
* This is essentially a global variable, but it makes the code simpler and
45+
* since we currently don't have any unit tests, we don't really need to avoid
46+
* global variables that much. If needed, it could be provided to Vue components
47+
* from a parent via props or context.
48+
*/
49+
export const COMPILE_DETAIL_RESOLVER = new CompileBenchmarkDetailResolver();
50+
51+
async function loadDetail(
52+
selector: CompileDetailSelector
53+
): Promise<CompileDetail> {
54+
const params = {
55+
start: selector.start,
56+
end: selector.end,
57+
stat: selector.stat,
58+
benchmark: selector.benchmark,
59+
scenario: selector.scenario,
60+
profile: selector.profile,
61+
kinds: selector.kinds.join(","),
62+
};
63+
return await getJson<CompileDetail>(COMPARE_COMPILE_DETAIL_DATA_URL, params);
64+
}

site/frontend/src/urls.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export const STATUS_DATA_URL = `${BASE_URL}/status_page`;
77
export const BOOTSTRAP_DATA_URL = `${BASE_URL}/bootstrap`;
88
export const GRAPH_DATA_URL = `${BASE_URL}/graphs`;
99
export const COMPARE_DATA_URL = `${BASE_URL}/get`;
10+
export const COMPARE_COMPILE_DETAIL_DATA_URL = `${BASE_URL}/compare-compile-detail`;
1011
export const SELF_PROFILE_DATA_URL = `${BASE_URL}/self-profile`;

site/src/api.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,62 @@ pub mod graphs {
110110
}
111111
}
112112

113+
pub mod detail {
114+
use crate::api::graphs::{GraphKind, Series};
115+
use collector::Bound;
116+
use serde::de::{DeserializeOwned, Error};
117+
use serde::{Deserialize, Deserializer, Serialize};
118+
use std::fmt::Formatter;
119+
use std::marker::PhantomData;
120+
121+
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
122+
pub struct Request {
123+
pub start: Bound,
124+
pub end: Bound,
125+
pub stat: String,
126+
pub benchmark: String,
127+
pub scenario: String,
128+
pub profile: String,
129+
#[serde(deserialize_with = "vec_from_comma_separated")]
130+
pub kinds: Vec<GraphKind>,
131+
}
132+
133+
// Deserializes a comma separated list of GraphKind values
134+
fn vec_from_comma_separated<'de, T: DeserializeOwned, D>(
135+
deserializer: D,
136+
) -> Result<Vec<T>, D::Error>
137+
where
138+
D: Deserializer<'de>,
139+
{
140+
struct CommaSeparatedVisitor<T>(PhantomData<T>);
141+
142+
impl<'de, T: DeserializeOwned> serde::de::Visitor<'de> for CommaSeparatedVisitor<T> {
143+
type Value = Vec<T>;
144+
145+
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
146+
formatter.write_str("comma separated list of GraphKind values")
147+
}
148+
149+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
150+
where
151+
E: Error,
152+
{
153+
v.split(',')
154+
.map(|v| T::deserialize(serde::de::value::StrDeserializer::new(v)))
155+
.collect::<Result<Vec<T>, _>>()
156+
}
157+
}
158+
159+
deserializer.deserialize_str(CommaSeparatedVisitor(Default::default()))
160+
}
161+
162+
#[derive(Debug, PartialEq, Clone, Serialize)]
163+
pub struct Response {
164+
pub commits: Vec<(i64, String)>,
165+
pub graphs: Vec<Series>,
166+
}
167+
}
168+
113169
pub mod bootstrap {
114170
use collector::Bound;
115171
use hashbrown::HashMap;

site/src/request_handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod status_page;
99
pub use bootstrap::handle_bootstrap;
1010
pub use dashboard::handle_dashboard;
1111
pub use github::handle_github;
12-
pub use graph::{handle_graph, handle_graphs};
12+
pub use graph::{handle_compile_detail, handle_graphs};
1313
pub use next_artifact::handle_next_artifact;
1414
pub use self_profile::{
1515
handle_self_profile, handle_self_profile_processed_download, handle_self_profile_raw,

0 commit comments

Comments
 (0)