Skip to content

Commit efa8444

Browse files
authored
Merge pull request #1404 from rust-lang/compare-ux
Add summary table to compare page
2 parents adb015f + 0e8b789 commit efa8444

File tree

3 files changed

+201
-117
lines changed

3 files changed

+201
-117
lines changed

site/static/compare.html

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ <h2>Comparing <span id="stat-header">{{stat}}</span> between <span id="before">{
262262
</span>
263263
</span>
264264
</div>
265-
<input type="checkbox" v-model="filter.showNonRelevant" style="margin-left: 20px;" />
265+
<input type="checkbox" v-model="filter.nonRelevant" style="margin-left: 20px;" />
266266
</div>
267267
<div class="section">
268268
<div class="section-heading"><span>Display raw data</span>
@@ -274,53 +274,22 @@ <h2>Comparing <span id="stat-header">{{stat}}</span> between <span id="before">{
274274
</div>
275275
<input type="checkbox" v-model="showRawData" style="margin-left: 20px;" />
276276
</div>
277+
<button @click="resetFilter">Reset filters</button>
277278
</div>
278279
</fieldset>
279280
<p v-if="dataLoading && !data">Loading ...</p>
280281
<div v-if="data" id="content" style="margin-top: 15px">
281-
<div id="summary">
282-
<div style="display: flex; justify-content: end;">
282+
<div id="main-summary">
283+
<summary-table :cases="filteredSummary"></summary-table>
284+
<div style="position: absolute; right: 5px; top: 5px;">
283285
<span class="tooltip" style="margin-right: 1em;">?
284286
<span class="tooltiptext">
285-
The percents show arithmetic mean amongst regressions, amongst improvements
286-
and finally amongst all benchmarks in each category (all benchmarks or
287-
filtered benchmarks).
287+
The table shows summaries of regressions, improvements and all changes
288+
calculated from data that is currently visible (data that passes the
289+
active filters).
288290
</span>
289291
</span>
290292
</div>
291-
<div v-for="summaryPair in Object.entries(summary)" class="summary-container">
292-
<span style="font-weight: bold; margin-left: 5px; text-transform: capitalize;">{{
293-
summaryPair[0] }}:</span>
294-
<div class="summary-values">
295-
<span class="summary summary-wide positive">
296-
{{summaryPair[1].regressions.toString().padStart(3, "&nbsp;")}}
297-
<svg style="width:18px;height:18px" viewBox="0 0 24 24">
298-
<path
299-
d="M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z">
300-
</path>
301-
</svg>
302-
&nbsp;(+{{(summaryPair[1].regressions_avg).toFixed(2)}}%)
303-
</span>
304-
<span class="summary">
305-
{{summaryPair[1].unchanged.toString().padStart(3, "&nbsp;")}}
306-
<svg style="width:18px;height:18px" viewBox="0 0 24 24">
307-
<path d="M22,12L18,8V11H3V13H18V16L22,12Z"></path>
308-
</svg>
309-
</span>
310-
<span class="summary summary-wide negative">
311-
{{summaryPair[1].improvements.toString().padStart(3, "&nbsp;")}}
312-
<svg style="width:18px;height:18px" viewBox="0 0 24 24">
313-
<path
314-
d="M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z">
315-
</path>
316-
</svg>
317-
&nbsp;({{(summaryPair[1].improvements_avg).toFixed(2)}}%)
318-
</span>
319-
<span class="summary" v-bind:class="percentClass(summaryPair[1].average)">
320-
&nbsp;{{ signIfPositive(summaryPair[1].average) }}{{ (summaryPair[1].average).toFixed(2) }}%
321-
</span>
322-
</div>
323-
</div>
324293
</div>
325294
<div v-if="data.new_errors.length">
326295
<p><b>Newly broken benchmarks</b>:</p>

site/static/compare/script.js

Lines changed: 180 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ function findQueryParam(name) {
66
}
77
}
88

9+
function createDefaultFilter() {
10+
return {
11+
name: null,
12+
nonRelevant: false,
13+
profile: {
14+
check: true,
15+
debug: true,
16+
opt: true,
17+
doc: true
18+
},
19+
scenario: {
20+
full: true,
21+
incrFull: true,
22+
incrUnchanged: true,
23+
incrPatched: true
24+
},
25+
category: {
26+
primary: true,
27+
secondary: true
28+
}
29+
};
30+
}
31+
932
const app = Vue.createApp({
1033
mounted() {
1134
const app = this;
@@ -20,26 +43,7 @@ const app = Vue.createApp({
2043
},
2144
data() {
2245
return {
23-
filter: {
24-
name: null,
25-
showNonRelevant: false,
26-
profile: {
27-
check: true,
28-
debug: true,
29-
opt: true,
30-
doc: true
31-
},
32-
scenario: {
33-
full: true,
34-
incrFull: true,
35-
incrUnchanged: true,
36-
incrPatched: true
37-
},
38-
category: {
39-
primary: true,
40-
secondary: true
41-
}
42-
},
46+
filter: createDefaultFilter(),
4347
showRawData: false,
4448
data: null,
4549
dataLoading: false
@@ -103,7 +107,7 @@ const app = Vue.createApp({
103107
let nameFilter = filter.name && filter.name.trim();
104108
nameFilter = !nameFilter || name.includes(nameFilter);
105109

106-
const relevanceFilter = filter.showNonRelevant ? true : testCase.isRelevant;
110+
const relevanceFilter = filter.nonRelevant ? true : testCase.isRelevant;
107111

108112
return (
109113
profileFilter(testCase.profile) &&
@@ -219,56 +223,9 @@ const app = Vue.createApp({
219223
stat() {
220224
return findQueryParam("stat") || "instructions:u";
221225
},
222-
summary() {
223-
// Create object with each test case that is not filtered out as a key
224-
const filtered = this.testCases.reduce((sum, next) => {
225-
sum[testCaseString(next)] = true;
226-
return sum;
227-
}, {});
228-
const newCount = {
229-
regressions: 0,
230-
regressions_avg: 0,
231-
improvements: 0,
232-
improvements_avg: 0,
233-
unchanged: 0,
234-
average: 0
235-
};
236-
237-
const addDatum = (result, datum, percent) => {
238-
if (percent > 0 && datum.is_relevant) {
239-
result.regressions += 1;
240-
result.regressions_avg += percent;
241-
} else if (percent < 0 && datum.is_relevant) {
242-
result.improvements += 1;
243-
result.improvements_avg += percent;
244-
} else {
245-
result.unchanged += 1;
246-
}
247-
result.average += percent;
248-
};
249-
250-
let result = {all: {...newCount}, filtered: {...newCount}}
251-
for (let d of this.data.comparisons) {
252-
const testCase = testCaseString(d)
253-
const datumA = d.statistics[0];
254-
const datumB = d.statistics[1];
255-
let percent = 100 * ((datumB - datumA) / datumA);
256-
addDatum(result.all, d, percent);
257-
if (filtered[testCase]) {
258-
addDatum(result.filtered, d, percent);
259-
}
260-
}
261-
262-
const computeAvg = (result) => {
263-
result.improvements_avg /= Math.max(result.improvements, 1);
264-
result.regressions_avg /= Math.max(result.regressions, 1);
265-
result.average /= Math.max(result.regressions + result.improvements + result.unchanged, 1);
266-
};
267-
computeAvg(result.all);
268-
computeAvg(result.filtered);
269-
270-
return result;
271-
226+
// Returns summary of currently filtered data
227+
filteredSummary() {
228+
return computeSummary(this.testCases);
272229
},
273230
benchmarkMap() {
274231
if (!this.data) return {};
@@ -289,12 +246,6 @@ const app = Vue.createApp({
289246
prLink(pr) {
290247
return `https://github.com/rust-lang/rust/pull/${pr}`;
291248
},
292-
signIfPositive(pct) {
293-
if (pct >= 0) {
294-
return "+";
295-
}
296-
return "";
297-
},
298249
diffClass(diff) {
299250
let klass = "";
300251
if (diff > 1) {
@@ -331,6 +282,9 @@ const app = Vue.createApp({
331282

332283
return createUrlFromParams(createSearchParamsForMetric(metric, start, end));
333284
},
285+
resetFilter() {
286+
this.filter = createDefaultFilter();
287+
}
334288
},
335289
});
336290

@@ -433,6 +387,86 @@ app.component('test-cases-table', {
433387
</div>
434388
`
435389
});
390+
391+
const SummaryPercentValue = {
392+
props: {
393+
value: Number,
394+
padWidth: {
395+
type: Number,
396+
default: null
397+
}
398+
},
399+
template: `
400+
<span><span v-html="padSpaces" />{{ formattedValue }}%</span>
401+
`,
402+
computed: {
403+
formattedValue() {
404+
return `${this.signIfPositive(this.value)}${this.value.toFixed(2)}`;
405+
},
406+
padSpaces() {
407+
let value = this.formattedValue;
408+
if (value.length < this.padWidth) {
409+
return "&nbsp;".repeat(this.padWidth - value.length);
410+
}
411+
return "";
412+
}
413+
}
414+
};
415+
const SummaryRange = {
416+
props: {
417+
range: Array,
418+
},
419+
template: `
420+
<div v-if="range.length > 0">
421+
[<SummaryPercentValue :value="range[0]" :padWidth="6" />, <SummaryPercentValue :value="range[1]" :padWidth="6" />]
422+
</div>
423+
<div v-else>-</div>
424+
`, components: {SummaryPercentValue}
425+
};
426+
const SummaryCount = {
427+
props: {
428+
cases: Number,
429+
benchmarks: Number
430+
},
431+
template: `
432+
<span :title="cases + ' test case(s), ' + benchmarks + ' unique benchmark(s)'">{{ cases }} ({{ benchmarks }})</span>
433+
`
434+
};
435+
436+
app.component('summary-table', {
437+
props: ['cases'],
438+
template: `
439+
<table class="summary-table">
440+
<thead>
441+
<th><!-- icon --></th>
442+
<th>Range</th>
443+
<th>Mean</th>
444+
<th>Count</th>
445+
</thead>
446+
<tbody>
447+
<tr class="positive">
448+
<td title="Regresions">❌</td>
449+
<td><SummaryRange :range="cases.regressions.range" /></td>
450+
<td><SummaryPercentValue :value="cases.regressions.average" /></td>
451+
<td><SummaryCount :cases="cases.regressions.count" :benchmarks="cases.regressions.benchmarks" /></td>
452+
</tr>
453+
<tr class="negative">
454+
<td title="Improvements">✅</td>
455+
<td><SummaryRange :range="cases.improvements.range" /></td>
456+
<td><SummaryPercentValue :value="cases.improvements.average" /></td>
457+
<td><SummaryCount :cases="cases.improvements.count" :benchmarks="cases.improvements.benchmarks" /></td>
458+
</tr>
459+
<tr>
460+
<td title="All changes">❌,✅</td>
461+
<td><SummaryRange :range="cases.all.range" /></td>
462+
<td><SummaryPercentValue :value="cases.all.average" /></td>
463+
<td><SummaryCount :cases="cases.all.count" :benchmarks="cases.all.benchmarks" /></td>
464+
</tr>
465+
</tbody>
466+
</table>
467+
`,
468+
components: {SummaryRange, SummaryPercentValue, SummaryCount}
469+
});
436470
app.mixin({
437471
methods: {
438472
percentClass(pct) {
@@ -448,6 +482,12 @@ app.mixin({
448482
}
449483
return klass;
450484
},
485+
signIfPositive(pct) {
486+
if (pct >= 0) {
487+
return "+";
488+
}
489+
return "";
490+
},
451491
}
452492
});
453493

@@ -470,6 +510,69 @@ function testCaseString(testCase) {
470510
return testCase.benchmark + "-" + testCase.profile + "-" + testCase.scenario;
471511
}
472512

513+
/**
514+
* Computes summaries of improvements, regressions and all changes from the given `comparisons`.
515+
* Returns a dictionary {improvements, regressions, all}.
516+
*/
517+
function computeSummary(testCases) {
518+
const regressions = {
519+
values: [],
520+
benchmarks: new Set(),
521+
};
522+
const improvements = {
523+
values: [],
524+
benchmarks: new Set(),
525+
};
526+
const all = {
527+
values: [],
528+
benchmarks: new Set(),
529+
};
530+
531+
const handleTestCase = (items, testCase) => {
532+
items.benchmarks.add(testCase.benchmark);
533+
items.values.push(testCase.percent);
534+
};
535+
536+
for (const testCase of testCases) {
537+
if (testCase.percent > 0) {
538+
handleTestCase(regressions, testCase);
539+
} else if (testCase.percent < 0) {
540+
handleTestCase(improvements, testCase);
541+
}
542+
handleTestCase(all, testCase);
543+
}
544+
545+
const computeSummary = (data) => {
546+
const values = data.values;
547+
const benchmarks = data.benchmarks;
548+
549+
const count = values.length;
550+
let range = [];
551+
if (count > 0) {
552+
range = [
553+
Math.min.apply(null, values),
554+
Math.max.apply(null, values),
555+
];
556+
}
557+
558+
const sum = values.reduce((acc, item) => acc + item, 0);
559+
const average = sum / Math.max(count, 1);
560+
561+
return {
562+
count,
563+
benchmarks: benchmarks.size,
564+
average,
565+
range,
566+
}
567+
};
568+
569+
return {
570+
improvements: computeSummary(improvements),
571+
regressions: computeSummary(regressions),
572+
all: computeSummary(all)
573+
};
574+
}
575+
473576
function unique(arr) {
474577
return arr.filter((value, idx) => arr.indexOf(value) == idx);
475578
}

0 commit comments

Comments
 (0)