@yourfam/yf-vitals 0.2.0 → 0.3.0
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/README.md +1 -0
- package/package.json +1 -1
- package/src/args.js +1 -0
- package/src/dashboard.js +12 -2
- package/src/disk.js +6 -2
- package/src/diskuse.js +103 -0
- package/src/format.js +4 -2
- package/src/history.js +3 -1
- package/src/render.js +13 -0
- package/src/sample.js +128 -65
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ One live dashboard. Refresh every **1.0s** (or `--interval`, or **2.0s** with `-
|
|
|
36
36
|
|---|---|
|
|
37
37
|
| **CPU** | Overall utilization 0–100%, plus physical / logical core counts |
|
|
38
38
|
| **RAM** | Percent used, plus used / total (`GiB` / `MiB`) |
|
|
39
|
+
| **USE** | Local hard-disk fill (system volume first). Skips Google Drive, iCloud, network, FAT/USB. Extra local disks as extra lines |
|
|
39
40
|
| **DSK** | Read and write **rates** (KiB/s, MiB/s, GiB/s) |
|
|
40
41
|
| **NET** | Send ↑ and receive ↓ in **kbps / Mbps / Gbps** (decimal bits; 1 Mbps = 1e6 bit/s) |
|
|
41
42
|
| **GPU** | Utilization and VRAM when telemetry exists; **the row is omitted** when it does not |
|
package/package.json
CHANGED
package/src/args.js
CHANGED
|
@@ -98,6 +98,7 @@ Usage:
|
|
|
98
98
|
--version, -V Package version
|
|
99
99
|
|
|
100
100
|
q / Q / Ctrl+C quit. GPU row is omitted when the OS has no GPU telemetry.
|
|
101
|
+
USE is local NTFS/APFS/HFS fill (no Google Drive, iCloud, or network shares).
|
|
101
102
|
Disk rates are KiB/s–GiB/s; network rates are kbps/Mbps/Gbps.
|
|
102
103
|
Disk / net sparks are split (R/W, ↑/↓) and scale to the pair max in the last 60 ticks.
|
|
103
104
|
Bar fill turns yellow ≥50% and red ≥80%. Unused spark slots use the lowest tick.
|
package/src/dashboard.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { appendHistory, createHistory } from "./history.js";
|
|
2
2
|
import { renderFrame } from "./render.js";
|
|
3
|
-
import { createSampler, mergeLastGood } from "./sample.js";
|
|
3
|
+
import { createSampler, instantSnapshot, mergeLastGood } from "./sample.js";
|
|
4
4
|
import { CLEAR_HOME, enterTerminal, restoreTerminal } from "./tty.js";
|
|
5
5
|
|
|
6
6
|
const CTRL_C = 0x03;
|
|
@@ -61,6 +61,16 @@ export async function runDashboard(args, deps) {
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
enterTerminal(write, stdin);
|
|
64
|
+
const first = instantSnapshot();
|
|
65
|
+
write(
|
|
66
|
+
CLEAR_HOME +
|
|
67
|
+
renderFrame(first, createHistory(), {
|
|
68
|
+
columns: deps.columns(),
|
|
69
|
+
env: deps.env,
|
|
70
|
+
isTTY: true,
|
|
71
|
+
intervalSec: args.interval,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
64
74
|
|
|
65
75
|
const onData = (chunk) => {
|
|
66
76
|
if (isQuitKey(chunk)) quit = true;
|
|
@@ -82,7 +92,7 @@ export async function runDashboard(args, deps) {
|
|
|
82
92
|
sampler = deps.sampler || createSampler();
|
|
83
93
|
let history = createHistory();
|
|
84
94
|
/** @type {import("./sample.js").Snapshot | null} */
|
|
85
|
-
let last =
|
|
95
|
+
let last = first;
|
|
86
96
|
|
|
87
97
|
while (!shouldStop()) {
|
|
88
98
|
let snap;
|
package/src/disk.js
CHANGED
|
@@ -87,10 +87,14 @@ export function createWindowsDiskReader() {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
return {
|
|
90
|
+
start() {
|
|
91
|
+
ensure();
|
|
92
|
+
},
|
|
90
93
|
/**
|
|
94
|
+
* @param {number} [timeoutMs]
|
|
91
95
|
* @returns {Promise<ReturnType<typeof parseDiskCounterLine>>}
|
|
92
96
|
*/
|
|
93
|
-
read() {
|
|
97
|
+
read(timeoutMs = 400) {
|
|
94
98
|
if (closed) return Promise.resolve(null);
|
|
95
99
|
ensure();
|
|
96
100
|
if (!child || !child.stdin.writable) return Promise.resolve(null);
|
|
@@ -99,7 +103,7 @@ export function createWindowsDiskReader() {
|
|
|
99
103
|
const i = pending.findIndex((p) => p.resolve === done);
|
|
100
104
|
if (i >= 0) pending.splice(i, 1);
|
|
101
105
|
resolve(null);
|
|
102
|
-
},
|
|
106
|
+
}, timeoutMs);
|
|
103
107
|
const done = (v) => {
|
|
104
108
|
clearTimeout(t);
|
|
105
109
|
resolve(v);
|
package/src/diskuse.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const MIN_BYTES = 8 * 1024 * 1024 * 1024;
|
|
2
|
+
|
|
3
|
+
const SKIP_FS =
|
|
4
|
+
/^(fat|fat12|fat16|fat32|msdos|nfs|smbfs|cifs|afp|webdav|fuse|sshfs|osxfuse|macfuse|tmpfs|devfs|autofs|iso9660|udf|overlay|9p)$/i;
|
|
5
|
+
|
|
6
|
+
const SKIP_NAME =
|
|
7
|
+
/google\s*drive|gdrive|icloud|onedrive|dropbox|cloudstorage|cloudmounter|box\.com/i;
|
|
8
|
+
|
|
9
|
+
const SKIP_MOUNT =
|
|
10
|
+
/^\/(System\/Volumes\/(Preboot|VM|Update|Recovery|iOS|hardware|xarts|cryptex)|\.MobileBackups|Volumes\/\.timemachine)/i;
|
|
11
|
+
|
|
12
|
+
const LOCAL_FS = /^(ntfs|refs|apfs|hfs|hfs\+|ext[234]|xfs|btrfs|zfs)$/i;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {{ fs?: string, type?: string, mount?: string, size?: number, used?: number }} row
|
|
16
|
+
*/
|
|
17
|
+
export function isLocalCapacityVolume(row) {
|
|
18
|
+
if (!row) return false;
|
|
19
|
+
const type = String(row.type || "");
|
|
20
|
+
const mount = String(row.mount || "");
|
|
21
|
+
const fs = String(row.fs || "");
|
|
22
|
+
const blob = `${type} ${mount} ${fs}`;
|
|
23
|
+
if (SKIP_NAME.test(blob)) return false;
|
|
24
|
+
if (SKIP_FS.test(type)) return false;
|
|
25
|
+
if (SKIP_MOUNT.test(mount)) return false;
|
|
26
|
+
const size = Number(row.size) || 0;
|
|
27
|
+
if (size < MIN_BYTES) return false;
|
|
28
|
+
if (LOCAL_FS.test(type)) return true;
|
|
29
|
+
if (/^[A-Z]:\\?$/i.test(mount || fs) && LOCAL_FS.test(type)) return true;
|
|
30
|
+
if (mount === "/" || mount === "/System/Volumes/Data") return true;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} mount
|
|
36
|
+
*/
|
|
37
|
+
export function shortMount(mount) {
|
|
38
|
+
const m = String(mount || "");
|
|
39
|
+
const drive = m.match(/^([A-Z]:)\\?$/i);
|
|
40
|
+
if (drive) return drive[1].toUpperCase();
|
|
41
|
+
if (m === "/System/Volumes/Data") return "Data";
|
|
42
|
+
if (m === "/") return "/";
|
|
43
|
+
const parts = m.split(/[/\\]/).filter(Boolean);
|
|
44
|
+
return parts[parts.length - 1] || m || "?";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {{ fs?: string, type?: string, mount?: string, size?: number, used?: number, use?: number }} row
|
|
49
|
+
*/
|
|
50
|
+
function systemScore(row) {
|
|
51
|
+
const m = String(row.mount || "");
|
|
52
|
+
if (/^C:\\?$/i.test(m)) return 3;
|
|
53
|
+
if (m === "/System/Volumes/Data") return 2;
|
|
54
|
+
if (m === "/") return 1;
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Array<{ fs?: string, type?: string, mount?: string, size?: number, used?: number, use?: number }>} rows
|
|
60
|
+
* @returns {{ percent: number, used: number, total: number, mount: string, others: { percent: number, used: number, total: number, mount: string }[] } | null}
|
|
61
|
+
*/
|
|
62
|
+
export function summarizeLocalDisks(rows) {
|
|
63
|
+
const local = (Array.isArray(rows) ? rows : []).filter(isLocalCapacityVolume);
|
|
64
|
+
/** @type {typeof local} */
|
|
65
|
+
const uniq = [];
|
|
66
|
+
for (const r of local.slice().sort((a, b) => systemScore(b) - systemScore(a) || (b.size || 0) - (a.size || 0))) {
|
|
67
|
+
const dupIdx = uniq.findIndex(
|
|
68
|
+
(u) =>
|
|
69
|
+
Math.abs((u.size || 0) - (r.size || 0)) < 1024 * 1024 &&
|
|
70
|
+
Math.abs((u.used || 0) - (r.used || 0)) < 0.01 * (r.size || 1),
|
|
71
|
+
);
|
|
72
|
+
if (dupIdx >= 0) continue;
|
|
73
|
+
uniq.push(r);
|
|
74
|
+
}
|
|
75
|
+
if (!uniq.length) return null;
|
|
76
|
+
const primary = uniq[0];
|
|
77
|
+
const total = Number(primary.size) || 0;
|
|
78
|
+
const used = Number(primary.used) || 0;
|
|
79
|
+
const percent =
|
|
80
|
+
typeof primary.use === "number" && Number.isFinite(primary.use)
|
|
81
|
+
? primary.use
|
|
82
|
+
: total > 0
|
|
83
|
+
? (used / total) * 100
|
|
84
|
+
: 0;
|
|
85
|
+
return {
|
|
86
|
+
percent: Math.min(100, Math.max(0, percent)),
|
|
87
|
+
used,
|
|
88
|
+
total,
|
|
89
|
+
mount: shortMount(primary.mount || primary.fs || ""),
|
|
90
|
+
others: uniq.slice(1).map((r) => {
|
|
91
|
+
const t = Number(r.size) || 0;
|
|
92
|
+
const u = Number(r.used) || 0;
|
|
93
|
+
const p =
|
|
94
|
+
typeof r.use === "number" && Number.isFinite(r.use) ? r.use : t > 0 ? (u / t) * 100 : 0;
|
|
95
|
+
return {
|
|
96
|
+
percent: Math.min(100, Math.max(0, p)),
|
|
97
|
+
used: u,
|
|
98
|
+
total: t,
|
|
99
|
+
mount: shortMount(r.mount || r.fs || ""),
|
|
100
|
+
};
|
|
101
|
+
}),
|
|
102
|
+
};
|
|
103
|
+
}
|
package/src/format.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const KIB = 1024;
|
|
2
2
|
const MIB = 1024 * 1024;
|
|
3
3
|
const GIB = 1024 * 1024 * 1024;
|
|
4
|
+
const TIB = 1024 * 1024 * 1024 * 1024;
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @param {number} bytes
|
|
@@ -8,6 +9,7 @@ const GIB = 1024 * 1024 * 1024;
|
|
|
8
9
|
export function formatBytes(bytes) {
|
|
9
10
|
const n = Number(bytes);
|
|
10
11
|
if (!Number.isFinite(n) || n < 0) return "n/a";
|
|
12
|
+
if (n >= TIB) return `${(n / TIB).toFixed(1)} TiB`;
|
|
11
13
|
if (n >= GIB) return `${(n / GIB).toFixed(1)} GiB`;
|
|
12
14
|
return `${(n / MIB).toFixed(1)} MiB`;
|
|
13
15
|
}
|
|
@@ -20,8 +22,8 @@ export function formatBytePair(used, total) {
|
|
|
20
22
|
const t = Number(total);
|
|
21
23
|
const u = Number(used);
|
|
22
24
|
if (!Number.isFinite(t) || t < 0 || !Number.isFinite(u) || u < 0) return "n/a";
|
|
23
|
-
const unit = t >= GIB ? "GiB" : "MiB";
|
|
24
|
-
const div = unit === "GiB" ? GIB : MIB;
|
|
25
|
+
const unit = t >= TIB ? "TiB" : t >= GIB ? "GiB" : "MiB";
|
|
26
|
+
const div = unit === "TiB" ? TIB : unit === "GiB" ? GIB : MIB;
|
|
25
27
|
return `${(u / div).toFixed(1)} / ${(t / div).toFixed(1)} ${unit}`;
|
|
26
28
|
}
|
|
27
29
|
|
package/src/history.js
CHANGED
|
@@ -9,10 +9,11 @@ import { HISTORY_SIZE } from "./bar.js";
|
|
|
9
9
|
* @property {number[]} dskW
|
|
10
10
|
* @property {number[]} netUp
|
|
11
11
|
* @property {number[]} netDn
|
|
12
|
+
* @property {number[]} diskUse
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
export function createHistory() {
|
|
15
|
-
return { cpu: [], ram: [], gpu: [], dskR: [], dskW: [], netUp: [], netDn: [] };
|
|
16
|
+
return { cpu: [], ram: [], gpu: [], dskR: [], dskW: [], netUp: [], netDn: [], diskUse: [] };
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -43,5 +44,6 @@ export function appendHistory(history, snap) {
|
|
|
43
44
|
dskW: pushSample(history.dskW, snap.disk?.writeBps),
|
|
44
45
|
netUp: pushSample(history.netUp, snap.net?.txBps),
|
|
45
46
|
netDn: pushSample(history.netDn, snap.net?.rxBps),
|
|
47
|
+
diskUse: pushSample(history.diskUse, snap.diskUse?.percent),
|
|
46
48
|
};
|
|
47
49
|
}
|
package/src/render.js
CHANGED
|
@@ -168,6 +168,19 @@ export function renderFrame(snap, history, opts = {}) {
|
|
|
168
168
|
}
|
|
169
169
|
lines.push("");
|
|
170
170
|
|
|
171
|
+
if (snap.diskUse) {
|
|
172
|
+
lines.push(
|
|
173
|
+
`${color.green("USE")} ${paintPercentBar(snap.diskUse.percent, color, "green", ascii)} ${formatPercent(snap.diskUse.percent)} ${formatBytePair(snap.diskUse.used, snap.diskUse.total)} ${snap.diskUse.mount}`,
|
|
174
|
+
);
|
|
175
|
+
lines.push(` ${color.green(sparkline(history.diskUse, sparkW, { ascii, max: 100 }))}`);
|
|
176
|
+
for (const extra of snap.diskUse.others || []) {
|
|
177
|
+
lines.push(
|
|
178
|
+
` ${extra.mount} ${formatPercent(extra.percent).trim()} ${formatBytePair(extra.used, extra.total)}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (snap.diskUse) lines.push("");
|
|
183
|
+
|
|
171
184
|
const gpuLines = buildGpuRow(snap.gpu, {
|
|
172
185
|
history: history.gpu,
|
|
173
186
|
ascii,
|
package/src/sample.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import { createWindowsDiskReader } from "./disk.js";
|
|
3
|
+
import { summarizeLocalDisks } from "./diskuse.js";
|
|
3
4
|
import { rateFromCounters } from "./rates.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -36,6 +37,7 @@ import { rateFromCounters } from "./rates.js";
|
|
|
36
37
|
* @property {DiskSample | null} disk
|
|
37
38
|
* @property {NetSample | null} net
|
|
38
39
|
* @property {GpuSample | null} gpu
|
|
40
|
+
* @property {{ percent: number, used: number, total: number, mount: string, others: { percent: number, used: number, total: number, mount: string }[] } | null} diskUse
|
|
39
41
|
*/
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -82,6 +84,43 @@ function windowsLabel(release) {
|
|
|
82
84
|
return "Windows";
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Sync snapshot from `os` only — first paint must not wait on PowerShell / nvidia-smi.
|
|
89
|
+
* @returns {Snapshot}
|
|
90
|
+
*/
|
|
91
|
+
export function instantSnapshot() {
|
|
92
|
+
const logical = Math.max(os.cpus().length, 1);
|
|
93
|
+
const total = os.totalmem();
|
|
94
|
+
const free = os.freemem();
|
|
95
|
+
const used = Math.max(0, total - free);
|
|
96
|
+
return {
|
|
97
|
+
ts: Date.now(),
|
|
98
|
+
hostname: os.hostname(),
|
|
99
|
+
osName: shortOsName(null),
|
|
100
|
+
physical: logical,
|
|
101
|
+
logical,
|
|
102
|
+
ramTotal: total,
|
|
103
|
+
cpu: null,
|
|
104
|
+
ram: total > 0 ? { percent: Math.min(100, (used / total) * 100), used, total } : null,
|
|
105
|
+
disk: { readBps: null, writeBps: null },
|
|
106
|
+
net: { txBps: null, rxBps: null },
|
|
107
|
+
gpu: null,
|
|
108
|
+
diskUse: null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ramFromOs(ramTotal) {
|
|
113
|
+
const total = ramTotal || os.totalmem();
|
|
114
|
+
const free = os.freemem();
|
|
115
|
+
const used = Math.max(0, total - free);
|
|
116
|
+
if (total <= 0) return null;
|
|
117
|
+
return {
|
|
118
|
+
percent: Math.min(100, Math.max(0, (used / total) * 100)),
|
|
119
|
+
used,
|
|
120
|
+
total,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
85
124
|
/**
|
|
86
125
|
* GPU memory fields in systeminformation are sometimes MiB, sometimes bytes.
|
|
87
126
|
* @param {number | null} n
|
|
@@ -139,6 +178,7 @@ export function mergeLastGood(last, next) {
|
|
|
139
178
|
disk: null,
|
|
140
179
|
net: null,
|
|
141
180
|
gpu: null,
|
|
181
|
+
diskUse: null,
|
|
142
182
|
};
|
|
143
183
|
return {
|
|
144
184
|
ts: next.ts,
|
|
@@ -152,6 +192,7 @@ export function mergeLastGood(last, next) {
|
|
|
152
192
|
disk: mergePair(next.disk, base.disk, "readBps", "writeBps"),
|
|
153
193
|
net: mergePair(next.net, base.net, "txBps", "rxBps"),
|
|
154
194
|
gpu: next.gpu ?? base.gpu,
|
|
195
|
+
diskUse: next.diskUse ?? base.diskUse,
|
|
155
196
|
};
|
|
156
197
|
}
|
|
157
198
|
|
|
@@ -248,9 +289,29 @@ export function createSampler(si) {
|
|
|
248
289
|
let prevTs = null;
|
|
249
290
|
/** @type {Snapshot | null} */
|
|
250
291
|
let last = null;
|
|
251
|
-
/** @type {
|
|
252
|
-
let
|
|
253
|
-
|
|
292
|
+
/** @type {{ hostname: string, osName: string, physical: number, logical: number, ramTotal: number }} */
|
|
293
|
+
let meta = (() => {
|
|
294
|
+
const s = instantSnapshot();
|
|
295
|
+
return {
|
|
296
|
+
hostname: s.hostname,
|
|
297
|
+
osName: s.osName,
|
|
298
|
+
physical: s.physical,
|
|
299
|
+
logical: s.logical,
|
|
300
|
+
ramTotal: s.ramTotal,
|
|
301
|
+
};
|
|
302
|
+
})();
|
|
303
|
+
let staticStarted = false;
|
|
304
|
+
/** @type {GpuSample | null} */
|
|
305
|
+
let gpuCache = null;
|
|
306
|
+
let gpuStarted = false;
|
|
307
|
+
/** @type {Snapshot["diskUse"]} */
|
|
308
|
+
let diskUseCache = null;
|
|
309
|
+
let diskUseStarted = false;
|
|
310
|
+
const win32 = process.platform === "win32";
|
|
311
|
+
const winDisk = win32 ? createWindowsDiskReader() : null;
|
|
312
|
+
winDisk?.start();
|
|
313
|
+
let winReady = false;
|
|
314
|
+
prevCpu = cpuIdleTotal(os.cpus());
|
|
254
315
|
|
|
255
316
|
async function loadSi() {
|
|
256
317
|
if (si) return si;
|
|
@@ -258,43 +319,27 @@ export function createSampler(si) {
|
|
|
258
319
|
return mod.default ?? mod;
|
|
259
320
|
}
|
|
260
321
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
// keep os.cpus() counts
|
|
280
|
-
}
|
|
281
|
-
try {
|
|
282
|
-
const mem = await lib.mem();
|
|
283
|
-
if (mem?.total) ramTotal = mem.total;
|
|
284
|
-
} catch {
|
|
285
|
-
// keep os.totalmem
|
|
286
|
-
}
|
|
287
|
-
return { hostname, osName, physical, logical, ramTotal };
|
|
322
|
+
function kickStatic(lib) {
|
|
323
|
+
if (staticStarted) return;
|
|
324
|
+
staticStarted = true;
|
|
325
|
+
(async () => {
|
|
326
|
+
try {
|
|
327
|
+
const info = await lib.osInfo();
|
|
328
|
+
meta = { ...meta, osName: shortOsName(info) };
|
|
329
|
+
} catch {
|
|
330
|
+
// keep fallback
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const cpu = await lib.cpu();
|
|
334
|
+
if (cpu?.physicalCores) meta = { ...meta, physical: cpu.physicalCores };
|
|
335
|
+
if (cpu?.cores) meta = { ...meta, logical: cpu.cores };
|
|
336
|
+
} catch {
|
|
337
|
+
// keep os.cpus() counts
|
|
338
|
+
}
|
|
339
|
+
})();
|
|
288
340
|
}
|
|
289
341
|
|
|
290
|
-
async function sampleCpu(
|
|
291
|
-
try {
|
|
292
|
-
const load = await lib.currentLoad();
|
|
293
|
-
const pct = num(load?.currentLoad);
|
|
294
|
-
if (pct != null) return { percent: Math.min(100, Math.max(0, pct)) };
|
|
295
|
-
} catch {
|
|
296
|
-
// fall through to os.cpus() idle-delta
|
|
297
|
-
}
|
|
342
|
+
async function sampleCpu() {
|
|
298
343
|
const curr = cpuIdleTotal(os.cpus());
|
|
299
344
|
const pct = cpuPercentFromDelta(prevCpu, curr);
|
|
300
345
|
prevCpu = curr;
|
|
@@ -303,6 +348,7 @@ export function createSampler(si) {
|
|
|
303
348
|
}
|
|
304
349
|
|
|
305
350
|
async function sampleRam(lib, ramTotal) {
|
|
351
|
+
if (win32) return ramFromOs(ramTotal);
|
|
306
352
|
try {
|
|
307
353
|
const mem = await lib.mem();
|
|
308
354
|
const total = num(mem?.total) ?? ramTotal;
|
|
@@ -310,22 +356,14 @@ export function createSampler(si) {
|
|
|
310
356
|
let used = null;
|
|
311
357
|
if (total != null && available != null) used = Math.max(0, total - available);
|
|
312
358
|
else used = num(mem?.used) ?? num(mem?.active);
|
|
313
|
-
if (used == null || total == null || total <= 0) return
|
|
359
|
+
if (used == null || total == null || total <= 0) return ramFromOs(ramTotal);
|
|
314
360
|
return {
|
|
315
361
|
percent: Math.min(100, Math.max(0, (used / total) * 100)),
|
|
316
362
|
used,
|
|
317
363
|
total,
|
|
318
364
|
};
|
|
319
365
|
} catch {
|
|
320
|
-
|
|
321
|
-
const free = os.freemem();
|
|
322
|
-
const used = total - free;
|
|
323
|
-
if (total <= 0) return null;
|
|
324
|
-
return {
|
|
325
|
-
percent: Math.min(100, Math.max(0, (used / total) * 100)),
|
|
326
|
-
used,
|
|
327
|
-
total,
|
|
328
|
-
};
|
|
366
|
+
return ramFromOs(ramTotal);
|
|
329
367
|
}
|
|
330
368
|
}
|
|
331
369
|
|
|
@@ -336,13 +374,13 @@ export function createSampler(si) {
|
|
|
336
374
|
async function sampleDisk(lib, ts, win) {
|
|
337
375
|
let rx = win ? num(win.rx) : null;
|
|
338
376
|
let wx = win ? num(win.wx) : null;
|
|
339
|
-
if (rx == null && wx == null) {
|
|
377
|
+
if (rx == null && wx == null && !win32) {
|
|
340
378
|
try {
|
|
341
379
|
const fs = await lib.fsStats();
|
|
342
380
|
rx = num(fs?.rx);
|
|
343
381
|
wx = num(fs?.wx);
|
|
344
382
|
} catch {
|
|
345
|
-
//
|
|
383
|
+
// ignore
|
|
346
384
|
}
|
|
347
385
|
}
|
|
348
386
|
if (rx == null && wx == null) return { readBps: null, writeBps: null };
|
|
@@ -359,7 +397,7 @@ export function createSampler(si) {
|
|
|
359
397
|
async function sampleNet(lib, ts, win) {
|
|
360
398
|
let rx = win ? num(win.netRx) : null;
|
|
361
399
|
let tx = win ? num(win.netTx) : null;
|
|
362
|
-
if (rx == null && tx == null) {
|
|
400
|
+
if (rx == null && tx == null && !win32) {
|
|
363
401
|
try {
|
|
364
402
|
const stats = await lib.networkStats("*");
|
|
365
403
|
const summed = sumNetBytes(Array.isArray(stats) ? stats : stats ? [stats] : []);
|
|
@@ -375,13 +413,36 @@ export function createSampler(si) {
|
|
|
375
413
|
return { txBps, rxBps };
|
|
376
414
|
}
|
|
377
415
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
416
|
+
function kickGpu(lib) {
|
|
417
|
+
if (gpuStarted) return;
|
|
418
|
+
gpuStarted = true;
|
|
419
|
+
lib
|
|
420
|
+
.graphics()
|
|
421
|
+
.then((g) => {
|
|
422
|
+
gpuCache = pickGpu(g?.controllers || []);
|
|
423
|
+
})
|
|
424
|
+
.catch(() => {
|
|
425
|
+
gpuCache = null;
|
|
426
|
+
})
|
|
427
|
+
.finally(() => {
|
|
428
|
+
gpuStarted = false;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function kickDiskUse(lib) {
|
|
433
|
+
if (diskUseStarted) return;
|
|
434
|
+
diskUseStarted = true;
|
|
435
|
+
lib
|
|
436
|
+
.fsSize()
|
|
437
|
+
.then((rows) => {
|
|
438
|
+
diskUseCache = summarizeLocalDisks(rows);
|
|
439
|
+
})
|
|
440
|
+
.catch(() => {})
|
|
441
|
+
.finally(() => {
|
|
442
|
+
setTimeout(() => {
|
|
443
|
+
diskUseStarted = false;
|
|
444
|
+
}, 15000);
|
|
445
|
+
});
|
|
385
446
|
}
|
|
386
447
|
|
|
387
448
|
return {
|
|
@@ -390,16 +451,17 @@ export function createSampler(si) {
|
|
|
390
451
|
*/
|
|
391
452
|
async sample() {
|
|
392
453
|
const lib = await loadSi();
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
454
|
+
kickStatic(lib);
|
|
455
|
+
kickGpu(lib);
|
|
456
|
+
kickDiskUse(lib);
|
|
457
|
+
const win = winDisk ? await winDisk.read(winReady ? 400 : 50).catch(() => null) : null;
|
|
458
|
+
if (win) winReady = true;
|
|
396
459
|
const ts = Date.now();
|
|
397
|
-
const [cpu, ram, disk, net
|
|
398
|
-
sampleCpu(
|
|
460
|
+
const [cpu, ram, disk, net] = await Promise.all([
|
|
461
|
+
sampleCpu().catch(() => last?.cpu ?? null),
|
|
399
462
|
sampleRam(lib, meta.ramTotal).catch(() => last?.ram ?? null),
|
|
400
463
|
sampleDisk(lib, ts, win).catch(() => last?.disk ?? { readBps: null, writeBps: null }),
|
|
401
464
|
sampleNet(lib, ts, win).catch(() => last?.net ?? { txBps: null, rxBps: null }),
|
|
402
|
-
sampleGpu(lib).catch(() => last?.gpu ?? null),
|
|
403
465
|
]);
|
|
404
466
|
prevTs = ts;
|
|
405
467
|
const snap = mergeLastGood(last, {
|
|
@@ -409,7 +471,8 @@ export function createSampler(si) {
|
|
|
409
471
|
ram,
|
|
410
472
|
disk,
|
|
411
473
|
net,
|
|
412
|
-
gpu,
|
|
474
|
+
gpu: gpuCache,
|
|
475
|
+
diskUse: diskUseCache,
|
|
413
476
|
});
|
|
414
477
|
last = snap;
|
|
415
478
|
return snap;
|