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:
Sarah Clements 2019-01-11 09:47:05 -08:00 коммит произвёл GitHub
Родитель d1b81bde58
Коммит fbb60958cd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 287 добавлений и 351 удалений

Просмотреть файл

@ -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}}&nbsp;&nbsp;
<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,