@@ -82,16 +87,14 @@
{% block extra_script %}
{{ block.super }}
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
@@ -128,7 +121,7 @@
executables: [{% for exe in checkedexecutables %}{{ exe.id }}, {% endfor %}],
branches: [{% for b in branch_list %}"{{ branch }}", {% endfor %}],
benchmark: "{{ defaultbenchmark }}",
- environment: {{ defaultenvironment.id }},
+ environments: [{% for env in defaultenvironments %}{{ env.id }}, {% endfor %}],
equidistant: "{{ defaultequid }}",
quartiles: "{{ defaultquarts }}",
extrema: "{{ defaultextr }}"
diff --git a/codespeed/tests/settings.py b/codespeed/tests/settings.py
new file mode 100644
index 00000000..a9058c33
--- /dev/null
+++ b/codespeed/tests/settings.py
@@ -0,0 +1,53 @@
+import os
+from codespeed.settings import *
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': ':memory:',
+ }
+}
+
+DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+SECRET_KEY = 'test-secret-key-not-for-production'
+USE_TZ = False
+TIME_ZONE = 'UTC'
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.admin',
+ 'django.contrib.staticfiles',
+ 'codespeed',
+)
+
+MIDDLEWARE = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'speed_pypy.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+STATIC_URL = '/static/'
+REPOSITORY_BASE_PATH = '/tmp/codespeed-test-repos'
diff --git a/codespeed/tests/test_auth.py b/codespeed/tests/test_auth.py
index 8e2868bc..bc2b356b 100644
--- a/codespeed/tests/test_auth.py
+++ b/codespeed/tests/test_auth.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-import mock
+from unittest import mock
from django.test import TestCase, override_settings
from django.http import HttpResponse
diff --git a/codespeed/tests/test_commits.py b/codespeed/tests/test_commits.py
new file mode 100644
index 00000000..12af1c57
--- /dev/null
+++ b/codespeed/tests/test_commits.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+from datetime import datetime, timedelta
+import subprocess
+import tempfile
+import os
+
+from django.test import TestCase, override_settings
+from django.urls import reverse
+
+from codespeed.models import (Project, Benchmark, Revision, Branch, Executable,
+ Environment, Result, Report)
+
+hgdir = ''
+
+def setUpModule():
+ global hgdir
+ hgdir = tempfile.TemporaryDirectory()
+ subprocess.check_call(['hg', 'init'], cwd=hgdir.name)
+ # Add some commits
+ readme = os.path.join(hgdir.name, 'README.md')
+ file1 = os.path.join(hgdir.name, 'file1.py')
+ with open(readme, 'w') as hg_file:
+ hg_file.write('readme')
+ subprocess.check_call(['hg', 'add', readme], cwd=hgdir.name)
+ subprocess.check_call(['hg', 'commit', '-m', "first commit"], cwd=hgdir.name)
+ with open(file1, 'w') as hg_file:
+ hg_file.write('value = 10')
+ subprocess.check_call(['hg', 'add', file1], cwd=hgdir.name)
+ subprocess.check_call(['hg', 'commit', '-m', "second commit"], cwd=hgdir.name)
+
+def tearDownModule():
+ hgdir.cleanup()
+
+class TestMercurial(TestCase):
+
+ def setUp(self):
+ self.days = 0
+ self.hgdir = hgdir.name
+ self.starttime = datetime.now() + timedelta(days=-100)
+
+ Project(repo_type='M', name='pro',
+ repo_path=str(self.hgdir)).save()
+ self.pro = Project.objects.get(name='pro')
+
+ Branch(project=self.pro, name='default').save()
+ self.b = Branch.objects.get(name='default')
+
+ Environment(name='Walden Pond').save()
+ Executable(name='walden', project=self.pro).save()
+ Benchmark(name='TestBench').save()
+
+ self.env = Environment.objects.get(name='Walden Pond')
+ self.exe = Executable.objects.get(name='walden')
+ self.bench = Benchmark.objects.get(name='TestBench')
+ cmd = ['hg', '-R', self.hgdir, 'id', '-r', '1']
+ self.cid = subprocess.check_output(cmd).split()[0]
+ Revision(commitid=self.cid.decode('utf-8'), date=self.starttime, branch=self.b,
+ project=self.pro).save()
+
+ def test_hg(self):
+ with self.settings(REPOSITORY_BASE_PATH=os.path.dirname(self.hgdir)):
+ response = self.client.get(reverse('displaylogs'), {'revisionid':1})
+ assert response.status_code == 200, 'expected 200 got %d' % response.status_code
diff --git a/codespeed/tests/test_models.py b/codespeed/tests/test_models.py
index 5d116c73..eb1fc58d 100644
--- a/codespeed/tests/test_models.py
+++ b/codespeed/tests/test_models.py
@@ -104,7 +104,7 @@ def test_good_benchmark_change_beats_bad_average_trend(self):
self.make_result(x, rev=s1, benchmark=b1)
self.make_result(changes[-2] * .97, rev=s1, benchmark=b1)
rep = self.make_report(s1)
- self.assertEquals('green', rep.colorcode)
+ self.assertEqual('green', rep.colorcode)
self.assertIn('b1', rep.summary)
def test_good_average_change_beats_bad_average_trend(self):
@@ -116,7 +116,7 @@ def test_good_average_change_beats_bad_average_trend(self):
self.make_result(x, rev=s1, benchmark=b1)
self.make_result(changes[-2] * .92, rev=s1, benchmark=b1)
rep = self.make_report(s1)
- self.assertEquals('green', rep.colorcode)
+ self.assertEqual('green', rep.colorcode)
self.assertIn('Average', rep.summary)
def test_good_change_beats_good_trend(self):
@@ -156,7 +156,7 @@ def test_bad_change_beats_good_trend(self):
rep = self.make_report(s1)
self.assertIn('b1', rep.summary)
self.assertNotIn('trend', rep.summary)
- self.assertEquals('red', rep.colorcode)
+ self.assertEqual('red', rep.colorcode)
def test_bad_beats_good_change(self):
b1 = self.make_bench('b1')
@@ -184,7 +184,7 @@ def test_bigger_bad_beats_smaller_bad(self):
rep = self.make_report(s2)
self.assertIn('b1', rep.summary)
- self.assertEquals('red', rep.colorcode)
+ self.assertEqual('red', rep.colorcode)
def test_multiple_quantities(self):
b1 = self.make_bench('b1', quantity='Space', units='bytes')
@@ -195,8 +195,8 @@ def test_multiple_quantities(self):
self.make_result(1.5, rev=s2, benchmark=b1)
rep = self.make_report(s2)
- self.assertRegexpMatches(rep.summary, '[sS]pace')
- self.assertEquals('red', rep.colorcode)
+ self.assertRegex(rep.summary, '[sS]pace')
+ self.assertEqual('red', rep.colorcode)
def make_result(self, value, rev=None, benchmark=None):
from uuid import uuid4
@@ -280,18 +280,18 @@ def test_github_browsing_url(self):
# It should work with https:// as well as git:// urls
self.github_project.save()
- self.assertEquals(self.github_project.commit_browsing_url,
+ self.assertEqual(self.github_project.commit_browsing_url,
'https://github.com/tobami/codespeed.git/'
'commit/{commitid}')
self.github_project.repo_path = 'git://github.com/tobami/codespeed.git'
self.github_project.save()
- self.assertEquals(self.github_project.commit_browsing_url,
+ self.assertEqual(self.github_project.commit_browsing_url,
'https://github.com/tobami/codespeed.git/'
'commit/{commitid}')
# If filled in, commit browsing url should not change
self.github_project.commit_browsing_url = 'https://example.com/{commitid}'
self.github_project.save()
- self.assertEquals(self.github_project.commit_browsing_url,
+ self.assertEqual(self.github_project.commit_browsing_url,
'https://example.com/{commitid}')
diff --git a/codespeed/tests/test_settings.py b/codespeed/tests/test_settings.py
index f525bd22..e621d50c 100644
--- a/codespeed/tests/test_settings.py
+++ b/codespeed/tests/test_settings.py
@@ -12,7 +12,7 @@ def setUp(self):
self.cs_setting_keys = [key for key in dir(default_settings) if key.isupper()]
def test_website_name(self):
- """See if WEBSITENAME is set
+ """See if WEBSITE_NAME is set
"""
self.assertTrue(default_settings.WEBSITE_NAME)
self.assertEqual(default_settings.WEBSITE_NAME, 'MySpeedSite',
diff --git a/codespeed/tests/test_views.py b/codespeed/tests/test_views.py
index c56132be..6f46c445 100644
--- a/codespeed/tests/test_views.py
+++ b/codespeed/tests/test_views.py
@@ -36,15 +36,15 @@ def test_add_correct_result(self):
response = self.client.post(self.path, self.data)
# Check that we get a success response
- self.assertEquals(response.status_code, 202)
- self.assertEquals(response.content.decode(), "Result data saved successfully")
+ self.assertEqual(response.status_code, 202)
+ self.assertEqual(response.content.decode(), "Result data saved successfully")
# Check that the data was correctly saved
e = Environment.objects.get(name='Dual Core')
b = Benchmark.objects.get(name='float')
- self.assertEquals(b.benchmark_type, "C")
- self.assertEquals(b.units, "seconds")
- self.assertEquals(b.lessisbetter, True)
+ self.assertEqual(b.source, "legacy")
+ self.assertEqual(b.units, "seconds")
+ self.assertEqual(b.lessisbetter, True)
p = Project.objects.get(name='MyProject')
branch = Branch.objects.get(name='default', project=p)
r = Revision.objects.get(commitid='23', branch=branch)
@@ -68,15 +68,15 @@ def test_add_non_default_result(self):
modified_data['max'] = 2
modified_data['min'] = 1.0
response = self.client.post(self.path, modified_data)
- self.assertEquals(response.status_code, 202)
- self.assertEquals(response.content.decode(), "Result data saved successfully")
+ self.assertEqual(response.status_code, 202)
+ self.assertEqual(response.content.decode(), "Result data saved successfully")
e = Environment.objects.get(name='Dual Core')
p = Project.objects.get(name='MyProject')
branch = Branch.objects.get(name='default', project=p)
r = Revision.objects.get(commitid='23', branch=branch)
# Tweak the resolution down to avoid failing over very slight differences:
- self.assertEquals(r.date, revision_date)
+ self.assertEqual(r.date, revision_date)
i = Executable.objects.get(name='myexe O3 64bits')
b = Benchmark.objects.get(name='float')
@@ -86,18 +86,18 @@ def test_add_non_default_result(self):
benchmark=b,
environment=e
)
- self.assertEquals(res.date, result_date)
- self.assertEquals(res.std_dev, 1.11111)
- self.assertEquals(res.val_max, 2)
- self.assertEquals(res.val_min, 1)
+ self.assertEqual(res.date, result_date)
+ self.assertEqual(res.std_dev, 1.11111)
+ self.assertEqual(res.val_max, 2)
+ self.assertEqual(res.val_min, 1)
def test_bad_environment(self):
"""Should return 400 when environment does not exist"""
bad_name = '10 Core'
self.data['environment'] = bad_name
response = self.client.post(self.path, self.data)
- self.assertEquals(response.status_code, 400)
- self.assertEquals(response.content.decode(),
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(response.content.decode(),
"Environment " + bad_name + " not found")
self.data['environment'] = 'Dual Core'
@@ -107,20 +107,20 @@ def test_empty_argument(self):
backup = self.data[key]
self.data[key] = ""
response = self.client.post(self.path, self.data)
- self.assertEquals(response.status_code, 400)
- self.assertEquals(
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(
response.content.decode(),
'Value for key "' + key + '" empty in request')
self.data[key] = backup
def test_missing_argument(self):
"""Should respond 400 when a POST request is missing an argument"""
- for key in self.data:
+ for key in list(self.data):
backup = self.data[key]
del(self.data[key])
response = self.client.post(self.path, self.data)
- self.assertEquals(response.status_code, 400)
- self.assertEquals(
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(
response.content.decode(),
'Key "' + key + '" missing from request')
self.data[key] = backup
@@ -130,7 +130,7 @@ def test_report_is_not_created(self):
self.client.post(self.path, self.data)
number_of_reports = len(Report.objects.all())
# After adding one result for one revision, there should be no reports
- self.assertEquals(number_of_reports, 0)
+ self.assertEqual(number_of_reports, 0)
def test_report_is_created(self):
"""Should create a report when adding a result for two revisions"""
@@ -142,15 +142,14 @@ def test_report_is_created(self):
# Second result should trigger report creation
self.client.post(self.path, modified_data)
number_of_reports = len(Report.objects.all())
- self.assertEquals(number_of_reports, 1)
+ self.assertEqual(number_of_reports, 1)
def test_submit_data_with_none_timestamp(self):
"""Should add a default revision date when timestamp is None"""
modified_data = copy.deepcopy(self.data)
- # The value None will get urlencoded and converted to a "None" string
- modified_data['revision_date'] = None
+ modified_data['revision_date'] = 'None'
response = self.client.post(self.path, modified_data)
- self.assertEquals(response.status_code, 202)
+ self.assertEqual(response.status_code, 202)
def test_add_result_with_no_project(self):
"""Should add a revision with the project"""
@@ -158,10 +157,48 @@ def test_add_result_with_no_project(self):
modified_data['project'] = "My new project"
modified_data['executable'] = "My new executable"
response = self.client.post(self.path, modified_data)
- self.assertEquals(response.status_code, 202)
- self.assertEquals(
+ self.assertEqual(response.status_code, 202)
+ self.assertEqual(
response.content.decode(), "Result data saved successfully")
+ def test_suite_version_is_saved(self):
+ """suite_version in the payload should be stored on the Result"""
+ modified_data = copy.deepcopy(self.data)
+ modified_data['suite_version'] = '2.3.1'
+ self.client.post(self.path, modified_data)
+ res = Result.objects.get(
+ revision__commitid='23',
+ benchmark__name='float',
+ )
+ self.assertEqual(res.suite_version, '2.3.1')
+
+ def test_suite_version_defaults_to_empty(self):
+ """Omitting suite_version should store an empty string"""
+ self.client.post(self.path, self.data)
+ res = Result.objects.get(
+ revision__commitid='23',
+ benchmark__name='float',
+ )
+ self.assertEqual(res.suite_version, '')
+
+ def test_source_set_on_new_benchmark(self):
+ """source in the payload should be set on an auto-created Benchmark"""
+ modified_data = copy.deepcopy(self.data)
+ modified_data['benchmark'] = 'newbench'
+ modified_data['source'] = 'pyperformance'
+ self.client.post(self.path, modified_data)
+ b = Benchmark.objects.get(name='newbench')
+ self.assertEqual(b.source, 'pyperformance')
+
+ def test_source_not_changed_on_existing_benchmark(self):
+ """source in the payload should not overwrite an existing Benchmark"""
+ self.client.post(self.path, self.data)
+ modified_data = copy.deepcopy(self.data)
+ modified_data['source'] = 'pyperformance'
+ self.client.post(self.path, modified_data)
+ b = Benchmark.objects.get(name='float')
+ self.assertEqual(b.source, 'legacy')
+
@override_settings(ALLOW_ANONYMOUS_POST=True)
class TestAddJSONResults(TestCase):
@@ -210,7 +247,7 @@ def test_get_returns_405(self):
response = self.client.get(self.path,
{'json': json.dumps(self.data)})
- self.assertEquals(response.status_code, 405)
+ self.assertEqual(response.status_code, 405)
def test_add_correct_results(self):
"""Should add all results when the request data is valid"""
@@ -218,16 +255,16 @@ def test_add_correct_results(self):
{'json': json.dumps(self.data)})
# Check that we get a success response
- self.assertEquals(response.status_code, 202)
- self.assertEquals(response.content.decode(),
+ self.assertEqual(response.status_code, 202)
+ self.assertEqual(response.content.decode(),
"All result data saved successfully")
# Check that the data was correctly saved
e = Environment.objects.get(name='bigdog')
b = Benchmark.objects.get(name='Richards')
- self.assertEquals(b.benchmark_type, "C")
- self.assertEquals(b.units, "seconds")
- self.assertEquals(b.lessisbetter, True)
+ self.assertEqual(b.source, "legacy")
+ self.assertEqual(b.units, "seconds")
+ self.assertEqual(b.lessisbetter, True)
p = Project.objects.get(name='pypy')
branch = Branch.objects.get(name='default', project=p)
r = Revision.objects.get(commitid='123', branch=branch)
@@ -271,8 +308,8 @@ def test_bad_environment(self):
response = self.client.post(self.path,
{'json': json.dumps(self.data)})
- self.assertEquals(response.status_code, 400)
- self.assertEquals(
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(
response.content.decode(), "Environment " + bad_name + " not found")
data['environment'] = 'bigdog'
@@ -284,8 +321,8 @@ def test_empty_argument(self):
data[key] = ""
response = self.client.post(self.path,
{'json': json.dumps(self.data)})
- self.assertEquals(response.status_code, 400)
- self.assertEquals(
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(
response.content.decode(),
'Value for key "' + key + '" empty in request')
data[key] = backup
@@ -293,13 +330,13 @@ def test_empty_argument(self):
def test_missing_argument(self):
'''Should return 400 when making a request with a missing argument'''
data = self.data[2]
- for key in data:
+ for key in list(data):
backup = data[key]
del(data[key])
response = self.client.post(self.path,
{'json': json.dumps(self.data)})
- self.assertEquals(response.status_code, 400)
- self.assertEquals(
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(
response.content.decode(), 'Key "' + key + '" missing from request')
data[key] = backup
@@ -310,13 +347,13 @@ def test_report_is_created(self):
{'json': json.dumps(self.data)})
# Check that we get a success response
- self.assertEquals(response.status_code, 202)
+ self.assertEqual(response.status_code, 202)
number_of_reports = len(Report.objects.all())
# After adding 4 result for 3 revisions, only 2 reports should be created
# The third revision will need an extra result for Richards2 in order
# to trigger report creation
- self.assertEquals(number_of_reports, 1)
+ self.assertEqual(number_of_reports, 1)
class TestTimeline(TestCase):
@@ -326,19 +363,19 @@ def test_fixture(self):
"""Test the loaded fixture data
"""
env = Environment.objects.filter(name="Dual Core")
- self.assertEquals(len(env), 1)
+ self.assertEqual(len(env), 1)
benchmarks = Benchmark.objects.filter(name="float")
- self.assertEquals(len(benchmarks), 1)
- self.assertEquals(benchmarks[0].units, "seconds")
+ self.assertEqual(len(benchmarks), 1)
+ self.assertEqual(benchmarks[0].units, "seconds")
results = benchmarks[0].results.all()
- self.assertEquals(len(results), 8)
+ self.assertEqual(len(results), 8)
def test_timeline(self):
path = reverse('timeline')
response = self.client.get(path)
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
responsedata = response.content.decode()
- self.assertIn('My Own Title\n: Timeline', responsedata)
+ self.assertIn("MyProject's Speed Center: Timeline", responsedata)
def test_gettimelinedata(self):
"""Test that gettimelinedata returns correct timeline data
@@ -352,28 +389,28 @@ def test_gettimelinedata(self):
"revs": "2"
}
response = self.client.get(path, data)
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
responsedata = json.loads(response.getvalue().decode())
- self.assertEquals(
+ self.assertEqual(
responsedata['error'], "None", "there should be no errors")
- self.assertEquals(
+ self.assertEqual(
len(responsedata['timelines']), 1, "there should be 1 benchmark")
- self.assertEquals(
+ self.assertEqual(
len(responsedata['timelines'][0]['branches']),
2,
"there should be 2 branches")
- self.assertEquals(
+ self.assertEqual(
len(responsedata['timelines'][0]['branches']['default']),
1,
"there should be 1 timeline for master")
- self.assertEquals(
+ self.assertEqual(
len(responsedata['timelines'][0]['branches']['master']['1']),
2,
"There are 2 datapoints")
- self.assertEquals(
+ self.assertEqual(
responsedata['timelines'][0]['branches']['master']['1'][1],
- [u'2011/04/13 17:04:22 ', 2000.0, 1.11111, u'2', u'', u'master'])
+ [u'2011/04/13 17:04:22 ', 2000.0, 1.11111, u'2', u'', u'master', u''])
@override_settings(ALLOW_ANONYMOUS_POST=True)
diff --git a/codespeed/urls.py b/codespeed/urls.py
index af61e5e2..e8976a45 100644
--- a/codespeed/urls.py
+++ b/codespeed/urls.py
@@ -1,35 +1,35 @@
# -*- coding: utf-8 -*-
-from django.conf.urls import url
+from django.urls import re_path
from django.views.generic import TemplateView
from codespeed import views
from codespeed.feeds import LatestEntries, LatestSignificantEntries
urlpatterns = [
- url(r'^$', views.HomeView.as_view(), name='home'),
- url(r'^about/$',
+ re_path(r'^$', views.HomeView.as_view(), name='home'),
+ re_path(r'^about/$',
TemplateView.as_view(template_name='about.html'), name='about'),
# RSS for reports
- url(r'^feeds/latest/$', LatestEntries(), name='latest-results'),
- url(r'^feeds/latest_significant/$', LatestSignificantEntries(),
+ re_path(r'^feeds/latest/$', LatestEntries(), name='latest-results'),
+ re_path(r'^feeds/latest_significant/$', LatestSignificantEntries(),
name='latest-significant-results'),
]
urlpatterns += [
- url(r'^historical/json/$', views.gethistoricaldata, name='gethistoricaldata'),
- url(r'^reports/$', views.reports, name='reports'),
- url(r'^changes/$', views.changes, name='changes'),
- url(r'^changes/table/$', views.getchangestable, name='getchangestable'),
- url(r'^changes/logs/$', views.displaylogs, name='displaylogs'),
- url(r'^timeline/$', views.timeline, name='timeline'),
- url(r'^timeline/json/$', views.gettimelinedata, name='gettimelinedata'),
- url(r'^comparison/$', views.comparison, name='comparison'),
- url(r'^comparison/json/$', views.getcomparisondata, name='getcomparisondata'),
- url(r'^makeimage/$', views.makeimage, name='makeimage'),
+ re_path(r'^historical/json/$', views.gethistoricaldata, name='gethistoricaldata'),
+ re_path(r'^reports/$', views.reports, name='reports'),
+ re_path(r'^changes/$', views.changes, name='changes'),
+ re_path(r'^changes/table/$', views.getchangestable, name='getchangestable'),
+ re_path(r'^changes/logs/$', views.displaylogs, name='displaylogs'),
+ re_path(r'^timeline/$', views.timeline, name='timeline'),
+ re_path(r'^timeline/json/$', views.gettimelinedata, name='gettimelinedata'),
+ re_path(r'^comparison/$', views.comparison, name='comparison'),
+ re_path(r'^comparison/json/$', views.getcomparisondata, name='getcomparisondata'),
+ re_path(r'^makeimage/$', views.makeimage, name='makeimage'),
]
urlpatterns += [
# URLs for adding results
- url(r'^result/add/json/$', views.add_json_results, name='add-json-results'),
- url(r'^result/add/$', views.add_result, name='add-result'),
+ re_path(r'^result/add/json/$', views.add_json_results, name='add-json-results'),
+ re_path(r'^result/add/$', views.add_result, name='add-result'),
]
diff --git a/codespeed/views.py b/codespeed/views.py
index 408d62b0..21928771 100644
--- a/codespeed/views.py
+++ b/codespeed/views.py
@@ -11,7 +11,7 @@
from django.http import HttpResponse, Http404, HttpResponseBadRequest, \
HttpResponseNotFound, StreamingHttpResponse
from django.db.models import F
-from django.shortcuts import get_object_or_404, render_to_response
+from django.shortcuts import get_object_or_404, render
from django.views.decorators.http import require_GET, require_POST
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
@@ -33,7 +33,7 @@
def no_environment_error(request):
admin_url = reverse('admin:codespeed_environment_changelist')
- return render_to_response('codespeed/nodata.html', {
+ return render(request, 'codespeed/nodata.html', {
'message': ('You need to configure at least one Environment. '
'Please go to the '
'
admin interface' % admin_url)
@@ -42,7 +42,7 @@ def no_environment_error(request):
def no_default_project_error(request):
admin_url = reverse('admin:codespeed_project_changelist')
- return render_to_response('codespeed/nodata.html', {
+ return render(request, 'codespeed/nodata.html', {
'message': ('You need to configure at least one one Project as '
'default (checked "Track changes" field).
'
'Please go to the '
@@ -51,13 +51,13 @@ def no_default_project_error(request):
def no_executables_error(request):
- return render_to_response('codespeed/nodata.html', {
+ return render(request, 'codespeed/nodata.html', {
'message': 'There needs to be at least one executable'
})
def no_data_found(request):
- return render_to_response('codespeed/nodata.html', {
+ return render(request, 'codespeed/nodata.html', {
'message': 'No data found'
})
@@ -69,17 +69,26 @@ def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
context['show_reports'] = settings.SHOW_REPORTS
context['show_historical'] = settings.SHOW_HISTORICAL
- historical_settings = ['SHOW_HISTORICAL', 'DEF_BASELINE', 'DEF_EXECUTABLE']
+ historical_settings = ['SHOW_HISTORICAL', 'DEF_BASELINES', 'DEF_EXECUTABLES']
if not all(getattr(settings, var) for var in historical_settings):
context['show_historical'] = False
return context
- baseline_exe = Executable.objects.get(
- name=settings.DEF_BASELINE['executable'])
- context['baseline'] = baseline_exe
- default_exe = Executable.objects.get(name=settings.DEF_EXECUTABLE)
- context['default_exe'] = default_exe
- return context
+ try:
+ baseline_exe = Executable.objects.get(
+ name=settings.DEF_BASELINES[0]['executable'])
+ context['baseline'] = baseline_exe
+ def_name = settings.DEF_EXECUTABLES[0]['name']
+ def_project = Project.objects.get(name=settings.DEF_EXECUTABLES[0]['project'])
+ default_exe = Executable.objects.get(name=def_name,
+ project=def_project,
+ )
+ context['default_exe'] = default_exe
+ return context
+ except Exception as e:
+ print('exception', e)
+ context['show_historical'] = False
+ return context
@require_GET
@@ -92,68 +101,97 @@ def gethistoricaldata(request):
env = env.first()
# Fetch Baseline data, filter by executable
- baseline_exe = Executable.objects.get(
- name=settings.DEF_BASELINE['executable'])
- baseline_revs = Revision.objects.filter(
- branch__project=baseline_exe.project).order_by('-date')
- baseline_lastrev = baseline_revs[0]
- for rev in baseline_revs:
- baseline_results = Result.objects.filter(
- executable=baseline_exe, revision=rev, environment=env)
- if baseline_results:
- baseline_lastrev = rev
- break
- if len(baseline_results) == 0:
- logger.error('Could not find results for {} rev="{}" env="{}"'.format(
- baseline_exe, baseline_lastrev, env))
- data['baseline'] = '{} {}'.format(
- settings.DEF_BASELINE['executable'], baseline_lastrev.tag)
-
- default_exe = Executable.objects.get(name=settings.DEF_EXECUTABLE)
- default_branch = Branch.objects.get(
- name=default_exe.project.default_branch,
- project=default_exe.project)
+ baseline_results = []
+ for b in settings.DEF_BASELINES:
+ baseline_exe = Executable.objects.get(
+ name=b['executable'])
+ tag=b['revision']
+ rev = Revision.objects.filter(branch__project=baseline_exe.project, tag=tag)
+ if len(rev) < 1:
+ return HttpResponse(json.dumps(
+ f"Could not find {tag=} for {b['executable']} in database")
+ )
+ rev0 = rev[0]
+ resname = '{} {}'.format(b['executable'], rev0.tag)
+ baseline_results.append((resname, Result.objects.filter(
+ executable=baseline_exe, revision=rev0, environment=env,
+ benchmark__source='legacy')))
+ if not baseline_results[-1][1]:
+ logger.error('Could not find results for {} rev="{}" env="{}"'.format(
+ baseline_exe, rev0, env))
- # Fetch tagged revisions for executable
- default_taggedrevs = Revision.objects.filter(
- branch=default_branch
- ).exclude(tag="").order_by('date')
default_results = {}
- for rev in default_taggedrevs:
- res = Result.objects.filter(
- executable=default_exe, revision=rev, environment=env)
- if not res:
- logger.info('no results for %s %s %s' % (str(default_exe), str(rev), str(env)))
- continue
- default_results[rev.tag] = res
- data['tagged_revs'] = [rev.tag for rev in default_taggedrevs if rev.tag in default_results]
+ all_taggedrevs = []
+ for executable in settings.DEF_EXECUTABLES[::-1]:
+ _def_name = executable['name']
+ _def_project = Project.objects.get(name=executable['project'])
+ _default_exe = Executable.objects.get(name=_def_name, project=_def_project)
+ _default_branch = Branch.objects.get(
+ name=_default_exe.project.default_branch,
+ project=_default_exe.project)
+
+ # Fetch tagged revisions for executable
+ default_taggedrevs = Revision.objects.filter(
+ branch=_default_branch
+ ).exclude(tag="").order_by('date')
+ all_taggedrevs += default_taggedrevs
+ for rev in default_taggedrevs:
+ # Filter to legacy only; pyperformance history can get its own panel later
+ res = Result.objects.filter(
+ executable=_default_exe, revision=rev, environment=env,
+ benchmark__source='legacy')
+ if not res:
+ logger.info("no results for '%s' '%s' '%s'" % (str(_default_exe), str(rev), str(env)))
+ continue
+ default_results[rev.tag] = res
+ data['tagged_revs'] = [rev.tag for rev in all_taggedrevs if rev.tag in default_results]
# Fetch data for latest results
+ executable = settings.DEF_EXECUTABLES[0]
+ def_name = executable['name']
+ def_project = Project.objects.get(name=executable['project'])
+ default_exe = Executable.objects.get(name=def_name, project=def_project)
+ default_branch = Branch.objects.get(
+ name=default_exe.project.default_branch,
+ project=default_exe.project)
revs = Revision.objects.filter(
- branch=default_branch).order_by('-date')[:5]
+ branch=default_branch).order_by('-date')[:100]
default_lastrev = None
- for i in range(4):
- default_lastrev = revs[i]
+ for rev in revs:
+ default_lastrev = rev
if default_lastrev.results.filter(executable=default_exe, environment=env):
break
default_lastrev = None
- if default_lastrev is None:
- return HttpResponse(json.dumps(data))
- default_results['latest'] = Result.objects.filter(
- executable=default_exe, revision=default_lastrev, environment=env)
+ if default_lastrev is not None:
+ default_results['latest'] = Result.objects.filter(
+ executable=default_exe, revision=default_lastrev, environment=env,
+ benchmark__source='legacy')
# Collect data
benchmarks = []
- for res in baseline_results:
+ # Collate first baseline and all the default_results
+ resset = baseline_results[0][1]
+ resname = baseline_results[0][0]
+ data['baseline'] = resname
+ for res in resset:
if res == 0:
continue
benchmarks.append(res.benchmark.name)
- data['results'][res.benchmark.name] = {data['baseline']: res.value}
+ data['results'][res.benchmark.name] = {resname: res.value}
for rev_name in default_results:
val = 0
for default_res in default_results[rev_name]:
if default_res.benchmark.name == res.benchmark.name:
val = default_res.value
- data['results'][res.benchmark.name][rev_name] = val
+ data['results'][res.benchmark.name][rev_name] = val
+ # Collate other baseline
+ for resname, resset in baseline_results[1:]:
+ for res in resset:
+ if res == 0:
+ continue
+ if not res.benchmark.name in data['results']:
+ continue
+ data['results'][res.benchmark.name][resname] = res.value
+ data['tagged_revs'].insert(0, resname)
benchmarks.sort()
data['benchmarks'] = benchmarks
return HttpResponse(json.dumps(data))
@@ -162,29 +200,59 @@ def gethistoricaldata(request):
@require_GET
def getcomparisondata(request):
executables, exekeys = getcomparisonexes()
+
+ requested_exes = None
+ if 'exe' in request.GET:
+ requested_exes = set(
+ i for i in request.GET['exe'].split(",") if i and i in exekeys
+ )
+
benchmarks = Benchmark.objects.all()
+ if 'ben' in request.GET:
+ bench_ids = set()
+ for i in request.GET['ben'].split(","):
+ try:
+ bench_ids.add(int(i))
+ except ValueError:
+ pass
+ if bench_ids:
+ benchmarks = benchmarks.filter(id__in=bench_ids)
+
environments = Environment.objects.all()
compdata = {}
compdata['error'] = "Unknown error"
+ suite_versions = {} # exe_key -> env_id -> sorted list of unique non-empty versions
for proj in executables:
for exe in executables[proj]:
+ if requested_exes is not None and exe['key'] not in requested_exes:
+ continue
compdata[exe['key']] = {}
+ suite_versions[exe['key']] = {}
for env in environments:
compdata[exe['key']][env.id] = {}
# Load all results for this env/executable/revision in a
# dict for fast lookup
- results = dict(Result.objects.filter(
+ rows = Result.objects.filter(
environment=env,
executable=exe['executable'],
revision=exe['revision'],
- ).values_list('benchmark', 'value'))
+ ).values_list('benchmark', 'value', 'suite_version')
+
+ results = {}
+ env_versions = set()
+ for bench_id, value, sv in rows:
+ results[bench_id] = value
+ if sv:
+ env_versions.add(sv)
for bench in benchmarks:
compdata[exe['key']][env.id][bench.id] = results.get(
bench.id, None)
+ suite_versions[exe['key']][env.id] = sorted(env_versions)
+ compdata['suite_versions'] = suite_versions
compdata['error'] = "None"
return HttpResponse(json.dumps(compdata))
@@ -198,7 +266,7 @@ def comparison(request):
enviros = Environment.objects.all()
if not enviros:
return no_environment_error(request)
- checkedenviros = get_default_environment(enviros, data, multi=True)
+ checkedenviros = get_default_environment(enviros, data)
if not len(Project.objects.filter(track=True)):
return no_default_project_error(request)
@@ -237,26 +305,48 @@ def comparison(request):
except Revision.DoesNotExist:
#TODO: log
pass
+ if not checkedexecutables:
+ if hasattr(settings, 'DEF_EXECUTABLES') and settings.DEF_EXECUTABLES:
+ for exe_spec in settings.DEF_EXECUTABLES:
+ try:
+ proj = Project.objects.get(name=exe_spec['project'])
+ exe = Executable.objects.get(name=exe_spec['name'], project=proj)
+ for key in exekeys:
+ if key.startswith(str(exe.id) + "+L+"):
+ checkedexecutables.append(key)
+ break
+ except (Executable.DoesNotExist, Project.DoesNotExist):
+ pass
+ if (not checkedexecutables and
+ hasattr(settings, 'DEF_BASELINES') and settings.DEF_BASELINES):
+ baselines = getbaselineexecutables()
+ for base_spec in settings.DEF_BASELINES:
+ for base in baselines:
+ if base['key'] == "none":
+ continue
+ if (base['executable'].name == base_spec['executable'] and
+ base['revision'].commitid == base_spec['revision']):
+ if base['key'] in exekeys:
+ checkedexecutables.append(base['key'])
+ break
if not checkedexecutables:
checkedexecutables = exekeys
- units_titles = Benchmark.objects.filter(
- benchmark_type="C"
- ).values('units_title').distinct()
- units_titles = [unit['units_title'] for unit in units_titles]
benchmarks = {}
bench_units = {}
- for unit in units_titles:
- # Only include benchmarks marked as cross-project
- benchmarks[unit] = Benchmark.objects.filter(
- benchmark_type="C"
- ).filter(units_title=unit)
- units = benchmarks[unit][0].units
- lessisbetter = (benchmarks[unit][0].lessisbetter and
- ' (less is better)' or ' (more is better)')
- bench_units[unit] = [
- [b.id for b in benchmarks[unit]], lessisbetter, units
- ]
+ for source_val, source_label in Benchmark.S_TYPES:
+ qs = Benchmark.objects.filter(source=source_val)
+ if not qs.exists():
+ continue
+ benchmarks[source_label] = qs
+ for unit in qs.values_list('units_title', flat=True).distinct():
+ unit_qs = qs.filter(units_title=unit)
+ units = unit_qs[0].units
+ lessisbetter = (unit_qs[0].lessisbetter and
+ ' (less is better)' or ' (more is better)')
+ bench_units[unit] = [
+ [b.id for b in unit_qs], lessisbetter, units
+ ]
checkedbenchmarks = []
if 'ben' in data:
checkedbenchmarks = []
@@ -268,9 +358,7 @@ def comparison(request):
except Benchmark.DoesNotExist:
pass
if not checkedbenchmarks:
- # Only include benchmarks marked as cross-project
- checkedbenchmarks = Benchmark.objects.filter(
- benchmark_type="C", default_on_comparison=True)
+ checkedbenchmarks = Benchmark.objects.filter(default_on_comparison=True)
charts = ['normal bars', 'stacked bars', 'relative bars']
# Don't show relative charts as an option if there is only one executable
@@ -303,13 +391,26 @@ def comparison(request):
except:
pass # Keep "none" as default baseline
+ if selectedbaseline == "none" and 'bas' not in data:
+ if hasattr(settings, 'DEF_EXECUTABLES') and settings.DEF_EXECUTABLES:
+ try:
+ exe_spec = settings.DEF_EXECUTABLES[0]
+ proj = Project.objects.get(name=exe_spec['project'])
+ exe = Executable.objects.get(name=exe_spec['name'], project=proj)
+ for key in exekeys:
+ if key.startswith(str(exe.id) + "+L+") and key in checkedexecutables:
+ selectedbaseline = key
+ break
+ except (Executable.DoesNotExist, Project.DoesNotExist):
+ pass
+
selecteddirection = False
if ('hor' in data and data['hor'] == "true" or
hasattr(settings, 'CHART_ORIENTATION') and
settings.CHART_ORIENTATION == 'horizontal'):
selecteddirection = True
- return render_to_response('codespeed/comparison.html', {
+ return render(request, 'codespeed/comparison.html', {
'checkedexecutables': checkedexecutables,
'checkedbenchmarks': checkedbenchmarks,
'checkedenviros': checkedenviros,
@@ -350,11 +451,18 @@ def gettimelinedata(request):
if not executables:
timeline_list['error'] = "No executables selected"
return HttpResponse(json.dumps(timeline_list))
- environment = None
- try:
- environment = get_object_or_404(Environment, id=data.get('env'))
- except ValueError:
- Http404()
+
+ environments = []
+ for env_id in data.get('env', '').split(',')[:2]:
+ if not env_id:
+ continue
+ try:
+ environments.append(get_object_or_404(Environment, id=int(env_id)))
+ except (ValueError, Http404):
+ pass
+ if not environments:
+ timeline_list['error'] = "No environment selected"
+ return HttpResponse(json.dumps(timeline_list))
number_of_revs, benchmarks = get_num_revs_and_benchmarks(data)
@@ -370,13 +478,13 @@ def gettimelinedata(request):
next_benchmarks = int(next_benchmarks)
resp = StreamingHttpResponse(stream_timeline(baseline_exe, baseline_rev, benchmarks, data,
- environment, executables, number_of_revs,
+ environments, executables, number_of_revs,
next_benchmarks),
content_type='application/json')
return resp
-def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environment, executables,
+def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environments, executables,
number_of_revs, next_benchmarks):
yield '{"timelines": ['
num_results = {"results": 0}
@@ -392,7 +500,7 @@ def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environment, e
num_benchmark += 1
if not next_benchmarks or num_benchmark > next_benchmarks:
- result = get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environment,
+ result = get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environments,
executables, number_of_revs, num_results)
if result != "":
transmitted_benchmarks += 1
@@ -415,7 +523,7 @@ def stream_timeline(baseline_exe, baseline_rev, benchmarks, data, environment, e
yield ']' + not_first + next_page + ', "error":"None"}\n'
-def get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environment, executables,
+def get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environments, executables,
number_of_revs, num_results):
lessisbetter = bench.lessisbetter and ' (less is better)' or ' (more is better)'
timeline = {
@@ -427,72 +535,67 @@ def get_timeline_for_benchmark(baseline_exe, baseline_rev, bench, environment, e
'lessisbetter': lessisbetter,
'branches': {},
'baseline': "None",
+ 'environments': [{'id': env.id, 'name': env.name} for env in environments],
}
append = False
for branch in Branch.objects.filter(
project__track=True, name=F('project__default_branch')):
- # For now, we'll only work with default branches
- for executable in executables:
- if executable.project != branch.project:
- continue
-
- resultquery = Result.objects.filter(
- benchmark=bench
- ).filter(
- environment=environment
- ).filter(
- executable=executable
- ).filter(
- revision__branch=branch
- ).select_related(
- "revision"
- ).order_by('-revision__date')[:number_of_revs]
- if not len(resultquery):
- continue
- timeline['branches'].setdefault(branch.name, {})
-
- results = []
- for res in resultquery:
- if bench.data_type == 'M':
- q1, q3, val_max, val_min = get_stats_with_defaults(res)
- results.append(
- [
+ for environment in environments:
+ for executable in executables:
+ if executable.project != branch.project:
+ continue
+
+ resultquery = Result.objects.filter(
+ benchmark=bench,
+ environment=environment,
+ executable=executable,
+ revision__branch=branch,
+ ).select_related(
+ "revision"
+ ).order_by('-revision__date')[:number_of_revs]
+ if not len(resultquery):
+ continue
+ timeline['branches'].setdefault(branch.name, {})
+
+ results = []
+ for res in resultquery:
+ if bench.data_type == 'M':
+ q1, q3, val_max, val_min = get_stats_with_defaults(res)
+ 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(), res.revision.tag, branch.name
- ]
- )
- else:
- std_dev = ""
- if res.std_dev is not None:
- std_dev = res.std_dev
- results.append(
- [
+ res.revision.get_short_commitid(), res.revision.tag, branch.name,
+ res.suite_version,
+ ])
+ 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(), res.revision.tag, branch.name
- ]
- )
- timeline['branches'][branch.name][executable.id] = results
- append = True
+ res.revision.get_short_commitid(), res.revision.tag, branch.name,
+ res.suite_version,
+ ])
+ # Key is "exe_id:env_id" so multiple environments render as separate series
+ timeline['branches'][branch.name][f"{executable.id}:{environment.id}"] = results
+ append = True
if baseline_rev is not None and append:
try:
baselinevalue = Result.objects.get(
executable=baseline_exe,
benchmark=bench,
revision=baseline_rev,
- environment=environment
+ environment=environments[0],
).value
except Result.DoesNotExist:
timeline['baseline'] = "None"
else:
- # determine start and end revision (x axis)
- # from longest data series
results = []
for branch in timeline['branches']:
- for exe in timeline['branches'][branch]:
- if len(timeline['branches'][branch][exe]) > len(results):
- results = timeline['branches'][branch][exe]
+ for key in timeline['branches'][branch]:
+ if len(timeline['branches'][branch][key]) > len(results):
+ results = timeline['branches'][branch][key]
end = results[0][0]
start = results[len(results) - 1][0]
timeline['baseline'] = [
@@ -522,6 +625,10 @@ def timeline(request):
if not enviros:
return no_environment_error(request)
defaultenviro = get_default_environment(enviros, data)
+ if 'env' in data:
+ defaultenvironments = get_default_environment(enviros, data, multi=True)[:2]
+ else:
+ defaultenvironments = defaultenviro # already respects DEF_ENVIRONMENT
# Default Project
defaultproject = Project.objects.filter(track=True)
@@ -540,6 +647,15 @@ def timeline(request):
except Executable.DoesNotExist:
pass
+ if not checkedexecutables:
+ if hasattr(settings, 'DEF_EXECUTABLES') and settings.DEF_EXECUTABLES:
+ for def_exe in settings.DEF_EXECUTABLES:
+ try:
+ proj = Project.objects.get(name=def_exe['project'])
+ checkedexecutables.append(
+ Executable.objects.get(name=def_exe['name'], project=proj))
+ except (Project.DoesNotExist, Executable.DoesNotExist):
+ pass
if not checkedexecutables:
checkedexecutables = Executable.objects.filter(project__track=True)
@@ -568,12 +684,17 @@ def timeline(request):
except ValueError:
pass
- lastrevisions = [10, 50, 200, 1000]
+ lastrevisions = [10, 15, 50, 200]
defaultlast = settings.DEF_TIMELINE_LIMIT
- if 'revs' in data:
- if int(data['revs']) not in lastrevisions:
- lastrevisions.append(data['revs'])
- defaultlast = data['revs']
+ if 'revs' in data and data['revs']:
+ try:
+ revs_int = int(data['revs'])
+ except ValueError:
+ revs_int = None
+ if revs_int is not None:
+ if revs_int not in lastrevisions:
+ lastrevisions.append(revs_int)
+ defaultlast = revs_int
benchmarks = Benchmark.objects.all()
@@ -623,13 +744,14 @@ def timeline(request):
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', {
+ return render(request, 'codespeed/timeline.html', {
'pagedesc': pagedesc,
'checkedexecutables': checkedexecutables,
'defaultbaseline': defaultbaseline,
'baseline': baseline,
'defaultbenchmark': defaultbenchmark,
'defaultenvironment': defaultenviro,
+ 'defaultenvironments': defaultenvironments,
'lastrevisions': lastrevisions,
'defaultlast': defaultlast,
'executables': executables,
@@ -707,7 +829,7 @@ def getchangestable(request):
'
No results for this '
'parameters
')
- return render_to_response('codespeed/changes_data.html', {
+ return render(request, 'codespeed/changes_data.html', {
'tablelist': tablelist,
'trendconfig': trendconfig,
'rev': selectedrev,
@@ -805,6 +927,16 @@ def changes(request):
projectmatrix[e.id] = e.project.name
projectmatrix = json.dumps(projectmatrix)
+ all_commitids = [rev.commitid for revisions in revisionlists.values() for rev in revisions]
+ env_has_results = {}
+ for env in enviros:
+ has = set(Result.objects.filter(
+ environment=env,
+ revision__commitid__in=all_commitids,
+ ).values_list('revision__commitid', flat=True).distinct())
+ env_has_results[str(env.id)] = list(has)
+ env_has_results = json.dumps(env_has_results)
+
for project, revisions in revisionlists.items():
revisionlists[project] = [
(str(rev), rev.commitid) for rev in revisions
@@ -813,7 +945,7 @@ def changes(request):
pagedesc = "Report of %s performance changes for commit %s on branch %s" % \
(defaultexecutable, selectedrevision.commitid, selectedrevision.branch)
- return render_to_response('codespeed/changes.html', {
+ return render(request, 'codespeed/changes.html', {
'pagedesc': pagedesc,
'defaultenvironment': defaultenv,
'defaultexecutable': defaultexecutable,
@@ -825,6 +957,7 @@ def changes(request):
'executables': executables,
'projectmatrix': projectmatrix,
'revisionlists': revisionlists,
+ 'env_has_results': env_has_results,
'trends': trends,
})
@@ -840,7 +973,7 @@ def reports(request):
colorcode__in=('red', 'green')
).order_by('-revision__date')[:10]
- return render_to_response('codespeed/reports.html', context)
+ return render(request, 'codespeed/reports.html', context)
@require_GET
@@ -885,7 +1018,8 @@ def displaylogs(request):
for log in logs:
log['commit_browse_url'] = project.commit_browsing_url.format(**log)
- return render_to_response(
+ return render(
+ request,
'codespeed/changes_logs.html',
{
'error': error, 'logs': logs,
diff --git a/codespeed/views_data.py b/codespeed/views_data.py
index 583f5457..ccb7ab3f 100644
--- a/codespeed/views_data.py
+++ b/codespeed/views_data.py
@@ -21,15 +21,14 @@ def get_default_environment(enviros, data, multi=False):
# Use permalink values
if 'env' in data:
for env_value in data['env'].split(","):
+ try:
+ env_id = int(env_value)
+ except ValueError:
+ continue
for env in enviros:
- try:
- env_id = int(env_value)
- except ValueError:
- # Not an int
- continue
- for env in enviros:
- if env_id == env.id:
- defaultenviros.append(env)
+ if env_id == env.id:
+ defaultenviros.append(env)
+ break
if not multi:
break
# Use settings.py value
@@ -46,7 +45,7 @@ def get_default_environment(enviros, data, multi=False):
if multi:
return defaultenviros
else:
- return defaultenviros[0]
+ return [defaultenviros[0],]
def getbaselineexecutables(include_tags=None):
@@ -83,15 +82,16 @@ def getbaselineexecutables(include_tags=None):
'name': name,
})
# move default to first place
- if hasattr(settings, 'DEF_BASELINE') and settings.DEF_BASELINE is not None:
+ if hasattr(settings, 'DEF_BASELINES') and settings.DEF_BASELINES is not None:
try:
- exename = settings.DEF_BASELINE['executable']
- commitid = settings.DEF_BASELINE['revision']
+ exename = settings.DEF_BASELINES[0]['executable']
+ commitid = settings.DEF_BASELINES[0]['revision']
for base in baseline:
if base['key'] == "none":
continue
if (base['executable'].name == exename and
- base['revision'].commitid == commitid):
+ (base['revision'].commitid == commitid or
+ base['revision'].tag == commitid)):
baseline.remove(base)
baseline.insert(1, base)
break
@@ -104,10 +104,12 @@ def getbaselineexecutables(include_tags=None):
def getdefaultexecutable():
default = None
- if (hasattr(settings, 'DEF_EXECUTABLE') and
- settings.DEF_EXECUTABLE is not None):
+ if (hasattr(settings, 'DEF_EXECUTABLES') and
+ settings.DEF_EXECUTABLES is not None):
try:
- default = Executable.objects.get(name=settings.DEF_EXECUTABLE)
+ def_name = settings.DEF_EXECUTABLES[0]['name']
+ def_project = Project.objects.get(name=settings.DEF_EXECUTABLES[0]['project'])
+ default = Executable.objects.get(name=def_name, project=def_project)
except Executable.DoesNotExist:
pass
if default is None:
diff --git a/deploy-requirements.txt b/deploy-requirements.txt
new file mode 100644
index 00000000..711c7577
--- /dev/null
+++ b/deploy-requirements.txt
@@ -0,0 +1,3 @@
+-r requirements.txt
+psycopg2-binary==2.9.9
+gunicorn==21.2.0
diff --git a/example/override/static/images/favicon.ico b/example/override/static/images/favicon.ico
new file mode 100644
index 00000000..c79bb6de
Binary files /dev/null and b/example/override/static/images/favicon.ico differ
diff --git a/example/settings.py b/example/settings.py
new file mode 100644
index 00000000..f63cf7ae
--- /dev/null
+++ b/example/settings.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+# Django settings for a speedcenter project.
+import os
+import os.path
+import sys
+
+sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+BASEDIR = os.path.dirname(__file__)
+
+#: The directory which should contain checked out source repositories:
+REPOSITORY_BASE_PATH = os.path.join(BASEDIR, "repos")
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASEDIR, 'data.db'),
+ }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = False
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = os.path.join(BASEDIR, "media")
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/media/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'as%n_m#)^vee2pe91^^@c))sl7^c6t-9r8n)_69%)2yt+(la2&'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+ # 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+if DEBUG:
+ import traceback
+ import logging
+
+ # Define a class that logs unhandled errors
+ class LogUncatchedErrors:
+ def process_exception(self, request, exception):
+ logging.error("Unhandled Exception on request for %s\n%s" %
+ (request.build_absolute_uri(),
+ traceback.format_exc()))
+ # And add it to the middleware classes
+ MIDDLEWARE_CLASSES += ('settings.LogUncatchedErrors',)
+
+ # set shown level of logging output to debug
+ logging.basicConfig(level=logging.DEBUG)
+
+
+ROOT_URLCONF = 'example.urls'
+
+TEMPLATE_DIRS = (
+ os.path.join(os.path.dirname(__file__), 'templates'),
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ 'django.core.context_processors.debug',
+ 'django.core.context_processors.i18n',
+ 'django.core.context_processors.media',
+ 'django.core.context_processors.static',
+ 'django.core.context_processors.request',
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ #'django.contrib.sites',
+ 'django.contrib.admin',
+ 'django.contrib.staticfiles',
+ 'codespeed',
+ 'south'
+)
+
+STATIC_URL = '/static/'
+
+STATIC_ROOT = os.path.join(BASEDIR, "sitestatic")
+
+# Codespeed settings that can be overwritten here.
+## General default options ##
+WEBSITE_NAME = "PyPy Speed Center" # This name will be used in the reports RSS feed
+
+#DEF_ENVIRONMENT = None #Name of the environment which should be selected as default
+
+
+DEF_BASELINE = {'executable': 'cpython', 'revision': '101'} # Which executable + revision should be default as a baseline
+ # Given as the name of the executable and commitid of the revision
+ # Example: defaultbaseline = {'executable': 'myexe', 'revision': '21'}
+
+#TREND = 10 # Default value for the depth of the trend
+ # Used by reports for the latest runs and changes view
+
+# Threshold that determines when a performance change over the last result is significant
+CHANGE_THRESHOLD = 5.0
+
+# Threshold that determines when a performance change
+# over a number of revisions is significant
+TREND_THRESHOLD = 6.0
+
+## Changes view options ##
+#DEF_EXECUTABLE = None # Executable that should be chosen as default in the changes view
+ # Given as the name of the executable.
+ # Example: defaultexecutable = "myexe"
+
+## Timeline view options ##
+#DEF_BENCHMARK = "grid" # Default selected benchmark. Possible values:
+ # "grid": will show the grid of plots
+ # "show_none": will just show a text message
+ # "mybench": will select benchmark "mybench"
+
+#TIMELINE_BRANCHES = True # NOTE: Only the default branch is currently shown
+ # Get timeline results for specific branches
+ # Set to False if you want timeline plots and results only for trunk.
+
+## Comparison view options ##
+#CHART_TYPE = 'normal bars' # The options are 'normal bars', 'stacked bars' and 'relative bars'
+
+NORMALIZATION = True # True will enable normalization as the default selection
+ # in the Comparison view. The default normalization can be
+ # chosen in the defaultbaseline setting
+
+#CHART_ORIENTATION = 'vertical' # 'vertical' or 'horizontal can be chosen as
+ # default chart orientation
+
+COMP_EXECUTABLES = [('pypy-c-jit', 'L'), ('pypy-c', 'L')] # Which executable + revision should be checked as default
+ # Given as a list of tuples containing the
+ # name of an executable + commitid of a revision
+ # An 'L' denotes the last revision
+ # Example:
+ # COMP_EXECUTABLES = [
+ # ('myexe', '21df2423ra'),
+ # ('myexe', 'L'),]
+
+from .local_settings import *
+
diff --git a/example/templates/base.html b/example/templates/base.html
new file mode 100644
index 00000000..cc60dfb4
--- /dev/null
+++ b/example/templates/base.html
@@ -0,0 +1,75 @@
+
+
+
+
{% block title %}PyPy's Speed Center{% endblock %}
+
+
+
+
+
+
+
+
+ {% block extra_head %}{% endblock %}
+
+
+
+ {# TODO: Rename id=title to id=header and/or switch to
+
+
+
+
diff --git a/manage.py b/manage.py
index 1c3713d6..7cfa57cd 100755
--- a/manage.py
+++ b/manage.py
@@ -2,8 +2,8 @@
import os, sys
if __name__ == "__main__":
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings")
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "speed_pypy.settings")
from django.core.management import execute_from_command_line
-
execute_from_command_line(sys.argv)
+
diff --git a/requirements.txt b/requirements.txt
index 884d03e1..7a9d1bce 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-Django>=1.11,<2.2
-isodate>=0.4.7,<0.6
-matplotlib>=1.4.3,<2.0
+django<4
+isodate==0.6.1
+matplotlib<3.8
diff --git a/sample_project/templates/about.html b/sample_project/templates/about.html
deleted file mode 100644
index 7eebe2e7..00000000
--- a/sample_project/templates/about.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "codespeed/base_site.html" %}
-{% block title %}{{ block.super }}: About this site{% endblock %}
-{% block body %}
-
-
-
About this site
-
<description of site and what benchmarks that are run>
-
This site runs on top of Django and Codespeed
-
About the benchmarks
-
The code can be found here.
-
About MyProject
-
<Description of the main project>
-
Main website: MySite
-
About Codespeed
-Codespeed is a web application to monitor and analyze the performance of your code.
-
Code: github.com/tobami/codespeed
-
Wiki: wiki.github.com/tobami/codespeed/
-
Contact
-
For problems or suggestions about this website write to...
-
-{% endblock %}
diff --git a/sample_project/templates/codespeed/base_site.html b/sample_project/templates/codespeed/base_site.html
deleted file mode 100644
index 0ba7ca30..00000000
--- a/sample_project/templates/codespeed/base_site.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends "codespeed/base.html" %}
-
-{% block title %}
- My Own Title
-{% endblock %}
diff --git a/sample_project/templates/feeds/latest_description.html b/sample_project/templates/feeds/latest_description.html
deleted file mode 100644
index 66f20dcf..00000000
--- a/sample_project/templates/feeds/latest_description.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% ifequal obj.colorcode "red" %}Performance regressed: {% else %}
- {% ifequal obj.colorcode "green" %}Performance improved: {% else %}
- {% ifequal obj.colorcode "yellow" %}Trend regressed: {% else %}
- No significant changes.
- {% endifequal %}
- {% endifequal %}
-{% endifequal %}
-{{ obj.summary }}
diff --git a/sample_project/templates/home.html b/sample_project/templates/home.html
deleted file mode 100644
index 2a0ddf3c..00000000
--- a/sample_project/templates/home.html
+++ /dev/null
@@ -1,216 +0,0 @@
-{% extends "codespeed/base_site.html" %}
-{% load static %}
-
-{% block navigation %}
-{% endblock navigation %}
-
-{% block body %}
-
-
-
-
-
-
-
-
-
-
-
-
- {% if show_historical %}
-
-
How fast is {{ default_exe.project }}?
-
-
Plot 1: The above plot represents {{ default_exe.project }} ({{ default_exe }}) benchmark times normalized to {{ baseline }}. Smaller is better.
-
It depends greatly on the type of task being performed. The geometric average of all benchmarks is or times faster than {{ baseline }}
-
-
How has {{ default_exe.project }} performance evolved over time?
-
-
Plot 2: Speedup compared to {{ baseline }}, using the inverse of the geometric average of normalized times, out of benchmarks (see paper on why the geometric mean is better for normalized results).
-
- {% endif %}
-
-{% endblock body %}
-
-{% block extra_script %}
-{{ block.super }}
-{% if show_historical %}
-
-
-
-
-
-
-
-
-{% endif %}
-
-
-{% endblock %}
diff --git a/setup.py b/setup.py
index 5cd5f0e9..373c7d95 100644
--- a/setup.py
+++ b/setup.py
@@ -9,10 +9,10 @@
download_url="https://github.com/tobami/codespeed/tags",
license='GNU Lesser General Public License version 2.1',
keywords=['benchmarking', 'visualization'],
- install_requires=['django>=1.11<2.2', 'isodate>=0.4.7,<0.6', 'matplotlib>=1.4.3,<2.0'],
+ install_requires=['django', 'isodate', 'matplotlib'],
packages=find_packages(exclude=['ez_setup', 'sample_project']),
- setup_requires=['setuptools-markdown'],
- long_description_markdown_filename='README.md',
+ long_description='README.md',
+ long_description_content_type="text/markdown",
description='A web application to monitor and analyze the performance of your code',
include_package_data=True,
zip_safe=False,
@@ -32,3 +32,4 @@
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
]
)
+
diff --git a/sample_project/README.md b/speed_pypy/README.md
similarity index 100%
rename from sample_project/README.md
rename to speed_pypy/README.md
diff --git a/speed_pypy/__init__.py b/speed_pypy/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/speed_pypy/settings.py b/speed_pypy/settings.py
new file mode 100644
index 00000000..361d4397
--- /dev/null
+++ b/speed_pypy/settings.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# Django settings for a Codespeed project.
+import os
+
+from codespeed.settings import *
+WEBSITE_NAME = "PyPy's Speed Center" # This name will be used in the reports RSS feed
+
+DEBUG = True
+
+BASEDIR = os.path.abspath(os.path.dirname(__file__))
+TOPDIR = os.path.split(BASEDIR)[1]
+
+#: The directory which should contain checked out source repositories:
+REPOSITORY_BASE_PATH = os.path.join(BASEDIR, "repos")
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASEDIR, 'data.db'),
+ }
+}
+
+DEFAULT_AUTO_FIELD='django.db.models.AutoField'
+
+TIME_ZONE = 'America/Chicago'
+
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+USE_I18N = False
+
+MEDIA_ROOT = os.path.join(BASEDIR, "media")
+
+MEDIA_URL = '/media/'
+
+SECRET_KEY = 'as%n_m#)^vee2pe91^^@c))sl7^c6t-9r8n)_69%)2yt+(la2&'
+
+
+MIDDLEWARE = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = '{0}.urls'.format(TOPDIR)
+
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [os.path.join(BASEDIR, 'templates')],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.admin',
+ 'django.contrib.staticfiles',
+ 'codespeed',
+)
+
+
+STATIC_URL = '/static/'
+STATIC_URL = 'https://speed.pypy.org/static/'
+STATIC_ROOT = os.path.join(BASEDIR, "sitestatic")
+STATICFILES_DIRS = (
+ os.path.join(BASEDIR, 'static'),
+)
+
+SHOW_REPORTS = False
+SHOW_HISTORICAL = True
+DEF_BASELINES = [
+ {'executable': 'cpython', 'revision': '3.11.9'},
+ {'executable': 'cpython', 'revision': '3.12.4'},
+ ]
+DEF_EXECUTABLES = [
+ {'name': 'pypy3.11-jit-64', 'project': 'PyPy3.11'},
+ ]
+DEF_ENVIRONMENT = 'benchmarker'
+CHART_ORIENTATION = 'horizontal'
+DEF_BENCHMARK = 'grid'
+
+
+from .local_settings import *
diff --git a/speed_pypy/static/.gitkeep b/speed_pypy/static/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/sample_project/templates/404.html b/speed_pypy/templates/404.html
similarity index 100%
rename from sample_project/templates/404.html
rename to speed_pypy/templates/404.html
diff --git a/sample_project/templates/500.html b/speed_pypy/templates/500.html
similarity index 100%
rename from sample_project/templates/500.html
rename to speed_pypy/templates/500.html
diff --git a/speed_pypy/templates/about.html b/speed_pypy/templates/about.html
new file mode 100644
index 00000000..ead38b43
--- /dev/null
+++ b/speed_pypy/templates/about.html
@@ -0,0 +1,43 @@
+{% extends "codespeed/base_site.html" %}
+{% block title %}{{ block.super }}: About this site{% endblock %}
+{% block body %}
+
+
+
About this site
+
We have nightly benchmark runs of PyPy, together with CPython data for
+comparison. The current benchmarks have run since 2016 on a dedicated machine
+named benchmarker. Previously, we had other machines (tannit and
+briefly speed-python). The benchmarker machine is generously
+donated by Baroque Software. The
+specs are:
+
- Processor: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
+- RAM: 64GB Micron DDR4 2400 MHz
+- Disk: 4TB TOSHIBA MG04ACA4
+
+
+
About the benchmarks
+
The benchmark code can be found here.
+
This is a benchmark suite based on Unladen Swallow, adapted for PyPy and
+runs on Python2 and Python3. Also see the
+speed.python.org site.
+
+
About PyPy
+
PyPy is a very compliant implementation of the Python language.
+
Main website: www.pypy.org
+
Documentation: doc.pypy.org
+
Blog: www.pypy.org/blog/
+
+
About Codespeed
+
Codespeed is a web application
+to monitor and analyze the performance of your code.
+
The source code of this website can be found on the
+PyPy
+branch of the Python fork of codespeed.
+
+
Contact
+
For problems or suggestions about this website, please file an issue in the
+GitHub repository.
+
+{% endblock %}
diff --git a/sample_project/templates/admin/base_site.html b/speed_pypy/templates/admin/base_site.html
similarity index 100%
rename from sample_project/templates/admin/base_site.html
rename to speed_pypy/templates/admin/base_site.html
diff --git a/speed_pypy/templates/admin/index.html b/speed_pypy/templates/admin/index.html
new file mode 100644
index 00000000..540c9303
--- /dev/null
+++ b/speed_pypy/templates/admin/index.html
@@ -0,0 +1,73 @@
+{% extends "admin/index.html" %}
+{% load i18n %}
+
+{% block content %}
+
+ {% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
+
+
+
+
+{% endblock %}
diff --git a/speed_pypy/templates/codespeed/base_site.html b/speed_pypy/templates/codespeed/base_site.html
new file mode 100644
index 00000000..337a9769
--- /dev/null
+++ b/speed_pypy/templates/codespeed/base_site.html
@@ -0,0 +1,3 @@
+{% extends "codespeed/base.html" %}
+
+{% block title %}PyPy Speed{% endblock %}
diff --git a/speed_pypy/templates/feeds/latest_description.html b/speed_pypy/templates/feeds/latest_description.html
new file mode 100644
index 00000000..f790f4c0
--- /dev/null
+++ b/speed_pypy/templates/feeds/latest_description.html
@@ -0,0 +1,8 @@
+{% if obj.colorcode == "red" %}Performance regressed: {% else %}
+ {% if obj.colorcode == "green" %}Performance improved: {% else %}
+ {% if obj.colorcode == "yellow" %}Trend regressed: {% else %}
+ No significant changes.
+ {% endif %}
+ {% endif %}
+{% endif %}
+{{ obj.summary }}
diff --git a/sample_project/templates/feeds/latest_title.html b/speed_pypy/templates/feeds/latest_title.html
similarity index 100%
rename from sample_project/templates/feeds/latest_title.html
rename to speed_pypy/templates/feeds/latest_title.html
diff --git a/speed_pypy/templates/home.html b/speed_pypy/templates/home.html
new file mode 100644
index 00000000..93b051d6
--- /dev/null
+++ b/speed_pypy/templates/home.html
@@ -0,0 +1,192 @@
+{% extends "codespeed/base_site.html" %}
+{% load static %}
+
+{% block navigation %}
+{% endblock navigation %}
+
+{% block body %}
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if show_historical %}
+
+
How fast is {{ default_exe.project }}?
+
+
Plot 1: The above plot represents {{ default_exe.project }} ({{ default_exe }}) benchmark times normalized to {{ baseline }}. Smaller is better.
+
It depends greatly on the type of task being performed. The geometric average of all benchmarks is or times faster than {{ baseline }}
+
+
How has PyPy performance evolved over time?
+
+
Plot 2: Speedup compared to {{ baseline }}, using the inverse of the geometric average of normalized times, out of benchmarks (see paper on why the geometric mean is better for normalized results).
+
+ {% endif %}
+
+{% endblock body %}
+
+{% block extra_script %}
+{{ block.super }}
+{% if show_historical %}
+
+{% endif %}
+
+
+{% endblock %}
diff --git a/sample_project/urls.py b/speed_pypy/urls.py
similarity index 52%
rename from sample_project/urls.py
rename to speed_pypy/urls.py
index ed416c79..b149cb30 100644
--- a/sample_project/urls.py
+++ b/speed_pypy/urls.py
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from django.conf import settings
-from django.conf.urls import include, url
+from django.urls import include, re_path
from django.contrib import admin
+from codespeed import admin_views
+
urlpatterns = [
- url(r'^admin/', admin.site.urls),
- url(r'^', include('codespeed.urls'))
+ re_path(r'^admin/download-db/$', admin_views.download_db, name='admin-download-db'),
+ re_path(r'^admin/', admin.site.urls),
+ re_path(r'^', include('codespeed.urls'))
]
if settings.DEBUG:
diff --git a/speed_pypy/wsgi.py b/speed_pypy/wsgi.py
new file mode 100644
index 00000000..6d7a2a33
--- /dev/null
+++ b/speed_pypy/wsgi.py
@@ -0,0 +1,8 @@
+# This is used for staging & production
+import os
+import sys
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "speed_pypy.settings")
+
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()
diff --git a/tools/pypy/import_from_json.py b/tools/pypy/import_from_json.py
index 5d3eb21d..0fe722cb 100644
--- a/tools/pypy/import_from_json.py
+++ b/tools/pypy/import_from_json.py
@@ -2,64 +2,88 @@
################################################################################
# This script imports PyPy's result data from json files located on the server #
################################################################################
-import simplejson, urllib2
+import simplejson
+from urllib.request import urlopen
+from urllib.error import URLError
import sys
from xml.dom.minidom import parse
from datetime import datetime
-import saveresults, savecpython
+import saveresults
-RESULTS_URLS = {
- 'pypy-c-jit': 'http://buildbot.pypy.org/bench_results/',
- 'pypy-c': 'http://buildbot.pypy.org/bench_results_nojit/',
-}
-START_REV = 79485
-END_REV = 79485
-PROJECT = "PyPy"
+URL = 'http://buildbot.pypy.org/benchmark-results/'
+START_REV = 186137
+END_REV = 200000
-for INTERP in RESULTS_URLS:
- RESULTS_URL = RESULTS_URLS[INTERP]
- # get json filenames
- filelist = []
- try:
- datasource = urllib2.urlopen(RESULTS_URL)
- dom = parse(datasource)
- for elem in dom.getElementsByTagName('td'):
- for e in elem.childNodes:
- if len(e.childNodes):
- filename = e.firstChild.toxml()
- if e.tagName == "a" and ".json" in filename:
- if int(filename.replace(".json", "")) >= START_REV and\
- int(filename.replace(".json", "")) <= END_REV:
- filelist.append(filename)
- except urllib2.URLError, e:
- response = "None"
- if hasattr(e, 'reason'):
- response = '\n We failed to reach ' + RESULTS_URL + '\n'
- response += ' Reason: ' + str(e.reason)
- elif hasattr(e, 'code'):
- response = '\n The server couldn\'t fulfill the request\n'
- response += ' Error code: ' + str(e)
- print "Results Server (%s) response: %s\n" % (RESULTS_URL, response)
- sys.exit(1)
- finally:
- datasource.close()
- # read json result and save to speed.pypy.org
- for filename in filelist:
- print "Reading %s..." % filename
- f = urllib2.urlopen(RESULTS_URL + filename)
- result = simplejson.load(f)
- f.close()
- proj = PROJECT
- revision = result['revision']
- interpreter = INTERP
- int_options = ""
- options = ""
- if 'options' in result:
- options = result['options']
+speed_url = "http://127.0.0.1:8000/"
- host = 'tannit'
- #saveresults.save(proj, revision, result['results'], options, interpreter, host)
- if filename == filelist[len(filelist)-1]:
- savecpython.save('cpython', '100', result['results'], options, 'cpython', host)
-print "\nOK"
+# get json filenames
+filelist = []
+try:
+ datasource = urlopen(URL)
+ dom = parse(datasource)
+ for elem in dom.getElementsByTagName('td'):
+ for e in elem.childNodes:
+ if len(e.childNodes):
+ filename = e.firstChild.toxml()
+ if e.tagName == "a" and ".json" in filename:
+ rev_int = int(filename.split(":")[0])
+ if START_REV <= rev_int <= END_REV:
+ filelist.append(filename)
+except URLError as e:
+ response = "None"
+ if hasattr(e, 'reason'):
+ response = '\n We failed to reach ' + URL + '\n'
+ response += ' Reason: ' + str(e.reason)
+ elif hasattr(e, 'code'):
+ response = '\n The server couldn\'t fulfill the request\n'
+ response += ' Error code: ' + str(e)
+ print("Results Server (%s) response: %s\n" % (URL, response))
+ sys.exit(1)
+finally:
+ datasource.close()
+
+# read json result and save to speed.pypy.org
+skipped = []
+for filename in filelist:
+ print(f"Reading {filename}...")
+ f = urlopen(URL + filename)
+ if f.length < 10:
+ skipped.append(filename)
+ continue
+ result = simplejson.load(f)
+ f.close()
+ revision = result['revision']
+ int_options = ""
+ options = ""
+ if 'options' in result:
+ options = result['options']
+
+ host = 'benchmarker'
+ if revision and len(revision) >10:
+ branch = result['branch']
+ if branch in ("default", "main"):
+ proj = "PyPy"
+ executable = "pypy-c"
+ branch = "main"
+ elif branch == "py3.9":
+ proj = "PyPy3.9"
+ executable = "pypy3.9-64"
+ elif branch == "py3.10":
+ proj = "PyPy3.10"
+ executable = "pypy3.10-64"
+ elif branch == "py3.11":
+ proj = "PyPy3.11"
+ executable = "pypy3.11-64"
+ else:
+ skipped.append(filename)
+ continue
+ saveresults.save(proj, revision, result['results'], executable,
+ host, speed_url, branch=branch, changed=True)
+ saveresults.save(proj, revision, result['results'],
+ executable.replace('-', '-jit-'),
+ host, speed_url, branch=branch, changed=False)
+ else:
+ savecpython.save('cpython', '100', result['results'], options, 'cpython', host)
+print("\nOK")
+print("skipped", skipped)
diff --git a/tools/pypy/saveresults.py b/tools/pypy/saveresults.py
index 8ca8ee75..3d3eb0df 100644
--- a/tools/pypy/saveresults.py
+++ b/tools/pypy/saveresults.py
@@ -3,64 +3,172 @@
# This script saves result data #
# It expects the format of unladen swallow's perf.py #
#######################################################
-import urllib, urllib2
+
+"""
+Upload a json file generated by runner.py.
+
+Revision, name and host are required.
+
+Example usage:
+
+ $ ./saveresults.py result.json -r '45757:fabe4fc0dc08' -n pypy-c-jit \
+ -H benchmarker
+
+ OR
+
+ $ ./saveresults.py result.json -r '45757:fabe4fc0dc08' -n pypy-c-jit-64 \
+ -H benchmarker
+"""
+from __future__ import division, print_function
+
from datetime import datetime
+import optparse
+import sys
+import time
+try:
+ import urllib2
+ from urllib import urlencode
+except ImportError:
+ import urllib.request as urllib2
+ from urllib.parse import urlencode
+import json
+import pprint
-#SPEEDURL = 'http://127.0.0.1:8000/'
-SPEEDURL = 'http://speed.pypy.org/'
+SPEEDURL = 'http://127.0.0.1:8000/'
+# SPEEDURL = 'https://speed.pypy.org/'
-def save(project, revision, results, options, executable, environment, testing=False):
+def save(project, revision, results, executable, host, url, testing=False,
+ changed=True, branch='default'):
testparams = []
#Parse data
data = {}
- current_date = datetime.today()
+ error = 0
+
+ print("saveresults::save sending from %s to %s" %(host, url))
for b in results:
bench_name = b[0]
res_type = b[1]
results = b[2]
value = 0
if res_type == "SimpleComparisonResult":
- value = results['changed_time']
+ if changed:
+ value = results['changed_time']
+ else:
+ value = results['base_time']
elif res_type == "ComparisonResult":
- value = results['avg_changed']
+ if changed:
+ value = results['avg_changed']
+ else:
+ value = results['avg_base']
+ elif res_type == "RawResult":
+ if changed:
+ value = results["changed_times"]
+ else:
+ value = results["base_times"]
+ if value:
+ assert len(value) == 1
+ value = value[0]
else:
print("ERROR: result type unknown " + b[1])
return 1
- data = {
+ data = [{
'commitid': revision,
'project': project,
'executable': executable,
'benchmark': bench_name,
- 'environment': environment,
+ 'environment': host,
'result_value': value,
- }
+ 'branch': branch,
+ }]
+ if not value:
+ print("Ignoring skipped result", data)
+ continue
if res_type == "ComparisonResult":
- data['std_dev'] = results['std_changed']
- if testing: testparams.append(data)
- else: send(data)
- if testing: return testparams
- else: return 0
+ if changed:
+ data[0]['std_dev'] = results['std_changed']
+ else:
+ data[0]['std_dev'] = results['std_base']
+ if testing:
+ testparams.append(data)
+ else:
+ error |= send(data, url)
-def send(data):
+ if error:
+ raise IOError("Saving failed. See messages above.")
+ if testing:
+ return testparams
+ else:
+ return 0
+
+
+def send(data, url):
#save results
- params = urllib.urlencode(data)
+ params = urlencode({'json': json.dumps(data)}).encode("utf-8")
f = None
response = "None"
- info = str(datetime.today()) + ": Saving result for " + data['executable']
- info += " revision " + " " + str(data['commitid']) + ", benchmark "
- info += data['benchmark']
+ info = ("%s: Saving result for %s revision %s, benchmark %s" %
+ (str(datetime.today()), data[0]['executable'],
+ str(data[0]['commitid']), data[0]['benchmark']))
print(info)
try:
- f = urllib2.urlopen(SPEEDURL + 'result/add/', params)
- response = f.read()
- f.close()
+ retries = [1, 3, 6, 20]
+ while True:
+ try:
+ print('result/add')
+ f = urllib2.urlopen(url + 'result/add/json/', params)
+ print('urlopen')
+ response = f.read().decode()
+ print('response')
+ f.close()
+ break
+ except urllib2.URLError:
+ if not retries:
+ raise
+ d = retries.pop(0)
+ print("retrying in %d seconds..." % d)
+ time.sleep(d)
except urllib2.URLError as e:
if hasattr(e, 'reason'):
- response = '\n We failed to reach a server\n'
+ response = '\n We failed to save the data\n'
response += ' Reason: ' + str(e.reason)
elif hasattr(e, 'code'):
- response = '\n The server couldn\'t fulfill the request\n'
- response += ' Error code: ' + str(e)
- print("Server (%s) response: %s\n" % (SPEEDURL, response))
+ response = '\n The server couldn\'t fulfill the request'
+ if hasattr(e, 'readlines'):
+ response = "".join([response] + [s.decode() for s in e.readlines()])
+ print(response)
+ print('when sending')
+ pprint.pprint(data)
+ raise
+ with open('error.html', 'w') as error_file:
+ error_file.write(response)
+ print("Server (%s) response written to error.html" % (url,))
+ print(' Error code: %s\n' % (e,))
return 1
+ print("saved correctly!", end='\n\n')
return 0
+
+if __name__ == '__main__':
+ usage = "Usage: %prog [options]
"
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option('-b', '--base', action='store_true',
+ help='take base values instead of modified')
+ parser.add_option('--host', type=str, default=SPEEDURL,
+ help="codespeed instance, defaults to '%s'" % SPEEDURL)
+ parser.add_option('--revision',
+ help=('for cpython, revision number (100 for cpython 2.6.2, 101 for 2.7.2, '
+ '110 for cpython3.7.6, 120 for cpython3.11, '
+ 'edit admin interface "Revisions" to add more)\n\n'
+ 'for pypy, use the hash from the json file'),
+ )
+ parser.add_option('--branch', help="branch name (main, py3.11)", default='main')
+ options, args = parser.parse_args(sys.argv)
+ if options.host[-1] != '/':
+ raise ValueError("host must end with '/'")
+ if not options.host.startswith("http"):
+ raise ValueError("host must start with 'http'")
+ if len(args) != 2:
+ print(parser.usage)
+ sys.exit(1)
+ results = json.load(open(args[1]))['results']
+ save('cpython', options, results, 'cpython', 'benchmarker',
+ testing=False)