Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for displaying median benchmarks #203

Merged
merged 2 commits into from
Jun 10, 2016
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
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'),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could factor out the data formatting somewhere common to both data_types. Even a method on revision date_formatted() or similar

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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think hasattr is needed since you added USE_MEDIAN_BANDS to the basic settings

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