Skip to content

Commit 58fb9f1

Browse files
authored
Merge pull request #71 from mattip/multiple-timelines
allow up to two environments on timeline and other smaller fixes
2 parents 3e55b90 + 0781320 commit 58fb9f1

7 files changed

Lines changed: 269 additions & 135 deletions

File tree

codespeed/static/css/main.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ div.about_content { text-align: left; }
234234
.boxbody input { margin-left: 0; vertical-align: top; }
235235
#options li { margin-bottom: 0.8em; }
236236
.seriescolor { float: right; margin-top: 2px; height: 13px; width: 14px; }
237+
.envlinecolor { float: right; font-size: 12px; line-height: 18px; color: #555; }
237238
.compplot-wrap { display: inline-block; }
238239
a.togglefold { font-size: normal; color: #000000; }
239240
a.togglefold::before {

codespeed/static/js/changes.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ function updateRevisionMarkers(env_id) {
9393
});
9494
$("#revision").html(options);
9595
$("#revision").val(current);
96+
if (!$("#revision").val()) {
97+
$("#revision").prop('selectedIndex', 0);
98+
}
9699
}
97100

98101
function refreshContent() {
@@ -123,8 +126,19 @@ function changeRevisions() {
123126
selected_project = projectmatrix[executable];
124127

125128
if (selected_project !== currentproject) {
129+
var oldRevs = revisiondata[currentproject] || [];
130+
var oldEntry = oldRevs.find(function(r) { return r[1] === $("#revision").val(); });
131+
var oldDate = oldEntry ? new Date(oldEntry[0].slice(0, 19)) : null;
126132
currentproject = selected_project;
127133
updateRevisionMarkers($("input[name='environment']:checked").val());
134+
if (oldDate) {
135+
var newRevs = revisiondata[currentproject] || [];
136+
var best = newRevs.reduce(function(bi, r, i) {
137+
return Math.abs(new Date(r[0].slice(0, 19)) - oldDate) <
138+
Math.abs(new Date(newRevs[bi][0].slice(0, 19)) - oldDate) ? i : bi;
139+
}, 0);
140+
$("#revision").prop('selectedIndex', best);
141+
}
128142

129143
//Give visual cue that the select box has changed
130144
var bgc = $("#revision").parent().parent().css("backgroundColor");

codespeed/static/js/timeline.js

Lines changed: 162 additions & 62 deletions
Large diffs are not rendered by default.

codespeed/templates/codespeed/timeline.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
<ul>
2424
{% for env in environments %}
2525
<li title="{{ env.os }}, {{ env.cpu }}">
26-
<input id="env{{ env.name }}" type="radio" name="environments" value="{{ env.id }}" />
27-
<label for="env{{ env.name }}">{{ env }}</label>
26+
<input id="env{{ env.name }}" type="checkbox" name="environments" value="{{ env.id }}" />
27+
<label for="env{{ env.name }}">{{ env }}</label><div class="envlinecolor"></div>
2828
</li>
2929
{% endfor %}
3030
</ul>
@@ -77,7 +77,7 @@
7777
</div>
7878

7979
<div id="configbar">
80-
<span class="options" title="Last {{ defaultlast }} revisions tested">
80+
<span class="options">
8181
Show the last
8282
<select id="revisions">{% for rev in lastrevisions %}
8383
<option value="{{ rev }}">{{ rev }}</option>{% endfor %}
@@ -121,7 +121,7 @@
121121
executables: [{% for exe in checkedexecutables %}{{ exe.id }}, {% endfor %}],
122122
branches: [{% for b in branch_list %}"{{ branch }}", {% endfor %}],
123123
benchmark: "{{ defaultbenchmark }}",
124-
environment: "{{ defaultenvironment.0.id }}",
124+
environments: [{% for env in defaultenvironments %}{{ env.id }}, {% endfor %}],
125125
equidistant: "{{ defaultequid }}",
126126
quartiles: "{{ defaultquarts }}",
127127
extrema: "{{ defaultextr }}"

codespeed/tests/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,11 +405,11 @@ def test_gettimelinedata(self):
405405
1,
406406
"there should be 1 timeline for master")
407407
self.assertEqual(
408-
len(responsedata['timelines'][0]['branches']['master']['1']),
408+
len(responsedata['timelines'][0]['branches']['master']['1:1']),
409409
2,
410410
"There are 2 datapoints")
411411
self.assertEqual(
412-
responsedata['timelines'][0]['branches']['master']['1'][1],
412+
responsedata['timelines'][0]['branches']['master']['1:1'][1],
413413
[u'2011/04/13 17:04:22 ', 2000.0, 1.11111, u'2', u'', u'master', u''])
414414

415415

codespeed/views.py

Lines changed: 77 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -451,11 +451,18 @@ def gettimelinedata(request):
451451
if not executables:
452452
timeline_list['error'] = "No executables selected"
453453
return HttpResponse(json.dumps(timeline_list))
454-
environment = None
455-
try:
456-
environment = get_object_or_404(Environment, id=data.get('env'))
457-
except ValueError:
458-
Http404()
454+
455+
environments = []
456+
for env_id in data.get('env', '').split(',')[:2]:
457+
if not env_id:
458+
continue
459+
try:
460+
environments.append(get_object_or_404(Environment, id=int(env_id)))
461+
except (ValueError, Http404):
462+
pass
463+
if not environments:
464+
timeline_list['error'] = "No environment selected"
465+
return HttpResponse(json.dumps(timeline_list))
459466

460467
number_of_revs, benchmarks = get_num_revs_and_benchmarks(data)
461468

@@ -471,13 +478,13 @@ def gettimelinedata(request):
471478
next_benchmarks = int(next_benchmarks)
472479

473480
resp = StreamingHttpResponse(stream_timeline(baseline_exe, baseline_rev, benchmarks, data,
474-
environment, executables, number_of_revs,
481+
environments, executables, number_of_revs,
475482
next_benchmarks),
476483
content_type='application/json')
477484
return resp
478485

479486

480-
def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environment, executables,
487+
def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environments, executables,
481488
number_of_revs, next_benchmarks):
482489
yield '{"timelines": ['
483490
num_results = {"results": 0}
@@ -493,7 +500,7 @@ def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environment, e
493500
num_benchmark += 1
494501

495502
if not next_benchmarks or num_benchmark > next_benchmarks:
496-
result = get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environment,
503+
result = get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environments,
497504
executables, number_of_revs, num_results)
498505
if result != "":
499506
transmitted_benchmarks += 1
@@ -516,7 +523,7 @@ def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environment, e
516523
yield ']' + not_first + next_page + ', "error":"None"}\n'
517524

518525

519-
def get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environment, executables,
526+
def get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environments, executables,
520527
number_of_revs, num_results):
521528
lessisbetter = bench.lessisbetter and ' (less is better)' or ' (more is better)'
522529
timeline = {
@@ -528,74 +535,67 @@ def get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environment, e
528535
'lessisbetter': lessisbetter,
529536
'branches': {},
530537
'baseline': "None",
538+
'environments': [{'id': env.id, 'name': env.name} for env in environments],
531539
}
532540
append = False
533541
for branch in Branch.objects.filter(
534542
project__track=True, name=F('project__default_branch')):
535-
# For now, we'll only work with default branches
536-
for executable in executables:
537-
if executable.project != branch.project:
538-
continue
539-
540-
resultquery = Result.objects.filter(
541-
benchmark=bench
542-
).filter(
543-
environment=environment
544-
).filter(
545-
executable=executable
546-
).filter(
547-
revision__branch=branch
548-
).select_related(
549-
"revision"
550-
).order_by('-revision__date')[:number_of_revs]
551-
if not len(resultquery):
552-
continue
553-
timeline['branches'].setdefault(branch.name, {})
554-
555-
results = []
556-
for res in resultquery:
557-
if bench.data_type == 'M':
558-
q1, q3, val_max, val_min = get_stats_with_defaults(res)
559-
results.append(
560-
[
543+
for environment in environments:
544+
for executable in executables:
545+
if executable.project != branch.project:
546+
continue
547+
548+
resultquery = Result.objects.filter(
549+
benchmark=bench,
550+
environment=environment,
551+
executable=executable,
552+
revision__branch=branch,
553+
).select_related(
554+
"revision"
555+
).order_by('-revision__date')[:number_of_revs]
556+
if not len(resultquery):
557+
continue
558+
timeline['branches'].setdefault(branch.name, {})
559+
560+
results = []
561+
for res in resultquery:
562+
if bench.data_type == 'M':
563+
q1, q3, val_max, val_min = get_stats_with_defaults(res)
564+
results.append([
561565
res.revision.date.strftime('%Y/%m/%d %H:%M:%S %z'),
562566
res.value, val_max, q3, q1, val_min,
563567
res.revision.get_short_commitid(), res.revision.tag, branch.name,
564568
res.suite_version,
565-
]
566-
)
567-
else:
568-
std_dev = ""
569-
if res.std_dev is not None:
570-
std_dev = res.std_dev
571-
results.append(
572-
[
569+
])
570+
else:
571+
std_dev = ""
572+
if res.std_dev is not None:
573+
std_dev = res.std_dev
574+
results.append([
573575
res.revision.date.strftime('%Y/%m/%d %H:%M:%S %z'),
574576
res.value, std_dev,
575577
res.revision.get_short_commitid(), res.revision.tag, branch.name,
576578
res.suite_version,
577-
]
578-
)
579-
timeline['branches'][branch.name][executable.id] = results
580-
append = True
579+
])
580+
# Key is "exe_id:env_id" so multiple environments render as separate series
581+
timeline['branches'][branch.name][f"{executable.id}:{environment.id}"] = results
582+
append = True
581583
if baseline_rev is not None and append:
582584
try:
583585
baselinevalue = Result.objects.get(
584586
executable=baseline_exe,
585587
benchmark=bench,
586588
revision=baseline_rev,
587-
environment=environment
589+
environment=environments[0],
588590
).value
589591
except Result.DoesNotExist:
590592
timeline['baseline'] = "None"
591593
else:
592-
# determine start and end revision (x axis)
593-
# from longest data series
594594
results = []
595595
for branch in timeline['branches']:
596-
for exe in timeline['branches'][branch]:
597-
if len(timeline['branches'][branch][exe]) > len(results):
598-
results = timeline['branches'][branch][exe]
596+
for key in timeline['branches'][branch]:
597+
if len(timeline['branches'][branch][key]) > len(results):
598+
results = timeline['branches'][branch][key]
599599
end = results[0][0]
600600
start = results[len(results) - 1][0]
601601
timeline['baseline'] = [
@@ -625,6 +625,10 @@ def timeline(request):
625625
if not enviros:
626626
return no_environment_error(request)
627627
defaultenviro = get_default_environment(enviros, data)
628+
if 'env' in data:
629+
defaultenvironments = get_default_environment(enviros, data, multi=True)[:2]
630+
else:
631+
defaultenvironments = defaultenviro # already respects DEF_ENVIRONMENT
628632

629633
# Default Project
630634
defaultproject = Project.objects.filter(track=True)
@@ -643,6 +647,15 @@ def timeline(request):
643647
except Executable.DoesNotExist:
644648
pass
645649

650+
if not checkedexecutables:
651+
if hasattr(settings, 'DEF_EXECUTABLES') and settings.DEF_EXECUTABLES:
652+
for def_exe in settings.DEF_EXECUTABLES:
653+
try:
654+
proj = Project.objects.get(name=def_exe['project'])
655+
checkedexecutables.append(
656+
Executable.objects.get(name=def_exe['name'], project=proj))
657+
except (Project.DoesNotExist, Executable.DoesNotExist):
658+
pass
646659
if not checkedexecutables:
647660
checkedexecutables = Executable.objects.filter(project__track=True)
648661

@@ -671,12 +684,17 @@ def timeline(request):
671684
except ValueError:
672685
pass
673686

674-
lastrevisions = [10, 50, 200, 1000]
687+
lastrevisions = [10, 15, 50, 200]
675688
defaultlast = settings.DEF_TIMELINE_LIMIT
676-
if 'revs' in data:
677-
if int(data['revs']) not in lastrevisions:
678-
lastrevisions.append(data['revs'])
679-
defaultlast = data['revs']
689+
if 'revs' in data and data['revs']:
690+
try:
691+
revs_int = int(data['revs'])
692+
except ValueError:
693+
revs_int = None
694+
if revs_int is not None:
695+
if revs_int not in lastrevisions:
696+
lastrevisions.append(revs_int)
697+
defaultlast = revs_int
680698

681699
benchmarks = Benchmark.objects.all()
682700

@@ -733,6 +751,7 @@ def timeline(request):
733751
'baseline': baseline,
734752
'defaultbenchmark': defaultbenchmark,
735753
'defaultenvironment': defaultenviro,
754+
'defaultenvironments': defaultenvironments,
736755
'lastrevisions': lastrevisions,
737756
'defaultlast': defaultlast,
738757
'executables': executables,

codespeed/views_data.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ def get_default_environment(enviros, data, multi=False):
2121
# Use permalink values
2222
if 'env' in data:
2323
for env_value in data['env'].split(","):
24+
try:
25+
env_id = int(env_value)
26+
except ValueError:
27+
continue
2428
for env in enviros:
25-
try:
26-
env_id = int(env_value)
27-
except ValueError:
28-
# Not an int
29-
continue
30-
for env in enviros:
31-
if env_id == env.id:
32-
defaultenviros.append(env)
29+
if env_id == env.id:
30+
defaultenviros.append(env)
31+
break
3332
if not multi:
3433
break
3534
# Use settings.py value
@@ -91,7 +90,8 @@ def getbaselineexecutables(include_tags=None):
9190
if base['key'] == "none":
9291
continue
9392
if (base['executable'].name == exename and
94-
base['revision'].commitid == commitid):
93+
(base['revision'].commitid == commitid or
94+
base['revision'].tag == commitid)):
9595
baseline.remove(base)
9696
baseline.insert(1, base)
9797
break

0 commit comments

Comments
 (0)