Skip to content

Commit 9dd752e

Browse files
committed
[Benchmarks] Add chart annotations
with information on each version change of important dependecies, like Compute Runtime and benchmarks repos
1 parent 57bdbe3 commit 9dd752e

File tree

3 files changed

+230
-23
lines changed

3 files changed

+230
-23
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Copyright (C) 2024-2025 Intel Corporation
2+
// Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
3+
// See LICENSE.TXT
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
6+
/**
7+
* Find version changes in data points to create annotations
8+
* @param {Array} points - Data points to analyze
9+
* @param {string} versionKey - Key to track version changes
10+
* @returns {Array} - List of change points
11+
*/
12+
function findVersionChanges(points, versionKey) {
13+
if (!points || points.length < 2) return [];
14+
15+
const changes = [];
16+
// Sort points by date
17+
const sortedPoints = [...points].sort((a, b) => a.x - b.x);
18+
let lastVersion = sortedPoints[0][versionKey];
19+
20+
for (let i = 1; i < sortedPoints.length; i++) {
21+
const currentPoint = sortedPoints[i];
22+
23+
const currentVersion = currentPoint[versionKey];
24+
if (currentVersion && currentVersion !== lastVersion) {
25+
changes.push({
26+
date: currentPoint.x,
27+
newVersion: currentVersion,
28+
});
29+
lastVersion = currentVersion;
30+
}
31+
}
32+
33+
return changes;
34+
}
35+
36+
/**
37+
* Add version change annotations to chart options
38+
* @param {Object} data - Chart data
39+
* @param {Object} options - Chart.js options object
40+
*/
41+
function addVersionChangeAnnotations(data, options) {
42+
const changeTrackers = [
43+
{
44+
// Benchmark repos updates
45+
versionKey: 'gitBenchHash',
46+
sources: [
47+
{
48+
name: "Compute Benchmarks",
49+
url: "https://github.com/intel/compute-benchmarks.git",
50+
color: {
51+
border: 'rgba(220, 53, 69, 0.8)',
52+
background: 'rgba(220, 53, 69, 0.9)'
53+
}
54+
},
55+
{
56+
name: "SYCL-Bench",
57+
url: "https://github.com/unisa-hpc/sycl-bench.git",
58+
color: {
59+
border: 'rgba(32, 156, 238, 0.8)',
60+
background: 'rgba(32, 156, 238, 0.9)'
61+
}
62+
},
63+
{
64+
name: "Velocity-Bench",
65+
url: "https://github.com/oneapi-src/Velocity-Bench/",
66+
color: {
67+
border: 'rgba(255, 153, 0, 0.8)',
68+
background: 'rgba(255, 153, 0, 0.9)'
69+
}
70+
}
71+
],
72+
pointsFilter: (points, url) => points.filter(p => p.gitBenchUrl === url),
73+
formatLabel: (sourceName, version) => `${sourceName}: ${version.substring(0, 7)}`
74+
},
75+
{
76+
// Compute Runtime updates
77+
versionKey: 'compute_runtime',
78+
sources: [
79+
{
80+
name: "Compute Runtime",
81+
url: "https://github.com/intel/compute-runtime.git",
82+
color: {
83+
border: 'rgba(40, 167, 69, 0.8)',
84+
background: 'rgba(40, 167, 69, 0.9)'
85+
}
86+
}
87+
],
88+
}
89+
];
90+
91+
changeTrackers.forEach(tracker => {
92+
tracker.sources.forEach((source) => {
93+
const changes = {};
94+
95+
// Find changes across all runs
96+
Object.values(data.runs).flatMap(runData =>
97+
findVersionChanges(
98+
tracker.pointsFilter ? tracker.pointsFilter(runData.data, source.url) : runData.data,
99+
tracker.versionKey
100+
)
101+
).forEach(change => {
102+
const changeKey = `${source.name}-${change.newVersion}`;
103+
if (!changes[changeKey] || change.date < changes[changeKey].date) {
104+
changes[changeKey] = change;
105+
}
106+
});
107+
108+
// Create annotation for each unique change
109+
Object.values(changes).forEach(change => {
110+
const annotationId = `${change.date}`;
111+
// If annotation at a given date already exists, update it
112+
if (options.plugins.annotation.annotations[annotationId]) {
113+
options.plugins.annotation.annotations[annotationId].label.content.push(`${tracker.formatLabel ?
114+
`tracker.formatLabel(source.name, change.newVersion)` :
115+
`${source.name}: ${change.newVersion}`}`);
116+
options.plugins.annotation.annotations[annotationId].borderColor = 'rgba(128, 128, 128, 0.8)';
117+
options.plugins.annotation.annotations[annotationId].borderWidth += 1;
118+
options.plugins.annotation.annotations[annotationId].label.backgroundColor = 'rgba(128, 128, 128, 0.9)';
119+
} else {
120+
options.plugins.annotation.annotations[annotationId] = {
121+
type: 'line',
122+
xMin: change.date,
123+
xMax: change.date,
124+
borderColor: source.color.border,
125+
borderWidth: 2,
126+
borderDash: [3, 3],
127+
label: {
128+
content: [ tracker.formatLabel ?
129+
tracker.formatLabel(source.name, change.newVersion) :
130+
`${source.name}: ${change.newVersion}` ],
131+
display: false,
132+
position: 'start',
133+
backgroundColor: source.color.background,
134+
z: 1,
135+
}
136+
}
137+
};
138+
});
139+
});
140+
});
141+
}
142+
143+
/**
144+
* Set up event listeners for annotation interactions
145+
* @param {Chart} chart - Chart.js instance
146+
* @param {CanvasRenderingContext2D} ctx - Canvas context
147+
* @param {Object} options - Chart.js options object
148+
*/
149+
function setupAnnotationListeners(chart, ctx, options) {
150+
// Add event listener for annotation clicks - display/hide label
151+
ctx.canvas.addEventListener('click', function(e) {
152+
const activeElements = chart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, false);
153+
154+
// If no data point is clicked, check if an annotation was clicked
155+
if (activeElements.length === 0) {
156+
const rect = chart.canvas.getBoundingClientRect();
157+
const x = e.clientX - rect.left;
158+
159+
// Check if click is near any annotation line
160+
const annotations = options.plugins.annotation.annotations;
161+
Object.values(annotations).some(annotation => {
162+
// Get the position of the annotation line
163+
const xPos = chart.scales.x.getPixelForValue(annotation.xMin);
164+
165+
// Display label if click is near the annotation line (within 5 pixels)
166+
if (Math.abs(x - xPos) < 5) {
167+
annotation.label.display = !annotation.label.display;
168+
chart.update();
169+
return true; // equivalent to break in a for loop
170+
}
171+
return false;
172+
});
173+
}
174+
});
175+
176+
// Add mouse move handler to change cursor when hovering over annotations
177+
ctx.canvas.addEventListener('mousemove', function(e) {
178+
const rect = chart.canvas.getBoundingClientRect();
179+
const x = e.clientX - rect.left;
180+
181+
// Check if mouse is near any annotation line
182+
const annotations = options.plugins.annotation.annotations;
183+
const isNearAnnotation = Object.values(annotations).some(annotation => {
184+
const xPos = chart.scales.x.getPixelForValue(annotation.xMin);
185+
186+
if (Math.abs(x - xPos) < 5) {
187+
return true;
188+
}
189+
return false;
190+
});
191+
192+
// Change cursor style based on proximity to annotation
193+
ctx.canvas.style.cursor = isNearAnnotation ? 'pointer' : '';
194+
});
195+
196+
// Reset cursor when mouse leaves the chart area
197+
ctx.canvas.addEventListener('mouseleave', function() {
198+
ctx.canvas.style.cursor = '';
199+
});
200+
}
201+
202+
// Export functions to make them available to other modules
203+
window.ChartAnnotations = {
204+
findVersionChanges,
205+
addVersionChangeAnnotations,
206+
setupAnnotationListeners
207+
};

devops/scripts/benchmarks/html/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
<title>Benchmark Results</title>
1313
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
1414
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
15+
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation"></script>
1516
<script src="data.js"></script>
1617
<script src="config.js"></script>
18+
<script src="chart-annotations.js"></script>
1719
<script src="scripts.js"></script>
1820
<link rel="stylesheet" href="styles.css">
1921
</head>

devops/scripts/benchmarks/html/scripts.js

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ function createChart(data, containerId, type) {
132132
`Stddev: ${point.stddev.toFixed(2)} ${data.unit}`,
133133
`Git Hash: ${point.gitHash}`,
134134
`Compute Runtime: ${point.compute_runtime}`,
135+
`Bench hash: ${point.gitBenchHash?.substring(0, 7)}`,
136+
`Bench URL: ${point.gitBenchUrl}`,
135137
];
136138
} else {
137139
return [`${context.dataset.label}:`,
@@ -140,7 +142,10 @@ function createChart(data, containerId, type) {
140142
}
141143
}
142144
}
143-
}
145+
},
146+
annotation: type === 'time' ? {
147+
annotations: {}
148+
} : undefined
144149
},
145150
scales: {
146151
y: {
@@ -158,7 +163,7 @@ function createChart(data, containerId, type) {
158163
if (type === 'time') {
159164
options.interaction = {
160165
mode: 'nearest',
161-
intersect: false
166+
intersect: true // Require to hover directly over a point
162167
};
163168
options.onClick = (event, elements) => {
164169
if (elements.length > 0) {
@@ -180,6 +185,11 @@ function createChart(data, containerId, type) {
180185
maxTicksLimit: 10
181186
}
182187
};
188+
189+
// Add dependencies version change annotations
190+
if (Object.keys(data.runs).length > 0) {
191+
ChartAnnotations.addVersionChangeAnnotations(data, options);
192+
}
183193
}
184194

185195
const chartConfig = {
@@ -202,29 +212,15 @@ function createChart(data, containerId, type) {
202212

203213
const chart = new Chart(ctx, chartConfig);
204214
chartInstances.set(containerId, chart);
215+
216+
// Add annotation interaction handlers for time-series charts
217+
if (type === 'time') {
218+
ChartAnnotations.setupAnnotationListeners(chart, ctx, options);
219+
}
220+
205221
return chart;
206222
}
207223

208-
function createTimeseriesDatasets(data) {
209-
return Object.entries(data.runs).map(([name, runData], index) => ({
210-
label: runData.runName, // Use run name for legend
211-
data: runData.points.map(p => ({
212-
seriesName: runData.runName, // Use run name for tooltips
213-
x: p.date,
214-
y: p.value,
215-
gitHash: p.git_hash,
216-
gitRepo: p.github_repo,
217-
stddev: p.stddev
218-
})),
219-
borderColor: colorPalette[index % colorPalette.length],
220-
backgroundColor: colorPalette[index % colorPalette.length],
221-
borderWidth: 1,
222-
pointRadius: 3,
223-
pointStyle: 'circle',
224-
pointHoverRadius: 5
225-
}));
226-
}
227-
228224
function updateCharts() {
229225
const filterRunData = (chart) => ({
230226
...chart,
@@ -815,7 +811,9 @@ function addRunDataPoint(group, run, result, comparison, name = null) {
815811
stddev: result.stddev,
816812
gitHash: run.git_hash,
817813
gitRepo: run.github_repo,
818-
compute_runtime: run.compute_runtime
814+
compute_runtime: run.compute_runtime,
815+
gitBenchUrl: result.git_url,
816+
gitBenchHash: result.git_hash,
819817
});
820818

821819
return group;

0 commit comments

Comments
 (0)