fm-bench 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.js +14 -11
- package/src/system.js +65 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { loadHistory, renderHistoryReport } from './history.js';
|
|
|
6
6
|
import { runProcess } from './process.js';
|
|
7
7
|
import { createProgress } from './progress.js';
|
|
8
8
|
import { flattenResults, toCsv, writeReport } from './report.js';
|
|
9
|
+
import { parseBatteryOutput, parseThermalOutput } from './system.js';
|
|
9
10
|
import { legendEntries, renderBenchmarkReport, renderLatencyHistogram, renderLegend, renderModelsTable } from './table.js';
|
|
10
11
|
|
|
11
12
|
const require = createRequire(import.meta.url);
|
|
@@ -473,21 +474,23 @@ async function runDoctor(options) {
|
|
|
473
474
|
}
|
|
474
475
|
|
|
475
476
|
const thermalResult = await runProcess('pmset', ['-g', 'therm'], { timeoutMs: 5_000 });
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const limit = Number.parseInt(thermalMatch[1], 10);
|
|
477
|
+
const thermal = parseThermalOutput(`${thermalResult.stdout || ''}${thermalResult.stderr || ''}`);
|
|
478
|
+
if (thermal.available && thermal.schedulerLimit != null) {
|
|
479
|
+
const limit = thermal.schedulerLimit;
|
|
480
480
|
checks.push(['thermal limit', `${limit}%`, limit >= 100]);
|
|
481
|
+
} else if (thermal.available && thermal.healthyIdle) {
|
|
482
|
+
checks.push(['thermal', 'no thermal pressure', true]);
|
|
483
|
+
} else if (!thermal.available) {
|
|
484
|
+
checks.push(['thermal', 'unavailable', true]);
|
|
481
485
|
}
|
|
482
486
|
|
|
483
487
|
const batteryResult = await runProcess('pmset', ['-g', 'batt'], { timeoutMs: 5_000 });
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
checks.push(['battery', `${pct}${charging ? ' (charging/AC)' : ' (battery)'}`, charging || Number.parseInt(pctMatch?.[1], 10) >= 20]);
|
|
488
|
+
const battery = parseBatteryOutput(`${batteryResult.stdout || ''}${batteryResult.stderr || ''}`);
|
|
489
|
+
if (battery.present) {
|
|
490
|
+
const pct = battery.pct != null ? `${battery.pct}%` : '?%';
|
|
491
|
+
const source = battery.onAC ? 'charging/AC' : 'battery';
|
|
492
|
+
const ok = battery.onAC || (battery.pct != null && battery.pct >= 20);
|
|
493
|
+
checks.push(['battery', `${pct} (${source})`, ok]);
|
|
491
494
|
}
|
|
492
495
|
|
|
493
496
|
const inspection = await inspectModels(options);
|
package/src/system.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Parsers for macOS system probes used by `fm-bench doctor`.
|
|
2
|
+
//
|
|
3
|
+
// macOS 27 changed the text emitted by `pmset -g therm` and `pmset -g batt`.
|
|
4
|
+
// These helpers accept the raw stdout/stderr of those commands and return
|
|
5
|
+
// structured values so the doctor command and its tests are not coupled to
|
|
6
|
+
// the exact wording of any one macOS release.
|
|
7
|
+
|
|
8
|
+
export function parseThermalOutput(output = '') {
|
|
9
|
+
const text = String(output).trim();
|
|
10
|
+
if (!text) {
|
|
11
|
+
return { available: false, schedulerLimit: null, raw: '' };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const limitMatch = text.match(/CPU_Scheduler_Limit\s*=\s*(-?\d+)/);
|
|
15
|
+
if (limitMatch) {
|
|
16
|
+
const schedulerLimit = Number.parseInt(limitMatch[1], 10);
|
|
17
|
+
return {
|
|
18
|
+
available: true,
|
|
19
|
+
schedulerLimit,
|
|
20
|
+
raw: text
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// macOS 27 (and recent releases) prints these informational "Note:" lines
|
|
25
|
+
// when the system is NOT under thermal pressure. Treat that as a healthy,
|
|
26
|
+
// explicitly-reported state rather than silently dropping the check.
|
|
27
|
+
const hasNote = /^Note:/m.test(text);
|
|
28
|
+
if (hasNote) {
|
|
29
|
+
return {
|
|
30
|
+
available: true,
|
|
31
|
+
schedulerLimit: null,
|
|
32
|
+
raw: text,
|
|
33
|
+
healthyIdle: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { available: false, schedulerLimit: null, raw: text };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseBatteryOutput(output = '') {
|
|
41
|
+
const text = String(output).trim();
|
|
42
|
+
if (!text) {
|
|
43
|
+
return { present: false, pct: null, onAC: false, raw: '' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const lines = text.split(/\r?\n/);
|
|
47
|
+
const batteryLine = lines.find((line) => line.includes('%')) || '';
|
|
48
|
+
const pctMatch = batteryLine.match(/(\d+)%/);
|
|
49
|
+
const pct = pctMatch ? Number.parseInt(pctMatch[1], 10) : null;
|
|
50
|
+
|
|
51
|
+
// macOS 27 writes "Now drawing from 'AC Power'" on its own line and
|
|
52
|
+
// "AC attached" on the battery line. Older releases used "AC Power" in
|
|
53
|
+
// the battery line itself. Accept either form.
|
|
54
|
+
const onAC = /AC Power|AC attached|AC attached;/i.test(text)
|
|
55
|
+
|| /;\s*charging\b/i.test(batteryLine);
|
|
56
|
+
|
|
57
|
+
const present = /InternalBattery|present:\s*true/i.test(text);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
present: present || pct != null,
|
|
61
|
+
pct,
|
|
62
|
+
onAC,
|
|
63
|
+
raw: text
|
|
64
|
+
};
|
|
65
|
+
}
|