From aed576043b35484bc4ac035fbf35a75f4ab94c07 Mon Sep 17 00:00:00 2001 From: mattip Date: Tue, 26 May 2026 15:33:32 +0300 Subject: [PATCH 1/2] show which revisions have results in changes page --- codespeed/static/js/changes.js | 25 +++++++++++++++++++--- codespeed/templates/codespeed/changes.html | 5 +++-- codespeed/views.py | 11 ++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/codespeed/static/js/changes.js b/codespeed/static/js/changes.js index 63a2d870..21e5895a 100644 --- a/codespeed/static/js/changes.js +++ b/codespeed/static/js/changes.js @@ -3,7 +3,7 @@ var Changes = (function(window){ // Localize globals var TIMELINE_URL = window.TIMELINE_URL, getLoadText = window.getLoadText; -var currentproject, changethres, trendthres, projectmatrix, revisionboxes = {}; +var currentproject, changethres, trendthres, projectmatrix, revisionboxes = {}, revisiondata = {}, envhasresults = {}; function getConfiguration(revision) { return { @@ -82,6 +82,19 @@ function updateTable() { }); } +function updateRevisionMarkers(env_id) { + var has = {}; + $.each(envhasresults[env_id] || [], function(_, commitid) { has[commitid] = true; }); + var current = $("#revision").val(); + var options = ""; + $.each(revisiondata[currentproject] || [], function(_, r) { + var marker = has[r[1]] ? '● ' : '○ '; + options += ""; + }); + $("#revision").html(options); + $("#revision").val(current); +} + function refreshContent() { refreshContentTable($("#revision option:selected").val()); } @@ -110,8 +123,8 @@ function changeRevisions() { selected_project = projectmatrix[executable]; if (selected_project !== currentproject) { - $("#revision").html(revisionboxes[selected_project]); currentproject = selected_project; + updateRevisionMarkers($("input[name='environment']:checked").val()); //Give visual cue that the select box has changed var bgc = $("#revision").parent().parent().css("backgroundColor"); @@ -132,8 +145,10 @@ function config(c) { function init(defaults) { currentproject = defaults.project; projectmatrix = defaults.projectmatrix; + envhasresults = defaults.envhasresults || {}; $.each(defaults.revisionlists, function(project, revs) { + revisiondata[project] = revs; var options = ""; $.each(revs, function(index, r) { options += ""; @@ -148,10 +163,14 @@ function init(defaults) { $("input[name='executable']").change(changeRevisions); $("#env" + defaults.environment).prop('checked', true); - $("input[name='environment']").change(refreshContent); + $("input[name='environment']").change(function() { + updateRevisionMarkers($("input[name='environment']:checked").val()); + refreshContent(); + }); $("#revision").html(revisionboxes[defaults.project]); $("#revision").val(defaults.revision); + updateRevisionMarkers(defaults.environment); $("#revision").change(refreshContent); $("#permalink").click(function() { diff --git a/codespeed/templates/codespeed/changes.html b/codespeed/templates/codespeed/changes.html index 4403b913..82fa8386 100644 --- a/codespeed/templates/codespeed/changes.html +++ b/codespeed/templates/codespeed/changes.html @@ -81,11 +81,12 @@ Changes.init({ project: "{{ defaultexecutable.project }}", executable: "{{ defaultexecutable.id }}", - environment: "{{ defaultenvironment.id }}", + environment: "{{ defaultenvironment.0.id }}", revision: "{{ selectedrevision.commitid }}", trend: "{{ defaulttrend }}", projectmatrix: eval({{ projectmatrix|safe }}), - revisionlists: eval({{ revisionlists|safe }}) + revisionlists: eval({{ revisionlists|safe }}), + envhasresults: eval({{ env_has_results|safe }}) }); }); diff --git a/codespeed/views.py b/codespeed/views.py index 0a13336e..97e9a920 100644 --- a/codespeed/views.py +++ b/codespeed/views.py @@ -908,6 +908,16 @@ def changes(request): projectmatrix[e.id] = e.project.name projectmatrix = json.dumps(projectmatrix) + all_commitids = [rev.commitid for revisions in revisionlists.values() for rev in revisions] + env_has_results = {} + for env in enviros: + has = set(Result.objects.filter( + environment=env, + revision__commitid__in=all_commitids, + ).values_list('revision__commitid', flat=True).distinct()) + env_has_results[str(env.id)] = list(has) + env_has_results = json.dumps(env_has_results) + for project, revisions in revisionlists.items(): revisionlists[project] = [ (str(rev), rev.commitid) for rev in revisions @@ -928,6 +938,7 @@ def changes(request): 'executables': executables, 'projectmatrix': projectmatrix, 'revisionlists': revisionlists, + 'env_has_results': env_has_results, 'trends': trends, }) From 29b6015ce03e667f0f0667ba5c820ee17eaf21a6 Mon Sep 17 00:00:00 2001 From: mattip Date: Tue, 26 May 2026 16:23:23 +0300 Subject: [PATCH 2/2] make change revision queries work in bulk --- codespeed/models.py | 87 +++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/codespeed/models.py b/codespeed/models.py index a317a48c..1bc24803 100644 --- a/codespeed/models.py +++ b/codespeed/models.py @@ -401,34 +401,41 @@ def get_changes_table(self, trend_depth=10, force_save=False): return self._get_tablecache() # Otherwise generate a new changes table # Get latest revisions for this branch (which also sets the project) - lastrevisions = self.get_last_revisions(trend_depth) + lastrevisions = list(self.get_last_revisions(trend_depth)) if not lastrevisions: return [] - change_list = [] + changerevision = None pastrevisions = [] if len(lastrevisions) > 1: changerevision = lastrevisions[1] - change_list = Result.objects.filter( - revision=changerevision - ).filter( - environment=self.environment - ).filter( - executable=self.executable - ) pastrevisions = lastrevisions[trend_depth - 2:trend_depth + 1] - result_list = Result.objects.filter( - revision=lastrevisions[0] - ).filter( - environment=self.environment - ).filter( - executable=self.executable - ) + # Bulk fetch all results needed across current, change, and past revisions + relevant_revs = [lastrevisions[0]] + if changerevision: + relevant_revs.append(changerevision) + relevant_revs.extend(pastrevisions) + results_map = { + (r.revision_id, r.benchmark_id): r + for r in Result.objects.filter( + revision__in=relevant_revs, + environment=self.environment, + executable=self.executable, + ) + } + + # Fetch and group all benchmarks in one query, preserving DB order + benchmarks_by_units = {} + for bench in Benchmark.objects.all(): + benchmarks_by_units.setdefault(bench.units_title, []).append(bench) + + current_rev_id = lastrevisions[0].pk + change_rev_id = changerevision.pk if changerevision else None + past_rev_ids = [rev.pk for rev in pastrevisions] tablelist = [] - for units_title in Benchmark.objects.all().values_list( - 'units_title', flat=True).distinct(): + for units_title, bench_group in benchmarks_by_units.items(): currentlist = [] units = "" hasmin = False @@ -436,14 +443,13 @@ def get_changes_table(self, trend_depth=10, force_save=False): has_stddev = False smallest = 1000 totals = {'change': [], 'trend': []} - for bench in Benchmark.objects.filter(units_title=units_title): + for bench in bench_group: units = bench.units lessisbetter = bench.lessisbetter - resultquery = result_list.filter(benchmark=bench) - if not len(resultquery): - continue - resobj = resultquery.filter(benchmark=bench)[0] + resobj = results_map.get((current_rev_id, bench.pk)) + if resobj is None: + continue std_dev = resobj.std_dev if std_dev is not None: @@ -466,13 +472,13 @@ def get_changes_table(self, trend_depth=10, force_save=False): # Calculate percentage change relative to previous result result = max(resobj.value, 0) change = "-" - if len(change_list): - c = change_list.filter(benchmark=bench) - if c.count() and result is not None: - if c[0].value != 0: - change = (result - c[0].value) * 100 / c[0].value - totals['change'].append(result / c[0].value) - elif c[0].value == 0: + if change_rev_id is not None: + c = results_map.get((change_rev_id, bench.pk)) + if c is not None: + if c.value != 0: + change = (result - c.value) * 100 / c.value + totals['change'].append(result / c.value) + elif c.value == 0: if result == 0: # 0/0 = 1, in our world change = 0 @@ -481,27 +487,16 @@ def get_changes_table(self, trend_depth=10, force_save=False): # n/0 = ∞ change = float("inf") totals['change'].append(float("inf")) - else: - # no previous result, no change available - pass # Calculate trend: # percentage change relative to average of 3 previous results - # Calculate past average result_sum = 0 num_past_results = 0 - if len(pastrevisions): - for rev in pastrevisions: - past_result = Result.objects.filter( - revision=rev - ).filter( - environment=self.environment - ).filter( - executable=self.executable - ).filter(benchmark=bench) - if past_result.count(): - result_sum += past_result[0].value - num_past_results += 1 + for rev_id in past_rev_ids: + past_r = results_map.get((rev_id, bench.pk)) + if past_r is not None: + result_sum += past_r.value + num_past_results += 1 trend = "-" if result_sum: average = result_sum / num_past_results