mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
fix: apply the review items for #692 at merge
host-sensors: never await the PowerShell RAM probe on a read; keep the resolved total and show the memory in use alone until it answers (test with a held PowerShell) test: rename the Windows sampler test that said the GPU counters are added "for good" test: make host-sensors.test.ts's @fileoverview describe what that file covers host-sensors: make the nvidiaSkipUntil JSDoc describe the back-off deadline docs: Settings Reference says the host readings live inside System Stats and nothing is fetched while it is off Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
23a9d78cdd
commit
7783fcd0ce
4 files changed
+81
-22
No files matched your search
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* The order and the back-offs of readHostSensors, with `child_process` and the sysfs reads mocked so no
|
||||
* hardware is needed: native GPU readers go first (a machine with a working nvidia-smi never starts the
|
||||
* Windows GPU counters), a failing reader is left alone for a minute, a cold Windows sampler is not
|
||||
* waited for, and the GPU counters leave the sampler again once nothing needs them.
|
||||
* Windows GPU counters), a failing reader is left alone for a minute, neither a cold Windows sampler nor
|
||||
* the PowerShell RAM probe is waited for, and the GPU counters leave the sampler again once nothing needs them.
|
||||
*/
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { PassThrough } from 'node:stream';
|
||||
@@ -12,6 +12,9 @@ type ExecCall = { cmd: string; args: string[] };
|
||||
const state = vi.hoisted(() => ({
|
||||
nvidia: '' as string | Error,
|
||||
powershell: '' as string | Error,
|
||||
/** When set, PowerShell does not answer until the test calls the callbacks kept here. */
|
||||
holdPowershell: false,
|
||||
heldPowershell: [] as Array<() => void>,
|
||||
execCalls: [] as Array<{ cmd: string; args: string[] }>,
|
||||
spawnCalls: [] as Array<{ cmd: string; args: string[]; stdout: unknown }>,
|
||||
}));
|
||||
@@ -19,6 +22,14 @@ const state = vi.hoisted(() => ({
|
||||
vi.mock('node:child_process', () => ({
|
||||
execFile: (cmd: string, args: string[], _opts: unknown, cb: (err: Error | null, stdout: string) => void) => {
|
||||
state.execCalls.push({ cmd, args });
|
||||
if (state.holdPowershell && cmd.endsWith('powershell.exe')) {
|
||||
state.heldPowershell.push(() => {
|
||||
const held = state.powershell;
|
||||
if (held instanceof Error) cb(held, '');
|
||||
else cb(null, held);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const answer = cmd === 'nvidia-smi' ? state.nvidia : cmd.endsWith('powershell.exe') ? state.powershell : '';
|
||||
if (answer instanceof Error) cb(answer, '');
|
||||
else cb(null, answer);
|
||||
@@ -56,6 +67,8 @@ const tick = () => new Promise((r) => setImmediate(r));
|
||||
beforeEach(() => {
|
||||
state.nvidia = NVIDIA;
|
||||
state.powershell = new Error('powershell failed');
|
||||
state.holdPowershell = false;
|
||||
state.heldPowershell = [];
|
||||
state.execCalls = [];
|
||||
state.spawnCalls = [];
|
||||
process.env.WSL_INTEROP = '1';
|
||||
@@ -138,6 +151,46 @@ describe('a failing reader is left alone for a minute', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('the PowerShell RAM probe', () => {
|
||||
it('is never waited for: the memory in use shows at once, and the total once Windows has answered', async () => {
|
||||
state.nvidia = new Error('no nvidia');
|
||||
state.holdPowershell = true;
|
||||
const { readHostSensors, stopHostSensors } = await load();
|
||||
await readHostSensors({ gpu: true }); // starts the sampler
|
||||
(state.spawnCalls[0].stdout as PassThrough).write(HEADER + ROW);
|
||||
await tick();
|
||||
vi.setSystemTime(Date.now() + 2500);
|
||||
// PowerShell has not answered (and never will unless the test lets it): the read must not wait for it
|
||||
const first = await Promise.race([
|
||||
readHostSensors({ gpu: true }),
|
||||
new Promise<'waited'>((r) => setTimeout(() => r('waited'), 1000)),
|
||||
]);
|
||||
expect(first).not.toBe('waited');
|
||||
expect((first as { gpu?: object }).gpu).toEqual({ percent: 20, memUsedMB: 1024 });
|
||||
expect(counted('powershell.exe')).toBe(1);
|
||||
// a poll while the probe is still running starts no second PowerShell
|
||||
vi.setSystemTime(Date.now() + 2500);
|
||||
(state.spawnCalls[0].stdout as PassThrough).write(ROW);
|
||||
await tick();
|
||||
expect((await readHostSensors({ gpu: true })).gpu?.memTotalMB).toBeUndefined();
|
||||
expect(counted('powershell.exe')).toBe(1);
|
||||
// Windows answers 16 GiB: an integrated GPU may use half of it
|
||||
state.powershell = '17179869184\r\n';
|
||||
state.heldPowershell.forEach((answer) => answer());
|
||||
await tick();
|
||||
for (let i = 0; i < 2; i++) {
|
||||
vi.setSystemTime(Date.now() + 2500);
|
||||
(state.spawnCalls[0].stdout as PassThrough).write(ROW);
|
||||
await tick();
|
||||
const out = await readHostSensors({ gpu: true });
|
||||
expect(out.gpu).toEqual({ percent: 20, memUsedMB: 1024, memTotalMB: 8192, memPercent: 13 });
|
||||
}
|
||||
// the answer is kept: PowerShell is not asked again
|
||||
expect(counted('powershell.exe')).toBe(1);
|
||||
stopHostSensors();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the GPU counters do not stay in the sampler for good', () => {
|
||||
it('holds them for a while after the last read that needed them, then drops them', async () => {
|
||||
const { createWindowsSource, windowsCounterList } = await load();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* @fileoverview The pure parsers behind the opt-in header readings (GPU, CPU clock), the route's
|
||||
* opt-in query handling, and the header display's defaults. No hardware needed.
|
||||
* @fileoverview The pure parsers behind the opt-in header readings (nvidia-smi, the amdgpu clock table,
|
||||
* cpufreq and /proc/cpuinfo, typeperf), readHostSensors reading nothing unless a reading is asked for, the
|
||||
* typeperf counter stream's life cycle (first sample, recycling, idle stop, back-off after a failed start,
|
||||
* no wait on a cold sampler by default, a late row from a replaced sampler), and the Windows sampler that
|
||||
* every browser shares. No hardware needed.
|
||||
*/
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { PassThrough } from 'node:stream';
|
||||
@@ -233,7 +236,7 @@ describe('the Windows sampler shared by every browser', () => {
|
||||
expect(children[0].killed).toBe(false);
|
||||
});
|
||||
|
||||
it('adds the GPU counters once, for good, on a machine with no native GPU reader', async () => {
|
||||
it('adds the GPU counters with one restart on a machine with no native GPU reader, and keeps them while needed', async () => {
|
||||
const { children, source } = rig();
|
||||
void source.read();
|
||||
expect(children[0].counters).toEqual(windowsCounterList(false));
|
||||
|
||||
Reference in new issue
Block a user