diff options
author | rubentopo@gmail.com <rubentopo@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-09 21:16:54 +0000 |
---|---|---|
committer | rubentopo@gmail.com <rubentopo@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-09 21:16:54 +0000 |
commit | 68ac87d689deb6c676d122591b49bd529c175375 (patch) | |
tree | fe7ef902c11e45cfe35f87cb569d2d6558f9ab03 | |
parent | 86335314c3230915d78494d89b25d3ae79520151 (diff) | |
download | chromium_src-68ac87d689deb6c676d122591b49bd529c175375.zip chromium_src-68ac87d689deb6c676d122591b49bd529c175375.tar.gz chromium_src-68ac87d689deb6c676d122591b49bd529c175375.tar.bz2 |
[telemetry] Rough prototype of results viewer
Results viewer tool for the scrolling benchmarks csv output. Given a csv file, displays it's contents, upon column selection graphs it's values.
BUG=
TEST=
Review URL: https://chromiumcodereview.appspot.com/11411214
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@172016 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | tools/perf/utils/results_viewer/src/base.js | 84 | ||||
-rw-r--r-- | tools/perf/utils/results_viewer/src/main.css | 13 | ||||
-rw-r--r-- | tools/perf/utils/results_viewer/src/main.js | 293 | ||||
-rw-r--r-- | tools/perf/utils/results_viewer/src/results_viewer.html | 39 | ||||
-rw-r--r-- | tools/perf/utils/results_viewer/src/results_viewer.js | 51 | ||||
-rw-r--r-- | tools/perf/utils/results_viewer/src/ui.js | 83 |
6 files changed, 563 insertions, 0 deletions
diff --git a/tools/perf/utils/results_viewer/src/base.js b/tools/perf/utils/results_viewer/src/base.js new file mode 100644 index 0000000..df65c76 --- /dev/null +++ b/tools/perf/utils/results_viewer/src/base.js @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + + +/** + * The global object. + * @type {!Object} + * @const + */ +var global = this; + + +/** Platform, package, object property, and Event support. */ +this.base = (function() { + + function mLog(text, opt_indentLevel) { + if (true) + return; + + var spacing = ''; + var indentLevel = opt_indentLevel || 0; + for (var i = 0; i < indentLevel; i++) + spacing += ' '; + console.log(spacing + text); + } + + /** + * Builds an object structure for the provided namespace path, + * ensuring that names that already exist are not overwritten. For + * example: + * 'a.b.c' -> a = {};a.b={};a.b.c={}; + * @param {string} name Name of the object that this file defines. + * @param {*=} opt_object The object to expose at the end of the path. + * @param {Object=} opt_objectToExportTo The object to add the path to; + * default is {@code global}. + * @private + */ + function exportPath(name, opt_object, opt_objectToExportTo) { + var parts = name.split('.'); + var cur = opt_objectToExportTo || global; + + for (var part; parts.length && (part = parts.shift());) { + if (!parts.length && opt_object !== undefined) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (part in cur) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } + return cur; + } + + function exportTo(namespace, fn) { + var obj = exportPath(namespace); + try { + var exports = fn(); + } catch (e) { + console.log('While running exports for ', name, ':'); + console.log(e.stack || e); + return; + } + + for (var propertyName in exports) { + // Maybe we should check the prototype chain here? The current usage + // pattern is always using an object literal so we only care about own + // properties. + var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, + propertyName); + if (propertyDescriptor) { + Object.defineProperty(obj, propertyName, propertyDescriptor); + mLog(' +' + propertyName); + } + } + } + + return { + exportTo: exportTo + }; +})();
\ No newline at end of file diff --git a/tools/perf/utils/results_viewer/src/main.css b/tools/perf/utils/results_viewer/src/main.css new file mode 100644 index 0000000..8bb8729 --- /dev/null +++ b/tools/perf/utils/results_viewer/src/main.css @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +.yui-dt, .viewer { + /*Viewer extends yui-dt class*/ +} + +.viewer { + /*Implementation goes here*/ +} diff --git a/tools/perf/utils/results_viewer/src/main.js b/tools/perf/utils/results_viewer/src/main.js new file mode 100644 index 0000000..a889387 --- /dev/null +++ b/tools/perf/utils/results_viewer/src/main.js @@ -0,0 +1,293 @@ +/** + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +'use strict'; + +(function() { + /** + * Main results viewer component. + */ + var Viewer = ui.define('div'); + (function() { + //"Private" functions for Viewer + /** + * Determines the appropriate parser for a given column, using it's first + * data element. + * @param {String} firstElement The first (non-header) element of a given + * column. + * @return {String} The YUI parser needed for the column. + */ + function getColumnParser(firstElement) { + if (isNumeric(firstElement)) { + return 'number'; + } else if (isDate(firstElement)) { + return 'date'; + } else { + return 'string'; + } + } + + /** + * Determines whether or not the given element is a date. + * @param {String} str The string representation of a potential date. + * @return {boolean} true/false whether or not str can be parsed to + * a date. + */ + function isDate(str) { + var timestamp = Date.parse(str); + return !isNaN(timestamp); + } + + /** + * Generates the YUI column definition for the given dataset. + * @param {String[][]} dataSet the dataset that will be displayed. + * @return {JSON[]} The array containing the column definitions. + */ + function createYUIColumnDefinitions(dataSet) { + var header = dataSet[0]; + var firstRow = dataSet[1]; + var columnDefinitions = []; + header.forEach(function (element, index, array) { + columnDefinitions.push({ + 'key' : index.toString(), + 'label':element.toString(), + 'maxAutoWidth':95, + 'sortable':true, + 'parser':getColumnParser(firstRow[index])}); + }); + return columnDefinitions; + } + + /** + * Generates the YUI data source for the given dataset. + * @param {String[][]} dataSet the dataset that will be displayed. + * @return {YAHOO.util.FunctionDataSource} The YUI data source + * derived from the dataset. + */ + function createYUIDataSource(dataSet) { + var dataSource = []; + //Starts from the first non-header line. + for (var i = 1; i < dataSet.length; i++) { + var dataSourceLine = {}; + dataSet[i].forEach(function (element, index, array) { + if (isNumeric(element)) { + dataSourceLine[index.toString()] = parseFloat(element); + } else { + dataSourceLine[index.toString()] = element + } + }); + dataSource.push(dataSourceLine); + } + return new YAHOO.util.FunctionDataSource(function() { + return dataSource}); + } + + /** + * Un-selects all the columns from the given data table. + * @param {YAHOO.widget.DataTable} dataTable The data table that + * contains the results. + */ + function unselectAllColumns(dataTable) { + var selectedColumns = dataTable.getSelectedColumns(); + for (var i = 0; i < selectedColumns.length; i++) { + dataTable.unselectColumn(selectedColumns[i]); + } + } + + /** + * Generates an array that contains the indices of the selected + * columns in the data table. + * @param {YAHOO.widget.DataTable} dataTable + * @return {int[]} An array with the indices of the selected columns. + */ + function getSelectedColumnIndices(dataTable) { + var selectedColumnIndices = []; + var selectedColumns = dataTable.getSelectedColumns(); + for (var i = 0; i < selectedColumns.length; i++) { + selectedColumnIndices.push(selectedColumns[i].key); + } + return selectedColumnIndices; + } + + Viewer.prototype = { + __proto__: HTMLDivElement.prototype, + decorate:function() { + /** + * The id for the element that contains the barchart (Optional). + * @type {String} + */ + this.barChartElementId_ = undefined; + /** + * The rectangular array that contains the contents of the cvs file. + * @type {String[][]} + */ + this.dataSet_ = undefined; + }, + set barChartElementId(e) { + this.barChartElementId_ = e; + }, + get barChartElementId() { + return this.barChartElementId_; + }, + set dataSet(ds) { + this.dataSet_ = ds; + }, + get dataSet() { + return this.dataSet_; + }, + /** + * Renders the Viewer component. + * @expose + */ + render: function() { + document.body.appendChild(this); + var previousBarChart = this.barChartElementId_ != null ? + $(this.barChartElementId_) : null; + if (previousBarChart != null) { + document.body.removeChild(previousBarChart); + window.location.hash = this.id; + } + + var columnDefinitions = createYUIColumnDefinitions(this.dataSet_); + var dataSource = createYUIDataSource(this.dataSet_); + var dataTable = new YAHOO.widget.DataTable(this.id, columnDefinitions, + dataSource, {caption:'Results'}); + var firstRow = this.dataSet_[1]; + var currentViewer = this; + + dataTable.subscribe('cellClickEvent', function (oArgs) { + var selectedColumn = dataTable.getColumn(oArgs.target); + var selectedColumnIndex = parseInt(selectedColumn.key); + + if (selectedColumnIndex == 0) { + unselectAllColumns(dataTable); + return; + } + + if (isNumeric(firstRow[selectedColumnIndex])) { + dataTable.selectColumn(selectedColumn); + if (currentViewer.barChartElementId_ != null) { + var viewerBarChart_ = + new ViewerBarChart({ownerDocument:window.document}); + viewerBarChart_.id = currentViewer.barChartElementId_; + viewerBarChart_.dataSet = currentViewer.dataSet_; + viewerBarChart_.selectedColumnIndices + = getSelectedColumnIndices(dataTable); + viewerBarChart_.render(); + } + } + }); + } + }; + }()); + + /** + * BarChart component for the results viewer. + */ + var ViewerBarChart = ui.define('div'); + (function () { + //"Private" functions for ViewerBarChart + /** + * Generates a new array that contains only the first column, and all + * other selected columns. + * @param {(string|number)[][]} dataset Array with the csv contents. + * @param {int[]} selectedColumnIndices Indices for all the selected + * columns. + * @return {String[][]} A new array containing the first column + * and all selected columns. + */ + function extractColumnsToPlot(dataset, selectedColumnIndices) { + var lines = []; + var line = []; + for (var i = 0; i < dataset.length; ++i) { + line.push(dataset[i][0]); + for (var j = 0; j < selectedColumnIndices.length; j++) { + var elementValue = dataset[i][selectedColumnIndices[j]]; + line.push(isNumeric(elementValue) ? parseFloat(elementValue) : + elementValue); + } + lines.push(line); + line = []; + } + return lines; + } + + ViewerBarChart.prototype = { + __proto__:HTMLDivElement.prototype, + decorate: function() { + /** + * Percetage of the window width that will be used for the chart + * @const + * @type {float} + */ + ViewerBarChart.PERCENTAGE_OF_WINDOW_WIDTH_FOR_CHART = 0.75; + /** + * Approximate number of pixels that will be used per line + * @const + * @type {int} + */ + ViewerBarChart.HEIGHT_PER_LINE_IN_PIXELS = 28; + + /** + * Raw dataset, which contains the csv file contents. + * @type {(String|number)[][]} + */ + this.dataSet_ = undefined; + /** + * Array that contains the selected indices from the table view. + * @type {number[]} + */ + this.selectedColumnIndices_ = undefined; + }, + /** + * Renders the ViewerBarChart component. + * @expose + */ + render : function() { + var existingBarChart = $(this.id); + if (existingBarChart != null) { + //Remove the previous bar chart + document.body.removeChild(existingBarChart); + } + //Attach this component to the document + document.body.appendChild(this); + var lines = extractColumnsToPlot(this.dataSet_, + this.selectedColumnIndices_); + var data = google.visualization.arrayToDataTable(lines); + + var barCharWidth = window.width * + ViewerBarChart.PERCENTAGE_OF_WINDOW_WIDTH_FOR_CHART; + var barCharHeight = this.dataSet_.length * + ViewerBarChart.HEIGHT_PER_LINE_IN_PIXELS; + var options = { + 'width': barCharWidth, + 'height':barCharHeight, + 'fontSize':15 + }; + new google.visualization.BarChart(this).draw(data, options); + window.location.hash = this.id; + }, + set dataSet(ds) { + this.dataSet_ = ds; + }, + set selectedColumnIndices(sci) { + this.selectedColumnIndices_ = sci; + } + }; + }()); + + /** + * Determines whether or not a string can be parsed to a number. + * @param {String} element String representation of the potential number. + * @return {boolean} True or false depending on whether the element is + * numeric or not. + */ + function isNumeric(element) { + return !isNaN(parseFloat(element)) && isFinite(element); + } + + window.Viewer = Viewer; + window.ViewerBarChart = ViewerBarChart; +})();
\ No newline at end of file diff --git a/tools/perf/utils/results_viewer/src/results_viewer.html b/tools/perf/utils/results_viewer/src/results_viewer.html new file mode 100644 index 0000000..64554ba --- /dev/null +++ b/tools/perf/utils/results_viewer/src/results_viewer.html @@ -0,0 +1,39 @@ +<!-- +Copyright (c) 2012 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<!DOCTYPE HTML> +<html i18n-values="dir:textdirection"> + <head> + <meta charset="utf-8"> + <title> + Results viewer + </title> + <link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/2.9.0/build/datatable/assets/skins/sam/datatable.css"> + <link type="text/css" rel="stylesheet" href="main.css"> + <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script> + <script src="http://yui.yahooapis.com/2.9.0/build/yahoo-dom-event/yahoo-dom-event.js"></script> + <script src="http://yui.yahooapis.com/2.9.0/build/element/element-min.js"></script> + <script src="http://yui.yahooapis.com/2.9.0/build/datasource/datasource-min.js"></script> + <script src="http://yui.yahooapis.com/2.9.0/build/datatable/datatable-min.js"></script> + <script type="text/javascript" src="https://www.google.com/jsapi"></script> + <script type="text/javascript"> + google.load("visualization", "1", {packages:["corechart"]}); + </script> + <script type="text/javascript" src="base.js"></script> + <script type="text/javascript" src="ui.js"></script> + <script type="text/javascript" src="results_viewer.js"></script> + <script type="text/javascript" src="main.js"></script> + </head> + <body class="yui-skin-sam"> + <form action=""> + <input type="file" id="fileSelector" name="files[]" /> + </form> + <!--<div id="csvContentsDataTable" class="viewer"></div> TODO remove me!--> + <!--<div id="barChart"></div> TODO remove me--> + </body> +</html> +<script type="text/javascript"> + $('fileSelector').addEventListener('change', handleFileSelectEvent, false); +</script>
\ No newline at end of file diff --git a/tools/perf/utils/results_viewer/src/results_viewer.js b/tools/perf/utils/results_viewer/src/results_viewer.js new file mode 100644 index 0000000..06783c4 --- /dev/null +++ b/tools/perf/utils/results_viewer/src/results_viewer.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2012 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +'use strict'; + +/** + * Main entry point for the rendering of the Viewer component. + * @param {Event} evt File select event. + */ +function handleFileSelectEvent(evt) { + var file = evt.target.files[0]; + var fileReader = new FileReader(); + + fileReader.onload = (function(theFile) { + return function(e) { + var dataSet = createViewerDataset(e.target.result); + var viewer = new Viewer({ownerDocument:window.document}); + viewer.id = 'csvContentsDataTable'; + viewer.dataSet = dataSet; + viewer.barChartElementId = 'barChart'; + viewer.render(); + }; + })(file); + fileReader.readAsText(file); +} + +/** + * Splits the csv file contents, and creates a square array of strings for + * each token in the line. + * @param fileContents + * @return {String[][]} The data set with the contents of the csv file. + */ +function createViewerDataset(fileContents) { + var rows = fileContents.split('\n'); + var dataset = []; + rows.forEach(function (row, index, array) { + if (row.trim().length == 0) { + //Ignore empty lines + return; + } + var newRow = []; + var columns = row.split(','); + columns.forEach(function (element, index, array) { + newRow.push(element); + }); + dataset.push(newRow); + }); + return dataset; +}
\ No newline at end of file diff --git a/tools/perf/utils/results_viewer/src/ui.js b/tools/perf/utils/results_viewer/src/ui.js new file mode 100644 index 0000000..576e87b --- /dev/null +++ b/tools/perf/utils/results_viewer/src/ui.js @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +base.exportTo('ui', function() { + + /** + * Helper function for creating new element for define. + */ + function createElementHelper(tagName, opt_bag) { + // Allow passing in ownerDocument to create in a different document. + var doc; + if (opt_bag && opt_bag.ownerDocument) + doc = opt_bag.ownerDocument; + + return doc.createElement(tagName); + } + + /** + * Creates the constructor for a UI element class. + * + * Usage: + * <pre> + * var List = base.ui.define('list'); + * List.prototype = { + * __proto__: HTMLUListElement.prototype, + * decorate: function() { + * ... + * }, + * ... + * }; + * </pre> + * + * @param {string|Function} tagNameOrFunction The tagName or + * function to use for newly created elements. If this is a function it + * needs to return a new element when called. + * @return {function(Object=):Element} The constructor function which takes + * an optional property bag. The function also has a static + * {@code decorate} method added to it. + */ + function define(tagNameOrFunction) { + var createFunction, tagName; + if (typeof tagNameOrFunction == 'function') { + createFunction = tagNameOrFunction; + tagName = ''; + } else { + createFunction = createElementHelper; + tagName = tagNameOrFunction; + } + + /** + * Creates a new UI element constructor. + * @param {Object=} opt_propertyBag Optional bag of properties to set on the + * object after created. The property {@code ownerDocument} is special + * cased and it allows you to create the element in a different + * document than the default. + * @constructor + */ + function f(opt_propertyBag) { + var el = createFunction(tagName, opt_propertyBag); + f.decorate(el); + for (var propertyName in opt_propertyBag) { + el[propertyName] = opt_propertyBag[propertyName]; + } + return el; + } + + /** + * Decorates an element as a UI element class. + * @param {!Element} el The element to decorate. + */ + f.decorate = function(el) { + el.__proto__ = f.prototype; + el.decorate(); + }; + + return f; + } + + return { + define: define + }; +}); |