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

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

Issue 10820031: Modifications to performance monitor UI to address real webui. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Further updates reflecting last week's fixes and tweaks Created 8 years, 4 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
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';
Dan Beam 2012/08/15 20:53:53 I think you want to put this 'use strict'; inside
clintstaley 2012/08/16 00:20:25 Done.
6 6
7 /* convert to cr.define('PerformanceMonitor', function() {... 7 cr.define('performance_monitor', function() {
8 * when integrating into webui */
9
10 var Installer = function() {
11 /** 8 /**
12 * Enum for time ranges, giving a descriptive name, time span prior to |now|, 9 * Enum for time ranges, giving a descriptive name, time span prior to |now|,
13 * data point resolution, and time-label frequency and format for each. 10 * data point resolution, and time-label frequency and format for each.
14 * @enum {{ 11 * @enum {{
15 * value: !number, 12 * value: !number,
16 * name: !string, 13 * name: !string,
17 * timeSpan: !number, 14 * timeSpan: !number,
18 * resolution: !number, 15 * resolution: !number,
19 * labelEvery: !number, 16 * labelEvery: !number,
20 * format: !string 17 * format: !string
21 * }} 18 * }}
22 * @private 19 * @private
23 */ 20 */
24 var TimeRange_ = { 21 var TimeRange_ = {
22 // Prior 12 min, resolution of 1s, at most 720 points.
23 // Labels at 60 point (1 min) intervals.
24 minutes: {value: 0, name: 'Last 12 min', timeSpan: 720 * 1000,
25 resolution: 1000, labelEvery: 60, format: 'MM-dd'},
26
27 // Prior hour, resolution of 5s, at most 720 points.
28 // Labels at 60 point (5 min) intervals.
29 hour: {value: 1, name: 'Last Hour', timeSpan: 3600 * 1000,
30 resolution: 1000 * 5, labelEvery: 60, format: 'MM-dd'},
31
25 // Prior day, resolution of 2 min, at most 720 points. 32 // Prior day, resolution of 2 min, at most 720 points.
26 // Labels at 90 point (3 hour) intervals. 33 // Labels at 90 point (3 hour) intervals.
27 day: {value: 0, name: 'Last Day', timeSpan: 24 * 3600 * 1000, 34 day: {value: 2, name: 'Last Day', timeSpan: 24 * 3600 * 1000,
28 resolution: 1000 * 60 * 2, labelEvery: 90, format: 'MM-dd'}, 35 resolution: 1000 * 60 * 2, labelEvery: 90, format: 'MM-dd'},
29 36
30 // Prior week, resolution of 15 min -- at most 672 data points. 37 // Prior week, resolution of 15 min -- at most 672 data points.
31 // Labels at 96 point (daily) intervals. 38 // Labels at 96 point (daily) intervals.
32 week: {value: 1, name: 'Last Week', timeSpan: 7 * 24 * 3600 * 1000, 39 week: {value: 3, name: 'Last Week', timeSpan: 7 * 24 * 3600 * 1000,
33 resolution: 1000 * 60 * 15, labelEvery: 96, format: 'M/d'}, 40 resolution: 1000 * 60 * 15, labelEvery: 96, format: 'M/d'},
34 41
35 // Prior month (30 days), resolution of 1 hr -- at most 720 data points. 42 // Prior month (30 days), resolution of 1 hr -- at most 720 data points.
36 // Labels at 168 point (weekly) intervals. 43 // Labels at 168 point (weekly) intervals.
37 month: {value: 2, name: 'Last Month', timeSpan: 30 * 24 * 3600 * 1000, 44 month: {value: 4, name: 'Last Month', timeSpan: 30 * 24 * 3600 * 1000,
38 resolution: 1000 * 3600, labelEvery: 168, format: 'M/d'}, 45 resolution: 1000 * 3600, labelEvery: 168, format: 'M/d'},
39 46
40 // Prior quarter (90 days), resolution of 3 hr -- at most 720 data points. 47 // Prior quarter (90 days), resolution of 3 hr -- at most 720 data points.
41 // Labels at 112 point (fortnightly) intervals. 48 // Labels at 112 point (fortnightly) intervals.
42 quarter: {value: 3, name: 'Last Quarter', timeSpan: 90 * 24 * 3600 * 1000, 49 quarter: {value: 5, name: 'Last Quarter', timeSpan: 90 * 24 * 3600 * 1000,
43 resolution: 1000 * 3600 * 3, labelEvery: 112, format: 'M/yy'}, 50 resolution: 1000 * 3600 * 3, labelEvery: 112, format: 'M/yy'},
44 }; 51 };
45 52
53 /*
54 * Offset, in ms, by which to subtract to convert GMT to local time
55 * @type {!number}
56 */
57 var timezoneOffset = new Date().getTimezoneOffset() * 60000;
58
46 /** @constructor */ 59 /** @constructor */
47 function PerformanceMonitor() { 60 function PerformanceMonitor() {
48 this.__proto__ = PerformanceMonitor.prototype; 61 this.__proto__ = PerformanceMonitor.prototype;
49 /** 62 /**
50 * All metrics have entries, but those not displayed have an empty div list. 63 * All metrics have entries, but those not displayed have an empty div list.
51 * If a div list is not empty, the associated data will be non-null, or 64 * If a div list is not empty, the associated data will be non-null, or
52 * null but about to be filled by webui response. Thus, any metric with 65 * null but about to be filled by webui handler response. Thus, any metric
53 * non-empty div list but null data is awaiting a data response from the 66 * with non-empty div list but null data is awaiting a data response
54 * webui. 67 * from the webui handler. The webui handler uses numbers to uniquely
68 * identify metric and event types, so we use the same numbers (in
69 * string form) for the map key, repeating the id number in the |id|
70 * field, as a number.
55 * @type {Object.<string, { 71 * @type {Object.<string, {
72 * id: !number,
73 * description: !string,
74 * units: !string,
75 * yAxis: !{max: !number, color: !string},
56 * divs: !Array.<HTMLDivElement>, 76 * divs: !Array.<HTMLDivElement>,
57 * yAxis: !{max: !number, color: !string}, 77 * data: ?Array.<{time: !number, value: !number}>
58 * data: ?Array.<{time: !number, value: !number}>,
59 * description: !string,
60 * units: !string
61 * }>} 78 * }>}
62 * @private 79 * @private
63 */ 80 */
64 this.metricMap_ = { 81 this.metricMap_ = {};
65 jankiness: {
66 divs: [],
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 82
81 /* 83 /*
82 * Similar data for events, though no yAxis info is needed since events 84 * Similar data for events, though no yAxis info is needed since events
83 * are simply labelled markers at X locations. Rules regarding null data 85 * are simply labelled markers at X locations. Rules regarding null data
84 * with non-empty div list apply here as for metricMap_ above. 86 * with non-empty div list apply here as for metricMap_ above.
85 * @type {Object.<string, { 87 * @type {Object.<number, {
86 * divs: !Array.<HTMLDivElement>, 88 * id: !number,
87 * description: !string, 89 * description: !string,
88 * color: !string, 90 * color: !string,
91 * divs: !Array.<HTMLDivElement>,
89 * data: ?Array.<{time: !number, longDescription: !string}> 92 * data: ?Array.<{time: !number, longDescription: !string}>
Dan Beam 2012/08/15 20:53:53 nit: ! in front of number|string|boolean|function
clintstaley 2012/08/16 00:20:25 I guess I need to understand this a bit better. n
Dan Beam 2012/08/16 00:32:42 ! in front of number|boolean|string|function isn't
clintstaley 2012/08/17 16:37:56 Done.
90 * }>} 93 * }>}
91 * @private 94 * @private
92 */ 95 */
93 this.eventMap_ = { 96 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 97
108 /** 98 /**
109 * Time periods in which the browser was active and collecting metrics. 99 * Time periods in which the browser was active and collecting metrics
110 * and events. 100 * and events.
111 * @type {!Array.<{start: !number, end: !number}>} 101 * @type {!Array.<{start: !number, end: !number}>}
112 * @private 102 * @private
113 */ 103 */
114 this.intervals_ = []; 104 this.intervals_ = [];
115 105
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_(); 106 this.setupTimeRangeChooser_();
107 chrome.send('getAllEventTypes');
108 chrome.send('getAllMetricTypes');
121 this.setupMainChart_(); 109 this.setupMainChart_();
122 TimeRange_.day.element.click().call(this); 110 TimeRange_.day.element.click();
123 } 111 }
124 112
125 PerformanceMonitor.prototype = { 113 PerformanceMonitor.prototype = {
126 /** 114 /**
127 * Set up the radio button set to choose time range. Use div#radioTemplate 115 * Receive a list of all metrics, and populate |this.metricMap_| to
116 * reflect said list. Reconfigure the checkbox set for metric selection.
117 * @param {Array.<{metricType: !String, shortDescription: !String}>}
Dan Beam 2012/08/15 20:53:53 s/String/string/g (String = new String("blah"), st
clintstaley 2012/08/16 00:20:25 Sorry. Too much Java. :) Fixed here and elsewher
clintstaley 2012/08/17 16:37:56 Done.
118 * allMetrics All metrics from which to select.
119 */
120 getAllMetricTypesCallback: function(allMetrics) {
121 for (var i = 0; i < allMetrics.length; i++) {
122 var metric = allMetrics[i];
123
124 this.metricMap_[metric.metricType] = {
125 id: metric.metricType,
126 description: metric.shortDescription,
127 units: metric.units,
128 yAxis: {min: 0, max: metric.maxValue, color: 'rgb(0, 0, 255'},
Dan Beam 2012/08/15 20:53:53 color: 'rgba(0,0,255)' ^--- ri
clintstaley 2012/08/16 00:20:25 That's a Flot property, not HTML. Flot wants rgb n
Dan Beam 2012/08/16 00:32:42 a) you're missing an end ')', which is mainly what
clintstaley 2012/08/17 16:37:56 Doh. Sorry to miss that. And thanks for the JQue
129 divs: [],
130 data: null
131 };
132 }
133
134 this.setupCheckboxes_($('#choose-metrics')[0],
135 this.metricMap_, this.addMetric, this.dropMetric);
136 },
137
138 /**
139 * Receive a list of all event types, and populate |this.eventMap_| to
140 * reflect said list. Reconfigure the checkbox set for event selection.
141 * @param {Array.<{eventType: !String, shortDescription: !String}>}
142 * allEvents All events from which to select.
143 */
144 getAllEventTypesCallback: function(allEvents) {
145 for (var i = 0; i < allEvents.length; i++) {
146 var eventInfo = allEvents[i];
147
148 this.eventMap_[eventInfo.eventType] = {
149 id: eventInfo.eventType,
150 color: 'rgb(255, 0, 0)',
Dan Beam 2012/08/15 20:53:53 this could be 'red' and it'd probably be easier to
clintstaley 2012/08/16 00:20:25 Same answer, sadly. Flot requirement.
clintstaley 2012/08/17 16:37:56 Done.
151 data: null,
152 description: eventInfo.shortDescription,
153 divs: []
154 };
155 }
156
157 this.setupCheckboxes_($('#choose-events')[0],
158 this.eventMap_, this.addEventType, this.dropEventType);
159 },
160
161 /**
162 * Set up the radio button set to choose time range. Use div#radio-template
128 * as a template. 163 * as a template.
129 * @private 164 * @private
130 */ 165 */
131 setupTimeRangeChooser_: function() { 166 setupTimeRangeChooser_: function() {
132 var timeDiv = $('#chooseTimeRange')[0]; 167 var timeDiv = $('#choose-time-range')[0];
133 var radioTemplate = $('#radioTemplate')[0]; 168 var radioTemplate = $('#radio-template')[0];
134 169
135 for (var time in TimeRange_) { 170 for (var time in TimeRange_) {
136 var timeRange = TimeRange_[time]; 171 var timeRange = TimeRange_[time];
137 var radio = radioTemplate.cloneNode(true); 172 var radio = radioTemplate.cloneNode(true);
138 var input = radio.querySelector('input'); 173 var input = radio.querySelector('input');
139 174
140 input.value = timeRange.value; 175 input.value = timeRange.value;
141 input.timeRange = timeRange; 176 input.timeRange = timeRange;
142 radio.querySelector('span').innerText = timeRange.name; 177 radio.querySelector('span').innerText = timeRange.name;
143 timeDiv.appendChild(radio); 178 timeDiv.appendChild(radio);
144 timeRange.element = input; 179 timeRange.element = input;
145 } 180 }
146 181
147 timeDiv.addEventListener('click', function(e) { 182 timeDiv.addEventListener('click', function(e) {
148 if (!e.target.webkitMatchesSelector('input[type="radio"]')) 183 if (!e.target.webkitMatchesSelector('input[type="radio"]'))
149 return; 184 return;
150 185
151 this.setTimeRange(e.target.timeRange); 186 this.setTimeRange(e.target.timeRange);
152 }.bind(this)); 187 }.bind(this));
153 }, 188 },
154 189
155 /** 190 /**
156 * Generalized function for setting up checkbox blocks for either events 191 * Generalized function for setting up checkbox blocks for either events
157 * or metrics. Take a div ID |divId| into which to place the checkboxes, 192 * or metrics. Take a div |div| into which to place the checkboxes,
158 * and a map |optionMap| with values that each include a property 193 * and a map |optionMap| with values that each include a property
159 * |description|. Set up one checkbox for each entry in |optionMap| 194 * |description|. Set up one checkbox for each entry in |optionMap|
160 * labelled with that description. Arrange callbacks to function |check| 195 * labelled with that description. Arrange callbacks to function |check|
161 * or |uncheck|, passing them the key of the checked or unchecked option, 196 * or |uncheck|, passing them the key of the checked or unchecked option,
162 * when the relevant checkbox state changes. 197 * when the relevant checkbox state changes.
163 * @param {!string} divId Id of division into which to put checkboxes 198 * @param {!HTMLDivElement} div A <div> into which to put checkboxes.
164 * @param {!Object} optionMap map of metric/event entries 199 * @param {!Object} optionMap A map of metric/event entries.
165 * @param {!function(this:Controller, Object)} check 200 * @param {!function(this:Controller, Object)} check
166 * function to select an entry (metric or event) 201 * The function to select an entry (metric or event).
167 * @param {!function(this:Controller, Object)} uncheck 202 * @param {!function(this:Controller, Object)} uncheck
168 * function to deselect an entry (metric or event) 203 * The function to deselect an entry (metric or event).
169 * @private 204 * @private
170 */ 205 */
171 setupCheckboxes_: function(divId, optionMap, check, uncheck) { 206 setupCheckboxes_: function(div, optionMap, check, uncheck) {
172 var checkboxTemplate = $('#checkboxTemplate')[0]; 207 var checkboxTemplate = $('#checkbox-template')[0];
173 var chooseMetricsDiv = $(divId)[0];
174 208
175 for (var option in optionMap) { 209 for (var option in optionMap) {
176 var checkbox = checkboxTemplate.cloneNode(true); 210 var checkbox = checkboxTemplate.cloneNode(true);
177 checkbox.querySelector('span').innerText = 'Show ' + 211 checkbox.querySelector('span').innerText = 'Show ' +
178 optionMap[option].description; 212 optionMap[option].description;
179 chooseMetricsDiv.appendChild(checkbox); 213 div.appendChild(checkbox);
180 214
181 var input = checkbox.querySelector('input'); 215 var input = checkbox.querySelector('input');
182 input.option = option; 216 input.option = optionMap[option].id;
183 input.addEventListener('change', function(e) { 217 input.addEventListener('change', function(e) {
184 if (e.target.checked) 218 (e.target.checked ? check : uncheck).call(this, e.target.option);
185 check.call(this, e.target.option);
186 else
187 uncheck.call(this, e.target.option);
188 }.bind(this)); 219 }.bind(this));
189 } 220 }
190 }, 221 },
191 222
192 /** 223 /**
193 * Set up just one chart in which all metrics will be displayed 224 * Set up just one chart in which all metrics will be displayed
194 * initially. But, the design readily accommodates addition of 225 * initially. But, the design readily accommodates addition of
195 * new charts, and movement of metrics into those other charts. 226 * new charts, and movement of metrics into those other charts.
196 * @private 227 * @private
197 */ 228 */
198 setupMainChart_: function() { 229 setupMainChart_: function() {
199 this.chartParent = $('#charts')[0]; 230 this.chartParent = $('#charts')[0];
200 this.charts = [document.createElement('div')]; 231 this.charts = [document.createElement('div')];
201 this.charts[0].className = 'chart'; 232 this.charts[0].className = 'chart';
202 this.chartParent.appendChild(this.charts[0]); 233 this.chartParent.appendChild(this.charts[0]);
203 }, 234 },
204 235
205 /** 236 /**
206 * Set the time range for which to display metrics and events. For 237 * Set the time range for which to display metrics and events. For
207 * now, the time range always ends at "now", but future implementations 238 * now, the time range always ends at "now", but future implementations
208 * may allow time ranges not so anchored. 239 * may allow time ranges not so anchored.
209 * @param {!{start: !number, end: !number, resolution: !number}} range 240 * @param {!{start: !number, end: !number, resolution: !number}} range
241 * The time range for which to get display data.
210 */ 242 */
211 setTimeRange: function(range) { 243 setTimeRange: function(range) {
212 this.range = range; 244 this.range = range;
213 this.end = Math.floor(Date.now() / range.resolution) * 245 this.end = Math.floor(Date.now() / range.resolution) *
214 range.resolution; 246 range.resolution;
215 247
216 // Take the GMT value of this.end ("now") and subtract from it the
217 // number of minutes by which we lag GMT in the present timezone,
218 // X mS/minute. This will show time in the present timezone.
219 this.end -= new Date().getTimezoneOffset() * 60000;
220 this.start = this.end - range.timeSpan; 248 this.start = this.end - range.timeSpan;
221 this.requestIntervals(); 249 this.requestIntervals();
222 }, 250 },
223 251
224 /** 252 /**
225 * Return mock interval set for testing.
226 * @return {!Array.<{start: !number, end: !number}>} intervals
227 */
228 getMockIntervals: function() {
229 var interval = this.end - this.start;
230
231 return [
232 {start: this.start + interval * .1,
233 end: this.start + interval * .2},
234 {start: this.start + interval * .7,
235 end: this.start + interval}
236 ];
237 },
238
239 /**
240 * Request activity intervals in the current time range. 253 * Request activity intervals in the current time range.
241 */ 254 */
242 requestIntervals: function() { 255 requestIntervals: function() {
243 this.onReceiveIntervals(this.getMockIntervals()); 256 chrome.send('getActiveIntervals', [this.start, this.end]);
244 // Replace with: chrome.send('getIntervals', this.start, this.end,
245 // this.onReceiveIntervals);
246 }, 257 },
247 258
248 /** 259 /**
249 * Webui callback delivering response from requestIntervals call. Assumes 260 * Webui callback delivering response from requestIntervals call. Assumes
250 * this is a new time range choice, which results in complete refresh of 261 * this is a new time range choice, which results in complete refresh of
251 * all metrics and event types that are currently selected. 262 * all metrics and event types that are currently selected.
252 * @param {!Array.<{start: !number, end: !number}>} intervals new intervals 263 * @param {!Array.<{start: !number, end: !number}>} intervals
264 * The new intervals.
253 */ 265 */
254 onReceiveIntervals: function(intervals) { 266 getActiveIntervalsCallback: function(intervals) {
255 this.intervals_ = intervals; 267 this.intervals_ = intervals;
256 268
257 for (var metric in this.metricMap_) { 269 for (var metric in this.metricMap_) {
258 var metricValue = this.metricMap_[metric]; 270 var metricValue = this.metricMap_[metric];
259 if (metricValue.divs.length > 0) // if we're displaying this metric. 271 if (metricValue.divs.length > 0) // if we're displaying this metric.
260 this.refreshMetric(metric); 272 this.refreshMetric(metric);
261 } 273 }
262 274
263 for (var eventType in this.eventMap_) { 275 for (var eventType in this.eventMap_) {
264 var eventValue = this.eventMap_[eventType]; 276 var eventValue = this.eventMap_[eventType];
265 if (eventValue.divs.length > 0) 277 if (eventValue.divs.length > 0)
266 this.refreshEventType(eventType); 278 this.refreshEventType(eventValue.id);
267 } 279 }
268 }, 280 },
269 281
270 /** 282 /**
271 * Add a new metric to the main (currently only) chart. 283 * Add a new metric to the main (currently only) chart.
272 * @param {!string} metric Metric to start displaying 284 * @param {!string} metric The metric to start displaying.
273 */ 285 */
274 addMetric: function(metric) { 286 addMetric: function(metric) {
275 this.metricMap_[metric].divs.push(this.charts[0]); 287 this.metricMap_[metric].divs.push(this.charts[0]);
276 this.refreshMetric(metric); 288 this.refreshMetric(metric);
277 }, 289 },
278 290
279 /** 291 /**
280 * Remove a metric from the chart(s). 292 * Remove a metric from the chart(s).
281 * @param {!string} metric Metric to stop displaying 293 * @param {!string} metric The metric to stop displaying.
282 */ 294 */
283 dropMetric: function(metric) { 295 dropMetric: function(metric) {
284 var metricValue = this.metricMap_[metric]; 296 var metricValue = this.metricMap_[metric];
285 var affectedCharts = metricValue.divs; 297 var affectedCharts = metricValue.divs;
286 metricValue.divs = []; 298 metricValue.divs = [];
287 299
288 affectedCharts.forEach(this.drawChart, this); 300 affectedCharts.forEach(this.drawChart, this);
289 }, 301 },
290 302
291 /** 303 /**
292 * Return mock metric data points for testing. Give values ranging from
293 * offset to max-offset. (This lets us avoid direct overlap of
294 * different mock data sets in an ugly way that will die in the next
295 * version anyway.)
296 * @param {!number} max Max data value to return, less offset
297 * @param {!number} offset Adjustment factor
298 * @return {!Array.<{time: !number, value: !number}>}
299 */
300 getMockDataPoints: function(max, offset) {
301 var dataPoints = [];
302
303 for (var i = 0; i < this.intervals_.length; i++) {
304 // Rise from low offset to high max-offset in 100 point steps.
305 for (var time = this.intervals_[i].start;
306 time <= this.intervals_[i].end; time += this.range.resolution) {
307 var numPoints = time / this.range.resolution;
308 dataPoints.push({time: time, value: offset + (numPoints % 100) *
309 (max - 2 * offset) / 100});
310 }
311 }
312 return dataPoints;
313 },
314
315 /**
316 * Request new metric data, assuming the metric table and chart already 304 * Request new metric data, assuming the metric table and chart already
317 * exist. 305 * exist.
318 * @param {!string} metric Metric for which to get data 306 * @param {!string} metric The metric for which to get data.
319 */ 307 */
320 refreshMetric: function(metric) { 308 refreshMetric: function(metric) {
321 var metricValue = this.metricMap_[metric]; 309 var metricValue = this.metricMap_[metric];
322 310
323 metricValue.data = null; // Mark metric as awaiting response. 311 metricValue.data = null; // Mark metric as awaiting response.
324 this.onReceiveMetric(metric, 312 chrome.send('getMetric', [metricValue.id,
325 this.getMockDataPoints(metricValue.yAxis.max, 5)); 313 this.start, this.end, this.range.resolution]);
326 // Replace with:
327 // chrome.send("getMetric", this.range.start, this.range.end,
328 // this.range.resolution, this.onReceiveMetric);
329 }, 314 },
330 315
331 /** 316 /**
332 * Receive new datapoints for |metric|, convert the data to Flot-usable 317 * Receive new datapoints for a metric, convert the data to Flot-usable
333 * form, and redraw all affected charts. 318 * form, and redraw all affected charts.
334 * @param {!string} metric Metric to which |points| applies 319 * @param {!{
335 * @param {!Array.<{time: !number, value: !number}>} points 320 * type: !number,
336 * new data points 321 * points: !Array.<{time: !number, value: !number}>
322 * }} result An object giving metric ID, and time/value point pairs for
323 * that id.
337 */ 324 */
338 onReceiveMetric: function(metric, points) { 325 getMetricCallback: function(result) {
339 var metricValue = this.metricMap_[metric]; 326 var metricValue = this.metricMap_[result.metricType];
340
341 // Might have been dropped while waiting for data. 327 // Might have been dropped while waiting for data.
342 if (metricValue.divs.length == 0) 328 if (metricValue.divs.length == 0)
343 return; 329 return;
344 330
345 var series = []; 331 var series = [];
346 metricValue.data = [series]; 332 metricValue.data = [series]; // Data ends with current open series.
347 333
348 // Traverse the points, and the intervals, in parallel. Both are in 334 // Traverse the points, and the intervals, in parallel. Both are in
349 // ascending time order. Create a sequence of data "series" (per Flot) 335 // ascending time order. Create a sequence of data "series" (per Flot)
350 // arrays, with each series comprising all points within a given interval. 336 // arrays, with each series comprising all points within a given interval.
351 var interval = this.intervals_[0]; 337 var interval = this.intervals_[0];
352 var intervalIndex = 0; 338 var intervalIndex = 0;
353 var pointIndex = 0; 339 var pointIndex = 0;
354 while (pointIndex < points.length && 340 while (pointIndex < result.points.length &&
355 intervalIndex < this.intervals_.length) { 341 intervalIndex < this.intervals_.length) {
356 var point = points[pointIndex++]; 342 var point = result.points[pointIndex++];
357 while (intervalIndex < this.intervals_.length && 343 while (intervalIndex < this.intervals_.length &&
358 point.time > interval.end) { 344 point.time > interval.end) {
359 interval = this.intervals_[++intervalIndex]; // Jump to new interval. 345 interval = this.intervals_[++intervalIndex]; // Jump to new interval.
360 if (series.length > 0) { 346 if (series.length > 0) {
361 series = []; // Start a new series. 347 series = []; // Start a new series.
362 metricValue.data.push(series); // Put it on the end of the data. 348 metricValue.data.push(series); // Put it on the end of the data.
363 } 349 }
364 } 350 }
365 if (intervalIndex < this.intervals_.length && 351 if (intervalIndex < this.intervals_.length &&
366 point.time > interval.start) 352 point.time > interval.start) {
367 series.push([point.time, point.value]); 353 series.push([point.time - timezoneOffset, point.value]);
354 }
368 } 355 }
369
370 metricValue.divs.forEach(this.drawChart, this); 356 metricValue.divs.forEach(this.drawChart, this);
371 }, 357 },
372 358
373 /** 359 /**
374 * Add a new event to the chart(s). 360 * Add a new event to the chart(s).
375 * @param {!string} eventType type of event to start displaying 361 * @param {!string} eventType The type of event to start displaying.
376 */ 362 */
377 addEventType: function(eventType) { 363 addEventType: function(eventType) {
378 // Events show on all charts. 364 // Events show on all charts.
379 this.eventMap_[eventType].divs = this.charts; 365 this.eventMap_[eventType].divs = this.charts;
380 this.refreshEventType(eventType); 366 this.refreshEventType(eventType);
381 }, 367 },
382 368
383 /* 369 /*
384 * Remove an event from the chart(s). 370 * Remove an event from the chart(s).
385 * @param {!string} eventType type of event to stop displaying 371 * @param {!string} eventType The type of event to stop displaying.
386 */ 372 */
387 dropEventType: function(eventType) { 373 dropEventType: function(eventType) {
388 var eventValue = this.eventMap_[eventType]; 374 var eventValue = this.eventMap_[eventType];
389 var affectedCharts = eventValue.divs; 375 var affectedCharts = eventValue.divs;
390 eventValue.divs = []; 376 eventValue.divs = [];
391 377
392 affectedCharts.forEach(this.drawChart, this); 378 affectedCharts.forEach(this.drawChart, this);
393 }, 379 },
394 380
395 /** 381 /**
396 * Return mock event points for testing.
397 * @param {!string} eventType type of event to generate mock data for
398 * @return {!Array.<{time: !number, longDescription: !string}>}
399 */
400 getMockEventValues: function(eventType) {
401 var mockValues = [];
402 for (var i = 0; i < this.intervals_.length; i++) {
403 var interval = this.intervals_[i];
404
405 mockValues.push({
406 time: interval.start,
407 longDescription: eventType + ' at ' +
408 new Date(interval.start) + ' blah, blah blah'});
409 mockValues.push({
410 time: (interval.start + interval.end) / 2,
411 longDescription: eventType + ' at ' +
412 new Date((interval.start + interval.end) / 2) + ' blah, blah'});
413 mockValues.push({
414 time: interval.end,
415 longDescription: eventType + ' at ' + new Date(interval.end) +
416 ' blah, blah blah'});
417 }
418 return mockValues;
419 },
420
421 /**
422 * Request new data for |eventType|, for times in the current range. 382 * Request new data for |eventType|, for times in the current range.
423 * @param {!string} eventType type of event to get new data for 383 * @param {!string} eventType The type of event to get new data for.
424 */ 384 */
425 refreshEventType: function(eventType) { 385 refreshEventType: function(eventType) {
426 // Mark eventType as awaiting response. 386 // Mark eventType as awaiting response.
427 this.eventMap_[eventType].data = null; 387 this.eventMap_[eventType].data = null;
428 this.onReceiveEventType(eventType, this.getMockEventValues(eventType)); 388
429 // Replace with: 389 chrome.send('getEvents', [eventType, this.start, this.end]);
430 // chrome.send("getEvents", eventType, this.range.start, this.range.end);
431 }, 390 },
432 391
433 /** 392 /**
434 * Receive new data for |eventType|. If the event has been deselected while 393 * Receive new events for |eventType|. If |eventType| has been deselected
435 * awaiting webui response, do nothing. Otherwise, save the data directly, 394 * while awaiting webui handler response, do nothing. Otherwise, save the
436 * since events are handled differently than metrics when drawing 395 * data directly, since events are handled differently than metrics
437 * (no "series"), and redraw all the affected charts. 396 * when drawing (no "series"), and redraw all the affected charts.
438 * @param {!string} eventType type of event the new data applies to 397 * @param {!{
439 * @param {!Array.<{time: !number, longDescription: !string}>} values 398 * type: !number,
440 * new event values 399 * points: !Array.<{time: !number, longDescription: string}>
400 * }} result An object giving eventType ID, and time/description pairs for
401 * each event of that type in the range requested.
441 */ 402 */
442 onReceiveEventType: function(eventType, values) { 403 getEventsCallback: function(result) {
443 var eventValue = this.eventMap_[eventType]; 404 var eventValue = this.eventMap_[result.eventType];
444 405
445 if (eventValue.divs.length == 0) 406 if (eventValue.divs.length == 0)
446 return; 407 return;
447 408
448 eventValue.data = values; 409 result.points.forEach(function(element) {
410 console.log('Got event at time ' + element.time);
Dan Beam 2012/08/15 20:53:53 remove
clintstaley 2012/08/16 00:20:25 We're still building this thing, so some log messa
411 element.time -= timezoneOffset;
412 });
413
414 eventValue.data = result.points;
449 eventValue.divs.forEach(this.drawChart, this); 415 eventValue.divs.forEach(this.drawChart, this);
450 }, 416 },
451 417
452 /** 418 /**
453 * Return an object containing an array of metrics and another of events 419 * Return an object containing an array of metrics and another of events
454 * that include |chart| as one of the divs into which they display. 420 * that include |chart| as one of the divs into which they display.
455 * @param {HTMLDivElement} chart div for which to get relevant items 421 * @param {HTMLDivElement} chart The <div> for which to get relevant items.
456 * @return {!{metrics: !Array,<Object>, events: !Array.<Object>}} 422 * @return {!{metrics: !Array,<Object>, events: !Array.<Object>}}
457 * @private 423 * @private
458 */ 424 */
459 getChartData_: function(chart) { 425 getChartData_: function(chart) {
460 var result = {metrics: [], events: []}; 426 var result = {metrics: [], events: []};
461 427
462 for (var metric in this.metricMap_) { 428 for (var metric in this.metricMap_) {
463 var metricValue = this.metricMap_[metric]; 429 var metricValue = this.metricMap_[metric];
464 430
465 if (metricValue.divs.indexOf(chart) != -1) 431 if (metricValue.divs.indexOf(chart) != -1)
466 result.metrics.push(metricValue); 432 result.metrics.push(metricValue);
467 } 433 }
468 434
469 for (var eventType in this.eventMap_) { 435 for (var eventType in this.eventMap_) {
470 var eventValue = this.eventMap_[eventType]; 436 var eventValue = this.eventMap_[eventType];
471 437
472 // Events post to all divs, if they post to any. 438 // Events post to all divs, if they post to any.
473 if (eventValue.divs.length > 0) 439 if (eventValue.divs.length > 0)
474 result.events.push(eventValue); 440 result.events.push(eventValue);
475 } 441 }
476 442
477 return result; 443 return result;
478 }, 444 },
479 445
480 /** 446 /**
481 * Check all entries in an object of the type returned from getChartData, 447 * Check all entries in an object of the type returned from getChartData,
482 * above, to see if all events and metrics have completed data (none is 448 * above, to see if all events and metrics have completed data (none is
483 * awaiting an asynchronous webui response to get their current data). 449 * awaiting an asynchronous webui handler response to get their current
450 * data).
484 * @param {!{metrics: !Array,<Object>, events: !Array.<Object>}} chartData 451 * @param {!{metrics: !Array,<Object>, events: !Array.<Object>}} chartData
485 * event/metric data to check for readiness 452 * The event/metric data to check for readiness.
486 * @return {boolean} is data ready? 453 * @return {boolean} Is data ready?
Dan Beam 2012/08/15 20:53:53 nit: Whether the data is ready.
clintstaley 2012/08/16 00:20:25 Done.
487 * @private 454 * @private
488 */ 455 */
489 isDataReady_: function(chartData) { 456 isDataReady_: function(chartData) {
490 for (var i = 0; i < chartData.metrics.length; i++) { 457 for (var i = 0; i < chartData.metrics.length; i++) {
491 if (!chartData.metrics[i].data) 458 if (!chartData.metrics[i].data)
492 return false; 459 return false;
493 } 460 }
494 461
495 for (var i = 0; i < chartData.events.length; i++) { 462 for (var i = 0; i < chartData.events.length; i++) {
496 if (!chartData.events[i].data) 463 if (!chartData.events[i].data)
497 return false; 464 return false;
498 } 465 }
499 466
500 return true; 467 return true;
501 }, 468 },
502 469
503 /** 470 /**
504 * Create and return an array of "markings" (per Flot), representing 471 * Create and return an array of "markings" (per Flot), representing
505 * vertical lines at the event time, in the event's color. Also add 472 * vertical lines at the event time, in the event's color. Also add
506 * (not per Flot) a |description| property to each, to be used for hand 473 * (not per Flot) a |description| property to each, to be used for hand
507 * creating description boxes. 474 * creating description boxes.
508 * @param {!Array.<{ 475 * @param {!Array.<{
509 * description: !string, 476 * description: !string,
510 * color: !string, 477 * color: !string,
511 * data: !Array.<{time: !number}> 478 * data: !Array.<{time: !number}>
512 * }>} eventValues events to make markings for 479 * }>} eventValues The events to make markings for.
513 * @return {!Array.<{ 480 * @return {!Array.<{
514 * color: !string, 481 * color: !string,
515 * description: !string, 482 * description: !string,
516 * xaxis: {from: !number, to: !number} 483 * xaxis: {from: !number, to: !number}
517 * }>} mark data structure for Flot to use 484 * }>} A marks data structure for Flot to use.
518 * @private 485 * @private
519 */ 486 */
520 getEventMarks_: function(eventValues) { 487 getEventMarks_: function(eventValues) {
521 var markings = []; 488 var markings = [];
522 489
523 for (var i = 0; i < eventValues.length; i++) { 490 for (var i = 0; i < eventValues.length; i++) {
524 var eventValue = eventValues[i]; 491 var eventValue = eventValues[i];
525 for (var d = 0; d < eventValue.data.length; d++) { 492 for (var d = 0; d < eventValue.data.length; d++) {
526 var point = eventValue.data[d]; 493 var point = eventValue.data[d];
527 markings.push({ 494 if (point.time >= this.start - timezoneOffset &&
528 color: eventValue.color, 495 point.time <= this.end - timezoneOffset) {
529 description: eventValue.description, 496 markings.push({
530 xaxis: {from: point.time, to: point.time} 497 color: eventValue.color,
531 }); 498 description: eventValue.description,
499 xaxis: {from: point.time, to: point.time}
500 });
501 } else {
502 console.log('Event out of time range ' + this.start + ' -> ' +
Dan Beam 2012/08/15 20:53:53 remove or make error or warning?
clintstaley 2012/08/16 00:20:25 Agreed, but would like to do this in the next iter
503 this.end + ' at: ' + point.time);
504 }
532 } 505 }
533 } 506 }
534 507
535 return markings; 508 return markings;
536 }, 509 },
537 510
538 /** 511 /**
539 * Redraw the chart in div |chart|, *if* all its dependent data is present. 512 * Redraw the chart in div |chart|, *if* all its dependent data is present.
540 * Otherwise simply return, and await another call when all data is 513 * Otherwise simply return, and await another call when all data is
541 * available. 514 * available.
542 * @param {HTMLDivElement} chart div to redraw 515 * @param {HTMLDivElement} chart The <div> to redraw.
543 */ 516 */
544 drawChart: function(chart) { 517 drawChart: function(chart) {
545 var chartData = this.getChartData_(chart); 518 var chartData = this.getChartData_(chart);
546 519
547 if (!this.isDataReady_(chartData)) 520 if (!this.isDataReady_(chartData))
548 return; 521 return;
549 522
550 var seriesSeq = []; 523 var seriesSeq = [];
551 var yAxes = []; 524 var yAxes = [];
552 chartData.metrics.forEach(function(value) { 525 chartData.metrics.forEach(function(value) {
553 yAxes.push(value.yAxis); 526 yAxes.push(value.yAxis);
554 for (var i = 0; i < value.data.length; i++) { 527 for (var i = 0; i < value.data.length; i++) {
555 seriesSeq.push({ 528 seriesSeq.push({
556 color: value.yAxis.color, 529 color: value.yAxis.color,
557 data: value.data[i], 530 data: value.data[i],
558 label: i == 0 ? value.description + ' (' + value.units + ')' : null, 531 label: i == 0 ? value.description + ' (' + value.units + ')' : null,
559 yaxis: yAxes.length, // Use just-added Y axis. 532 yaxis: yAxes.length, // Use just-added Y axis.
560 }); 533 });
561 } 534 }
562 }); 535 });
563 536
537 // Ensure at least one y axis, as a reference for event placement.
538 if (yAxes.length == 0)
539 yAxes.push({max: 1.0});
540
564 var markings = this.getEventMarks_(chartData.events); 541 var markings = this.getEventMarks_(chartData.events);
565 var chart = this.charts[0]; 542 var chart = this.charts[0];
566 var plot = $.plot(chart, seriesSeq, { 543 var plot = $.plot(chart, seriesSeq, {
567 yaxes: yAxes, 544 yaxes: yAxes,
568 xaxis: {mode: 'time'}, 545 xaxis: {
546 mode: 'time',
547 min: this.start - timezoneOffset,
548 max: this.end - timezoneOffset
549 },
550 points: {show: true, radius: 1},
551 lines: {show: true},
569 grid: {markings: markings} 552 grid: {markings: markings}
570 }); 553 });
571 554
572 // For each event in |markings|, create also a label div, with left 555 // For each event in |markings|, create also a label div, with left
573 // edge colinear with the event vertical-line. Top of label is 556 // edge colinear with the event vertical line. Top of label is
574 // presently a hack-in, putting labels in three tiers of 25px height 557 // presently a hack-in, putting labels in three tiers of 25px height
575 // each to avoid overlap. Will need something better. 558 // each to avoid overlap. Will need something better.
576 var labelTemplate = $('#labelTemplate')[0]; 559 var labelTemplate = $('#label-template')[0];
577 for (var i = 0; i < markings.length; i++) { 560 for (var i = 0; i < markings.length; i++) {
578 var mark = markings[i]; 561 var mark = markings[i];
579 var point = 562 var point =
580 plot.pointOffset({x: mark.xaxis.to, y: yAxes[0].max, yaxis: 1}); 563 plot.pointOffset({x: mark.xaxis.to, y: yAxes[0].max, yaxis: 1});
581 var labelDiv = labelTemplate.cloneNode(true); 564 var labelDiv = labelTemplate.cloneNode(true);
582 labelDiv.innerText = mark.description; 565 labelDiv.innerText = mark.description;
583 labelDiv.style.left = point.left + 'px'; 566 labelDiv.style.left = point.left + 'px';
584 labelDiv.style.top = (point.top + 25 * (i % 3)) + 'px'; 567 labelDiv.style.top = (point.top + 25 * (i % 3)) + 'px';
568
585 chart.appendChild(labelDiv); 569 chart.appendChild(labelDiv);
586 } 570 }
587 } 571 }
588 }; 572 };
589 return { 573 return {
590 PerformanceMonitor: PerformanceMonitor 574 PerformanceMonitor: PerformanceMonitor
591 }; 575 };
592 }(); 576 });
593 577
594 var performanceMonitor = new Installer.PerformanceMonitor(); 578 var PerformanceMonitor = new performance_monitor.PerformanceMonitor();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698