зеркало из https://github.com/mozilla/treeherder.git
Bug 1505796 - improve perfherder compare performance (#4414)
create new endoint `performance/summary/` and update queries in compare and compare subtest controllers
This commit is contained in:
Родитель
d1b81bde58
Коммит
fbb60958cd
|
@ -375,3 +375,34 @@ def test_filter_data_by_signature(client, test_repository, test_perf_signature,
|
|||
assert len(resp.data[signature.signature_hash]) == 1
|
||||
assert resp.data[signature.signature_hash][0]['signature_id'] == signature.id
|
||||
assert resp.data[signature.signature_hash][0]['value'] == float(i)
|
||||
|
||||
|
||||
def test_perf_summary(client, test_perf_signature, test_perf_data):
|
||||
|
||||
query_params1 = '?repository={}&framework={}&interval=172800&no_subtests=true&revision={}'.format(
|
||||
test_perf_signature.repository.name, test_perf_signature.framework_id, test_perf_data[0].push.revision)
|
||||
|
||||
query_params2 = '?repository={}&framework={}&interval=172800&no_subtests=true&startday=2018-07-01T23%3A28%3A29&endday=2018-12-31T23%3A28%3A29'.format(
|
||||
test_perf_signature.repository.name, test_perf_signature.framework_id)
|
||||
|
||||
expected = [{
|
||||
'signature_id': test_perf_signature.id,
|
||||
'framework_id': test_perf_signature.framework_id,
|
||||
'signature_hash': test_perf_signature.signature_hash,
|
||||
'platform': test_perf_signature.platform.platform,
|
||||
'test': test_perf_signature.test,
|
||||
'lower_is_better': test_perf_signature.lower_is_better,
|
||||
'has_subtests': test_perf_signature.has_subtests,
|
||||
'values': [test_perf_data[0].value],
|
||||
'name': 'mysuite mytest opt e10s opt',
|
||||
'parent_signature': None
|
||||
}]
|
||||
|
||||
resp1 = client.get(reverse('performance-summary') + query_params1)
|
||||
assert resp1.status_code == 200
|
||||
assert resp1.json() == expected
|
||||
|
||||
expected[0]['values'] = [item.value for item in test_perf_data]
|
||||
resp2 = client.get(reverse('performance-summary') + query_params2)
|
||||
assert resp2.status_code == 200
|
||||
assert resp2.json() == expected
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.17 on 2019-01-02 23:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('model', '0012_branch_maxlen'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='push',
|
||||
name='revision',
|
||||
field=models.CharField(db_index=True, max_length=40),
|
||||
),
|
||||
]
|
|
@ -132,7 +132,7 @@ class Push(models.Model):
|
|||
the changesets that were part of the push
|
||||
'''
|
||||
repository = models.ForeignKey(Repository, on_delete=models.CASCADE)
|
||||
revision = models.CharField(max_length=40)
|
||||
revision = models.CharField(max_length=40, db_index=True)
|
||||
author = models.CharField(max_length=150)
|
||||
time = models.DateTimeField(db_index=True)
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.conf import settings
|
|||
from django.db import transaction
|
||||
from rest_framework import (exceptions,
|
||||
filters,
|
||||
generics,
|
||||
pagination,
|
||||
viewsets)
|
||||
from rest_framework.response import Response
|
||||
|
@ -17,6 +18,7 @@ from rest_framework.status import HTTP_400_BAD_REQUEST
|
|||
from treeherder.model import models
|
||||
from treeherder.perf.alerts import get_alert_properties
|
||||
from treeherder.perf.models import (IssueTracker,
|
||||
OptionCollection,
|
||||
PerformanceAlert,
|
||||
PerformanceAlertSummary,
|
||||
PerformanceBugTemplate,
|
||||
|
@ -30,7 +32,9 @@ from .performance_serializers import (IssueTrackerSerializer,
|
|||
PerformanceAlertSerializer,
|
||||
PerformanceAlertSummarySerializer,
|
||||
PerformanceBugTemplateSerializer,
|
||||
PerformanceFrameworkSerializer)
|
||||
PerformanceFrameworkSerializer,
|
||||
PerformanceQueryParamsSerializer,
|
||||
PerformanceSummarySerializer)
|
||||
|
||||
|
||||
class PerformanceSignatureViewSet(viewsets.ViewSet):
|
||||
|
@ -423,3 +427,67 @@ class PerformanceIssueTrackerViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
serializer_class = IssueTrackerSerializer
|
||||
filter_backends = [filters.OrderingFilter]
|
||||
ordering = 'id'
|
||||
|
||||
|
||||
class PerformanceSummary(generics.ListAPIView):
|
||||
|
||||
serializer_class = PerformanceSummarySerializer
|
||||
queryset = None
|
||||
|
||||
def list(self, request):
|
||||
query_params = PerformanceQueryParamsSerializer(data=request.query_params)
|
||||
if not query_params.is_valid():
|
||||
return Response(data=query_params.errors,
|
||||
status=HTTP_400_BAD_REQUEST)
|
||||
|
||||
startday = query_params.validated_data['startday']
|
||||
endday = query_params.validated_data['endday']
|
||||
revision = query_params.validated_data['revision']
|
||||
repository_name = query_params.validated_data['repository']
|
||||
interval = query_params.validated_data['interval']
|
||||
frameworks = query_params.validated_data['framework']
|
||||
parent_signature = query_params.validated_data['parent_signature']
|
||||
no_subtests = query_params.validated_data['no_subtests']
|
||||
|
||||
signature_data = (PerformanceSignature.objects
|
||||
.select_related('framework', 'repository', 'platform', 'push')
|
||||
.filter(repository__name=repository_name, framework__in=frameworks,
|
||||
parent_signature__isnull=no_subtests))
|
||||
if parent_signature:
|
||||
signature_data = signature_data.filter(parent_signature_id=parent_signature)
|
||||
|
||||
if interval:
|
||||
signature_data = signature_data.filter(last_updated__gte=datetime.datetime.utcfromtimestamp(
|
||||
int(time.time() - int(interval))))
|
||||
|
||||
# TODO signature_hash is being returned for legacy support - should be removed at some point
|
||||
self.queryset = (signature_data.values('framework_id', 'id', 'lower_is_better', 'has_subtests', 'extra_options', 'suite',
|
||||
'signature_hash', 'platform__platform', 'test', 'option_collection_id',
|
||||
'parent_signature_id'))
|
||||
|
||||
signature_ids = [item['id'] for item in list(self.queryset)]
|
||||
|
||||
data = (PerformanceDatum.objects.select_related('push', 'repository')
|
||||
.filter(signature_id__in=signature_ids, repository__name=repository_name))
|
||||
|
||||
if revision:
|
||||
data = data.filter(push__revision=revision)
|
||||
else:
|
||||
data = data.filter(push_timestamp__gt=startday, push_timestamp__lt=endday)
|
||||
|
||||
# more efficient than creating a join on option_collection and option
|
||||
option_collection = OptionCollection.objects.select_related('option').values('id', 'option__name')
|
||||
option_collection_map = {item['id']: item['option__name'] for item in list(option_collection)}
|
||||
|
||||
grouped_values = defaultdict(list)
|
||||
for signature_id, value in data.values_list('signature_id', 'value'):
|
||||
if value is not None:
|
||||
grouped_values[signature_id].append(value)
|
||||
|
||||
# name field is created in the serializer
|
||||
for item in self.queryset:
|
||||
item['values'] = grouped_values.get(item['id'], [])
|
||||
item['option_name'] = option_collection_map[item['option_collection_id']]
|
||||
|
||||
serializer = self.get_serializer(self.queryset, many=True)
|
||||
return Response(data=serializer.data)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import decimal
|
||||
|
||||
import six
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import (exceptions,
|
||||
serializers)
|
||||
|
||||
from treeherder.model.models import Repository
|
||||
from treeherder.perf.models import (IssueTracker,
|
||||
PerformanceAlert,
|
||||
PerformanceAlertSummary,
|
||||
|
@ -146,3 +150,50 @@ class IssueTrackerSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = IssueTracker
|
||||
fields = ['id', 'text', 'issueTrackerUrl']
|
||||
|
||||
|
||||
class PerformanceQueryParamsSerializer(serializers.Serializer):
|
||||
startday = serializers.DateTimeField(required=False, allow_null=True, default=None)
|
||||
endday = serializers.DateTimeField(required=False, allow_null=True, default=None)
|
||||
revision = serializers.CharField(required=False, allow_null=True, default=None)
|
||||
repository = serializers.CharField()
|
||||
framework = serializers.ListField(child=serializers.IntegerField())
|
||||
interval = serializers.IntegerField(required=False, allow_null=True, default=None)
|
||||
parent_signature = serializers.CharField(required=False, allow_null=True, default=None)
|
||||
no_subtests = serializers.BooleanField(required=False)
|
||||
|
||||
def validate(self, data):
|
||||
if data['revision'] is None and (data['startday'] is None or data['endday'] is None):
|
||||
raise serializers.ValidationError('Required: revision or startday and endday.')
|
||||
|
||||
return data
|
||||
|
||||
def validate_repository(self, repository):
|
||||
try:
|
||||
Repository.objects.get(name=repository)
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
raise serializers.ValidationError('{} does not exist.'.format(repository))
|
||||
|
||||
return repository
|
||||
|
||||
|
||||
class PerformanceSummarySerializer(serializers.ModelSerializer):
|
||||
platform = serializers.CharField(source="platform__platform")
|
||||
values = serializers.ListField(child=serializers.DecimalField(
|
||||
rounding=decimal.ROUND_HALF_EVEN, decimal_places=2, max_digits=None, coerce_to_string=False))
|
||||
name = serializers.SerializerMethodField()
|
||||
parent_signature = serializers.CharField(source="parent_signature_id")
|
||||
signature_id = serializers.IntegerField(source="id")
|
||||
|
||||
class Meta:
|
||||
model = PerformanceSignature
|
||||
fields = ['signature_id', 'framework_id', 'signature_hash', 'platform', 'test',
|
||||
'lower_is_better', 'has_subtests', 'values', 'name', 'parent_signature']
|
||||
|
||||
def get_name(self, value):
|
||||
test = value['test']
|
||||
suite = value['suite']
|
||||
test_suite = suite if test == '' or test == suite else '{} {}'.format(suite, test)
|
||||
return '{} {} {}'.format(test_suite, value['option_name'],
|
||||
value['extra_options'])
|
||||
|
|
|
@ -139,4 +139,5 @@ urlpatterns = [
|
|||
url(r'^failures/$', intermittents_view.Failures.as_view(), name='failures'),
|
||||
url(r'^failuresbybug/$', intermittents_view.FailuresByBug.as_view(), name='failures-by-bug'),
|
||||
url(r'^failurecount/$', intermittents_view.FailureCount.as_view(), name='failure-count'),
|
||||
url(r'^performance/summary/$', performance_data.PerformanceSummary.as_view(), name='performance-summary'),
|
||||
]
|
||||
|
|
|
@ -24,6 +24,8 @@ export const pushEndpoint = '/resultset/';
|
|||
|
||||
export const repoEndpoint = '/repository/';
|
||||
|
||||
export const perfSummaryEndpoint = 'performance/summary/';
|
||||
|
||||
export const getRunnableJobsURL = function getRunnableJobsURL(decisionTaskId) {
|
||||
return `https://queue.taskcluster.net/v1/task/${decisionTaskId}/runs/0/artifacts/public/runnable-jobs.json`;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// Remove the eslint-disable when rewriting this file during the React conversion.
|
||||
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback, prefer-destructuring, prefer-template, radix */
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import difference from 'lodash/difference';
|
||||
import metricsgraphics from 'metrics-graphics';
|
||||
|
||||
|
@ -13,9 +12,8 @@ import {
|
|||
import PushModel from '../../../models/push';
|
||||
import RepositoryModel from '../../../models/repository';
|
||||
import PerfSeriesModel from '../../../models/perfSeries';
|
||||
import { getCounterMap, getInterval, validateQueryParams, getResultsMap,
|
||||
getGraphsLink } from '../../../perfherder/helpers';
|
||||
import { getApiUrl } from '../../../helpers/url';
|
||||
import { getCounterMap, getInterval, validateQueryParams, getGraphsLink } from '../../../perfherder/helpers';
|
||||
import { getApiUrl, createApiUrl, perfSummaryEndpoint } from '../../../helpers/url';
|
||||
import { getData } from '../../../helpers/http';
|
||||
|
||||
|
||||
|
@ -25,6 +23,7 @@ perf.controller('CompareResultsCtrl', [
|
|||
function CompareResultsCtrl($state, $stateParams, $scope,
|
||||
$httpParamSerializer, $q) {
|
||||
function displayResults(rawResultsMap, newRawResultsMap) {
|
||||
|
||||
$scope.compareResults = {};
|
||||
$scope.titles = {};
|
||||
if ($scope.originalRevision) {
|
||||
|
@ -46,15 +45,13 @@ perf.controller('CompareResultsCtrl', [
|
|||
if (Object.keys($scope.newStddevVariance).indexOf(platform) < 0) {
|
||||
$scope.newStddevVariance[platform] = { values: [], lowerIsBetter: true, frameworkID: $scope.filterOptions.framework.id };
|
||||
}
|
||||
|
||||
const oldSig = Object.keys(rawResultsMap).find(sig =>
|
||||
rawResultsMap[sig].name === testName && rawResultsMap[sig].platform === platform,
|
||||
const oldResults = rawResultsMap.find(sig =>
|
||||
sig.name === testName && sig.platform === platform
|
||||
);
|
||||
const newSig = Object.keys(newRawResultsMap).find(sig =>
|
||||
newRawResultsMap[sig].name === testName && newRawResultsMap[sig].platform === platform,
|
||||
const newResults = newRawResultsMap.find(sig =>
|
||||
sig.name === testName && sig.platform === platform
|
||||
);
|
||||
|
||||
const cmap = getCounterMap(testName, rawResultsMap[oldSig], newRawResultsMap[newSig]);
|
||||
const cmap = getCounterMap(testName, oldResults, newResults);
|
||||
if (cmap.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
@ -70,9 +67,7 @@ perf.controller('CompareResultsCtrl', [
|
|||
}
|
||||
}
|
||||
cmap.links = [];
|
||||
|
||||
const hasSubtests = ((rawResultsMap[oldSig] && rawResultsMap[oldSig].hasSubtests) ||
|
||||
(newRawResultsMap[newSig] && newRawResultsMap[newSig].hasSubtests));
|
||||
const hasSubtests = ((oldResults && oldResults.has_subtests) || (newResults && newResults.has_subtests));
|
||||
|
||||
if ($scope.originalRevision) {
|
||||
if (hasSubtests) {
|
||||
|
@ -82,8 +77,8 @@ perf.controller('CompareResultsCtrl', [
|
|||
originalRevision: $scope.originalRevision,
|
||||
newProject: $scope.newProject.name,
|
||||
newRevision: $scope.newRevision,
|
||||
originalSignature: oldSig,
|
||||
newSignature: newSig,
|
||||
originalSignature: oldResults ? oldResults.signature_id : null,
|
||||
newSignature: oldResults ? newResults.signature_id : null,
|
||||
framework: $scope.filterOptions.framework.id,
|
||||
});
|
||||
cmap.links.push({
|
||||
|
@ -97,7 +92,7 @@ perf.controller('CompareResultsCtrl', [
|
|||
href: getGraphsLink([...new Set(
|
||||
[$scope.originalProject, $scope.newProject])].map(project => ({
|
||||
projectName: project.name,
|
||||
signature: oldSig,
|
||||
signature: !oldResults ? newResults.signature_hash : oldResults.signature_hash,
|
||||
frameworkId: $scope.filterOptions.framework.id,
|
||||
})),
|
||||
[$scope.originalResultSet, $scope.newResultSet]),
|
||||
|
@ -109,8 +104,8 @@ perf.controller('CompareResultsCtrl', [
|
|||
originalProject: $scope.originalProject.name,
|
||||
newProject: $scope.newProject.name,
|
||||
newRevision: $scope.newRevision,
|
||||
originalSignature: oldSig,
|
||||
newSignature: newSig,
|
||||
originalSignature: oldResults ? oldResults.signature_id : null,
|
||||
newSignature: newResults ? newResults.signature_id : null,
|
||||
framework: $scope.filterOptions.framework.id,
|
||||
selectedTimeRange: $scope.selectedTimeRange.value,
|
||||
});
|
||||
|
@ -119,13 +114,12 @@ perf.controller('CompareResultsCtrl', [
|
|||
href: detailsLink,
|
||||
});
|
||||
}
|
||||
|
||||
cmap.links.push({
|
||||
title: 'graph',
|
||||
href: getGraphsLink([...new Set(
|
||||
[$scope.originalProject, $scope.newProject])].map(project => ({
|
||||
projectName: project.name,
|
||||
signature: oldSig,
|
||||
signature: !oldResults ? newResults.signature_hash : oldResults.signature_hash,
|
||||
frameworkId: $scope.filterOptions.framework.id,
|
||||
})),
|
||||
[$scope.newResultSet], $scope.selectedTimeRange.value),
|
||||
|
@ -162,105 +156,47 @@ perf.controller('CompareResultsCtrl', [
|
|||
$scope.$apply();
|
||||
}
|
||||
|
||||
function load() {
|
||||
const createQueryParams = (repository, interval) => ({
|
||||
repository,
|
||||
framework: $scope.filterOptions.framework.id,
|
||||
interval,
|
||||
no_subtests: true,
|
||||
});
|
||||
|
||||
async function load() {
|
||||
$scope.dataLoading = true;
|
||||
$scope.testList = [];
|
||||
$scope.platformList = [];
|
||||
let originalParams;
|
||||
let interval;
|
||||
|
||||
if ($scope.originalRevision) {
|
||||
const timeRange = getInterval($scope.originalResultSet.push_timestamp, $scope.newResultSet.push_timestamp);
|
||||
// Optimization - if old/new branches are the same collect data in one pass
|
||||
const resultSetIds = (isEqual($scope.originalProject, $scope.newProject)) ?
|
||||
[$scope.originalResultSet.id, $scope.newResultSet.id] : [$scope.originalResultSet.id];
|
||||
|
||||
PerfSeriesModel.getSeriesList($scope.originalProject.name, {
|
||||
interval: timeRange,
|
||||
subtests: 0,
|
||||
framework: $scope.filterOptions.framework.id,
|
||||
}).then((originalSeriesList) => {
|
||||
$scope.platformList = [...new Set(
|
||||
originalSeriesList.map(series => series.platform))];
|
||||
$scope.testList = [...new Set(
|
||||
originalSeriesList.map(series => series.name))];
|
||||
return getResultsMap($scope.originalProject.name,
|
||||
originalSeriesList,
|
||||
{ push_id: resultSetIds });
|
||||
}).then((resultMaps) => {
|
||||
const originalResultsMap = resultMaps[$scope.originalResultSet.id] || {};
|
||||
const newResultsMap = resultMaps[$scope.newResultSet.id] || {};
|
||||
|
||||
// Optimization - we collected all data in a single pass
|
||||
if (isEqual($scope.originalProject, $scope.newProject)) {
|
||||
$scope.dataLoading = false;
|
||||
displayResults(originalResultsMap, newResultsMap);
|
||||
return;
|
||||
}
|
||||
|
||||
PerfSeriesModel.getSeriesList($scope.newProject.name, {
|
||||
interval: timeRange,
|
||||
subtests: 0,
|
||||
framework: $scope.filterOptions.framework.id,
|
||||
}).then((newSeriesList) => {
|
||||
$scope.platformList = [...new Set([
|
||||
...$scope.platformList,
|
||||
...new Set(newSeriesList.map(series => series.platform)),
|
||||
])];
|
||||
$scope.testList = [...new Set([
|
||||
...$scope.testList,
|
||||
...new Set(newSeriesList.map(series => series.name)),
|
||||
])];
|
||||
return getResultsMap($scope.newProject.name,
|
||||
newSeriesList,
|
||||
{ push_id: [$scope.newResultSet.id] });
|
||||
}).then((resultMaps) => {
|
||||
$scope.dataLoading = false;
|
||||
displayResults(originalResultsMap, resultMaps[$scope.newResultSet.id] || {});
|
||||
});
|
||||
});
|
||||
interval = getInterval($scope.originalResultSet.push_timestamp, $scope.newResultSet.push_timestamp);
|
||||
originalParams = createQueryParams($scope.originalProject.name, interval);
|
||||
originalParams.revision = $scope.originalResultSet.revision;
|
||||
} else {
|
||||
// using a range of data for baseline comparison
|
||||
PerfSeriesModel.getSeriesList($scope.originalProject.name, {
|
||||
interval: $scope.selectedTimeRange.value,
|
||||
subtests: 0,
|
||||
framework: $scope.filterOptions.framework.id,
|
||||
}).then((originalSeriesList) => {
|
||||
$scope.platformList = [...new Set(originalSeriesList.map(series => series.platform))];
|
||||
$scope.testList = [...new Set(originalSeriesList.map(series => series.name))];
|
||||
const startDateMs = ($scope.newResultSet.push_timestamp -
|
||||
$scope.selectedTimeRange.value) * 1000;
|
||||
interval = $scope.selectedTimeRange.value;
|
||||
const startDateMs = ($scope.newResultSet.push_timestamp - interval) * 1000;
|
||||
const endDateMs = $scope.newResultSet.push_timestamp * 1000;
|
||||
return getResultsMap(
|
||||
$scope.originalProject.name, originalSeriesList, {
|
||||
start_date: new Date(startDateMs).toISOString().slice(0, -5),
|
||||
end_date: new Date(endDateMs).toISOString().slice(0, -5),
|
||||
});
|
||||
}).then((originalResultsMap) => {
|
||||
PerfSeriesModel.getSeriesList($scope.newProject.name, {
|
||||
interval: $scope.selectedTimeRange.value,
|
||||
subtests: 0,
|
||||
framework: $scope.filterOptions.framework.id,
|
||||
}).then((newSeriesList) => {
|
||||
$scope.platformList = [...new Set([
|
||||
...$scope.platformList,
|
||||
...new Set(newSeriesList.map(series => series.platform)),
|
||||
])];
|
||||
$scope.testList = [...new Set([
|
||||
...$scope.testList,
|
||||
...new Set(newSeriesList.map(series => series.name)),
|
||||
])];
|
||||
return getResultsMap($scope.newProject.name,
|
||||
newSeriesList,
|
||||
{ push_id: [$scope.newResultSet.id] });
|
||||
}).then((resultMaps) => {
|
||||
|
||||
originalParams = createQueryParams($scope.originalProject.name, interval);
|
||||
originalParams.startday = new Date(startDateMs).toISOString().slice(0, -5);
|
||||
originalParams.endday = new Date(endDateMs).toISOString().slice(0, -5);
|
||||
}
|
||||
|
||||
const newParams = createQueryParams($scope.newProject.name, interval);
|
||||
newParams.revision = $scope.newResultSet.revision;
|
||||
|
||||
const [originalResults, newResults] = await Promise.all([getData(createApiUrl(perfSummaryEndpoint, originalParams)),
|
||||
getData(createApiUrl(perfSummaryEndpoint, newParams))]);
|
||||
|
||||
$scope.dataLoading = false;
|
||||
const newResult = resultMaps[$scope.newResultSet.id];
|
||||
if (newResult) {
|
||||
displayResults(originalResultsMap, newResult);
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const data = [...originalResults.data, ...newResults.data];
|
||||
$scope.platformList = [...new Set(data.map(item => item.platform))];
|
||||
$scope.testList = [...new Set(data.map(item => item.name))];
|
||||
|
||||
return displayResults(originalResults.data, newResults.data);
|
||||
}
|
||||
// TODO: duplicated in comparesubtestctrl
|
||||
function verifyRevision(project, revision, rsid) {
|
||||
|
@ -414,12 +350,13 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
}
|
||||
|
||||
function displayResults(rawResultsMap, newRawResultsMap) {
|
||||
|
||||
$scope.compareResults = {};
|
||||
$scope.titles = {};
|
||||
|
||||
const testName = $scope.testList[0];
|
||||
const testName = $scope.subtestTitle;
|
||||
|
||||
$scope.titles[testName] = $scope.platformList[0] + ': ' + testName;
|
||||
$scope.titles[testName] = `${$scope.platformList[0]}: ${testName}`;
|
||||
$scope.compareResults[testName] = [];
|
||||
|
||||
$scope.subtestTitle = $scope.titles[testName];
|
||||
|
@ -435,9 +372,7 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
let tempsig;
|
||||
// If no data for a given platform, or test, display N/A in table
|
||||
if (resultsMap) {
|
||||
tempsig = Object.keys(resultsMap).find(sig =>
|
||||
resultsMap[sig].name === page,
|
||||
);
|
||||
tempsig = resultsMap.find(sig => sig.test === page);
|
||||
} else {
|
||||
tempsig = 'undefined';
|
||||
resultsMap = {};
|
||||
|
@ -445,14 +380,14 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
}
|
||||
mapsigs.push(tempsig);
|
||||
});
|
||||
const oldSig = mapsigs[0];
|
||||
const newSig = mapsigs[1];
|
||||
const oldData = mapsigs[0];
|
||||
const newData = mapsigs[1];
|
||||
|
||||
const cmap = getCounterMap(testName, rawResultsMap[oldSig], newRawResultsMap[newSig]);
|
||||
if (oldSig === $scope.originalSignature ||
|
||||
oldSig === $scope.newSignature ||
|
||||
newSig === $scope.originalSignature ||
|
||||
newSig === $scope.newSignature) {
|
||||
const cmap = getCounterMap(testName, oldData, newData);
|
||||
if (oldData.parent_signature === $scope.originalSignature ||
|
||||
oldData.parent_signature === $scope.newSignature ||
|
||||
newData.parent_signature === $scope.originalSignature ||
|
||||
newData.parent_signature === $scope.newSignature) {
|
||||
cmap.highlightedTest = true;
|
||||
}
|
||||
|
||||
|
@ -476,7 +411,7 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
$scope.newProject,
|
||||
])].map(project => ({
|
||||
projectName: project.name,
|
||||
signature: oldSig,
|
||||
signature: !oldData ? newData.signature_hash : oldData.signature_hash,
|
||||
frameworkId: $scope.filterOptions.framework,
|
||||
})), [$scope.originalResultSet, $scope.newResultSet]),
|
||||
}];
|
||||
|
@ -489,8 +424,8 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
newProject: $scope.newProject.name,
|
||||
originalRevision: $scope.originalRevision,
|
||||
newRevision: $scope.newRevision,
|
||||
originalSubtestSignature: oldSig,
|
||||
newSubtestSignature: newSig,
|
||||
originalSubtestSignature: oldData ? oldData.signature_hash : null,
|
||||
newSubtestSignature: newData ? newData.signature_hash: null,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -502,7 +437,7 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
$scope.newProject,
|
||||
])].map(project => ({
|
||||
projectName: project.name,
|
||||
signature: oldSig,
|
||||
signature: !oldData ? newData.signature_hash : oldData.signature_hash,
|
||||
frameworkId: $scope.filterOptions.framework,
|
||||
})), [$scope.newResultSet], $scope.selectedTimeRange.value),
|
||||
}];
|
||||
|
@ -525,6 +460,47 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
$scope.$apply();
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
const createQueryParams = (parent_signature, repository) => ({
|
||||
parent_signature,
|
||||
framework: $scope.filterOptions.framework,
|
||||
repository,
|
||||
});
|
||||
|
||||
const originalParams = createQueryParams($scope.originalSignature, $scope.originalProject.name);
|
||||
|
||||
if ($scope.originalRevision) {
|
||||
originalParams.revision = $scope.originalResultSet.revision;
|
||||
} else {
|
||||
// TODO create a helper for the startday and endday since this is also used in compare view
|
||||
const startDateMs = ($scope.newResultSet.push_timestamp -
|
||||
$scope.selectedTimeRange.value) * 1000;
|
||||
const endDateMs = $scope.newResultSet.push_timestamp * 1000;
|
||||
|
||||
originalParams.startday = new Date(startDateMs).toISOString().slice(0, -5);
|
||||
originalParams.endday = new Date(endDateMs).toISOString().slice(0, -5);
|
||||
}
|
||||
|
||||
const newParams = createQueryParams($scope.newSignature, $scope.newProject.name);
|
||||
newParams.revision = $scope.newResultSet.revision;
|
||||
|
||||
const [originalResults, newResults] = await Promise.all([getData(createApiUrl(perfSummaryEndpoint, originalParams)),
|
||||
getData(createApiUrl(perfSummaryEndpoint, newParams))]);
|
||||
|
||||
$scope.dataLoading = false;
|
||||
|
||||
const results = [...originalResults.data, ...newResults.data];
|
||||
|
||||
const subtestName = results[0].name.split(' ');
|
||||
subtestName.splice(1, 1);
|
||||
$scope.subtestTitle = subtestName.join(' ');
|
||||
|
||||
$scope.pageList = [...new Set(results.map(subtest => subtest.test))];
|
||||
$scope.platformList = [...new Set(results.map(subtest => subtest.platform))];
|
||||
|
||||
return displayResults(originalResults.data, newResults.data);
|
||||
}
|
||||
|
||||
$scope.dataLoading = true;
|
||||
|
||||
RepositoryModel.getList().then((repos) => {
|
||||
|
@ -566,16 +542,6 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
return;
|
||||
}
|
||||
|
||||
let resultSetIds;
|
||||
if ($scope.originalRevision) {
|
||||
resultSetIds = [$scope.originalResultSet.id];
|
||||
|
||||
// Optimization - if old/new branches are the same collect data in one pass
|
||||
if (isEqual($scope.originalProject, $scope.newProject)) {
|
||||
resultSetIds.push($scope.newResultSet.id);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.filterOptions = {
|
||||
framework: $stateParams.framework || 1, // 1 == talos
|
||||
filter: $stateParams.filter || '',
|
||||
|
@ -624,158 +590,8 @@ perf.controller('CompareSubtestResultsCtrl', [
|
|||
selectedTimeRange: $scope.selectedTimeRange.value,
|
||||
});
|
||||
};
|
||||
if ($scope.originalRevision) {
|
||||
$q.all([
|
||||
PerfSeriesModel.getSeriesList(
|
||||
$scope.originalProject.name, {
|
||||
signature: $scope.originalSignature,
|
||||
framework: $scope.filterOptions.framework,
|
||||
}).then(function (originalSeries) {
|
||||
$scope.testList = [originalSeries[0].name];
|
||||
return undefined;
|
||||
}),
|
||||
PerfSeriesModel.getSeriesList(
|
||||
$scope.originalProject.name,
|
||||
{
|
||||
parent_signature: $scope.originalSignature,
|
||||
framework: $scope.filterOptions.framework,
|
||||
}).then(function (originalSubtestList) {
|
||||
$scope.pageList = originalSubtestList.map(subtest => subtest.name);
|
||||
$scope.platformList = [...new Set(originalSubtestList.map(subtest => subtest.platform))];
|
||||
return getResultsMap($scope.originalProject.name,
|
||||
originalSubtestList,
|
||||
{ push_id: resultSetIds });
|
||||
}),
|
||||
]).then(function (results) {
|
||||
const originalSeriesMap = results[1][$scope.originalResultSet.id] || {};
|
||||
const newSeriesMap = results[1][$scope.newResultSet.id] || {};
|
||||
[originalSeriesMap, newSeriesMap].forEach(function (seriesMap) {
|
||||
// If there is no data for a given signature, handle it gracefully
|
||||
if (seriesMap) {
|
||||
Object.keys(seriesMap).forEach(function (series) {
|
||||
if ($scope.pageList.indexOf(seriesMap[series].name) === -1) {
|
||||
$scope.pageList.push(seriesMap[series].name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// if original and new project are same, we should
|
||||
// have collected all data in a single pass
|
||||
if (isEqual($scope.originalProject, $scope.newProject)) {
|
||||
$scope.dataLoading = false;
|
||||
displayResults(originalSeriesMap, newSeriesMap);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.newSignature) {
|
||||
PerfSeriesModel.getSeriesList(
|
||||
$scope.newProject.name, {
|
||||
parent_signature: $scope.newSignature,
|
||||
framework: $scope.filterOptions.framework,
|
||||
}).then((newSeriesList) => {
|
||||
$scope.platformList = [...new Set([
|
||||
...$scope.platformList,
|
||||
...newSeriesList.map(series => series.platform),
|
||||
])];
|
||||
$scope.testList = [...new Set([
|
||||
...$scope.testList,
|
||||
...newSeriesList.map(series => series.name),
|
||||
])];
|
||||
|
||||
return getResultsMap($scope.newProject.name,
|
||||
newSeriesList,
|
||||
{ push_id: [$scope.newResultSet.id] });
|
||||
}).then(function (newSeriesMaps) {
|
||||
let newSeriesMap = newSeriesMaps[$scope.newResultSet.id];
|
||||
// There is a chance that we haven't received data for the given signature/resultSet yet
|
||||
if (newSeriesMap) {
|
||||
Object.keys(newSeriesMap).forEach(function (series) {
|
||||
if ($scope.pageList.indexOf(newSeriesMap[series].name) === -1) {
|
||||
$scope.pageList.push(newSeriesMap[series].name);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newSeriesMap = {};
|
||||
}
|
||||
$scope.dataLoading = false;
|
||||
displayResults(originalSeriesMap, newSeriesMap);
|
||||
});
|
||||
} else {
|
||||
$scope.dataLoading = false;
|
||||
displayResults(originalSeriesMap, {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$q.all([
|
||||
PerfSeriesModel.getSeriesList(
|
||||
$scope.originalProject.name, {
|
||||
signature: $scope.originalSignature,
|
||||
framework: $scope.filterOptions.framework,
|
||||
}).then(function (originalSeries) {
|
||||
$scope.testList = [originalSeries[0].name];
|
||||
return undefined;
|
||||
}),
|
||||
PerfSeriesModel.getSeriesList(
|
||||
$scope.originalProject.name,
|
||||
{
|
||||
parent_signature: $scope.originalSignature,
|
||||
framework: $scope.filterOptions.framework,
|
||||
}).then(function (originalSubtestList) {
|
||||
$scope.pageList = originalSubtestList.map(subtest => subtest.name);
|
||||
$scope.platformList = [...new Set(originalSubtestList.map(subtest => subtest.platform))];
|
||||
const startDateMs = ($scope.newResultSet.push_timestamp -
|
||||
$scope.selectedTimeRange.value) * 1000;
|
||||
const endDateMs = $scope.newResultSet.push_timestamp * 1000;
|
||||
return getResultsMap(
|
||||
$scope.originalProject.name,
|
||||
originalSubtestList, {
|
||||
start_date: new Date(startDateMs).toISOString().slice(0, -5),
|
||||
end_date: new Date(endDateMs).toISOString().slice(0, -5),
|
||||
});
|
||||
}),
|
||||
]).then(
|
||||
function (originalResults) {
|
||||
const originalSeriesMap = originalResults[1];
|
||||
if ($scope.newSignature) {
|
||||
PerfSeriesModel.getSeriesList(
|
||||
$scope.newProject.name, {
|
||||
parent_signature: $scope.newSignature,
|
||||
framework: $scope.filterOptions.framework,
|
||||
}).then(function (newSeriesList) {
|
||||
$scope.platformList = [...new Set([
|
||||
...$scope.platformList,
|
||||
...newSeriesList.map(series => series.platform),
|
||||
])];
|
||||
$scope.testList = [...new Set([
|
||||
...$scope.testList,
|
||||
...newSeriesList.map(series => series.name),
|
||||
])];
|
||||
|
||||
return getResultsMap($scope.newProject.name,
|
||||
newSeriesList,
|
||||
{ push_id: [$scope.newResultSet.id] });
|
||||
}).then(function (newSeriesMaps) {
|
||||
let newSeriesMap = newSeriesMaps[$scope.newResultSet.id];
|
||||
// There is a chance that we haven't received data for the given signature/resultSet yet
|
||||
if (newSeriesMap) {
|
||||
Object.keys(newSeriesMap).forEach(function (series) {
|
||||
if ($scope.pageList.indexOf(newSeriesMap[series].name) === -1) {
|
||||
$scope.pageList.push(newSeriesMap[series].name);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newSeriesMap = {};
|
||||
}
|
||||
$scope.dataLoading = false;
|
||||
displayResults(originalSeriesMap, newSeriesMap);
|
||||
});
|
||||
} else {
|
||||
$scope.dataLoading = false;
|
||||
displayResults(originalSeriesMap, {});
|
||||
}
|
||||
});
|
||||
}
|
||||
fetchData()
|
||||
});
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<th class="num-runs" style="width: 80px"># Runs</th>
|
||||
<th class="test-warning" style="width: 30px"><!-- warning if not enough --></th>
|
||||
</tr>
|
||||
<tr ng-class="getCompareClasses(compareResult, 'row')" ng-repeat="compareResult in compareResults.results | orderBy: 'name' track by compareResult.name">
|
||||
<tr ng-class="getCompareClasses(compareResult, 'row')" ng-repeat="compareResult in compareResults.results | orderBy: 'name' track by $index">
|
||||
<td class="test-title">{{compareResult.name}}
|
||||
<span class="result-links" ng-if="compareResult.links.length > 0">
|
||||
<span ng-repeat="link in compareResult.links track by link.title">
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import chunk from 'lodash/chunk';
|
||||
import numeral from 'numeral';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
|
||||
import { getApiUrl, createQueryParams, repoEndpoint } from '../helpers/url';
|
||||
import { create, getData, update } from '../helpers/http';
|
||||
import PerfSeriesModel, {
|
||||
getSeriesName,
|
||||
getTestName,
|
||||
} from '../models/perfSeries';
|
||||
import { getSeriesName, getTestName } from '../models/perfSeries';
|
||||
import OptionCollectionModel from '../models/optionCollection';
|
||||
import {
|
||||
phAlertStatusMap,
|
||||
|
@ -200,12 +196,13 @@ export const getCounterMap = function getCounterMap(
|
|||
return cmap; // No comparison, just display for one side.
|
||||
}
|
||||
|
||||
cmap.frameworkId = originalData.frameworkId;
|
||||
cmap.frameworkId = originalData.framework_id;
|
||||
// Normally tests are "lower is better", can be over-ridden with a series option
|
||||
cmap.delta = cmap.newValue - cmap.originalValue;
|
||||
|
||||
cmap.newIsBetter =
|
||||
(originalData.lowerIsBetter && cmap.delta < 0) ||
|
||||
(!originalData.lowerIsBetter && cmap.delta > 0);
|
||||
(originalData.lower_is_better && cmap.delta < 0) ||
|
||||
(!originalData.lower_is_better && cmap.delta > 0);
|
||||
|
||||
cmap.deltaPercentage = calcPercentOf(cmap.delta, cmap.originalValue);
|
||||
// arbitrary scale from 0-20% multiplied by 5, capped
|
||||
|
@ -306,56 +303,6 @@ export const validateQueryParams = async function validateQueryParams(params) {
|
|||
return errors;
|
||||
};
|
||||
|
||||
const getResultMapEntry = (datum, resultsMap, params) => {
|
||||
if (params.push_id) {
|
||||
if (!resultsMap[datum.push_id]) {
|
||||
resultsMap[datum.push_id] = {};
|
||||
}
|
||||
return resultsMap[datum.push_id];
|
||||
}
|
||||
return resultsMap;
|
||||
};
|
||||
|
||||
export const getResultsMap = function getResultsMap(
|
||||
projectName,
|
||||
seriesList,
|
||||
params,
|
||||
) {
|
||||
const resultsMap = {};
|
||||
|
||||
return Promise.all(
|
||||
chunk(seriesList, 150).map(seriesChunk =>
|
||||
PerfSeriesModel.getSeriesData(projectName, {
|
||||
signature_id: seriesChunk.map(series => series.id),
|
||||
framework: [...new Set(seriesChunk.map(series => series.frameworkId))],
|
||||
...params,
|
||||
}).then(seriesData => {
|
||||
// Aggregates data from a single group of values and returns an object containing
|
||||
// description (name/platform) and values; these are later processed in getCounterMap.
|
||||
for (const [signatureHash, data] of Object.entries(seriesData)) {
|
||||
const signature = seriesList.find(
|
||||
series => series.signature === signatureHash,
|
||||
);
|
||||
|
||||
if (signature) {
|
||||
data.forEach(datum => {
|
||||
const entry = getResultMapEntry(datum, resultsMap, params);
|
||||
if (!entry[signatureHash]) {
|
||||
entry[signatureHash] = {
|
||||
...signature,
|
||||
values: [datum.value],
|
||||
};
|
||||
} else {
|
||||
entry[signatureHash].values.push(datum.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
).then(() => resultsMap);
|
||||
};
|
||||
|
||||
export const getGraphsLink = function getGraphsLink(
|
||||
seriesList,
|
||||
resultSets,
|
||||
|
|
Загрузка…
Ссылка в новой задаче