| // Copyright 2017 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. |
| |
| cr.define('SysInternals', function() { |
| /** @type {!DataSeriesSet} */ |
| const dataSeries = initDataSeries(); |
| |
| /** @type {!GeneralInfoType} */ |
| const generalInfo = initGeneralInfo(); |
| |
| /** @type{!Object<string, PromiseResolver>} */ |
| const promiseResolvers = { |
| waitCpuInitialized: new PromiseResolver(), |
| |
| /* For testing */ |
| waitDrawerActionCompleted: null, |
| waitOnHashChangeCompleted: null, |
| waitSysInternalsInitialized: new PromiseResolver(), |
| }; |
| |
| /** @type {!LineChart.LineChart} */ |
| const lineChart = new LineChart.LineChart(); |
| |
| /** |
| * The maximum value of the counter of the system info data. |
| * @type {number} |
| */ |
| let counterMax = 0; |
| |
| /** @const{Map<string, !CounterType>} */ |
| const counterDict = new Map(); |
| |
| /** |
| * Initialize the whole page. |
| */ |
| function initialize() { |
| initPage(); |
| initChart(); |
| startUpdateRequests(); |
| |
| /* Initialize with the current url hash value. */ |
| onHashChange(); |
| promiseResolvers.waitSysInternalsInitialized.resolve(); |
| } |
| |
| /** |
| * Initialize the DOM of the page. |
| */ |
| function initPage() { |
| $('nav-menu-btn').addEventListener('click', function(event) { |
| event.preventDefault(); |
| openDrawer(); |
| }); |
| |
| $('sys-internals-drawer').addEventListener('click', function(event) { |
| closeDrawer(); |
| }); |
| |
| window.addEventListener('hashchange', onHashChange); |
| } |
| |
| /** |
| * Open the navbar drawer menu. |
| */ |
| function openDrawer() { |
| $('sys-internals-drawer').removeAttribute('hidden'); |
| /* Preventing CSS transition blocking by JS. */ |
| setTimeout(function() { |
| $('sys-internals-drawer').classList.remove('hidden'); |
| $('drawer-menu').classList.remove('hidden'); |
| if (promiseResolvers.waitDrawerActionCompleted) { |
| promiseResolvers.waitDrawerActionCompleted.resolve(); |
| } |
| }); |
| } |
| |
| /** |
| * Close the navbar drawer menu. |
| */ |
| function closeDrawer() { |
| const /** Element */ drawer = $('sys-internals-drawer'); |
| drawer.classList.add('hidden'); |
| $('drawer-menu').classList.add('hidden'); |
| /* Wait for the drawer close. */ |
| setTimeout(function() { |
| drawer.setAttribute('hidden', ''); |
| if (promiseResolvers.waitDrawerActionCompleted) { |
| promiseResolvers.waitDrawerActionCompleted.resolve(); |
| } |
| }, 200); |
| } |
| |
| /** |
| * Initialize the data series of the page. |
| * @return {!DataSeriesSet} |
| */ |
| function initDataSeries() { |
| const /** DataSeriesSet */ dataSeriesRes = { |
| cpus: null, |
| memory: { |
| memUsed: new LineChart.DataSeries( |
| 'Used Memory', SysInternals.MEMORY_COLOR_SET[0]), |
| swapUsed: new LineChart.DataSeries( |
| 'Used Swap', SysInternals.MEMORY_COLOR_SET[1]), |
| pswpin: new LineChart.DataSeries( |
| 'Pswpin', SysInternals.MEMORY_COLOR_SET[2]), |
| pswpout: new LineChart.DataSeries( |
| 'Pswpout', SysInternals.MEMORY_COLOR_SET[3]), |
| }, |
| zram: { |
| origDataSize: new LineChart.DataSeries( |
| 'Original Data Size', SysInternals.ZRAM_COLOR_SET[0]), |
| comprDataSize: new LineChart.DataSeries( |
| 'Compress Data Size', SysInternals.ZRAM_COLOR_SET[1]), |
| memUsedTotal: new LineChart.DataSeries( |
| 'Total Memory', SysInternals.ZRAM_COLOR_SET[2]), |
| numReads: new LineChart.DataSeries( |
| 'Num Reads', SysInternals.ZRAM_COLOR_SET[3]), |
| numWrites: new LineChart.DataSeries( |
| 'Num Writes', SysInternals.ZRAM_COLOR_SET[4]), |
| }, |
| }; |
| dataSeriesRes.zram.memUsedTotal.setMenuTextBlack(true); |
| dataSeriesRes.zram.numReads.setMenuTextBlack(true); |
| return dataSeriesRes; |
| } |
| |
| /** |
| * Initialize generalInfo. |
| * @return {!GeneralInfoType} |
| */ |
| function initGeneralInfo() { |
| return { |
| cpu: { |
| core: 0, |
| idle: 0, |
| kernel: 0, |
| usage: 0, |
| user: 0, |
| }, |
| memory: { |
| swapTotal: 0, |
| swapUsed: 0, |
| total: 0, |
| used: 0, |
| }, |
| zram: { |
| compr: 0, |
| comprRatio: NaN, |
| orig: 0, |
| total: 0, |
| }, |
| }; |
| } |
| |
| /** |
| * Initialize the LineChart object. |
| */ |
| function initChart() { |
| lineChart.attachRootDiv($('chart-root')); |
| } |
| |
| /** |
| * Wait until next period, and then send the update request to backend to |
| * update the system information. |
| */ |
| function startUpdateRequests() { |
| if (DONT_SEND_UPDATE_REQUEST) |
| return; |
| |
| const doUpdate = function() { |
| cr.sendWithPromise('getSysInfo').then(function(data) { |
| handleUpdateData(data, Date.now()); |
| }); |
| }; |
| doUpdate(); |
| setInterval(doUpdate, SysInternals.UPDATE_PERIOD); |
| } |
| |
| /** |
| * Handle the new data which received from backend. |
| * @param {!SysInfoApiResult} data |
| * @param {number} timestamp - The time tick for these data. |
| */ |
| function handleUpdateData(data, timestamp) { |
| counterMax = data.const.counterMax; |
| |
| updateCpuData(data.cpus, timestamp); |
| updateMemoryData(data.memory, timestamp); |
| updateZramData(data.zram, timestamp); |
| |
| if (isInfoPage()) { |
| updateInfoPage(); |
| } else { |
| lineChart.updateEndTime(timestamp); |
| } |
| } |
| |
| /** |
| * Handle the new cpu data. |
| * @param {!Array<!SysInfoApiCpuResult>} cpus |
| * @param {number} timestamp |
| */ |
| function updateCpuData(cpus, timestamp) { |
| if (dataSeries.cpus == null) { |
| initCpuDataSeries(cpus); |
| } |
| const /** Array<!LineChart.DataSeries> */ cpuDataSeries = dataSeries.cpus; |
| if (cpus.length != cpuDataSeries.length) { |
| console.warn('Cpu Data: Number of processors changed.'); |
| return; |
| } |
| let allKernel = 0; |
| let allUser = 0; |
| let allIdle = 0; |
| for (let i = 0; i < cpus.length; ++i) { |
| /* Check if this cpu is offline */ |
| if (cpus[i].total == 0) { |
| cpuDataSeries[i].addDataPoint(0, timestamp); |
| continue; |
| } |
| const /** number */ user = |
| getDiffAndUpdateCounter(`cpu_${i}_user`, cpus[i].user, timestamp); |
| const /** number */ kernel = |
| getDiffAndUpdateCounter(`cpu_${i}_kernel`, cpus[i].kernel, timestamp); |
| const /** number */ idle = |
| getDiffAndUpdateCounter(`cpu_${i}_idle`, cpus[i].idle, timestamp); |
| const /** number */ total = |
| getDiffAndUpdateCounter(`cpu_${i}_total`, cpus[i].total, timestamp); |
| /* Total may be zero at first update. */ |
| const /** number */ percentage = |
| total == 0 ? 0 : (user + kernel) / total * 100; |
| cpuDataSeries[i].addDataPoint(percentage, timestamp); |
| allKernel += kernel; |
| allUser += user; |
| allIdle += idle; |
| } |
| |
| const /** !GeneralCpuType */ generalCpu = generalInfo.cpu; |
| generalCpu.core = cpus.length; |
| const allTotal = allKernel + allUser + allIdle; |
| generalCpu.usage = allTotal == 0 ? 0 : (allKernel + allUser) / allTotal; |
| generalCpu.kernel = allTotal == 0 ? 0 : allKernel / allTotal; |
| generalCpu.user = allTotal == 0 ? 0 : allUser / allTotal; |
| generalCpu.idle = allTotal == 0 ? 0 : allIdle / allTotal; |
| } |
| |
| /** |
| * Initialize the data series of the page. This function will be called for |
| * the first time we get the cpus data. |
| * @param {!Array<!SysInfoApiCpuResult>} cpus |
| */ |
| function initCpuDataSeries(cpus) { |
| if (cpus.length == 0) |
| return; |
| dataSeries.cpus = []; |
| for (let i = 0; i < cpus.length; ++i) { |
| const colorIdx = i % SysInternals.CPU_COLOR_SET.length; |
| dataSeries.cpus[i] = new LineChart.DataSeries( |
| `CPU ${i}`, SysInternals.CPU_COLOR_SET[colorIdx]); |
| } |
| promiseResolvers.waitCpuInitialized.resolve(); |
| } |
| |
| /** |
| * Handle the new memory data. |
| * @param {!SysInfoApiMemoryResult} memory |
| * @param {number} timestamp |
| */ |
| function updateMemoryData(memory, timestamp) { |
| const /** !MemoryDataSeriesSet */ memDataSeries = dataSeries.memory; |
| const /** number */ memUsed = memory.total - memory.available; |
| memDataSeries.memUsed.addDataPoint(memUsed, timestamp); |
| const /** number */ swapUsed = memory.swapTotal - memory.swapFree; |
| memDataSeries.swapUsed.addDataPoint(swapUsed, timestamp); |
| const /** number */ pswpin = |
| getDiffPerSecAndUpdateCounter('pswpin', memory.pswpin, timestamp); |
| memDataSeries.pswpin.addDataPoint(pswpin, timestamp); |
| const /** number */ pswpout = |
| getDiffPerSecAndUpdateCounter('pswpout', memory.pswpout, timestamp); |
| memDataSeries.pswpout.addDataPoint(pswpout, timestamp); |
| |
| const /** !GeneralMemoryType */ generalMem = generalInfo.memory; |
| generalMem.total = memory.total; |
| generalMem.used = memUsed; |
| generalMem.swapTotal = memory.swapTotal; |
| generalMem.swapUsed = swapUsed; |
| } |
| |
| /** |
| * Handle the new zram data. |
| * @param {!SysInfoApiZramResult} zram |
| * @param {number} timestamp |
| */ |
| function updateZramData(zram, timestamp) { |
| const /** !ZramDataSeriesSet */ zramDataSeries = dataSeries.zram; |
| zramDataSeries.origDataSize.addDataPoint(zram.origDataSize, timestamp); |
| zramDataSeries.comprDataSize.addDataPoint(zram.comprDataSize, timestamp); |
| zramDataSeries.memUsedTotal.addDataPoint(zram.memUsedTotal, timestamp); |
| const /** number */ numReads = |
| getDiffPerSecAndUpdateCounter('numReads', zram.numReads, timestamp); |
| zramDataSeries.numReads.addDataPoint(numReads, timestamp); |
| const /** number */ numWrites = |
| getDiffPerSecAndUpdateCounter('numWrites', zram.numWrites, timestamp); |
| zramDataSeries.numWrites.addDataPoint(numWrites, timestamp); |
| |
| const /** !GeneralZramType */ generalZram = generalInfo.zram; |
| generalZram.total = zram.memUsedTotal; |
| generalZram.orig = zram.origDataSize; |
| generalZram.compr = zram.comprDataSize; |
| /* Note: |comprRatio| may be NaN, this is ok because it can remind user |
| * there is no comprRatio now. */ |
| generalZram.comprRatio = |
| (zram.origDataSize - zram.comprDataSize) / zram.origDataSize; |
| } |
| |
| /** |
| * Get the increments from the last value to the current value. Return the |
| * increments, and store the current value. |
| * @param {string} name - The key to identify the counter. |
| * @param {number} newValue |
| * @param {number} timestamp |
| * @return {number} |
| */ |
| function getDiffAndUpdateCounter(name, newValue, timestamp) { |
| if (counterDict.get(name) == undefined) { |
| counterDict.set(name, {value: newValue, timestamp: timestamp}); |
| return 0; |
| } |
| const /** !CounterType */ counter = counterDict.get(name); |
| let /** number */ valueDelta = newValue - counter.value; |
| |
| /* If the increments of the counter is negative, it means that the counter |
| * is circulating. Get the real increments with the |counterMax|. The result |
| * is guaranteed to be greater than zero because the maximum difference of |
| * two counter never greater than |counterMax|. */ |
| if (valueDelta < 0) { |
| valueDelta += counterMax; |
| } |
| counter.value = newValue; |
| counter.timestamp = timestamp; |
| return valueDelta; |
| } |
| |
| /** |
| * Return the average increments per second, and store the current value. |
| * @param {string} name - The key to identify the counter. |
| * @param {number} newValue |
| * @param {number} timestamp |
| * @return {number} |
| */ |
| function getDiffPerSecAndUpdateCounter(name, newValue, timestamp) { |
| const /** number */ oldTimeStamp = |
| counterDict.get(name) ? counterDict.get(name).timestamp : -1; |
| const /** number */ valueDelta = |
| getDiffAndUpdateCounter(name, newValue, timestamp); |
| |
| /* If oldTimeStamp is -1, it means that this is the first value of the |
| * counter. */ |
| if (oldTimeStamp == -1) |
| return 0; |
| |
| /** |
| * The time increments, in seconds. |
| * @type {number} |
| */ |
| const timeDelta = (timestamp - oldTimeStamp) / 1000; |
| const /** number */ deltaPerSec = |
| (timeDelta == 0) ? 0 : valueDelta / timeDelta; |
| return deltaPerSec; |
| } |
| |
| /** |
| * Updata the info page with the current data. |
| */ |
| function updateInfoPage() { |
| const setPercentageById = function(/** string */ id, /** number */ value) { |
| setTextById( |
| id, toPercentageString(value, SysInternals.INFO_PAGE_PRECISION)); |
| }; |
| const setMemoryById = function(/** string */ id, /** number */ value) { |
| setTextById( |
| id, |
| getValueWithUnit( |
| value, SysInternals.UNITS_MEMORY, SysInternals.UNITBASE_MEMORY)); |
| }; |
| |
| const /** !GeneralCpuType */ cpu = generalInfo.cpu; |
| setTextById('infopage-num-of-cpu', cpu.core.toString()); |
| setPercentageById('infopage-cpu-usage', cpu.usage); |
| setPercentageById('infopage-cpu-kernel', cpu.kernel); |
| setPercentageById('infopage-cpu-user', cpu.user); |
| setPercentageById('infopage-cpu-idle', cpu.idle); |
| |
| const /** !GeneralMemoryType */ memory = generalInfo.memory; |
| setMemoryById('infopage-memory-total', memory.total); |
| setMemoryById('infopage-memory-used', memory.used); |
| setMemoryById('infopage-memory-swap-total', memory.swapTotal); |
| setMemoryById('infopage-memory-swap-used', memory.swapUsed); |
| |
| const /** !GeneralZramType */ zram = generalInfo.zram; |
| setMemoryById('infopage-zram-total', zram.total); |
| setMemoryById('infopage-zram-orig', zram.orig); |
| setMemoryById('infopage-zram-compr', zram.compr); |
| setPercentageById('infopage-zram-compr-ratio', zram.comprRatio); |
| } |
| |
| /** |
| * Set the element inner text by the element id. |
| * @param {string} id |
| * @param {string} text |
| */ |
| function setTextById(id, text) { |
| $(id).innerText = text; |
| } |
| |
| /** |
| * Transform the number to percentage string. |
| * @param {number} number |
| * @param {number} fixed - The percision of the number. |
| * @return {string} |
| */ |
| function toPercentageString(number, fixed) { |
| const fixedNumber = (number * 100).toFixed(fixed); |
| return fixedNumber + '%'; |
| } |
| |
| /** |
| * Return the value with a suitable unit. See |
| * |LineChart.getSuitableUint()|. |
| * @param {number} value |
| * @param {!Array<string>} units |
| * @param {number} unitBase |
| * @return {string} |
| */ |
| function getValueWithUnit(value, units, unitBase) { |
| const result = LineChart.getSuitableUnit(value, units, unitBase); |
| const suitableValue = |
| result.value.toFixed(SysInternals.INFO_PAGE_PRECISION); |
| const unitStr = units[result.unitIdx]; |
| return `${suitableValue} ${unitStr}`; |
| } |
| |
| /** |
| * Handle the url onhashchange event. |
| */ |
| function onHashChange() { |
| const /** string */ hash = location.hash; |
| switch (hash) { |
| case SysInternals.PAGE_HASH.INFO: |
| setupInfoPage(); |
| break; |
| case SysInternals.PAGE_HASH.CPU: |
| /* Wait for cpu dataseries initialized. */ |
| promiseResolvers.waitCpuInitialized.promise.then(setupCPUPage); |
| break; |
| case SysInternals.PAGE_HASH.MEMORY: |
| setupMemoryPage(); |
| break; |
| case SysInternals.PAGE_HASH.ZRAM: |
| setupZramPage(); |
| break; |
| } |
| const /** Element */ title = $('drawer-title'); |
| const /** Element */ infoPage = $('infopage-root'); |
| if (isInfoPage()) { |
| title.innerText = 'Info'; |
| infoPage.removeAttribute('hidden'); |
| } else { |
| title.innerText = hash.slice(1); |
| infoPage.setAttribute('hidden', ''); |
| } |
| |
| if (promiseResolvers.waitOnHashChangeCompleted) { |
| promiseResolvers.waitOnHashChangeCompleted.resolve(); |
| } |
| } |
| |
| /** |
| * Return true if the current page is info page. |
| * @return {boolean} |
| */ |
| function isInfoPage() { |
| return location.hash == ''; |
| } |
| |
| /** |
| * Set the current page to info page. |
| */ |
| function setupInfoPage() { |
| lineChart.clearAllSubChart(); |
| updateInfoPage(); |
| } |
| |
| const /** number */ LEFT = LineChart.UnitLabelAlign.LEFT; |
| const /** number */ RIGHT = LineChart.UnitLabelAlign.RIGHT; |
| |
| /** |
| * Set the current page to cpu page. |
| */ |
| function setupCPUPage() { |
| /* This function is async so we need to check the page is still CPU page. */ |
| if (location.hash != SysInternals.PAGE_HASH.CPU) |
| return; |
| |
| const /** Array<!LineChart.DataSeries> */ cpuDataSeries = dataSeries.cpus; |
| const /** number */ UNITBASE_NO_CARRY = 1; |
| const /** !Array<string> */ UNIT_PURE_NUMBER = ['']; |
| lineChart.setSubChart(LEFT, UNIT_PURE_NUMBER, UNITBASE_NO_CARRY); |
| const /** !Array<string> */ UNIT_PERCENTAGE = ['%']; |
| lineChart.setSubChart(RIGHT, UNIT_PERCENTAGE, UNITBASE_NO_CARRY); |
| lineChart.setSubChartMaxValue(RIGHT, 100); |
| for (let i = 0; i < cpuDataSeries.length; ++i) { |
| lineChart.addDataSeries(RIGHT, cpuDataSeries[i]); |
| } |
| } |
| |
| /** |
| * Set the current page to memory page. |
| */ |
| function setupMemoryPage() { |
| const /** !MemoryDataSeriesSet */ memDataSeries = dataSeries.memory; |
| lineChart.setSubChart( |
| LEFT, SysInternals.UNITS_NUMBER_PER_SECOND, |
| SysInternals.UNITBASE_NUMBER_PER_SECOND); |
| lineChart.setSubChart( |
| RIGHT, SysInternals.UNITS_MEMORY, SysInternals.UNITBASE_MEMORY); |
| lineChart.addDataSeries(RIGHT, memDataSeries.memUsed); |
| lineChart.addDataSeries(RIGHT, memDataSeries.swapUsed); |
| lineChart.addDataSeries(LEFT, memDataSeries.pswpin); |
| lineChart.addDataSeries(LEFT, memDataSeries.pswpout); |
| } |
| |
| /** |
| * Set the current page to zram page. |
| */ |
| function setupZramPage() { |
| const /** !ZramDataSeriesSet */ zramDataSeries = dataSeries.zram; |
| lineChart.setSubChart( |
| LEFT, SysInternals.UNITS_NUMBER_PER_SECOND, |
| SysInternals.UNITBASE_NUMBER_PER_SECOND); |
| lineChart.setSubChart( |
| RIGHT, SysInternals.UNITS_MEMORY, SysInternals.UNITBASE_MEMORY); |
| lineChart.addDataSeries(RIGHT, zramDataSeries.origDataSize); |
| lineChart.addDataSeries(RIGHT, zramDataSeries.comprDataSize); |
| lineChart.addDataSeries(RIGHT, zramDataSeries.memUsedTotal); |
| lineChart.addDataSeries(LEFT, zramDataSeries.numReads); |
| lineChart.addDataSeries(LEFT, zramDataSeries.numWrites); |
| } |
| |
| /* Exposed for testing. */ |
| return { |
| closeDrawer: closeDrawer, |
| dataSeries: dataSeries, |
| getDiffAndUpdateCounter: getDiffAndUpdateCounter, |
| getDiffPerSecAndUpdateCounter: getDiffPerSecAndUpdateCounter, |
| getValueWithUnit: getValueWithUnit, |
| handleUpdateData: handleUpdateData, |
| initialize: initialize, |
| isInfoPage: isInfoPage, |
| lineChart: lineChart, |
| openDrawer: openDrawer, |
| promiseResolvers: promiseResolvers, |
| toPercentageString: toPercentageString, |
| updateInfoPage: updateInfoPage, |
| }; |
| |
| }); // cr.define |
| |
| /** @type {boolean} - Tag used by browser test. */ |
| var DONT_SEND_UPDATE_REQUEST; |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| SysInternals.initialize(); |
| }); |