Files
flipper/desktop/plugins/public/cpu/index.tsx
dependabot[bot] 0fbf8c3cfa Bump prettier from 2.4.1 to 2.5.1 in /desktop (#3232)
Summary:
Bumps [prettier](https://github.com/prettier/prettier) from 2.4.1 to 2.5.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/prettier/prettier/releases">prettier's releases</a>.</em></p>
<blockquote>
<h2>2.5.1</h2>
<p>🔗 <a href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md#251">Changelog</a></p>
<h2>2.5.0</h2>
<p><a href="https://github.com/prettier/prettier/compare/2.4.1...2.5.0">diff</a></p>
<p>🔗 <a href="https://prettier.io/blog/2021/11/25/2.5.0.html">Release note</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md">prettier's changelog</a>.</em></p>
<blockquote>
<h1>2.5.1</h1>
<p><a href="https://github.com/prettier/prettier/compare/2.5.0...2.5.1">diff</a></p>
<h4>Improve formatting for empty tuple types (<a href="https://github-redirect.dependabot.com/prettier/prettier/pull/11884">#11884</a> by <a href="https://github.com/sosukesuzuki"><code>@​sosukesuzuki</code></a>)</h4>

<pre lang="tsx"><code>// Input
type Foo =
  Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooo extends []
    ? Foo3
    : Foo4;
<p>// Prettier 2.5.0
type Foo = Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooo extends [</p>
<p>]
? Foo3
: Foo4;</p>
<p>// Prettier 2.5.0 (tailingCommma = all)
// Invalid TypeScript code
type Foo = Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooo extends [
,
]
? Foo3
: Foo4;</p>
<p>// Prettier 2.5.1
type Foo =
Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooo extends []
? Foo3
: Foo4;</p>
<p></code></pre></p>
<h4>Fix compatibility with Jest inline snapshot test (<a href="https://github-redirect.dependabot.com/prettier/prettier/pull/11892">#11892</a> by <a href="https://github.com/fisker"><code>@​fisker</code></a>)</h4>
<p>A internal change in Prettier@v2.5.0 accidentally breaks the Jest inline snapshot test.</p>
<h4>Support Glimmer's named blocks (<a href="https://github-redirect.dependabot.com/prettier/prettier/pull/11899">#11899</a> by <a href="https://github.com/duailibe"><code>@​duailibe</code></a>)</h4>
<p>Prettier already supported this feature, but it converted empty named blocks to self-closing, which is not supported by the Glimmer compiler.</p>
<p>See: <a href="https://emberjs.github.io/rfcs/0460-yieldable-named-blocks.html">Glimmer's named blocks</a>.</p>

<pre lang="hbs"><code>// Input
&lt;/tr&gt;&lt;/table&gt;
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="56ed71d1c9"><code>56ed71d</code></a> Release 2.5.1</li>
<li><a href="29f408c99e"><code>29f408c</code></a> Add changelog for <a href="https://github-redirect.dependabot.com/prettier/prettier/issues/11892">#11892</a> (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/11904">#11904</a>)</li>
<li><a href="e7a74529a5"><code>e7a7452</code></a> Make <code>options</code> argument in parsers optional (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/11892">#11892</a>)</li>
<li><a href="99649c7fd2"><code>99649c7</code></a> [handlebars] Named blocks can't be self closing (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/11900">#11900</a>)</li>
<li><a href="c0250b3837"><code>c0250b3</code></a> Improve formatting for empty tuple types (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/11884">#11884</a>)</li>
<li><a href="7fc196e96b"><code>7fc196e</code></a> Clean changelog_unreleased</li>
<li><a href="04aa850fde"><code>04aa850</code></a> Add 2.5 blog (<a href="https://github-redirect.dependabot.com/prettier/prettier/issues/11823">#11823</a>)</li>
<li><a href="255d38982f"><code>255d389</code></a> Update dependents count</li>
<li><a href="854ca328a2"><code>854ca32</code></a> Git blame ignore 2.5.0</li>
<li><a href="846dbdd7e8"><code>846dbdd</code></a> Bump Prettier dependency to 2.5.0</li>
<li>Additional commits viewable in <a href="https://github.com/prettier/prettier/compare/2.4.1...2.5.1">compare view</a></li>
</ul>
</details>
<br />

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=prettier&package-manager=npm_and_yarn&previous-version=2.4.1&new-version=2.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

 ---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `dependabot rebase` will rebase this PR
- `dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `dependabot merge` will merge this PR after your CI passes on it
- `dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `dependabot cancel merge` will cancel a previously requested merge and block automerging
- `dependabot reopen` will reopen this PR if it is closed
- `dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

</details>

Pull Request resolved: https://github.com/facebook/flipper/pull/3232

Reviewed By: lawrencelomax

Differential Revision: D33403866

Pulled By: passy

fbshipit-source-id: 184b3db11f10dc34500c01e6a11790b89032dba4
2022-01-05 10:49:16 -08:00

614 lines
17 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {
createState,
PluginClient,
usePlugin,
useValue,
Panel,
theme,
Layout,
DetailSidebar,
DataTable,
DataTableColumn,
Toolbar,
} from 'flipper-plugin';
import TemperatureTable from './TemperatureTable';
import {Button, Typography, Switch} from 'antd';
import {PlayCircleOutlined, PauseCircleOutlined} from '@ant-design/icons';
import React, {useCallback, useState} from 'react';
// we keep vairable name with underline for to physical path mappings on device
type CPUFrequency = {
[index: string]: number | Array<number> | string | Array<string>;
cpu_id: number;
scaling_cur_freq: number;
scaling_min_freq: number;
scaling_max_freq: number;
scaling_available_freqs: Array<number>;
scaling_governor: string;
scaling_available_governors: Array<string>;
cpuinfo_max_freq: number;
cpuinfo_min_freq: number;
};
type CPUState = {
cpuFreq: Array<CPUFrequency>;
cpuCount: number;
monitoring: boolean;
hardwareInfo: string;
temperatureMap: any;
thermalAccessible: boolean;
displayThermalInfo: boolean;
displayCPUDetail: boolean;
};
// check if str is a number
function isNormalInteger(str: string) {
const n = Math.floor(Number(str));
return String(n) === str && n >= 0;
}
// format frequency to MHz, GHz
function formatFrequency(freq: number) {
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 function devicePlugin(client: PluginClient<{}, {}>) {
const device = client.device;
const executeShell = async (command: string) => device.executeShell(command);
let intervalID: NodeJS.Timer | null = null;
const cpuState = createState<CPUState>({
cpuCount: 0,
cpuFreq: [],
monitoring: false,
hardwareInfo: '',
temperatureMap: {},
thermalAccessible: true,
displayThermalInfo: false,
displayCPUDetail: true,
});
const updateCoreFrequency: (
core: number,
type: string,
) => Promise<void> = async (core: number, type: string) => {
const output = await executeShell(
'cat /sys/devices/system/cpu/cpu' + core + '/cpufreq/' + type,
);
cpuState.update((draft) => {
const newFreq = isNormalInteger(output) ? parseInt(output, 10) : -1;
// update table only if frequency changed
if (draft.cpuFreq[core][type] != newFreq) {
draft.cpuFreq[core][type] = newFreq;
if (type == 'scaling_cur_freq' && draft.cpuFreq[core][type] < 0) {
// cannot find current freq means offline
draft.cpuFreq[core][type] = -2;
}
}
});
};
const updateAvailableFrequencies: (core: number) => Promise<void> = async (
core: number,
) => {
const output = await executeShell(
'cat /sys/devices/system/cpu/cpu' +
core +
'/cpufreq/scaling_available_frequencies',
);
cpuState.update((draft) => {
const freqs = output.split(' ').map((num: string) => {
return parseInt(num, 10);
});
draft.cpuFreq[core].scaling_available_freqs = freqs;
const maxFreq = draft.cpuFreq[core].scaling_max_freq;
if (maxFreq > 0 && freqs.indexOf(maxFreq) == -1) {
freqs.push(maxFreq); // always add scaling max to available frequencies
}
});
};
const updateCoreGovernor: (core: number) => Promise<void> = async (
core: number,
) => {
const output = await executeShell(
'cat /sys/devices/system/cpu/cpu' + core + '/cpufreq/scaling_governor',
);
cpuState.update((draft) => {
if (output.toLowerCase().includes('no such file')) {
draft.cpuFreq[core].scaling_governor = 'N/A';
} else {
draft.cpuFreq[core].scaling_governor = output;
}
});
};
const readAvailableGovernors: (core: number) => Promise<string[]> = async (
core: number,
) => {
const output = await executeShell(
'cat /sys/devices/system/cpu/cpu' +
core +
'/cpufreq/scaling_available_governors',
);
return output.split(' ');
};
const readCoreFrequency = async (core: number) => {
const freq = cpuState.get().cpuFreq[core];
const promises = [];
if (freq.cpuinfo_max_freq < 0) {
promises.push(updateCoreFrequency(core, 'cpuinfo_max_freq'));
}
if (freq.cpuinfo_min_freq < 0) {
promises.push(updateCoreFrequency(core, 'cpuinfo_min_freq'));
}
promises.push(updateCoreFrequency(core, 'scaling_cur_freq'));
promises.push(updateCoreFrequency(core, 'scaling_min_freq'));
promises.push(updateCoreFrequency(core, 'scaling_max_freq'));
return Promise.all(promises).then(() => {});
};
const updateHardwareInfo = async () => {
const output = await executeShell('getprop ro.board.platform');
let hwInfo = '';
if (
output.startsWith('msm') ||
output.startsWith('apq') ||
output.startsWith('sdm')
) {
hwInfo = 'QUALCOMM ' + output.toUpperCase();
} else if (output.startsWith('exynos')) {
const chipname = await executeShell('getprop ro.chipname');
if (chipname != null) {
cpuState.update((draft) => {
draft.hardwareInfo = 'SAMSUMG ' + chipname.toUpperCase();
});
}
return;
} else if (output.startsWith('mt')) {
hwInfo = 'MEDIATEK ' + output.toUpperCase();
} else if (output.startsWith('sc')) {
hwInfo = 'SPREADTRUM ' + output.toUpperCase();
} else if (output.startsWith('hi') || output.startsWith('kirin')) {
hwInfo = 'HISILICON ' + output.toUpperCase();
} else if (output.startsWith('rk')) {
hwInfo = 'ROCKCHIP ' + output.toUpperCase();
} else if (output.startsWith('bcm')) {
hwInfo = 'BROADCOM ' + output.toUpperCase();
}
cpuState.update((draft) => {
draft.hardwareInfo = hwInfo;
});
};
const readThermalZones = async () => {
const thermal_dir = '/sys/class/thermal/';
const map = {};
const output = await executeShell('ls ' + thermal_dir);
if (output.toLowerCase().includes('permission denied')) {
cpuState.update((draft) => {
draft.thermalAccessible = false;
});
return;
}
const dirs = output.split(/\s/);
const promises = [];
for (let d of dirs) {
d = d.trim();
if (d.length == 0) {
continue;
}
const path = thermal_dir + d;
promises.push(readThermalZone(path, d, map));
}
await Promise.all(promises);
cpuState.update((draft) => {
draft.temperatureMap = map;
draft.thermalAccessible = true;
});
if (cpuState.get().displayThermalInfo) {
setTimeout(readThermalZones, 1000);
}
};
const readThermalZone = async (path: string, dir: string, map: any) => {
const type = await executeShell('cat ' + path + '/type');
if (type.length == 0) {
return;
}
const temp = await executeShell('cat ' + path + '/temp');
if (Number.isNaN(Number(temp))) {
return;
}
map[type] = {
path: dir,
temp: parseInt(temp, 10),
};
};
const onStartMonitor = () => {
if (cpuState.get().monitoring) {
return;
}
cpuState.update((draft) => {
draft.monitoring = true;
});
for (let i = 0; i < cpuState.get().cpuCount; ++i) {
readAvailableGovernors(i)
.then((output) => {
cpuState.update((draft) => {
draft.cpuFreq[i].scaling_available_governors = output;
});
})
.catch((e) => {
console.error('Failed to read CPU governors:', e);
});
}
const update = async () => {
if (!cpuState.get().monitoring) {
return;
}
const promises = [];
for (let i = 0; i < cpuState.get().cpuCount; ++i) {
promises.push(readCoreFrequency(i));
promises.push(updateCoreGovernor(i));
promises.push(updateAvailableFrequencies(i)); // scaling max might change, so we also update this
}
await Promise.all(promises);
intervalID = setTimeout(update, 500);
};
intervalID = setTimeout(update, 500);
};
const onStopMonitor = () => {
intervalID && clearInterval(intervalID);
intervalID = null;
cpuState.update((draft) => {
draft.monitoring = false;
});
};
const cleanup = () => {
onStopMonitor();
cpuState.update((draft) => {
for (let i = 0; i < draft.cpuCount; ++i) {
draft.cpuFreq[i].scaling_cur_freq = -1;
draft.cpuFreq[i].scaling_min_freq = -1;
draft.cpuFreq[i].scaling_max_freq = -1;
draft.cpuFreq[i].scaling_available_freqs = [];
draft.cpuFreq[i].scaling_governor = 'N/A';
// we don't cleanup cpuinfo_min_freq, cpuinfo_max_freq
// because usually they are fixed (hardware)
}
});
};
const toggleThermalSidebar = () => {
if (!cpuState.get().displayThermalInfo) {
readThermalZones();
}
cpuState.update((draft) => {
draft.displayThermalInfo = !draft.displayThermalInfo;
draft.displayCPUDetail = false;
});
};
const toggleCPUSidebar = () => {
cpuState.update((draft) => {
draft.displayCPUDetail = !draft.displayCPUDetail;
draft.displayThermalInfo = false;
});
};
// check how many cores we have on this device
executeShell('cat /sys/devices/system/cpu/possible')
.then((output) => {
const idx = output.indexOf('-');
const cpuFreq = [];
const 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,
scaling_available_freqs: [],
scaling_governor: 'N/A',
scaling_available_governors: [],
};
}
cpuState.set({
cpuCount: count,
cpuFreq: cpuFreq,
monitoring: false,
hardwareInfo: '',
temperatureMap: {},
thermalAccessible: true,
displayThermalInfo: false,
displayCPUDetail: true,
});
})
.catch((e) => {
console.error('Failed to read CPU cores:', e);
});
client.onDeactivate(() => cleanup());
client.onActivate(() => {
updateHardwareInfo();
readThermalZones();
});
return {
executeShell,
cpuState,
onStartMonitor,
onStopMonitor,
toggleCPUSidebar,
toggleThermalSidebar,
};
}
const columns: DataTableColumn[] = [
{key: 'cpu_id', title: 'CPU ID'},
{key: 'scaling_cur_freq', title: 'Current Frequency'},
{key: 'scaling_min_freq', title: 'Scaling min'},
{key: 'scaling_max_freq', title: 'Scaling max'},
{key: 'cpuinfo_min_freq', title: 'CPU min'},
{key: 'cpuinfo_max_freq', title: 'CPU max'},
{key: 'scaling_governor', title: 'Scaling governor'},
];
const cpuSidebarColumns: DataTableColumn[] = [
{
key: 'key',
title: 'key',
wrap: true,
},
{
key: 'value',
title: 'value',
wrap: true,
},
];
export function Component() {
const instance = usePlugin(devicePlugin);
const {
onStartMonitor,
onStopMonitor,
toggleCPUSidebar,
toggleThermalSidebar,
} = instance;
const cpuState = useValue(instance.cpuState);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const sidebarRows = (id: number) => {
let availableFreqTitle = 'Scaling Available Frequencies';
const selected = cpuState.cpuFreq[id];
if (selected.scaling_available_freqs.length > 0) {
availableFreqTitle +=
' (' + selected.scaling_available_freqs.length.toString() + ')';
}
const keys = [availableFreqTitle, 'Scaling Available Governors'];
const vals = [
buildAvailableFreqList(selected),
buildAvailableGovList(selected),
];
return keys.map<any>((key, idx) => {
return buildSidebarRow(key, vals[idx]);
});
};
const renderCPUSidebar = () => {
if (!cpuState.displayCPUDetail || selectedIds.length == 0) {
return null;
}
const id = selectedIds[0];
return (
<DetailSidebar width={500}>
<Layout.Container pad>
<Typography.Title>CPU Details: CPU_{id}</Typography.Title>
<DataTable
records={sidebarRows(id)}
columns={cpuSidebarColumns}
scrollable={false}
enableSearchbar={false}
/>
</Layout.Container>
</DetailSidebar>
);
};
const renderThermalSidebar = () => {
if (!cpuState.displayThermalInfo) {
return null;
}
return (
<DetailSidebar width={500}>
<Panel
pad={theme.space.small}
title="Thermal Information"
collapsible={false}>
{cpuState.thermalAccessible ? (
<TemperatureTable temperatureMap={cpuState.temperatureMap} />
) : (
'Temperature information not accessible on this device.'
)}
</Panel>
</DetailSidebar>
);
};
const setSelected = useCallback((selected: any) => {
setSelectedIds(selected ? [selected.core] : []);
}, []);
return (
<Layout.Container pad>
<Typography.Title>CPU Info</Typography.Title>
<Toolbar>
{cpuState.monitoring ? (
<Button onClick={onStopMonitor} icon={<PauseCircleOutlined />}>
Pause
</Button>
) : (
<Button onClick={onStartMonitor} icon={<PlayCircleOutlined />}>
Start
</Button>
)}
&nbsp; {cpuState.hardwareInfo}
<Switch
checked={cpuState.displayThermalInfo}
onClick={toggleThermalSidebar}
/>
Thermal Information
<Switch
onClick={toggleCPUSidebar}
checked={cpuState.displayCPUDetail}
/>
CPU Details
{cpuState.displayCPUDetail &&
selectedIds.length == 0 &&
' (Please select a core in the table below)'}
</Toolbar>
<DataTable
records={frequencyRows(cpuState.cpuFreq)}
columns={columns}
scrollable={false}
onSelect={setSelected}
onRowStyle={getRowStyle}
enableSearchbar={false}
/>
{renderCPUSidebar()}
{renderThermalSidebar()}
</Layout.Container>
);
}
function buildAvailableGovList(freq: CPUFrequency): string {
if (freq.scaling_available_governors.length == 0) {
return 'N/A';
}
return freq.scaling_available_governors.join(', ');
}
function buildSidebarRow(key: string, val: any) {
return {
key: key,
value: val,
};
}
function buildRow(freq: CPUFrequency) {
return {
core: freq.cpu_id,
cpu_id: `CPU_${freq.cpu_id}`,
scaling_cur_freq: formatFrequency(freq.scaling_cur_freq),
scaling_min_freq: formatFrequency(freq.scaling_min_freq),
scaling_max_freq: formatFrequency(freq.scaling_max_freq),
cpuinfo_min_freq: formatFrequency(freq.cpuinfo_min_freq),
cpuinfo_max_freq: formatFrequency(freq.cpuinfo_max_freq),
scaling_governor: freq.scaling_governor,
};
}
function frequencyRows(cpuFreqs: Array<CPUFrequency>) {
return cpuFreqs.map(buildRow);
}
function getRowStyle(freq: CPUFrequency) {
if (freq.scaling_cur_freq == -2) {
return {
backgroundColor: theme.backgroundWash,
color: theme.textColorPrimary,
fontWeight: 700,
};
} else if (
freq.scaling_min_freq != freq.cpuinfo_min_freq &&
freq.scaling_min_freq > 0 &&
freq.cpuinfo_min_freq > 0
) {
return {
backgroundColor: theme.warningColor,
color: theme.textColorPrimary,
fontWeight: 700,
};
} else if (
freq.scaling_max_freq != freq.cpuinfo_max_freq &&
freq.scaling_max_freq > 0 &&
freq.cpuinfo_max_freq > 0
) {
return {
backgroundColor: theme.backgroundWash,
color: theme.textColorSecondary,
fontWeight: 700,
};
}
}
function buildAvailableFreqList(freq: CPUFrequency) {
if (freq.scaling_available_freqs.length == 0) {
return <Typography.Text>N/A</Typography.Text>;
}
const info = freq;
return (
<Typography.Text>
{freq.scaling_available_freqs.map((freq, idx) => {
const bold =
freq == info.scaling_cur_freq ||
freq == info.scaling_min_freq ||
freq == info.scaling_max_freq;
return (
<Typography.Text key={idx} strong={bold}>
{formatFrequency(freq)}
{freq == info.scaling_cur_freq && (
<Typography.Text strong={bold}>
{' '}
(scaling current)
</Typography.Text>
)}
{freq == info.scaling_min_freq && (
<Typography.Text strong={bold}> (scaling min)</Typography.Text>
)}
{freq == info.scaling_max_freq && (
<Typography.Text strong={bold}> (scaling max)</Typography.Text>
)}
<br />
</Typography.Text>
);
})}
</Typography.Text>
);
}