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

Side by Side Diff: chrome/browser/resources/performance_monitor/chart.js

Issue 10679009: Revision to CPM UI using Flot and JQuery (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Revised constructor patterns per review Created 8 years, 5 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
« no previous file with comments | « chrome/browser/resources/performance_monitor/chart.html ('k') | third_party/flot/LICENSE.txt » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 /* Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be 2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file. */ 3 * found in the LICENSE file. */
4 4
5 'use strict'; 5 'use strict';
6 6
7 google.load('visualization', '1', {packages: ['corechart']}); 7 /* convert to cr.define('PerformanceMonitor', function() {...
8 8 * when integrating into webui */
9 function $(criterion) { 9
10 return document.querySelector(criterion); 10 var Installer = function() {
Dan Beam 2012/07/06 23:49:36 var Installer = (function() {
clintstaley 2012/07/09 22:03:22 Done.
11 } 11 /**
12 12 * Enum for time ranges, giving a descriptive name, time span prior to |now|,
13 var controller = new function() { 13 * data point resolution, and time-label frequency and format for each.
14 // Tabular setup for various time ranges, giving a descriptive name, time span 14 * @enum {{
15 // prior to |now|, data point resolution, and time-label frequency and format 15 * value: !number,
Dan Beam 2012/07/06 23:49:36 technically {string,number,boolean,function} don't
clintstaley 2012/07/09 22:03:22 Not sure I understand, here. Can I not write valu
Dan Beam 2012/07/09 23:20:53 I honestly have no idea what that means either, bu
16 // for each. 16 * name: !string,
17 this.TimeRange = { 17 * timeSpan: !number,
18 // Prior day, resolution of 2 min, at most 720 points, 18 * resolution: !number,
19 // Labels at 90 point (3 hour) intervals 19 * labelEvery: !number,
20 * format: !string
21 * }}
22 * @private
23 */
24 var TimeRange_ = {
25 // Prior day, resolution of 2 min, at most 720 points.
26 // Labels at 90 point (3 hour) intervals.
20 day: {value: 0, name: 'Last Day', timeSpan: 24 * 3600 * 1000, 27 day: {value: 0, name: 'Last Day', timeSpan: 24 * 3600 * 1000,
21 resolution: 1000 * 60 * 2, labelEvery: 90, format: 'MM-dd'}, 28 resolution: 1000 * 60 * 2, labelEvery: 90, format: 'MM-dd'},
22 29
23 // Prior week, resolution of 15 min -- at most 672 data points 30 // Prior week, resolution of 15 min -- at most 672 data points.
24 // Labels at 96 point (daily) intervals 31 // Labels at 96 point (daily) intervals.
25 week: {value: 1, name: 'Last Week', timeSpan: 7 * 24 * 3600 * 1000, 32 week: {value: 1, name: 'Last Week', timeSpan: 7 * 24 * 3600 * 1000,
26 resolution: 1000 * 60 * 15, labelEvery: 96, format: 'M/d'}, 33 resolution: 1000 * 60 * 15, labelEvery: 96, format: 'M/d'},
27 34
28 // Prior month (30 days), resolution of 1 hr -- at most 720 data points 35 // Prior month (30 days), resolution of 1 hr -- at most 720 data points.
29 // Labels at 168 point (weekly) intervals 36 // Labels at 168 point (weekly) intervals.
30 month: {value: 2, name: 'Last Month', timeSpan: 30 * 24 * 3600 * 1000, 37 month: {value: 2, name: 'Last Month', timeSpan: 30 * 24 * 3600 * 1000,
31 resolution: 1000 * 3600, labelEvery: 168, format: 'M/d'}, 38 resolution: 1000 * 3600, labelEvery: 168, format: 'M/d'},
32 39
33 // Prior quarter (90 days), resolution of 3 hr -- at most 720 data points 40 // Prior quarter (90 days), resolution of 3 hr -- at most 720 data points.
34 // Labels at 112 point (fortnightly) intervals 41 // Labels at 112 point (fortnightly) intervals.
35 quarter: {value: 3, name: 'Last Quarter', timeSpan: 90 * 24 * 3600 * 1000, 42 quarter: {value: 3, name: 'Last Quarter', timeSpan: 90 * 24 * 3600 * 1000,
36 resolution: 1000 * 3600 * 3, labelEvery: 112, format: 'M/yy'}, 43 resolution: 1000 * 3600 * 3, labelEvery: 112, format: 'M/yy'},
37 }; 44 };
38 45
39 // Parent container of all line graphs 46 /** @constructor */
40 this.chartDiv = $('#charts'); 47 function PerformanceMonitor() {
41 48 this.__proto__ = PerformanceMonitor.prototype;
42 // Parent container of checkboxes to choose metrics to display 49 /**
43 this.chooseMetricsDiv = $('#chooseMetrics'); 50 * All metrics have entries, but those not displayed have an empty div list.
44 51 * If a div list is not empty, the associated data will be non-null, or
45 // Parent container of checkboxes to choose event types to display 52 * null but about to be filled by webui response. Thus, any metric with
46 this.chooseEventsDiv = $('#chooseEvents'); 53 * non-empty div list but null data is awaiting a data response from the
47 54 * webui.
48 // Parent container of radio buttons to select time range 55 * @type {Object.<string, {
49 this.timeDiv = $('#chooseTimeRange'); 56 * divs: !Array.<HTMLDivElement>,
50 57 * yAxis: !{max: !number, color: !string},
51 this.metricMap = {}; // MetricName => {div, lineChart, dataTable} objects 58 * data: ?Array.<{time: !number, value: !number}>,
52 this.eventMap = {}; // EventName => event point lists, as returned by webui 59 * description: !string,
53 this.intervals = []; // Array of objects {start, end} 60 * units: !string
54 61 * }>}
55 this.setTimeRange = function(range) { 62 * @private
56 this.range = range; 63 */
57 this.end = Math.floor(new Date().getTime() / range.resolution) * 64 this.metricMap_ = {
58 range.resolution; 65 jankiness: {
59 this.start = this.end - range.timeSpan; 66 divs: [],
60 this.requestIntervals(); 67 yAxis: {max: 100, color: 'rgb(255, 128, 128)'},
68 data: null,
69 description: 'Jankiness',
70 units: 'milliJanks'
71 },
72 oddness: {
73 divs: [],
74 yAxis: {max: 20, color: 'rgb(0, 192, 0)'},
75 data: null,
76 description: 'Oddness',
77 units: 'kOdds'
78 }
79 };
80
81 /*
82 * Similar data for events, though no yAxis info is needed since events
83 * are simply labelled markers at X locations. Rules regarding null data
84 * with non-empty div list apply here as for metricMap_ above.
85 * @type {Object.<string, {
86 * divs: !Array.<HTMLDivElement>,
87 * description: !string,
88 * color: !string,
89 * data: ?Array.<{time: !number, longDescription: !string}>
90 * }>}
91 * @private
92 */
93 this.eventMap_ = {
94 wampusAttacks: {
95 divs: [],
96 description: 'Wampus Attack',
97 color: 'rgb(0, 0, 255)',
98 data: null
99 },
100 solarEclipses: {
101 divs: [],
102 description: 'Solar Eclipse',
103 color: 'rgb(255, 0, 255)',
104 data: null
105 }
106 };
107
108 /**
109 * Time periods in which the browser was active and collecting metrics.
110 * and events.
111 * @type {!Array.<{start: !number, end: !number}>}
112 * @private
113 */
114 this.intervals_ = [];
115
116 this.setupCheckboxes_(
117 '#chooseMetrics', this.metricMap_, this.addMetric, this.dropMetric);
118 this.setupCheckboxes_(
119 '#chooseEvents', this.eventMap_, this.addEventType, this.dropEventType);
120 this.setupTimeRangeChooser_();
121 this.setupMainChart_();
122 TimeRange_.day.element.click().call(this);
61 } 123 }
62 124
63 // Return mock interval set for testing 125 PerformanceMonitor.prototype = {
64 this.getMockIntervals = function() { 126 /**
65 var interval = this.end - this.start; 127 * Set up the radio button set to choose time range. Use div#radioTemplate
66 return [ 128 * as a template.
67 {'start': this.start + interval * .1, 129 * @private
68 'end': this.start + interval * .2}, 130 */
69 {'start': this.start + interval * .7, 'end': this.start + interval * .9} 131 setupTimeRangeChooser_: function() {
70 ]; 132 var timeDiv = $('#chooseTimeRange')[0];
71 } 133 var radioTemplate = $('#radioTemplate')[0];
72 // Request array of objects with start and end fields showing browser 134 var controller = this;
73 // activity intervals in the specified time range 135
74 this.requestIntervals = function() { 136 for (var time in TimeRange_) {
75 this.onReceiveIntervals(this.getMockIntervals()); 137 var timeRange = TimeRange_[time];
76 // Replace with: chrome.send('getIntervals', this.start, this.end, 138 var radio = radioTemplate.cloneNode(true);
77 // this.onReceiveIntervals); 139 var input = radio.querySelector('input');
78 } 140
79 141 input.value = timeRange.value;
80 // Webui callback delivering response from requestIntervals call. Assumes 142 input.timeRange = timeRange;
81 // this is a new time range choice, which results in complete refresh of 143 radio.querySelector('span').innerText = timeRange.name;
82 // all metrics and event types that are currently selected. 144 timeDiv.appendChild(radio);
83 this.onReceiveIntervals = function(intervals) { 145 timeRange.element = input;
84 this.intervals = intervals; 146 }
85 for (var metric in this.metricMap) 147
Dan Beam 2012/07/06 23:49:36 move: var controller = this; to right here or
clintstaley 2012/07/09 22:03:22 Done.
148 timeDiv.addEventListener('click', function(e) {
149 if (!e.target.webkitMatchesSelector('input[type="radio"]'))
150 return;
151
152 controller.setTimeRange(e.target.timeRange);
153 });
154 },
155
156 /**
157 * Generalized function for setting up checkbox blocks for either events
158 * or metrics. Take a div ID |divId| into which to place the checkboxes,
159 * and a map |optionMap| with values that each include a property
160 * |description|. Set up one checkbox for each entry in |optionMap|
161 * labelled with that description. Arrange callbacks to function |check|
162 * or |uncheck|, passing them the key of the checked or unchecked option,
163 * when the relevant checkbox state changes.
164 * @param {!string} divId Id of division into which to put checkboxes
165 * @param {!Object} optionMap map of metric/event entries
166 * @param {!function(this:Controller, Object)} check
167 * function to select an entry (metric or event)
168 * @param {!function(this:Controller, Object)} uncheck
169 * function to deselect an entry (metric or event)
170 * @private
171 */
172 setupCheckboxes_: function(divId, optionMap, check, uncheck) {
173 var checkboxTemplate = $('#checkboxTemplate')[0];
174 var chooseMetricsDiv = $(divId)[0];
175
176 for (var option in optionMap) {
177 var checkbox = checkboxTemplate.cloneNode(true);
178 checkbox.querySelector('span').innerText = 'Show ' +
179 optionMap[option].description;
180 chooseMetricsDiv.appendChild(checkbox);
181
182 var input = checkbox.querySelector('input');
183 input.option = option;
184 input.addEventListener('change', function(e) {
185 if (e.target.checked)
186 check.call(this, e.target.option);
187 else
188 uncheck.call(this, e.target.option);
189 }.bind(this));
190 }
191 },
192
193 /**
194 * Set up just one chart in which all metrics will be displayed
195 * initially. But, the design readily accommodates addition of
196 * new charts, and movement of metrics into those other charts.
197 * @private
198 */
199 setupMainChart_: function() {
200 this.chartParent = $('#charts')[0];
201 this.charts = [document.createElement('div')];
202 this.charts[0].className = 'chart';
203 this.chartParent.appendChild(this.charts[0]);
204 },
205
206 /**
207 * Set the time range for which to display metrics and events. For
208 * now, the time range always ends at "now", but future implementations
209 * may allow time ranges not so anchored.
210 * @param {!{start: !number, end: !number, resolution: !number}} range
211 */
212 setTimeRange: function(range) {
213 this.range = range;
214 this.end = Math.floor(Date.now() / range.resolution) *
215 range.resolution;
216
217 // Take the GMT value of this.end ("now") and subtract from it the
218 // number of minutes by which we lag GMT in the present timezone,
219 // X mS/minute. This will show time in the present timezone.
220 this.end -= new Date().getTimezoneOffset() * 60000;
221 this.start = this.end - range.timeSpan;
222 this.requestIntervals();
223 },
224
225 /**
226 * Return mock interval set for testing.
227 * @return {!Array.<{start: !number, end: !number}>} intervals
228 */
229 getMockIntervals: function() {
230 var interval = this.end - this.start;
231
232 return [
233 {start: this.start + interval * .1,
234 end: this.start + interval * .2},
235 {start: this.start + interval * .7,
236 end: this.start + interval}
237 ];
238 },
239
240 /**
241 * Request activity intervals in the current time range.
242 */
243 requestIntervals: function() {
244 this.onReceiveIntervals(this.getMockIntervals());
245 // Replace with: chrome.send('getIntervals', this.start, this.end,
246 // this.onReceiveIntervals);
247 },
248
249 /**
250 * Webui callback delivering response from requestIntervals call. Assumes
251 * this is a new time range choice, which results in complete refresh of
252 * all metrics and event types that are currently selected.
253 * @param {!Array.<{start: !number, end: !number}>} intervals new intervals
254 */
255 onReceiveIntervals: function(intervals) {
256 this.intervals_ = intervals;
257
258 for (var metric in this.metricMap_) {
259 var metricValue = this.metricMap_[metric];
260 if (metricValue.divs.length > 0) // if we're displaying this metric.
261 this.refreshMetric(metric);
262 }
263
264 for (var eventType in this.eventMap_) {
265 var eventValue = this.eventMap_[eventType];
266 if (eventValue.divs.length > 0)
267 this.refreshEventType(eventType);
268 }
269 },
270
271 /**
272 * Add a new metric to the main (currently only) chart.
273 * @param {!string} metric Metric to start displaying
274 */
275 addMetric: function(metric) {
276 this.metricMap_[metric].divs.push(this.charts[0]);
86 this.refreshMetric(metric); 277 this.refreshMetric(metric);
87 for (var eventType in this.eventMap) 278 },
88 this.refreshEvent(eventType); 279
89 } 280 /**
90 281 * Remove a metric from the chart(s).
91 // Add a new metric, and its associated linegraph. The linegraph for 282 * @param {!string} metric Metric to stop displaying
92 // each metric has a discrete domain of times. This is not continuous 283 */
93 // because of breaks for each interval of activity. (No point in showing 284 dropMetric: function(metric) {
94 // a lot of "dead air" when the browser wasn't running.) Column 1 of 285 var metricValue = this.metricMap_[metric];
95 // its DataTable is the metric data, and higher numbered columns are added 286 var affectedCharts = metricValue.divs;
96 // in pairs for each event type currently chosen. Each pair gives the 287 metricValue.divs = [];
97 // event occurence points, and mouseover detailed description for one event 288
98 // type. 289 affectedCharts.forEach(this.drawChart, this);
99 this.addMetric = function(metric) { 290 },
100 if (!(metric in this.metricMap)) { 291
101 var div = document.createElement('div'); 292 /**
102 293 * Return mock metric data points for testing. Give values ranging from
103 div.className = 'chart'; 294 * offset to max-offset. (This lets us avoid direct overlap of
104 this.chartDiv.appendChild(div); 295 * different mock data sets in an ugly way that will die in the next
105 296 * version anyway.)
106 var table = new google.visualization.DataTable(); 297 * @param {!number} max Max data value to return, less offset
107 var chart = new google.visualization.LineChart(div); 298 * @param {!number} offset Adjustment factor
108 this.metricMap[metric] = {'div': div, 'chart': chart, 'table': table}; 299 * @return {!Array.<{time: !number, value: !number}>}
109 300 */
110 table.addColumn('string', 'Time'); // Default domain column 301 getMockDataPoints: function(max, offset) {
111 table.addColumn('number', 'Value'); // Only numerical range column 302 var dataPoints = [];
112 for (var event in this.events) 303
113 this.addEventColumns(table, event); 304 for (var i = 0; i < this.intervals_.length; i++) {
114 305 // Rise from low offset to high max-offset in 100 point steps.
115 this.refreshMetric(metric); 306 for (var time = this.intervals_[i].start;
307 time <= this.intervals_[i].end; time += this.range.resolution) {
308 var numPoints = time / this.range.resolution;
309 dataPoints.push({time: time, value: offset + (numPoints % 100) *
310 (max - 2 * offset) / 100});
311 }
312 }
313 return dataPoints;
314 },
315
316 /**
317 * Request new metric data, assuming the metric table and chart already
318 * exist.
319 * @param {!string} metric Metric for which to get data
320 */
321 refreshMetric: function(metric) {
322 var metricValue = this.metricMap_[metric];
323
324 metricValue.data = null; // Mark metric as awaiting response.
325 this.onReceiveMetric(metric,
326 this.getMockDataPoints(metricValue.yAxis.max, 5));
327 // Replace with:
328 // chrome.send("getMetric", this.range.start, this.range.end,
329 // this.range.resolution, this.onReceiveMetric);
330 },
331
332 /**
333 * Receive new datapoints for |metric|, convert the data to Flot-usable
334 * form, and redraw all affected charts.
335 * @param {!string} metric Metric to which |points| applies
336 * @param {!Array.<{time: !number, value: !number}>} points
337 * new data points
338 */
339 onReceiveMetric: function(metric, points) {
340 var metricValue = this.metricMap_[metric];
341
342 // Might have been dropped while waiting for data.
343 if (metricValue.divs.length == 0)
344 return;
345
346 var series = [];
347 metricValue.data = [series];
348
349 // Traverse the points, and the intervals, in parallel. Both are in
350 // ascending time order. Create a sequence of data "series" (per Flot)
351 // arrays, with each series comprising all points within a given interval.
352 var interval = this.intervals_[0];
353 var intervalIndex = 0;
354 var pointIndex = 0;
355 while (pointIndex < points.length &&
356 intervalIndex < this.intervals_.length) {
357 var point = points[pointIndex++];
358 while (intervalIndex < this.intervals_.length &&
359 point.time > interval.end) {
360 interval = this.intervals_[++intervalIndex]; // Jump to new interval.
361 if (series.length > 0) {
362 series = []; // Start a new series.
363 metricValue.data.push(series); // Put it on the end of the data.
364 }
365 }
366 if (intervalIndex < this.intervals_.length &&
367 point.time > interval.start)
368 series.push([point.time, point.value]);
369 }
370
371 metricValue.divs.forEach(this.drawChart, this);
372 },
373
374 /**
375 * Add a new event to the chart(s).
376 * @param {!string} eventType type of event to start displaying
377 */
378 addEventType: function(eventType) {
379 // Events show on all charts.
380 this.eventMap_[eventType].divs = this.charts;
381 this.refreshEventType(eventType);
382 },
383
384 /*
385 * Remove an event from the chart(s).
386 * @param {!string} eventType type of event to stop displaying
387 */
388 dropEventType: function(eventType) {
389 var eventValue = this.eventMap_[eventType];
390 var affectedCharts = eventValue.divs;
391 eventValue.divs = [];
392
393 affectedCharts.forEach(this.drawChart, this);
394 },
395
396 /**
397 * Return mock event points for testing.
398 * @param {!string} eventType type of event to generate mock data for
399 * @return {!Array.<{time: !number, longDescription: !string}>}
400 */
401 getMockEventValues: function(eventType) {
402 var mockValues = [];
403 for (var i = 0; i < this.intervals_.length; i++) {
404 var interval = this.intervals_[i];
405
406 mockValues.push({
407 time: interval.start,
408 longDescription: eventType + ' at ' +
409 new Date(interval.start) + ' blah, blah blah'});
410 mockValues.push({
411 time: (interval.start + interval.end) / 2,
412 longDescription: eventType + ' at ' +
413 new Date((interval.start + interval.end) / 2) + ' blah, blah'});
414 mockValues.push({
415 time: interval.end,
416 longDescription: eventType + ' at ' + new Date(interval.end) +
417 ' blah, blah blah'});
418 }
419 return mockValues;
420 },
421
422 /**
423 * Request new data for |eventType|, for times in the current range.
424 * @param {!string} eventType type of event to get new data for
425 */
426 refreshEventType: function(eventType) {
427 // Mark eventType as awaiting response.
428 this.eventMap_[eventType].data = null;
429 this.onReceiveEventType(eventType, this.getMockEventValues(eventType));
430 // Replace with:
431 // chrome.send("getEvents", eventType, this.range.start, this.range.end);
432 },
433
434 /**
435 * Receive new data for |eventType|. If the event has been deselected while
436 * awaiting webui response, do nothing. Otherwise, save the data directly,
437 * since events are handled differently than metrics when drawing
438 * (no "series"), and redraw all the affected charts.
439 * @param {!string} eventType type of event the new data applies to
440 * @param {!Array.<{time: !number, longDescription: !string}>} values
441 * new event values
442 */
443 onReceiveEventType: function(eventType, values) {
444 var eventValue = this.eventMap_[eventType];
445
446 if (eventValue.divs.length == 0)
447 return;
448
449 eventValue.data = values;
450 eventValue.divs.forEach(this.drawChart, this);
451 },
452
453 /**
454 * Return an object containing an array of metrics and another of events
455 * that include |chart| as one of the divs into which they display.
456 * @param {HTMLDivElement} chart div for which to get relevant items
457 * @return {!{metrics: !Array,<Object>, events: !Array.<Object>}}
458 * @private
459 */
460 getChartData_: function(chart) {
461 var result = {metrics: [], events: []};
462
463 for (var metric in this.metricMap_) {
464 var metricValue = this.metricMap_[metric];
465
466 if (metricValue.divs.indexOf(chart) != -1)
467 result.metrics.push(metricValue);
468 }
469
470 for (var eventType in this.eventMap_) {
471 var eventValue = this.eventMap_[eventType];
472
473 // Events post to all divs, if they post to any.
474 if (eventValue.divs.length > 0)
475 result.events.push(eventValue);
476 }
477
478 return result;
479 },
480
481 /**
482 * Check all entries in an object of the type returned from getChartData,
483 * above, to see if all events and metrics have completed data (none is
484 * awaiting an asynchronous webui response to get their current data).
485 * @param {!{metrics: !Array,<Object>, events: !Array.<Object>}} chartData
486 * event/metric data to check for readiness
487 * @return {boolean} is data ready?
488 * @private
489 */
490 isDataReady_: function(chartData) {
491 for (var i = 0; i < chartData.metrics.length; i++) {
492 if (!chartData.metrics[i].data)
493 return false;
494 }
495
496 for (var i = 0; i < chartData.events.length; i++) {
497 if (!chartData.events[i].data)
498 return false;
499 }
500
501 return true;
502 },
503
504 /**
505 * Create and return an array of "markings" (per Flot), representing
506 * vertical lines at the event time, in the event's color. Also add
507 * (not per Flot) a |description| property to each, to be used for hand
508 * creating description boxes.
509 * @param {!Array.<{
510 * description: !string,
511 * color: !string,
512 * data: !Array.<{time: !number}>
513 * }>} eventValues events to make markings for
514 * @return {!Array.<{
515 * color: !string,
516 * description: !string,
517 * xaxis: {from: !number, to: !number}
518 * }>} mark data structure for Flot to use
519 * @private
520 */
521 getEventMarks_: function(eventValues) {
522 var markings = [];
523
524 for (var i = 0; i < eventValues.length; i++) {
525 var eventValue = eventValues[i];
526 for (var d = 0; d < eventValue.data.length; d++) {
527 var point = eventValue.data[d];
528 markings.push({
529 color: eventValue.color,
530 description: eventValue.description,
531 xaxis: {from: point.time, to: point.time}
532 });
533 }
534 }
535
536 return markings;
537 },
538
539 /**
540 * Redraw the chart in div |chart|, *if* all its dependent data is present.
541 * Otherwise simply return, and await another call when all data is
542 * available.
543 * @param {HTMLDivElement} chart div to redraw
544 */
545 drawChart: function(chart) {
546 var chartData = this.getChartData_(chart);
547
548 if (!this.isDataReady_(chartData))
549 return;
550
551 var seriesSeq = [];
552 var yAxes = [];
553 chartData.metrics.forEach(function(value) {
554 yAxes.push(value.yAxis);
555 for (var i = 0; i < value.data.length; i++) {
556 seriesSeq.push({
557 color: value.yAxis.color,
558 data: value.data[i],
559 label: i == 0 ? value.description + ' (' + value.units + ')' : null,
560 yaxis: yAxes.length, // Use just-added Y axis.
561 });
562 }
563 });
564
565 var markings = this.getEventMarks_(chartData.events);
566 var chart = this.charts[0];
567 var plot = $.plot(chart, seriesSeq, {
568 yaxes: yAxes,
569 xaxis: {mode: 'time'},
570 grid: {markings: markings}
571 });
572
573 // For each event in |markings|, create also a label div, with left
574 // edge colinear with the event vertical-line. Top of label is
575 // presently a hack-in, putting labels in three tiers of 25px height
576 // each to avoid overlap. Will need something better.
577 var labelTemplate = $('#labelTemplate')[0];
578 for (var i = 0; i < markings.length; i++) {
579 var mark = markings[i];
580 var point =
581 plot.pointOffset({x: mark.xaxis.to, y: yAxes[0].max, yaxis: 1});
582 var labelDiv = labelTemplate.cloneNode(true);
583 labelDiv.innerText = mark.description;
584 labelDiv.style.left = point.left + 'px';
585 labelDiv.style.top = (point.top + 25 * (i % 3)) + 'px';
586 chart.appendChild(labelDiv);
587 }
116 } 588 }
117 } 589 };
118 590 return {
119 // Remove a metric from the UI 591 PerformanceMonitor: PerformanceMonitor
120 this.dropMetric = function(metric) { 592 };
121 if (metric in this.metricMap) { 593 };
Dan Beam 2012/07/06 23:49:36 }());
clintstaley 2012/07/09 22:03:22 Done.
122 this.chartDiv.removeChild(this.metricMap[metric].div); 594
123 delete this.metricMap[metric]; 595 var performanceMonitor = new Installer().PerformanceMonitor();
Dan Beam 2012/07/06 23:49:36 var performanceMonitor = new Installer.Performance
clintstaley 2012/07/09 22:03:22 Done.
124 }
125 }
126
127 // Return mock metric data points for testing
128 this.getMockDataPoints = function() {
129 var dataPoints = [];
130
131 for (var x = 0; x < this.intervals.length; x++) {
132 // Rise from low 0 to high 100 every 20 min
133 for (var time = this.intervals[x].start; time <= this.intervals[x].end;
134 time += this.range.resolution)
135 dataPoints.push({'time': time, 'value': time % 1000000 / 10000});
136 }
137 return dataPoints;
138 }
139
140 // Request new metric data, assuming the metric table and chart already
141 // exist.
142 this.refreshMetric = function(metric) {
143 this.onReceiveMetric(metric, this.getMockDataPoints());
144 // Replace with:
145 // chrome.send("getMetric", this.range.start, this.range.end,
146 // this.range.resolution, this.onReceiveMetric);
147 }
148
149 // Receive new datapoints for |metric|, and completely refresh the DataTable
150 // for that metric, redrawing the chart. (We cannot preserve the event
151 // columns because entirely new rows may be implied by the new metric
152 // datapoints.)
153 this.onReceiveMetric = function(metric, points) {
154 // Might have been dropped while waiting for data
155 if (!(metric in this.metricMap))
156 return;
157
158 var data = this.metricMap[metric].table;
159
160 data.removeRows(0, data.getNumberOfRows());
161
162 // Traverse the points, which are in time order, and the intervals,
163 // placing each value in the interval (if any) in which it belongs.
164 var interval = this.intervals[0];
165 var intervalIndex = 0;
166 var valueIndex = 0;
167 var value;
168 while (valueIndex < points.length &&
169 intervalIndex < this.intervals.length) {
170 value = points[valueIndex++];
171 while (value.time > interval.end &&
172 intervalIndex < this.intervals.length) {
173 interval = this.intervals[++intervalIndex]; // Jump to new interval
174 data.addRow(null, null); // Force gap in line chart
175 }
176 if (intervalIndex < this.intervals.length && value.time > interval.start)
177 if (data.getNumberOfRows() % this.range.labelEvery == 0)
178 data.addRow([new Date(value.time).toString(this.range.format),
179 value.value]);
180 else
181 data.addRow(['', value.value]);
182 }
183 this.drawChart(metric);
184 }
185
186 // Add a new event to all line graphs.
187 this.addEventType = function(eventType) {
188 if (!(eventType in this.eventMap)) {
189 this.eventMap[eventType] = [];
190 this.refreshEventType(eventType);
191 }
192 }
193
194 // Return mock event point for testing
195 this.getMockEventValues = function(eventType) {
196 var mockValues = [];
197 for (var i = 0; i < this.intervals.length; i++) {
198 var interval = this.intervals[i];
199
200 mockValues.push({
201 time: interval.start,
202 shortDescription: eventType,
203 longDescription: eventType + ' at ' +
204 new Date(interval.start) + ' blah, blah blah'});
205 mockValues.push({
206 time: (interval.start + interval.end) / 2,
207 shortDescription: eventType,
208 longDescription: eventType + ' at ' +
209 new Date((interval.start + interval.end) / 2) + ' blah, blah blah'});
210 mockValues.push({
211 time: interval.end,
212 shortDescription: eventType,
213 longDescription: eventType + ' at ' + new Date(interval.end) +
214 ' blah, blah blah'});
215 }
216 return mockValues;
217 }
218
219 // Request new data for |eventType|, for times in the current range.
220 this.refreshEventType = function(eventType) {
221 this.onReceiveEventType(eventType, this.getMockEventValues(eventType));
222 // Replace with:
223 // chrome.send("getEvents", eventType, this.range.start, this.range.end);
224 }
225
226 // Add an event column pair to DataTable |table| for |eventType|
227 this.addEventColumns = function(table, eventType) {
228 var annotationCol = table.addColumn({'id': eventType, type: 'string',
229 role: 'annotation'});
230 var rolloverCol = table.addColumn({'id': eventType + 'Tip', type: 'string',
231 role: 'annotationText'});
232 var values = this.eventMap[eventType];
233 var interval = this.intervals[0], intervalIndex = 0;
234
235 for (var i = 0; i < values.length; i++) {
236 var event = values[i];
237 var rowIndex = 0;
238 while (event.time > interval.end &&
239 intervalIndex < this.intervals.length - 1)
240 {
241 // Skip interval times, inclusive of interval.end, and of following null
242 rowIndex += (interval.end - interval.start) / this.range.resolution + 2;
243 interval = this.intervals[++intervalIndex];
244 }
245 if (event.time >= interval.start && event.time <= interval.end) {
246 table.setCell(rowIndex + (event.time - interval.start) /
247 this.range.resolution, annotationCol, event.shortDescription);
248 table.setCell(rowIndex + (event.time - interval.start) /
249 this.range.resolution, rolloverCol, event.longDescription);
250 }
251 }
252 }
253
254 this.dropEventColumns = function(table, eventType) {
255 var colIndex, numCols = table.getNumberOfColumns();
256
257 for (colIndex = 0; colIndex < numCols; colIndex++)
258 if (table.getColumnId(colIndex) == eventType)
259 break;
260
261 if (colIndex < numCols) {
262 table.removeColumn(colIndex + 1);
263 table.removeColumn(colIndex);
264 }
265 }
266
267 // Receive new data for |eventType|. Save this in eventMap for future
268 // redraws. Then, for each metric linegraph remove any current column pair
269 // for |eventType| and replace it with a new pair, which will reflect the
270 // new data. Redraw the linegraph.
271 this.onReceiveEventType = function(eventType, values) {
272 this.eventMap[eventType] = values;
273
274 for (var metric in this.metricMap) {
275 var table = this.metricMap[metric].table;
276
277 this.dropEventColumns(table, eventType);
278 this.addEventColumns(table, eventType);
279 this.drawChart(metric);
280 }
281 }
282
283 this.dropEventType = function(eventType) {
284 delete this.eventMap[eventType];
285
286 for (var metric in this.metricMap) {
287 var table = this.metricMap[metric].table;
288
289 this.dropEventColumns(table, eventType);
290 this.drawChart(metric);
291 }
292 }
293
294
295 // Redraw the linegraph for |metric|, assuming its DataTable is fully up to
296 // date.
297 this.drawChart = function(metric) {
298 var entry = this.metricMap[metric];
299
300 entry.chart.draw(entry.table, {title: metric + ' for ' + this.range.name,
301 hAxis: {showTextEvery: this.range.labelEvery}});
302 }
303
304 this.setupTimeRangeChooser = function() {
305 var controller = this;
306 var radioTemplate = $('#radioTemplate');
307
308 for (var time in this.TimeRange) {
309 var range = this.TimeRange[time];
310 var radio = radioTemplate.cloneNode(true);
311 var input = radio.querySelector('input');
312
313 input.value = range.value;
314 radio.querySelector('span').innerText = range.name;
315 this.timeDiv.appendChild(radio);
316 range.element = input;
317 radio.range = range;
318 radio.addEventListener('click', function() {
319 controller.setTimeRange(this.range);
320 });
321 }
322 }
323
324 this.setupMetricChooser = function(metricTypes) {
325 var checkboxTemplate = $('#checkboxTemplate');
326
327 metricTypes.forEach(function(metric) {
328 var checkbox = checkboxTemplate.cloneNode(true);
329 var input = checkbox.querySelector('input');
330 input.addEventListener('change', function() {
331 if (input.checked)
332 this.addMetric(metric);
333 else
334 this.dropMetric(metric);
335 }.bind(this));
336 checkbox.getElementsByTagName('span')[0].innerText = 'Show ' + metric;
337 this.chooseMetricsDiv.appendChild(checkbox);
338 }, this);
339 }
340
341 this.setupEventChooser = function(eventTypes) {
342 var checkboxTemplate = $('#checkboxTemplate');
343
344 eventTypes.forEach(function(event) {
345 var checkbox = checkboxTemplate.cloneNode(true);
346 var input = checkbox.querySelector('input');
347 input.addEventListener('change', function() {
348 if (input.checked)
349 this.addEventType(event);
350 else
351 this.dropEventType(event);
352 }.bind(this));
353 checkbox.getElementsByTagName('span')[0].innerText = 'Show ' + event;
354 this.chooseEventsDiv.appendChild(checkbox);
355 }, this);
356 }
357
358 this.setupMetricChooser(['Oddness', 'Jankiness']);
359 this.setupEventChooser(['Wampus Attack', 'Solar Eclipse']);
360 this.setupTimeRangeChooser();
361 this.TimeRange.day.element.click();
362 }
OLDNEW
« no previous file with comments | « chrome/browser/resources/performance_monitor/chart.html ('k') | third_party/flot/LICENSE.txt » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698