Skip to content

Commit b1447d6

Browse files
authored
Merge pull request #86 from mattip/last-revision
ensure last revision is valid
2 parents 3bc709b + 2895914 commit b1447d6

4 files changed

Lines changed: 57 additions & 13 deletions

File tree

‎codespeed/views.py‎

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434

3535
logger = logging.getLogger(__name__)
3636

37+
# Fraction of the plottable benchmark set a revision must have legacy results
38+
# for before gethistoricaldata() will render it as the 'latest' column. Runs
39+
# glitch and drop individual benchmarks, so this is deliberately below 1.0.
40+
LATEST_MIN_COVERAGE = 0.8
41+
3742

3843
def no_environment_error(request):
3944
admin_url = reverse('admin:codespeed_environment_changelist')
@@ -190,16 +195,33 @@ def gethistoricaldata(request):
190195
project=default_exe.project)
191196
revs = Revision.objects.filter(
192197
branch=default_branch).order_by('-date')[:100]
193-
default_lastrev = None
198+
# The benchmarks the plots can actually use: those the baseline and every
199+
# tagged revision all have legacy results for. A benchmark missing from any
200+
# of them is dropped client side, so it cannot count towards coverage.
201+
plottable = {res.benchmark.name for res in baseline_results[0][1]}
202+
for tag in data['tagged_revs']:
203+
plottable &= {res.benchmark.name for res in default_results[tag]}
204+
required = len(plottable) * LATEST_MIN_COVERAGE
205+
206+
# Accept a revision as 'latest' only once its legacy run has covered enough
207+
# of that set, otherwise fall back to the previous one that has.
194208
for rev in revs:
195-
default_lastrev = rev
196-
if default_lastrev.results.filter(executable=default_exe, environment=env):
197-
break
198-
default_lastrev = None
199-
if default_lastrev is not None:
200-
default_results['latest'] = Result.objects.filter(
201-
executable=default_exe, revision=default_lastrev, environment=env,
209+
latest_results = Result.objects.filter(
210+
executable=default_exe, revision=rev, environment=env,
202211
benchmark__source='legacy')
212+
covered = plottable & {res.benchmark.name for res in latest_results}
213+
if covered and len(covered) >= required:
214+
default_results['latest'] = latest_results
215+
break
216+
logger.info(
217+
"skipping '%s' as 'latest': %d of %d plottable benchmarks have "
218+
"legacy results for '%s'" % (
219+
str(rev), len(covered), len(plottable), str(env)))
220+
else:
221+
logger.error(
222+
"no revision covers %.0f%% of the %d plottable benchmarks for "
223+
"'%s' '%s'" % (LATEST_MIN_COVERAGE * 100, len(plottable),
224+
str(default_exe), str(env)))
203225

204226
# Collect data
205227
benchmarks = []

‎speed_pypy/settings.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
SHOW_HISTORICAL = True
9292
DEF_BASELINES = [
9393
{'executable': 'cpython', 'revision': '3.11.15'},
94+
{'executable': 'cpython', 'revision': '3.12.13'},
9495
]
9596
DEF_EXECUTABLES = [
9697
{'name': 'pypy3.12-jit-64', 'project': 'PyPy3.12'},

‎speed_pypy/templates/embed_comparison.html‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,21 @@
4848
tagged_data[i].push(data['results'][benchname][rev] / data['results'][benchname][data['baseline']]);
4949
}
5050
if (!add_to_tagged_data) { continue; }
51-
benchmarks.push(benchname);
5251
var rel = data['results'][benchname]['latest'] / data['results'][benchname][data['baseline']];
52+
// Drop a benchmark the latest run is missing before pushing its
53+
// label, so the bars stay aligned with the x axis ticks.
54+
// (!(rel > 0) also catches the NaN from a missing value.)
55+
if (!(rel > 0)) { continue; }
56+
benchmarks.push(benchname);
5357
latestValues.push(rel);
5458
baselineValues.push(1.0);
5559
}
5660

61+
if (benchmarks.length === 0) {
62+
wrap.innerHTML = 'No complete benchmark run available yet';
63+
return;
64+
}
65+
5766
var canvas1 = document.createElement('canvas');
5867
wrap.appendChild(canvas1);
5968
new Chart(canvas1, {

‎speed_pypy/templates/home.html‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,25 @@ <h3>How has PyPy performance evolved over time?</h3>
9494
}
9595
if (!add_to_tagged_data) { continue; }
9696
var rel = data['results'][benchname]['latest'] / data['results'][benchname][data['baseline']];
97+
// Only count what we multiply. A benchmark the latest run is
98+
// missing would otherwise inflate the root below and drag the
99+
// geometric mean towards 1.0. (!(rel > 0) also catches the NaN
100+
// from a missing value.)
101+
if (!(rel > 0)) { continue; }
97102
latestValues.push(rel);
98-
if (rel > 0 && !isNaN(rel)) { trunk_geomean *= rel; }
103+
trunk_geomean *= rel;
99104
}
100105

101-
trunk_geomean = Math.pow(trunk_geomean, 1 / latestValues.length);
102-
$('#geomean').html(trunk_geomean.toFixed(2));
103-
$('#geofaster').html((1 / trunk_geomean).toFixed(1));
106+
if (latestValues.length === 0) {
107+
// No usable latest run: report nothing rather than a bogus 1.0x.
108+
trunk_geomean = NaN;
109+
$('#geomean').html('n/a');
110+
$('#geofaster').html('n/a');
111+
} else {
112+
trunk_geomean = Math.pow(trunk_geomean, 1 / latestValues.length);
113+
$('#geomean').html(trunk_geomean.toFixed(2));
114+
$('#geofaster').html((1 / trunk_geomean).toFixed(1));
115+
}
104116

105117
// Plot 1 (per-benchmark normalized comparison) is rendered by the
106118
// embed_comparison page shown in the iframe above.

0 commit comments

Comments
 (0)