1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * Plot a line graph of data versus time on a HTML canvas element.
8 * @param {HTMLCanvasElement} plotCanvas The canvas on which the line graph is
10 * @param {HTMLCanvasElement} legendCanvas The canvas on which the legend for
11 * the line graph is drawn.
12 * @param {Array<number>} tData The time (in seconds) in the past when the
13 * corresponding data in plots was sampled.
14 * @param {Array<{data: Array<number>, color: string}>} plots An
15 * array of plots to plot on the canvas. The field 'data' of a plot is an
16 * array of samples to be plotted as a line graph with color speficied by
17 * the field 'color'. The elements in the 'data' array are ordered
18 * corresponding to their sampling time in the argument 'tData'. Also, the
19 * number of elements in the 'data' array should be the same as in the time
20 * array 'tData' above.
21 * @param {number} yMin Minimum bound of y-axis
22 * @param {number} yMax Maximum bound of y-axis.
23 * @param {integer} yPrecision An integer value representing the number of
24 * digits of precision the y-axis data should be printed with.
26 function plotLineGraph(
27 plotCanvas
, legendCanvas
, tData
, plots
, yMin
, yMax
, yPrecision
) {
28 var textFont
= 12 * devicePixelRatio
+ 'px Arial';
29 var textHeight
= 12 * devicePixelRatio
;
30 var padding
= 5 * devicePixelRatio
; // Pixels
31 var errorOffsetPixels
= 15 * devicePixelRatio
;
32 var gridColor
= '#ccc';
33 var plotCtx
= plotCanvas
.getContext('2d');
34 var size
= tData
.length
;
36 function drawText(ctx
, text
, x
, y
) {
38 ctx
.fillStyle
= '#000';
39 ctx
.fillText(text
, x
, y
);
42 function printErrorText(ctx
, text
) {
43 ctx
.clearRect(0, 0, plotCanvas
.width
, plotCanvas
.height
);
44 drawText(ctx
, text
, errorOffsetPixels
, errorOffsetPixels
);
48 printErrorText(plotCtx
,
49 loadTimeData
.getString('notEnoughDataAvailableYet'));
53 for (var count
= 0; count
< plots
.length
; count
++) {
54 if (plots
[count
].data
.length
!= size
) {
55 throw new Error('Mismatch in time and plot data.');
59 function valueToString(value
) {
60 if (Math
.abs(value
) < 1) {
61 return Number(value
).toFixed(yPrecision
- 1);
63 return Number(value
).toPrecision(yPrecision
);
67 function getTextWidth(ctx
, text
) {
69 // For now, all text is drawn to the left of vertical lines, or centered.
70 // Add a 2 pixel padding so that there is some spacing between the text
71 // and the vertical line.
72 return Math
.round(ctx
.measureText(text
).width
) + 2 * devicePixelRatio
;
75 function getLegend(text
) {
76 return ' ' + text
+ ' ';
79 function drawHighlightText(ctx
, text
, x
, y
, color
) {
80 ctx
.strokeStyle
= '#000';
81 ctx
.strokeRect(x
, y
- textHeight
, getTextWidth(ctx
, text
), textHeight
);
82 ctx
.fillStyle
= color
;
83 ctx
.fillRect(x
, y
- textHeight
, getTextWidth(ctx
, text
), textHeight
);
84 ctx
.fillStyle
= '#fff';
85 ctx
.fillText(text
, x
, y
);
88 function drawLine(ctx
, x1
, y1
, x2
, y2
, color
) {
93 ctx
.strokeStyle
= color
;
94 ctx
.lineWidth
= 1 * devicePixelRatio
;
99 // The strokeRect method of the 2d context of a plotCanvas draws a bounding
100 // rectangle with an offset origin and greater dimensions. Hence, use this
101 // function to draw a rect at the desired location with desired dimensions.
102 function drawRect(ctx
, x
, y
, width
, height
, color
) {
103 var offset
= 1 * devicePixelRatio
;
104 drawLine(ctx
, x
, y
, x
+ width
- offset
, y
, color
);
105 drawLine(ctx
, x
, y
, x
, y
+ height
- offset
, color
);
106 drawLine(ctx
, x
, y
+ height
- offset
, x
+ width
- offset
,
107 y
+ height
- offset
, color
);
108 drawLine(ctx
, x
+ width
- offset
, y
, x
+ width
- offset
,
109 y
+ height
- offset
, color
);
112 function drawLegend() {
113 // Show a legend only if at least one individual plot has a name.
115 for (var i
= 0; i
< plots
.length
; i
++) {
116 if (plots
[i
].name
!= null) {
122 legendCanvas
.hidden
= true;
127 var padding
= 2 * devicePixelRatio
;
128 var legendSquareSide
= 12 * devicePixelRatio
;
129 var legendCtx
= legendCanvas
.getContext('2d');
132 // Adjust the height of the canvas before drawing on it.
133 for (var i
= 0; i
< plots
.length
; i
++) {
134 if (plots
[i
].name
== null) {
137 var legendText
= getLegend(plots
[i
].name
);
138 xLoc
+= legendSquareSide
+ getTextWidth(legendCtx
, legendText
) +
140 if (i
< plots
.length
- 1) {
141 var xLocNext
= xLoc
+
142 getTextWidth(legendCtx
, getLegend(plots
[i
+ 1].name
)) +
144 if (xLocNext
>= legendCanvas
.width
) {
146 yLoc
= yLoc
+ 2 * padding
+ textHeight
;
151 legendCanvas
.height
= yLoc
+ textHeight
+ padding
;
152 legendCanvas
.style
.height
=
153 legendCanvas
.height
/ devicePixelRatio
+ 'px';
157 // Go over the plots again, this time drawing the legends.
158 for (var i
= 0; i
< plots
.length
; i
++) {
159 legendCtx
.fillStyle
= plots
[i
].color
;
160 legendCtx
.fillRect(xLoc
, yLoc
, legendSquareSide
, legendSquareSide
);
161 xLoc
+= legendSquareSide
;
163 var legendText
= getLegend(plots
[i
].name
);
164 drawText(legendCtx
, legendText
, xLoc
, yLoc
+ textHeight
- 1);
165 xLoc
+= getTextWidth(legendCtx
, legendText
) + 2 * padding
;
167 if (i
< plots
.length
- 1) {
168 var xLocNext
= xLoc
+
169 getTextWidth(legendCtx
, getLegend(plots
[i
+ 1].name
)) +
171 if (xLocNext
>= legendCanvas
.width
) {
173 yLoc
= yLoc
+ 2 * padding
+ textHeight
;
179 var yMinStr
= valueToString(yMin
);
180 var yMaxStr
= valueToString(yMax
);
181 var yHalfStr
= valueToString((yMax
+ yMin
) / 2);
182 var yMinWidth
= getTextWidth(plotCtx
, yMinStr
);
183 var yMaxWidth
= getTextWidth(plotCtx
, yMaxStr
);
184 var yHalfWidth
= getTextWidth(plotCtx
, yHalfStr
);
186 var xMinStr
= tData
[0];
187 var xMaxStr
= tData
[size
- 1];
188 var xMinWidth
= getTextWidth(plotCtx
, xMinStr
);
189 var xMaxWidth
= getTextWidth(plotCtx
, xMaxStr
);
191 var xOrigin
= padding
+ Math
.max(yMinWidth
,
193 Math
.round(xMinWidth
/ 2));
194 var yOrigin
= padding
+ textHeight
;
195 var width
= plotCanvas
.width
- xOrigin
- Math
.floor(xMaxWidth
/ 2) - padding
;
197 plotCanvas
.width
+= size
- width
;
200 var height
= plotCanvas
.height
- yOrigin
- textHeight
- padding
;
201 var linePlotEndMarkerWidth
= 3;
203 function drawPlots() {
205 plotCtx
.clearRect(0, 0, plotCanvas
.width
, plotCanvas
.height
);
207 // Draw the bounding rectangle.
208 drawRect(plotCtx
, xOrigin
, yOrigin
, width
, height
, gridColor
);
210 // Draw the x and y bound values.
211 drawText(plotCtx
, yMaxStr
, xOrigin
- yMaxWidth
, yOrigin
+ textHeight
);
212 drawText(plotCtx
, yMinStr
, xOrigin
- yMinWidth
, yOrigin
+ height
);
215 xOrigin
- xMinWidth
/ 2,
216 yOrigin
+ height
+ textHeight
);
219 xOrigin
+ width
- xMaxWidth
/ 2,
220 yOrigin
+ height
+ textHeight
);
222 // Draw y-level (horizontal) lines.
224 xOrigin
+ 1, yOrigin
+ height
/ 4,
225 xOrigin
+ width
- 2, yOrigin
+ height
/ 4,
228 xOrigin
+ 1, yOrigin
+ height
/ 2,
229 xOrigin
+ width
- 2, yOrigin
+ height
/ 2, gridColor
);
231 xOrigin
+ 1, yOrigin
+ 3 * height
/ 4,
232 xOrigin
+ width
- 2, yOrigin
+ 3 * height
/ 4,
235 // Draw half-level value.
238 xOrigin
- yHalfWidth
,
239 yOrigin
+ height
/ 2 + textHeight
/ 2);
242 var yValRange
= yMax
- yMin
;
243 for (var count
= 0; count
< plots
.length
; count
++) {
244 var plot
= plots
[count
];
245 var yData
= plot
.data
;
246 plotCtx
.strokeStyle
= plot
.color
;
247 plotCtx
.lineWidth
= 2;
249 var beginPath
= true;
250 for (var i
= 0; i
< size
; i
++) {
252 if (typeof val
=== 'string') {
253 // Stroke the plot drawn so far and begin a fresh plot.
259 var xPos
= xOrigin
+ Math
.floor(i
/ (size
- 1) * (width
- 1));
260 var yPos
= yOrigin
+ height
- 1 -
261 Math
.round((val
- yMin
) / yValRange
* (height
- 1));
263 plotCtx
.moveTo(xPos
, yPos
);
264 // A simple move to does not print anything. Hence, draw a little
265 // square here to mark a beginning.
266 plotCtx
.fillStyle
= '#000';
267 plotCtx
.fillRect(xPos
- linePlotEndMarkerWidth
,
268 yPos
- linePlotEndMarkerWidth
,
269 linePlotEndMarkerWidth
* devicePixelRatio
,
270 linePlotEndMarkerWidth
* devicePixelRatio
);
273 plotCtx
.lineTo(xPos
, yPos
);
274 if (i
=== size
- 1 || typeof yData
[i
+ 1] === 'string') {
275 // Draw a little square to mark an end to go with the start
276 // markers from above.
277 plotCtx
.fillStyle
= '#000';
278 plotCtx
.fillRect(xPos
- linePlotEndMarkerWidth
,
279 yPos
- linePlotEndMarkerWidth
,
280 linePlotEndMarkerWidth
* devicePixelRatio
,
281 linePlotEndMarkerWidth
* devicePixelRatio
);
288 // Paint the missing time intervals with |gridColor|.
289 // Pick one of the plots to look for missing time intervals.
290 function drawMissingRect(start
, end
) {
291 var xLeft
= xOrigin
+ Math
.floor(start
/ (size
- 1) * (width
- 1));
292 var xRight
= xOrigin
+ Math
.floor(end
/ (size
- 1) * (width
- 1));
293 plotCtx
.fillStyle
= gridColor
;
294 // The x offsets below are present so that the blank space starts
295 // and ends between two valid samples.
296 plotCtx
.fillRect(xLeft
+ 1, yOrigin
, xRight
- xLeft
- 2, height
- 1);
298 var inMissingInterval
= false;
300 for (var i
= 0; i
< size
; i
++) {
301 if (typeof plots
[0].data
[i
] === 'string') {
302 if (!inMissingInterval
) {
303 inMissingInterval
= true;
304 // The missing interval should actually start from the previous
306 intervalStart
= Math
.max(i
- 1, 0);
310 // If this is the last sample, just draw missing rect.
311 drawMissingRect(intervalStart
, i
);
313 } else if (inMissingInterval
) {
314 inMissingInterval
= false;
315 drawMissingRect(intervalStart
, i
);
320 function drawTimeGuide(tDataIndex
) {
321 var x
= xOrigin
+ tDataIndex
/ (size
- 1) * (width
- 1);
322 drawLine(plotCtx
, x
, yOrigin
, x
, yOrigin
+ height
- 1, '#000');
325 x
- getTextWidth(plotCtx
, tData
[tDataIndex
]) / 2,
328 for (var count
= 0; count
< plots
.length
; count
++) {
329 var yData
= plots
[count
].data
;
331 // Draw small black square on the plot where the time guide intersects
333 var val
= yData
[tDataIndex
];
335 if (typeof val
=== 'string') {
336 yPos
= yOrigin
+ Math
.round(height
/ 2);
339 yPos
= yOrigin
+ height
- 1 -
340 Math
.round((val
- yMin
) / (yMax
- yMin
) * (height
- 1));
341 valStr
= valueToString(val
);
343 plotCtx
.fillStyle
= '#000';
344 plotCtx
.fillRect(x
- 2, yPos
- 2, 4, 4);
346 // Draw the val to right of the intersection.
348 if (yPos
- textHeight
/ 2 < yOrigin
) {
349 yLoc
= yOrigin
+ textHeight
;
350 } else if (yPos
+ textHeight
/ 2 >= yPos
+ height
) {
351 yLoc
= yOrigin
+ height
- 1;
353 yLoc
= yPos
+ textHeight
/ 2;
355 drawHighlightText(plotCtx
, valStr
, x
+ 5, yLoc
, plots
[count
].color
);
359 function onMouseOverOrMove(event
) {
362 var boundingRect
= plotCanvas
.getBoundingClientRect();
363 var x
= Math
.round((event
.clientX
- boundingRect
.left
) * devicePixelRatio
);
364 var y
= Math
.round((event
.clientY
- boundingRect
.top
) * devicePixelRatio
);
365 if (x
< xOrigin
|| x
>= xOrigin
+ width
||
366 y
< yOrigin
|| y
>= yOrigin
+ height
) {
371 drawTimeGuide(x
- xOrigin
);
373 drawTimeGuide(Math
.round((x
- xOrigin
) / (width
- 1) * (size
- 1)));
377 function onMouseOut(event
) {
383 plotCanvas
.addEventListener('mouseover', onMouseOverOrMove
);
384 plotCanvas
.addEventListener('mousemove', onMouseOverOrMove
);
385 plotCanvas
.addEventListener('mouseout', onMouseOut
);
388 var sleepSampleInterval
= 30 * 1000; // in milliseconds.
389 var sleepText
= loadTimeData
.getString('systemSuspended');
390 var invalidDataText
= loadTimeData
.getString('invalidData');
391 var offlineText
= loadTimeData
.getString('offlineText');
393 var plotColors
= ['Red', 'Blue', 'Green', 'Gold', 'CadetBlue', 'LightCoral',
394 'LightSlateGray', 'Peru', 'DarkRed', 'LawnGreen', 'Tan'];
397 * Add canvases for plotting to |plotsDiv|. For every header in |headerArray|,
398 * one canvas for the plot and one for its legend are added.
400 * @param {Array<string>} headerArray Headers for the different plots to be
401 * added to |plotsDiv|.
402 * @param {HTMLDivElement} plotsDiv The div element into which the canvases
404 * @return {<string>: {plotCanvas: <HTMLCanvasElement>,
405 * legendCanvas: <HTMLCanvasElement>} Returns an object
406 * with the headers as 'keys'. Each element is an object containing the
407 * legend canvas and the plot canvas that have been added to |plotsDiv|.
409 function addCanvases(headerArray
, plotsDiv
) {
410 // Remove the contents before adding new ones.
411 while (plotsDiv
.firstChild
!= null) {
412 plotsDiv
.removeChild(plotsDiv
.firstChild
);
414 var width
= Math
.floor(plotsDiv
.getBoundingClientRect().width
);
416 for (var i
= 0; i
< headerArray
.length
; i
++) {
417 var header
= document
.createElement('h4');
418 header
.textContent
= headerArray
[i
];
419 plotsDiv
.appendChild(header
);
421 var legendCanvas
= document
.createElement('canvas');
422 legendCanvas
.width
= width
* devicePixelRatio
;
423 legendCanvas
.style
.width
= width
+ 'px';
424 plotsDiv
.appendChild(legendCanvas
);
426 var plotCanvasDiv
= document
.createElement('div');
427 plotCanvasDiv
.style
.overflow
= 'auto';
428 plotsDiv
.appendChild(plotCanvasDiv
);
430 plotCanvas
= document
.createElement('canvas');
431 plotCanvas
.width
= width
* devicePixelRatio
;
432 plotCanvas
.height
= 200 * devicePixelRatio
;
433 plotCanvas
.style
.height
= '200px';
434 plotCanvasDiv
.appendChild(plotCanvas
);
436 canvases
[headerArray
[i
]] = {plot
: plotCanvas
, legend
: legendCanvas
};
442 * Add samples in |sampleArray| to individual plots in |plots|. If the system
443 * resumed from a sleep/suspend, then "suspended" sleep samples are added to
444 * the plot for the sleep duration.
446 * @param {Array<{data: Array<number>, color: string}>} plots An
447 * array of plots to plot on the canvas. The field 'data' of a plot is an
448 * array of samples to be plotted as a line graph with color speficied by
449 * the field 'color'. The elements in the 'data' array are ordered
450 * corresponding to their sampling time in the argument 'tData'. Also, the
451 * number of elements in the 'data' array should be the same as in the time
452 * array 'tData' below.
453 * @param {Array<number>} tData The time (in seconds) in the past when the
454 * corresponding data in plots was sampled.
455 * @param {Array<number>} absTime
456 * @param {Array<number>} sampleArray The array of samples wherein each
457 * element corresponds to the individual plot in |plots|.
458 * @param {number} sampleTime Time in milliseconds since the epoch when the
459 * samples in |sampleArray| were captured.
460 * @param {number} previousSampleTime Time in milliseconds since the epoch
461 * when the sample prior to the current sample was captured.
462 * @param {Array<{time: number, sleepDuration: number}>} systemResumedArray An
463 * array objects corresponding to system resume events. The 'time' field is
464 * for the time in milliseconds since the epoch when the system resumed. The
465 * 'sleepDuration' field is for the time in milliseconds the system spent
466 * in sleep/suspend state.
468 function addTimeDataSample(plots
, tData
, absTime
, sampleArray
,
469 sampleTime
, previousSampleTime
,
470 systemResumedArray
) {
471 for (var i
= 0; i
< plots
.length
; i
++) {
472 if (plots
[i
].data
.length
!= tData
.length
) {
473 throw new Error('Mismatch in time and plot data.');
478 if (tData
.length
== 0) {
479 time
= new Date(sampleTime
);
480 absTime
[0] = sampleTime
;
481 tData
[0] = time
.toLocaleTimeString();
482 for (var i
= 0; i
< plots
.length
; i
++) {
483 plots
[i
].data
[0] = sampleArray
[i
];
488 for (var i
= 0; i
< systemResumedArray
.length
; i
++) {
489 var resumeTime
= systemResumedArray
[i
].time
;
490 var sleepDuration
= systemResumedArray
[i
].sleepDuration
;
491 var sleepStartTime
= resumeTime
- sleepDuration
;
492 if (resumeTime
< sampleTime
) {
493 if (sleepStartTime
< previousSampleTime
) {
494 // This can happen if pending callbacks were handled before actually
496 sleepStartTime
= previousSampleTime
+ 1000;
498 // Add sleep samples for every |sleepSampleInterval|.
499 var sleepSampleTime
= sleepStartTime
;
500 while (sleepSampleTime
< resumeTime
) {
501 time
= new Date(sleepSampleTime
);
502 absTime
.push(sleepSampleTime
);
503 tData
.push(time
.toLocaleTimeString());
504 for (var j
= 0; j
< plots
.length
; j
++) {
505 plots
[j
].data
.push(sleepText
);
507 sleepSampleTime
+= sleepSampleInterval
;
512 time
= new Date(sampleTime
);
513 absTime
.push(sampleTime
);
514 tData
.push(time
.toLocaleTimeString());
515 for (var i
= 0; i
< plots
.length
; i
++) {
516 plots
[i
].data
.push(sampleArray
[i
]);
521 * Display the battery charge vs time on a line graph.
523 * @param {Array<{time: number,
524 * batteryPercent: number,
525 * batteryDischargeRate: number,
526 * externalPower: number}>} powerSupplyArray An array of objects
527 * with fields representing the battery charge, time when the charge
528 * measurement was taken, and whether there was external power connected at
530 * @param {Array<{time: ?, sleepDuration: ?}>} systemResumedArray An array
531 * objects with fields 'time' and 'sleepDuration'. Each object corresponds
532 * to a system resume event. The 'time' field is for the time in
533 * milliseconds since the epoch when the system resumed. The 'sleepDuration'
534 * field is for the time in milliseconds the system spent in sleep/suspend
537 function showBatteryChargeData(powerSupplyArray
, systemResumedArray
) {
538 var chargeTimeData
= [];
539 var chargeAbsTime
= [];
542 name
: loadTimeData
.getString('batteryChargePercentageHeader'),
547 var dischargeRateTimeData
= [];
548 var dischargeRateAbsTime
= [];
549 var dischargeRatePlot
= [
551 name
: loadTimeData
.getString('dischargeRateLegendText'),
556 name
: loadTimeData
.getString('movingAverageLegendText'),
561 name
: loadTimeData
.getString('binnedAverageLegendText'),
566 var minDischargeRate
= 1000; // A high unrealistic number to begin with.
567 var maxDischargeRate
= -1000; // A low unrealistic number to begin with.
568 for (var i
= 0; i
< powerSupplyArray
.length
; i
++) {
569 var j
= Math
.max(i
- 1, 0);
571 addTimeDataSample(chargePlot
,
574 [powerSupplyArray
[i
].batteryPercent
],
575 powerSupplyArray
[i
].time
,
576 powerSupplyArray
[j
].time
,
579 var dischargeRate
= powerSupplyArray
[i
].batteryDischargeRate
;
580 var inputSampleCount
= $('sample-count-input').value
;
582 var movingAverage
= 0;
584 for (k
= 0; k
< inputSampleCount
&& i
- k
>= 0; k
++) {
585 movingAverage
+= powerSupplyArray
[i
- k
].batteryDischargeRate
;
587 // |k| will be atleast 1 because the 'min' value of the input field is 1.
590 var binnedAverage
= 0;
591 for (k
= 0; k
< inputSampleCount
; k
++) {
592 var currentSampleIndex
= i
- i
% inputSampleCount
+ k
;
593 if (currentSampleIndex
>= powerSupplyArray
.length
) {
598 powerSupplyArray
[currentSampleIndex
].batteryDischargeRate
;
602 minDischargeRate
= Math
.min(dischargeRate
, minDischargeRate
);
603 maxDischargeRate
= Math
.max(dischargeRate
, maxDischargeRate
);
604 addTimeDataSample(dischargeRatePlot
,
605 dischargeRateTimeData
,
606 dischargeRateAbsTime
,
607 [dischargeRate
, movingAverage
, binnedAverage
],
608 powerSupplyArray
[i
].time
,
609 powerSupplyArray
[j
].time
,
612 if (minDischargeRate
== maxDischargeRate
) {
613 // This means that all the samples had the same value. Hence, offset the
614 // extremes by a bit so that the plot looks good.
615 minDischargeRate
-= 1;
616 maxDischargeRate
+= 1;
619 plotsDiv
= $('battery-charge-plots-div');
621 canvases
= addCanvases(
622 [loadTimeData
.getString('batteryChargePercentageHeader'),
623 loadTimeData
.getString('batteryDischargeRateHeader')],
626 batteryChargeCanvases
= canvases
[
627 loadTimeData
.getString('batteryChargePercentageHeader')];
629 batteryChargeCanvases
['plot'],
630 batteryChargeCanvases
['legend'],
637 dischargeRateCanvases
= canvases
[
638 loadTimeData
.getString('batteryDischargeRateHeader')];
640 dischargeRateCanvases
['plot'],
641 dischargeRateCanvases
['legend'],
642 dischargeRateTimeData
,
650 * Shows state occupancy data (CPU idle or CPU freq state occupancy) on a set of
651 * plots on the about:power UI.
653 * @param {Array<Array<{
655 * cpuOnline: boolean,
656 * timeInState: Object<number>}>} timeInStateData Array of arrays
657 * where each array corresponds to a CPU on the system. The elements of the
658 * individual arrays contain state occupancy samples.
659 * @param {Array<{time: ?, sleepDuration: ?}>} systemResumedArray An array
660 * objects with fields 'time' and 'sleepDuration'. Each object corresponds
661 * to a system resume event. The 'time' field is for the time in
662 * milliseconds since the epoch when the system resumed. The 'sleepDuration'
663 * field is for the time in milliseconds the system spent in sleep/suspend
665 * @param {string} i18nHeaderString The header string to be displayed with each
666 * plot. For example, CPU idle data will have its own header format, and CPU
667 * freq data will have its header format.
668 * @param {string} unitString This is the string capturing the unit, if any,
669 * for the different states. Note that this is not the unit of the data
671 * @param {HTMLDivElement} plotsDivId The div element in which the plots should
674 function showStateOccupancyData(timeInStateData
,
680 for (var cpu
= 0; cpu
< timeInStateData
.length
; cpu
++) {
681 var cpuData
= timeInStateData
[cpu
];
682 if (cpuData
.length
== 0) {
683 cpuPlots
[cpu
] = {plots
: [], tData
: []};
688 // Each element of |plots| is an array of samples, one for each of the CPU
689 // states. The number of states is dicovered by looking at the first
690 // sample for which the CPU is online.
692 var stateIndexMap
= [];
694 for (var i
= 0; i
< cpuData
.length
; i
++) {
695 if (cpuData
[i
].cpuOnline
) {
696 for (var state
in cpuData
[i
].timeInState
) {
697 var stateName
= state
;
698 if (unitString
!= null) {
699 stateName
+= ' ' + unitString
;
704 color
: plotColors
[stateCount
]
706 stateIndexMap
.push(state
);
712 // If stateCount is 0, then it means the CPU has been offline
713 // throughout. Just add a single plot for such a case.
714 if (stateCount
== 0) {
720 stateCount
= 1; // Some invalid state!
723 // Pass the samples through the function addTimeDataSample to add 'sleep'
725 for (var i
= 0; i
< cpuData
.length
; i
++) {
726 var sample
= cpuData
[i
];
728 for (var j
= 0; j
< stateCount
; j
++) {
729 if (sample
.cpuOnline
) {
730 valArray
[j
] = sample
.timeInState
[stateIndexMap
[j
]];
732 valArray
[j
] = offlineText
;
736 var k
= Math
.max(i
- 1, 0);
737 addTimeDataSample(plots
,
746 // Calculate the percentage occupancy of each state. A valid number is
747 // possible only if two consecutive samples are valid/numbers.
748 for (var k
= 0; k
< stateCount
; k
++) {
749 var stateData
= plots
[k
].data
;
750 // Skip the first sample as there is no previous sample.
751 for (var i
= stateData
.length
- 1; i
> 0; i
--) {
752 if (typeof stateData
[i
] === 'number') {
753 if (typeof stateData
[i
- 1] === 'number') {
754 stateData
[i
] = (stateData
[i
] - stateData
[i
- 1]) /
755 (absTime
[i
] - absTime
[i
- 1]) * 100;
757 stateData
[i
] = invalidDataText
;
763 // Remove the first sample from the time and data arrays.
765 for (var k
= 0; k
< stateCount
; k
++) {
766 plots
[k
].data
.shift();
768 cpuPlots
[cpu
] = {plots
: plots
, tData
: tData
};
772 for (var cpu
= 0; cpu
< timeInStateData
.length
; cpu
++) {
774 'CPU ' + cpu
+ ' ' + loadTimeData
.getString(i18nHeaderString
);
777 canvases
= addCanvases(headers
, $(plotsDivId
));
778 for (var cpu
= 0; cpu
< timeInStateData
.length
; cpu
++) {
779 cpuCanvases
= canvases
[headers
[cpu
]];
780 plotLineGraph(cpuCanvases
['plot'],
781 cpuCanvases
['legend'],
782 cpuPlots
[cpu
]['tData'],
783 cpuPlots
[cpu
]['plots'],
790 function showCpuIdleData(idleStateData
, systemResumedArray
) {
791 showStateOccupancyData(idleStateData
,
793 'idleStateOccupancyPercentageHeader',
795 'cpu-idle-plots-div');
798 function showCpuFreqData(freqStateData
, systemResumedArray
) {
799 showStateOccupancyData(freqStateData
,
801 'frequencyStateOccupancyPercentageHeader',
803 'cpu-freq-plots-div');
806 function requestBatteryChargeData() {
807 chrome
.send('requestBatteryChargeData');
810 function requestCpuIdleData() {
811 chrome
.send('requestCpuIdleData');
814 function requestCpuFreqData() {
815 chrome
.send('requestCpuFreqData');
819 * Return a callback for the 'Show'/'Hide' buttons for each section of the
822 * @param {string} sectionId The ID of the section which is to be shown or
824 * @param {string} buttonId The ID of the 'Show'/'Hide' button.
825 * @param {function} requestFunction The function which should be invoked on
826 * 'Show' to request for data from chrome.
827 * @return {function} The button callback function.
829 function showHideCallback(sectionId
, buttonId
, requestFunction
) {
831 if ($(sectionId
).hidden
) {
832 $(sectionId
).hidden
= false;
833 $(buttonId
).textContent
= loadTimeData
.getString('hideButton');
836 $(sectionId
).hidden
= true;
837 $(buttonId
).textContent
= loadTimeData
.getString('showButton');
843 showBatteryChargeData
: showBatteryChargeData
,
844 showCpuIdleData
: showCpuIdleData
,
845 showCpuFreqData
: showCpuFreqData
848 document
.addEventListener('DOMContentLoaded', function() {
849 $('battery-charge-section').hidden
= true;
850 $('battery-charge-show-button').onclick
= showHideCallback(
851 'battery-charge-section',
852 'battery-charge-show-button',
853 requestBatteryChargeData
);
854 $('battery-charge-reload-button').onclick
= requestBatteryChargeData
;
855 $('sample-count-input').onclick
= requestBatteryChargeData
;
857 $('cpu-idle-section').hidden
= true;
858 $('cpu-idle-show-button').onclick
= showHideCallback(
859 'cpu-idle-section', 'cpu-idle-show-button', requestCpuIdleData
);
860 $('cpu-idle-reload-button').onclick
= requestCpuIdleData
;
862 $('cpu-freq-section').hidden
= true;
863 $('cpu-freq-show-button').onclick
= showHideCallback(
864 'cpu-freq-section', 'cpu-freq-show-button', requestCpuFreqData
);
865 $('cpu-freq-reload-button').onclick
= requestCpuFreqData
;