summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/chromeos/power.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/power.js')
-rw-r--r--chromium/chrome/browser/resources/chromeos/power.js865
1 files changed, 865 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/power.js b/chromium/chrome/browser/resources/chromeos/power.js
new file mode 100644
index 00000000000..3249e860876
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/power.js
@@ -0,0 +1,865 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Plot a line graph of data versus time on a HTML canvas element.
+ *
+ * @param {HTMLCanvasElement} plotCanvas The canvas on which the line graph is
+ * drawn.
+ * @param {HTMLCanvasElement} legendCanvas The canvas on which the legend for
+ * the line graph is drawn.
+ * @param {Array.<number>} tData The time (in seconds) in the past when the
+ * corresponding data in plots was sampled.
+ * @param {Array.<{data: Array.<number>, color: string}>} plots An
+ * array of plots to plot on the canvas. The field 'data' of a plot is an
+ * array of samples to be plotted as a line graph with color speficied by
+ * the field 'color'. The elements in the 'data' array are ordered
+ * corresponding to their sampling time in the argument 'tData'. Also, the
+ * number of elements in the 'data' array should be the same as in the time
+ * array 'tData' above.
+ * @param {number} yMin Minimum bound of y-axis
+ * @param {number} yMax Maximum bound of y-axis.
+ * @param {integer} yPrecision An integer value representing the number of
+ * digits of precision the y-axis data should be printed with.
+ */
+function plotLineGraph(
+ plotCanvas, legendCanvas, tData, plots, yMin, yMax, yPrecision) {
+ var textFont = 12 * devicePixelRatio + 'px Arial';
+ var textHeight = 12 * devicePixelRatio;
+ var padding = 5 * devicePixelRatio; // Pixels
+ var errorOffsetPixels = 15 * devicePixelRatio;
+ var gridColor = '#ccc';
+ var plotCtx = plotCanvas.getContext('2d');
+ var size = tData.length;
+
+ function drawText(ctx, text, x, y) {
+ ctx.font = textFont;
+ ctx.fillStyle = '#000';
+ ctx.fillText(text, x, y);
+ }
+
+ function printErrorText(ctx, text) {
+ ctx.clearRect(0, 0, plotCanvas.width, plotCanvas.height);
+ drawText(ctx, text, errorOffsetPixels, errorOffsetPixels);
+ }
+
+ if (size < 2) {
+ printErrorText(plotCtx,
+ loadTimeData.getString('notEnoughDataAvailableYet'));
+ return;
+ }
+
+ for (var count = 0; count < plots.length; count++) {
+ if (plots[count].data.length != size) {
+ throw new Error('Mismatch in time and plot data.');
+ }
+ }
+
+ function valueToString(value) {
+ if (Math.abs(value) < 1) {
+ return Number(value).toFixed(yPrecision - 1);
+ } else {
+ return Number(value).toPrecision(yPrecision);
+ }
+ }
+
+ function getTextWidth(ctx, text) {
+ ctx.font = textFont;
+ // For now, all text is drawn to the left of vertical lines, or centered.
+ // Add a 2 pixel padding so that there is some spacing between the text
+ // and the vertical line.
+ return Math.round(ctx.measureText(text).width) + 2 * devicePixelRatio;
+ }
+
+ function getLegend(text) {
+ return ' ' + text + ' ';
+ }
+
+ function drawHighlightText(ctx, text, x, y, color) {
+ ctx.strokeStyle = '#000';
+ ctx.strokeRect(x, y - textHeight, getTextWidth(ctx, text), textHeight);
+ ctx.fillStyle = color;
+ ctx.fillRect(x, y - textHeight, getTextWidth(ctx, text), textHeight);
+ ctx.fillStyle = '#fff';
+ ctx.fillText(text, x, y);
+ }
+
+ function drawLine(ctx, x1, y1, x2, y2, color) {
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 1 * devicePixelRatio;
+ ctx.stroke();
+ ctx.restore();
+ }
+
+ // The strokeRect method of the 2d context of a plotCanvas draws a bounding
+ // rectangle with an offset origin and greater dimensions. Hence, use this
+ // function to draw a rect at the desired location with desired dimensions.
+ function drawRect(ctx, x, y, width, height, color) {
+ var offset = 1 * devicePixelRatio;
+ drawLine(ctx, x, y, x + width - offset, y, color);
+ drawLine(ctx, x, y, x, y + height - offset, color);
+ drawLine(ctx, x, y + height - offset, x + width - offset,
+ y + height - offset, color);
+ drawLine(ctx, x + width - offset, y, x + width - offset,
+ y + height - offset, color);
+ }
+
+ function drawLegend() {
+ // Show a legend only if at least one individual plot has a name.
+ var valid = false;
+ for (var i = 0; i < plots.length; i++) {
+ if (plots[i].name != null) {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid) {
+ legendCanvas.hidden = true;
+ return;
+ }
+
+
+ var padding = 2 * devicePixelRatio;
+ var legendSquareSide = 12 * devicePixelRatio;
+ var legendCtx = legendCanvas.getContext('2d');
+ var xLoc = padding;
+ var yLoc = padding;
+ // Adjust the height of the canvas before drawing on it.
+ for (var i = 0; i < plots.length; i++) {
+ if (plots[i].name == null) {
+ continue;
+ }
+ var legendText = getLegend(plots[i].name);
+ xLoc += legendSquareSide + getTextWidth(legendCtx, legendText) +
+ 2 * padding;
+ if (i < plots.length - 1) {
+ var xLocNext = xLoc +
+ getTextWidth(legendCtx, getLegend(plots[i + 1].name)) +
+ legendSquareSide;
+ if (xLocNext >= legendCanvas.width) {
+ xLoc = padding;
+ yLoc = yLoc + 2 * padding + textHeight;
+ }
+ }
+ }
+
+ legendCanvas.height = yLoc + textHeight + padding;
+ legendCanvas.style.height =
+ legendCanvas.height / devicePixelRatio + 'px';
+
+ xLoc = padding;
+ yLoc = padding;
+ // Go over the plots again, this time drawing the legends.
+ for (var i = 0; i < plots.length; i++) {
+ legendCtx.fillStyle = plots[i].color;
+ legendCtx.fillRect(xLoc, yLoc, legendSquareSide, legendSquareSide);
+ xLoc += legendSquareSide;
+
+ var legendText = getLegend(plots[i].name);
+ drawText(legendCtx, legendText, xLoc, yLoc + textHeight - 1);
+ xLoc += getTextWidth(legendCtx, legendText) + 2 * padding;
+
+ if (i < plots.length - 1) {
+ var xLocNext = xLoc +
+ getTextWidth(legendCtx, getLegend(plots[i + 1].name)) +
+ legendSquareSide;
+ if (xLocNext >= legendCanvas.width) {
+ xLoc = padding;
+ yLoc = yLoc + 2 * padding + textHeight;
+ }
+ }
+ }
+ }
+
+ var yMinStr = valueToString(yMin);
+ var yMaxStr = valueToString(yMax);
+ var yHalfStr = valueToString((yMax + yMin) / 2);
+ var yMinWidth = getTextWidth(plotCtx, yMinStr);
+ var yMaxWidth = getTextWidth(plotCtx, yMaxStr);
+ var yHalfWidth = getTextWidth(plotCtx, yHalfStr);
+
+ var xMinStr = tData[0];
+ var xMaxStr = tData[size - 1];
+ var xMinWidth = getTextWidth(plotCtx, xMinStr);
+ var xMaxWidth = getTextWidth(plotCtx, xMaxStr);
+
+ var xOrigin = padding + Math.max(yMinWidth,
+ yMaxWidth,
+ Math.round(xMinWidth / 2));
+ var yOrigin = padding + textHeight;
+ var width = plotCanvas.width - xOrigin - Math.floor(xMaxWidth / 2) - padding;
+ if (width < size) {
+ plotCanvas.width += size - width;
+ width = size;
+ }
+ var height = plotCanvas.height - yOrigin - textHeight - padding;
+ var linePlotEndMarkerWidth = 3;
+
+ function drawPlots() {
+ // Start fresh.
+ plotCtx.clearRect(0, 0, plotCanvas.width, plotCanvas.height);
+
+ // Draw the bounding rectangle.
+ drawRect(plotCtx, xOrigin, yOrigin, width, height, gridColor);
+
+ // Draw the x and y bound values.
+ drawText(plotCtx, yMaxStr, xOrigin - yMaxWidth, yOrigin + textHeight);
+ drawText(plotCtx, yMinStr, xOrigin - yMinWidth, yOrigin + height);
+ drawText(plotCtx,
+ xMinStr,
+ xOrigin - xMinWidth / 2,
+ yOrigin + height + textHeight);
+ drawText(plotCtx,
+ xMaxStr,
+ xOrigin + width - xMaxWidth / 2,
+ yOrigin + height + textHeight);
+
+ // Draw y-level (horizontal) lines.
+ drawLine(plotCtx,
+ xOrigin + 1, yOrigin + height / 4,
+ xOrigin + width - 2, yOrigin + height / 4,
+ gridColor);
+ drawLine(plotCtx,
+ xOrigin + 1, yOrigin + height / 2,
+ xOrigin + width - 2, yOrigin + height / 2, gridColor);
+ drawLine(plotCtx,
+ xOrigin + 1, yOrigin + 3 * height / 4,
+ xOrigin + width - 2, yOrigin + 3 * height / 4,
+ gridColor);
+
+ // Draw half-level value.
+ drawText(plotCtx,
+ yHalfStr,
+ xOrigin - yHalfWidth,
+ yOrigin + height / 2 + textHeight / 2);
+
+ // Draw the plots.
+ var yValRange = yMax - yMin;
+ for (var count = 0; count < plots.length; count++) {
+ var plot = plots[count];
+ var yData = plot.data;
+ plotCtx.strokeStyle = plot.color;
+ plotCtx.lineWidth = 2;
+ plotCtx.beginPath();
+ var beginPath = true;
+ for (var i = 0; i < size; i++) {
+ var val = yData[i];
+ if (typeof val === 'string') {
+ // Stroke the plot drawn so far and begin a fresh plot.
+ plotCtx.stroke();
+ plotCtx.beginPath();
+ beginPath = true;
+ continue;
+ }
+ var xPos = xOrigin + Math.floor(i / (size - 1) * (width - 1));
+ var yPos = yOrigin + height - 1 -
+ Math.round((val - yMin) / yValRange * (height - 1));
+ if (beginPath) {
+ plotCtx.moveTo(xPos, yPos);
+ // A simple move to does not print anything. Hence, draw a little
+ // square here to mark a beginning.
+ plotCtx.fillStyle = '#000';
+ plotCtx.fillRect(xPos - linePlotEndMarkerWidth,
+ yPos - linePlotEndMarkerWidth,
+ linePlotEndMarkerWidth * devicePixelRatio,
+ linePlotEndMarkerWidth * devicePixelRatio);
+ beginPath = false;
+ } else {
+ plotCtx.lineTo(xPos, yPos);
+ if (i === size - 1 || typeof yData[i + 1] === 'string') {
+ // Draw a little square to mark an end to go with the start
+ // markers from above.
+ plotCtx.fillStyle = '#000';
+ plotCtx.fillRect(xPos - linePlotEndMarkerWidth,
+ yPos - linePlotEndMarkerWidth,
+ linePlotEndMarkerWidth * devicePixelRatio,
+ linePlotEndMarkerWidth * devicePixelRatio);
+ }
+ }
+ }
+ plotCtx.stroke();
+ }
+
+ // Paint the missing time intervals with |gridColor|.
+ // Pick one of the plots to look for missing time intervals.
+ function drawMissingRect(start, end) {
+ var xLeft = xOrigin + Math.floor(start / (size - 1) * (width - 1));
+ var xRight = xOrigin + Math.floor(end / (size - 1) * (width - 1));
+ plotCtx.fillStyle = gridColor;
+ // The x offsets below are present so that the blank space starts
+ // and ends between two valid samples.
+ plotCtx.fillRect(xLeft + 1, yOrigin, xRight - xLeft - 2, height - 1);
+ }
+ var inMissingInterval = false;
+ var intervalStart;
+ for (var i = 0; i < size; i++) {
+ if (typeof plots[0].data[i] === 'string') {
+ if (!inMissingInterval) {
+ inMissingInterval = true;
+ // The missing interval should actually start from the previous
+ // sample.
+ intervalStart = Math.max(i - 1, 0);
+ }
+
+ if (i == size - 1) {
+ // If this is the last sample, just draw missing rect.
+ drawMissingRect(intervalStart, i);
+ }
+ } else if (inMissingInterval) {
+ inMissingInterval = false;
+ drawMissingRect(intervalStart, i);
+ }
+ }
+ }
+
+ function drawTimeGuide(tDataIndex) {
+ var x = xOrigin + tDataIndex / (size - 1) * (width - 1);
+ drawLine(plotCtx, x, yOrigin, x, yOrigin + height - 1, '#000');
+ drawText(plotCtx,
+ tData[tDataIndex],
+ x - getTextWidth(plotCtx, tData[tDataIndex]) / 2,
+ yOrigin - 2);
+
+ for (var count = 0; count < plots.length; count++) {
+ var yData = plots[count].data;
+
+ // Draw small black square on the plot where the time guide intersects
+ // it.
+ var val = yData[tDataIndex];
+ var yPos, valStr;
+ if (typeof val === 'string') {
+ yPos = yOrigin + Math.round(height / 2);
+ valStr = val;
+ } else {
+ yPos = yOrigin + height - 1 -
+ Math.round((val - yMin) / (yMax - yMin) * (height - 1));
+ valStr = valueToString(val);
+ }
+ plotCtx.fillStyle = '#000';
+ plotCtx.fillRect(x - 2, yPos - 2, 4, 4);
+
+ // Draw the val to right of the intersection.
+ var yLoc;
+ if (yPos - textHeight / 2 < yOrigin) {
+ yLoc = yOrigin + textHeight;
+ } else if (yPos + textHeight / 2 >= yPos + height) {
+ yLoc = yOrigin + height - 1;
+ } else {
+ yLoc = yPos + textHeight / 2;
+ }
+ drawHighlightText(plotCtx, valStr, x + 5, yLoc, plots[count].color);
+ }
+ }
+
+ function onMouseOverOrMove(event) {
+ drawPlots();
+
+ var boundingRect = plotCanvas.getBoundingClientRect();
+ var x = Math.round((event.clientX - boundingRect.left) * devicePixelRatio);
+ var y = Math.round((event.clientY - boundingRect.top) * devicePixelRatio);
+ if (x < xOrigin || x >= xOrigin + width ||
+ y < yOrigin || y >= yOrigin + height) {
+ return;
+ }
+
+ if (width == size) {
+ drawTimeGuide(x - xOrigin);
+ } else {
+ drawTimeGuide(Math.round((x - xOrigin) / (width - 1) * (size - 1)));
+ }
+ }
+
+ function onMouseOut(event) {
+ drawPlots();
+ }
+
+ drawLegend();
+ drawPlots();
+ plotCanvas.addEventListener('mouseover', onMouseOverOrMove);
+ plotCanvas.addEventListener('mousemove', onMouseOverOrMove);
+ plotCanvas.addEventListener('mouseout', onMouseOut);
+}
+
+var sleepSampleInterval = 30 * 1000; // in milliseconds.
+var sleepText = loadTimeData.getString('systemSuspended');
+var invalidDataText = loadTimeData.getString('invalidData');
+var offlineText = loadTimeData.getString('offlineText');
+
+var plotColors = ['Red', 'Blue', 'Green', 'Gold', 'CadetBlue', 'LightCoral',
+ 'LightSlateGray', 'Peru', 'DarkRed', 'LawnGreen', 'Tan'];
+
+/**
+ * Add canvases for plotting to |plotsDiv|. For every header in |headerArray|,
+ * one canvas for the plot and one for its legend are added.
+ *
+ * @param {Array.<string>} headerArray Headers for the different plots to be
+ * added to |plotsDiv|.
+ * @param {HTMLDivElement} plotsDiv The div element into which the canvases
+ * are added.
+ * @return {<string>: {plotCanvas: <HTMLCanvasElement>,
+ * legendCanvas: <HTMLCanvasElement>} Returns an object
+ * with the headers as 'keys'. Each element is an object containing the
+ * legend canvas and the plot canvas that have been added to |plotsDiv|.
+ */
+function addCanvases(headerArray, plotsDiv) {
+ // Remove the contents before adding new ones.
+ while (plotsDiv.firstChild != null) {
+ plotsDiv.removeChild(plotsDiv.firstChild);
+ }
+ var width = Math.floor(plotsDiv.getBoundingClientRect().width);
+ var canvases = {};
+ for (var i = 0; i < headerArray.length; i++) {
+ var header = document.createElement('h4');
+ header.textContent = headerArray[i];
+ plotsDiv.appendChild(header);
+
+ var legendCanvas = document.createElement('canvas');
+ legendCanvas.width = width * devicePixelRatio;
+ legendCanvas.style.width = width + 'px';
+ plotsDiv.appendChild(legendCanvas);
+
+ var plotCanvasDiv = document.createElement('div');
+ plotCanvasDiv.style.overflow = 'auto';
+ plotsDiv.appendChild(plotCanvasDiv);
+
+ plotCanvas = document.createElement('canvas');
+ plotCanvas.width = width * devicePixelRatio;
+ plotCanvas.height = 200 * devicePixelRatio;
+ plotCanvas.style.height = '200px';
+ plotCanvasDiv.appendChild(plotCanvas);
+
+ canvases[headerArray[i]] = {plot: plotCanvas, legend: legendCanvas};
+ }
+ return canvases;
+}
+
+/**
+ * Add samples in |sampleArray| to individual plots in |plots|. If the system
+ * resumed from a sleep/suspend, then "suspended" sleep samples are added to
+ * the plot for the sleep duration.
+ *
+ * @param {Array.<{data: Array.<number>, color: string}>} plots An
+ * array of plots to plot on the canvas. The field 'data' of a plot is an
+ * array of samples to be plotted as a line graph with color speficied by
+ * the field 'color'. The elements in the 'data' array are ordered
+ * corresponding to their sampling time in the argument 'tData'. Also, the
+ * number of elements in the 'data' array should be the same as in the time
+ * array 'tData' below.
+ * @param {Array.<number>} tData The time (in seconds) in the past when the
+ * corresponding data in plots was sampled.
+ * @param {Array.<number>} sampleArray The array of samples wherein each
+ * element corresponds to the individual plot in |plots|.
+ * @param {number} sampleTime Time in milliseconds since the epoch when the
+ * samples in |sampleArray| were captured.
+ * @param {number} previousSampleTime Time in milliseconds since the epoch
+ * when the sample prior to the current sample was captured.
+ * @param {Array.<{time: number, sleepDuration: number}>} systemResumedArray An
+ * array objects corresponding to system resume events. The 'time' field is
+ * for the time in milliseconds since the epoch when the system resumed. The
+ * 'sleepDuration' field is for the time in milliseconds the system spent
+ * in sleep/suspend state.
+ */
+function addTimeDataSample(plots, tData, absTime, sampleArray,
+ sampleTime, previousSampleTime,
+ systemResumedArray) {
+ for (var i = 0; i < plots.length; i++) {
+ if (plots[i].data.length != tData.length) {
+ throw new Error('Mismatch in time and plot data.');
+ }
+ }
+
+ var time;
+ if (tData.length == 0) {
+ time = new Date(sampleTime);
+ absTime[0] = sampleTime;
+ tData[0] = time.toLocaleTimeString();
+ for (var i = 0; i < plots.length; i++) {
+ plots[i].data[0] = sampleArray[i];
+ }
+ return;
+ }
+
+ for (var i = 0; i < systemResumedArray.length; i++) {
+ var resumeTime = systemResumedArray[i].time;
+ var sleepDuration = systemResumedArray[i].sleepDuration;
+ var sleepStartTime = resumeTime - sleepDuration;
+ if (resumeTime < sampleTime) {
+ if (sleepStartTime < previousSampleTime) {
+ // This can happen if pending callbacks were handled before actually
+ // suspending.
+ sleepStartTime = previousSampleTime + 1000;
+ }
+ // Add sleep samples for every |sleepSampleInterval|.
+ var sleepSampleTime = sleepStartTime;
+ while (sleepSampleTime < resumeTime) {
+ time = new Date(sleepSampleTime);
+ absTime.push(sleepSampleTime);
+ tData.push(time.toLocaleTimeString());
+ for (var j = 0; j < plots.length; j++) {
+ plots[j].data.push(sleepText);
+ }
+ sleepSampleTime += sleepSampleInterval;
+ }
+ }
+ }
+
+ time = new Date(sampleTime);
+ absTime.push(sampleTime);
+ tData.push(time.toLocaleTimeString());
+ for (var i = 0; i < plots.length; i++) {
+ plots[i].data.push(sampleArray[i]);
+ }
+}
+
+/**
+ * Display the battery charge vs time on a line graph.
+ *
+ * @param {Array.<{time: number,
+ * batteryPercent: number,
+ * batteryDischargeRate: number,
+ * externalPower: number}>} powerSupplyArray An array of objects
+ * with fields representing the battery charge, time when the charge
+ * measurement was taken, and whether there was external power connected at
+ * that time.
+ * @param {Array.<{time: ?, sleepDuration: ?}>} systemResumedArray An array
+ * objects with fields 'time' and 'sleepDuration'. Each object corresponds
+ * to a system resume event. The 'time' field is for the time in
+ * milliseconds since the epoch when the system resumed. The 'sleepDuration'
+ * field is for the time in milliseconds the system spent in sleep/suspend
+ * state.
+ */
+function showBatteryChargeData(powerSupplyArray, systemResumedArray) {
+ var chargeTimeData = [];
+ var chargeAbsTime = [];
+ var chargePlot = [
+ {
+ name: loadTimeData.getString('batteryChargePercentageHeader'),
+ color: 'Blue',
+ data: []
+ }
+ ];
+ var dischargeRateTimeData = [];
+ var dischargeRateAbsTime = [];
+ var dischargeRatePlot = [
+ {
+ name: loadTimeData.getString('dischargeRateLegendText'),
+ color: 'Red',
+ data: []
+ },
+ {
+ name: loadTimeData.getString('movingAverageLegendText'),
+ color: 'Green',
+ data: []
+ },
+ {
+ name: loadTimeData.getString('binnedAverageLegendText'),
+ color: 'Blue',
+ data: []
+ }
+ ];
+ var minDischargeRate = 1000; // A high unrealistic number to begin with.
+ var maxDischargeRate = -1000; // A low unrealistic number to begin with.
+ for (var i = 0; i < powerSupplyArray.length; i++) {
+ var j = Math.max(i - 1, 0);
+
+ addTimeDataSample(chargePlot,
+ chargeTimeData,
+ chargeAbsTime,
+ [powerSupplyArray[i].batteryPercent],
+ powerSupplyArray[i].time,
+ powerSupplyArray[j].time,
+ systemResumedArray);
+
+ var dischargeRate = powerSupplyArray[i].batteryDischargeRate;
+ var inputSampleCount = $('sample-count-input').value;
+
+ var movingAverage = 0;
+ var k = 0;
+ for (k = 0; k < inputSampleCount && i - k >= 0; k++) {
+ movingAverage += powerSupplyArray[i - k].batteryDischargeRate;
+ }
+ // |k| will be atleast 1 because the 'min' value of the input field is 1.
+ movingAverage /= k;
+
+ var binnedAverage = 0;
+ for (k = 0; k < inputSampleCount; k++) {
+ var currentSampleIndex = i - i % inputSampleCount + k;
+ if (currentSampleIndex >= powerSupplyArray.length) {
+ break;
+ }
+
+ binnedAverage +=
+ powerSupplyArray[currentSampleIndex].batteryDischargeRate;
+ }
+ binnedAverage /= k;
+
+ minDischargeRate = Math.min(dischargeRate, minDischargeRate);
+ maxDischargeRate = Math.max(dischargeRate, maxDischargeRate);
+ addTimeDataSample(dischargeRatePlot,
+ dischargeRateTimeData,
+ dischargeRateAbsTime,
+ [dischargeRate, movingAverage, binnedAverage],
+ powerSupplyArray[i].time,
+ powerSupplyArray[j].time,
+ systemResumedArray);
+ }
+ if (minDischargeRate == maxDischargeRate) {
+ // This means that all the samples had the same value. Hence, offset the
+ // extremes by a bit so that the plot looks good.
+ minDischargeRate -= 1;
+ maxDischargeRate += 1;
+ }
+
+ plotsDiv = $('battery-charge-plots-div');
+
+ canvases = addCanvases(
+ [loadTimeData.getString('batteryChargePercentageHeader'),
+ loadTimeData.getString('batteryDischargeRateHeader')],
+ plotsDiv);
+
+ batteryChargeCanvases = canvases[
+ loadTimeData.getString('batteryChargePercentageHeader')];
+ plotLineGraph(
+ batteryChargeCanvases['plot'],
+ batteryChargeCanvases['legend'],
+ chargeTimeData,
+ chargePlot,
+ 0.00,
+ 100.00,
+ 3);
+
+ dischargeRateCanvases = canvases[
+ loadTimeData.getString('batteryDischargeRateHeader')];
+ plotLineGraph(
+ dischargeRateCanvases['plot'],
+ dischargeRateCanvases['legend'],
+ dischargeRateTimeData,
+ dischargeRatePlot,
+ minDischargeRate,
+ maxDischargeRate,
+ 3);
+}
+
+/**
+ * Shows state occupancy data (CPU idle or CPU freq state occupancy) on a set of
+ * plots on the about:power UI.
+ *
+ * @param {Array.<{Array.<{
+ * time: number,
+ * cpuOnline:boolean,
+ * timeInState: {<string>: number}>}>} timeInStateData Array of arrays
+ * where each array corresponds to a CPU on the system. The elements of the
+ * individual arrays contain state occupancy samples.
+ * @param {Array.<{time: ?, sleepDuration: ?}>} systemResumedArray An array
+ * objects with fields 'time' and 'sleepDuration'. Each object corresponds
+ * to a system resume event. The 'time' field is for the time in
+ * milliseconds since the epoch when the system resumed. The 'sleepDuration'
+ * field is for the time in milliseconds the system spent in sleep/suspend
+ * state.
+ * @param {string} i18nHeaderString The header string to be displayed with each
+ * plot. For example, CPU idle data will have its own header format, and CPU
+ * freq data will have its header format.
+ * @param {string} unitString This is the string capturing the unit, if any,
+ * for the different states. Note that this is not the unit of the data
+ * being plotted.
+ * @param {HTMLDivElement} plotsDivId The div element in which the plots should
+ * be added.
+ */
+function showStateOccupancyData(timeInStateData,
+ systemResumedArray,
+ i18nHeaderString,
+ unitString,
+ plotsDivId) {
+ var cpuPlots = [];
+ for (var cpu = 0; cpu < timeInStateData.length; cpu++) {
+ var cpuData = timeInStateData[cpu];
+ if (cpuData.length == 0) {
+ cpuPlots[cpu] = {plots: [], tData: []};
+ continue;
+ }
+ tData = [];
+ absTime = [];
+ // Each element of |plots| is an array of samples, one for each of the CPU
+ // states. The number of states is dicovered by looking at the first
+ // sample for which the CPU is online.
+ var plots = [];
+ var stateIndexMap = [];
+ var stateCount = 0;
+ for (var i = 0; i < cpuData.length; i++) {
+ if (cpuData[i].cpuOnline) {
+ for (var state in cpuData[i].timeInState) {
+ var stateName = state;
+ if (unitString != null) {
+ stateName += ' ' + unitString;
+ }
+ plots.push({
+ name: stateName,
+ data: [],
+ color: plotColors[stateCount]
+ });
+ stateIndexMap.push(state);
+ stateCount += 1;
+ }
+ break;
+ }
+ }
+ // If stateCount is 0, then it means the CPU has been offline
+ // throughout. Just add a single plot for such a case.
+ if (stateCount == 0) {
+ plots.push({
+ name: null,
+ data: [],
+ color: null
+ });
+ stateCount = 1; // Some invalid state!
+ }
+
+ // Pass the samples through the function addTimeDataSample to add 'sleep'
+ // samples.
+ for (var i = 0; i < cpuData.length; i++) {
+ var sample = cpuData[i];
+ var valArray = [];
+ for (var j = 0; j < stateCount; j++) {
+ if (sample.cpuOnline) {
+ valArray[j] = sample.timeInState[stateIndexMap[j]];
+ } else {
+ valArray[j] = offlineText;
+ }
+ }
+
+ var k = Math.max(i - 1, 0);
+ addTimeDataSample(plots,
+ tData,
+ absTime,
+ valArray,
+ sample.time,
+ cpuData[k].time,
+ systemResumedArray);
+ }
+
+ // Calculate the percentage occupancy of each state. A valid number is
+ // possible only if two consecutive samples are valid/numbers.
+ for (var k = 0; k < stateCount; k++) {
+ var stateData = plots[k].data;
+ // Skip the first sample as there is no previous sample.
+ for (var i = stateData.length - 1; i > 0; i--) {
+ if (typeof stateData[i] === 'number') {
+ if (typeof stateData[i - 1] === 'number') {
+ stateData[i] = (stateData[i] - stateData[i - 1]) /
+ (absTime[i] - absTime[i - 1]) * 100;
+ } else {
+ stateData[i] = invalidDataText;
+ }
+ }
+ }
+ }
+
+ // Remove the first sample from the time and data arrays.
+ tData.shift();
+ for (var k = 0; k < stateCount; k++) {
+ plots[k].data.shift();
+ }
+ cpuPlots[cpu] = {plots: plots, tData: tData};
+ }
+
+ headers = [];
+ for (var cpu = 0; cpu < timeInStateData.length; cpu++) {
+ headers[cpu] =
+ 'CPU ' + cpu + ' ' + loadTimeData.getString(i18nHeaderString);
+ }
+
+ canvases = addCanvases(headers, $(plotsDivId));
+ for (var cpu = 0; cpu < timeInStateData.length; cpu++) {
+ cpuCanvases = canvases[headers[cpu]];
+ plotLineGraph(cpuCanvases['plot'],
+ cpuCanvases['legend'],
+ cpuPlots[cpu]['tData'],
+ cpuPlots[cpu]['plots'],
+ 0,
+ 100,
+ 3);
+ }
+}
+
+function showCpuIdleData(idleStateData, systemResumedArray) {
+ showStateOccupancyData(idleStateData,
+ systemResumedArray,
+ 'idleStateOccupancyPercentageHeader',
+ null,
+ 'cpu-idle-plots-div');
+}
+
+function showCpuFreqData(freqStateData, systemResumedArray) {
+ showStateOccupancyData(freqStateData,
+ systemResumedArray,
+ 'frequencyStateOccupancyPercentageHeader',
+ 'MHz',
+ 'cpu-freq-plots-div');
+}
+
+function requestBatteryChargeData() {
+ chrome.send('requestBatteryChargeData');
+}
+
+function requestCpuIdleData() {
+ chrome.send('requestCpuIdleData');
+}
+
+function requestCpuFreqData() {
+ chrome.send('requestCpuFreqData');
+}
+
+/**
+ * Return a callback for the 'Show'/'Hide' buttons for each section of the
+ * about:power page.
+ *
+ * @param {string} sectionId The ID of the section which is to be shown or
+ * hidden.
+ * @param {string} buttonId The ID of the 'Show'/'Hide' button.
+ * @param {function} requestFunction The function which should be invoked on
+ * 'Show' to request for data from chrome.
+ * @return {function} The button callback function.
+ */
+function showHideCallback(sectionId, buttonId, requestFunction) {
+ return function() {
+ if ($(sectionId).hidden) {
+ $(sectionId).hidden = false;
+ $(buttonId).textContent = loadTimeData.getString('hideButton');
+ requestFunction();
+ } else {
+ $(sectionId).hidden = true;
+ $(buttonId).textContent = loadTimeData.getString('showButton');
+ }
+ }
+}
+
+var powerUI = {
+ showBatteryChargeData: showBatteryChargeData,
+ showCpuIdleData: showCpuIdleData,
+ showCpuFreqData: showCpuFreqData
+};
+
+document.addEventListener('DOMContentLoaded', function() {
+ $('battery-charge-section').hidden = true;
+ $('battery-charge-show-button').onclick = showHideCallback(
+ 'battery-charge-section',
+ 'battery-charge-show-button',
+ requestBatteryChargeData);
+ $('battery-charge-reload-button').onclick = requestBatteryChargeData;
+ $('sample-count-input').onclick = requestBatteryChargeData;
+
+ $('cpu-idle-section').hidden = true;
+ $('cpu-idle-show-button').onclick = showHideCallback(
+ 'cpu-idle-section', 'cpu-idle-show-button', requestCpuIdleData);
+ $('cpu-idle-reload-button').onclick = requestCpuIdleData;
+
+ $('cpu-freq-section').hidden = true;
+ $('cpu-freq-show-button').onclick = showHideCallback(
+ 'cpu-freq-section', 'cpu-freq-show-button', requestCpuFreqData);
+ $('cpu-freq-reload-button').onclick = requestCpuFreqData;
+});