Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ environment and define a default project.
* Go to `http://localhost:8000/admin/codespeed/project/`
and create a project.

An environment that no longer receives results can be marked as historical
by unticking its "Active" field in the admin. Historical environments keep
their data and permalinks, but are listed in a folded "Historical" group in
the Changes, Timeline and Comparison views and are not selected by default.

Check the field "Track changes" and, in case you want version control
integration, configure the relevant fields.

Expand Down Expand Up @@ -192,6 +197,7 @@ several parameters (the file includes comments with full examples).
the Timeline and Changes views.
* `DEF_ENVIRONMENT`: Defines which environment should be selected as default
in the Changes and Timeline views.
Without it, the first *active* environment is used.
* `CHANGE_THRESHOLD`
* `TREND_THRESHOLD`

Expand Down Expand Up @@ -229,8 +235,9 @@ The main customization for the homepage is to display either the reports
* `NORMALIZATION`: Defines whether normalization should be enabled as default
in the Comparison view.
* `CHART_ORIENTATION`: horizontal or vertical
* `COMP_ENVIRONMENTS_DEFAULT`: `all` (the default) checks every environment;
`default` checks only `DEF_ENVIRONMENT`, or the first environment.
* `COMP_ENVIRONMENTS_DEFAULT`: `all` (the default) checks every active
environment; `default` checks only `DEF_ENVIRONMENT`, or the first active
environment.
* `COMP_EXECUTABLES`: per default all executables will be checked. When there
are a large number of tags or executables, it is better to only select a few
so that the plots are not too cluttered.
Expand Down
6 changes: 4 additions & 2 deletions codespeed/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ class BenchmarkAdmin(admin.ModelAdmin):

@admin.register(Environment)
class EnvironmentAdmin(admin.ModelAdmin):
list_display = ('name', 'cpu', 'memory', 'os', 'kernel')
ordering = ['name']
list_display = ('name', 'cpu', 'memory', 'os', 'kernel', 'active')
list_editable = ('active',)
list_filter = ('active',)
ordering = ['-active', 'name']
search_fields = ('name', 'cpu', 'memory', 'os', 'kernel')


Expand Down
1 change: 1 addition & 0 deletions codespeed/management/commands/import_sqlite_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
_BOOL_COLS = {
'codespeed_project': {'track'},
'codespeed_branch': {'display_on_comparison_page'},
'codespeed_environment': {'active'},
'codespeed_benchmark': {'lessisbetter', 'default_on_comparison'},
}

Expand Down
18 changes: 18 additions & 0 deletions codespeed/migrations/0007_environment_active.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 6.1.2 on 2026-11-13 18:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('codespeed', '0006_alter_benchmark_name_alter_benchmark_unique_together'),
]

operations = [
migrations.AddField(
model_name='environment',
name='active',
field=models.BooleanField(db_default=True, default=True, help_text='Untick for a historical environment that no longer receives results. It is then listed in a folded "Historical" group and not selected by default.'),
),
]
5 changes: 5 additions & 0 deletions codespeed/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ class Environment(models.Model):
memory = models.CharField(max_length=100, blank=True)
os = models.CharField(max_length=100, blank=True)
kernel = models.CharField(max_length=100, blank=True)
active = models.BooleanField(
default=True, db_default=True,
help_text="Untick for a historical environment that no longer "
"receives results. It is then listed in a folded "
"\"Historical\" group and not selected by default.")

def __str__(self):
return self.name
Expand Down
2 changes: 1 addition & 1 deletion codespeed/static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ div.about_content p, div.about_content li, div.about_content h2, div.about_conte
.compplot-wrap { display: inline-block; }

a.togglefold { color: var(--text); font-weight: 500; }
a.togglefold:hover { text-decoration: none; }
a.togglefold:hover, a.togglefold:focus { text-decoration: none; }
a.togglefold::before {
/* Bootstrap Icons chevron-right (MIT, twbs/icons), coloured via mask */
content: '';
Expand Down
1 change: 1 addition & 0 deletions codespeed/static/js/changes.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ function init(defaults) {
$("input[name='executable']").change(changeRevisions);

$("#env" + defaults.environment).prop('checked', true);
unfoldGroups($("input[name='environment']:checked"));
$("input[name='environment']").change(function() {
updateRevisionMarkers($("input[name='environment']:checked").val());
refreshContent();
Expand Down
8 changes: 8 additions & 0 deletions codespeed/static/js/codespeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ function getLoadText(text, h) {
return loadtext;
}

// Unfold the .togglefold groups holding the given inputs.
function unfoldGroups(inputs) {
inputs.closest("ul").each(function() {
$(this).children("li").show();
$(this).children("a.togglefold").removeClass('folded');
});
}

$(function() {
// Check all and none links
$('.checkall').each(function() {
Expand Down
3 changes: 2 additions & 1 deletion codespeed/static/js/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -625,14 +625,15 @@ function setValuesOfInputFields(params) {
}
benchRadio.prop('checked', true);
// reveal the suite accordion section that holds the selected benchmark
benchRadio.closest("ul").show().children("a.togglefold").removeClass('folded');
unfoldGroups(benchRadio);

var envDefault = (defaults.environments || []).map(String).join(',');
var envIds = valueOrDefault(params.env, envDefault).split(',').filter(Boolean);
var envSel = $("input[name='environments']");
envIds.forEach(function(envId) {
envSel.filter("[value='" + envId.trim() + "']").prop('checked', true);
});
unfoldGroups(envSel.filter(':checked'));
updateEnvIndicators();
updateExeColors();
$("#baselinecolor").css("background-color", baselineColor);
Expand Down
10 changes: 9 additions & 1 deletion codespeed/templates/codespeed/changes.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@
<label for="env{{ env.id }}">{{ env }}</label>
</li>
{% endfor %}
</ul>
</ul>{% if historical_environments %}
<ul class="historical"><a href="#" class="togglefold{% if historical_folded %} folded{% endif %}" title="Environments that are no longer in use and no longer receive results">Historical</a>
{% for env in historical_environments %}
<li{% if historical_folded %} style="display: none"{% endif %} title="{{ env.os }}, {{ env.cpu }}">
<input id="env{{ env.id }}" type="radio" name="environment" value="{{ env.id }}" />
<label for="env{{ env.id }}">{{ env }}</label>
</li>
{% endfor %}
</ul>{% endif %}
</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion codespeed/templates/codespeed/changes_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
</tr>
</thead>
<tbody>
<tr><td class="infofirst">Name</td><td>{{ env.name }}</td></tr>
<tr><td class="infofirst">Name</td><td>{{ env.name }}{% if not env.active %} (historical){% endif %}</td></tr>
<tr><td class="infofirst">CPU</td><td>{{ env.cpu }}</td></tr>
<tr><td class="infofirst">Memory</td><td>{{ env.memory }}</td></tr>
<tr><td class="infofirst">OS</td><td>{{ env.os }}</td></tr>
Expand Down
21 changes: 14 additions & 7 deletions codespeed/templates/codespeed/comparison.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,29 @@
<div id="environment" class="sidebox">
<div class="boxhead"><h2>Environments</h2></div>
<div class="boxbody">
<ul><a href="#" class="togglefold">Environments</a> <span class="checklinks">(<a href="#" class="checkall">All</a>, <a href="#" class="uncheckall">None</a>)</span>
<ul><a href="#" class="togglefold{% if enviros_folded %} folded{% endif %}">Environments</a> <span class="checklinks">(<a href="#" class="checkall">All</a>, <a href="#" class="uncheckall">None</a>)</span>
{% for env in enviros %}
<li title="{{ env.os }}, {{ env.cpu }}">
<li{% if enviros_folded %} style="display: none"{% endif %} title="{{ env.os }}, {{ env.cpu }}">
<input id="env_{{ env.id }}" type="checkbox" name="environments" value="{{ env.id }}"{% if env in checkedenviros %} checked{% endif %} />
<label for="env_{{ env.id }}">{{ env }}</label>
</li>{% endfor %}
</ul>
</ul>{% if historical_enviros %}
<ul class="historical"><a href="#" class="togglefold{% if historical_folded %} folded{% endif %}" title="Environments that are no longer in use and no longer receive results">Historical</a> <span class="checklinks">(<a href="#" class="checkall">All</a>, <a href="#" class="uncheckall">None</a>)</span>
{% for env in historical_enviros %}
<li{% if historical_folded %} style="display: none"{% endif %} title="{{ env.os }}, {{ env.cpu }}">
<input id="env_{{ env.id }}" type="checkbox" name="environments" value="{{ env.id }}"{% if env in checkedenviros %} checked{% endif %} />
<label for="env_{{ env.id }}">{{ env }}</label>
</li>{% endfor %}
</ul>{% endif %}
</div>
</div>
<div id="executable" class="sidebox">
<div class="boxhead"><h2>Executables</h2></div>
<div class="boxbody">
{% for proj, executable in executables.items %}
<ul><a href="#" class="togglefold">{{ proj }}</a> <span class="checklinks">(<a href="#" class="checkall">All</a>, <a href="#" class="uncheckall">None</a>)</span>
<ul><a href="#" class="togglefold{% if proj in folded_exe_groups %} folded{% endif %}">{{ proj }}</a> <span class="checklinks">(<a href="#" class="checkall">All</a>, <a href="#" class="uncheckall">None</a>)</span>
{% for exe in executable %}
<li title="{{ exe.executable }} - {{ exe.revision }}">
<li{% if proj in folded_exe_groups %} style="display: none"{% endif %} title="{{ exe.executable }} - {{ exe.revision }}">
{% if exe.key in checkedexecutables %}
<input id="exe_{{ exe.key }}" type="checkbox" name="executables" value="{{ exe.key }}" checked />
{% else %}
Expand All @@ -48,9 +55,9 @@
<div class="boxhead"><h2>Benchmarks</h2></div>
<div class="boxbody">
{% for key,benchlist in benchmarks.items %}
<ul><a href="#" class="togglefold">{{ key }}</a> <span class="checklinks">(<a href="#" class="checkall">All</a>, <a href="#" class="uncheckall">None</a>)</span>
<ul><a href="#" class="togglefold{% if key in folded_bench_groups %} folded{% endif %}">{{ key }}</a> <span class="checklinks">(<a href="#" class="checkall">All</a>, <a href="#" class="uncheckall">None</a>)</span>
{% for bench in benchlist|dictsort:"name" %}
<li title="{{ bench.description }}">
<li{% if key in folded_bench_groups %} style="display: none"{% endif %} title="{{ bench.description }}">
{% if bench in checkedbenchmarks %}
<input id="benchmark_{{ bench.id }}" type="checkbox" name="benchmarks" value="{{ bench.id }}" data-source="{{ bench.source }}" checked />
{% else %}
Expand Down
14 changes: 11 additions & 3 deletions codespeed/templates/codespeed/timeline.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@
<label for="env{{ env.name }}">{{ env }}</label><div class="envlinecolor"></div>
</li>
{% endfor %}
</ul>
</ul>{% if historical_environments %}
<ul class="historical"><a href="#" class="togglefold{% if historical_folded %} folded{% endif %}" title="Environments that are no longer in use and no longer receive results">Historical</a>
{% for env in historical_environments %}
<li{% if historical_folded %} style="display: none"{% endif %} title="{{ env.os }}, {{ env.cpu }}">
<input id="env{{ env.name }}" type="checkbox" name="environments" value="{{ env.id }}" />
<label for="env{{ env.name }}">{{ env }}</label><div class="envlinecolor"></div>
</li>
{% endfor %}
</ul>{% endif %}
</div>
</div>
<div id="executable" class="sidebox">
Expand Down Expand Up @@ -72,9 +80,9 @@
<div id="benchmarklist">
{% regroup benchmarks by get_source_display as benchmark_groups %}
{% for group in benchmark_groups %}
<ul>{% if benchmark_groups|length > 1 %}<a href="#" class="togglefold">{{ group.grouper }}</a>{% endif %}
<ul>{% if benchmark_groups|length > 1 %}<a href="#" class="togglefold{% if defaultbenchmark not in group.list %} folded{% endif %}">{{ group.grouper }}</a>{% endif %}
{% for bench in group.list|dictsort:"name" %}
<li title="{{ bench.description }}">
<li{% if benchmark_groups|length > 1 and defaultbenchmark not in group.list %} style="display: none"{% endif %} title="{{ bench.description }}">
<input id="benchmark_{{ bench.id }}" type="radio" name="benchmark" value="{{ bench.ident }}" />
<label for="benchmark_{{ bench.id }}">{{ bench }}</label>
</li>
Expand Down
43 changes: 42 additions & 1 deletion codespeed/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,14 @@ def test_no_source_fold_with_one_source(self):

def test_source_fold_with_several_sources(self):
Benchmark.objects.create(name='float', source='pyperformance')
# Suites are rendered folded, except the one of the selected benchmark
response = self.client.get(reverse('timeline'))
self.assertContains(response, 'class="togglefold">Legacy</a>')
self.assertContains(response, 'class="togglefold folded">Legacy</a>')
self.assertContains(
response, 'class="togglefold folded">PyPerformance</a>')
response = self.client.get(
reverse('timeline'), {'ben': 'float.pyperformance'})
self.assertContains(response, 'class="togglefold folded">Legacy</a>')
self.assertContains(response, 'class="togglefold">PyPerformance</a>')

@override_settings(TIMELINE_GRID_REVISIONS=25)
Expand Down Expand Up @@ -516,11 +522,46 @@ def test_only_default_environment_checked(self):
self.assertEqual(self.checked_env_names(response),
['Dual Core', 'Quad Core'])

def test_historical_environments_not_checked_by_default(self):
self.quad.active = False
self.quad.save()
response = self.client.get(self.path)
self.assertEqual(self.checked_env_names(response), ['Dual Core'])
self.assertEqual(response.context['enviros'], [self.dual])
self.assertEqual(response.context['historical_enviros'], [self.quad])
self.assertContains(response, '>Historical</a>')
# Folded in the markup, not by script after the page has rendered
self.assertTrue(response.context['historical_folded'])
self.assertContains(response, 'class="togglefold folded"')
# Permalinks still select them
response = self.client.get(self.path, {'env': str(self.quad.id)})
self.assertFalse(response.context['historical_folded'])
self.assertEqual(self.checked_env_names(response), ['Quad Core'])

def test_no_historical_group_when_all_active(self):
response = self.client.get(self.path)
self.assertNotContains(response, '>Historical</a>')

def test_benchmarks_grouped_by_units_with_one_source(self):
response = self.client.get(self.path)
self.assertEqual(list(response.context['benchmarks']), ['Time'])
self.assertContains(response, 'class="togglefold">Time</a>')

def test_groups_with_nothing_checked_are_rendered_folded(self):
response = self.client.get(self.path)
self.assertFalse(response.context['enviros_folded'])
self.assertEqual(response.context['folded_exe_groups'], [])
self.assertEqual(response.context['folded_bench_groups'], [])
self.assertNotContains(response, 'togglefold folded')

Benchmark.objects.create(name='float', source='pyperformance',
default_on_comparison=False)
response = self.client.get(self.path)
self.assertEqual(response.context['folded_bench_groups'],
['PyPerformance'])
self.assertContains(
response, 'class="togglefold folded">PyPerformance</a>')

def test_benchmarks_grouped_by_source_with_several_sources(self):
Benchmark.objects.create(name='float', source='pyperformance')
response = self.client.get(self.path)
Expand Down
33 changes: 28 additions & 5 deletions codespeed/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from .auth import basic_auth_required
from .models import (Environment, Report, Project, Revision, Result,
Executable, Benchmark, Branch)
from .views_data import (get_default_environment, getbaselineexecutables,
from .views_data import (get_default_environment, split_environments,
getbaselineexecutables,
getdefaultexecutable, getcomparisonexes,
get_benchmark_results, get_num_revs_and_benchmarks,
get_stats_with_defaults, parse_benchmark_ident)
Expand Down Expand Up @@ -113,7 +114,7 @@ def gethistoricaldata(request):
if settings.DEF_ENVIRONMENT:
env = env.get(name=settings.DEF_ENVIRONMENT)
else:
env = env.first()
env = env.order_by('-active', 'id').first()

# Fetch Baseline data, filter by executable
baseline_results = []
Expand Down Expand Up @@ -323,6 +324,7 @@ def comparison(request):
enviros = Environment.objects.all()
if not enviros:
return no_environment_error(request)
active_enviros, historical_enviros = split_environments(enviros)
checkedenviros = get_default_environment(enviros, data, multi=True)
if 'env' not in data and \
get_setting('COMP_ENVIRONMENTS_DEFAULT', 'all') == 'default':
Expand Down Expand Up @@ -473,14 +475,27 @@ def comparison(request):
settings.CHART_ORIENTATION == 'horizontal'):
selecteddirection = True

checked_bench_ids = {bench.id for bench in checkedbenchmarks}
return render(request, 'codespeed/comparison.html', {
'checkedexecutables': checkedexecutables,
'checkedbenchmarks': checkedbenchmarks,
'checkedenviros': checkedenviros,
'executables': executables,
'benchmarks': benchmarks,
'bench_units': json.dumps(bench_units),
'enviros': enviros,
'enviros': active_enviros,
'historical_enviros': historical_enviros,
'historical_folded': not any(
env in checkedenviros for env in historical_enviros),
# Sidebar groups with nothing checked are rendered folded
'enviros_folded': not any(
env in checkedenviros for env in active_enviros),
'folded_exe_groups': [
proj for proj, exes in executables.items()
if not any(exe['key'] in checkedexecutables for exe in exes)],
'folded_bench_groups': [
key for key, benchlist in benchmarks.items()
if not any(bench.id in checked_bench_ids for bench in benchlist)],
'charts': charts,
'selectedbaseline': selectedbaseline,
'selectedchart': selectedchart,
Expand Down Expand Up @@ -687,6 +702,7 @@ def timeline(request):
enviros = Environment.objects.all()
if not enviros:
return no_environment_error(request)
active_enviros, historical_enviros = split_environments(enviros)
defaultenviro = get_default_environment(enviros, data)
if 'env' in data:
defaultenvironments = get_default_environment(enviros, data, multi=True)[:2]
Expand Down Expand Up @@ -832,7 +848,10 @@ def timeline(request):
'grid_revisions': grid_revisions,
'executables': executables,
'benchmarks': benchmarks,
'environments': enviros,
'environments': active_enviros,
'historical_environments': historical_enviros,
'historical_folded': not any(
env in defaultenvironments for env in historical_enviros),
'branch_list': branch_list,
'defaultbranch': defaultbranch,
'defaultequid': defaultequid,
Expand Down Expand Up @@ -938,6 +957,7 @@ def changes(request):
enviros = Environment.objects.all()
if not enviros:
return no_environment_error(request)
active_enviros, historical_enviros = split_environments(enviros)
defaultenv = get_default_environment(enviros, data)

if not len(Project.objects.filter(track=True)):
Expand Down Expand Up @@ -1030,7 +1050,10 @@ def changes(request):
'defaulttrend': defaulttrend,
'defaultchangethres': defaultchangethres,
'defaulttrendthres': defaulttrendthres,
'environments': enviros,
'environments': active_enviros,
'historical_environments': historical_enviros,
'historical_folded': not any(
env in defaultenv for env in historical_enviros),
'executables': executables,
'projectmatrix': projectmatrix,
'revisionlists': revisionlists,
Expand Down
Loading
Loading