Skip to content

Commit 1f7dec2

Browse files
authored
Merge pull request #73 from mattip/simulate
Add title to comparison graph
2 parents 58fb9f1 + a37dc8d commit 1f7dec2

5 files changed

Lines changed: 201 additions & 60 deletions

File tree

codespeed/static/js/comparison.js

Lines changed: 131 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ var COLORS = [
1616
];
1717
function getColor(i) { return COLORS[i % COLORS.length]; }
1818

19+
function getExeLabel(key) {
20+
return $("label[for='exe_" + key.replace(/:/g, '\\:') + "']").text().trim();
21+
}
22+
1923
function getConfiguration() {
2024
return {
2125
exe: readCheckbox("input[name='executables']:checked"),
@@ -27,13 +31,94 @@ function getConfiguration() {
2731
};
2832
}
2933

34+
function updateGraphTitle(exes, enviros, bens, baseline, chart, chartTitles) {
35+
var $title = $("#graph-title");
36+
37+
if (enviros.length === 1 && exes.length === 2 && baseline !== "none" &&
38+
chart !== "stacked bars" && compdata) {
39+
40+
var baselineExe = baseline, baselineEnv = null;
41+
if (baseline.indexOf('@') !== -1) {
42+
var bparts = baseline.split('@');
43+
baselineExe = bparts[0];
44+
baselineEnv = bparts[1];
45+
}
46+
47+
var otherExes = exes.filter(function(e) { return e !== baselineExe; });
48+
if (otherExes.length === 1) {
49+
var otherExe = otherExes[0];
50+
var envId = enviros[0];
51+
var envForBase = baselineEnv !== null ? baselineEnv : envId;
52+
53+
var product = 1, count = 0;
54+
for (var b = 0; b < bens.length; b++) {
55+
var val = compdata[otherExe] && compdata[otherExe][envId]
56+
? compdata[otherExe][envId][bens[b]]
57+
: null;
58+
var baseval = compdata[baselineExe] && compdata[baselineExe][envForBase]
59+
? compdata[baselineExe][envForBase][bens[b]]
60+
: null;
61+
if (val !== null && baseval !== null && baseval !== 0 && val > 0) {
62+
product *= val / baseval;
63+
count++;
64+
}
65+
}
66+
67+
if (count > 0) {
68+
var geomean = Math.pow(product, 1 / count);
69+
70+
var lessCount = 0, moreCount = 0;
71+
var benSet = {};
72+
for (var b = 0; b < bens.length; b++) { benSet[bens[b]] = true; }
73+
for (var u in bench_units) {
74+
var unitBens = bench_units[u][0];
75+
var unitLess = bench_units[u][1].indexOf("less") !== -1;
76+
for (var ub = 0; ub < unitBens.length; ub++) {
77+
if (benSet[unitBens[ub]]) {
78+
if (unitLess) { lessCount++; } else { moreCount++; }
79+
}
80+
}
81+
}
82+
83+
var otherLabel = getExeLabel(otherExe);
84+
var baselineLabel = getExeLabel(baselineExe);
85+
if (baselineEnv !== null) {
86+
baselineLabel += ' @ ' + $("label[for='env_" + baselineEnv + "']").text().trim();
87+
}
88+
89+
var suffix;
90+
if (moreCount === 0 && lessCount > 0) {
91+
suffix = geomean < 1
92+
? ' or <strong>' + (1 / geomean).toFixed(1) + '&times;</strong> faster'
93+
: ' or <strong>' + geomean.toFixed(1) + '&times;</strong> slower';
94+
} else if (lessCount === 0 && moreCount > 0) {
95+
suffix = geomean > 1
96+
? ' or <strong>' + geomean.toFixed(1) + '&times;</strong> faster'
97+
: ' or <strong>' + (1 / geomean).toFixed(1) + '&times;</strong> slower';
98+
} else {
99+
suffix = ' relative to baseline';
100+
}
101+
102+
$title.html('The geometric average of ' + count + ' benchmarks for <strong>' +
103+
otherLabel + '</strong> is <strong>' + geomean.toFixed(2) + '</strong>' +
104+
suffix + ' than the baseline <strong>' + baselineLabel + '</strong>');
105+
return;
106+
}
107+
}
108+
}
109+
110+
// Fall back to the chart title(s)
111+
$title.text(chartTitles.join(' / '));
112+
}
113+
30114
function refreshContent() {
31115
var conf = getConfiguration(),
32116
exes = conf.exe.split(","),
33117
bens = conf.ben.split(","),
34118
enviros = conf.env.split(","),
35119
msg = "";
36120

121+
$("#graph-title").html("");
37122
var h = $("#plotwrapper").height();//get height for error message
38123
if (exes[0] === "") {
39124
$("#plotwrapper").html('<p class="warning">No executables selected</p>');
@@ -73,6 +158,7 @@ function refreshContent() {
73158
$("#plotwrapper").fadeOut("fast", function() {
74159
$(this).html(msg).show();
75160
var plotcounter = 1;
161+
var chartTitles = [];
76162
for (var unit in bench_units) {
77163
var benchmarks = [];
78164
for (var ben in bens) {
@@ -85,8 +171,9 @@ function refreshContent() {
85171
var plotid = "plot" + plotcounter;
86172
$("#plotwrapper").append('<div class="compplot-wrap"><canvas id="' + plotid + '"></canvas></div>');
87173
plotcounter++;
88-
renderComparisonPlot(plotid, unit, benchmarks, exes, enviros, conf.bas, conf.chart, conf.hor);
174+
chartTitles.push(renderComparisonPlot(plotid, unit, benchmarks, exes, enviros, conf.bas, conf.chart, conf.hor));
89175
}
176+
updateGraphTitle(exes, enviros, bens, conf.bas, conf.chart, chartTitles);
90177
});
91178
}
92179

@@ -114,7 +201,7 @@ function updateBaselineDropdown() {
114201
if (multiEnv) {
115202
enviros.forEach(function(envId) {
116203
var envName = $("label[for='env_" + envId + "']").text().trim();
117-
$baseline.append($('<option>').val(key + ':' + envId).text(name + ' @ ' + envName));
204+
$baseline.append($('<option>').val(key + '@' + envId).text(name + ' @ ' + envName));
118205
});
119206
} else {
120207
$baseline.append($('<option>').val(key).text(name));
@@ -144,18 +231,18 @@ function loadData() {
144231
}
145232

146233
function renderComparisonPlot(plotid, unit, benchmarks, exes, enviros, baseline, chart, horizontal) {
147-
// baseline may be "exe_key" or "exe_key:env_id" (for cross-env normalization)
234+
// baseline may be "exe_key" or "exe_key@env_id" (for cross-env normalization)
148235
if (!baseline) { baseline = "none"; }
149236
var baselineExe = baseline, baselineEnv = null;
150-
if (baseline !== "none" && baseline.indexOf(':') !== -1) {
151-
var bparts = baseline.split(':');
237+
if (baseline !== "none" && baseline.indexOf('@') !== -1) {
238+
var bparts = baseline.split('@');
152239
baselineExe = bparts[0];
153240
baselineEnv = bparts[1];
154241
}
155242

156243
var baselineLabel = "";
157244
if (baseline !== "none") {
158-
baselineLabel = $("label[for='exe_" + baselineExe + "']").text().trim();
245+
baselineLabel = getExeLabel(baselineExe);
159246
if (baselineEnv !== null) {
160247
baselineLabel += ' @ ' + $("label[for='env_" + baselineEnv + "']").text().trim();
161248
}
@@ -187,7 +274,7 @@ function renderComparisonPlot(plotid, unit, benchmarks, exes, enviros, baseline,
187274
}
188275
for (var i = 0; i < exes.length; i++) {
189276
for (var j = 0; j < enviros.length; j++) {
190-
var exeLabel = $("label[for='exe_" + exes[i] + "']").text().trim();
277+
var exeLabel = getExeLabel(exes[i]);
191278
if (chart === "relative bars" && exes[i] === baselineExe &&
192279
(baselineEnv === null || baselineEnv === enviros[j])) { continue; }
193280
var data = [];
@@ -218,7 +305,7 @@ function renderComparisonPlot(plotid, unit, benchmarks, exes, enviros, baseline,
218305
// Labels = exe@env names
219306
for (var i = 0; i < exes.length; i++) {
220307
for (var j = 0; j < enviros.length; j++) {
221-
var exeLabel = $("label[for='exe_" + exes[i] + "']").text().trim();
308+
var exeLabel = getExeLabel(exes[i]);
222309
labels.push(exeLabel + (enviros.length > 1 ? " @ " + $("label[for='env_" + enviros[j] + "']").text().trim() : ""));
223310
}
224311
}
@@ -286,7 +373,7 @@ function renderComparisonPlot(plotid, unit, benchmarks, exes, enviros, baseline,
286373
responsive: true,
287374
maintainAspectRatio: false,
288375
plugins: {
289-
title: {display: true, text: title, font: {size: 15}},
376+
title: {display: false},
290377
tooltip: {position: 'cursor'},
291378
legend: {
292379
position: 'right',
@@ -318,6 +405,7 @@ function renderComparisonPlot(plotid, unit, benchmarks, exes, enviros, baseline,
318405
}
319406
});
320407
chartInstances.push(instance);
408+
return title;
321409
}
322410

323411
function init(defaults) {
@@ -371,7 +459,39 @@ function init(defaults) {
371459
loadData();
372460

373461
$("#permalink").click(function() {
374-
window.location = "?" + $.param(getConfiguration());
462+
var conf = getConfiguration();
463+
var $all = $("input[name='benchmarks']");
464+
var $checked = $("input[name='benchmarks']:checked");
465+
if ($all.length > 0 && $all.length === $checked.length) {
466+
conf.ben = 'all';
467+
} else {
468+
// Tally checked vs total per source
469+
var sources = {};
470+
$all.each(function() {
471+
var src = $(this).data('source');
472+
if (!sources[src]) { sources[src] = {total: 0, checked: 0}; }
473+
sources[src].total++;
474+
if ($(this).is(':checked')) { sources[src].checked++; }
475+
});
476+
var full = Object.keys(sources).filter(function(s) {
477+
return sources[s].checked === sources[s].total;
478+
});
479+
var empty = Object.keys(sources).filter(function(s) {
480+
return sources[s].checked === 0;
481+
});
482+
// Use source aliases only when every source is either fully selected or fully empty
483+
if (full.length + empty.length === Object.keys(sources).length && full.length > 0) {
484+
conf.ben = full.join(',');
485+
}
486+
}
487+
var qs = Object.keys(conf).map(function(k) {
488+
return k + '=' + encodeURIComponent(String(conf[k]))
489+
.replace(/%2C/gi, ',')
490+
.replace(/%3A/gi, ':')
491+
.replace(/%40/gi, '@')
492+
.replace(/%20/g, '+');
493+
}).join('&');
494+
window.location = '?' + qs;
375495
});
376496

377497
$("#exportcsv").click(function(e) {
@@ -386,7 +506,7 @@ function init(defaults) {
386506
var header = ["benchmark"];
387507
for (var i = 0; i < exes.length; i++) {
388508
for (var j = 0; j < enviros.length; j++) {
389-
var exeLabel = $("label[for='exe_" + exes[i] + "']").text().trim();
509+
var exeLabel = getExeLabel(exes[i]);
390510
var envLabel = $("label[for='env_" + enviros[j] + "']").text().trim();
391511
header.push(enviros.length > 1 ? exeLabel + "@" + envLabel : exeLabel);
392512
}

codespeed/templates/codespeed/comparison.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@
5151
{% for bench in benchlist|dictsort:"name" %}
5252
<li title="{{ bench.description }}">
5353
{% if bench in checkedbenchmarks %}
54-
<input id="benchmark_{{ bench.id }}" type="checkbox" name="benchmarks" value="{{ bench.id }}" checked />
54+
<input id="benchmark_{{ bench.id }}" type="checkbox" name="benchmarks" value="{{ bench.id }}" data-source="{{ bench.source }}" checked />
5555
{% else %}
56-
<input id="benchmark_{{ bench.id }}" type="checkbox" name="benchmarks" value="{{ bench.id }}" />
56+
<input id="benchmark_{{ bench.id }}" type="checkbox" name="benchmarks" value="{{ bench.id }}" data-source="{{ bench.source }}" />
5757
{% endif %}
5858
<label for="benchmark_{{ bench.id }}">{{ bench }}</label>
5959
</li>{% endfor %}
@@ -80,6 +80,7 @@
8080
<a id="permalink" href="#">Permalink</a>
8181
<a id="exportcsv" href="#">Export CSV</a>
8282
</div>
83+
<div id="graph-title" style="text-align:center; padding:8px 0; font-size:15px;"></div>
8384
<div id="content" class="clearfix">
8485
<div id="plotwrapper"></div>
8586
</div>

codespeed/tests/test_views_data.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -84,29 +84,29 @@ def test_get_comparisonexes_master_default_branch(self):
8484
self.assertEqual(executables[self.project][0]['revision'],
8585
self.revision_1_master)
8686
self.assertEqual(executables[self.project][0]['key'],
87-
'1+L+master')
87+
'1:L:master')
8888
self.assertEqual(executables[self.project][0]['name'],
8989
'TestExecutable1 latest')
9090
self.assertEqual(executables[self.project][0]['revision'],
9191
self.revision_1_master)
9292

9393
self.assertEqual(executables[self.project][1]['key'],
94-
'2+L+master')
94+
'2:L:master')
9595
self.assertEqual(executables[self.project][1]['name'],
9696
'TestExecutable2 latest')
9797

9898
self.assertEqual(executables[self.project][2]['key'],
99-
'1+L+custom')
99+
'1:L:custom')
100100
self.assertEqual(executables[self.project][2]['name'],
101101
'TestExecutable1 latest in branch \'custom\'')
102102

103103
self.assertEqual(executables[self.project][3]['key'],
104-
'2+L+custom')
104+
'2:L:custom')
105105
self.assertEqual(executables[self.project][3]['name'],
106106
'TestExecutable2 latest in branch \'custom\'')
107107

108-
self.assertEqual(exe_keys[0], '1+L+master')
109-
self.assertEqual(exe_keys[1], '2+L+master')
108+
self.assertEqual(exe_keys[0], '1:L:master')
109+
self.assertEqual(exe_keys[1], '2:L:master')
110110

111111
def test_get_comparisonexes_custom_default_branch(self):
112112
# Custom default branch is used
@@ -123,31 +123,31 @@ def test_get_comparisonexes_custom_default_branch(self):
123123
self.assertEqual(executables[self.project][0]['revision'],
124124
self.revision_1_master)
125125
self.assertEqual(executables[self.project][0]['key'],
126-
'1+L+master')
126+
'1:L:master')
127127
self.assertEqual(executables[self.project][0]['name'],
128128
'TestExecutable1 latest in branch \'master\'')
129129
self.assertEqual(executables[self.project][0]['revision'],
130130
self.revision_1_master)
131131

132132
self.assertEqual(executables[self.project][1]['key'],
133-
'2+L+master')
133+
'2:L:master')
134134
self.assertEqual(executables[self.project][1]['name'],
135135
'TestExecutable2 latest in branch \'master\'')
136136

137137
self.assertEqual(executables[self.project][2]['key'],
138-
'1+L+custom')
138+
'1:L:custom')
139139
self.assertEqual(executables[self.project][2]['name'],
140140
'TestExecutable1 latest')
141141

142142
self.assertEqual(executables[self.project][3]['key'],
143-
'2+L+custom')
143+
'2:L:custom')
144144
self.assertEqual(executables[self.project][3]['name'],
145145
'TestExecutable2 latest')
146146

147-
self.assertEqual(exe_keys[0], '1+L+master')
148-
self.assertEqual(exe_keys[1], '2+L+master')
149-
self.assertEqual(exe_keys[2], '1+L+custom')
150-
self.assertEqual(exe_keys[3], '2+L+custom')
147+
self.assertEqual(exe_keys[0], '1:L:master')
148+
self.assertEqual(exe_keys[1], '2:L:master')
149+
self.assertEqual(exe_keys[2], '1:L:custom')
150+
self.assertEqual(exe_keys[3], '2:L:custom')
151151

152152
def test_get_comparisonexes_branch_filtering(self):
153153
# branch1 and branch3 have display_on_comparison_page flag set to False
@@ -169,12 +169,12 @@ def test_get_comparisonexes_branch_filtering(self):
169169
self.assertEqual(len(exe_keys), 6)
170170

171171
expected_exe_keys = [
172-
'1+L+master',
173-
'2+L+master',
174-
'1+L+custom',
175-
'2+L+custom',
176-
'1+L+branch2',
177-
'2+L+branch2'
172+
'1:L:master',
173+
'2:L:master',
174+
'1:L:custom',
175+
'2:L:custom',
176+
'1:L:branch2',
177+
'2:L:branch2'
178178
]
179179
self.assertEqual(exe_keys, expected_exe_keys)
180180

0 commit comments

Comments
 (0)