Address Said's comments on the benchmark, and do some clean up.
[WebKit-https.git] / PerformanceTests / Animometer / resources / debug-runner / graph.js
1 Utilities.extendObject(window.benchmarkController, {
2     layoutCounter: 0,
3
4     updateGraphData: function(graphData)
5     {
6         var element = document.getElementById("test-graph-data");
7         element.innerHTML = "";
8         element.graphData = graphData;
9         document.querySelector("hr").style.width = this.layoutCounter++ + "px";
10
11         var margins = new Insets(30, 30, 30, 40);
12         var size = Point.elementClientSize(element).subtract(margins.size);
13
14         this.createTimeGraph(graphData, margins, size);
15         this.onTimeGraphOptionsChanged();
16
17         this.onGraphTypeChanged();
18     },
19
20     _addRegressionLine: function(parent, xScale, yScale, points, stdev, isAlongYAxis)
21     {
22         var polygon = [];
23         var line = []
24         var xStdev = isAlongYAxis ? stdev : 0;
25         var yStdev = isAlongYAxis ? 0 : stdev;
26         for (var i = 0; i < points.length; ++i) {
27             var point = points[i];
28             polygon.push(xScale(point[0] + xStdev), yScale(point[1] + yStdev));
29             line.push(xScale(point[0]), yScale(point[1]));
30         }
31         for (var i = points.length - 1; i >= 0; --i) {
32             var point = points[i];
33             polygon.push(xScale(point[0] - xStdev), yScale(point[1] - yStdev));
34         }
35         parent.append("polygon")
36             .attr("points", polygon.join(","));
37         parent.append("line")
38             .attr("x1", line[0])
39             .attr("y1", line[1])
40             .attr("x2", line[2])
41             .attr("y2", line[3]);
42     },
43
44     createTimeGraph: function(graphData, margins, size)
45     {
46         var svg = d3.select("#test-graph-data").append("svg")
47             .attr("id", "time-graph")
48             .attr("width", size.width + margins.left + margins.right)
49             .attr("height", size.height + margins.top + margins.bottom)
50             .append("g")
51                 .attr("transform", "translate(" + margins.left + "," + margins.top + ")");
52
53         var axes = graphData.axes;
54         var targetFrameLength = graphData.targetFrameLength;
55
56         // Axis scales
57         var x = d3.scale.linear()
58                 .range([0, size.width])
59                 .domain([
60                     Math.min(d3.min(graphData.samples, function(s) { return s.time; }), 0),
61                     d3.max(graphData.samples, function(s) { return s.time; })]);
62         var complexityMax = d3.max(graphData.samples, function(s) { return s.complexity; });
63
64         var yLeft = d3.scale.linear()
65                 .range([size.height, 0])
66                 .domain([0, complexityMax]);
67         var yRight = d3.scale.linear()
68                 .range([size.height, 0])
69                 .domain([1000/20, 1000/60]);
70
71         // Axes
72         var xAxis = d3.svg.axis()
73                 .scale(x)
74                 .orient("bottom")
75                 .tickFormat(function(d) { return (d/1000).toFixed(0); });
76         var yAxisLeft = d3.svg.axis()
77                 .scale(yLeft)
78                 .orient("left");
79         var yAxisRight = d3.svg.axis()
80                 .scale(yRight)
81                 .tickValues([1000/20, 1000/25, 1000/30, 1000/35, 1000/40, 1000/45, 1000/50, 1000/55, 1000/60])
82                 .tickFormat(function(d) { return (1000/d).toFixed(0); })
83                 .orient("right");
84
85         // x-axis
86         svg.append("g")
87             .attr("class", "x axis")
88             .attr("fill", "rgb(235, 235, 235)")
89             .attr("transform", "translate(0," + size.height + ")")
90             .call(xAxis)
91             .append("text")
92                 .attr("class", "label")
93                 .attr("x", size.width)
94                 .attr("y", -6)
95                 .attr("fill", "rgb(235, 235, 235)")
96                 .style("text-anchor", "end")
97                 .text("time");
98
99         // yLeft-axis
100         svg.append("g")
101             .attr("class", "y axis")
102             .attr("fill", "#7ADD49")
103             .call(yAxisLeft)
104             .append("text")
105                 .attr("class", "label")
106                 .attr("transform", "rotate(-90)")
107                 .attr("y", 6)
108                 .attr("fill", "#7ADD49")
109                 .attr("dy", ".71em")
110                 .style("text-anchor", "end")
111                 .text(axes[0]);
112
113         // yRight-axis
114         svg.append("g")
115             .attr("class", "y axis")
116             .attr("fill", "#FA4925")
117             .attr("transform", "translate(" + size.width + ", 0)")
118             .call(yAxisRight)
119             .append("text")
120                 .attr("class", "label")
121                 .attr("transform", "rotate(-90)")
122                 .attr("y", 6)
123                 .attr("fill", "#FA4925")
124                 .attr("dy", ".71em")
125                 .style("text-anchor", "end")
126                 .text(axes[1]);
127
128         // marks
129         var yMin = yLeft(0);
130         var yMax = yLeft(yAxisLeft.scale().domain()[1]);
131         for (var markName in graphData.marks) {
132             var mark = graphData.marks[markName];
133             var xLocation = x(mark.time);
134
135             var markerGroup = svg.append("g")
136                 .attr("class", "marker")
137                 .attr("transform", "translate(" + xLocation + ", 0)");
138             markerGroup.append("text")
139                 .attr("transform", "translate(10, " + (yMin - 10) + ") rotate(-90)")
140                 .style("text-anchor", "start")
141                 .text(markName)
142             markerGroup.append("line")
143                 .attr("x1", 0)
144                 .attr("x2", 0)
145                 .attr("y1", yMin)
146                 .attr("y2", yMax);
147         }
148
149         if (Strings.json.experiments.complexity in graphData.averages) {
150             var complexity = graphData.averages[Strings.json.experiments.complexity];
151             var regression = svg.append("g")
152                 .attr("class", "complexity mean");
153             this._addRegressionLine(regression, x, yLeft, [[graphData.samples[0].time, complexity.average], [graphData.samples[graphData.samples.length - 1].time, complexity.average]], complexity.stdev);
154         }
155         if (Strings.json.experiments.frameRate in graphData.averages) {
156             var frameRate = graphData.averages[Strings.json.experiments.frameRate];
157             var regression = svg.append("g")
158                 .attr("class", "fps mean");
159             this._addRegressionLine(regression, x, yRight, [[graphData.samples[0].time, 1000/frameRate.average], [graphData.samples[graphData.samples.length - 1].time, 1000/frameRate.average]], frameRate.stdev);
160         }
161
162         // right-target
163         if (targetFrameLength) {
164             svg.append("line")
165                 .attr("x1", x(0))
166                 .attr("x2", size.width)
167                 .attr("y1", yRight(targetFrameLength))
168                 .attr("y2", yRight(targetFrameLength))
169                 .attr("class", "target-fps marker");
170         }
171
172         // Cursor
173         var cursorGroup = svg.append("g").attr("class", "cursor");
174         cursorGroup.append("line")
175             .attr("x1", 0)
176             .attr("x2", 0)
177             .attr("y1", yMin)
178             .attr("y2", yMin);
179
180         // Data
181         var allData = graphData.samples;
182         var filteredData = graphData.samples.filter(function (sample) {
183             return "smoothedFrameLength" in sample;
184         });
185
186         function addData(name, data, yCoordinateCallback, pointRadius, omitLine) {
187             var svgGroup = svg.append("g").attr("id", name);
188             if (!omitLine) {
189                 svgGroup.append("path")
190                     .datum(data)
191                     .attr("d", d3.svg.line()
192                         .x(function(d) { return x(d.time); })
193                         .y(yCoordinateCallback));
194             }
195             svgGroup.selectAll("circle")
196                 .data(data)
197                 .enter()
198                 .append("circle")
199                 .attr("cx", function(d) { return x(d.time); })
200                 .attr("cy", yCoordinateCallback)
201                 .attr("r", pointRadius);
202
203             cursorGroup.append("circle")
204                 .attr("class", name)
205                 .attr("r", pointRadius + 2);
206         }
207
208         addData("complexity", allData, function(d) { return yLeft(d.complexity); }, 2);
209         addData("rawFPS", allData, function(d) { return yRight(d.frameLength); }, 1);
210         addData("filteredFPS", filteredData, function(d) { return yRight(d.smoothedFrameLength); }, 2);
211
212         // Area to handle mouse events
213         var area = svg.append("rect")
214             .attr("fill", "transparent")
215             .attr("x", 0)
216             .attr("y", 0)
217             .attr("width", size.width)
218             .attr("height", size.height);
219
220         var timeBisect = d3.bisector(function(d) { return d.time; }).right;
221         var statsToHighlight = ["complexity", "rawFPS", "filteredFPS"];
222         area.on("mouseover", function() {
223             document.querySelector("#time-graph .cursor").classList.remove("hidden");
224             document.querySelector("#test-graph nav").classList.remove("hide-data");
225         }).on("mouseout", function() {
226             document.querySelector("#time-graph .cursor").classList.add("hidden");
227             document.querySelector("#test-graph nav").classList.add("hide-data");
228         }).on("mousemove", function() {
229             var form = document.forms["time-graph-options"].elements;
230
231             var mx_domain = x.invert(d3.mouse(this)[0]);
232             var index = Math.min(timeBisect(allData, mx_domain), allData.length - 1);
233             var data = allData[index];
234             var cursor_x = x(data.time);
235             var cursor_y = yAxisRight.scale().domain()[1];
236             var ys = [yRight(yAxisRight.scale().domain()[0]), yRight(yAxisRight.scale().domain()[1])];
237
238             document.querySelector("#test-graph nav .time").textContent = (data.time / 1000).toFixed(4) + "s (" + index + ")";
239             statsToHighlight.forEach(function(name) {
240                 var element = document.querySelector("#test-graph nav ." + name);
241                 var content = "";
242                 var data_y = null;
243                 switch (name) {
244                 case "complexity":
245                     content = data.complexity;
246                     data_y = yLeft(data.complexity);
247                     break;
248                 case "rawFPS":
249                     content = (1000/data.frameLength).toFixed(2);
250                     data_y = yRight(data.frameLength);
251                     break;
252                 case "filteredFPS":
253                     if ("smoothedFrameLength" in data) {
254                         content = (1000/data.smoothedFrameLength).toFixed(2);
255                         data_y = yRight(data.smoothedFrameLength);
256                     }
257                     break;
258                 }
259
260                 element.textContent = content;
261
262                 if (form[name].checked && data_y !== null) {
263                     ys.push(data_y);
264                     cursorGroup.select("." + name)
265                         .attr("cx", cursor_x)
266                         .attr("cy", data_y);
267                     document.querySelector("#time-graph .cursor ." + name).classList.remove("hidden");
268                 } else
269                     document.querySelector("#time-graph .cursor ." + name).classList.add("hidden");
270             });
271
272             if (form["rawFPS"].checked)
273                 cursor_y = Math.max(cursor_y, data.frameLength);
274             cursorGroup.select("line")
275                 .attr("x1", cursor_x)
276                 .attr("x2", cursor_x)
277                 .attr("y1", Math.min.apply(null, ys))
278                 .attr("y2", Math.max.apply(null, ys));
279
280         });
281     },
282
283     _showOrHideNodes: function(isShown, selector) {
284         var nodeList = document.querySelectorAll(selector);
285         if (isShown) {
286             for (var i = 0; i < nodeList.length; ++i)
287                 nodeList[i].classList.remove("hidden");
288         } else {
289             for (var i = 0; i < nodeList.length; ++i)
290                 nodeList[i].classList.add("hidden");
291         }
292     },
293
294     onTimeGraphOptionsChanged: function() {
295         var form = document.forms["time-graph-options"].elements;
296         benchmarkController._showOrHideNodes(form["markers"].checked, ".marker");
297         benchmarkController._showOrHideNodes(form["averages"].checked, "#test-graph-data .mean");
298         benchmarkController._showOrHideNodes(form["complexity"].checked, "#complexity");
299         benchmarkController._showOrHideNodes(form["rawFPS"].checked, "#rawFPS");
300         benchmarkController._showOrHideNodes(form["filteredFPS"].checked, "#filteredFPS");
301     },
302
303     onGraphTypeChanged: function() {
304         var graphData = document.getElementById("test-graph-data").graphData;
305         var isTimeSelected = true;
306
307         benchmarkController._showOrHideNodes(isTimeSelected, "#time-graph");
308         benchmarkController._showOrHideNodes(isTimeSelected, "form[name=time-graph-options]");
309
310         var score, mean;
311         if (isTimeSelected) {
312             score = graphData.score.toFixed(2);
313
314             var regression = graphData.averages.complexity;
315             mean = [
316                 "mean: ",
317                 regression.average.toFixed(2),
318                 " ± ",
319                 regression.stdev.toFixed(2),
320                 " (",
321                 regression.percent.toFixed(2),
322                 "%)"];
323             if (regression.concern) {
324                 mean = mean.concat([
325                     ", worst 5%: ",
326                     regression.concern.toFixed(2)]);
327             }
328             mean = mean.join("");
329         }
330
331         sectionsManager.setSectionScore("test-graph", score, mean);
332     }
333 });