Changeset 180333 in webkit
- Timestamp:
- Feb 18, 2015 6:41:45 PM (9 years ago)
- Location:
- trunk/Websites/perf.webkit.org
- Files:
-
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Websites/perf.webkit.org/ChangeLog
r180120 r180333 1 2015-02-18 Ryosuke Niwa <rniwa@webkit.org> 2 3 Analysis task pages are unusable 4 https://bugs.webkit.org/show_bug.cgi?id=141786 5 6 Reviewed by Andreas Kling. 7 8 This patch makes following improvements to analysis task pages: 9 1. Making the main chart interactive. This change required the use of App.Pane as well as moving the code to 10 compute the data for the details pane from PaneController. 11 2. Moving the form to add a new test group to the top of test groups instead of the bottom of them. 12 3. Grouping the build requests in each test group by root sets instead of the order by which they were ran. 13 This change required the creation of App.TestGroupPane as well as its methods. 14 4. Show a box plot for each root set configuration as well as each build request. This change required 15 App.BoxPlotComponent. 16 5. Show revisions of each repository (e.g. WebKit) for each root set and build request. 17 18 * public/api/build-requests.php: 19 (main): Update per the rename of BuildRequestsFetcher::root_sets to BuildRequestsFetcher::root_sets_by_id. 20 21 * public/api/test-groups.php: 22 (main): Include root sets and roots in the response. 23 (format_test_group): 24 25 * public/include/build-requests-fetcher.php: 26 (BuildRequestsFetcher::root_sets_by_id): Renamed from root_sets. 27 (BuildRequestsFetcher::root_sets): Added. 28 (BuildRequestsFetcher::roots): Added. 29 (BuildRequestsFetcher::fetch_roots_for_set): Takes a boolean argument $resolve_ids. This flag is only set to 30 true in /api/build-requests/ (as done prior to this patch) to use repository names as identifiers since 31 tools/sync-with-buildbot.py can't convert repository names to their ids. 32 33 * public/v2/analysis.js: 34 (App.Root): Added. 35 (App.RootSet): Added. 36 (App.RootSet.revisionForRepository): Added. 37 (App.TestGroup.rootSets): Deleted the code to compute root set ids from build requests now that the JSON 38 response at /api/test-groups will include them. 39 (App.BuildRequest): Ditto. Also deleted 'configLetter' property, which has been moved to a proxy created by 40 _createConfigurationSummary. 41 (App.BuildRequest.statusLabel): Use 'Completed' as the human readable label for 'completed' status. 42 (App.BuildRequest.aggregateStatuses): Added. Generates a human readable status for a set of build requests. 43 44 * public/v2/app.css: Updated style rules for analysis task pages. 45 46 * public/v2/app.js: 47 (App.Pane): This class is now used in analysis task pages to make the main chart interactive. 48 (App.Pane._updateDetails): Moved from App.PaneController. 49 50 (App.PaneController._updateCanAnalyze): Updated the code per the move of selectedPoints. 51 52 (App.AnalysisTaskController): Added 'details'. 53 (App.AnalysisTaskController._taskUpdated): 54 (App.AnalysisTaskController.paneDomain):Renamed from _fetchedRuns. 55 (App.AnalysisTaskController.updateTestGroupPanes): Added. Creates App.TestGroupPane for each test group. 56 (App.AnalysisTaskController.actions.toggleShowRequestList): Added. 57 58 (App.TestGroupPane): Added. 59 (App.TestGroupPane._populate): Added. Group build requests by root sets and create a summary for each group. 60 (App.TestGroupPane._computeRepositoryList): Added. Returns a sorted list of repositories which is the union 61 of all repositories appearing in root sets and builds associated with A/B testing results. 62 (App.TestGroupPane._groupRequestsByConfigurations): Added. Groups build requests by root sets. 63 (App.TestGroupPane._createConfigurationSummary): Added. Creates a summary for a group of build requests that 64 use the same root set. We start by wrapping "raw" build requests in a proxy with formatted values, 65 build numbers, etc... obtained from the fetched chart data. The list of revisions shown in the group summary 66 is a union of revisions in the root set and the first build request in the group. We null-out revision info 67 for a build request if it is identical to the one in the summary. The range of values is expanded as needed 68 by the values in the group as well as 95% percentile confidence interval. 69 70 (App.BoxPlotComponent): Added. Controls a box plot shown for each test group summary and build request. 71 (App.BoxPlotComponent.didInsertElement): Added. Inserts a SVG element as well as two indicator rects to show 72 the mean and the confidence interval. 73 (App.BoxPlotComponent._updateBars): Added. Updates the dimensions of the indicator rects. 74 (App.BoxPlotComponent.valueChanged): Added. Computes the relative dimensions of the indicator rects and 75 calls _updateBars to update the rects. 76 77 * public/v2/chart-pane.css: Added some style rules to be used in the details pane in analysis task pages. 78 79 * public/v2/data.js: 80 (Measurement.prototype.formattedRevisions): 81 (Measurement.formatRevisionRange): Renamed from Measurement.prototype._formatRevisionRange so that it can be 82 called in _createConfigurationSummary. 83 84 * public/v2/index.html: Updated the templates for analysis task pages. Moved the form to create a new test 85 group above all test groups, and replaced the list of data points by "details" pane used in the charts page. 86 Also made the fetching of chartData no longer block showing of test groups. 87 88 * public/v2/interactive-chart.js: 89 (App.InteractiveChartComponent._updateDomain): Added an early exit to fix a newly revealed race condition. 90 (App.InteractiveChartComponent._domainChanged): Ditto. 91 (App.InteractiveChartComponent._updateSelectionToolbar): Made it respect 'zoomable' boolean property. 92 93 * public/v2/js/statistics.js: 94 (Statistics.min): Added. 95 (Statistics.max): Added. 96 97 * public/v2/manifest.js: 98 (App.Manifest.fetchRunsWithPlatformAndMetric): Added formatWithDeltaAndUnit to be used in _createConfigurationSummary. 99 1 100 2015-02-14 Ryosuke Niwa <rniwa@webkit.org> 2 101 -
trunk/Websites/perf.webkit.org/public/api/build-requests.php
r180091 r180333 46 46 exit_with_success(array( 47 47 'buildRequests' => $requests_fetcher->results_with_resolved_ids(), 48 'rootSets' => $requests_fetcher->root_sets (),48 'rootSets' => $requests_fetcher->root_sets_by_id(), 49 49 'updates' => $updates, 50 50 )); -
trunk/Websites/perf.webkit.org/public/api/test-groups.php
r178234 r180333 40 40 41 41 $build_requests = $build_requests_fetcher->results(); 42 foreach ($build_requests as $request) 43 array_push($group_by_id[$request['testGroup']]['buildRequests'], $request['id']); 42 foreach ($build_requests as $request) { 43 $request_group = &$group_by_id[$request['testGroup']]; 44 array_push($request_group['buildRequests'], $request['id']); 45 array_push($request_group['rootSets'], $request['rootSet']); 46 } 44 47 45 exit_with_success(array('testGroups' => $test_groups, 'buildRequests' => $build_requests)); 48 exit_with_success(array('testGroups' => $test_groups, 49 'buildRequests' => $build_requests, 50 'rootSets' => $build_requests_fetcher->root_sets(), 51 'roots' => $build_requests_fetcher->roots())); 46 52 } 47 53 … … 54 60 'createdAt' => strtotime($group_row['testgroup_created_at']) * 1000, 55 61 'buildRequests' => array(), 62 'rootSets' => array(), 56 63 ); 57 64 } -
trunk/Websites/perf.webkit.org/public/include/build-requests-fetcher.php
r178234 r180333 7 7 $this->db = $db; 8 8 $this->rows = null; 9 $this->root_sets = array(); 10 $this->roots = array(); 9 11 $this->root_sets_by_id = array(); 10 12 } … … 51 53 52 54 if (!array_key_exists($root_set_id, $this->root_sets_by_id)) 53 $this->root_sets_by_id[$root_set_id] = $this->fetch_roots_for_set($root_set_id );55 $this->root_sets_by_id[$root_set_id] = $this->fetch_roots_for_set($root_set_id, $resolve_ids); 54 56 55 57 array_push($requests, array( … … 70 72 } 71 73 72 function root_sets () {74 function root_sets_by_id() { 73 75 return $this->root_sets_by_id; 74 76 } 75 77 76 private function fetch_roots_for_set($root_set_id) { 78 function root_sets() { 79 return $this->root_sets; 80 } 81 82 function roots() { 83 return $this->roots; 84 } 85 86 private function fetch_roots_for_set($root_set_id, $resolve_ids) { 77 87 $root_rows = $this->db->query_and_fetch_all('SELECT * 78 88 FROM roots, commits LEFT OUTER JOIN repositories ON commit_repository = repository_id … … 80 90 81 91 $roots = array(); 82 foreach ($root_rows as $row) 83 $roots[$row['repository_name']] = $row['commit_revision']; 92 $root_ids = array(); 93 foreach ($root_rows as $row) { 94 $repository = $row['repository_id']; 95 $revision = $row['commit_revision']; 96 $root_id = $root_set_id . '-' . $repository; 97 array_push($root_ids, $root_id); 98 array_push($this->roots, array('id' => $root_id, 'repository' => $repository, 'revision' => $revision)); 99 $roots[$resolve_ids ? $row['repository_name'] : $row['repository_id']] = $revision; 100 } 101 array_push($this->root_sets, array('id' => $root_set_id, 'roots' => $root_ids)); 84 102 85 103 return $roots; -
trunk/Websites/perf.webkit.org/public/v2/analysis.js
r180000 r180333 66 66 }); 67 67 68 App.Root = App.Model.extend({ 69 repository: DS.belongsTo('repository'), 70 revision: DS.attr('string'), 71 }); 72 73 App.RootSet = App.Model.extend({ 74 roots: DS.hasMany('roots'), 75 revisionForRepository: function (repository) 76 { 77 var root = this.get('roots').findBy('repository', repository); 78 if (!root) 79 return null; 80 return root.get('revision'); 81 } 82 }); 83 68 84 App.TestGroup = App.NameLabelModel.extend({ 69 85 task: DS.belongsTo('analysisTask'), … … 71 87 createdAt: DS.attr('date'), 72 88 buildRequests: DS.hasMany('buildRequests'), 73 rootSets: function () 74 { 75 var rootSetIds = []; 76 this.get('buildRequests').forEach(function (request) { 77 var rootSet = request.get('rootSet'); 78 if (!rootSetIds.contains(rootSet)) 79 rootSetIds.push(rootSet); 80 }); 81 return rootSetIds; 82 }.property('buildRequests'), 89 rootSets: DS.hasMany('rootSets'), 83 90 _fetchChartData: function () 84 91 { … … 145 152 return this.get('order') + 1; 146 153 }.property('order'), 147 rootSet: DS.attr('number'), 148 configLetter: function () 149 { 150 var rootSets = this.get('testGroup').get('rootSets'); 151 var index = rootSets.indexOf(this.get('rootSet')); 152 return String.fromCharCode('A'.charCodeAt(0) + index); 153 }.property('testGroup', 'testGroup.rootSets'), 154 rootSet: DS.belongsTo('rootSet'), 154 155 status: DS.attr('string'), 155 156 statusLabel: function () … … 165 166 return 'Failed'; 166 167 case 'completed': 167 return ' Finished';168 return 'Completed'; 168 169 } 169 170 }.property('status'), 170 171 url: DS.attr('string'), 171 172 build: DS.attr('number'), 172 _fetchMean: function () 173 { 174 var testGroup = this.get('testGroup'); 175 if (!testGroup) 176 return; 177 var chartData = testGroup.get('chartData'); 178 if (!chartData) 179 return; 173 }); 180 174 181 var point = chartData.current.findPointByBuild(this.get('build')); 182 if (!point) 183 return; 184 this.set('mean', chartData.formatter(point.value) + (chartData.unit ? ' ' + chartData.unit : '')); 185 this.set('buildNumber', point.measurement.buildNumber()); 186 }.observes('build', 'testGroup', 'testGroup.chartData').on('init'), 187 }); 175 App.BuildRequest.aggregateStatuses = function (requests) 176 { 177 var completeCount = 0; 178 var failureCount = 0; 179 requests.forEach(function (request) { 180 switch (request.get('status')) { 181 case 'failed': 182 failureCount++; 183 break; 184 case 'completed': 185 completeCount++; 186 break; 187 } 188 }); 189 if (completeCount == requests.length) 190 return 'Done'; 191 if (failureCount == requests.length) 192 return 'All failed'; 193 var status = completeCount + ' out of ' + requests.length + ' completed'; 194 if (failureCount) 195 status += ', ' + failureCount + ' failed'; 196 return status; 197 } -
trunk/Websites/perf.webkit.org/public/v2/app.css
r179878 r180333 452 452 } 453 453 454 .analysis-group .results .summary td { 455 vertical-align: top; 456 } 457 458 .analysis-group .results thead td { 459 text-align: center; 460 } 461 462 .analysis-group .results .config-letter, 463 .analysis-group .results .summary { 464 cursor: pointer; 465 } 466 467 .analysis-group .results .request .config-letter { 468 border-color: transparent; 469 } 470 471 .analysis-group .results .hideRequests .request { 472 display: none; 473 } 474 475 .box-plot { 476 display: inline-block; 477 width: 100px; 478 height: 0.6rem; 479 border: solid 1px #ddd; 480 padding: 1px; 481 vertical-align: middle; 482 } 483 484 .box-plot .percentage { 485 fill: #ccc; 486 } 487 488 .box-plot .delta { 489 fill: #333; 490 opacity: 0.5; 491 } 492 493 .box-plot svg { 494 display: block; 495 } 496 454 497 #analysis-task-title { 455 498 font-weight: normal; … … 471 514 border-radius: 0.5rem; 472 515 box-shadow: rgba(0, 0, 0, 0.03) 1px 1px 0px 0px; 473 474 padding: 0.5rem 1rem;475 516 margin-bottom: 1.5rem; 476 517 } 477 518 478 .analysis-group caption { 519 .analysis-group > * { 520 margin: 0.5rem; 521 } 522 523 .analysis-group > h1 { 479 524 font-size: 1.1rem; 525 font-weight: normal; 480 526 text-align: left; 481 527 margin-bottom: 0.5rem; 528 border-bottom: 1px solid #bbb; 529 margin: 0; 530 padding: 0.2rem 0.5rem; 531 } 532 533 .analysis-group h1 > input { 534 font-size: 1rem; 535 min-width: 20rem; 536 margin: 0.2rem 0; 482 537 } 483 538 -
trunk/Websites/perf.webkit.org/public/v2/app.js
r180000 r180333 298 298 metric: null, 299 299 selectedItem: null, 300 selectedPoints: null, 301 hoveredOrSelectedItem: null, 300 302 showFullYAxis: false, 301 303 searchCommit: function (repository, keyword) { … … 539 541 this.set(configName, config); 540 542 }, 543 _updateDetails: function () 544 { 545 var selectedPoints = this.get('selectedPoints'); 546 var currentPoint = this.get('hoveredOrSelectedItem'); 547 if (!selectedPoints && !currentPoint) { 548 this.set('details', null); 549 return; 550 } 551 552 var currentMeasurement; 553 var previousPoint; 554 if (!selectedPoints) 555 previousPoint = currentPoint.series.previousPoint(currentPoint); 556 else { 557 currentPoint = selectedPoints[selectedPoints.length - 1]; 558 previousPoint = selectedPoints[0]; 559 } 560 var currentMeasurement = currentPoint.measurement; 561 var oldMeasurement = previousPoint ? previousPoint.measurement : null; 562 563 var formattedRevisions = currentMeasurement.formattedRevisions(oldMeasurement); 564 var revisions = App.Manifest.get('repositories') 565 .filter(function (repository) { return formattedRevisions[repository.get('id')]; }) 566 .map(function (repository) { 567 var revision = Ember.Object.create(formattedRevisions[repository.get('id')]); 568 revision['url'] = revision.previousRevision 569 ? repository.urlForRevisionRange(revision.previousRevision, revision.currentRevision) 570 : repository.urlForRevision(revision.currentRevision); 571 revision['name'] = repository.get('name'); 572 revision['repository'] = repository; 573 return revision; 574 }); 575 576 var buildNumber = null; 577 var buildURL = null; 578 if (!selectedPoints) { 579 buildNumber = currentMeasurement.buildNumber(); 580 var builder = App.Manifest.builder(currentMeasurement.builderId()); 581 if (builder) 582 buildURL = builder.urlFromBuildNumber(buildNumber); 583 } 584 585 this.set('details', Ember.Object.create({ 586 status: this.computeStatus(currentPoint, previousPoint), 587 buildNumber: buildNumber, 588 buildURL: buildURL, 589 buildTime: currentMeasurement.formattedBuildTime(), 590 revisions: revisions, 591 })); 592 }.observes('hoveredOrSelectedItem', 'selectedPoints'), 541 593 }); 542 594 … … 901 953 this.set('overviewSelection', newSelection); 902 954 }.observes('parentController.sharedZoom').on('init'), 903 _updateDetails: function ()904 {905 var selectedPoints = this.get('selectedPoints');906 var currentPoint = this.get('currentItem');907 if (!selectedPoints && !currentPoint) {908 this.set('details', null);909 return;910 }911 912 var currentMeasurement;913 var previousPoint;914 if (!selectedPoints)915 previousPoint = currentPoint.series.previousPoint(currentPoint);916 else {917 currentPoint = selectedPoints[selectedPoints.length - 1];918 previousPoint = selectedPoints[0];919 }920 var currentMeasurement = currentPoint.measurement;921 var oldMeasurement = previousPoint ? previousPoint.measurement : null;922 923 var formattedRevisions = currentMeasurement.formattedRevisions(oldMeasurement);924 var revisions = App.Manifest.get('repositories')925 .filter(function (repository) { return formattedRevisions[repository.get('id')]; })926 .map(function (repository) {927 var revision = Ember.Object.create(formattedRevisions[repository.get('id')]);928 revision['url'] = revision.previousRevision929 ? repository.urlForRevisionRange(revision.previousRevision, revision.currentRevision)930 : repository.urlForRevision(revision.currentRevision);931 revision['name'] = repository.get('name');932 revision['repository'] = repository;933 return revision;934 });935 936 var buildNumber = null;937 var buildURL = null;938 if (!selectedPoints) {939 buildNumber = currentMeasurement.buildNumber();940 var builder = App.Manifest.builder(currentMeasurement.builderId());941 if (builder)942 buildURL = builder.urlFromBuildNumber(buildNumber);943 }944 945 this.set('details', Ember.Object.create({946 status: this.get('model').computeStatus(currentPoint, previousPoint),947 buildNumber: buildNumber,948 buildURL: buildURL,949 buildTime: currentMeasurement.formattedBuildTime(),950 revisions: revisions,951 }));952 this._updateCanAnalyze();953 }.observes('currentItem', 'selectedPoints'),954 955 _updateCanAnalyze: function () 955 956 { 956 var points = this.get(' selectedPoints');957 var points = this.get('model').get('selectedPoints'); 957 958 this.set('cannotAnalyze', !this.get('newAnalysisTaskName') || !points || points.length < 2); 958 }.observes('newAnalysisTaskName'), 959 }); 960 959 }.observes('newAnalysisTaskName', 'model.selectedPoints'), 960 }); 961 961 962 962 App.AnalysisRoute = Ember.Route.extend({ … … 979 979 platform: Ember.computed.alias('model.platform'), 980 980 metric: Ember.computed.alias('model.metric'), 981 testGroups: Ember.computed.alias('model.testGroups'),981 details: Ember.computed.alias('pane.details'), 982 982 testSets: [], 983 983 roots: [], … … 990 990 return; 991 991 992 var platformId = model.get('platform').get('id');993 var metricId = model.get('metric').get('id');994 992 App.Manifest.fetch(this.store).then(this._fetchedManifest.bind(this)); 995 App.Manifest.fetchRunsWithPlatformAndMetric(this.store, platformId, metricId).then(this._fetchedRuns.bind(this)); 993 this.set('pane', App.Pane.create({ 994 store: this.store, 995 platformId: model.get('platform').get('id'), 996 metricId: model.get('metric').get('id'), 997 })); 996 998 }.observes('model').on('init'), 997 999 _fetchedManifest: function () … … 1011 1013 })); 1012 1014 }, 1013 _fetchedRuns: function (result) 1014 { 1015 var chartData = result.data; 1015 paneDomain: function () 1016 { 1017 var pane = this.get('pane'); 1018 if (!pane) 1019 return; 1020 1021 var chartData = pane.get('chartData'); 1022 if (!chartData) 1023 return null; 1024 1016 1025 var currentTimeSeries = chartData.current; 1017 1026 if (!currentTimeSeries) 1018 return ; // FIXME: Report an error.1027 return null; // FIXME: Report an error. 1019 1028 1020 1029 var start = currentTimeSeries.findPointByMeasurementId(this.get('model').get('startRun')); 1021 1030 var end = currentTimeSeries.findPointByMeasurementId(this.get('model').get('endRun')); 1022 1031 if (!start || !end) 1023 return ; // FIXME: Report an error.1032 return null; // FIXME: Report an error. 1024 1033 1025 1034 var highlightedItems = {}; … … 1032 1041 measurement: point.measurement, 1033 1042 label: 'Point ' + (index + 1), 1034 value: chartData.format ter(point.value) + (chartData.unit ? ' ' + chartData.unit : ''),1043 value: chartData.formatWithUnit(point.value), 1035 1044 }; 1036 1045 }); 1037 1046 1038 1047 var margin = (end.time - start.time) * 0.1; 1039 this.set('chartData', chartData);1040 this.set('chartDomain', [start.time - margin, +end.time + margin]);1041 1048 this.set('highlightedItems', highlightedItems); 1042 1049 this.set('analysisPoints', formatedPoints); 1043 }, 1050 1051 return [start.time - margin, +end.time + margin]; 1052 }.property('pane.chartData', 'model', 'model'), 1044 1053 testSets: function () 1045 1054 { … … 1116 1125 }); 1117 1126 }.observes('analysisPoints'), 1127 updateTestGroupPanes: function () 1128 { 1129 var model = this.get('model'); 1130 if (!model) 1131 return; 1132 var self = this; 1133 model.get('testGroups').then(function (groups) { 1134 self.set('testGroupPanes', groups.map(function (group) { return App.TestGroupPane.create({content: group}); })); 1135 }); 1136 }.observes('model'), 1118 1137 actions: { 1119 1138 associateBug: function (bugTracker, bugNumber) … … 1137 1156 }); 1138 1157 }, 1139 }, 1140 }); 1158 toggleShowRequestList: function (configuration) 1159 { 1160 configuration.toggleProperty('showRequestList'); 1161 } 1162 }, 1163 }); 1164 1165 App.TestGroupPane = Ember.ObjectProxy.extend({ 1166 _populate: function () 1167 { 1168 var buildRequests = this.get('buildRequests'); 1169 var chartData = this.get('chartData'); 1170 if (!buildRequests || !chartData) 1171 return []; 1172 1173 var repositories = this._computeRepositoryList(); 1174 this.set('repositories', repositories); 1175 1176 var requestsByRooSet = this._groupRequestsByConfigurations(buildRequests); 1177 1178 var configurations = []; 1179 var index = 0; 1180 var range = {min: Infinity, max: -Infinity}; 1181 for (var rootSetId in requestsByRooSet) { 1182 var configLetter = String.fromCharCode('A'.charCodeAt(0) + index++); 1183 configurations.push(this._createConfigurationSummary(requestsByRooSet[rootSetId], configLetter, range)); 1184 } 1185 1186 var margin = 0.1 * (range.max - range.min); 1187 range.max += margin; 1188 range.min -= margin; 1189 1190 this.set('configurations', configurations); 1191 }.observes('chartData', 'buildRequests'), 1192 _computeRepositoryList: function () 1193 { 1194 var specifiedRepositories = new Ember.Set(); 1195 (this.get('rootSets') || []).forEach(function (rootSet) { 1196 (rootSet.get('roots') || []).forEach(function (root) { 1197 specifiedRepositories.add(root.get('repository')); 1198 }); 1199 }); 1200 var reportedRepositories = new Ember.Set(); 1201 var chartData = this.get('chartData'); 1202 (this.get('buildRequests') || []).forEach(function (request) { 1203 var point = chartData.current.findPointByBuild(request.get('build')); 1204 if (!point) 1205 return; 1206 1207 var revisionByRepositoryId = point.measurement.formattedRevisions(); 1208 for (var repositoryId in revisionByRepositoryId) { 1209 var repository = App.Manifest.repository(repositoryId); 1210 if (!specifiedRepositories.contains(repository)) 1211 reportedRepositories.add(repository); 1212 } 1213 }); 1214 return specifiedRepositories.sortBy('name').concat(reportedRepositories.sortBy('name')); 1215 }, 1216 _groupRequestsByConfigurations: function (requests, repositoryList) 1217 { 1218 var rootSetIdToRequests = {}; 1219 var testGroup = this; 1220 requests.forEach(function (request) { 1221 var rootSetId = request.get('rootSet').get('id'); 1222 if (!rootSetIdToRequests[rootSetId]) 1223 rootSetIdToRequests[rootSetId] = []; 1224 rootSetIdToRequests[rootSetId].push(request); 1225 }); 1226 return rootSetIdToRequests; 1227 }, 1228 _createConfigurationSummary: function (buildRequests, configLetter, range) 1229 { 1230 var repositories = this.get('repositories'); 1231 var chartData = this.get('chartData'); 1232 var requests = buildRequests.map(function (originalRequest) { 1233 var point = chartData.current.findPointByBuild(originalRequest.get('build')); 1234 var revisionByRepositoryId = point ? point.measurement.formattedRevisions() : {}; 1235 return Ember.ObjectProxy.create({ 1236 content: originalRequest, 1237 revisions: repositories.map(function (repository, index) { 1238 return (revisionByRepositoryId[repository.get('id')] || {label:null}).label; 1239 }), 1240 value: point ? point.value : null, 1241 valueRange: range, 1242 formattedValue: point ? chartData.formatWithUnit(point.value) : null, 1243 buildNumber: point ? point.measurement.buildNumber() : null, 1244 }); 1245 }); 1246 1247 var rootSet = requests ? requests[0].get('rootSet') : null; 1248 var summaryRevisions = repositories.map(function (repository, index) { 1249 var revision = rootSet ? rootSet.revisionForRepository(repository) : null; 1250 if (!revision) 1251 return requests[0].get('revisions')[index]; 1252 return Measurement.formatRevisionRange(revision).label; 1253 }); 1254 1255 requests.forEach(function (request) { 1256 var revisions = request.get('revisions'); 1257 repositories.forEach(function (repository, index) { 1258 if (revisions[index] == summaryRevisions[index]) 1259 revisions[index] = null; 1260 }); 1261 }); 1262 1263 var valuesInConfig = requests.mapBy('value').filter(function (value) { return typeof(value) === 'number' && !isNaN(value); }); 1264 var sum = Statistics.sum(valuesInConfig); 1265 var ciDelta = Statistics.confidenceIntervalDelta(0.95, valuesInConfig.length, sum, Statistics.squareSum(valuesInConfig)); 1266 var mean = sum / valuesInConfig.length; 1267 1268 range.min = Math.min(range.min, Statistics.min(valuesInConfig)); 1269 range.max = Math.max(range.max, Statistics.max(valuesInConfig)); 1270 if (ciDelta && !isNaN(ciDelta)) { 1271 range.min = Math.min(range.min, mean - ciDelta); 1272 range.max = Math.max(range.max, mean + ciDelta); 1273 } 1274 1275 var summary = Ember.Object.create({ 1276 isAverage: true, 1277 configLetter: configLetter, 1278 revisions: summaryRevisions, 1279 formattedValue: isNaN(mean) ? null : chartData.formatWithDeltaAndUnit(mean, ciDelta), 1280 value: mean, 1281 confidenceIntervalDelta: ciDelta, 1282 valueRange: range, 1283 statusLabel: App.BuildRequest.aggregateStatuses(requests), 1284 }); 1285 1286 return Ember.Object.create({summary: summary, items: requests}); 1287 }, 1288 }); 1289 1290 App.BoxPlotComponent = Ember.Component.extend({ 1291 classNames: ['box-plot'], 1292 range: null, 1293 value: null, 1294 delta: null, 1295 didInsertElement: function () 1296 { 1297 var element = this.get('element'); 1298 var svg = d3.select(element).append('svg') 1299 .attr('viewBox', '0 0 100 20') 1300 .attr('preserveAspectRatio', 'none') 1301 .style({width: '100%', height: '100%'}); 1302 1303 this._percentageRect = svg 1304 .append('rect') 1305 .attr('x', 0) 1306 .attr('y', 0) 1307 .attr('width', 0) 1308 .attr('height', 20) 1309 .attr('class', 'percentage'); 1310 1311 this._deltaRect = svg 1312 .append('rect') 1313 .attr('x', 0) 1314 .attr('y', 5) 1315 .attr('width', 0) 1316 .attr('height', 10) 1317 .attr('class', 'delta') 1318 .attr('opacity', 0.5) 1319 this._updateBars(); 1320 }, 1321 _updateBars: function () 1322 { 1323 if (!this._percentageRect || typeof(this._percentage) !== 'number' || isNaN(this._percentage)) 1324 return; 1325 1326 this._percentageRect.attr('width', this._percentage); 1327 if (typeof(this._delta) === 'number' && !isNaN(this._delta)) { 1328 this._deltaRect.attr('x', this._percentage - this._delta); 1329 this._deltaRect.attr('width', this._delta * 2); 1330 } 1331 }, 1332 valueChanged: function () 1333 { 1334 var range = this.get('range'); 1335 var value = this.get('value'); 1336 if (!range || !value) 1337 return; 1338 var scalingFactor = 100 / (range.max - range.min); 1339 var percentage = (value - range.min) * scalingFactor; 1340 this._percentage = percentage; 1341 this._delta = this.get('delta') * scalingFactor; 1342 this._updateBars(); 1343 }.observes('value', 'range').on('init'), 1344 }); -
trunk/Websites/perf.webkit.org/public/v2/chart-pane.css
r179913 r180333 218 218 } 219 219 220 .analysis-chart-pane { 221 height: 15rem; 222 } 223 220 224 .analysis-chart-pane .details { 221 225 overflow: scroll; … … 237 241 height: 13rem; 238 242 overflow: scroll; 243 } 244 .analysis-chart-pane .details-table-container { 245 position: static; 246 height: 15rem; 239 247 } 240 248 -
trunk/Websites/perf.webkit.org/public/v2/data.js
r180000 r180333 170 170 var currentRevision = revisions[repositoryId][0]; 171 171 var previousRevision = previousRevisions ? previousRevisions[repositoryId][0] : null; 172 var formatttedRevision = this._formatRevisionRange(previousRevision, currentRevision);172 var formatttedRevision = Measurement.formatRevisionRange(currentRevision, previousRevision); 173 173 formattedRevisions[repositoryId] = formatttedRevision; 174 174 } … … 177 177 } 178 178 179 Measurement. prototype._formatRevisionRange = function (previousRevision, currentRevision)179 Measurement.formatRevisionRange = function (currentRevision, previousRevision) 180 180 { 181 181 var revisionChanged = false; -
trunk/Websites/perf.webkit.org/public/v2/index.html
r180000 r180333 170 170 interactive=true 171 171 chartPointRadius=2 172 currentItem= currentItem172 currentItem=hoveredOrSelectedItem 173 173 currentTime=sharedTime 174 174 selectedItem=selectedItem … … 177 177 selection=timeRange 178 178 selectedPoints=selectedPoints 179 markedPoints=markedPoints180 179 showFullYAxis=showFullYAxis 180 zoomable=true 181 181 zoom="zoomed"}} 182 182 {{else}} … … 198 198 {{/if}} 199 199 </div> 200 {{#if details}} 201 {{partial "chart-details"}} 202 {{/if}} 200 {{partial "chart-details"}} 203 201 </div> 204 202 </div> … … 249 247 250 248 <script type="text/x-handlebars" data-template-name="chart-details"> 249 {{#if details}} 251 250 <div class="details-table-container"> 252 251 <table class="details-table"> … … 314 313 </div> 315 314 </div> 315 {{/if}} 316 316 </script> 317 317 … … 530 530 {{/if}} 531 531 532 {{#if chartData}}533 <section class="analysis-chart-pane chart-pane" >532 {{#if pane}} 533 <section class="analysis-chart-pane chart-pane" tabindex="0"> 534 534 <div class="svg-container"> 535 535 {{interactive-chart 536 chartData=chartData 537 enableSelection=false 536 chartData=pane.chartData 537 ranges=pane.analyticRanges 538 domain=paneDomain 539 interactive=true 538 540 chartPointRadius=2 539 domain=chartDomain 540 highlightedItems=highlightedItems}} 541 currentItem=pane.hoveredOrSelectedItem 542 selectedPoints=pane.selectedPoints 543 selection=timeRange 544 highlightedItems=highlightedItems 545 rangeRoute="analysisTask"}} 541 546 </div> 542 547 <div class="details"> 543 <table class="analysis-bugs"> 544 <tbody> 545 {{#each bugTrackers}} 546 <tr> 547 <th>{{label}}</th> 548 <td> 549 <form {{action "associateBug" this editedBugNumber on="submit"}}> 550 {{input type=text value=editedBugNumber}} 551 </form> 552 </td> 553 </tr> 554 {{/each}} 555 </tbody> 556 </table> 557 <table> 558 <tbody> 559 {{#each analysisPoints}} 560 <tr><td>{{label}}</td><td>{{value}}</td></tr> 561 {{/each}} 562 </tbody> 563 </table> 548 {{partial "chart-details"}} 564 549 </div> 565 550 </section> 566 {{#each testGroups}} 567 <section class="analysis-group"> 568 <table> 569 <caption>{{name}}</caption> 570 <thead> 571 <tr> 572 <td>Order</td> 573 <td>Configuration</td> 574 <td>Status</td> 575 <td>Build</td> 576 <td>{{../metric.fullName}}</td> 551 {{/if}} 552 553 {{partial "testGroupForm"}} 554 555 {{#each testGroupPanes}} 556 {{partial "testGroup"}} 557 {{/each}} 558 </script> 559 560 <script type="text/x-handlebars" data-template-name="testGroup"> 561 <section class="analysis-group"> 562 <h1>{{name}}</h1> 563 <table class="results"> 564 <thead> 565 <tr> 566 <td colspan="2">Configuration</td> 567 {{#each repositories}} 568 <td>{{name}}</td> 569 {{/each}} 570 <td>Results</td> 571 <td>Status</td> 572 </tr> 573 </thead> 574 {{#each configurations}} 575 <tbody {{bind-attr class="showRequestList::hideRequests"}}> 576 <tr class="summary" {{action toggleShowRequestList this}}> 577 <td class="config-letter" colspan="2">{{summary.configLetter}}</td> 578 {{#with summary}} 579 {{partial "testGroupRow"}} 580 {{/with}} 581 </tr> 582 {{#each items}} 583 <tr class="request"> 584 {{#with ../this}} 585 <td class="config-letter" {{action toggleShowRequestList this}}></td> 586 {{/with}} 587 <td>Run {{orderLabel}}</td> 588 {{partial "testGroupRow"}} 577 589 </tr> 578 </thead> 579 <tbody> 580 {{#each buildRequests}} 581 <tr> 582 <td>{{orderLabel}}</td> 583 <td>{{configLetter}}</td> 584 <td><a {{bind-attr href=url}}>{{statusLabel}}</a></td> 585 <td>{{buildNumber}}</td> 586 <td>{{mean}}</td> 587 </tr> 588 {{/each}} 589 </tbody> 590 </table> 591 </section> 592 {{/each}} 593 594 {{#if roots}} 595 <form method="POST" {{action "createTestGroup" newTestGroupName repetitionCount on="submit"}} class="analysis-group"> 590 {{/each}} 591 </tbody> 592 {{/each}} 593 </table> 594 </section> 595 </script> 596 597 <script type="text/x-handlebars" data-template-name="testGroupRow"> 598 {{#each revisions}} 599 <td>{{this}}</td> 600 {{/each}} 601 <td> 602 {{#if value}} 603 {{box-plot range=valueRange value=value delta=confidenceIntervalDelta}} 604 {{/if}} 605 {{formattedValue}} 606 </td> 607 <td> 608 {{#if buildNumber}} 609 {{statusLabel}} / <a {{bind-attr href=url}}>Build {{buildNumber}}</a> 610 {{else}} 611 <a {{bind-attr href=url}}>{{statusLabel}}</a> 612 {{/if}} 613 </td> 614 </script> 615 616 <script type="text/x-handlebars" data-template-name="testGroupForm"> 617 {{#if roots}} 618 <form method="POST" {{action "createTestGroup" newTestGroupName repetitionCount on="submit"}}> 619 <section class="analysis-group"> 620 <h1>{{input name="name" value=newTestGroupName placeholder="Test group name" required=true type="text"}}</h1> 596 621 <table> 597 <caption>{{input name="name" value=newTestGroupName placeholder="Test group name" required=true type="text"}}</caption>598 622 <thead> 599 623 <tr> … … 634 658 635 659 <button type="submit">Start A/B testing</button> 636 </ form>637 {{/if}}638 660 </section> 661 </form> 662 {{/if}} 639 663 </script> 640 664 -
trunk/Websites/perf.webkit.org/public/v2/interactive-chart.js
r179989 r180333 198 198 { 199 199 var xDomain = this.get('domain'); 200 if (!xDomain || !this._currentTimeSeriesData) 201 return null; 200 202 var intrinsicXDomain = this._computeXAxisDomain(this._currentTimeSeriesData); 201 203 if (!xDomain) … … 374 376 var selection = this._currentSelection() || this.get('sharedSelection'); 375 377 var newXDomain = this._updateDomain(); 378 if (!newXDomain) 379 return; 376 380 377 381 if (selection && newXDomain && selection[0] <= newXDomain[0] && newXDomain[1] <= selection[1]) … … 754 758 _updateSelectionToolbar: function () 755 759 { 756 if (!this.get(' interactive'))760 if (!this.get('zoomable')) 757 761 return; 758 762 -
trunk/Websites/perf.webkit.org/public/v2/js/statistics.js
r179913 r180333 1 1 var Statistics = new (function () { 2 3 this.min = function (values) { 4 return Math.min.apply(Math, values); 5 } 6 7 this.max = function (values) { 8 return Math.max.apply(Math, values); 9 } 2 10 3 11 this.sum = function (values) { -
trunk/Websites/perf.webkit.org/public/v2/manifest.js
r180120 r180333 303 303 304 304 var useSI = unit == 'bytes'; 305 var unitSuffix = unit ? ' ' + unit : ''; 306 var deltaFormatterWithoutSign = useSI ? d3.format('.2s') : d3.format('.2g'); 305 307 return { 306 308 platform: platform, … … 311 313 target: runs.target ? runs.target.timeSeriesByCommitTime() : null, 312 314 unit: unit, 315 formatWithUnit: function (value) { return this.formatter(value) + unitSuffix; }, 316 formatWithDeltaAndUnit: function (value, delta) 317 { 318 return this.formatter(value) + (delta && !isNaN(delta) ? ' \u00b1 ' + deltaFormatterWithoutSign(delta) : '') + unitSuffix; 319 }, 313 320 formatter: useSI ? d3.format('.4s') : d3.format('.4g'), 314 321 deltaFormatter: useSI ? d3.format('+.2s') : d3.format('+.2g'),
Note: See TracChangeset
for help on using the changeset viewer.