Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / power.js
blob3249e8608767716affd8351c64dcb32f49c7e00f
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.
5 /**
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
9 * drawn.
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) {
37 ctx.font = textFont;
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);
47 if (size < 2) {
48 printErrorText(plotCtx,
49 loadTimeData.getString('notEnoughDataAvailableYet'));
50 return;
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);
62 } else {
63 return Number(value).toPrecision(yPrecision);
67 function getTextWidth(ctx, text) {
68 ctx.font = textFont;
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) {
89 ctx.save();
90 ctx.beginPath();
91 ctx.moveTo(x1, y1);
92 ctx.lineTo(x2, y2);
93 ctx.strokeStyle = color;
94 ctx.lineWidth = 1 * devicePixelRatio;
95 ctx.stroke();
96 ctx.restore();
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.
114 var valid = false;
115 for (var i = 0; i < plots.length; i++) {
116 if (plots[i].name != null) {
117 valid = true;
118 break;
121 if (!valid) {
122 legendCanvas.hidden = true;
123 return;
127 var padding = 2 * devicePixelRatio;
128 var legendSquareSide = 12 * devicePixelRatio;
129 var legendCtx = legendCanvas.getContext('2d');
130 var xLoc = padding;
131 var yLoc = padding;
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) {
135 continue;
137 var legendText = getLegend(plots[i].name);
138 xLoc += legendSquareSide + getTextWidth(legendCtx, legendText) +
139 2 * padding;
140 if (i < plots.length - 1) {
141 var xLocNext = xLoc +
142 getTextWidth(legendCtx, getLegend(plots[i + 1].name)) +
143 legendSquareSide;
144 if (xLocNext >= legendCanvas.width) {
145 xLoc = padding;
146 yLoc = yLoc + 2 * padding + textHeight;
151 legendCanvas.height = yLoc + textHeight + padding;
152 legendCanvas.style.height =
153 legendCanvas.height / devicePixelRatio + 'px';
155 xLoc = padding;
156 yLoc = padding;
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)) +
170 legendSquareSide;
171 if (xLocNext >= legendCanvas.width) {
172 xLoc = padding;
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,
192 yMaxWidth,
193 Math.round(xMinWidth / 2));
194 var yOrigin = padding + textHeight;
195 var width = plotCanvas.width - xOrigin - Math.floor(xMaxWidth / 2) - padding;
196 if (width < size) {
197 plotCanvas.width += size - width;
198 width = size;
200 var height = plotCanvas.height - yOrigin - textHeight - padding;
201 var linePlotEndMarkerWidth = 3;
203 function drawPlots() {
204 // Start fresh.
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);
213 drawText(plotCtx,
214 xMinStr,
215 xOrigin - xMinWidth / 2,
216 yOrigin + height + textHeight);
217 drawText(plotCtx,
218 xMaxStr,
219 xOrigin + width - xMaxWidth / 2,
220 yOrigin + height + textHeight);
222 // Draw y-level (horizontal) lines.
223 drawLine(plotCtx,
224 xOrigin + 1, yOrigin + height / 4,
225 xOrigin + width - 2, yOrigin + height / 4,
226 gridColor);
227 drawLine(plotCtx,
228 xOrigin + 1, yOrigin + height / 2,
229 xOrigin + width - 2, yOrigin + height / 2, gridColor);
230 drawLine(plotCtx,
231 xOrigin + 1, yOrigin + 3 * height / 4,
232 xOrigin + width - 2, yOrigin + 3 * height / 4,
233 gridColor);
235 // Draw half-level value.
236 drawText(plotCtx,
237 yHalfStr,
238 xOrigin - yHalfWidth,
239 yOrigin + height / 2 + textHeight / 2);
241 // Draw the plots.
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;
248 plotCtx.beginPath();
249 var beginPath = true;
250 for (var i = 0; i < size; i++) {
251 var val = yData[i];
252 if (typeof val === 'string') {
253 // Stroke the plot drawn so far and begin a fresh plot.
254 plotCtx.stroke();
255 plotCtx.beginPath();
256 beginPath = true;
257 continue;
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));
262 if (beginPath) {
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);
271 beginPath = false;
272 } else {
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);
285 plotCtx.stroke();
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;
299 var intervalStart;
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
305 // sample.
306 intervalStart = Math.max(i - 1, 0);
309 if (i == size - 1) {
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');
323 drawText(plotCtx,
324 tData[tDataIndex],
325 x - getTextWidth(plotCtx, tData[tDataIndex]) / 2,
326 yOrigin - 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
332 // it.
333 var val = yData[tDataIndex];
334 var yPos, valStr;
335 if (typeof val === 'string') {
336 yPos = yOrigin + Math.round(height / 2);
337 valStr = val;
338 } else {
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.
347 var yLoc;
348 if (yPos - textHeight / 2 < yOrigin) {
349 yLoc = yOrigin + textHeight;
350 } else if (yPos + textHeight / 2 >= yPos + height) {
351 yLoc = yOrigin + height - 1;
352 } else {
353 yLoc = yPos + textHeight / 2;
355 drawHighlightText(plotCtx, valStr, x + 5, yLoc, plots[count].color);
359 function onMouseOverOrMove(event) {
360 drawPlots();
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) {
367 return;
370 if (width == size) {
371 drawTimeGuide(x - xOrigin);
372 } else {
373 drawTimeGuide(Math.round((x - xOrigin) / (width - 1) * (size - 1)));
377 function onMouseOut(event) {
378 drawPlots();
381 drawLegend();
382 drawPlots();
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
403 * are added.
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);
415 var canvases = {};
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};
438 return canvases;
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>} sampleArray The array of samples wherein each
456 * element corresponds to the individual plot in |plots|.
457 * @param {number} sampleTime Time in milliseconds since the epoch when the
458 * samples in |sampleArray| were captured.
459 * @param {number} previousSampleTime Time in milliseconds since the epoch
460 * when the sample prior to the current sample was captured.
461 * @param {Array.<{time: number, sleepDuration: number}>} systemResumedArray An
462 * array objects corresponding to system resume events. The 'time' field is
463 * for the time in milliseconds since the epoch when the system resumed. The
464 * 'sleepDuration' field is for the time in milliseconds the system spent
465 * in sleep/suspend state.
467 function addTimeDataSample(plots, tData, absTime, sampleArray,
468 sampleTime, previousSampleTime,
469 systemResumedArray) {
470 for (var i = 0; i < plots.length; i++) {
471 if (plots[i].data.length != tData.length) {
472 throw new Error('Mismatch in time and plot data.');
476 var time;
477 if (tData.length == 0) {
478 time = new Date(sampleTime);
479 absTime[0] = sampleTime;
480 tData[0] = time.toLocaleTimeString();
481 for (var i = 0; i < plots.length; i++) {
482 plots[i].data[0] = sampleArray[i];
484 return;
487 for (var i = 0; i < systemResumedArray.length; i++) {
488 var resumeTime = systemResumedArray[i].time;
489 var sleepDuration = systemResumedArray[i].sleepDuration;
490 var sleepStartTime = resumeTime - sleepDuration;
491 if (resumeTime < sampleTime) {
492 if (sleepStartTime < previousSampleTime) {
493 // This can happen if pending callbacks were handled before actually
494 // suspending.
495 sleepStartTime = previousSampleTime + 1000;
497 // Add sleep samples for every |sleepSampleInterval|.
498 var sleepSampleTime = sleepStartTime;
499 while (sleepSampleTime < resumeTime) {
500 time = new Date(sleepSampleTime);
501 absTime.push(sleepSampleTime);
502 tData.push(time.toLocaleTimeString());
503 for (var j = 0; j < plots.length; j++) {
504 plots[j].data.push(sleepText);
506 sleepSampleTime += sleepSampleInterval;
511 time = new Date(sampleTime);
512 absTime.push(sampleTime);
513 tData.push(time.toLocaleTimeString());
514 for (var i = 0; i < plots.length; i++) {
515 plots[i].data.push(sampleArray[i]);
520 * Display the battery charge vs time on a line graph.
522 * @param {Array.<{time: number,
523 * batteryPercent: number,
524 * batteryDischargeRate: number,
525 * externalPower: number}>} powerSupplyArray An array of objects
526 * with fields representing the battery charge, time when the charge
527 * measurement was taken, and whether there was external power connected at
528 * that time.
529 * @param {Array.<{time: ?, sleepDuration: ?}>} systemResumedArray An array
530 * objects with fields 'time' and 'sleepDuration'. Each object corresponds
531 * to a system resume event. The 'time' field is for the time in
532 * milliseconds since the epoch when the system resumed. The 'sleepDuration'
533 * field is for the time in milliseconds the system spent in sleep/suspend
534 * state.
536 function showBatteryChargeData(powerSupplyArray, systemResumedArray) {
537 var chargeTimeData = [];
538 var chargeAbsTime = [];
539 var chargePlot = [
541 name: loadTimeData.getString('batteryChargePercentageHeader'),
542 color: 'Blue',
543 data: []
546 var dischargeRateTimeData = [];
547 var dischargeRateAbsTime = [];
548 var dischargeRatePlot = [
550 name: loadTimeData.getString('dischargeRateLegendText'),
551 color: 'Red',
552 data: []
555 name: loadTimeData.getString('movingAverageLegendText'),
556 color: 'Green',
557 data: []
560 name: loadTimeData.getString('binnedAverageLegendText'),
561 color: 'Blue',
562 data: []
565 var minDischargeRate = 1000; // A high unrealistic number to begin with.
566 var maxDischargeRate = -1000; // A low unrealistic number to begin with.
567 for (var i = 0; i < powerSupplyArray.length; i++) {
568 var j = Math.max(i - 1, 0);
570 addTimeDataSample(chargePlot,
571 chargeTimeData,
572 chargeAbsTime,
573 [powerSupplyArray[i].batteryPercent],
574 powerSupplyArray[i].time,
575 powerSupplyArray[j].time,
576 systemResumedArray);
578 var dischargeRate = powerSupplyArray[i].batteryDischargeRate;
579 var inputSampleCount = $('sample-count-input').value;
581 var movingAverage = 0;
582 var k = 0;
583 for (k = 0; k < inputSampleCount && i - k >= 0; k++) {
584 movingAverage += powerSupplyArray[i - k].batteryDischargeRate;
586 // |k| will be atleast 1 because the 'min' value of the input field is 1.
587 movingAverage /= k;
589 var binnedAverage = 0;
590 for (k = 0; k < inputSampleCount; k++) {
591 var currentSampleIndex = i - i % inputSampleCount + k;
592 if (currentSampleIndex >= powerSupplyArray.length) {
593 break;
596 binnedAverage +=
597 powerSupplyArray[currentSampleIndex].batteryDischargeRate;
599 binnedAverage /= k;
601 minDischargeRate = Math.min(dischargeRate, minDischargeRate);
602 maxDischargeRate = Math.max(dischargeRate, maxDischargeRate);
603 addTimeDataSample(dischargeRatePlot,
604 dischargeRateTimeData,
605 dischargeRateAbsTime,
606 [dischargeRate, movingAverage, binnedAverage],
607 powerSupplyArray[i].time,
608 powerSupplyArray[j].time,
609 systemResumedArray);
611 if (minDischargeRate == maxDischargeRate) {
612 // This means that all the samples had the same value. Hence, offset the
613 // extremes by a bit so that the plot looks good.
614 minDischargeRate -= 1;
615 maxDischargeRate += 1;
618 plotsDiv = $('battery-charge-plots-div');
620 canvases = addCanvases(
621 [loadTimeData.getString('batteryChargePercentageHeader'),
622 loadTimeData.getString('batteryDischargeRateHeader')],
623 plotsDiv);
625 batteryChargeCanvases = canvases[
626 loadTimeData.getString('batteryChargePercentageHeader')];
627 plotLineGraph(
628 batteryChargeCanvases['plot'],
629 batteryChargeCanvases['legend'],
630 chargeTimeData,
631 chargePlot,
632 0.00,
633 100.00,
636 dischargeRateCanvases = canvases[
637 loadTimeData.getString('batteryDischargeRateHeader')];
638 plotLineGraph(
639 dischargeRateCanvases['plot'],
640 dischargeRateCanvases['legend'],
641 dischargeRateTimeData,
642 dischargeRatePlot,
643 minDischargeRate,
644 maxDischargeRate,
649 * Shows state occupancy data (CPU idle or CPU freq state occupancy) on a set of
650 * plots on the about:power UI.
652 * @param {Array.<{Array.<{
653 * time: number,
654 * cpuOnline:boolean,
655 * timeInState: {<string>: number}>}>} timeInStateData Array of arrays
656 * where each array corresponds to a CPU on the system. The elements of the
657 * individual arrays contain state occupancy samples.
658 * @param {Array.<{time: ?, sleepDuration: ?}>} systemResumedArray An array
659 * objects with fields 'time' and 'sleepDuration'. Each object corresponds
660 * to a system resume event. The 'time' field is for the time in
661 * milliseconds since the epoch when the system resumed. The 'sleepDuration'
662 * field is for the time in milliseconds the system spent in sleep/suspend
663 * state.
664 * @param {string} i18nHeaderString The header string to be displayed with each
665 * plot. For example, CPU idle data will have its own header format, and CPU
666 * freq data will have its header format.
667 * @param {string} unitString This is the string capturing the unit, if any,
668 * for the different states. Note that this is not the unit of the data
669 * being plotted.
670 * @param {HTMLDivElement} plotsDivId The div element in which the plots should
671 * be added.
673 function showStateOccupancyData(timeInStateData,
674 systemResumedArray,
675 i18nHeaderString,
676 unitString,
677 plotsDivId) {
678 var cpuPlots = [];
679 for (var cpu = 0; cpu < timeInStateData.length; cpu++) {
680 var cpuData = timeInStateData[cpu];
681 if (cpuData.length == 0) {
682 cpuPlots[cpu] = {plots: [], tData: []};
683 continue;
685 tData = [];
686 absTime = [];
687 // Each element of |plots| is an array of samples, one for each of the CPU
688 // states. The number of states is dicovered by looking at the first
689 // sample for which the CPU is online.
690 var plots = [];
691 var stateIndexMap = [];
692 var stateCount = 0;
693 for (var i = 0; i < cpuData.length; i++) {
694 if (cpuData[i].cpuOnline) {
695 for (var state in cpuData[i].timeInState) {
696 var stateName = state;
697 if (unitString != null) {
698 stateName += ' ' + unitString;
700 plots.push({
701 name: stateName,
702 data: [],
703 color: plotColors[stateCount]
705 stateIndexMap.push(state);
706 stateCount += 1;
708 break;
711 // If stateCount is 0, then it means the CPU has been offline
712 // throughout. Just add a single plot for such a case.
713 if (stateCount == 0) {
714 plots.push({
715 name: null,
716 data: [],
717 color: null
719 stateCount = 1; // Some invalid state!
722 // Pass the samples through the function addTimeDataSample to add 'sleep'
723 // samples.
724 for (var i = 0; i < cpuData.length; i++) {
725 var sample = cpuData[i];
726 var valArray = [];
727 for (var j = 0; j < stateCount; j++) {
728 if (sample.cpuOnline) {
729 valArray[j] = sample.timeInState[stateIndexMap[j]];
730 } else {
731 valArray[j] = offlineText;
735 var k = Math.max(i - 1, 0);
736 addTimeDataSample(plots,
737 tData,
738 absTime,
739 valArray,
740 sample.time,
741 cpuData[k].time,
742 systemResumedArray);
745 // Calculate the percentage occupancy of each state. A valid number is
746 // possible only if two consecutive samples are valid/numbers.
747 for (var k = 0; k < stateCount; k++) {
748 var stateData = plots[k].data;
749 // Skip the first sample as there is no previous sample.
750 for (var i = stateData.length - 1; i > 0; i--) {
751 if (typeof stateData[i] === 'number') {
752 if (typeof stateData[i - 1] === 'number') {
753 stateData[i] = (stateData[i] - stateData[i - 1]) /
754 (absTime[i] - absTime[i - 1]) * 100;
755 } else {
756 stateData[i] = invalidDataText;
762 // Remove the first sample from the time and data arrays.
763 tData.shift();
764 for (var k = 0; k < stateCount; k++) {
765 plots[k].data.shift();
767 cpuPlots[cpu] = {plots: plots, tData: tData};
770 headers = [];
771 for (var cpu = 0; cpu < timeInStateData.length; cpu++) {
772 headers[cpu] =
773 'CPU ' + cpu + ' ' + loadTimeData.getString(i18nHeaderString);
776 canvases = addCanvases(headers, $(plotsDivId));
777 for (var cpu = 0; cpu < timeInStateData.length; cpu++) {
778 cpuCanvases = canvases[headers[cpu]];
779 plotLineGraph(cpuCanvases['plot'],
780 cpuCanvases['legend'],
781 cpuPlots[cpu]['tData'],
782 cpuPlots[cpu]['plots'],
784 100,
789 function showCpuIdleData(idleStateData, systemResumedArray) {
790 showStateOccupancyData(idleStateData,
791 systemResumedArray,
792 'idleStateOccupancyPercentageHeader',
793 null,
794 'cpu-idle-plots-div');
797 function showCpuFreqData(freqStateData, systemResumedArray) {
798 showStateOccupancyData(freqStateData,
799 systemResumedArray,
800 'frequencyStateOccupancyPercentageHeader',
801 'MHz',
802 'cpu-freq-plots-div');
805 function requestBatteryChargeData() {
806 chrome.send('requestBatteryChargeData');
809 function requestCpuIdleData() {
810 chrome.send('requestCpuIdleData');
813 function requestCpuFreqData() {
814 chrome.send('requestCpuFreqData');
818 * Return a callback for the 'Show'/'Hide' buttons for each section of the
819 * about:power page.
821 * @param {string} sectionId The ID of the section which is to be shown or
822 * hidden.
823 * @param {string} buttonId The ID of the 'Show'/'Hide' button.
824 * @param {function} requestFunction The function which should be invoked on
825 * 'Show' to request for data from chrome.
826 * @return {function} The button callback function.
828 function showHideCallback(sectionId, buttonId, requestFunction) {
829 return function() {
830 if ($(sectionId).hidden) {
831 $(sectionId).hidden = false;
832 $(buttonId).textContent = loadTimeData.getString('hideButton');
833 requestFunction();
834 } else {
835 $(sectionId).hidden = true;
836 $(buttonId).textContent = loadTimeData.getString('showButton');
841 var powerUI = {
842 showBatteryChargeData: showBatteryChargeData,
843 showCpuIdleData: showCpuIdleData,
844 showCpuFreqData: showCpuFreqData
847 document.addEventListener('DOMContentLoaded', function() {
848 $('battery-charge-section').hidden = true;
849 $('battery-charge-show-button').onclick = showHideCallback(
850 'battery-charge-section',
851 'battery-charge-show-button',
852 requestBatteryChargeData);
853 $('battery-charge-reload-button').onclick = requestBatteryChargeData;
854 $('sample-count-input').onclick = requestBatteryChargeData;
856 $('cpu-idle-section').hidden = true;
857 $('cpu-idle-show-button').onclick = showHideCallback(
858 'cpu-idle-section', 'cpu-idle-show-button', requestCpuIdleData);
859 $('cpu-idle-reload-button').onclick = requestCpuIdleData;
861 $('cpu-freq-section').hidden = true;
862 $('cpu-freq-show-button').onclick = showHideCallback(
863 'cpu-freq-section', 'cpu-freq-show-button', requestCpuFreqData);
864 $('cpu-freq-reload-button').onclick = requestCpuFreqData;