Diff
Modified: trunk/Websites/perf.webkit.org/ChangeLog (213299 => 213300)
--- trunk/Websites/perf.webkit.org/ChangeLog 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/ChangeLog 2017-03-02 21:23:07 UTC (rev 213300)
@@ -1,3 +1,154 @@
+2017-03-02 Ryosuke Niwa <rn...@webkit.org>
+
+ Make baseline data points selectable
+ https://bugs.webkit.org/show_bug.cgi?id=169069
+ <rdar://problem/29209427>
+
+ Reviewed by Antti Koivisto.
+
+ Add the capability to select data points other than "current" configuration type.
+
+ This patch refactors the way the "chart status" is computed. Before this patch, ChartStatusView was
+ responsible for determining two data points for which to compute the status, and computing the status
+ between two data points. ChartPaneStatusView which inherits from ChartStatusView and used in the charts
+ page relied upon ChartStatusView to compute these values, and computed the list of revision ranges for
+ each relevant repository between the data points. ChartPane then had callbacks on ChartPaneStatusView
+ to know whenever these values changed. Because of this entangled mess, ChartStatusView had to be aware
+ of InteractiveTimeSeriesChart even though only ChartPaneStatusView could be used with it.
+
+ This patch dramatically simplifies the situation by adding referencePoints() on TimeSeriesChart and
+ InteractiveTimeSeriesChart which returns the current point, the previous point if there is any, and
+ their time series view. It also extracts ChartStatusEvaluator which computes the current status values
+ and ChartRevisionRange which computes a list of revision differences both based on the referencePoints.
+ As a result, ChartPaneStatusView no longer inherits from ChartStatusView, and ChartStatusView has been
+ renamed to DashboardChartStatusView to reflect its purpose. Furthermore, ChartPane which used to rely on
+ ChartPaneStatusView's revisionCallback to update the commit log viewer simply uses another instance of
+ ChartRevisionRange, eliminating the need for the callback.
+
+ To implement these classes easily, this patch also introduces a new class, LazilyEvaluatedFunction to
+ memoize the return value of a function when called with the same arguments. Delaying the computation of
+ a value and avoiding the work when the values are the same is a very common pattern in the perf dashboard
+ so I expect this class would be used in a lot more places in the future.
+
+ * browser-tests/chart-revision-range-tests.js: Added. Tests for ChartRevisionRange.
+ * browser-tests/chart-status-evaluator-tests.js: Added. Tests for ChartStatusEvaluator.
+
+ * browser-tests/index.html:
+ (BrowsingContext):
+ (BrowsingContext.importScripts): Fixed the bug that calling importScripts twice results in MockRemoteAPI
+ being loaded twice.
+ (ChartTest.importChartScripts): Import more model objects.
+ (ChartTest.sampleCluster): Made this a getter.
+ (ChartTest.makeModelObjectsForSampleCluster):
+ (ChartTest.makeSampleCluster): Added. Cutomizes the valus of baseline / target based on options.
+ (ChartTest.respondWithSampleCluster): Now takes an options argument for makeSampleCluster.
+
+ * public/v3/components/chart-pane-base.js:
+ (ChartPaneBase): Added _openRepository to keep track of the currently open repository instead of relying
+ on _mainChartStatus or _commitLogViewer to keep track of it.
+ (ChartPaneBase.prototype.configure): The callback for when the user clicked on a repository name in
+ ChartPaneStatusView has been replaced by "openRepository" action.
+ (ChartPaneBase.prototype.setOpenRepository): Moved from ChartPane.
+ (ChartPaneBase.prototype._mainSelectionDidChange):
+ (ChartPaneBase.prototype._indicatorDidChange):
+ (ChartPaneBase.prototype._didFetchData):
+ (ChartPaneBase.prototype._updateCommitLogViewer): Renamed from _updateStatus.
+ (ChartPaneBase.prototype.openNewRepository): Renamed from _requestOpeningCommitViewer. Fixed a bug that
+ clicking on the repository name inside ChartPaneStatusView would not focus the pane, which resulted in
+ arrow keys to be ignored instead of moving the main chart's indicator or the currently open repository.
+ (ChartPaneBase.prototype._keyup):
+ (ChartPaneBase.prototype._moveOpenRepository): Moved from ChartPaneStatusView's
+ moveRepositoryWithNotification. Used when changing the open repository by up/down arrow keys.
+
+ * public/v3/components/chart-revision-range.js: Added. Extracted from ChartPaneStatusView.
+ (ChartRevisionRange): Added.
+ (ChartRevisionRange.prototype.revisionList): Added.
+ (ChartRevisionRange.prototype.rangeForRepository): Added.
+ (ChartRevisionRange._revisionForPoint): Added. Extracted from ChartPaneStatusView's
+ _updateRevisionListForNewCurrentRepository.
+ (ChartRevisionRange._computeRevisionList): Ditto from computeChartStatusLabels.
+
+ * public/v3/components/chart-status-evaluator.js: Added.
+ (ChartStatusEvaluator): Added.
+ (ChartStatusEvaluator.prototype.status): Added.
+ (ChartStatusEvaluator.computeChartStatus): Added. Extracted from ChartStatusView's updateStatusIfNeeded.
+
+ * public/v3/components/chart-status-view.js: Removed.
+ (ChartStatusView): Deleted. Split into ChartStatusEvaluator and DashboardChartStatusView.
+
+ * public/v3/components/chart-styles.js:
+ (ChartStyles.baselineStyle): Make baseline data points interactive. This single line change is what
+ enables the user to interact with the data points. The rest of changes in this patch mostly deals with
+ the status text such as "5% worse than baseline" and the list of revisions shown in the commit log viewer
+ which would have shown the wrong range without these changes.
+
+ * public/v3/components/dashboard-chart-status-view.js: Added. Extracted from ChartStatusView.
+ (DashboardChartStatusView): Added.
+ (DashboardChartStatusView.prototype.render): Added.
+ (DashboardChartStatusView.htmlTemplate): Added.
+ (DashboardChartStatusView.cssTemplate): Added.
+
+ * public/v3/components/interactive-time-series-chart.js:
+ (InteractiveTimeSeriesChart.prototype.referencePoints): Added. Return the first point and the last point
+ as the reference points when there is a selection. Only report the previous point if they are distinct as
+ showing a range of revisions from a data point to itself makes no sense. When there is a indicator simply
+ return it and its previous point as reference points. Otherwise return null unlike TimeSeriesChart's
+ referencePoints which always returns the latest point as the reference point.
+
+ * public/v3/components/time-series-chart.js:
+ (TimeSeriesChart.prototype.referencePoints): Added. Return the latest point as the reference point. It
+ never returns the previous point even if there were more data points as there is no way for the user to
+ specify which data points to compare.
+
+ * public/v3/index.html: Include newly added files.
+
+ * public/v3/lazily-evaluated-function.js: Added.
+ (LazilyEvaluatedFunction): Added.
+ (LazilyEvaluatedFunction.prototype.evaluate): Added.
+
+ * public/v3/models/commit-log.js:
+ (CommitLog.prototype.diff): Fixed a bug that computing the diff of two Subversion-like revisions results
+ in "from" field to be unexpectedly an integer instead of a string.
+
+ * public/v3/models/metric.js:
+ (Metric): Moved the code to compute the unit from the metric name from v2's RunsData class. This makes
+ writing tests easier since it eliminates the need to load v2's data.js.
+ (Metric.prototype.unit):
+ (Metric.prototype.isSmallerBetter): Ditto for determining whether the unit is smaller-is-better.
+
+ * public/v3/pages/analysis-task-page.js:
+ (AnalysisTaskChartPane.prototype._updateStatus): Deleted the unused code.
+
+ * public/v3/pages/chart-pane-status-view.js:
+ (ChartPaneStatusView): No longer inherits from ChartStatusView. Uses ChartStatusEvaluator and
+ ChartRevisionRange to to compute the chart status and the list of revision changes.
+ (ChartPaneStatusView.prototype.pointsRangeForAnalysis): Deleted.
+ (ChartPaneStatusView.prototype.render): Split it into _renderStatus and _renderBuildRevisionTable using
+ LazilyEvaluatedFunction.
+ (ChartPaneStatusView.prototype._renderStatus): Added.
+ (ChartPaneStatusView.prototype._renderBuildRevisionTable): Added.
+ (ChartPaneStatusView.prototype.setCurrentRepository): _updateRevisionListForNewCurrentRepository has been
+ moved into ChartRevisionRange. Just enqueue itself to re-render.
+ (ChartPaneStatusView.prototype._setRevisionRange): Deleted.
+ (ChartPaneStatusView.prototype.moveRepositoryWithNotification): Deleted.
+ (ChartPaneStatusView.prototype.updateRevisionList): Deleted.
+ (ChartPaneStatusView.prototype._updateRevisionListForNewCurrentRepository): Deleted.
+ (ChartPaneStatusView.prototype.computeChartStatusLabels): Deleted.
+ (ChartPaneStatusView.htmlTemplate):
+ (ChartPaneStatusView.cssTemplate):
+
+ * public/v3/pages/chart-pane.js:
+ (ChartPane.prototype.openNewRepository): Overrides the one in ChartPaneBase, which has been renamed from
+ _requestOpeningCommitViewer.
+ (ChartPane.prototype._analyzeRange):
+ (ChartPane.prototype._renderActionToolbar): Use the main chart's selection directly to determine whether
+ an analysis task can be created for the currenty selected range.
+
+ * public/v3/pages/dashboard-page.js:
+ (DashboardPage.prototype._createChartForCell):
+
+ * unit-tests/lazily-evaluated-function-tests.js: Added. Tests for LazilyEvaluatedFunction.
+
2017-03-01 Ryosuke Niwa <rn...@webkit.org>
Build fix after r212853. Make creating an analysis task work again.
Added: trunk/Websites/perf.webkit.org/browser-tests/chart-revision-range-tests.js (0 => 213300)
--- trunk/Websites/perf.webkit.org/browser-tests/chart-revision-range-tests.js (rev 0)
+++ trunk/Websites/perf.webkit.org/browser-tests/chart-revision-range-tests.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -0,0 +1,165 @@
+
+describe('ChartRevisionRange', () => {
+
+ function importRevisionList(context)
+ {
+ return ChartTest.importChartScripts(context).then(() => {
+ ChartTest.makeModelObjectsForSampleCluster(context);
+ return context.importScripts(['lazily-evaluated-function.js', 'components/chart-revision-range.js'], 'ChartRevisionRange');
+ });
+ }
+
+ describe('revisionList on a non-interactive chart', () => {
+ it('should report the list of revision for the latest point', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importRevisionList(context).then((ChartRevisionRange) => {
+ const chart = ChartTest.createChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ evaluator = new ChartRevisionRange(chart);
+ expect(evaluator.revisionList()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const revisionList = evaluator.revisionList();
+ expect(revisionList).to.not.be(null);
+ expect(revisionList.length).to.be(2);
+
+ expect(revisionList[0].repository.label()).to.be('SomeApp');
+ expect(revisionList[0].label).to.be('r4006');
+ expect(revisionList[0].from).to.be(null);
+ expect(revisionList[0].to).to.be('4006');
+
+ expect(revisionList[1].repository.label()).to.be('macOS');
+ expect(revisionList[1].label).to.be('15C50');
+ expect(revisionList[1].from).to.be(null);
+ expect(revisionList[1].to).to.be('15C50');
+ })
+ });
+ });
+
+
+ describe('revisionList on an interactive chart', () => {
+
+ it('should not report the list of revision for the latest point when there is no selection or indicator', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importRevisionList(context).then((ChartRevisionRange) => {
+ const chart = ChartTest.createInteractiveChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ evaluator = new ChartRevisionRange(chart);
+ expect(evaluator.revisionList()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ expect(evaluator.revisionList()).to.be(null);
+ })
+ });
+
+ it('should report the list of revision for the locked indicator with differences to the previous point', () => {
+ const context = new BrowsingContext();
+ let chart;
+ let evaluator;
+ return importRevisionList(context).then((ChartRevisionRange) => {
+ chart = ChartTest.createInteractiveChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ evaluator = new ChartRevisionRange(chart);
+ expect(evaluator.revisionList()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ expect(evaluator.revisionList()).to.be(null);
+
+ const currentView = chart.sampledTimeSeriesData('current');
+ chart.setIndicator(currentView.lastPoint().id, true);
+
+ let revisionList = evaluator.revisionList();
+ expect(revisionList).to.not.be(null);
+ expect(revisionList.length).to.be(2);
+
+ expect(revisionList[0].repository.label()).to.be('SomeApp');
+ expect(revisionList[0].label).to.be('r4005-r4006');
+ expect(revisionList[0].from).to.be('4005');
+ expect(revisionList[0].to).to.be('4006');
+
+ expect(revisionList[1].repository.label()).to.be('macOS');
+ expect(revisionList[1].label).to.be('15C50');
+ expect(revisionList[1].from).to.be(null);
+ expect(revisionList[1].to).to.be('15C50');
+
+ chart.setIndicator(1004, true); // Across macOS change.
+
+ revisionList = evaluator.revisionList();
+ expect(revisionList.length).to.be(2);
+
+ expect(revisionList[0].repository.label()).to.be('SomeApp');
+ expect(revisionList[0].label).to.be('r4004-r4004');
+ expect(revisionList[0].from).to.be('4004');
+ expect(revisionList[0].to).to.be('4004');
+
+ expect(revisionList[1].repository.label()).to.be('macOS');
+ expect(revisionList[1].label).to.be('15B42 - 15C50');
+ expect(revisionList[1].from).to.be('15B42');
+ expect(revisionList[1].to).to.be('15C50');
+ });
+ });
+
+ it('should report the list of revision for the selected range', () => {
+ const context = new BrowsingContext();
+ let chart;
+ let evaluator;
+ return importRevisionList(context).then((ChartRevisionRange) => {
+ chart = ChartTest.createInteractiveChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ evaluator = new ChartRevisionRange(chart);
+ expect(evaluator.revisionList()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ expect(evaluator.revisionList()).to.be(null);
+
+ const currentView = chart.sampledTimeSeriesData('current');
+ chart.setSelection([currentView.firstPoint().time + 1, currentView.lastPoint().time - 1]);
+
+ let revisionList = evaluator.revisionList();
+ expect(revisionList).to.not.be(null);
+ expect(revisionList.length).to.be(2);
+
+ expect(revisionList[0].repository.label()).to.be('SomeApp');
+ expect(revisionList[0].label).to.be('r4003-r4004'); // 4002 and 4005 are outliers and skipped.
+ expect(revisionList[0].from).to.be('4003');
+ expect(revisionList[0].to).to.be('4004');
+
+ expect(revisionList[1].repository.label()).to.be('macOS');
+ expect(revisionList[1].label).to.be('15B42 - 15C50');
+ expect(revisionList[1].from).to.be('15B42');
+ expect(revisionList[1].to).to.be('15C50');
+ });
+ });
+ });
+
+});
Added: trunk/Websites/perf.webkit.org/browser-tests/chart-status-evaluator-tests.js (0 => 213300)
--- trunk/Websites/perf.webkit.org/browser-tests/chart-status-evaluator-tests.js (rev 0)
+++ trunk/Websites/perf.webkit.org/browser-tests/chart-status-evaluator-tests.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -0,0 +1,477 @@
+
+describe('ChartStatusEvaluator', () => {
+
+ function importEvaluator(context)
+ {
+ const scripts = [
+ 'lazily-evaluated-function.js',
+ 'components/chart-status-evaluator.js'];
+
+ return ChartTest.importChartScripts(context).then(() => {
+ return context.importScripts(scripts, 'Test', 'Metric', 'ChartStatusEvaluator');
+ }).then(() => {
+ return context.symbols.ChartStatusEvaluator;
+ });
+ }
+
+ function makeMetric(context, name) {
+ const Test = context.symbols.Test;
+ const Metric = context.symbols.Metric;
+
+ const test = new Test(10, {name: 'SomeTest'});
+ const metric = new Metric(1, {name: name, test: test});
+
+ return metric;
+ }
+
+ describe('status on a non-interactive chart', () => {
+
+ it('should report the current value of the latest data point', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be(null);
+ expect(status.currentValue).to.be('116 ms');
+ expect(status.relativeDelta).to.be(null);
+ })
+ });
+
+ it('should compare the latest current data point to the baseline when for a smaller-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be('better');
+ expect(status.label).to.be('11.5% better than baseline (131 ms)');
+ expect(status.currentValue).to.be('116 ms');
+ expect(status.relativeDelta).to.be(null);
+ })
+ });
+
+ it('should compare the latest current data point to the baseline when for a bigger-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Score');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be('worse');
+ expect(status.label).to.be('11.5% worse than baseline (131 pt)');
+ expect(status.currentValue).to.be('116 pt');
+ expect(status.relativeDelta).to.be(null);
+ })
+ });
+
+ it('should compare the latest current data point to the target when for a smaller-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be('worse');
+ expect(status.label).to.be('27.5% until target (91.0 ms)');
+ expect(status.currentValue).to.be('116 ms');
+ expect(status.relativeDelta).to.be(null);
+ })
+ });
+
+ it('should compare the latest current data point to the target when for a bigger-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Score');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be('better');
+ expect(status.label).to.be('27.5% until target (91.0 pt)');
+ expect(status.currentValue).to.be('116 pt');
+ expect(status.relativeDelta).to.be(null);
+ })
+ });
+
+ it('should compare the latest current data point to the target when it is smaller than the baseline for a smaller-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be('27.5% until target (91.0 ms)');
+ expect(status.currentValue).to.be('116 ms');
+ expect(status.relativeDelta).to.be(null);
+ });
+ });
+
+ it('should compare the latest current data point to the baseline when it is smaller than the baseline for a bigger-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Score');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be('worse');
+ expect(status.label).to.be('11.5% worse than baseline (131 pt)');
+ expect(status.currentValue).to.be('116 pt');
+ expect(status.relativeDelta).to.be(null);
+ });
+ });
+
+ it('should compare the latest current data point to the baseline when it is bigger than the baseline and the target for a smaller-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0], {baselineIsSmaller: true});
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be('worse');
+ expect(status.label).to.be('274.2% worse than baseline (31.0 ms)');
+ expect(status.currentValue).to.be('116 ms');
+ expect(status.relativeDelta).to.be(null);
+ });
+ });
+
+ it('should compare the latest current data point to the target when it is bigger than the baseline but smaller than the target for a bigger-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0], {baselineIsSmaller: true, targetIsBigger: true});
+
+ const metric = makeMetric(context, 'Score');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be('4.1% until target (121 pt)');
+ expect(status.currentValue).to.be('116 pt');
+ expect(status.relativeDelta).to.be(null);
+ });
+ });
+
+ it('should compare the latest current data point to the target when it is smaller than the target for a smaller-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0], {targetIsBigger: true});
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be('better');
+ expect(status.label).to.be('4.1% better than target (121 ms)');
+ expect(status.currentValue).to.be('116 ms');
+ expect(status.relativeDelta).to.be(null);
+ });
+ });
+
+ it('should compare the latest current data point to the baseline when it is smaller than the target but bigger than the baseline for a bigger-is-better unit', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createChartWithSampleCluster(context, [{type: 'current'}, {type: 'target'}, {type: 'baseline'}]);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0], {baselineIsSmaller: true, targetIsBigger: true});
+
+ const metric = makeMetric(context, 'Score');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ const status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be('4.1% until target (121 pt)');
+ expect(status.currentValue).to.be('116 pt');
+ expect(status.relativeDelta).to.be(null);
+ });
+ });
+ });
+
+ describe('status on an interactive chart', () => {
+
+ it('should not report the current value of the latest data point', () => {
+ const context = new BrowsingContext();
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ const chart = ChartTest.createInteractiveChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ expect(evaluator.status()).to.be(null);
+ })
+ });
+
+ it('should report the current value and the relative delta when there is a locked indicator', () => {
+ const context = new BrowsingContext();
+ let chart;
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ chart = ChartTest.createInteractiveChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ expect(evaluator.status()).to.be(null);
+
+ const currentView = chart.sampledTimeSeriesData('current');
+ chart.setIndicator(currentView.lastPoint().id, true);
+
+ let status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be(null);
+ expect(status.currentValue).to.be('116 ms');
+ expect(status.relativeDelta).to.be('-6%');
+
+ chart.setIndicator(currentView.previousPoint(currentView.lastPoint()).id, true);
+ status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be(null);
+ expect(status.currentValue).to.be('124 ms');
+ expect(status.relativeDelta).to.be('10%');
+
+ chart.setIndicator(currentView.firstPoint().id, true);
+ status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be(null);
+ expect(status.currentValue).to.be('100 ms');
+ expect(status.relativeDelta).to.be(null);
+
+ return waitForComponentsToRender(context);
+ });
+ });
+
+ it('should report the current value and the relative delta when there is a selection with at least two points', () => {
+ const context = new BrowsingContext();
+ let chart;
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ chart = ChartTest.createInteractiveChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ expect(evaluator.status()).to.be(null);
+
+ const currentView = chart.sampledTimeSeriesData('current');
+ const firstPoint = currentView.firstPoint();
+ const lastPoint = currentView.lastPoint();
+ chart.setSelection([firstPoint.time + 1, lastPoint.time - 1]);
+
+ let status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be(null);
+ expect(status.currentValue).to.be('124 ms');
+ expect(status.relativeDelta).to.be('2%');
+
+ return waitForComponentsToRender(context);
+ });
+ });
+
+ it('should report the current value but not the relative delta when there is a selection with exaclyt one point', () => {
+ const context = new BrowsingContext();
+ let chart;
+ let evaluator;
+ return importEvaluator(context).then((ChartStatusEvaluator) => {
+ chart = ChartTest.createInteractiveChartWithSampleCluster(context);
+ chart.setDomain(ChartTest.sampleCluster.startTime, ChartTest.sampleCluster.endTime);
+ chart.fetchMeasurementSets();
+
+ const requests = context.symbols.MockRemoteAPI.requests;
+ expect(requests.length).to.be(1);
+ ChartTest.respondWithSampleCluster(requests[0]);
+
+ const metric = makeMetric(context, 'Time');
+ evaluator = new ChartStatusEvaluator(chart, metric);
+ expect(evaluator.status()).to.be(null);
+
+ return waitForComponentsToRender(context);
+ }).then(() => {
+ expect(evaluator.status()).to.be(null);
+
+ const currentView = chart.sampledTimeSeriesData('current');
+ const firstPoint = currentView.firstPoint();
+ chart.setSelection([firstPoint.time + 1, currentView.nextPoint(firstPoint).time + 1]);
+
+ let status = evaluator.status();
+ expect(status).to.not.be(null);
+ expect(status.comparison).to.be(null);
+ expect(status.label).to.be(null);
+ expect(status.currentValue).to.be('122 ms');
+ expect(status.relativeDelta).to.be(null);
+
+ return waitForComponentsToRender(context);
+ });
+ });
+
+ });
+
+});
Modified: trunk/Websites/perf.webkit.org/browser-tests/index.html (213299 => 213300)
--- trunk/Websites/perf.webkit.org/browser-tests/index.html 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/browser-tests/index.html 2017-03-02 21:23:07 UTC (rev 213300)
@@ -19,6 +19,8 @@
<script src=""
<script src=""
<script src=""
+<script src=""
+<script src=""
<script>
afterEach(() => {
@@ -40,6 +42,7 @@
this.symbols = {};
this.global = this.iframe.contentWindow;
this.document = this.iframe.contentDocument;
+ this._didLoadMockRemote = false;
}
importScripts(pathList, ...symbolList)
@@ -48,8 +51,12 @@
const global = this.iframe.contentWindow;
pathList = pathList.map((path) => `../public/v3/${path}`);
+ if (!this._didLoadMockRemote) {
+ this._didLoadMockRemote = true;
+ pathList.unshift('../unit-tests/resources/mock-remote-api.js');
+ }
- return Promise.all(['../unit-tests/resources/mock-remote-api.js', ...pathList].map((path) => {
+ return Promise.all(pathList.map((path) => {
return new Promise((resolve, reject) => {
let script = doc.createElement('script');
script.addEventListener('load', resolve);
@@ -196,15 +203,21 @@
'../shared/statistics.js',
'instrumentation.js',
'models/data-model.js',
- 'models/metric.js',
'models/time-series.js',
'models/measurement-set.js',
'models/measurement-cluster.js',
'models/measurement-adaptor.js',
+ 'models/repository.js',
+ 'models/platform.js',
+ 'models/test.js',
+ 'models/metric.js',
+ 'models/root-set.js',
+ 'models/commit-log.js',
'components/base.js',
'components/time-series-chart.js',
'components/interactive-time-series-chart.js'],
- 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart', 'Metric', 'MeasurementSet', 'MockRemoteAPI').then(() => {
+ 'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart',
+ 'Platform', 'Metric', 'Test', 'Repository', 'MeasurementSet', 'MockRemoteAPI').then(() => {
return context.symbols.TimeSeriesChart;
})
},
@@ -211,7 +224,24 @@
posixTime: posixTime,
- sampleCluster: {
+ get sampleCluster() { return this.makeSampleCluster(); },
+
+ makeModelObjectsForSampleCluster(context)
+ {
+ const test = context.symbols.Test.ensureSingleton(2, {name: 'Test'});
+ const metric = context.symbols.Metric.ensureSingleton(1, {name: 'Time', test})
+ const platform = context.symbols.Platform.ensureSingleton(1,
+ {name: 'SomePlatform', metrics: [metric], lastModifiedByMetric: [posixTime('2016-01-18T00:00:00Z')]});
+ metric.addPlatform(platform);
+ context.symbols.Repository.ensureSingleton(1, {name: 'SomeApp'});
+ context.symbols.Repository.ensureSingleton(2, {name: 'macOS'});
+ },
+
+ makeSampleCluster(options = {})
+ {
+ const baselineStart = options.baselineIsSmaller ? 30 : 130;
+ const targetStart = options.targetIsBigger ? 120 : 90;
+ return {
"clusterStart": posixTime('2016-01-01T00:00:00Z'),
"clusterSize": 7 * dayInMilliseconds,
"startTime": posixTime('2016-01-01T00:00:00Z'),
@@ -228,41 +258,65 @@
"current": [
[
1000, 100, 1, 100, 100 * 100, false,
- [ [ 2000, 1, "4000", posixTime('2016-01-05T17:35:00Z')] ],
+ [ [2000, 1, "4000", posixTime('2016-01-05T17:35:00Z')], [3000, 2, "15B42", 0] ],
posixTime('2016-01-05T17:35:00Z'), 5000, posixTime('2016-01-05T19:23:00Z'), "10", 7
],
[
1001, 131, 1, 131, 131 * 131, true,
- [ [ 2001, 1, "4001", posixTime('2016-01-05T18:43:01Z')] ],
+ [ [2001, 1, "4001", posixTime('2016-01-05T18:43:01Z')], [3000, 2, "15B42", 0] ],
posixTime('2016-01-05T18:43:01Z'), 5001, posixTime('2016-01-05T20:58:01Z'), "11", 7
],
[
1002, 122, 1, 122, 122 * 122, false,
- [ [ 2002, 1, "4002", posixTime('2016-01-05T20:01:02Z') ] ],
+ [ [2002, 1, "4002", posixTime('2016-01-05T20:01:02Z')], [3000, 2, "15B42", 0] ],
posixTime('2016-01-05T20:01:02Z'), 5002, posixTime('2016-01-05T22:37:02Z'), "12", 7
],
[
1003, 113, 1, 113, 113 * 113, false,
- [ [ 2003, 1, "4003", posixTime('2016-01-05T23:19:03Z') ] ],
+ [ [2003, 1, "4003", posixTime('2016-01-05T23:19:03Z')], [3000, 2, "15B42", 0] ],
posixTime('2016-01-05T23:19:03Z'), 5003, posixTime('2016-01-06T23:19:03Z'), "13", 7
],
[
1004, 124, 1, 124, 124 * 124, false,
- [ [ 2004, 1, "4004", posixTime('2016-01-06T01:52:04Z') ] ],
+ [ [2004, 1, "4004", posixTime('2016-01-06T01:52:04Z')], [3001, 2, "15C50", 0] ],
posixTime('2016-01-06T01:52:04Z'), 5004, posixTime('2016-01-06T02:42:04Z'), "14", 7
],
[
1005, 115, 1, 115, 115 * 115, true,
- [ [ 2005, 1, "4005", posixTime('2016-01-06T03:22:05Z') ] ],
+ [ [2005, 1, "4005", posixTime('2016-01-06T03:22:05Z')], [3001, 2, "15C50", 0] ],
posixTime('2016-01-06T03:22:05Z'), 5005, posixTime('2016-01-06T06:01:05Z'), "15", 7
],
[
1006, 116, 1, 116, 116 * 116, false,
- [ [ 2006, 1, "4006", posixTime('2016-01-06T05:59:06Z') ] ],
+ [ [2006, 1, "4006", posixTime('2016-01-06T05:59:06Z')], [3001, 2, "15C50", 0] ],
posixTime('2016-01-06T05:59:06Z'), 5006, posixTime('2016-01-06T08:34:06Z'), "16", 7
]
+ ],
+ "baseline": [
+ [
+ 7000, baselineStart, 1, baselineStart, baselineStart * baselineStart, false,
+ [ ],
+ posixTime('2016-01-05T12:00:30Z'), 5030, posixTime('2016-01-05T12:00:30Z'), "30", 7
+ ],
+ [
+ 7001, baselineStart + 1, 1, baselineStart + 1, Math.pow(baselineStart + 1, 2), false,
+ [ ],
+ posixTime('2016-01-06T00:00:31Z'), 5031, posixTime('2016-01-06T00:00:31Z'), "31", 7
+ ],
+ ],
+ "target": [
+ [
+ 8000, targetStart, 1, targetStart, targetStart * targetStart, false,
+ [ ],
+ posixTime('2016-01-05T12:00:30Z'), 5030, posixTime('2016-01-05T12:00:30Z'), "90", 7
+ ],
+ [
+ 8001, targetStart + 1, 1, targetStart + 1, Math.pow(targetStart + 1, 2), false,
+ [ ],
+ posixTime('2016-01-06T00:00:31Z'), 5031, posixTime('2016-01-06T00:00:31Z'), "91", 7
+ ],
]
- },
+ }};
},
createChartWithSampleCluster(context, sourceList = null, chartOptions = {}, className = 'TimeSeriesChart')
@@ -296,11 +350,11 @@
return this.createChartWithSampleCluster(context, sourceList, chartOptions, 'InteractiveTimeSeriesChart');
},
- respondWithSampleCluster(request)
+ respondWithSampleCluster(request, options)
{
expect(request.url).to.be('../data/measurement-set-1-1.json');
expect(request.method).to.be('GET');
- request.resolve(this.sampleCluster);
+ request.resolve(this.makeSampleCluster(options));
},
};
Modified: trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -12,6 +12,7 @@
this._metric = null;
this._disableSampling = false;
this._showOutliers = false;
+ this._openRepository = null;
this._overviewChart = null;
this._mainChart = null;
@@ -53,10 +54,13 @@
this._mainChart.listenToAction('annotationClick', this._openAnalysisTask.bind(this));
this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
- this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart, this._requestOpeningCommitViewer.bind(this));
+ this._revisionRange = new ChartRevisionRange(this._mainChart);
+
+ this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart);
+ this._mainChartStatus.listenToAction('openRepository', this.openNewRepository.bind(this));
this.renderReplace(this.content().querySelector('.chart-pane-details'), this._mainChartStatus);
- this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this));
+ this.content().querySelector('.chart-pane').addEventListener('keydown', this._keyup.bind(this));
this.fetchAnalysisTasks(false);
}
@@ -125,11 +129,18 @@
this._mainChart.setSelection(selection);
}
+ setOpenRepository(repository)
+ {
+ this._openRepository = repository;
+ this._mainChartStatus.setCurrentRepository(repository);
+ this._updateCommitLogViewer();
+ }
+
_overviewSelectionDidChange(domain, didEndDrag) { }
_mainSelectionDidChange(selection, didEndDrag)
{
- this._updateStatus();
+ this._updateCommitLogViewer();
}
_mainSelectionDidZoom(selection)
@@ -141,19 +152,19 @@
_indicatorDidChange(indicatorID, isLocked)
{
- this._updateStatus();
+ this._updateCommitLogViewer();
}
_didFetchData()
{
- this._updateStatus();
+ this._updateCommitLogViewer();
}
- _updateStatus()
+ _updateCommitLogViewer()
{
- var range = this._mainChartStatus.updateRevisionList();
+ const range = this._revisionRange.rangeForRepository(this._openRepository);
const updateRendering = () => { this.enqueueToRender(); };
- this._commitLogViewer.view(range.repository, range.from, range.to).then(updateRendering);
+ this._commitLogViewer.view(this._openRepository, range.from, range.to).then(updateRendering);
updateRendering();
}
@@ -166,12 +177,10 @@
router() { return null; }
- _requestOpeningCommitViewer(repository, from, to)
+ openNewRepository(repository)
{
- this._mainChartStatus.setCurrentRepository(repository);
- const updateRendering = () => { this.enqueueToRender(); };
- this._commitLogViewer.view(repository, from, to).then(updateRendering);
- updateRendering();
+ this.content().querySelector('.chart-pane').focus();
+ this.setOpenRepository(repository);
}
_keyup(event)
@@ -186,11 +195,13 @@
return;
break;
case 38: // Up
- if (!this._mainChartStatus.moveRepositoryWithNotification(false))
+ if (!this._moveOpenRepository(false))
return;
+ break;
case 40: // Down
- if (!this._mainChartStatus.moveRepositoryWithNotification(true))
+ if (!this._moveOpenRepository(true))
return;
+ break;
default:
return;
}
@@ -201,6 +212,28 @@
event.stopPropagation();
}
+ _moveOpenRepository(forward)
+ {
+ const openRepository = this._openRepository;
+ if (!openRepository)
+ return false;
+
+ const revisionList = this._revisionRange.revisionList();
+ if (!revisionList)
+ return false;
+
+ const currentIndex = revisionList.findIndex((info) => info.repository == openRepository);
+ console.assert(currentIndex >= 0);
+
+ const newIndex = currentIndex + (forward ? 1 : -1);
+ if (newIndex < 0 || newIndex >= revisionList.length)
+ return false;
+
+ this.openNewRepository(revisionList[newIndex].repository);
+
+ return true;
+ }
+
render()
{
Instrumentation.startMeasuringTime('ChartPane', 'render');
Added: trunk/Websites/perf.webkit.org/public/v3/components/chart-revision-range.js (0 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/chart-revision-range.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-revision-range.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -0,0 +1,68 @@
+
+class ChartRevisionRange {
+
+ constructor(chart, metric)
+ {
+ this._chart = chart;
+
+ const thisClass = new.target;
+ this._computeRevisionList = new LazilyEvaluatedFunction((currentPoint, prevoiusPoint) => {
+ return thisClass._computeRevisionList(currentPoint, prevoiusPoint);
+ });
+
+ this._computeRevisionRange = new LazilyEvaluatedFunction((repository, currentPoint, previousPoint) => {
+ return {
+ repository,
+ from: thisClass._revisionForPoint(repository, previousPoint),
+ to: thisClass._revisionForPoint(repository, currentPoint)};
+ });
+ }
+
+ revisionList()
+ {
+ const referencePoints = this._chart.referencePoints('current');
+ const currentPoint = referencePoints ? referencePoints.currentPoint : null;
+ const previousPoint = referencePoints ? referencePoints.previousPoint : null;
+ return this._computeRevisionList.evaluate(currentPoint, previousPoint);
+ }
+
+ rangeForRepository(repository)
+ {
+ const referencePoints = this._chart.referencePoints('current');
+ const currentPoint = referencePoints ? referencePoints.currentPoint : null;
+ const previousPoint = referencePoints ? referencePoints.previousPoint : null;
+ return this._computeRevisionRange.evaluate(repository, currentPoint, previousPoint);
+ }
+
+ static _revisionForPoint(repository, point)
+ {
+ if (!point || !repository)
+ return null;
+ const rootSet = point.rootSet();
+ if (!rootSet)
+ return null;
+ const commit = rootSet.commitForRepository(repository);
+ if (!commit)
+ return null;
+ return commit.revision();
+ }
+
+ static _computeRevisionList(currentPoint, previousPoint)
+ {
+ if (!currentPoint)
+ return null;
+
+ const currentRootSet = currentPoint.rootSet();
+ const previousRootSet = previousPoint ? previousPoint.rootSet() : null;
+
+ const repositoriesInCurrentRootSet = Repository.sortByNamePreferringOnesWithURL(currentRootSet.repositories());
+ const revisionList = [];
+ for (let repository of repositoriesInCurrentRootSet) {
+ let currentCommit = currentRootSet.commitForRepository(repository);
+ let previousCommit = previousRootSet ? previousRootSet.commitForRepository(repository) : null;
+ revisionList.push(currentCommit.diff(previousCommit));
+ }
+ return revisionList;
+ }
+
+}
Added: trunk/Websites/perf.webkit.org/public/v3/components/chart-status-evaluator.js (0 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/chart-status-evaluator.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-status-evaluator.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -0,0 +1,79 @@
+
+class ChartStatusEvaluator {
+
+ constructor(chart, metric)
+ {
+ this._chart = chart;
+ this._computeStatus = new LazilyEvaluatedFunction((currentPoint, previousPoint, view) => {
+ if (!currentPoint)
+ return null;
+
+ const baselineView = this._chart.sampledTimeSeriesData('baseline');
+ const targetView = this._chart.sampledTimeSeriesData('target');
+ return ChartStatusEvaluator.computeChartStatus(metric, currentPoint, previousPoint, view, baselineView, targetView);
+ });
+ }
+
+ status()
+ {
+ const referencePoints = this._chart.referencePoints('current');
+ const currentPoint = referencePoints ? referencePoints.currentPoint : null;
+ const previousPoint = referencePoints ? referencePoints.previousPoint : null;
+ const view = referencePoints ? referencePoints.view : null;
+ return this._computeStatus.evaluate(currentPoint, previousPoint, view);
+ }
+
+ static computeChartStatus(metric, currentPoint, previousPoint, currentView, baselineView, targetView)
+ {
+ const formatter = metric.makeFormatter(3);
+ const deltaFormatter = metric.makeFormatter(2, true);
+ const smallerIsBetter = metric.isSmallerBetter();
+
+ const labelForDiff = (diff, referencePoint, name, comparison) => {
+ const relativeDiff = Math.abs(diff * 100).toFixed(1);
+ const referenceValue = referencePoint ? ` (${formatter(referencePoint.value)})` : '';
+ if (comparison != 'until')
+ comparison += ' than';
+ return `${relativeDiff}% ${comparison} ${name}${referenceValue}`;
+ }
+
+ const pointIsInCurrentSeries = baselineView != currentView && targetView != currentView;
+
+ const baselinePoint = pointIsInCurrentSeries && baselineView ? baselineView.lastPointInTimeRange(0, currentPoint.time) : null;
+ const targetPoint = pointIsInCurrentSeries && targetView ? targetView.lastPointInTimeRange(0, currentPoint.time) : null;
+
+ const diffFromBaseline = baselinePoint ? (currentPoint.value - baselinePoint.value) / baselinePoint.value : undefined;
+ const diffFromTarget = targetPoint ? (currentPoint.value - targetPoint.value) / targetPoint.value : undefined;
+
+ let label = null;
+ let comparison = null;
+
+ if (diffFromBaseline !== undefined && diffFromTarget !== undefined) {
+ if (diffFromBaseline > 0 == smallerIsBetter) {
+ comparison = 'worse';
+ label = labelForDiff(diffFromBaseline, baselinePoint, 'baseline', comparison);
+ } else if (diffFromTarget < 0 == smallerIsBetter) {
+ comparison = 'better';
+ label = labelForDiff(diffFromTarget, targetPoint, 'target', comparison);
+ } else
+ label = labelForDiff(diffFromTarget, targetPoint, 'target', 'until');
+ } else if (diffFromBaseline !== undefined) {
+ comparison = diffFromBaseline > 0 == smallerIsBetter ? 'worse' : 'better';
+ label = labelForDiff(diffFromBaseline, baselinePoint, 'baseline', comparison);
+ } else if (diffFromTarget !== undefined) {
+ comparison = diffFromTarget < 0 == smallerIsBetter ? 'better' : 'worse';
+ label = labelForDiff(diffFromTarget, targetPoint, 'target', 'until');
+ }
+
+ let valueDelta = null;
+ let relativeDelta = null;
+ if (previousPoint) {
+ valueDelta = deltaFormatter(currentPoint.value - previousPoint.value);
+ relativeDelta = (currentPoint.value - previousPoint.value) / previousPoint.value;
+ relativeDelta = (relativeDelta * 100).toFixed(0) + '%';
+ }
+
+ return {comparison, label, currentValue: formatter(currentPoint.value), valueDelta, relativeDelta};
+ }
+
+}
Deleted: trunk/Websites/perf.webkit.org/public/v3/components/chart-status-view.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/chart-status-view.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-status-view.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -1,181 +0,0 @@
-
-class ChartStatusView extends ComponentBase {
-
- constructor(metric, chart)
- {
- super('chart-status');
- this._metric = metric;
- this._chart = chart;
-
- this._usedSelection = null;
- this._usedCurrentPoint = null;
- this._usedPreviousPoint = null;
-
- this._currentValue = null;
- this._comparisonClass = null;
- this._comparisonLabel = null;
-
- this._renderedCurrentValue = null;
- this._renderedComparisonClass = null;
- this._renderedComparisonLabel = null;
- }
-
- render()
- {
- this.updateStatusIfNeeded();
-
- if (this._renderedCurrentValue == this._currentValue
- && this._renderedComparisonClass == this._comparisonClass
- && this._renderedComparisonLabel == this._comparisonLabel)
- return;
-
- this._renderedCurrentValue = this._currentValue;
- this._renderedComparisonClass = this._comparisonClass;
- this._renderedComparisonLabel = this._comparisonLabel;
-
- this.content().querySelector('.chart-status-current-value').textContent = this._currentValue || '';
- var comparison = this.content().querySelector('.chart-status-comparison');
- comparison.className = 'chart-status-comparison ' + (this._comparisonClass || '');
- comparison.textContent = this._comparisonLabel;
- }
-
- updateStatusIfNeeded()
- {
- var currentPoint;
- var previousPoint;
-
- if (this._chart instanceof InteractiveTimeSeriesChart) {
- var selection = this._chart.currentSelection();
- if (selection && this._usedSelection == selection)
- return false;
-
- if (selection) {
- const view = this._chart.selectedPoints('current');
- if (!view)
- return false;
-
- if (view && view.length() > 1) {
- this._usedSelection = selection;
- currentPoint = view.lastPoint();
- previousPoint = view.firstPoint();
- }
- } else {
- const indicator = this._chart.currentIndicator();
- if (indicator) {
- currentPoint = indicator.point;
- previousPoint = indicator.view.previousPoint(currentPoint);
- }
- }
- } else {
- var data = ""
- if (!data)
- return false;
- if (data.length())
- currentPoint = data.lastPoint();
- }
-
- if (currentPoint == this._usedCurrentPoint && previousPoint == this._usedPreviousPoint)
- return false;
-
- this._usedCurrentPoint = currentPoint;
- this._usedPreviousPoint = previousPoint;
-
- this.computeChartStatusLabels(currentPoint, previousPoint);
-
- return true;
- }
-
- computeChartStatusLabels(currentPoint, previousPoint)
- {
- var status = currentPoint ? this._computeChartStatus(this._metric, this._chart, currentPoint, previousPoint) : null;
- if (status) {
- this._currentValue = status.currentValue;
- if (previousPoint)
- this._currentValue += ` (${status.valueDelta} / ${status.relativeDelta})`;
- this._comparisonClass = status.className;
- this._comparisonLabel = status.label;
- } else {
- this._currentValue = null;
- this._comparisonClass = null;
- this._comparisonLabel = null;
- }
- }
-
- _computeChartStatus(metric, chart, currentPoint, previousPoint)
- {
- console.assert(currentPoint);
- const baselineView = chart.sampledTimeSeriesData('baseline');
- const targetView = chart.sampledTimeSeriesData('target');
-
- const formatter = metric.makeFormatter(3);
- const deltaFormatter = metric.makeFormatter(2, true);
- const smallerIsBetter = metric.isSmallerBetter();
-
- const labelForDiff = (diff, referencePoint, name, comparison) => {
- const relativeDiff = Math.abs(diff * 100).toFixed(1);
- const referenceValue = referencePoint ? ` (${formatter(referencePoint.value)})` : '';
- return `${relativeDiff}% ${comparison} than ${name}${referenceValue}`;
- };
-
- const baselinePoint = baselineView ? baselineView.lastPointInTimeRange(0, currentPoint.time) : null;
- const targetPoint = targetView ? targetView.lastPointInTimeRange(0, currentPoint.time) : null;
-
- const diffFromBaseline = baselinePoint ? (currentPoint.value - baselinePoint.value) / baselinePoint.value : undefined;
- const diffFromTarget = targetPoint ? (currentPoint.value - targetPoint.value) / targetPoint.value : undefined;
-
- let label = null;
- let comparison = null;
-
- if (diffFromBaseline !== undefined && diffFromTarget !== undefined) {
- if (diffFromBaseline > 0 == smallerIsBetter) {
- comparison = 'worse';
- label = labelForDiff(diffFromBaseline, baselinePoint, 'baseline', comparison);
- } else if (diffFromTarget < 0 == smallerIsBetter) {
- comparison = 'better';
- label = labelForDiff(diffFromTarget, targetPoint, 'target', comparison);
- } else
- label = labelForDiff(diffFromTarget, targetPoint, 'target', 'until');
- } else if (diffFromBaseline !== undefined) {
- comparison = diffFromBaseline > 0 == smallerIsBetter ? 'worse' : 'better';
- label = labelForDiff(diffFromBaseline, baselinePoint, 'baseline', comparison);
- } else if (diffFromTarget !== undefined) {
- comparison = diffFromTarget < 0 == smallerIsBetter ? 'better' : 'worse';
- label = labelForDiff(diffFromTarget, targetPoint, 'target', comparison);
- }
-
- let valueDelta = null;
- let relativeDelta = null;
- if (previousPoint) {
- valueDelta = deltaFormatter(currentPoint.value - previousPoint.value);
- relativeDelta = (currentPoint.value - previousPoint.value) / previousPoint.value;
- relativeDelta = (relativeDelta * 100).toFixed(0) + '%';
- }
-
- return {className: comparison, label, currentValue: formatter(currentPoint.value), valueDelta, relativeDelta};
- }
-
- static htmlTemplate()
- {
- return `
- <div>
- <span class="chart-status-current-value"></span>
- <span class="chart-status-comparison"></span>
- </div>`;
- }
-
- static cssTemplate()
- {
- return `
- .chart-status-current-value {
- padding-right: 0.5rem;
- }
-
- .chart-status-comparison.worse {
- color: #c33;
- }
-
- .chart-status-comparison.better {
- color: #33c;
- }`;
- }
-}
\ No newline at end of file
Modified: trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -52,6 +52,7 @@
backgroundIntervalStyle: 'rgba(255, 153, 153, 0.1)',
backgroundPointStyle: '#f99',
backgroundLineStyle: '#fcc',
+ interactive: true,
};
}
Added: trunk/Websites/perf.webkit.org/public/v3/components/dashboard-chart-status-view.js (0 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/dashboard-chart-status-view.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/dashboard-chart-status-view.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -0,0 +1,45 @@
+
+class DashboardChartStatusView extends ComponentBase {
+
+ constructor(metric, chart)
+ {
+ super('chart-status-view');
+ this._statusEvaluator = new ChartStatusEvaluator(chart, metric);
+ this._renderLazily = new LazilyEvaluatedFunction((status) => {
+ status = status || {};
+ this.content('current-value').textContent = status.currentValue || '';
+ this.content('comparison').textContent = status.label || '';
+ this.content('comparison').className = status.comparison || '';
+ });
+ }
+
+ render()
+ {
+ this._renderLazily.evaluate(this._statusEvaluator.status());
+ }
+
+ static htmlTemplate()
+ {
+ return `<span id="current-value"></span> <span id="comparison"></span>`;
+ }
+
+ static cssTemplate()
+ {
+ return `
+ :host {
+ display: block;
+ }
+
+ #comparison {
+ padding-left: 0.5rem;
+ }
+
+ #comparison.worse {
+ color: #c33;
+ }
+
+ #comparison.better {
+ color: #33c;
+ }`;
+ }
+}
Modified: trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -52,6 +52,27 @@
return selection && data ? data.firstPointInTimeRange(selection[0], selection[1]) : null;
}
+ referencePoints(type)
+ {
+ const selection = this.currentSelection();
+ if (selection) {
+ const view = this.selectedPoints('current');
+ if (!view)
+ return null;
+ const firstPoint = view.lastPoint();
+ const lastPoint = view.firstPoint();
+ if (!firstPoint)
+ return null;
+ return {view, currentPoint: firstPoint, previousPoint: firstPoint != lastPoint ? lastPoint : null};
+ } else {
+ const indicator = this.currentIndicator();
+ if (!indicator)
+ return null;
+ return {view: indicator.view, currentPoint: indicator.point, previousPoint: indicator.view.previousPoint(indicator.point)};
+ }
+ return null;
+ }
+
setIndicator(id, shouldLock)
{
var selectionDidChange = !!this._sampledTimeSeriesData;
Modified: trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -131,6 +131,17 @@
return null;
}
+ referencePoints(type)
+ {
+ const view = this.sampledTimeSeriesData(type);
+ if (!view || !this._startTime || !this._endTime)
+ return null;
+ const point = view.lastPointInTimeRange(this._startTime, this._endTime);
+ if (!point)
+ return null;
+ return {view, currentPoint: point, previousPoint: null};
+ }
+
setAnnotations(annotations)
{
this._annotations = annotations;
Modified: trunk/Websites/perf.webkit.org/public/v3/index.html (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/index.html 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/index.html 2017-03-02 21:23:07 UTC (rev 213300)
@@ -38,12 +38,12 @@
<template id="unbundled-scripts">
<script src=""
- <script src=""
<script src=""
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
@@ -75,7 +75,7 @@
<script src=""
<script src=""
<script src=""
- <script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
@@ -84,6 +84,8 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Added: trunk/Websites/perf.webkit.org/public/v3/lazily-evaluated-function.js (0 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/lazily-evaluated-function.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/lazily-evaluated-function.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -0,0 +1,27 @@
+class LazilyEvaluatedFunction {
+ constructor(callback, ...observedPropertiesList)
+ {
+ console.assert(typeof(callback) == 'function');
+ this._callback = callback;
+ this._observedPropertiesList = observedPropertiesList;
+ this._cachedArguments = null;
+ this._cachedResult = undefined;
+ }
+
+ evaluate(...args)
+ {
+ if (this._cachedArguments) {
+ const length = this._cachedArguments.length;
+ if (args.length == length && (!length || this._cachedArguments.every((cached, i) => cached === args[i])))
+ return this._cachedResult;
+ }
+
+ this._cachedArguments = args;
+ this._cachedResult = this._callback.apply(null, args);
+
+ return this._cachedResult;
+ }
+}
+
+if (typeof module != 'undefined')
+ module.exports.LazilyEvaluatedFunction = LazilyEvaluatedFunction;
Modified: trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -57,7 +57,7 @@
var from = previousCommit.revision();
var label = null;
if (parseInt(to) == to) { // e.g. r12345.
- from = parseInt(from) + 1;
+ from = (parseInt(from) + 1).toString();
label = `r${from}-r${this.revision()}`;
} else if (to.length == 40) { // e.g. git hash
label = `${from.substring(0, 8)}..${to.substring(0, 8)}`;
Modified: trunk/Websites/perf.webkit.org/public/v3/models/metric.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/models/metric.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/models/metric.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -8,6 +8,19 @@
object.test.addMetric(this);
this._test = object.test;
this._platforms = [];
+
+ const suffix = this.name().match('([A-z][a-z]+|FrameRate)$')[0];
+ this._unit = {
+ 'FrameRate': 'fps',
+ 'Runs': '/s',
+ 'Time': 'ms',
+ 'Duration': 'ms',
+ 'Malloc': 'B',
+ 'Heap': 'B',
+ 'Allocations': 'B',
+ 'Size': 'B',
+ 'Score': 'pt',
+ }[suffix];
}
aggregatorName() { return this._aggregatorName; }
@@ -57,9 +70,14 @@
return this.name() + suffix;
}
- unit() { return RunsData.unitFromMetricName(this.name()); }
- isSmallerBetter() { return RunsData.isSmallerBetter(this.unit()); }
+ unit() { return this._unit; }
+ isSmallerBetter()
+ {
+ const unit = this._unit;
+ return unit != 'fps' && unit != '/s' && unit != 'pt';
+ }
+
makeFormatter(sigFig, alwaysShowSign) { return Metric.makeFormatter(this.unit(), sigFig, alwaysShowSign); }
static makeFormatter(unit, sigFig = 2, alwaysShowSign)
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -16,12 +16,6 @@
this._page._chartSelectionDidChange();
}
- _updateStatus()
- {
- super._updateStatus();
- this._page.enqueueToRender();
- }
-
selectedPoints()
{
return this._mainChart ? this._mainChart.selectedPoints('current') : null;
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane-status-view.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane-status-view.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane-status-view.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -1,68 +1,75 @@
-class ChartPaneStatusView extends ChartStatusView {
-
- constructor(metric, chart, revisionCallback)
+class ChartPaneStatusView extends ComponentBase {
+ constructor(metric, chart)
{
- super(metric, chart);
+ super('chart-pane-status-view');
- this._buildLabel = null;
- this._buildUrl = null;
-
- this._revisionList = [];
+ this._chart = chart;
+ this._status = new ChartStatusEvaluator(chart, metric);
+ this._revisionRange = new ChartRevisionRange(chart);
this._currentRepository = null;
- this._revisionCallback = revisionCallback;
- this._pointsRangeForAnalysis = null;
- this._renderedRevisionList = null;
- this._renderedRepository = null;
-
- this._usedRevisionRange = [null, null, null];
+ this._renderStatusLazily = new LazilyEvaluatedFunction(this._renderStatus.bind(this));
+ this._renderBuildRevisionTableLazily = new LazilyEvaluatedFunction(this._renderBuildRevisionTable.bind(this));
}
- pointsRangeForAnalysis() { return this._pointsRangeForAnalysis; }
-
render()
{
super.render();
- if (this._renderedRevisionList == this._revisionList && this._renderedRepository == this._currentRepository)
- return;
- this._renderedRevisionList = this._revisionList;
- this._renderedRepository = this._currentRepository;
+ this._renderStatusLazily.evaluate(this._status.status());
- var element = ComponentBase.createElement;
- var link = ComponentBase.createLink;
- var self = this;
- var buildInfo = this._buildInfo;
- var tableContent = this._revisionList.map(function (info, rowIndex) {
- var selected = info.repository == self._currentRepository;
- var action = "" () {
- if (self._currentRepository == info.repository)
- self._setRevisionRange(true, null, null, null);
- else
- self._setRevisionRange(true, info.repository, info.from, info.to);
- };
+ const indicator = this._chart.currentIndicator();
+ const build = indicator ? indicator.point.build() : null;
+ this._renderBuildRevisionTableLazily.evaluate(build, this._currentRepository, this._revisionRange.revisionList());
+ }
- return element('tr', {class: selected ? 'selected' : ''}, [
- element('td', info.repository.name()),
- element('td', info.url ? link(info.label, info.label, info.url, true) : info.label),
- element('td', {class: 'commit-viewer-opener'}, link('\u00BB', action)),
- ]);
- });
+ _renderStatus(status)
+ {
+ status = status || {};
+ let currentValue = status.currentValue || '';
+ if (currentValue)
+ currentValue += ` (${status.valueDelta} / ${status.relativeDelta})`;
- if (this._buildInfo) {
- var build = this._buildInfo;
- var number = build.buildNumber();
- var buildTime = this._formatTime(build.buildTime());
- var url = ""
+ this.content('current-value').textContent = currentValue;
+ this.content('comparison').textContent = status.label || '';
+ this.content('comparison').className = status.comparison || '';
+ }
- tableContent.unshift(element('tr', [
+ _renderBuildRevisionTable(build, currentRepository, revisionList)
+ {
+ const element = ComponentBase.createElement;
+ const link = ComponentBase.createLink;
+ let tableContent = [];
+
+ if (build) {
+ const url = ""
+ const buildNumber = build.buildNumber();
+ tableContent.push(element('tr', [
element('td', 'Build'),
- element('td', {colspan: 2}, [url ? link(number, build.label(), url, true) : number, ` (${buildTime})`]),
+ element('td', {colspan: 2}, [
+ url ? link(buildNumber, build.label(), url, true) : buildNumber,
+ ` (${this._formatTime(build.buildTime())})`
+ ]),
]));
}
- this.renderReplace(this.content().querySelector('.chart-pane-revisions'), tableContent);
+ if (revisionList) {
+ for (let info of revisionList) {
+ const selected = info.repository == this._currentRepository;
+ const action = "" => {
+ this.dispatchAction('openRepository', this._currentRepository == info.repository ? null : info.repository);
+ };
+
+ tableContent.push(element('tr', {class: selected ? 'selected' : ''}, [
+ element('td', info.repository.name()),
+ element('td', info.url ? link(info.label, info.label, info.url, true) : info.label),
+ element('td', {class: 'commit-viewer-opener'}, link('\u00BB', action)),
+ ]));
+ }
+ }
+
+ this.renderReplace(this.content('build-revision'), tableContent);
}
_formatTime(date)
@@ -74,116 +81,30 @@
setCurrentRepository(repository)
{
this._currentRepository = repository;
- return this._updateRevisionListForNewCurrentRepository();
+ this.enqueueToRender();
}
- _setRevisionRange(shouldNotify, repository, from, to)
- {
- if (this._usedRevisionRange[0] == repository
- && this._usedRevisionRange[1] == from && this._usedRevisionRange[2] == to)
- return;
- this._usedRevisionRange = [repository, from, to];
- if (shouldNotify)
- this._revisionCallback(repository, from, to);
- }
-
- moveRepositoryWithNotification(forward)
- {
- var currentRepository = this._currentRepository;
- if (!currentRepository)
- return false;
- var index = this._revisionList.findIndex(function (info) { return info.repository == currentRepository; });
- console.assert(index >= 0);
-
- var newIndex = index + (forward ? 1 : -1);
- newIndex = Math.min(this._revisionList.length - 1, Math.max(0, newIndex));
- if (newIndex == index)
- return false;
-
- var item = this._revisionList[newIndex];
- this.setCurrentRepository(item ? item.repository : null);
-
- return true;
- }
-
- updateRevisionList()
- {
- if (!this._currentRepository)
- return {repository: null, from: null, to: null};
- return this._updateRevisionListForNewCurrentRepository();
- }
-
- _updateRevisionListForNewCurrentRepository()
- {
- this.updateStatusIfNeeded();
-
- for (var info of this._revisionList) {
- if (info.repository == this._currentRepository) {
- this._setRevisionRange(false, info.repository, info.from, info.to);
- return {repository: info.repository, from: info.from, to: info.to};
- }
- }
- this._setRevisionRange(false, null, null, null);
- return {repository: this._currentRepository, from: null, to: null};
- }
-
- computeChartStatusLabels(currentPoint, previousPoint)
- {
- super.computeChartStatusLabels(currentPoint, previousPoint);
-
- this._buildInfo = null;
- this._revisionList = [];
- this._pointsRangeForAnalysis = null;
-
- if (!currentPoint)
- return;
-
- if (!this._chart.currentSelection())
- this._buildInfo = currentPoint.build();
-
- if (currentPoint && previousPoint && this._chart.currentSelection()) {
- this._pointsRangeForAnalysis = {
- startPointId: previousPoint.id,
- endPointId: currentPoint.id,
- };
- }
-
- // FIXME: Rewrite the interface to obtain the list of revision changes.
- var currentRootSet = currentPoint.rootSet();
- var previousRootSet = previousPoint ? previousPoint.rootSet() : null;
-
- var repositoriesInCurrentRootSet = Repository.sortByNamePreferringOnesWithURL(currentRootSet.repositories());
- var revisionList = [];
- for (var repository of repositoriesInCurrentRootSet) {
- var currentCommit = currentRootSet.commitForRepository(repository);
- var previousCommit = previousRootSet ? previousRootSet.commitForRepository(repository) : null;
- revisionList.push(currentCommit.diff(previousCommit));
- }
-
- this._revisionList = revisionList;
- }
-
static htmlTemplate()
{
return `
- <div class="chart-pane-status">
- <h3 class="chart-status-current-value"></h3>
- <span class="chart-status-comparison"></span>
+ <div id="chart-pane-status">
+ <h3 id="current-value"></h3>
+ <span id="comparison"></span>
</div>
- <table class="chart-pane-revisions"></table>
+ <table id="build-revision"></table>
`;
}
static cssTemplate()
{
- return Toolbar.cssTemplate() + ChartStatusView.cssTemplate() + `
- .chart-pane-status {
+ return Toolbar.cssTemplate() + `
+ #chart-pane-status {
display: block;
text-align: center;
}
- .chart-pane-status .chart-status-current-value,
- .chart-pane-status .chart-status-comparison {
+ #current-value,
+ #comparison {
display: block;
margin: 0;
padding: 0;
@@ -191,7 +112,15 @@
font-size: 1rem;
}
- .chart-pane-revisions {
+ #comparison.worse {
+ color: #c33;
+ }
+
+ #comparison.better {
+ color: #33c;
+ }
+
+ #build-revision {
line-height: 1rem;
font-size: 0.9rem;
font-weight: normal;
@@ -203,23 +132,23 @@
width: 100%;
}
- .chart-pane-revisions th,
- .chart-pane-revisions td {
+ #build-revision th,
+ #build-revision td {
font-weight: inherit;
border-top: solid 1px #ccc;
padding: 0.2rem 0.2rem;
}
- .chart-pane-revisions .selected > th,
- .chart-pane-revisions .selected > td {
+ #build-revision .selected > th,
+ #build-revision .selected > td {
background: rgba(204, 153, 51, 0.1);
}
- .chart-pane-revisions .commit-viewer-opener {
+ #build-revision .commit-viewer-opener {
width: 1rem;
}
- .chart-pane-revisions .commit-viewer-opener a {
+ #build-revision .commit-viewer-opener a {
text-decoration: none;
color: inherit;
font-weight: inherit;
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -181,21 +181,12 @@
router() { return this._chartsPage.router(); }
- _requestOpeningCommitViewer(repository, from, to)
+ openNewRepository(repository)
{
- super._requestOpeningCommitViewer(repository, from, to);
+ this.content().querySelector('.chart-pane').focus();
this._chartsPage.setOpenRepository(repository);
}
- setOpenRepository(repository)
- {
- if (repository != this._commitLogViewer.currentRepository()) {
- var range = this._mainChartStatus.setCurrentRepository(repository);
- this._commitLogViewer.view(repository, range.from, range.to).then(() => { this.enqueueToRender(); });
- this.enqueueToRender();
- }
- }
-
_indicatorDidChange(indicatorID, isLocked)
{
this._chartsPage.mainChartIndicatorDidChange(this, isLocked != this._mainChartIndicatorWasLocked);
@@ -203,7 +194,7 @@
super._indicatorDidChange(indicatorID, isLocked);
}
- _analyzeRange(pointsRangeForAnalysis)
+ _analyzeRange(startPoint, endPoint)
{
var router = this._chartsPage.router();
var newWindow = window.open(router.url('analysis/task/create'), '_blank');
@@ -210,10 +201,9 @@
var analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
var name = analyzePopover.querySelector('input').value;
- var self = this;
- AnalysisTask.create(name, pointsRangeForAnalysis.startPointId, pointsRangeForAnalysis.endPointId).then(function (data) {
+ AnalysisTask.create(name, startPoint.id, endPoint.id).then((data) => {
newWindow.location.href = "" + data['taskId']);
- self.fetchAnalysisTasks(true);
+ this.fetchAnalysisTasks(true);
}, function (error) {
newWindow.location.href = "" {error: error});
});
@@ -279,16 +269,17 @@
platformPopover.style.display = 'none';
var analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
- var pointsRangeForAnalysis = this._mainChartStatus.pointsRangeForAnalysis();
- if (pointsRangeForAnalysis) {
+ const selectedPoints = this._mainChart.selectedPoints('current');
+ const hasSelectedPoints = selectedPoints && selectedPoints.length();
+ if (hasSelectedPoints) {
actions.push(this._makePopoverActionItem(analyzePopover, 'Analyze', false));
- analyzePopover._onsubmit_ = function (event) {
- event.preventDefault();
- self._analyzeRange(pointsRangeForAnalysis);
- }
+ analyzePopover._onsubmit_ = this.createEventHandler(() => {
+ console.log(selectedPoints.length());
+ this._analyzeRange(selectedPoints.firstPoint(), selectedPoints.lastPoint());
+ });
} else {
analyzePopover.style.display = 'none';
- analyzePopover._onsubmit_ = function (event) { event.preventDefault(); }
+ analyzePopover._onsubmit_ = this.createEventHandler(() => {});
}
var filteringOptions = this.content().querySelector('.chart-pane-filtering-options');
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js (213299 => 213300)
--- trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js 2017-03-02 21:22:01 UTC (rev 213299)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -137,7 +137,7 @@
chart.listenToAction('dataChange', () => this._fetchedData())
this._charts.push(chart);
- var statusView = new ChartStatusView(result.metric, chart);
+ var statusView = new DashboardChartStatusView(result.metric, chart);
this._statusViews.push(statusView);
return {
Added: trunk/Websites/perf.webkit.org/unit-tests/lazily-evaluated-function-tests.js (0 => 213300)
--- trunk/Websites/perf.webkit.org/unit-tests/lazily-evaluated-function-tests.js (rev 0)
+++ trunk/Websites/perf.webkit.org/unit-tests/lazily-evaluated-function-tests.js 2017-03-02 21:23:07 UTC (rev 213300)
@@ -0,0 +1,187 @@
+
+const assert = require('assert');
+const LazilyEvaluatedFunction = require('../public/v3/lazily-evaluated-function.js').LazilyEvaluatedFunction;
+
+describe('LazilyEvaluatedFunction', () => {
+
+ describe('evaluate', () => {
+ it('should invoke the callback on the very first call with no arguments', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate();
+ assert.deepEqual(calls, [[]]);
+ });
+
+ it('should retrun the cached results without invoking the callback on the second call with no arguments', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate();
+ assert.deepEqual(calls, [[]]);
+ lazyFunction.evaluate();
+ assert.deepEqual(calls, [[]]);
+ });
+
+ it('should invoke the callback when calld with an argument after being called with no argument', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate();
+ assert.deepEqual(calls, [[]]);
+ lazyFunction.evaluate(1);
+ assert.deepEqual(calls, [[], [1]]);
+ });
+
+ it('should invoke the callback when calld with no arguments after being called with an argument', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate('foo');
+ assert.deepEqual(calls, [['foo']]);
+ lazyFunction.evaluate();
+ assert.deepEqual(calls, [['foo'], []]);
+ });
+
+ it('should invoke the callback when calld with null after being called with undefined', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate(undefined);
+ assert.deepEqual(calls, [[undefined]]);
+ lazyFunction.evaluate(null);
+ assert.deepEqual(calls, [[undefined], [null]]);
+ });
+
+ it('should invoke the callback when calld with 0 after being called with "0"', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate(0);
+ assert.deepEqual(calls, [[0]]);
+ lazyFunction.evaluate("0");
+ assert.deepEqual(calls, [[0], ["0"]]);
+ });
+
+ it('should invoke the callback when calld with an object after being called with another object with the same set of properties', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ const x = {};
+ const y = {};
+ lazyFunction.evaluate(x);
+ assert.deepEqual(calls, [[x]]);
+ lazyFunction.evaluate(y);
+ assert.deepEqual(calls, [[x], [y]]);
+ });
+
+ it('should return the cached result without invoking the callback when calld with a string after being called with the same string', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate("foo");
+ assert.deepEqual(calls, [["foo"]]);
+ lazyFunction.evaluate("foo");
+ assert.deepEqual(calls, [["foo"]]);
+ });
+
+ it('should invoke the callback when calld with a string after being called with another string', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate("foo");
+ assert.deepEqual(calls, [["foo"]]);
+ lazyFunction.evaluate("bar");
+ assert.deepEqual(calls, [["foo"], ["bar"]]);
+ });
+
+ it('should return the cached result without invoking the callback when calld with a number after being called with the same number', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate(8);
+ assert.deepEqual(calls, [[8]]);
+ lazyFunction.evaluate(8);
+ assert.deepEqual(calls, [[8]]);
+ });
+
+ it('should invoke the callback when calld with a number after being called with another number', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate(4);
+ assert.deepEqual(calls, [[4]]);
+ lazyFunction.evaluate(2);
+ assert.deepEqual(calls, [[4], [2]]);
+ });
+
+ it('should return the cached result without invoking the callback when calld with ["hello", 3, "world"] for the second time', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate("hello", 3, "world");
+ assert.deepEqual(calls, [["hello", 3, "world"]]);
+ lazyFunction.evaluate("hello", 3, "world");
+ assert.deepEqual(calls, [["hello", 3, "world"]]);
+ });
+
+ it('should invoke the callback when calld with ["hello", 3, "world"] after being called with ["hello", 4, "world"]', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate("hello", 3, "world");
+ assert.deepEqual(calls, [["hello", 3, "world"]]);
+ lazyFunction.evaluate("hello", 4, "world");
+ assert.deepEqual(calls, [["hello", 3, "world"], ["hello", 4, "world"]]);
+ });
+
+ it('should return the cached result without invoking the callback when called with [null, null] for the second time', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate(null, null);
+ assert.deepEqual(calls, [[null, null]]);
+ lazyFunction.evaluate(null, null);
+ assert.deepEqual(calls, [[null, null]]);
+ });
+
+ it('should invoke the callback when calld with [null] after being called with [null, null]', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate(null, null);
+ assert.deepEqual(calls, [[null, null]]);
+ lazyFunction.evaluate(null);
+ assert.deepEqual(calls, [[null, null], [null]]);
+ });
+
+ it('should invoke the callback when calld with [null, 4] after being called with [null]', () => {
+ const calls = [];
+ const lazyFunction = new LazilyEvaluatedFunction((...args) => calls.push(args));
+
+ assert.deepEqual(calls, []);
+ lazyFunction.evaluate(null, 4);
+ assert.deepEqual(calls, [[null, 4]]);
+ lazyFunction.evaluate(null);
+ assert.deepEqual(calls, [[null, 4], [null]]);
+ });
+
+ });
+
+});
+