diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b0bc3d5..cd3aa306 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,4 +37,4 @@ jobs: with: python-version: ${{ matrix.python-version }} - run: pip install -r requirements.txt "django==${{ matrix.django }}.*" - - run: python manage.py test codespeed + - run: python manage.py test codespeed speed_python diff --git a/.gitignore b/.gitignore index c60b703f..8393f1e2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ build dist *.egg-info .venv*/ +speed_python/local_settings.py diff --git a/speed_python/README.md b/speed_python/README.md index eb0c916a..dbc817d7 100644 --- a/speed_python/README.md +++ b/speed_python/README.md @@ -130,3 +130,11 @@ Please, also refer to the [Django URL dispatcher docu] ### Codespeed settings The main config file is `settings.py`. There you configure everything related to your set up. + +### Home page trend +`HOME_GEOMEAN_SERIES` in `settings.py` lists the executable/environment pairs +plotted on the home page as a trend of the geometric mean of all benchmarks +over the last 30 days. Each line is normalised to its first run in the window, +so 0.95 reads as "5% faster than at the start of the window". The view lives +in `speed_python/views.py`, the chart in `static/js/home_geomean.js`, and the +tests run with `python manage.py test speed_python`. diff --git a/speed_python/settings.py b/speed_python/settings.py index 45ff6043..c4ceb46a 100644 --- a/speed_python/settings.py +++ b/speed_python/settings.py @@ -118,4 +118,16 @@ def process_exception(self, request, exception): # Codespeed settings that can be overwritten here. from codespeed.settings import * +HOME_GEOMEAN_SERIES = [ + {'executable': 'lto-pgo', + 'project': 'CPython', + 'branch': 'main', + 'environment': + 'speed-python'}, + {'executable': 'lto-pgo', + 'project': 'CPython', + 'branch': 'main', + 'environment': 'ampere-one'}, +] + from .local_settings import * diff --git a/speed_python/static/css/home.css b/speed_python/static/css/home.css new file mode 100644 index 00000000..47f42878 --- /dev/null +++ b/speed_python/static/css/home.css @@ -0,0 +1,19 @@ +/* speed.python.org home page: geometric mean trend */ + +div#presentation div#geomean, div#presentation div#geomean > div { display: block; } +div#geomean { + max-width: 900px; + margin: 1.5em auto 0; + text-align: center; +} +div#geomean-plot { width: 100%; height: 320px; } +div#geomean-legend { min-height: 3.2em; margin: 0.4em 0 0 4em; font-size: smaller; text-align: left; } +div#geomean p.plot-caption { text-align: center; } +/* The plot area starts ~56px in (y-axis label and ticks) and ends ~5px before + the right edge, so centre the heading, legend and caption on the plot area + rather than on the box */ +div#geomean h3, div#geomean p.plot-caption { padding-left: 51px; } +/* codespeed's generic div rule gives the legend's colour markers + vertical-align: middle, which lifts them off the label line */ +div#geomean-legend .dygraph-legend-line { vertical-align: baseline; margin-right: 0.25em; } +div#geomean p.plot-caption { font-size: 0.9em; color: var(--text-muted); } diff --git a/speed_python/static/js/home_geomean.js b/speed_python/static/js/home_geomean.js new file mode 100644 index 00000000..df67bca2 --- /dev/null +++ b/speed_python/static/js/home_geomean.js @@ -0,0 +1,100 @@ +/* Home page trend of the geometric mean of all benchmarks, one line per + * configured executable/environment pair (see HOME_GEOMEAN_SERIES). + * + * Reads its configuration from data attributes on #geomean-plot: + * data-url JSON endpoint (gethomegeomeandata) + * data-changes-url changes page, used when a point is clicked + */ +(function() { + var COLORS = ['#4e79a7', '#f28e2b', '#59a14f', '#e15759', '#76b7b2', + '#edc948', '#b07aa1', '#ff9da7', '#9c755f', '#bab0ac']; + + function parseDate(s) { + // "YYYY/MM/DD HH:MM:SS +0000" -> Date (UTC) + var m = /^(\d+)\/(\d+)\/(\d+) (\d+):(\d+):(\d+)/.exec(s); + return new Date(Date.UTC(+m[1], m[2] - 1, +m[3], +m[4], +m[5], +m[6])); + } + + function render(container, data) { + var series = (data.series || []).filter(function(s) { return s.points.length; }); + if (!series.length) { + container.innerHTML = '
No results in the last ' + + data.days + ' days.
'; + return; + } + + // Merge all series on a shared time axis; dygraphs wants one row per x. + var rowsByTime = {}; + var commits = {}; // label -> time -> {commitid, benchmarks} + series.forEach(function(s, i) { + commits[s.label] = {}; + s.points.forEach(function(p) { + var t = parseDate(p[0]).getTime(); + if (!rowsByTime[t]) { + rowsByTime[t] = new Array(series.length + 1); + rowsByTime[t][0] = new Date(t); + } + rowsByTime[t][i + 1] = p[1]; + commits[s.label][t] = {commitid: p[2], benchmarks: p[3]}; + }); + }); + var rows = Object.keys(rowsByTime).sort(function(a, b) { return a - b; }) + .map(function(t) { return rowsByTime[t]; }); + for (var r = 0; r < rows.length; r++) { + for (var c = 1; c < rows[r].length; c++) { + if (rows[r][c] === undefined) { rows[r][c] = null; } + } + } + + var labels = ['Date'].concat(series.map(function(s) { return s.label; })); + + new Dygraph(container, rows, { + labels: labels, + colors: COLORS.slice(0, series.length), + connectSeparatedPoints: true, + drawPoints: true, + pointSize: 2, + strokeWidth: 1.5, + highlightCircleSize: 4, + legend: 'always', + labelsDiv: document.getElementById('geomean-legend'), + labelsSeparateLines: true, + ylabel: 'Relative to ' + data.days + ' days ago', + axes: { + y: { axisLabelFormatter: function(v) { return v.toFixed(2); } } + }, + valueFormatter: function(v, opts, name, g, row) { + if (name === 'Date') { return new Date(v).toISOString().slice(0, 16).replace('T', ' '); } + if (v === null || v === undefined) { return ''; } + var t = g.getValue(row, 0); + var c = commits[name] && commits[name][t]; + return v.toFixed(3) + 'x' + (c ? ' (' + c.commitid.slice(0, 10) + ', ' + + c.benchmarks + ' benchmarks)' : ''); + }, + pointClickCallback: function(e, point) { + var c = commits[point.name] && commits[point.name][point.xval]; + var s = series.filter(function(x) { return x.label === point.name; })[0]; + if (!c || !s) { return; } + window.location = container.getAttribute('data-changes-url') + + '?rev=' + encodeURIComponent(c.commitid) + + '&exe=' + s.executable + '&env=' + s.environment; + } + }); + } + + document.addEventListener('DOMContentLoaded', function() { + var container = document.getElementById('geomean-plot'); + if (!container) { return; } + var xhr = new XMLHttpRequest(); + xhr.open('GET', container.getAttribute('data-url')); + xhr.onload = function() { + if (xhr.status !== 200) { + container.innerHTML = 'Error retrieving data
'; + return; + } + render(container, JSON.parse(xhr.responseText)); + }; + xhr.onerror = xhr.onload; + xhr.send(); + }); +})(); diff --git a/speed_python/templates/home.html b/speed_python/templates/home.html index 49871a40..053f17c8 100644 --- a/speed_python/templates/home.html +++ b/speed_python/templates/home.html @@ -1,4 +1,10 @@ {% extends "codespeed/base_site.html" %} +{% load static %} + +{% block extra_head %} + + +{% endblock %} {% block navigation %} {% endblock navigation %} @@ -24,12 +30,26 @@+ Geometric mean of all benchmark times, relative to the first run in the + window. Lower is faster. Each line is one executable on one machine; + click a point to open that commit's results. +
+