Changeset 230295 in webkit
- Timestamp:
- Apr 4, 2018 9:49:21 PM (6 years ago)
- Location:
- trunk/Websites/perf.webkit.org
- Files:
-
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Websites/perf.webkit.org/ChangeLog
r230151 r230295 1 2018-03-29 Dewei Zhu <dewei_zhu@apple.com> 2 3 Added UI to show potential regressions in chart with t-testing against segmentations. 4 https://bugs.webkit.org/show_bug.cgi?id=184131 5 6 Reviewed by Ryosuke Niwa. 7 8 Added UI in the chart-pane so that user can use new option in trendline which not only 9 shows the segmentation, but also t-test against potential changes indicated by segmentation. 10 11 Fix a bug in AnalysisTaskPage that chart is not updated when change type of task changes. 12 13 * browser-tests/interactive-time-series-chart-tests.js: Fix a unit tests. 14 * browser-tests/time-series-chart-tests.js: Fix a unit tests. 15 * public/shared/statistics.js: Added a function to t-test certain range based on segmentation results. 16 (Statistics.supportedOneSideTTestProbabilities): 17 (Statistics.findRangesForChangeDetectionsWithWelchsTTest): The argument `segmentations`, every 2 items in the list defines 18 segmentation, that is why the index incremental is 2 in this funcion. 19 * public/v3/components/chart-pane-base.js: Will select the range if user clicks on a suggested annotation. 20 (ChartPaneBase.prototype.configure): 21 (ChartPaneBase.prototype._didClickAnnotation): 22 * public/v3/components/chart-styles.js: 23 (ChartStyles.annotationFillStyleForTask): Added 'annotationFillStyleForTask' to determine the fillStyle for annotation based on change type of a analysis task. 24 * public/v3/components/interactive-time-series-chart.js: 25 (InteractiveTimeSeriesChart.prototype._findAnnotation): Also need to search among suggested annotaions. 26 * public/v3/components/time-series-chart.js: Introduced 'suggested annotaion' which does not have an existing task and is suggested by t-test based on segmentation. 27 (TimeSeriesChart): 28 (TimeSeriesChart.prototype.setSuggestedAnnotations): 29 (TimeSeriesChart.prototype.allAnnotations): Returns both annotations with and without analysis task. 30 (TimeSeriesChart.prototype._layoutAnnotationBars): Should take all annotations in the calculation. 31 * public/v3/models/measurement-set.js: 32 (MeasurementSet.prototype.metricId): Returns metric id. 33 * public/v3/models/metric.js: 34 (Metric.prototype.summarizeForValues): Added helper function to summarize a given value 35 * public/v3/models/test-group.js: 36 (TestGroup.prototype.compareTestResults): Adapted to use 'Metric.summarizeForValues'. 37 * public/v3/pages/chart-pane.js: Added 'Segmentation with t-test analysis' to 'ChartTrendLineTypes'. 38 (ChartPane.prototype._renderTrendLinePopover): 39 (ChartPane.prototype.async._updateTrendLine): make it an async function. 40 * unit-tests/statistics-tests.js: Added unit tests for 'findRangesForChangeDetectionsWithWelchsTTest'. 41 1 42 2018-04-02 Aakash Jain <aakash_jain@apple.com> 2 43 -
trunk/Websites/perf.webkit.org/browser-tests/interactive-time-series-chart-tests.js
r213119 r230295 495 495 return ChartTest.importChartScripts(context).then(() => { 496 496 const chart = ChartTest.createInteractiveChartWithSampleCluster(context, null, 497 {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 10, barSpacing: 1 }});497 {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 10, barSpacing: 1, fillStyle: '#ccc'}}); 498 498 499 499 chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime); … … 506 506 endTime: ChartTest.sampleCluster.endTime - diff / 4, 507 507 label: 'hello, world', 508 fillStyle: 'rgb(0, 0, 255)', 509 }] 508 }]; 510 509 chart.setAnnotations(annotations); 511 510 -
trunk/Websites/perf.webkit.org/browser-tests/time-series-chart-tests.js
r213122 r230295 801 801 const context = new BrowsingContext(); 802 802 return ChartTest.importChartScripts(context).then(() => { 803 const options = {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 7, barSpacing: 2 }};803 const options = {annotations: { textStyle: '#000', textBackground: '#fff', minWidth: 3, barHeight: 7, barSpacing: 2, fillStyle: '#ccc'}}; 804 804 const chartWithoutAnnotations = ChartTest.createChartWithSampleCluster(context, null, options); 805 805 const chartWithAnnotations = ChartTest.createChartWithSampleCluster(context, null, options); … … 819 819 endTime: ChartTest.sampleCluster.startTime + diff / 2, 820 820 label: 'hello, world', 821 fillStyle: 'rgb(0, 0, 255)',822 821 }]); 823 822 -
trunk/Websites/perf.webkit.org/public/shared/statistics.js
r227182 r230295 38 38 supportedProbabilities.push(oneSidedToTwoSidedProbability(probability).toFixed(2)); 39 39 return supportedProbabilities 40 } 41 42 this.supportedOneSideTTestProbabilities = function () { 43 return Object.keys(tDistributionByOneSidedProbability); 40 44 } 41 45 … … 106 110 }; 107 111 } 112 113 this.findRangesForChangeDetectionsWithWelchsTTest = function (values, segmentations, oneSidedPossibility) { 114 if (!values.length) 115 return []; 116 117 const selectedRanges = []; 118 const twoSidedFromOneSidedPossibility = 2 * oneSidedPossibility - 1; 119 120 for (let i = 1; i + 2 < segmentations.length; i += 2) { 121 let found = false; 122 const previousMean = segmentations[i].value; 123 const currentMean = segmentations[i + 1].value; 124 console.assert(currentMean != previousMean); 125 const currentChangePoint = segmentations[i].seriesIndex; 126 const start = segmentations[i - 1].seriesIndex; 127 const end = segmentations[i + 2].seriesIndex; 128 129 for (let leftEdge = currentChangePoint - 2, rightEdge = currentChangePoint + 2; leftEdge >= start && rightEdge <= end; leftEdge--, rightEdge++) { 130 const result = this.computeWelchsT(values, leftEdge, currentChangePoint - leftEdge, values, currentChangePoint, rightEdge - currentChangePoint, twoSidedFromOneSidedPossibility); 131 if (result.significantlyDifferent) { 132 selectedRanges.push({ 133 startIndex: leftEdge, 134 endIndex: rightEdge - 1, 135 segmentationStartValue: previousMean, 136 segmentationEndValue: currentMean 137 }); 138 found = true; 139 break; 140 } 141 } 142 if (!found && Statistics.debuggingTestingRangeNomination) 143 console.log('Failed to find a testing range at', currentChangePoint, 'changing from', previousMean, 'to', currentMean); 144 } 145 146 return selectedRanges; 147 }; 108 148 109 149 function sampleMeanAndVarianceForValues(values, startIndex, length) { -
trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js
r219381 r230295 20 20 this._commitLogViewer = null; 21 21 this._tasksForAnnotations = null; 22 this._renderedAnnotations = false; 22 this._detectedAnnotations = null; 23 this._renderAnnotationsLazily = new LazilyEvaluatedFunction(this._renderAnnotations.bind(this)); 23 24 } 24 25 … … 52 53 this._mainChart.listenToAction('selectionChange', this._mainSelectionDidChange.bind(this)); 53 54 this._mainChart.listenToAction('zoom', this._mainSelectionDidZoom.bind(this)); 54 this._mainChart.listenToAction('annotationClick', this._ openAnalysisTask.bind(this));55 this._mainChart.listenToAction('annotationClick', this._didClickAnnotation.bind(this)); 55 56 this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart); 56 57 … … 98 99 AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId, noCache).then(function (tasks) { 99 100 self._tasksForAnnotations = tasks; 100 self._renderedAnnotations = false;101 101 self.enqueueToRender(); 102 102 }); … … 106 106 didUpdateAnnotations() 107 107 { 108 this._ renderedAnnotations = false;108 this._tasksForAnnotations = [...this._tasksForAnnotations]; 109 109 this.enqueueToRender(); 110 110 } … … 170 170 this._commitLogViewer.view(this._openRepository, range.from, range.to); 171 171 this.enqueueToRender(); 172 } 173 174 _didClickAnnotation(annotation) 175 { 176 if (annotation.task) 177 this._openAnalysisTask(annotation); 178 else { 179 const newSelection = [annotation.startTime, annotation.endTime]; 180 this._mainChart.setSelection(newSelection); 181 this._overviewChart.setSelection(newSelection, this); 182 this.enqueueToRender(); 183 } 172 184 } 173 185 … … 247 259 this._overviewChart.enqueueToRender(); 248 260 249 if (this._mainChart) 261 if (this._mainChart) { 250 262 this._mainChart.enqueueToRender(); 263 this._renderAnnotationsLazily.evaluate(this._tasksForAnnotations, this._detectedAnnotations); 264 } 251 265 252 266 if (this._errorMessage) { … … 255 269 } 256 270 257 this._renderAnnotations();258 271 259 272 if (this._mainChartStatus) … … 269 282 } 270 283 271 _renderAnnotations() 272 { 273 if (!this._tasksForAnnotations || this._renderedAnnotations) 274 return; 275 this._renderedAnnotations = true; 276 277 var annotations = this._tasksForAnnotations.map(function (task) { 278 var fillStyle = '#fc6'; 279 switch (task.changeType()) { 280 case 'inconclusive': 281 fillStyle = '#fcc'; 282 break; 283 case 'progression': 284 fillStyle = '#39f'; 285 break; 286 case 'regression': 287 fillStyle = '#c60'; 288 break; 289 case 'unchanged': 290 fillStyle = '#ccc'; 291 break; 292 } 293 284 _renderAnnotations(taskForAnnotations, detectedAnnotations) 285 { 286 let annotations = (taskForAnnotations || []).map((task) => { 294 287 return { 295 task: task, 288 task, 289 fillStyle: ChartStyles.annotationFillStyleForTask(task), 296 290 startTime: task.startTime(), 297 291 endTime: task.endTime(), 298 label: task.label(), 299 fillStyle: fillStyle, 292 label: task.label() 300 293 }; 301 294 }); 295 296 annotations = annotations.concat(detectedAnnotations || []); 297 302 298 this._mainChart.setAnnotations(annotations); 303 299 } -
trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js
r213300 r230295 153 153 minWidth: 3, 154 154 barHeight: 7, 155 barSpacing: 2 ,155 barSpacing: 2 156 156 }; 157 157 return options; 158 158 } 159 160 static annotationFillStyleForTask(task) { 161 if (!task) 162 return '#888'; 163 164 switch (task.changeType()) { 165 case 'inconclusive': 166 return '#fcc'; 167 case 'progression': 168 return '#39f'; 169 case 'regression': 170 return '#c60'; 171 case 'unchanged': 172 return '#ccc'; 173 } 174 return '#fc6'; 175 176 } 159 177 } -
trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js
r213300 r230295 342 342 return null; 343 343 344 for ( varitem of this._annotations) {344 for (const item of this._annotations) { 345 345 if (item.x <= cursorLocation.x && cursorLocation.x <= item.x + item.width 346 346 && item.y <= cursorLocation.y && cursorLocation.y <= item.y + item.height) -
trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js
r212520 r230295 23 23 24 24 platformId() { return this._platformId; } 25 metricId() { return this._metricId; } 25 26 26 27 static findSet(platformId, metricId, lastModified) … … 226 227 var addSegment = function (startingPoint, endingPoint) { 227 228 var value = Statistics.mean(timeSeries.valuesBetweenRange(startingPoint.seriesIndex, endingPoint.seriesIndex)); 228 segmentationSeries.push({value: value, time: startingPoint.time, interval: function () { return null; }});229 segmentationSeries.push({value: value, time: endingPoint.time, interval: function () { return null; }});229 segmentationSeries.push({value: value, time: startingPoint.time, seriesIndex: startingPoint.seriesIndex, interval: function () { return null; }}); 230 segmentationSeries.push({value: value, time: endingPoint.time, seriesIndex: endingPoint.seriesIndex, interval: function () { return null; }}); 230 231 }; 231 232 -
trunk/Websites/perf.webkit.org/public/v3/models/metric.js
r215633 r230295 86 86 } 87 87 88 labelForDifference(beforeValue, afterValue, progressionTypeName, regressionTypeName) 89 { 90 const diff = afterValue - beforeValue; 91 const changeType = diff < 0 == this.isSmallerBetter() ? progressionTypeName : regressionTypeName; 92 const relativeChange = diff / beforeValue * 100; 93 const changeLabel = Math.abs(relativeChange).toFixed(2) + '% ' + changeType; 94 return {changeType, relativeChange, changeLabel}; 95 } 96 88 97 makeFormatter(sigFig, alwaysShowSign) { return Metric.makeFormatter(this.unit(), sigFig, alwaysShowSign); } 89 98 -
trunk/Websites/perf.webkit.org/public/v3/models/test-group.js
r222219 r230295 145 145 146 146 if (beforeValues.length && afterValues.length) { 147 var diff = afterMean - beforeMean; 148 var smallerIsBetter = metric.isSmallerBetter(); 149 var changeType = diff < 0 == smallerIsBetter ? 'better' : 'worse'; 150 var changeLabel = Math.abs(diff / beforeMean * 100).toFixed(2) + '% ' + changeType; 147 const summary = metric.labelForDifference(beforeMean, afterMean, 'better', 'worse'); 148 result.changeType = summary.changeType; 149 result.changeLabel = summary.changeLabel; 151 150 var isSignificant = Statistics.testWelchsT(beforeValues, afterValues); 152 151 var significanceLabel = isSignificant ? 'significant' : 'insignificant'; 153 152 154 result.changeType = changeType;155 result.label = changeLabel;156 153 if (hasCompleted) 157 154 result.status = isSignificant ? result.changeType : 'unchanged'; -
trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js
r219321 r230295 752 752 753 753 const updateRendering = () => { 754 this. _chartPane.didUpdateAnnotations();754 this.part('chart-pane').didUpdateAnnotations(); 755 755 this.enqueueToRender(); 756 756 }; -
trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js
r215205 r230295 40 40 }, 41 41 { 42 id: 6, 43 label: 'Segmentation with Welch\'s t-test change detection', 44 execute: async function (source, parameters) { 45 const segmentation = await source.measurementSet.fetchSegmentation('segmentTimeSeriesByMaximizingSchwarzCriterion', parameters, 46 source.type, source.includeOutliers, source.extendToFuture); 47 if (!segmentation) 48 return segmentation; 49 50 const metric = Metric.findById(source.measurementSet.metricId()); 51 const timeSeries = source.measurementSet.fetchedTimeSeries(source.type, source.includeOutliers, source.extendToFuture); 52 segmentation.analysisAnnotations = Statistics.findRangesForChangeDetectionsWithWelchsTTest(timeSeries.values(), 53 segmentation, parameters[parameters.length - 1]).map((range) => { 54 const startPoint = timeSeries.findPointByIndex(range.startIndex); 55 const endPoint = timeSeries.findPointByIndex(range.endIndex); 56 const summary = metric.labelForDifference(range.segmentationStartValue, range.segmentationEndValue, 'progression', 'regression'); 57 return { 58 task: null, 59 fillStyle: ChartStyles.annotationFillStyleForTask(null), 60 startTime: startPoint.time, 61 endTime: endPoint.time, 62 label: `Potential ${summary.changeLabel}`, 63 }; 64 }); 65 return segmentation; 66 }, 67 parameterList: [ 68 {label: "Segment count weight", value: 2.5, min: 0.01, max: 10, step: 0.01}, 69 {label: "Grid size", value: 500, min: 100, max: 10000, step: 10}, 70 {label: "t-test significance", value: 0.99, options: Statistics.supportedOneSideTTestProbabilities()}, 71 ] 72 }, 73 { 42 74 id: 1, 43 75 label: 'Simple Moving Average', … … 418 450 element('h3', 'Parameters'), 419 451 element('ul', this._trendLineType.parameterList.map(function (parameter, index) { 452 if (parameter.options) { 453 const select = element('select', parameter.options.map((option) => 454 element('option', {value: option, selected: option == parameter.value}, option))); 455 select.onchange = self._trendLineParameterDidChange.bind(self); 456 select.parameterIndex = index; 457 return element('li', element('label', [parameter.label + ': ', select])); 458 } 459 420 460 var attributes = {type: 'number'}; 421 461 for (var name in parameter) 422 462 attributes[name] = parameter[name]; 463 423 464 attributes.value = configuredParameters[index]; 424 varinput = element('input', attributes);465 const input = element('input', attributes); 425 466 input.parameterIndex = index; 426 467 input.oninput = self._trendLineParameterDidChange.bind(self); … … 476 517 } 477 518 478 _updateTrendLine()519 async _updateTrendLine() 479 520 { 480 521 if (!this._mainChart.sourceList()) … … 485 526 var currentTrendLineParameters = this._trendLineParameters || this._defaultParametersForTrendLine(currentTrendLineType); 486 527 var currentTrendLineVersion = this._trendLineVersion; 487 var self = this;488 528 var sourceList = this._mainChart.sourceList(); 489 529 … … 493 533 } else { 494 534 // Wait for all trendlines to be ready. Otherwise we might see FOC when the domain is expanded. 495 Promise.all(sourceList.map(function (source, sourceIndex) { 496 return currentTrendLineType.execute.call(null, source, currentTrendLineParameters).then(function (trendlineSeries) { 497 if (self._trendLineVersion == currentTrendLineVersion) 498 self._mainChart.setTrendLine(sourceIndex, trendlineSeries); 499 }); 500 })).then(function () { 501 self.enqueueToRender(); 502 }); 535 await Promise.all(sourceList.map(async (source, sourceIndex) => { 536 const trendlineSeries = await currentTrendLineType.execute.call(null, source, currentTrendLineParameters); 537 if (this._trendLineVersion == currentTrendLineVersion) 538 this._mainChart.setTrendLine(sourceIndex, trendlineSeries); 539 540 if (trendlineSeries && trendlineSeries.analysisAnnotations) 541 this._detectedAnnotations = trendlineSeries.analysisAnnotations; 542 else 543 this._detectedAnnotations = null; 544 })); 545 this.enqueueToRender(); 503 546 } 504 547 } -
trunk/Websites/perf.webkit.org/unit-tests/statistics-tests.js
r227182 r230295 389 389 }); 390 390 }); 391 392 describe('findRangesForChangeDetectionsWithWelchsTTest', () => { 393 it('should return an empty array if the value is empty list', () => { 394 assert.deepEqual(Statistics.findRangesForChangeDetectionsWithWelchsTTest([], [], 0.975), []); 395 }); 396 397 it('should return an empty array if segmentation is empty list', () => { 398 assert.deepEqual(Statistics.findRangesForChangeDetectionsWithWelchsTTest([1,2,3], [], 0.975), []); 399 }); 400 401 it('should return the range if computeWelchsT shows a significant change', () => { 402 const values = [ 403 747.30337423744, 404 731.47392585276, 405 743.66763513161, 406 738.02055323487, 407 738.25426340842, 408 742.38680046471, 409 733.13921703284, 410 739.22069966147, 411 735.69295749633, 412 743.01705472504, 413 745.45778145306, 414 731.04841157169, 415 729.4372674973, 416 735.4497416527, 417 739.0230668644, 418 730.91782989909, 419 722.18725411279, 420 731.96223451728, 421 730.04119216192, 422 730.78087646284, 423 729.63155210365, 424 730.17585200878, 425 733.93766054706, 426 740.74920717197, 427 752.14718023647, 428 764.49990164847, 429 766.36100828473, 430 756.2291883252, 431 750.14522451097, 432 749.57595092266, 433 748.03624881866, 434 769.41522176386, 435 744.04660430456, 436 751.17927808265, 437 753.29996854062, 438 757.01813756936, 439 746.62413820741, 440 742.64420062736, 441 758.12726352772, 442 778.2278439089, 443 775.11818554541, 444 775.11818554541]; 445 const segmentation = [{ 446 seriesIndex: 0, 447 time: 1505176030671, 448 value: 736.5366704896555, 449 x: 370.4571789404566, 450 y: 185.52613334520248, 451 }, 452 { 453 seriesIndex: 18, 454 time: 1515074391534, 455 value: 736.5366704896555, 456 x: 919.4183852714947, 457 y: 185.52613334520248 458 }, 459 { 460 seriesIndex: 18, 461 time: 1515074391534, 462 value: 750.3483428383142, 463 x: 919.4183852714947, 464 y: 177.9710953409673, 465 }, 466 { 467 seriesIndex: 41, 468 time: 1553851695869, 469 value: 750.3483428383142, 470 x: 3070.000290764446, 471 y: 177.9710953409673, 472 }]; 473 assert.deepEqual(Statistics.findRangesForChangeDetectionsWithWelchsTTest(values, segmentation, 0.975), [ 474 { 475 "endIndex": 29, 476 "segmentationEndValue": 750.3483428383142, 477 "segmentationStartValue": 736.5366704896555, 478 "startIndex": 6 479 } 480 ]); 481 }) 482 }); 391 483 });
Note: See TracChangeset
for help on using the changeset viewer.