https://bugs.webkit.org/show_bug.cgi?id=141286
Reviewed by Chris Dumez.
Compare the selected value against baseline and target values as done in v1. e.g. "5% below target"
Also use d3.format to format the selected value to show four significant figures.
* public/v2/app.js:
(App.Pane.searchCommit):
(App.Pane._fetch): Create time series here via createChartData so that _computeStatus can use them
to compute the status text without having to recreate them.
(App.createChartData): Added.
(App.PaneController._updateDetails): Use 3d.format on current and old values.
(App.PaneController._computeStatus): Added. Computes the status text.
(App.PaneController._relativeDifferentToLaterPointInTimeSeries): Added.
(App.AnalysisTaskController._fetchedManifest): Use createChartData as done in App.Pane._fetch. Also
format the values using chartData.formatter.
* public/v2/chart-pane.css: Enlarge the status text. Show the status text in red if it's worse than
the baseline and in blue if it's better than the target.
* public/v2/data.js:
(TimeSeries.prototype.findPointAfterTime): Added.
* public/v2/index.html: Added a new tbody for the status text and the selected value. Also fixed
the bug that we were not showing the old value's unit.
* public/v2/interactive-chart.js:
(App.InteractiveChartComponent._constructGraphIfPossible): Use chartData.formatter. Also cleaned up
the code to show the baseline and the target lines.
* public/v2/manifest.js:
(App.Manifest.fetchRunsWithPlatformAndMetric): Added smallerIsBetter.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@179710
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
2015-02-05 Ryosuke Niwa <rniwa@webkit.org>
+ New perf dashboard should compare results to baseline and target
+ https://bugs.webkit.org/show_bug.cgi?id=141286
+
+ Reviewed by Chris Dumez.
+
+ Compare the selected value against baseline and target values as done in v1. e.g. "5% below target"
+ Also use d3.format to format the selected value to show four significant figures.
+
+ * public/v2/app.js:
+ (App.Pane.searchCommit):
+ (App.Pane._fetch): Create time series here via createChartData so that _computeStatus can use them
+ to compute the status text without having to recreate them.
+ (App.createChartData): Added.
+ (App.PaneController._updateDetails): Use 3d.format on current and old values.
+ (App.PaneController._computeStatus): Added. Computes the status text.
+ (App.PaneController._relativeDifferentToLaterPointInTimeSeries): Added.
+ (App.AnalysisTaskController._fetchedManifest): Use createChartData as done in App.Pane._fetch. Also
+ format the values using chartData.formatter.
+
+ * public/v2/chart-pane.css: Enlarge the status text. Show the status text in red if it's worse than
+ the baseline and in blue if it's better than the target.
+
+ * public/v2/data.js:
+ (TimeSeries.prototype.findPointAfterTime): Added.
+
+ * public/v2/index.html: Added a new tbody for the status text and the selected value. Also fixed
+ the bug that we were not showing the old value's unit.
+
+ * public/v2/interactive-chart.js:
+ (App.InteractiveChartComponent._constructGraphIfPossible): Use chartData.formatter. Also cleaned up
+ the code to show the baseline and the target lines.
+
+ * public/v2/manifest.js:
+ (App.Manifest.fetchRunsWithPlatformAndMetric): Added smallerIsBetter.
+
+2015-02-05 Ryosuke Niwa <rniwa@webkit.org>
+
Unreviewed build fix.
* public/v2/app.js:
CommitLogs.fetchForTimeRange(repositoryId, null, null, keyword).then(function (commits) {
if (self.isDestroyed || !self.get('chartData') || !commits.length)
return;
- var currentRuns = self.get('chartData').current.timeSeriesByCommitTime().series();
+ var currentRuns = self.get('chartData').current.series();
if (!currentRuns.length)
return;
App.Manifest.fetchRunsWithPlatformAndMetric(this.get('store'), platformId, metricId).then(function (result) {
self.set('platform', result.platform);
self.set('metric', result.metric);
- self.set('chartData', result.runs);
+ self.set('chartData', App.createChartData(result));
}, function (result) {
if (!result || typeof(result) === "string")
self.set('failure', 'Failed to fetch the JSON with an error: ' + result);
}
});
+App.createChartData = function (data)
+{
+ var runs = data.runs;
+ return {
+ current: runs.current.timeSeriesByCommitTime(),
+ baseline: runs.baseline ? runs.baseline.timeSeriesByCommitTime() : null,
+ target: runs.target ? runs.target.timeSeriesByCommitTime() : null,
+ unit: data.unit,
+ formatter: data.useSI ? d3.format('.4s') : d3.format('.4g'),
+ smallerIsBetter: data.smallerIsBetter,
+ };
+}
+
App.encodePrettifiedJSON = function (plain)
{
function numberIfPossible(string) {
buildURL = builder.urlFromBuildNumber(buildNumber);
}
+ var chartData = this.get('chartData');
this.set('details', Ember.Object.create({
- currentValue: currentMeasurement.mean().toFixed(2),
- oldValue: oldMeasurement && selectedPoints ? oldMeasurement.mean().toFixed(2) : null,
+ status: this._computeStatus(currentPoint),
+ currentValue: chartData.formatter(currentMeasurement.mean()),
+ oldValue: oldMeasurement && selectedPoints ? chartData.formatter(oldMeasurement.mean()) : null,
buildNumber: buildNumber,
buildURL: buildURL,
buildTime: currentMeasurement.formattedBuildTime(),
var points = this.get('selectedPoints');
this.set('cannotAnalyze', !this.get('newAnalysisTaskName') || !points || points.length < 2);
}.observes('newAnalysisTaskName'),
+ _computeStatus: function (currentPoint)
+ {
+ var chartData = this.get('chartData');
+
+ var diffFromBaseline = this._relativeDifferentToLaterPointInTimeSeries(currentPoint, chartData.baseline);
+ var diffFromTarget = this._relativeDifferentToLaterPointInTimeSeries(currentPoint, chartData.target);
+
+ var label = '';
+ var className = '';
+ var formatter = d3.format('.3p');
+
+ var smallerIsBetter = chartData.smallerIsBetter;
+ if (diffFromBaseline !== undefined && diffFromBaseline > 0 == smallerIsBetter) {
+ label = formatter(Math.abs(diffFromBaseline)) + ' ' + (smallerIsBetter ? 'above' : 'below') + ' baseline';
+ className = 'worse';
+ } else if (diffFromTarget !== undefined && diffFromTarget < 0 == smallerIsBetter) {
+ label = formatter(Math.abs(diffFromTarget)) + ' ' + (smallerIsBetter ? 'below' : 'above') + ' target';
+ className = 'better';
+ } else if (diffFromTarget !== undefined)
+ label = formatter(Math.abs(diffFromTarget)) + ' until target';
+
+ return {className: className, label: label};
+ },
+ _relativeDifferentToLaterPointInTimeSeries: function (currentPoint, timeSeries)
+ {
+ if (!currentPoint || !timeSeries)
+ return undefined;
+
+ var referencePoint = timeSeries.findPointAfterTime(currentPoint.time);
+ if (!referencePoint)
+ return undefined;
+
+ return (currentPoint.value - referencePoint.value) / referencePoint.value;
+ }
});
highlightedItems[start.measurement.id()] = true;
highlightedItems[end.measurement.id()] = true;
+ var chartData = App.createChartData(data);
var formatedPoints = currentTimeSeries.seriesBetweenPoints(start, end).map(function (point, index) {
return {
id: point.measurement.id(),
measurement: point.measurement,
label: 'Point ' + (index + 1),
- value: point.value + (runs.unit ? ' ' + runs.unit : ''),
+ value: chartData.formatter(point.value) + (data.unit ? ' ' + data.unit : ''),
};
});
var margin = (end.time - start.time) * 0.1;
- this.set('chartData', runs);
+ this.set('chartData', chartData);
this.set('chartDomain', [start.time - margin, +end.time + margin]);
this.set('highlightedItems', highlightedItems);
this.set('analysisPoints', formatedPoints);
.chart-pane .details-table td table td {
word-break: break-word;
border-top: solid 1px #ccc;
+ border-bottom: solid 1px #ccc;
padding: 0.2rem;
}
content: " : ";
}
+.chart-pane .details-table .status th {
+ visibility: hidden;
+}
+
+.chart-pane .details-table .status td {
+ font-size: 1rem;
+}
+
.chart-pane .chart {
height: 100%;
}
stroke-width: 1.5px;
}
-.chart .commit-time-line {
- stroke: #999;
-}
-
.chart .dot {
fill: #666;
stroke: none;
opacity: 0.8;
}
-.chart path.area.baseline {
+.chart path.current {
+ stroke: #999;
+}
+
+.chart path.baseline {
stroke: #f66;
- fill: #fdd;
- opacity: 0.4;
+}
+.chart-pane .status .worse {
+ color: #c33;
}
-.chart path.area.target {
+.chart path.target {
stroke: #66f;
- fill: #ddf;
- opacity: 0.4;
+}
+.chart-pane .status .better {
+ color: #33c;
}
.chart .axis,
return this._series.find(function (point) { return point.measurement.id() == measurementId; });
}
+TimeSeries.prototype.findPointAfterTime = function (time)
+{
+ return this._series.find(function (point) { return point.time >= time; });
+}
+
TimeSeries.prototype.seriesBetweenPoints = function (startPoint, endPoint)
{
if (!startPoint.seriesIndex || !endPoint.seriesIndex)
{{/if}}
{{/each}}
</tbody>
- <tr><th>Current</th><td>{{details.currentValue}} {{chartData.unit}}
- {{#if details.oldValue}}
- (from {{details.oldValue}})
- {{/if}}</td></tr>
+ <tbody class="status">
+ <tr>
+ <th>Current</th>
+ <td>
+ {{details.currentValue}} {{chartData.unit}}
+ {{#if details.oldValue}}
+ (from {{details.oldValue}} {{chartData.unit}})
+ {{/if}}
+ {{#if details.status.label}}
+ <br>
+ <span {{bind-attr class=details.status.className}}>{{details.status.label}}</span>
+ {{/if}}
+ </td>
+ </tr>
+ </tbody>
{{#if details.buildNumber}}
<tr>
<th>Build</th>
}
if (this.get('showYAxis')) {
- this._yAxis = d3.svg.axis().scale(this._y).orient("left").ticks(6).tickFormat(
- chartData.useSI ? d3.format("s") : d3.format(".3g"));
+ this._yAxis = d3.svg.axis().scale(this._y).orient("left").ticks(6).tickFormat(chartData.formatter);
this._yAxisLabels = svg.append("g")
.attr("class", "y axis");
}
this._highlights.remove();
this._highlights = null;
- this._currentTimeSeries = chartData.current.timeSeriesByCommitTime();
+ this._currentTimeSeries = chartData.current;
this._currentTimeSeriesData = this._currentTimeSeries.series();
- this._baselineTimeSeries = chartData.baseline ? chartData.baseline.timeSeriesByCommitTime() : null;
- this._targetTimeSeries = chartData.target ? chartData.target.timeSeriesByCommitTime() : null;
+ this._baselineTimeSeries = chartData.baseline;
+ this._targetTimeSeries = chartData.target;
this._yAxisUnit = chartData.unit;
- var minMax = this._minMaxForAllTimeSeries();
- var smallEnoughValue = minMax[0] - (minMax[1] - minMax[0]) * 10;
- var largeEnoughValue = minMax[1] + (minMax[1] - minMax[0]) * 10;
-
- // FIXME: Flip the sides based on smallerIsBetter-ness.
if (this._baselineTimeSeries) {
- var data = this._baselineTimeSeries.series();
- this._areas.push(this._clippedContainer
+ this._paths.push(this._clippedContainer
.append("path")
- .datum(data.map(function (point) { return {time: point.time, value: point.value, interval: point.interval ? point.interval : [point.value, largeEnoughValue]}; }))
- .attr("class", "area baseline"));
+ .datum(this._baselineTimeSeries.series())
+ .attr("class", "baseline"));
}
if (this._targetTimeSeries) {
- var data = this._targetTimeSeries.series();
- this._areas.push(this._clippedContainer
+ this._paths.push(this._clippedContainer
.append("path")
- .datum(data.map(function (point) { return {time: point.time, value: point.value, interval: point.interval ? point.interval : [smallEnoughValue, point.value]}; }))
- .attr("class", "area target"));
+ .datum(this._targetTimeSeries.series())
+ .attr("class", "target"));
}
this._areas.push(this._clippedContainer
this._paths.push(this._clippedContainer
.append("path")
.datum(this._currentTimeSeriesData)
- .attr("class", "commit-time-line"));
+ .attr("class", "current"));
this._dots.push(this._clippedContainer
.selectAll(".dot")
'Heap': 'bytes',
'Allocations': 'bytes'
}[suffix];
+ var smallerIsBetter = unit != 'fps' && unit != '/s'; // Assume smaller is better for unit-less metrics.
- // FIXME: Include this information in JSON and process it in RunsData.fetchRuns
- runs.unit = unit;
- runs.useSI = unit == 'bytes';
-
- return {platform: platform, metric: metric, runs: runs};
+ return {platform: platform, metric: metric, runs: runs, unit: unit, useSI: unit == 'bytes', smallerIsBetter: smallerIsBetter};
});
},
}).create();