Chromium Code Reviews| Index: chrome/browser/resources/performance_monitor/chart.js |
| diff --git a/chrome/browser/resources/performance_monitor/chart.js b/chrome/browser/resources/performance_monitor/chart.js |
| index 1f0f4ef3804c6d9604596d59f8089d53ab747b3a..3c2f9cf73da3e81514c1c8a081579e03cf98e1c4 100644 |
| --- a/chrome/browser/resources/performance_monitor/chart.js |
| +++ b/chrome/browser/resources/performance_monitor/chart.js |
| @@ -4,10 +4,7 @@ |
| 'use strict'; |
| -/* convert to cr.define('PerformanceMonitor', function() {... |
| - * when integrating into webui */ |
| - |
| -var Installer = function() { |
| +cr.define('performance_monitor', function() { |
| /** |
| * Enum for time ranges, giving a descriptive name, time span prior to |now|, |
| * data point resolution, and time-label frequency and format for each. |
| @@ -43,6 +40,12 @@ var Installer = function() { |
| resolution: 1000 * 3600 * 3, labelEvery: 112, format: 'M/yy'}, |
| }; |
| + /* |
| + * Offset, in mS, by which to subtract to convert GMT to local time |
|
Evan Stade
2012/08/03 17:50:59
isn't milliseconds abbreviated to ms?
clintstaley
2012/08/06 21:06:47
I've seen mS, and even msec, but happy to adjust t
|
| + * @type {!number} |
| + */ |
| + var timezoneOffset = new Date().getTimezoneOffset() * 60000; |
| + |
| /** @constructor */ |
| function PerformanceMonitor() { |
| this.__proto__ = PerformanceMonitor.prototype; |
| @@ -51,79 +54,100 @@ var Installer = function() { |
| * If a div list is not empty, the associated data will be non-null, or |
| * null but about to be filled by webui response. Thus, any metric with |
| * non-empty div list but null data is awaiting a data response from the |
| - * webui. |
| + * webui. The webui uses numbers to uniquely identify metric and event |
|
Dan Beam
2012/08/03 18:40:36
should these say "webui handler" instead of webui?
clintstaley
2012/08/06 21:06:47
Done.
|
| + * types, so we use the same numbers (in string form) for the map key, |
| + * repeating the id number in the |id| field, as a number. |
| * @type {Object.<string, { |
|
Dan Beam
2012/08/03 18:40:36
I think wrapping for these long @type expressions
clintstaley
2012/08/06 21:06:47
I've gotten different responses on this from diffe
|
| - * divs: !Array.<HTMLDivElement>, |
| - * yAxis: !{max: !number, color: !string}, |
| - * data: ?Array.<{time: !number, value: !number}>, |
| + * id: !number, |
| * description: !string, |
| - * units: !string |
| + * units: !string, |
| + * yAxis: !{max: !number, color: !string}, |
| + * divs: !Array.<HTMLDivElement>, |
| + * data: ?Array.<{time: !number, value: !number}> |
| * }>} |
| * @private |
| */ |
| - this.metricMap_ = { |
| - jankiness: { |
| - divs: [], |
| - yAxis: {max: 100, color: 'rgb(255, 128, 128)'}, |
| - data: null, |
| - description: 'Jankiness', |
| - units: 'milliJanks' |
| - }, |
| - oddness: { |
| - divs: [], |
| - yAxis: {max: 20, color: 'rgb(0, 192, 0)'}, |
| - data: null, |
| - description: 'Oddness', |
| - units: 'kOdds' |
| - } |
| - }; |
| + this.metricMap_ = {}; |
| /* |
| * Similar data for events, though no yAxis info is needed since events |
| * are simply labelled markers at X locations. Rules regarding null data |
| * with non-empty div list apply here as for metricMap_ above. |
| - * @type {Object.<string, { |
| - * divs: !Array.<HTMLDivElement>, |
| + * @type {Object.<number, { |
| + * id: !number, |
| * description: !string, |
| * color: !string, |
| + * divs: !Array.<HTMLDivElement>, |
| * data: ?Array.<{time: !number, longDescription: !string}> |
| * }>} |
| * @private |
| */ |
| - this.eventMap_ = { |
| - wampusAttacks: { |
| - divs: [], |
| - description: 'Wampus Attack', |
| - color: 'rgb(0, 0, 255)', |
| - data: null |
| - }, |
| - solarEclipses: { |
| - divs: [], |
| - description: 'Solar Eclipse', |
| - color: 'rgb(255, 0, 255)', |
| - data: null |
| - } |
| - }; |
| + this.eventMap_ = {}; |
| /** |
| - * Time periods in which the browser was active and collecting metrics. |
| + * Time periods in which the browser was active and collecting metrics |
| * and events. |
| * @type {!Array.<{start: !number, end: !number}>} |
| * @private |
| */ |
| this.intervals_ = []; |
| - this.setupCheckboxes_( |
| - '#chooseMetrics', this.metricMap_, this.addMetric, this.dropMetric); |
| - this.setupCheckboxes_( |
| - '#chooseEvents', this.eventMap_, this.addEventType, this.dropEventType); |
| this.setupTimeRangeChooser_(); |
| + chrome.send('getAllEventTypes'); |
| + chrome.send('getAllMetricTypes'); |
| this.setupMainChart_(); |
| - TimeRange_.day.element.click().call(this); |
| + TimeRange_.day.element.click(); |
| } |
| PerformanceMonitor.prototype = { |
| /** |
| + * Receive a list of all metrics, and populate |this.metricMap_| to |
| + * reflect said list. Reconfigure the checkbox set for metric selection. |
| + * @param {Array.<{metricType: !String, shortDescription: !String}>} |
| + * allMetrics All metrics from which to select. |
|
Dan Beam
2012/08/03 18:40:36
nit: 4 \s instead of 2 \s before allMetrics
clintstaley
2012/08/06 21:06:47
Done.
|
| + */ |
| + getAllMetricTypesCallback: function(allMetrics) { |
| + for (var i = 0; i < allMetrics.length; i++) { |
| + var metric = allMetrics[i]; |
| + |
| + this.metricMap_[metric.metricType] = { |
| + id: metric.metricType, |
| + description: metric.shortDescription, |
| + units: metric.units, |
| + yAxis: {min: 0, max: metric.maxValue, color: 'rgb(0, 0, 255'}, |
| + divs: [], |
| + data: null |
| + }; |
| + } |
| + |
| + this.setupCheckboxes_($('#chooseMetrics')[0], |
| + this.metricMap_, this.addMetric, this.dropMetric); |
| + }, |
| + |
| + /** |
| + * Receive a list of all event types, and populate |this.eventMap_| to |
| + * reflect said list. Reconfigure the checkbox set for event selection. |
| + * @param {Array.<{eventType: !String, shortDescription: !String}>} |
| + * allEvents All events from which to select. |
| + */ |
| + getAllEventTypesCallback: function(allEvents) { |
| + for (var i = 0; i < allEvents.length; i++) { |
| + var eventInfo = allEvents[i]; |
| + |
| + this.eventMap_[eventInfo.eventType] = { |
| + id: eventInfo.eventType, |
| + color: 'rgb(255, 0, 0)', |
| + data: null, |
| + description: eventInfo.shortDescription, |
| + divs: [] |
| + }; |
| + } |
| + |
| + this.setupCheckboxes_($('#chooseEvents')[0], |
| + this.eventMap_, this.addEventType, this.dropEventType); |
| + }, |
| + |
| + /** |
| * Set up the radio button set to choose time range. Use div#radioTemplate |
| * as a template. |
| * @private |
| @@ -154,13 +178,13 @@ var Installer = function() { |
| /** |
| * Generalized function for setting up checkbox blocks for either events |
| - * or metrics. Take a div ID |divId| into which to place the checkboxes, |
| + * or metrics. Take a div |div| into which to place the checkboxes, |
| * and a map |optionMap| with values that each include a property |
| * |description|. Set up one checkbox for each entry in |optionMap| |
| * labelled with that description. Arrange callbacks to function |check| |
| * or |uncheck|, passing them the key of the checked or unchecked option, |
| * when the relevant checkbox state changes. |
| - * @param {!string} divId Id of division into which to put checkboxes |
| + * @param {!HTMLDivElement} div div into which to put checkboxes |
|
Dan Beam
2012/08/03 18:40:36
Generally we capitalize after the parameter name a
clintstaley
2012/08/06 21:06:47
Done, here and elsewhere. Of course, it's a sente
|
| * @param {!Object} optionMap map of metric/event entries |
| * @param {!function(this:Controller, Object)} check |
| * function to select an entry (metric or event) |
| @@ -168,23 +192,19 @@ var Installer = function() { |
| * function to deselect an entry (metric or event) |
| * @private |
| */ |
| - setupCheckboxes_: function(divId, optionMap, check, uncheck) { |
| + setupCheckboxes_: function(div, optionMap, check, uncheck) { |
| var checkboxTemplate = $('#checkboxTemplate')[0]; |
| - var chooseMetricsDiv = $(divId)[0]; |
| for (var option in optionMap) { |
| var checkbox = checkboxTemplate.cloneNode(true); |
| checkbox.querySelector('span').innerText = 'Show ' + |
| optionMap[option].description; |
| - chooseMetricsDiv.appendChild(checkbox); |
| + div.appendChild(checkbox); |
| var input = checkbox.querySelector('input'); |
| - input.option = option; |
| + input.option = optionMap[option].id; |
| input.addEventListener('change', function(e) { |
| - if (e.target.checked) |
| - check.call(this, e.target.option); |
| - else |
| - uncheck.call(this, e.target.option); |
| + (e.target.checked ? check : uncheck).call(this, e.target.option); |
| }.bind(this)); |
| } |
| }, |
| @@ -213,36 +233,15 @@ var Installer = function() { |
| this.end = Math.floor(Date.now() / range.resolution) * |
| range.resolution; |
| - // Take the GMT value of this.end ("now") and subtract from it the |
| - // number of minutes by which we lag GMT in the present timezone, |
| - // X mS/minute. This will show time in the present timezone. |
| - this.end -= new Date().getTimezoneOffset() * 60000; |
| this.start = this.end - range.timeSpan; |
| this.requestIntervals(); |
| }, |
| /** |
| - * Return mock interval set for testing. |
| - * @return {!Array.<{start: !number, end: !number}>} intervals |
| - */ |
| - getMockIntervals: function() { |
| - var interval = this.end - this.start; |
| - |
| - return [ |
| - {start: this.start + interval * .1, |
| - end: this.start + interval * .2}, |
| - {start: this.start + interval * .7, |
| - end: this.start + interval} |
| - ]; |
| - }, |
| - |
| - /** |
| * Request activity intervals in the current time range. |
| */ |
| requestIntervals: function() { |
| - this.onReceiveIntervals(this.getMockIntervals()); |
| - // Replace with: chrome.send('getIntervals', this.start, this.end, |
| - // this.onReceiveIntervals); |
| + chrome.send('getActiveIntervals', [this.start, this.end]); |
| }, |
| /** |
| @@ -251,7 +250,7 @@ var Installer = function() { |
| * all metrics and event types that are currently selected. |
| * @param {!Array.<{start: !number, end: !number}>} intervals new intervals |
| */ |
| - onReceiveIntervals: function(intervals) { |
| + getActiveIntervalsCallback: function(intervals) { |
| this.intervals_ = intervals; |
| for (var metric in this.metricMap_) { |
| @@ -263,7 +262,7 @@ var Installer = function() { |
| for (var eventType in this.eventMap_) { |
| var eventValue = this.eventMap_[eventType]; |
| if (eventValue.divs.length > 0) |
| - this.refreshEventType(eventType); |
| + this.refreshEventType(eventValue.id); |
| } |
| }, |
| @@ -278,7 +277,7 @@ var Installer = function() { |
| /** |
| * Remove a metric from the chart(s). |
| - * @param {!string} metric Metric to stop displaying |
| + * @param {!string} metric Metric to stop displaying. |
| */ |
| dropMetric: function(metric) { |
| var metricValue = this.metricMap_[metric]; |
| @@ -289,61 +288,35 @@ var Installer = function() { |
| }, |
| /** |
| - * Return mock metric data points for testing. Give values ranging from |
| - * offset to max-offset. (This lets us avoid direct overlap of |
| - * different mock data sets in an ugly way that will die in the next |
| - * version anyway.) |
| - * @param {!number} max Max data value to return, less offset |
| - * @param {!number} offset Adjustment factor |
| - * @return {!Array.<{time: !number, value: !number}>} |
| - */ |
| - getMockDataPoints: function(max, offset) { |
| - var dataPoints = []; |
| - |
| - for (var i = 0; i < this.intervals_.length; i++) { |
| - // Rise from low offset to high max-offset in 100 point steps. |
| - for (var time = this.intervals_[i].start; |
| - time <= this.intervals_[i].end; time += this.range.resolution) { |
| - var numPoints = time / this.range.resolution; |
| - dataPoints.push({time: time, value: offset + (numPoints % 100) * |
| - (max - 2 * offset) / 100}); |
| - } |
| - } |
| - return dataPoints; |
| - }, |
| - |
| - /** |
| * Request new metric data, assuming the metric table and chart already |
| * exist. |
| - * @param {!string} metric Metric for which to get data |
| + * @param {!string} metric Metric for which to get data. |
| */ |
| refreshMetric: function(metric) { |
| var metricValue = this.metricMap_[metric]; |
| metricValue.data = null; // Mark metric as awaiting response. |
| - this.onReceiveMetric(metric, |
| - this.getMockDataPoints(metricValue.yAxis.max, 5)); |
| - // Replace with: |
| - // chrome.send("getMetric", this.range.start, this.range.end, |
| - // this.range.resolution, this.onReceiveMetric); |
| + chrome.send('getMetric', [metricValue.id, |
| + this.start, this.end, this.range.resolution]); |
| }, |
| /** |
| - * Receive new datapoints for |metric|, convert the data to Flot-usable |
| + * Receive new datapoints for a metric, convert the data to Flot-usable |
| * form, and redraw all affected charts. |
| - * @param {!string} metric Metric to which |points| applies |
| - * @param {!Array.<{time: !number, value: !number}>} points |
| - * new data points |
| + * @param {!{ |
| + * type: !number, |
| + * points: !Array.<{time: !number, value: !number}> |
| + * }} result Object giving metric ID, and time/value point pairs for |
| + * that id. |
| */ |
| - onReceiveMetric: function(metric, points) { |
| - var metricValue = this.metricMap_[metric]; |
| - |
| + getMetricCallback: function(result) { |
| + var metricValue = this.metricMap_[result.metricType]; |
| // Might have been dropped while waiting for data. |
| if (metricValue.divs.length == 0) |
| return; |
| var series = []; |
| - metricValue.data = [series]; |
| + metricValue.data = [series]; // Data ends with current open series. |
| // Traverse the points, and the intervals, in parallel. Both are in |
| // ascending time order. Create a sequence of data "series" (per Flot) |
| @@ -351,28 +324,28 @@ var Installer = function() { |
| var interval = this.intervals_[0]; |
| var intervalIndex = 0; |
| var pointIndex = 0; |
| - while (pointIndex < points.length && |
| + while (pointIndex < result.points.length && |
| intervalIndex < this.intervals_.length) { |
| - var point = points[pointIndex++]; |
| + var point = result.points[pointIndex++]; |
| while (intervalIndex < this.intervals_.length && |
| point.time > interval.end) { |
| - interval = this.intervals_[++intervalIndex]; // Jump to new interval. |
| + interval = this.intervals_[++intervalIndex]; // Jump to new interval. |
| if (series.length > 0) { |
| - series = []; // Start a new series. |
| - metricValue.data.push(series); // Put it on the end of the data. |
| + series = []; // Start a new series. |
| + metricValue.data.push(series); // Put it on the end of the data. |
| } |
| } |
| if (intervalIndex < this.intervals_.length && |
| - point.time > interval.start) |
| - series.push([point.time, point.value]); |
| + point.time > interval.start) { |
| + series.push([point.time - timezoneOffset, point.value]); |
| + } |
| } |
| - |
| metricValue.divs.forEach(this.drawChart, this); |
| }, |
| /** |
| * Add a new event to the chart(s). |
| - * @param {!string} eventType type of event to start displaying |
| + * @param {!string} eventType type of event to start displaying. |
| */ |
| addEventType: function(eventType) { |
| // Events show on all charts. |
| @@ -393,59 +366,38 @@ var Installer = function() { |
| }, |
| /** |
| - * Return mock event points for testing. |
| - * @param {!string} eventType type of event to generate mock data for |
| - * @return {!Array.<{time: !number, longDescription: !string}>} |
| - */ |
| - getMockEventValues: function(eventType) { |
| - var mockValues = []; |
| - for (var i = 0; i < this.intervals_.length; i++) { |
| - var interval = this.intervals_[i]; |
| - |
| - mockValues.push({ |
| - time: interval.start, |
| - longDescription: eventType + ' at ' + |
| - new Date(interval.start) + ' blah, blah blah'}); |
| - mockValues.push({ |
| - time: (interval.start + interval.end) / 2, |
| - longDescription: eventType + ' at ' + |
| - new Date((interval.start + interval.end) / 2) + ' blah, blah'}); |
| - mockValues.push({ |
| - time: interval.end, |
| - longDescription: eventType + ' at ' + new Date(interval.end) + |
| - ' blah, blah blah'}); |
| - } |
| - return mockValues; |
| - }, |
| - |
| - /** |
| * Request new data for |eventType|, for times in the current range. |
| * @param {!string} eventType type of event to get new data for |
| */ |
| refreshEventType: function(eventType) { |
| // Mark eventType as awaiting response. |
| this.eventMap_[eventType].data = null; |
| - this.onReceiveEventType(eventType, this.getMockEventValues(eventType)); |
| - // Replace with: |
| - // chrome.send("getEvents", eventType, this.range.start, this.range.end); |
| + |
| + chrome.send('getEvents', [eventType, this.start, this.end]); |
| }, |
| /** |
| - * Receive new data for |eventType|. If the event has been deselected while |
| - * awaiting webui response, do nothing. Otherwise, save the data directly, |
| - * since events are handled differently than metrics when drawing |
| + * Receive new events for |eventType|. If |eventType| has been deselected |
| + * while awaiting webui response, do nothing. Otherwise, save the data |
| + * directly, since events are handled differently than metrics when drawing |
| * (no "series"), and redraw all the affected charts. |
| - * @param {!string} eventType type of event the new data applies to |
| - * @param {!Array.<{time: !number, longDescription: !string}>} values |
| - * new event values |
| + * @param {!{ |
| + * type: !number, |
| + * points: !Array.<{time: !number, longDescription: string}> |
| + * }} result Object giving eventType ID, and time/description pairs for |
| + * each event of that type in the range requested. |
| */ |
| - onReceiveEventType: function(eventType, values) { |
| - var eventValue = this.eventMap_[eventType]; |
| + getEventsCallback: function(result) { |
| + var eventValue = this.eventMap_[result.eventType]; |
| if (eventValue.divs.length == 0) |
| return; |
| - eventValue.data = values; |
| + result.points.forEach(function(elememt) { |
| + element.time -= timezoneOffset; |
| + }); |
| + |
| + eventValue.data = result.points; |
| eventValue.divs.forEach(this.drawChart, this); |
| }, |
| @@ -524,11 +476,15 @@ var Installer = function() { |
| var eventValue = eventValues[i]; |
| for (var d = 0; d < eventValue.data.length; d++) { |
| var point = eventValue.data[d]; |
| - markings.push({ |
| - color: eventValue.color, |
| - description: eventValue.description, |
| - xaxis: {from: point.time, to: point.time} |
| - }); |
| + if (point.time >= this.start && point.time <= this.end) { |
| + markings.push({ |
| + color: eventValue.color, |
| + description: eventValue.description, |
| + xaxis: {from: point.time, to: point.time} |
| + }); |
| + } else { |
| + console.log('Event out of time range at: ' + point.time); |
| + } |
| } |
| } |
| @@ -561,16 +517,26 @@ var Installer = function() { |
| } |
| }); |
| + // Ensure at least one y axis, as a reference for event placement. |
| + if (yAxes.length == 0) |
| + yAxes.push({max: 1.0}); |
| + |
| var markings = this.getEventMarks_(chartData.events); |
| var chart = this.charts[0]; |
| var plot = $.plot(chart, seriesSeq, { |
| yaxes: yAxes, |
| - xaxis: {mode: 'time'}, |
| + xaxis: { |
| + mode: 'time', |
| + min: this.start - timezoneOffset, |
| + max: this.end - timezoneOffset |
| + }, |
| + points: {show: true, radius: 1}, |
| + lines: {show: true}, |
| grid: {markings: markings} |
| }); |
| // For each event in |markings|, create also a label div, with left |
| - // edge colinear with the event vertical-line. Top of label is |
| + // edge colinear with the event vertical line. Top of label is |
| // presently a hack-in, putting labels in three tiers of 25px height |
| // each to avoid overlap. Will need something better. |
| var labelTemplate = $('#labelTemplate')[0]; |
| @@ -582,6 +548,7 @@ var Installer = function() { |
| labelDiv.innerText = mark.description; |
| labelDiv.style.left = point.left + 'px'; |
| labelDiv.style.top = (point.top + 25 * (i % 3)) + 'px'; |
| + |
| chart.appendChild(labelDiv); |
| } |
| } |
| @@ -589,6 +556,6 @@ var Installer = function() { |
| return { |
| PerformanceMonitor: PerformanceMonitor |
| }; |
| -}(); |
| +}); |
| -var performanceMonitor = new Installer.PerformanceMonitor(); |
| +var PerformanceMonitor = new performance_monitor.PerformanceMonitor(); |