diff --git a/speedcenter/codespeed/admin.py b/speedcenter/codespeed/admin.py index 613a1935..0fc69142 100644 --- a/speedcenter/codespeed/admin.py +++ b/speedcenter/codespeed/admin.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -from codespeed.models import Project, Revision, Executable, Benchmark, Result, Environment +from codespeed.models import Project, Revision, Executable, Benchmark +from codespeed.models import Result, Environment, Report from django.contrib import admin class ProjectAdmin(admin.ModelAdmin): @@ -35,3 +36,8 @@ class ResultAdmin(admin.ModelAdmin): list_filter = ('date', 'executable', 'benchmark', 'environment') admin.site.register(Result, ResultAdmin) + +class ReportAdmin(admin.ModelAdmin): + list_display = ('revision', 'summary', 'colorcode') + +admin.site.register(Report, ReportAdmin) \ No newline at end of file diff --git a/speedcenter/codespeed/models.py b/speedcenter/codespeed/models.py index c9ce351b..a01d4848 100644 --- a/speedcenter/codespeed/models.py +++ b/speedcenter/codespeed/models.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from django.db import models +from codespeed import settings class Project(models.Model): REPO_TYPES = ( @@ -66,7 +67,7 @@ def __unicode__(self): class Environment(models.Model): - name = models.CharField(unique=True,max_length=30) + name = models.CharField(unique=True, max_length=30) cpu = models.CharField(max_length=30, blank=True) memory = models.CharField(max_length=30, blank=True) os = models.CharField(max_length=30, blank=True) @@ -92,3 +93,257 @@ def __unicode__(self): class Meta: unique_together = ("revision", "executable", "benchmark", "environment") + + +class Report(models.Model): + revision = models.ForeignKey(Revision) + environment = models.ForeignKey(Environment) + executable = models.ForeignKey(Executable) + summary = models.CharField(max_length=30, default="OK") + colorcode = models.CharField(max_length=10, default="none") + + def __unicode__(self): + return "Report for " + str(self.revision.commitid) + + class Meta: + unique_together = ("revision", "executable", "environment") + + def save(self, *args, **kwargs): + tablelist = self.get_changes_table() + max_change, max_change_ben, max_change_color = 0, None, "none" + max_trend, max_trend_ben, max_trend_color = 0, None, "none" + average_change, average_change_units, average_change_color = 0, None, "none" + average_trend, average_trend_units, average_trend_color = 0, None, "none" + + # Get default threshold values + change_threshold = 3.0 + trend_threshold = 4.0 + if hasattr(settings, 'change_threshold') and settings.change_threshold != None: + change_threshold = settings.change_threshold + if hasattr(settings, 'trend_threshold') and settings.trend_threshold != None: + trend_threshold = settings.trend_threshold + + # Fetch big changes for each unit type and each benchmark + for units in tablelist: + # Total change + val = units['totals']['change'] + if val == "-": continue + color = self.getcolorcode(val, units['lessisbetter'], change_threshold) + if self.is_big_change(val, color, average_change, average_change_color): + # Do update biggest total change + average_change = val + average_change_units = units['units_title'] + average_change_color = color + # Total trend + val = units['totals']['trend'] + if val == "-": continue + color = self.getcolorcode(val, units['lessisbetter'], trend_threshold) + if self.is_big_change(val, color, average_trend, average_trend_color): + # Do update biggest total trend change + average_trend = val + average_trend_units = units['units_title'] + # A trend break is only a warning + if color == "red": + color = "yellow" + average_trend_color = color + for row in units['rows']: + # Single change + val = row['change'] + if val == "-": continue + color = self.getcolorcode(val, units['lessisbetter'], change_threshold) + if self.is_big_change(val, color, max_change, max_change_color): + # Do update biggest single change + max_change = val + max_change_ben = row['benchmark'] + max_change_color = color + # Single trend + val = row['trend'] + if val == "-": continue + color = self.getcolorcode(val, units['lessisbetter'], trend_threshold) + if self.is_big_change(val, color, max_trend, max_trend_color): + # Do update biggest single trend change + max_trend = val + max_trend_ben = row['benchmark'] + # A trend break is only a warning + if color == "red": + color = "yellow" + max_trend_color = color + + if abs(max_trend) > trend_threshold: + self.summary = "%s trend %.1f%%" % ( + max_trend_ben, round(max_trend, 1)) + self.colorcode = max_trend_color + if abs(average_trend) > trend_threshold: + if average_trend_color == "yellow" or self.colorcode != "yellow": + self.summary = "Average %s trend %.1f%%" % ( + average_trend_units.lower(), round(average_trend, 1)) + self.colorcode = average_trend_color + if abs(max_change) > change_threshold: + if max_change_color == "red" or self.colorcode != "yellow": + self.summary = "%s %.1f%%" % ( + max_change_ben, round(max_change, 1)) + self.colorcode = max_change_color + if abs(average_change) > change_threshold: + if average_change_color == "red" or\ + (self.colorcode != "red" and self.colorcode != "yellow"): + self.summary = "Average %s %.1f%%" % ( + average_change_units.lower(), round(average_change, 1)) + self.colorcode = average_change_color + + super(Report, self).save(*args, **kwargs) + + def is_big_change(self, val, color, current_val, current_color): + if color == "red" and\ + abs(val) > abs(current_val): + return True + elif color == "green" and current_color != "red" and \ + abs(val) > abs(current_val): + return True + else: + return False + + def getcolorcode(self, val, lessisbetter, threshold): + if lessisbetter: + val = -val + colorcode = "yellow" + if val < -threshold: + colorcode = "red" + elif val > threshold: + colorcode = "green" + return colorcode; + + def get_changes_table(self, trend_depth=10): + lastrevisions = Revision.objects.filter( + project=self.executable.project + ).filter( + date__lte=self.revision.date + ).order_by('-date')[:trend_depth+1] + lastrevision = lastrevisions[0]#same as self.revision unless in a different branch + + change_list = [] + 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=lastrevision + ).filter( + environment=self.environment + ).filter( + executable=self.executable + ) + + tablelist = [] + for units in Benchmark.objects.all().values('units').distinct(): + currentlist = [] + units_title = "" + hasmin = False + hasmax = False + has_stddev = False + smallest = 1000 + totals = {'change': [], 'trend': [],} + for bench in Benchmark.objects.filter(units=units['units']): + units_title = bench.units_title + lessisbetter = bench.lessisbetter + resultquery = result_list.filter(benchmark=bench) + if not len(resultquery): continue + + resobj = resultquery.filter(benchmark=bench)[0] + + std_dev = resobj.std_dev + if std_dev is not None: has_stddev = True + else: std_dev = "-" + + val_min = resobj.val_min + if val_min is not None: hasmin = True + else: val_min = "-" + + val_max = resobj.val_max + if val_max is not None: hasmax = True + else: val_max = "-" + + # Calculate percentage change relative to previous result + result = resobj.value + change = "-" + if len(change_list): + c = change_list.filter(benchmark=bench) + if c.count() and c[0].value and result: + change = (result - c[0].value)*100/c[0].value + totals['change'].append(result / c[0].value) + + # Calculate trend: + # percentage change relative to average of 3 previous results + # Calculate past average + average = 0 + averagecount = 0 + if len(pastrevisions): + for rev in pastrevisions: + past_rev = Result.objects.filter( + revision=rev + ).filter( + environment=self.environment + ).filter( + executable=self.executable + ).filter(benchmark=bench) + if past_rev.count(): + average += past_rev[0].value + averagecount += 1 + trend = "-" + if average: + average = average / averagecount + trend = (result - average)*100/average + totals['trend'].append(result / average) + + # Retain lowest number different than 0 + # to be used later for calculating significant digits + if result < smallest and result: + smallest = result + + currentlist.append({ + 'benchmark': bench, + 'result': result, + 'std_dev': std_dev, + 'val_min': val_min, + 'val_max': val_max, + 'change': change, + 'trend': trend + }) + + # Compute Arithmetic averages + for key in totals.keys(): + if len(totals[key]): + totals[key] = float(sum(totals[key]) / len(totals[key])) + else: + totals[key] = "-" + + if totals['change'] != "-": + totals['change'] = (totals['change'] - 1) * 100#transform ratio to percentage + if totals['trend'] != "-": + totals['trend'] = (totals['trend'] - 1) * 100#transform ratio to percentage + + # Calculate significant digits + digits = 2; + while smallest < 1: + smallest *= 10 + digits += 1 + + tablelist.append({ + 'units': units['units'], + 'units_title': units_title, + 'lessisbetter': lessisbetter, + 'has_stddev': has_stddev, + 'hasmin': hasmin, + 'hasmax': hasmax, + 'precission': digits, + 'totals': totals, + 'rows': currentlist + }) + return tablelist diff --git a/speedcenter/codespeed/settings.py b/speedcenter/codespeed/settings.py index 32481c0b..120ab807 100644 --- a/speedcenter/codespeed/settings.py +++ b/speedcenter/codespeed/settings.py @@ -8,11 +8,11 @@ # Example: defaultbaseline = {'executable': 'myexe', 'revision': '21'} # Threshold that determines when a performance change over the last result is significant -changethreshold = 3.0 +change_threshold = 3.0 # Threshold that determines when a performance change # over a number of revisions is significant -trendthreshold = 3.0 +trend_threshold = 4.0 # Changes view options ## defaultexecutable = None # Executable that should be chosen as default in the changes view diff --git a/speedcenter/codespeed/views.py b/speedcenter/codespeed/views.py index 14458eda..090fd79f 100644 --- a/speedcenter/codespeed/views.py +++ b/speedcenter/codespeed/views.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from django.shortcuts import get_object_or_404, render_to_response -from codespeed.models import Project, Revision, Result, Executable, Benchmark, Environment +from codespeed.models import Project, Revision, Result, Executable, Benchmark +from codespeed.models import Environment, Report from django.http import HttpResponse, Http404, HttpResponseNotAllowed, HttpResponseBadRequest, HttpResponseNotFound from codespeed import settings from datetime import datetime @@ -457,166 +458,33 @@ def getchangestable(request): selectedrev = Revision.objects.get( commitid=data['rev'], project=executable.project ) - date = selectedrev.date - lastrevisions = Revision.objects.filter( - project=executable.project - ).filter( - date__lte=date - ).order_by('-date')[:trendconfig+1] - lastrevision = lastrevisions[0] - - change_list = [] - pastrevisions = [] - if len(lastrevisions) > 1: - changerevision = lastrevisions[1] - change_list = Result.objects.filter( - revision=changerevision - ).filter( - environment=environment - ).filter( - executable=executable - ) - pastrevisions = lastrevisions[trendconfig-2:trendconfig+1] - - result_list = Result.objects.filter( - revision=lastrevision - ).filter( - environment=environment - ).filter( - executable=executable + report, created = Report.objects.get_or_create( + executable=executable, environment=environment, revision=selectedrev ) - - tablelist = [] - for units in Benchmark.objects.all().values('units').distinct(): - currentlist = [] - units_title = "" - hasmin = False - hasmax = False - has_stddev = False - smallest = 1000 - totals = {'change': [], 'trend': [],} - for bench in Benchmark.objects.filter(units=units['units']): - units_title = bench.units_title - lessisbetter = bench.lessisbetter - resultquery = result_list.filter(benchmark=bench) - if not len(resultquery): continue - - resobj = resultquery.filter(benchmark=bench)[0] - - std_dev = resobj.std_dev - if std_dev is not None: has_stddev = True - else: std_dev = "-" - - val_min = resobj.val_min - if val_min is not None: hasmin = True - else: val_min = "-" - - val_max = resobj.val_max - if val_max is not None: hasmax = True - else: val_max = "-" - - # Calculate percentage change relative to previous result - result = resobj.value - change = "-" - if len(change_list): - c = change_list.filter(benchmark=bench) - if c.count() and c[0].value and result: - change = (result - c[0].value)*100/c[0].value - totals['change'].append(result / c[0].value) - - # Calculate trend: - # percentage change relative to average of 3 previous results - # Calculate past average - average = 0 - averagecount = 0 - if len(pastrevisions): - for rev in pastrevisions: - past_rev = Result.objects.filter( - revision=rev - ).filter( - environment=environment - ).filter( - executable=executable - ).filter(benchmark=bench) - if past_rev.count(): - average += past_rev[0].value - averagecount += 1 - trend = "-" - if average: - average = average / averagecount - trend = (result - average)*100/average - totals['trend'].append(result / average) - - # Retain lowest number different than 0 - # to be used later for calculating significant digits - if result < smallest and result: - smallest = result - - currentlist.append({ - 'benchmark': bench, - 'result': result, - 'std_dev': std_dev, - 'val_min': val_min, - 'val_max': val_max, - 'change': change, - 'trend': trend - }) - - # Compute Arithmetic averages - for key in totals.keys(): - if len(totals[key]): - totals[key] = float(sum(totals[key]) / len(totals[key])) - else: - totals[key] = "-" - - if totals['change'] != "-": - totals['change'] = (totals['change'] - 1) * 100#transform ratio to percentage - if totals['trend'] != "-": - totals['trend'] = (totals['trend'] - 1) * 100#transform ratio to percentage - - # Calculate significant digits - digits = 2; - while smallest < 1: - smallest *= 10 - digits += 1 - - tablelist.append({ - 'units': units['units'], - 'units_title': units_title, - 'lessisbetter': lessisbetter, - 'has_stddev': has_stddev, - 'hasmin': hasmin, - 'hasmax': hasmax, - 'precission': digits, - 'totals': totals, - 'rows': currentlist - }) - + tablelist = report.get_changes_table(trendconfig) + if not len(tablelist): return HttpResponse('

No results for this parameters

') return render_to_response('codespeed/changes_table.html', { 'tablelist': tablelist, 'trendconfig': trendconfig, - 'executable': executable, - 'lastrevision': lastrevision, - 'totals': totals, 'rev': selectedrev, 'exe': executable, 'env': environment, }) - + def changes(request): if request.method != 'GET': return HttpResponseNotAllowed('GET') data = request.GET # Configuration of default parameters - defaultchangethres = 3 - defaulttrendthres = 3 - if hasattr(settings, 'changethreshold') and settings.changethreshold != None: - defaultchangethres = settings.changethreshold - if hasattr(settings, 'trendthreshold') and settings.trendthreshold != None: - defaulttrendthres = settings.trendthreshold + defaultchangethres = 3.0 + defaulttrendthres = 4.0 + if hasattr(settings, 'change_threshold') and settings.change_threshold != None: + defaultchangethres = settings.change_threshold + if hasattr(settings, 'trend_threshold') and settings.trend_threshold != None: + defaulttrendthres = settings.trend_threshold defaulttrend = 10 trends = [5, 10, 20, 50, 100] @@ -624,7 +492,8 @@ def changes(request): defaulttrend = int(data['tre']) defaultenvironment = getdefaultenvironment() - if not defaultenvironment: return no_environment_error() + if not defaultenvironment: + return no_environment_error() if 'env' in data: try: defaultenvironment = Environment.objects.get(name=data['env']) diff --git a/speedcenter/templates/codespeed/changes_table.html b/speedcenter/templates/codespeed/changes_table.html index 1a6ee041..b204c5b7 100644 --- a/speedcenter/templates/codespeed/changes_table.html +++ b/speedcenter/templates/codespeed/changes_table.html @@ -72,13 +72,13 @@ Date{{ rev.date }} {% ifnotequal rev.project.repo_type "N" %}Repo{{ rev.project.repo_path }}{% endifnotequal %} -{% ifnotequal executable.project.repo_type 'N' %} +{% ifnotequal exe.project.repo_type 'N' %} Commit logs - + Loading...