@yourfam/yf-vitals 0.2.0 → 0.3.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/README.md CHANGED
@@ -36,9 +36,10 @@ 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
+ | **GPU** | Utilization and VRAM when telemetry exists; **the row is omitted** when it does not |
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
- | **GPU** | Utilization and VRAM when telemetry exists; **the row is omitted** when it does not |
42
+ | **C: / D: / Data** | Capacity last: one bar per local partition. Skips Google Drive, iCloud, network, FAT/USB |
42
43
 
43
44
  Header: `yf-vitals` · hostname · OS · `NP/NL` cores · RAM total · tick interval.
44
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yourfam/yf-vitals",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "Live terminal dashboard for CPU, RAM, disk, net, and optional GPU",
5
5
  "type": "module",
6
6
  "bin": {
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
+ Each local partition (C:, D:, Data, …) gets its own fill bar. 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 = null;
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
- }, 2500);
106
+ }, timeoutMs);
103
107
  const done = (v) => {
104
108
  clearTimeout(t);
105
109
  resolve(v);
package/src/diskuse.js ADDED
@@ -0,0 +1,98 @@
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 {{ fs?: string, type?: string, mount?: string, size?: number, used?: number, use?: number }} r
60
+ */
61
+ function toVolume(r) {
62
+ const total = Number(r.size) || 0;
63
+ const used = Number(r.used) || 0;
64
+ const percent =
65
+ typeof r.use === "number" && Number.isFinite(r.use)
66
+ ? r.use
67
+ : total > 0
68
+ ? (used / total) * 100
69
+ : 0;
70
+ return {
71
+ percent: Math.min(100, Math.max(0, percent)),
72
+ used,
73
+ total,
74
+ mount: shortMount(r.mount || r.fs || ""),
75
+ };
76
+ }
77
+
78
+ /**
79
+ * One first-class volume per local partition. System disk first, then the rest.
80
+ * @param {Array<{ fs?: string, type?: string, mount?: string, size?: number, used?: number, use?: number }>} rows
81
+ * @returns {{ percent: number, used: number, total: number, mount: string }[] | null}
82
+ */
83
+ export function summarizeLocalDisks(rows) {
84
+ const local = (Array.isArray(rows) ? rows : []).filter(isLocalCapacityVolume);
85
+ /** @type {typeof local} */
86
+ const uniq = [];
87
+ for (const r of local.slice().sort((a, b) => systemScore(b) - systemScore(a) || (b.size || 0) - (a.size || 0))) {
88
+ const dupIdx = uniq.findIndex(
89
+ (u) =>
90
+ Math.abs((u.size || 0) - (r.size || 0)) < 1024 * 1024 &&
91
+ Math.abs((u.used || 0) - (r.used || 0)) < 0.01 * (r.size || 1),
92
+ );
93
+ if (dupIdx >= 0) continue;
94
+ uniq.push(r);
95
+ }
96
+ if (!uniq.length) return null;
97
+ return uniq.slice(0, 6).map(toVolume);
98
+ }
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 {Record<string, 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,19 @@ 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: appendDiskUse(history.diskUse, snap.diskUse),
46
48
  };
47
49
  }
50
+
51
+ /**
52
+ * @param {Record<string, number[]>} prev
53
+ * @param {{ mount: string, percent: number }[] | null | undefined} vols
54
+ */
55
+ function appendDiskUse(prev, vols) {
56
+ /** @type {Record<string, number[]>} */
57
+ const next = { ...prev };
58
+ for (const vol of vols || []) {
59
+ next[vol.mount] = pushSample(next[vol.mount] || [], vol.percent);
60
+ }
61
+ return next;
62
+ }
package/src/render.js CHANGED
@@ -208,6 +208,19 @@ export function renderFrame(snap, history, opts = {}) {
208
208
  );
209
209
  }
210
210
  lines.push("");
211
+
212
+ if (Array.isArray(snap.diskUse) && snap.diskUse.length) {
213
+ for (const vol of snap.diskUse) {
214
+ const label = vol.mount.length >= 3 ? vol.mount.slice(0, 4) : vol.mount.padEnd(3);
215
+ const spark = history.diskUse?.[vol.mount] || [];
216
+ lines.push(
217
+ `${color.green(label)} ${paintPercentBar(vol.percent, color, "green", ascii)} ${formatPercent(vol.percent)} ${formatBytePair(vol.used, vol.total)}`,
218
+ );
219
+ lines.push(` ${color.green(sparkline(spark, sparkW, { ascii, max: 100 }))}`);
220
+ lines.push("");
221
+ }
222
+ }
223
+
211
224
  lines.push("q quit");
212
225
 
213
226
  return lines.map((ln) => fitLine(ln, columns)).join("\n");
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 }[] | 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 {Promise<{ hostname: string, osName: string, physical: number, logical: number, ramTotal: number }> | null} */
252
- let staticP = null;
253
- const winDisk = process.platform === "win32" ? createWindowsDiskReader() : null;
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
- async function loadStatic() {
262
- const lib = await loadSi();
263
- const hostname = os.hostname();
264
- let osName = shortOsName(null);
265
- let logical = Math.max(os.cpus().length, 1);
266
- let physical = logical;
267
- let ramTotal = os.totalmem();
268
- try {
269
- const info = await lib.osInfo();
270
- osName = shortOsName(info);
271
- } catch {
272
- // keep fallback
273
- }
274
- try {
275
- const cpu = await lib.cpu();
276
- if (cpu?.physicalCores) physical = cpu.physicalCores;
277
- if (cpu?.cores) logical = cpu.cores;
278
- } catch {
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(lib) {
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 null;
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
- const total = ramTotal || os.totalmem();
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
- // Windows fsStats is always null
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
- async function sampleGpu(lib) {
379
- try {
380
- const g = await lib.graphics();
381
- return pickGpu(g?.controllers || []);
382
- } catch {
383
- return null;
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
- if (!staticP) staticP = loadStatic();
394
- const meta = await staticP;
395
- const win = winDisk ? await winDisk.read().catch(() => null) : null;
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, gpu] = await Promise.all([
398
- sampleCpu(lib).catch(() => last?.cpu ?? null),
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;