/** * Copyright 2018-present Facebook. * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * @format */ import {SonarDevicePlugin} from 'sonar'; var adb = require('adbkit-fb'); import { FlexColumn, FlexRow, Button, Toolbar, Text, ManagedTable, colors, } from 'sonar'; type ADBClient = any; type AndroidDevice = any; type TableRows = any; // we keep vairable name with underline for to physical path mappings on device type CPUFrequency = {| cpu_id: number, scaling_cur_freq: number, scaling_min_freq: number, scaling_max_freq: number, cpuinfo_max_freq: number, cpuinfo_min_freq: number, |}; type CPUState = {| cpuFreq: Array, cpuCount: number, monitoring: boolean, |}; type ShellCallBack = (output: string) => void; const ColumnSizes = { cpu_id: '10%', scaling_cur_freq: 'flex', scaling_min_freq: 'flex', scaling_max_freq: 'flex', cpuinfo_min_freq: 'flex', cpuinfo_max_freq: 'flex', }; const Columns = { cpu_id: { value: 'CPU ID', resizable: true, }, scaling_cur_freq: { value: 'Scaling Current', resizable: true, }, scaling_min_freq: { value: 'Scaling MIN', resizable: true, }, scaling_max_freq: { value: 'Scaling MAX', resizable: true, }, cpuinfo_min_freq: { value: 'MIN Frequency', resizable: true, }, cpuinfo_max_freq: { value: 'MAX Frequency', resizable: true, }, }; // check if str is a number function isNormalInteger(str) { let n = Math.floor(Number(str)); return String(n) === str && n >= 0; } // format frequency to MHz, GHz function formatFrequency(freq) { if (freq == -1) { return 'N/A'; } else if (freq == -2) { return 'off'; } else if (freq > 1000 * 1000) { return (freq / 1000 / 1000).toFixed(2) + ' GHz'; } else { return freq / 1000 + ' MHz'; } } export default class CPUFrequencyTable extends SonarDevicePlugin { static id = 'DeviceCPU'; static title = 'CPU'; static icon = 'underline'; adbClient: ADBClient; intervalID: ?IntervalID; device: AndroidDevice; init() { this.setState({ cpuFreq: [], cpuCount: 0, monitoring: false, }); this.adbClient = this.device.adb; // check how many cores we have on this device this.executeShell((output: string) => { let idx = output.indexOf('-'); let cpuFreq = []; let count = parseInt(output.substring(idx + 1), 10) + 1; for (let i = 0; i < count; ++i) { cpuFreq[i] = { cpu_id: i, scaling_cur_freq: -1, scaling_min_freq: -1, scaling_max_freq: -1, cpuinfo_min_freq: -1, cpuinfo_max_freq: -1, }; } this.setState({ cpuCount: count, cpuFreq: cpuFreq, }); }, 'cat /sys/devices/system/cpu/possible'); } executeShell = (callback: ShellCallBack, command: string) => { this.adbClient .shell(this.device.serial, command) .then(adb.util.readAll) .then(function(output) { return callback(output.toString().trim()); }); }; updateCoreFrequency = (core: number, type: string) => { this.executeShell((output: string) => { let cpuFreq = this.state.cpuFreq; let newFreq = isNormalInteger(output) ? parseInt(output, 10) : -1; // update table only if frequency changed if (cpuFreq[core][type] != newFreq) { cpuFreq[core][type] = newFreq; if (type == 'scaling_cur_freq' && cpuFreq[core][type] < 0) { // cannot find current freq means offline cpuFreq[core][type] = -2; } this.setState({ cpuFreq: cpuFreq, }); } }, 'cat /sys/devices/system/cpu/cpu' + core + '/cpufreq/' + type); }; readCoreFrequency = (core: number) => { let freq = this.state.cpuFreq[core]; if (freq.cpuinfo_max_freq < 0) { this.updateCoreFrequency(core, 'cpuinfo_max_freq'); } if (freq.cpuinfo_min_freq < 0) { this.updateCoreFrequency(core, 'cpuinfo_min_freq'); } this.updateCoreFrequency(core, 'scaling_cur_freq'); this.updateCoreFrequency(core, 'scaling_min_freq'); this.updateCoreFrequency(core, 'scaling_max_freq'); }; onStartMonitor = () => { if (this.intervalID) { return; } this.intervalID = setInterval(() => { for (let i = 0; i < this.state.cpuCount; ++i) { this.readCoreFrequency(i); } }, 500); this.setState({ monitoring: true, }); }; onStopMonitor = () => { if (!this.intervalID) { return; } else { clearInterval(this.intervalID); this.intervalID = null; this.setState({ monitoring: false, }); this.cleanup(); } }; cleanup = () => { let cpuFreq = this.state.cpuFreq; for (let i = 0; i < this.state.cpuCount; ++i) { cpuFreq[i].scaling_cur_freq = -1; cpuFreq[i].scaling_min_freq = -1; cpuFreq[i].scaling_max_freq = -1; // we don't cleanup cpuinfo_min_freq, cpuinfo_max_freq // because usually they are fixed (hardware) } this.setState({ cpuFreq: cpuFreq, }); }; teardown = () => { this.cleanup(); }; buildRow = (freq: CPUFrequency) => { let style = {}; if (freq.scaling_cur_freq == -2) { style = { style: { backgroundColor: colors.blueTint30, color: colors.white, fontWeight: 700, }, }; } else if ( freq.scaling_min_freq != freq.cpuinfo_min_freq && freq.scaling_min_freq > 0 && freq.cpuinfo_min_freq > 0 ) { style = { style: { backgroundColor: colors.redTint, color: colors.red, fontWeight: 700, }, }; } else if ( freq.scaling_max_freq != freq.cpuinfo_max_freq && freq.scaling_max_freq > 0 && freq.cpuinfo_max_freq > 0 ) { style = { style: { backgroundColor: colors.yellowTint, color: colors.yellow, fontWeight: 700, }, }; } return { columns: { cpu_id: {value: CPU_{freq.cpu_id}}, scaling_cur_freq: { value: {formatFrequency(freq.scaling_cur_freq)}, }, scaling_min_freq: { value: {formatFrequency(freq.scaling_min_freq)}, }, scaling_max_freq: { value: {formatFrequency(freq.scaling_max_freq)}, }, cpuinfo_min_freq: { value: {formatFrequency(freq.cpuinfo_min_freq)}, }, cpuinfo_max_freq: { value: {formatFrequency(freq.cpuinfo_max_freq)}, }, }, key: freq.cpu_id, style, }; }; frequencyRows = (cpuFreqs: Array): TableRows => { let rows = []; for (const cpuFreq of cpuFreqs) { rows.push(this.buildRow(cpuFreq)); } return rows; }; render() { return ( {this.state.monitoring ? ( ) : ( )} ); } }