Skip to content

Add compile benchmark detail endpoint #1744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 24, 2023
Merged
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
3 changes: 2 additions & 1 deletion site/frontend/src/graph/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Series {

// Graph data received from the server
export interface GraphData {
commits: [[number, string]];
commits: Array<[number, string]>;
// benchmark -> profile -> scenario -> series
benchmarks: Dict<Dict<Dict<Series>>>;
}
29 changes: 0 additions & 29 deletions site/frontend/src/graph/resolver.ts

This file was deleted.

79 changes: 64 additions & 15 deletions site/frontend/src/pages/compare/compile/table/benchmark-detail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import {
CompileBenchmarkMetadata,
CompileTestCase,
} from "../common";
import {computed, onMounted, Ref, ref} from "vue";
import {capitalize, computed, onMounted, Ref, ref} from "vue";
import Tooltip from "../../tooltip.vue";
import {ArtifactDescription} from "../../types";
import {daysBetweenDates, getFutureDate, getPastDate} from "./utils";
import {GraphRenderOpts, renderPlots} from "../../../../graph/render";
import {GRAPH_RESOLVER} from "../../../../graph/resolver";
import {GraphKind} from "../../../../graph/data";
import {GraphData, GraphKind, GraphsSelector} from "../../../../graph/data";
import uPlot from "uplot";
import CachegrindCmd from "../../../../components/cachegrind-cmd.vue";
import {COMPILE_DETAIL_RESOLVER} from "./detail-resolver";

const props = defineProps<{
testCase: CompileTestCase;
Expand Down Expand Up @@ -98,16 +98,6 @@ function drawCurrentDate(opts: GraphRenderOpts, date: Date) {

// Render both relative and absolute graphs
async function renderGraphs() {
// We want to be able to see noise "blips" vs. a previous artifact.
// The "percent relative from previous commit" graph should be the best to
// see these kinds of changes.
renderGraph("percentrelative" as GraphKind, relativeChartElement);
// We also want to see whether a change maintained its value or whether it was noise and has since
// returned to steady state. Here, an absolute graph ("raw") is best.
renderGraph("raw" as GraphKind, absoluteChartElement);
}

async function renderGraph(kind: GraphKind, chartRef: Ref<HTMLElement | null>) {
const {start, end, date} = graphRange.value;
const selector = {
benchmark: props.testCase.benchmark,
Expand All @@ -116,9 +106,68 @@ async function renderGraph(kind: GraphKind, chartRef: Ref<HTMLElement | null>) {
stat: props.metric,
start,
end,
kind,
kinds: ["percentrelative", "raw"] as GraphKind[],
};
const graphData = await GRAPH_RESOLVER.loadGraph(selector);
const detail = await COMPILE_DETAIL_RESOLVER.loadDetail(selector);
if (detail.commits.length === 0) {
return;
}

function buildGraph(
index: number,
kind: GraphKind
): [GraphData, GraphsSelector] {
const data = {
commits: detail.commits,
benchmarks: {
[selector.benchmark]: {
// The server returns profiles capitalized, so we need to match that
// here, so that the graph code can find the expected profile.
[capitalize(selector.profile)]: {
[selector.scenario]: detail.graphs[index],
},
},
},
};
const graphSelector = {
benchmark: selector.benchmark,
profile: selector.profile,
scenario: selector.scenario,
stat: selector.stat,
start: selector.start,
end: selector.end,
kind,
};

return [data, graphSelector];
}

const [percentRelativeData, percentRelativeSelector] = buildGraph(
0,
"percentrelative"
);
const [rawData, rawSelector] = buildGraph(1, "raw");

// We want to be able to see noise "blips" vs. a previous artifact.
// The "percent relative from previous commit" graph should be the best to
// see these kinds of changes.
renderGraph(
percentRelativeData,
percentRelativeSelector,
date,
relativeChartElement
);
// We also want to see whether a change maintained its value or whether it was noise and has since
// returned to steady state. Here, an absolute graph ("raw") is best.
renderGraph(rawData, rawSelector, date, absoluteChartElement);
}

async function renderGraph(
graphData: GraphData,
selector: GraphsSelector,
date: Date | null,
chartRef: Ref<HTMLElement | null>
) {
const opts: GraphRenderOpts = {
width: Math.min(window.innerWidth - 40, 465),
renderTitle: false,
Expand Down
64 changes: 64 additions & 0 deletions site/frontend/src/pages/compare/compile/table/detail-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {GraphKind, Series} from "../../../../graph/data";
import {getJson} from "../../../../utils/requests";
import {COMPARE_COMPILE_DETAIL_DATA_URL} from "../../../../urls";

export interface CompileDetailSelector {
start: string;
end: string;
stat: string;
benchmark: string;
scenario: string;
profile: string;
kinds: GraphKind[];
}

// Compile benchmark detail received from the server
export interface CompileDetail {
commits: Array<[number, string]>;
// One Series for each GraphKind in the CompileDetailSelector
graphs: Series[];
}

/**
* Compile benchmark detail resolver that contains a cache of downloaded details.
* This is important for Vue components that download the benchmark detail on mount.
* Without a cache, they would download the detail each time they are destroyed
* and recreated.
*/
export class CompileBenchmarkDetailResolver {
private cache: Dict<CompileDetail> = {};

public async loadDetail(
selector: CompileDetailSelector
): Promise<CompileDetail> {
const key = `${selector.benchmark};${selector.profile};${selector.scenario};${selector.start};${selector.end};${selector.stat};${selector.kinds}`;
if (!this.cache.hasOwnProperty(key)) {
this.cache[key] = await loadDetail(selector);
}

return this.cache[key];
}
}

/**
* This is essentially a global variable, but it makes the code simpler and
* since we currently don't have any unit tests, we don't really need to avoid
* global variables that much. If needed, it could be provided to Vue components
* from a parent via props or context.
*/
export const COMPILE_DETAIL_RESOLVER = new CompileBenchmarkDetailResolver();

async function loadDetail(
selector: CompileDetailSelector
): Promise<CompileDetail> {
const params = {
start: selector.start,
end: selector.end,
stat: selector.stat,
benchmark: selector.benchmark,
scenario: selector.scenario,
profile: selector.profile,
kinds: selector.kinds.join(","),
};
return await getJson<CompileDetail>(COMPARE_COMPILE_DETAIL_DATA_URL, params);
}
1 change: 1 addition & 0 deletions site/frontend/src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const STATUS_DATA_URL = `${BASE_URL}/status_page`;
export const BOOTSTRAP_DATA_URL = `${BASE_URL}/bootstrap`;
export const GRAPH_DATA_URL = `${BASE_URL}/graphs`;
export const COMPARE_DATA_URL = `${BASE_URL}/get`;
export const COMPARE_COMPILE_DETAIL_DATA_URL = `${BASE_URL}/compare-compile-detail`;
export const SELF_PROFILE_DATA_URL = `${BASE_URL}/self-profile`;
56 changes: 56 additions & 0 deletions site/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,62 @@ pub mod graphs {
}
}

pub mod detail {
use crate::api::graphs::{GraphKind, Series};
use collector::Bound;
use serde::de::{DeserializeOwned, Error};
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::Formatter;
use std::marker::PhantomData;

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Request {
pub start: Bound,
pub end: Bound,
pub stat: String,
pub benchmark: String,
pub scenario: String,
pub profile: String,
#[serde(deserialize_with = "vec_from_comma_separated")]
pub kinds: Vec<GraphKind>,
}

// Deserializes a comma separated list of GraphKind values
fn vec_from_comma_separated<'de, T: DeserializeOwned, D>(
deserializer: D,
) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
{
struct CommaSeparatedVisitor<T>(PhantomData<T>);

impl<'de, T: DeserializeOwned> serde::de::Visitor<'de> for CommaSeparatedVisitor<T> {
type Value = Vec<T>;

fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("comma separated list of GraphKind values")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
v.split(',')
.map(|v| T::deserialize(serde::de::value::StrDeserializer::new(v)))
.collect::<Result<Vec<T>, _>>()
}
}

deserializer.deserialize_str(CommaSeparatedVisitor(Default::default()))
}

#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Response {
pub commits: Vec<(i64, String)>,
pub graphs: Vec<Series>,
}
}

pub mod bootstrap {
use collector::Bound;
use hashbrown::HashMap;
Expand Down
2 changes: 1 addition & 1 deletion site/src/request_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod status_page;
pub use bootstrap::handle_bootstrap;
pub use dashboard::handle_dashboard;
pub use github::handle_github;
pub use graph::{handle_graph, handle_graphs};
pub use graph::{handle_compile_detail, handle_graphs};
pub use next_artifact::handle_next_artifact;
pub use self_profile::{
handle_self_profile, handle_self_profile_processed_download, handle_self_profile_raw,
Expand Down
Loading