Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(160)

Side by Side Diff: perf/dashboard/ui/js/graph.js

Issue 1654813003: Remove old dead perf dashboard pages and js (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « perf/dashboard/ui/js/coordinates.js ('k') | perf/dashboard/ui/js/plotter.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 Use of this source code is governed by a BSD-style license that can be
4 found in the LICENSE file.
5 */
6
7 /*
8 Fetch all graph data files, prepare the data, and create Plotter() to
9 generate a graph.
10
11 To use:
12 var graph = new Graph('output_div', graphList)
13 graph.setTitle('Title');
14 graph.graph();
15 */
16
17 function JsonToJs(data) {
18 return eval('(' + data + ')');
19 }
20
21 /**
22 * Insert element a after element b.
23 */
24 function AppendChild(a, b) {
25 var elementA = (typeof(a) == 'object') ? a : document.getElementById(a);
26 var elementB = (typeof(b) == 'object') ? b : document.getElementById(b);
27 elementB.appendChild(elementA);
28 }
29
30 /**
31 * Insert element a before element b.
32 */
33 function InsertBefore(a, b) {
34 var elementA = (typeof(a) == 'object') ? a : document.getElementById(a);
35 var elementB = (typeof(b) == 'object') ? b : document.getElementById(b);
36 elementB.insertBefore(elementA);
37 }
38
39
40 /**
41 * Graph class.
42 * @constructor
43 *
44 * Fetch each graph in |graphList| and create Plotter(). Create Graph()
45 * and call graph() to display graph.
46 *
47 * @param div {String|DOMElement} The container that the graph should be
48 * rendered to.
49 * @param graphList {Array} List of graphs to be plotted.
50 * @param options {Object} Options to configure graph.
51 * - width {int} Width of graph.
52 * - height {int} Height of graph.
53 * - history {int} Number of row to show.
54 * - showDetail {Boolean} Specifies whether or not to display detail.
55 * Default false.
56 * - showTabs {Boolean} Specifies whether or not to show tabs.
57 * Default false.
58 * - enableMouseScroll {Boolean} Specifies whether or not to enable
59 * mouse wheel zooming. Default false.
60 * - channels {Array} Display graph by channels.
61 * - orderDataByVersion {Boolean} Order plot data by version number.
62 * Default false.
63 *
64 * Example of the graphList:
65 * [
66 * {"units": "ms", "important": false, "name": "SunSpider-individual",
67 * "SunSpider-individual-summary.dat"},
68 * ]
69 */
70 function Graph(div, graphList, opt_options) {
71 this.graphList_ = graphList;
72 this.options_ = (opt_options) ? opt_options : {};
73 this.history_ = (this.options_.history) ? this.options_.history : 150;
74 this.rev_ = (this.options_.rev) ? this.options_.rev : -1;
75 this.channels_ = (this.options_.channels) ? this.options_.channels : [];
76 this.firstTrace_ = '';
77 this.rows_ = [];
78 this.isDetailViewAdded_ = false;
79 this.selectedGraph_ = null;
80 this.plotterDiv_ = null;
81 this.tabs_ = [];
82 this.width = this.options_.width;
83 this.height = this.options_.height;
84
85 this.graphContainer = document.createElement('div');
86 this.graphContainer.setAttribute(
87 'style', 'display: block; overflow: hidden; ' +
88 'margin: 5px; padding: 5px; width:' + this.width);
89 AppendChild(this.graphContainer, div);
90
91 this.title = document.createElement('div');
92 this.title.setAttribute('style', 'text-align: center');
93 AppendChild(this.title, this.graphContainer);
94 }
95
96 /**
97 * Start fetching graph data.
98 */
99 Graph.prototype.graph = function() {
100 this.fetchSummary_();
101
102 if (this.options_.showTabs)
103 this.addTabs_();
104 }
105
106 /**
107 * Set graph title.
108 */
109 Graph.prototype.setTitle = function(title) {
110 this.title.innerHTML = title;
111 }
112
113 /**
114 * Display tabs for each graph.
115 */
116 Graph.prototype.addTabs_ = function() {
117 this.tabs_ = [];
118 var tabPane = document.createElement('div');
119 tabPane.setAttribute('class', 'switcher');
120 AppendChild(tabPane, this.graphContainer);
121
122 var graphNames = []
123 var inserted = {};
124 for (var i = 0; i < this.graphList_.length; i++) {
125 if (!inserted[this.graphList_[i].name]) {
126 graphNames.push(this.graphList_[i].name);
127 inserted[this.graphList_[i].name] = 1;
128 }
129 }
130
131 var obj = this;
132 for (var i = 0; i < graphNames.length; i++) {
133 var name = graphNames[i];
134 var tab = document.createElement('span');
135 if (name != this.selectedGraph_.name) {
136 tab.setAttribute('class', 'select');
137 }
138 tab.addEventListener(
139 "click",
140 (function(){
141 var cur = name; return function() {obj.switchGraph_(cur)}
142 })(),
143 false);
144 tab.appendChild(document.createTextNode(name + " "));
145 AppendChild(tab, tabPane);
146 this.tabs_.push(tab);
147 }
148 }
149
150 /**
151 * Fetch graph summary data files.
152 */
153 Graph.prototype.fetchSummary_ = function() {
154 this.rows_ = [];
155 if (!this.selectedGraph_) {
156 this.selectedGraph_ = this.graphList_[0];
157 }
158 var graphFiles = [];
159 this.selectedGraphList_ = [];
160 for (var i = 0; i < this.graphList_.length; i++) {
161 if (this.graphList_[i].name == this.selectedGraph_.name) {
162 graphFiles.push(this.graphList_[i].loc);
163 this.selectedGraphList_.push(this.graphList_[i]);
164 }
165 }
166 var obj = this;
167 new FetchList(graphFiles, function(data) {obj.onSummaryReceived_(data)});
168 }
169
170 /**
171 * Call addPlot_ once all graph summary data are received.
172 */
173 Graph.prototype.onSummaryReceived_ = function(data) {
174 // Parse the summary data file.
175 for (var i = 0; i < data.length; i++) {
176 if (data[i]) {
177 var rows = new Rows(data[i]);
178 this.rows_[i] = rows;
179 }
180 }
181 this.addPlot_();
182 }
183
184 /**
185 * Merge all data rows by channel and version. This is use in platform
186 * comparison graph.
187 *
188 * Example:
189 * Two rows:
190 * {"traces": {"score": ["777", "0.0"]}, "rev": "9",
191 * "ver": "17.1.963.19", "chan": "stable"}
192 * {"traces": {"score": ["888", "0.0"]}, "rev": "10",
193 * "ver": "17.1.963.19", "chan": "stable"}
194 * Become:
195 * {"traces": {"score_windows": ["777", "0.0"],
196 * "score_linux": ["888", "0.0"]},
197 * "rev": "9", "ver": "17.1.963.19", "chan": "stable"}
198 *
199 * @return {Array} Array of rows.
200 */
201 Graph.prototype.getMergedRowsByVersion_ = function() {
202 var channels = {};
203 for (var i = 0; i < this.channels_.length; i++)
204 channels[this.channels_[i]] = 1;
205 var allRows = [];
206 // Combind all rows to one list.
207 for (var i = 0; i < this.rows_.length; i++) {
208 if (this.rows_[i]) {
209 for (var j = 0; j < this.rows_[i].length; j++) {
210 var row = this.rows_[i].get(j);
211 if (row && row.chan in channels) {
212 row.machine = this.selectedGraphList_[i].machine;
213 allRows.push(row);
214 }
215 }
216 }
217 }
218
219 // Sort by version number.
220 allRows.sort(
221 function(a, b) {
222 var a_arr = a.version.split('.');
223 var b_arr = b.version.split('.');
224 var len = Math.min(b_arr.length, b_arr.length);
225 for (var i = 0; i < len; i++) {
226 if (parseInt(a_arr[i], 10) > parseInt(b_arr[i], 10))
227 return 1;
228 else if (parseInt(a_arr[i], 10) < parseInt(b_arr[i], 10))
229 return -1;
230 }
231 return a_arr.length - b_arr.length;
232 });
233
234 // Merge all rows by version number.
235 var combindedRows = [];
236 var index = 0;
237 while (index < allRows.length) {
238 var currentRow = allRows[index];
239 var traces = currentRow['traces'];
240 for (var traceName in traces) {
241 var traceRenamed = traceName + '_' + currentRow.machine.toLowerCase();
242 traces[traceRenamed] = traces[traceName];
243 delete(traces[traceName]);
244 }
245 while (index < allRows.length - 1 &&
246 allRows[index + 1].version == currentRow.version) {
247 var row = allRows[index + 1];
248 var traces = row['traces'];
249 for (var traceName in traces) {
250 var traceRenamed = traceName + '_' + row.machine.toLowerCase();
251 currentRow['traces'][traceRenamed] = traces[traceName];
252 }
253 index++;
254 }
255 combindedRows.push(currentRow);
256 index++;
257 }
258 return combindedRows;
259 }
260
261 /**
262 * Merge all channel data by their index in file. This is use in channel
263 * comparison graph.
264 *
265 * @return {Array} Array of rows.
266 */
267 Graph.prototype.getMergedRowByIndex_ = function() {
268 var rowByChannel = {};
269 for (var i = 0; i < this.channels_.length; i++)
270 rowByChannel[this.channels_[i]] = [];
271
272 // Order by channel.
273 for (var i = 0; i < this.rows_.length; i++) {
274 if (this.rows_[i]) {
275 for (var j = 0; j < this.rows_[i].length; j++) {
276 var row = this.rows_[i].get(j);
277 if (row && row.chan in rowByChannel) {
278 rowByChannel[row.chan].push(row);
279 }
280 }
281 }
282 }
283
284 var max = 0;
285 for (var channel in rowByChannel)
286 max = Math.max(rowByChannel[channel].length, max);
287
288 // Merge data.
289 var combindedRows = [];
290 for (var i = 0; i < max; i++) {
291 var currentRow = null;
292 for (var channel in rowByChannel) {
293 if (rowByChannel[channel].length > i) {
294 var row = rowByChannel[channel][i];
295 var traces = row['traces'];
296 for (var traceName in traces) {
297 traces[traceName + '_' + channel] = traces[traceName];
298 delete(traces[traceName]);
299 }
300 if (!currentRow) {
301 currentRow = row;
302 } else {
303 for (var traceName in traces)
304 currentRow['traces'][traceName] = traces[traceName];
305 currentRow.version += ', ' + row.version;
306 }
307 }
308 }
309 combindedRows.push(currentRow);
310 }
311 return combindedRows;
312 }
313
314 /**
315 * Get rows for a specific channel.
316 *
317 * @return {Array} Array of rows.
318 */
319 Graph.prototype.getRowByChannel_ = function() {
320 // Combind channel data.
321 var rows = [];
322 for (var i = 0; i < this.rows_.length; i++) {
323 if (this.rows_[i]) {
324 for (var j = 0; j < this.rows_[i].length; j++) {
325 var row = this.rows_[i].get(j);
326 if (row && row.chan == this.channels_[0])
327 rows.push(row);
328 }
329 }
330 }
331 return rows;
332 }
333
334 /**
335 * Prepare the data and create Plotter() to generate a graph.
336 */
337 Graph.prototype.addPlot_ = function() {
338 var rows = [];
339 if (this.options_.orderDataByVersion)
340 rows = this.getMergedRowsByVersion_();
341 else if (this.channels_.length > 1)
342 rows = this.getMergedRowByIndex_();
343 else
344 rows = this.getRowByChannel_();
345
346 var maxRows = rows.length;
347 if (maxRows > this.history_)
348 maxRows = this.history_;
349
350 // Find the start and end of the data slice we will focus on.
351 var startRow = 0;
352 if (this.rev_ > 0) {
353 var i = 0;
354 while (i < rows.length) {
355 var row = rows[i];
356 // If the current row's revision is higher than the desired revision,
357 // continue searching.
358 if (row.revision > this.rev_) {
359 i++;
360 continue;
361 }
362 // We're either just under or at the desired revision.
363 startRow = i;
364 // If the desired revision does not exist, use the row before it.
365 if (row.revision < this.rev_ && startRow > 0)
366 startRow -= 1;
367 break;
368 }
369 }
370
371 // Some summary files contain data not listed in rev-descending order. For
372 // those cases, it is possible we will find a start row in the middle of the
373 // data whose neighboring data is not nearby. See xp-release-dual-core
374 // moz rev 265 => no graph.
375 var endRow = startRow + maxRows;
376
377 // Build and order a list of revision numbers.
378 var allTraces = {};
379 var revisionNumbers = [];
380 var versionMap = {};
381 var hasNumericRevisions = true;
382 // graphData[rev] = {trace1:[value, stddev], trace2:[value, stddev], ...}
383 var graphData = {};
384 for (var i = startRow; i < endRow; ++i) {
385 var row = rows[i];
386 if (!row)
387 continue;
388 var traces = row['traces'];
389 for (var j = 0; j < traces.length; ++j)
390 traces[j] = parseFloat(traces[j]);
391
392 graphData[row.revision] = traces;
393 if (isNaN(row.revision)) {
394 hasNumericRevisions = false;
395 }
396 revisionNumbers.push(row.revision);
397
398 versionMap[row.revision] = row.version;
399
400 // Collect unique trace names. If traces are explicitly specified in
401 // params, delete unspecified trace data.
402 for (var traceName in traces) {
403 if (typeof(params['trace']) != 'undefined' &&
404 params['trace'][traceName] != 1) {
405 delete(traces[traceName]);
406 }
407 allTraces[traceName] = 1;
408 }
409 }
410
411 // Build a list of all the trace names we've seen, in the order in which
412 // they appear in the data file. Although JS objects are not required by
413 // the spec to iterate their properties in order, in practice they do,
414 // because it causes compatibility problems otherwise.
415 var traceNames = [];
416 for (var traceName in allTraces)
417 traceNames.push(traceName);
418 this.firstTrace_ = traceNames[0];
419
420 // If the revisions are numeric (svn), sort them numerically to ensure they
421 // are in ascending order. Otherwise, if the revisions aren't numeric (git),
422 // reverse them under the assumption the rows were prepended to the file.
423 if (hasNumericRevisions) {
424 revisionNumbers.sort(
425 function(a, b) { return parseInt(a, 10) - parseInt(b, 10) });
426 } else {
427 revisionNumbers.reverse();
428 }
429
430 // Build separate ordered lists of trace data.
431 var traceData = {};
432 var versionList = [];
433 for (var revIndex = 0; revIndex < revisionNumbers.length; ++revIndex) {
434 var rev = revisionNumbers[revIndex];
435 var revisionData = graphData[rev];
436 for (var nameIndex = 0; nameIndex < traceNames.length; ++nameIndex) {
437 var traceName = traceNames[nameIndex];
438 if (!traceData[traceName])
439 traceData[traceName] = [];
440 if (!revisionData[traceName])
441 traceData[traceName].push([NaN, NaN]);
442 else
443 traceData[traceName].push([parseFloat(revisionData[traceName][0]),
444 parseFloat(revisionData[traceName][1])]);
445 }
446 versionList.push(versionMap[rev]);
447 }
448
449 var plotData = [];
450 for (var traceName in traceData)
451 plotData.push(traceData[traceName]);
452
453 var plotterDiv = document.createElement('div');
454 if (!this.plotterDiv_)
455 AppendChild(plotterDiv, this.graphContainer)
456 else
457 this.graphContainer.replaceChild(plotterDiv, this.plotterDiv_);
458 this.plotterDiv_ = plotterDiv;
459
460 var plotter = new Plotter(
461 versionList, plotData, traceNames, this.selectedGraph_.units,
462 this.plotterDiv_, this.width, this.height);
463
464 var obj = this;
465 plotter.onclick = function(){obj.onPlotClicked.apply(obj, arguments)};
466 plotter.enableMouseScroll = this.options_.enableMouseScroll;
467 plotter.plot();
468 }
469
470 /**
471 * Handle switching graph when tab is clicked.
472 */
473 Graph.prototype.switchGraph_ = function(graphName) {
474 if (graphName == this.selectedGraph_.name)
475 return;
476
477 for (var i = 0; i < this.tabs_.length; i++) {
478 var name = this.tabs_[i].innerHTML;
479 if (graphName + ' ' == name) {
480 this.tabs_[i].removeAttribute('class');
481 } else {
482 this.tabs_[i].setAttribute('class', 'select');
483 }
484 }
485
486 for (var i = 0; i < this.graphList_.length; i++) {
487 if (this.graphList_[i].name == graphName) {
488 this.selectedGraph_ = this.graphList_[i];
489 break;
490 }
491 }
492
493 this.fetchSummary_();
494 }
495
496 /**
497 * On plot clicked, display detail view.
498 */
499 Graph.prototype.onPlotClicked = function(prev_cl, cl) {
500 if (!this.options_.showDetail)
501 return;
502 this.addDetailView_();
503
504 var getChildByName = function(div, name) {
505 var children = div.childNodes;
506 for (var i = 0; i < children.length; i++)
507 if (children[i].getAttribute('name') == name)
508 return children[i];
509 }
510 // Define sources for detail tabs
511 if ('view-change' in Config.detailTabs) {
512 // If the changeLinkPrefix has {PREV_CL}/{CL} markers, replace them.
513 // Otherwise, append to the URL.
514 var url = Config.changeLinkPrefix;
515 if (url.indexOf('{PREV_CL}') >= 0 || url.indexOf('{CL}') >= 0) {
516 url = url.replace('{PREV_CL}', prev_cl);
517 url = url.replace('{CL}', cl);
518 } else {
519 url += prev_cl + ':' + cl;
520 }
521 getChildByName(this.detailPane, 'view-change').setAttribute('src', url);
522 }
523
524 if ('view-pages' in Config.detailTabs) {
525 getChildByName(this.detailPane, 'view-pages').
526 setAttribute('src', 'details.html?cl=' + cl +
527 '&graph=' + this.milestone + '-' + this.selectedGraph_.name +
528 '&trace=' + this.firstTrace_);
529 }
530 if ('view-coverage' in Config.detailTabs) {
531 getChildByName(this.detailPane, 'view-coverage').setAttribute(
532 'src',Config.coverageLinkPrefix + cl);
533 }
534
535 if (!this.didPositionDetail) {
536 this.positionDetails_();
537 this.didPositionDetail = true;
538 }
539 }
540
541 /**
542 * Display detail view.
543 */
544 Graph.prototype.addDetailView_ = function() {
545 if (this.isDetailViewAdded_)
546 return;
547 this.isDetailViewAdded_ = true;
548 // Add detail page.
549 this.detailPane = document.createElement('div');
550 AppendChild(this.detailPane, this.graphContainer);
551
552 for (var tab in Config.detailTabs) {
553 var detail = document.createElement('iframe');
554 detail.setAttribute('class', 'detail');
555 detail.setAttribute('name', tab);
556 AppendChild(detail, this.detailPane);
557 }
558
559 this.selectorPane = document.createElement('div');
560 this.selectorPane.setAttribute('class', 'selectors');
561 this.selectorPane.setAttribute(
562 'style', 'display: block; 1px solid black; position: absolute;');
563 AppendChild(this.selectorPane, this.graphContainer);
564
565 var firstTab = true;
566 for (var tab in Config.detailTabs) {
567 var selector = document.createElement('div');
568 selector.setAttribute('class', 'selector');
569 var obj = this;
570 selector.onclick = (
571 function(){
572 var cur = tab; return function() {obj.changeDetailTab(cur)}})();
573 if (firstTab)
574 firstTab = false;
575 else
576 selector.setAttribute('style', 'border-top: none');
577 selector.innerHTML = Config.detailTabs[tab];
578 AppendChild(selector, this.selectorPane);
579 }
580 }
581
582 Graph.prototype.positionDetails_ = function() {
583 var win_height = window.innerHeight;
584
585 var views_width = this.graphContainer.offsetWidth -
586 this.selectorPane.offsetWidth;
587
588 this.detailPane.style.width = views_width + "px";
589 this.detailPane.style.height = (
590 win_height - this.graphContainer.offsetHeight -
591 this.graphContainer.offsetTop - 30) + "px";
592
593 this.selectorPane.style.left = (
594 this.detailPane.offsetLeft + views_width + 1) + "px";
595 this.selectorPane.style.top = this.detailPane.offsetTop + "px";
596
597 // Change to the first detail tab
598 for (var tab in Config.detailTabs) {
599 this.changeDetailTab_(tab);
600 break;
601 }
602 }
603
604 Graph.prototype.changeDetailTab_ = function(target) {
605 var detailArr = this.detailPane.getElementsByTagName('iframe');
606 var i = 0;
607 for (var tab in Config.detailTabs) {
608 detailArr[i++].style.display = (tab == target ? 'block' : 'none');
609 }
610 }
611
612 Graph.prototype.goTo = function(graph) {
613 params.graph = graph;
614 if (params.graph == '')
615 delete params.graph;
616 window.location.href = MakeURL(params);
617 }
618
619 Graph.prototype.getURL = function() {
620 new_url = window.location.href;
621 new_url = new_url.replace(/50/, "150");
622 new_url = new_url.replace(/\&lookout=1/, "");
623 return new_url;
624 }
625
626
627 /**
628 * Encapsulates a *-summary.dat file.
629 * @constructor
630 */
631 function Rows(data) {
632 this.rows_ = (data) ? data.split('\n') : [];
633 this.length = this.rows_.length;
634 }
635
636 /**
637 * Returns the row at the given index.
638 */
639 Rows.prototype.get = function(i) {
640 if (!this.rows_[i].length) return null;
641 var row = JsonToJs(this.rows_[i]);
642 row.revision = isNaN(row['rev']) ? row['rev'] : parseInt(row['rev']);
643 row.version = row['ver'];
644 return row;
645 };
OLDNEW
« no previous file with comments | « perf/dashboard/ui/js/coordinates.js ('k') | perf/dashboard/ui/js/plotter.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698