in desktop/plugins/public/cpu/index.tsx [73:375]
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,
};
}