Skip to content

Commit

Permalink
Merge pull request #203 from str4d/median
Browse files Browse the repository at this point in the history
Add support for displaying median benchmarks
  • Loading branch information
tobami authored Jun 10, 2016
2 parents a85b782 + 325a1fd commit b1ca8fa
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 25 deletions.
7 changes: 4 additions & 3 deletions codespeed/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ class ExecutableAdmin(admin.ModelAdmin):


class BenchmarkAdmin(admin.ModelAdmin):
list_display = ('name', 'benchmark_type', 'description', 'units_title',
'units', 'lessisbetter', 'default_on_comparison')
list_filter = ('lessisbetter',)
list_display = ('name', 'benchmark_type', 'data_type', 'description',
'units_title', 'units', 'lessisbetter',
'default_on_comparison')
list_filter = ('data_type','lessisbetter')
ordering = ['name']
search_fields = ('name', 'description')

Expand Down
29 changes: 29 additions & 0 deletions codespeed/migrations/0002_median.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('codespeed', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='benchmark',
name='data_type',
field=models.CharField(default='U', max_length=1, choices=[('U', 'Mean'), ('M', 'Median')]),
),
migrations.AddField(
model_name='result',
name='q1',
field=models.FloatField(null=True, blank=True),
),
migrations.AddField(
model_name='result',
name='q3',
field=models.FloatField(null=True, blank=True),
),
]
7 changes: 7 additions & 0 deletions codespeed/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,18 @@ class Benchmark(models.Model):
('C', 'Cross-project'),
('O', 'Own-project'),
)
D_TYPES = (
('U', 'Mean'),
('M', 'Median'),
)

name = models.CharField(unique=True, max_length=100)
parent = models.ForeignKey(
'self', verbose_name="parent",
help_text="allows to group benchmarks in hierarchies",
null=True, blank=True, default=None)
benchmark_type = models.CharField(max_length=1, choices=B_TYPES, default='C')
data_type = models.CharField(max_length=1, choices=D_TYPES, default='U')
description = models.CharField(max_length=300, blank=True)
units_title = models.CharField(max_length=30, default='Time')
units = models.CharField(max_length=20, default='seconds')
Expand Down Expand Up @@ -188,6 +193,8 @@ class Result(models.Model):
std_dev = models.FloatField(blank=True, null=True)
val_min = models.FloatField(blank=True, null=True)
val_max = models.FloatField(blank=True, null=True)
q1 = models.FloatField(blank=True, null=True)
q3 = models.FloatField(blank=True, null=True)
date = models.DateTimeField(blank=True, null=True)
revision = models.ForeignKey(Revision, related_name="results")
executable = models.ForeignKey(Executable, related_name="results")
Expand Down
2 changes: 2 additions & 0 deletions codespeed/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ def save_result(data):
r.std_dev = data.get('std_dev')
r.val_min = data.get('min')
r.val_max = data.get('max')
r.q1 = data.get('q1')
r.q3 = data.get('q3')

r.full_clean()
r.save()
Expand Down
2 changes: 2 additions & 0 deletions codespeed/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@
# COMP_EXECUTABLES = [
# ('myexe', '21df2423ra'),
# ('myexe', 'L'),]

USE_MEDIAN_BANDS = True # True to enable median bands on Timeline view
100 changes: 90 additions & 10 deletions codespeed/static/js/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,34 @@ function getColor(exe_id) {
.data('color');
}

function scaleColorAlpha(color, scale) {
var c = $.jqplot.getColorComponents(color);
c[3] = c[3] * scale;
return 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')';
}

function shouldPlotEquidistant() {
return $("#equidistant").is(':checked');
}

function shouldPlotQuartiles() {
return $("#show_quartile_bands").is(':checked');
}

function shouldPlotExtrema() {
return $("#show_extrema_bands").is(':checked');
}

function getConfiguration() {
var config = {
exe: readCheckbox("input[name='executable']:checked"),
base: $("#baseline option:selected").val(),
ben: $("input[name='benchmark']:checked").val(),
env: $("input[name='environments']:checked").val(),
revs: $("#revisions option:selected").val(),
equid: $("#equidistant").is(':checked') ? "on" : "off"
equid: $("#equidistant").is(':checked') ? "on" : "off",
quarts: $("#show_quartile_bands").is(':checked') ? "on" : "off",
extr: $("#show_extrema_bands").is(':checked') ? "on" : "off"
};

var branch = readCheckbox("input[name='branch']:checked");
Expand All @@ -53,25 +69,89 @@ function permalinkToChanges(commitid, executableid, environment) {
function OnMarkerClickHandler(ev, gridpos, datapos, neighbor, plot) {
if($("input[name='benchmark']:checked").val() === "grid") { return false; }
if (neighbor) {
var commitid = neighbor.data[3];
var commitid = neighbor.data[neighbor.data.length-2];
// Get executable ID from the seriesindex array
var executableid = seriesindex[neighbor.seriesIndex];
var environment = $("input[name='environments']:checked").val();
permalinkToChanges(commitid, executableid, environment);
}
}

function getHighlighterConfig(median) {
if (median) {
return {
show: true,
tooltipLocation: 'nw',
yvalues: 7,
formatString:'<table class="jqplot-highlighter"> <tr><td>date:</td><td>%s</td></tr> <tr><td>median:</td><td>%s</td></tr> <tr><td>max:</td><td>%s</td></tr> <tr><td>Q3:</td><td>%s</td></tr> <tr><td>Q1:</td><td>%s</td></tr> <tr><td>min:</td><td>%s</td></tr> <tr><td>commit:</td><td>%s</td></tr></table>'
};
} else {
return {
show: true,
tooltipLocation: 'nw',
yvalues: 4,
formatString:'<table class="jqplot-highlighter"> <tr><td>date:</td><td>%s</td></tr> <tr><td>result:</td><td>%s</td></tr> <tr><td>std dev:</td><td>%s</td></tr> <tr><td>commit:</td><td>%s</td></tr></table>'
};
}
}

function renderPlot(data) {
var plotdata = [],
series = [],
lastvalues = [];//hopefully the smallest values for determining significant digits.
seriesindex = [];
var hiddenSeries = 0;
var median = data['data_type'] === 'M';
for (var branch in data.branches) {
// NOTE: Currently, only the "default" branch is shown in the timeline
for (var exe_id in data.branches[branch]) {
// FIXME if (branch !== "default") { label += " - " + branch; }
var label = $("label[for*='executable" + exe_id + "']").html();
series.push({"label": label, "color": getColor(exe_id)});
var seriesConfig = {
label: label,
color: getColor(exe_id)
};
if (median) {
$("span.options.median").css("display", "inline");
var mins = new Array();
var maxes = new Array();
var q1s = new Array();
var q3s = new Array();
for (res in data["branches"][branch][exe_id]) {
var date = data["branches"][branch][exe_id][res][0];
var value = data["branches"][branch][exe_id][res][1];
var max = data["branches"][branch][exe_id][res][2];
var q3 = data["branches"][branch][exe_id][res][3];
var q1 = data["branches"][branch][exe_id][res][4];
var min = data["branches"][branch][exe_id][res][5];
if (min !== "")
mins.push([date, min]);
if (max !== "")
maxes.push([date, max]);
if (q1 !== "")
q1s.push([date, q1]);
if (q3 !== "")
q3s.push([date, q3]);
}
var extrema = new Array(mins, maxes);
var quartiles = new Array(q1s, q3s);
if (shouldPlotQuartiles()) {
seriesConfig['rendererOptions'] = {bandData: quartiles};
} else if (shouldPlotExtrema()) {
seriesConfig['rendererOptions'] = {bandData: extrema};
}
if (shouldPlotQuartiles() && shouldPlotExtrema()) {
series.push({
showLabel: false,
showMarker: false,
color: scaleColorAlpha(getColor(exe_id), 0.6),
rendererOptions: {bandData: extrema}
});
plotdata.push(data.branches[branch][exe_id]);
hiddenSeries++;
}
}
series.push(seriesConfig);
seriesindex.push(exe_id);
plotdata.push(data.branches[branch][exe_id]);
lastvalues.push(data.branches[branch][exe_id][0][1]);
Expand Down Expand Up @@ -121,15 +201,10 @@ function renderPlot(data) {
}
},
legend: {show: true, location: 'nw'},
highlighter: {
show: true,
tooltipLocation: 'nw',
yvalues: 4,
formatString:'<table class="jqplot-highlighter"> <tr><td>date:</td><td>%s</td></tr> <tr><td>result:</td><td>%s</td></tr> <tr><td>std dev:</td><td>%s</td></tr> <tr><td>commit:</td><td>%s</td></tr></table>'
},
highlighter: getHighlighterConfig(median),
cursor:{show:true, zoom:true, showTooltip:false, clickReset:true}
};
if (series.length > 4) {
if (series.length > 4 + hiddenSeries) {
// Move legend outside plot area to unclutter
var labels = [];
for (var l in series) {
Expand Down Expand Up @@ -193,6 +268,7 @@ function renderMiniplot(plotid, data) {
function render(data) {
$("#revisions").attr("disabled", false);
$("#equidistant").attr("disabled", false);
$("span.options.median").css("display", "none");
$("#plotgrid").html("");
if(data.error !== "None") {
var h = $("#content").height();//get height for error message
Expand Down Expand Up @@ -254,6 +330,8 @@ function initializeSite(event) {
$("input[name='benchmark']" ).change(updateUrl);
$("input[name='environments']").change(updateUrl);
$("#equidistant" ).change(updateUrl);
$("#show_quartile_bands" ).change(updateUrl);
$("#show_extrema_bands" ).change(updateUrl);
}

function refreshSite(event) {
Expand Down Expand Up @@ -307,6 +385,8 @@ function setValuesOfInputFields(event) {

$("#baselinecolor").css("background-color", baselineColor);
$("#equidistant").prop('checked', valueOrDefault(event.parameters.equid, defaults.equidistant) === "on");
$("#show_quartile_bands").prop('checked', valueOrDefault(event.parameters.quarts, defaults.quartiles) === "on");
$("#show_extrema_bands").prop('checked', valueOrDefault(event.parameters.extr, defaults.extrema) === "on");
}

function init(def) {
Expand Down
14 changes: 13 additions & 1 deletion codespeed/templates/codespeed/timeline.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@
<input id="equidistant" name="equidistant" type="checkbox" />
<label for="equidistant">Equidistant</label>
</span>
{% if use_median_bands %}
<span class="options median" title="Shows quartile bands in the plots" style="display: none">
<input id="show_quartile_bands" type="checkbox" name="show_quartile_bands" checked="checked"/>
<label for="show_quartile_bands">Show quartile bands</label>
</span>
<span class="options median" title="Shows extrema bands in the plots" style="display: none">
<input id="show_extrema_bands" type="checkbox" name="show_extrema_bands" checked="checked"/>
<label for="show_extrema_bands">Show extrema bands</label>
</span>
{% endif %}
<a id="permalink" href="#">Permalink</a>
</div>
<div id="content" class="clearfix">
Expand Down Expand Up @@ -115,7 +125,9 @@
branches: [{% for b in branch_list %}"{{ branch }}", {% endfor %}],
benchmark: "{{ defaultbenchmark }}",
environment: {{ defaultenvironment.id }},
equidistant: "{{ defaultequid }}"
equidistant: "{{ defaultequid }}",
quartiles: "{{ defaultquarts }}",
extrema: "{{ defaultextr }}"
});
});
</script>
Expand Down
56 changes: 45 additions & 11 deletions codespeed/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ def gettimelinedata(request):
'benchmark': bench.name,
'benchmark_id': bench.id,
'benchmark_description': bench.description,
'data_type': bench.data_type,
'units': bench.units,
'lessisbetter': lessisbetter,
'branches': {},
Expand Down Expand Up @@ -288,16 +289,37 @@ def gettimelinedata(request):

results = []
for res in resultquery:
std_dev = ""
if res.std_dev is not None:
std_dev = res.std_dev
results.append(
[
res.revision.date.strftime('%Y/%m/%d %H:%M:%S %z'),
res.value, std_dev,
res.revision.get_short_commitid(), branch
]
)
if bench.data_type == 'M':
val_min = ""
if res.val_min is not None:
val_min = res.val_min
val_max = ""
if res.val_max is not None:
val_max = res.val_max
q1 = ""
if res.q1 is not None:
q1 = res.q1
q3 = ""
if res.q3 is not None:
q3 = res.q3
results.append(
[
res.revision.date.strftime('%Y/%m/%d %H:%M:%S %z'),
res.value, val_max, q3, q1, val_min,
res.revision.get_short_commitid(), branch
]
)
else:
std_dev = ""
if res.std_dev is not None:
std_dev = res.std_dev
results.append(
[
res.revision.date.strftime('%Y/%m/%d %H:%M:%S %z'),
res.value, std_dev,
res.revision.get_short_commitid(), branch
]
)
timeline['branches'][branch][executable] = results
append = True
if baselinerev is not None and append:
Expand Down Expand Up @@ -424,11 +446,20 @@ def timeline(request):
defaultequid = data['equid']
else:
defaultequid = "off"
if 'quarts' in data:
defaultquarts = data['quarts']
else:
defaultquarts = "on"
if 'extr' in data:
defaultextr = data['extr']
else:
defaultextr = "on"

# Information for template
executables = {}
for proj in Project.objects.filter(track=True):
executables[proj] = Executable.objects.filter(project=proj)
use_median_bands = hasattr(settings, 'USE_MEDIAN_BANDS') and settings.USE_MEDIAN_BANDS
return render_to_response('codespeed/timeline.html', {
'checkedexecutables': checkedexecutables,
'defaultbaseline': defaultbaseline,
Expand All @@ -442,7 +473,10 @@ def timeline(request):
'environments': enviros,
'branch_list': branch_list,
'defaultbranch': defaultbranch,
'defaultequid': defaultequid
'defaultequid': defaultequid,
'defaultquarts': defaultquarts,
'defaultextr': defaultextr,
'use_median_bands': use_median_bands,
}, context_instance=RequestContext(request))


Expand Down

0 comments on commit b1ca8fa

Please sign in to comment.