@depup/systeminformation 5.33.8-depup.0 → 5.33.12-depup.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 CHANGED
@@ -13,8 +13,8 @@ npm install @depup/systeminformation
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [systeminformation](https://www.npmjs.com/package/systeminformation) @ 5.33.8 |
17
- | Processed | 2026-09-06 |
16
+ | Original | [systeminformation](https://www.npmjs.com/package/systeminformation) @ 5.33.12 |
17
+ | Processed | 2026-09-20 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 0 |
20
20
 
package/changes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "bumped": {},
3
- "timestamp": "2026-09-06T01:00:18.743Z",
3
+ "timestamp": "2026-09-20T01:02:36.649Z",
4
4
  "totalUpdated": 0
5
5
  }
package/lib/battery.js CHANGED
@@ -29,7 +29,7 @@ const _sunos = _platform === 'sunos';
29
29
 
30
30
  function parseWinBatteryPart(lines, designedCapacity, fullChargeCapacity) {
31
31
  const result = {};
32
- let status = parseInt(util.getValue(lines, 'BatteryStatus', ':').trim(), 10) || 0;
32
+ const status = parseInt(util.getValue(lines, 'BatteryStatus', ':').trim(), 10) || 0;
33
33
  // let status = util.getValue(lines, 'BatteryStatus', ':').trim();
34
34
  // 1 = "Discharging"
35
35
  // 2 = "On A/C"
@@ -111,7 +111,7 @@ module.exports = (callback) =>
111
111
  if (battery_path) {
112
112
  fs.readFile(battery_path + 'uevent', (error, stdout) => {
113
113
  if (!error) {
114
- let lines = stdout.toString().split('\n');
114
+ const lines = stdout.toString().split('\n');
115
115
 
116
116
  result.isCharging = util.getValue(lines, 'POWER_SUPPLY_STATUS', '=').toLowerCase() === 'charging';
117
117
  result.acConnected = acConnected || result.isCharging;
@@ -176,7 +176,7 @@ module.exports = (callback) =>
176
176
  }
177
177
  if (_freebsd || _openbsd || _netbsd) {
178
178
  exec('sysctl -i hw.acpi.battery hw.acpi.acline', (error, stdout) => {
179
- let lines = stdout.toString().split('\n');
179
+ const lines = stdout.toString().split('\n');
180
180
  const batteries = parseInt('0' + util.getValue(lines, 'hw.acpi.battery.units'), 10);
181
181
  const percent = parseInt('0' + util.getValue(lines, 'hw.acpi.battery.life'), 10);
182
182
  result.hasBattery = batteries > 0;
@@ -196,24 +196,45 @@ module.exports = (callback) =>
196
196
 
197
197
  if (_darwin) {
198
198
  exec(
199
- 'ioreg -n AppleSmartBattery -r | egrep "CycleCount|IsCharging|DesignCapacity|MaxCapacity|CurrentCapacity|DeviceName|BatterySerialNumber|Serial|TimeRemaining|Voltage"; pmset -g batt | grep %',
199
+ 'ioreg -n AppleSmartBattery -r | egrep "CycleCount|IsCharging|DesignCapacity|MaxCapacity|CurrentCapacity|DeviceName|BatterySerialNumber|Serial|TimeRemaining|Voltage|BatteryData|NominalChargeCapacity"; pmset -g batt | grep %',
200
200
  (error, stdout) => {
201
201
  if (stdout) {
202
- let lines = stdout.toString().replace(/ +/g, '').replace(/"+/g, '').replace(/-/g, '').split('\n');
202
+ const lines = stdout
203
+ .toString()
204
+ .replace(/^[ |]+/gm, '')
205
+ .replace(/ +/g, '')
206
+ .replace(/"+/g, '')
207
+ .replace(/-/g, '')
208
+ .split('\n');
209
+ const voltage = parseInt('0' + util.getValue(lines, 'voltage', '='), 10) / 1000.0;
210
+ const batteryData = util.getValue(lines, 'BatteryData', '=').replace(/^\{/, '').replace(/\}$/, '').split(',');
211
+ const maxCapacity = Math.round(
212
+ parseInt(
213
+ '0' +
214
+ (util.getValue(lines, 'AppleRawMaxCapacity', '=') ||
215
+ util.getValue(lines, 'NominalChargeCapacity', '=') ||
216
+ util.getValue(batteryData, 'FullChargeCapacity', '=') ||
217
+ util.getValue(batteryData, 'NominalChargeCapacity', '=')),
218
+ 10
219
+ ) * (voltage || 1)
220
+ );
221
+ const currentCapacity = Math.round(parseInt('0' + (util.getValue(lines, 'AppleRawCurrentCapacity', '=') || util.getValue(batteryData, 'RemainingCapacity', '=')), 10) * (voltage || 1));
222
+ const designedCapacity = Math.round(parseInt('0' + (util.getValue(lines, 'DesignCapacity', '=') || util.getValue(batteryData, 'DesignCapacity', '=')), 10) * (voltage || 1));
223
+
203
224
  result.cycleCount = parseInt('0' + util.getValue(lines, 'cyclecount', '='), 10);
204
- result.voltage = parseInt('0' + util.getValue(lines, 'voltage', '='), 10) / 1000.0;
225
+ result.voltage = voltage;
205
226
  result.capacityUnit = result.voltage ? 'mWh' : 'mAh';
206
- result.maxCapacity = Math.round(parseInt('0' + util.getValue(lines, 'applerawmaxcapacity', '='), 10) * (result.voltage || 1));
207
- result.currentCapacity = Math.round(parseInt('0' + util.getValue(lines, 'applerawcurrentcapacity', '='), 10) * (result.voltage || 1));
208
- result.designedCapacity = Math.round(parseInt('0' + util.getValue(lines, 'DesignCapacity', '='), 10) * (result.voltage || 1));
227
+ result.maxCapacity = maxCapacity;
228
+ result.currentCapacity = currentCapacity;
229
+ result.designedCapacity = designedCapacity;
209
230
  result.manufacturer = 'Apple';
210
231
  result.serial = util.getValue(lines, 'BatterySerialNumber', '=') || util.getValue(lines, 'Serial', '=');
211
232
  result.model = util.getValue(lines, 'DeviceName', '=');
212
233
  let percent = null;
213
234
  const line = util.getValue(lines, 'internal', 'Battery');
214
- let parts = line.split(';');
235
+ const parts = line.split(';');
215
236
  if (parts && parts[0]) {
216
- let parts2 = parts[0].split('\t');
237
+ const parts2 = parts[0].split('\t');
217
238
  if (parts2 && parts2[1]) {
218
239
  percent = parseFloat(parts2[1].trim().replace(/%/g, ''));
219
240
  }
package/lib/filesystem.js CHANGED
@@ -43,8 +43,20 @@ function fsSize(drive, callback) {
43
43
  }
44
44
 
45
45
  let macOsDisks = [];
46
+ const macOsFsTypes = new Map();
46
47
  let osMounts = [];
47
48
 
49
+ // macOS df has no type column, so the type used to be guessed from diskutil - which only ever
50
+ // produced APFS, HFS or NFS and therefore never recognised zfs, exfat, msdos or smbfs. mount
51
+ // knows the real type, so prefer it and keep the old names for the three it could produce.
52
+ function macOsFsType(fs) {
53
+ const type = macOsFsTypes.get(fs);
54
+ if (!type) {
55
+ return getmacOsFsType(fs);
56
+ }
57
+ return type === 'apfs' ? 'APFS' : type === 'hfs' ? 'HFS' : type === 'nfs' ? 'NFS' : type;
58
+ }
59
+
48
60
  function getmacOsFsType(fs) {
49
61
  if (!fs.startsWith('/')) {
50
62
  return 'NFS';
@@ -86,6 +98,59 @@ function fsSize(drive, callback) {
86
98
  return lines;
87
99
  }
88
100
 
101
+ // ZFS datasets share the pool, so df only reports what a dataset references itself - a parent
102
+ // holding its data in child datasets looks empty (#1017). Only `zfs list` knows the
103
+ // hierarchical usage, so query it once when a zfs mount is present.
104
+ function applyZfsUsage(data, cb) {
105
+ if (!data.some((item) => item.type === 'zfs')) {
106
+ return cb(data);
107
+ }
108
+ exec('zfs list -H -p -o name,used,avail,mountpoint', { ...util.execOptsLinux, timeout: 5000 }, (error, stdout) => {
109
+ if (error) {
110
+ // a truncated or timed out listing would correct only part of the datasets and leave the
111
+ // rest on their df values - correct none instead, so the result stays consistent
112
+ return cb(data);
113
+ }
114
+ const byMount = Object.create(null);
115
+ const byName = Object.create(null);
116
+ (stdout || '')
117
+ .toString()
118
+ .split('\n')
119
+ .forEach((line) => {
120
+ const parts = line.split('\t');
121
+ if (parts.length < 4) {
122
+ return;
123
+ }
124
+ const entry = { used: parseInt(parts[1], 10), available: parseInt(parts[2], 10) };
125
+ if (isNaN(entry.used) || isNaN(entry.available)) {
126
+ return;
127
+ }
128
+ byName[parts[0]] = entry;
129
+ const mount = parts[3].trim();
130
+ // several datasets can carry the same mountpoint (root-on-zfs boot environments all
131
+ // declare "/"), so the mount index is only a fallback - keep the first one
132
+ if (mount.startsWith('/') && !byMount[mount]) {
133
+ byMount[mount] = entry;
134
+ }
135
+ });
136
+ data.forEach((item) => {
137
+ if (item.type !== 'zfs') {
138
+ return;
139
+ }
140
+ // the fs column is the exact dataset name and therefore unambiguous, unlike the mountpoint
141
+ const dataset = byName[item.fs] || byMount[item.mount];
142
+ if (!dataset || !(dataset.used + dataset.available)) {
143
+ return;
144
+ }
145
+ item.used = dataset.used;
146
+ item.available = dataset.available;
147
+ item.size = dataset.used + dataset.available;
148
+ item.use = parseFloat(((100.0 * dataset.used) / item.size).toFixed(2));
149
+ });
150
+ cb(data);
151
+ });
152
+ }
153
+
89
154
  function parseDf(lines) {
90
155
  const data = [];
91
156
  // filesystem (first column) and mount point (last column) may contain spaces:
@@ -98,7 +163,7 @@ function fsSize(drive, callback) {
98
163
  const parts = line.trim().match(hasType ? dfWithType : dfNoType);
99
164
  if (parts && (parts[1].startsWith('/') || parts[hasType ? 6 : 5] === '/' || parts[1].indexOf('/') > 0 || parts[1].indexOf(':') === 1 || (!_darwin && !isLinuxTmpFs(parts[2])))) {
100
165
  const fs = parts[1];
101
- const fsType = hasType ? parts[2] : getmacOsFsType(parts[1]);
166
+ const fsType = hasType ? parts[2] : macOsFsType(parts[1]);
102
167
  const size = parseInt(parts[hasType ? 3 : 2], 10) * 1024;
103
168
  const used = parseInt(parts[hasType ? 4 : 3], 10) * 1024;
104
169
  const available = parseInt(parts[hasType ? 5 : 4], 10) * 1024;
@@ -142,11 +207,16 @@ function fsSize(drive, callback) {
142
207
  execSync('mount')
143
208
  .toString()
144
209
  .split('\n')
145
- .filter((line) => {
146
- return line.startsWith('/');
147
- })
148
210
  .forEach((line) => {
149
- osMounts[line.split(' ')[0]] = line.toLowerCase().indexOf('read-only') === -1;
211
+ // mount output: "<fs> on <mountpoint> (<type>, <options>)"
212
+ const fs = line.split(' ')[0];
213
+ const type = line.match(/\(([^),]+)[^)]*\)$/);
214
+ if (fs && type) {
215
+ macOsFsTypes.set(fs, type[1].trim().toLowerCase());
216
+ }
217
+ if (line.startsWith('/')) {
218
+ osMounts[fs] = line.toLowerCase().indexOf('read-only') === -1;
219
+ }
150
220
  });
151
221
  } catch {
152
222
  util.noop();
@@ -195,19 +265,23 @@ function fsSize(drive, callback) {
195
265
  });
196
266
  }
197
267
  if ((!error || data.length) && stdout.toString().trim() !== '') {
198
- if (callback) {
199
- callback(data);
200
- }
201
- resolve(data);
268
+ applyZfsUsage(data, (data) => {
269
+ if (callback) {
270
+ callback(data);
271
+ }
272
+ resolve(data);
273
+ });
202
274
  } else {
203
275
  exec('df -kPT 2>/dev/null', { maxBuffer: 1024 * 1024 }, (error, stdout) => {
204
276
  // fixed issue alpine fallback
205
277
  const lines = filterLines(stdout);
206
278
  data = parseDf(lines);
207
- if (callback) {
208
- callback(data);
209
- }
210
- resolve(data);
279
+ applyZfsUsage(data, (data) => {
280
+ if (callback) {
281
+ callback(data);
282
+ }
283
+ resolve(data);
284
+ });
211
285
  });
212
286
  }
213
287
  });
@@ -220,8 +294,9 @@ function fsSize(drive, callback) {
220
294
  }
221
295
  if (_windows) {
222
296
  try {
223
- const driveSanitized = drive ? util.sanitizeString(drive, true) : '';
224
- const cmd = `Get-WmiObject Win32_logicaldisk | select Access,Caption,FileSystem,FreeSpace,Size ${driveSanitized ? '| where -property Caption -eq ' + driveSanitized : ''} | fl`;
297
+ // invalid drive input yields '' -> filter matches no drive instead of listing all
298
+ const driveSanitized = util.sanitizeDriveLetter(drive);
299
+ const cmd = `Get-WmiObject Win32_logicaldisk | select Access,Caption,FileSystem,FreeSpace,Size ${drive ? "| where -property Caption -eq '" + driveSanitized + "'" : ''} | fl`;
225
300
  util.powerShell(cmd).then((stdout, error) => {
226
301
  if (!error) {
227
302
  const devices = stdout.toString().split(/\n\s*\n/);
@@ -492,7 +567,9 @@ function raidMatchLinux(data) {
492
567
  try {
493
568
  data.forEach((element) => {
494
569
  if (element.type.startsWith('raid')) {
495
- const lines = execSync(`mdadm --export --detail /dev/${util.sanitizeString(element.name, true)}`, util.execOptsLinux).toString().split('\n');
570
+ const lines = execSync(`mdadm --export --detail /dev/${util.sanitizeString(element.name, true)}`, util.execOptsLinux)
571
+ .toString()
572
+ .split('\n');
496
573
  const mdData = decodeMdabmData(lines);
497
574
 
498
575
  element.label = mdData.label; // <- assign label info
@@ -1359,7 +1436,7 @@ function diskLayout(callback) {
1359
1436
  }
1360
1437
  if (_darwin) {
1361
1438
  let cmdFullSmart = '';
1362
- exec(`system_profiler SPSerialATADataType SPNVMeDataType SPUSBDataType SPStorageDataType`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
1439
+ exec(`system_profiler SPSerialATADataType SPNVMeDataType SPUSBDataType SPUSBHostDataType SPStorageDataType`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
1363
1440
  if (!error) {
1364
1441
  // split by type:
1365
1442
  const lines = stdout.toString().split('\n');
@@ -1498,9 +1575,12 @@ function diskLayout(callback) {
1498
1575
  } catch {
1499
1576
  util.noop();
1500
1577
  }
1501
- // USB Drives (older macOS report storage devices under SPUSBDataType)
1578
+ // USB Drives (SPUSBDataType up to macOS 26, SPUSBHostDataType from macOS 27 on)
1502
1579
  try {
1503
- const devices = linesUSB.join('\n').replace(/Media:\n /g, 'Model:').split('\n\n Product ID:');
1580
+ const devices = linesUSB
1581
+ .join('\n')
1582
+ .replace(/Media:\n /g, 'Model:')
1583
+ .split('\n\n Product ID:');
1504
1584
  devices.shift();
1505
1585
  devices.forEach((device) => {
1506
1586
  const lines = device.split('\n');
@@ -1751,8 +1831,8 @@ function diskLayout(callback) {
1751
1831
  // in first case it will be "<serial number>&0"
1752
1832
  // in second case it will be opaque generated value that looks like this: "5&<8-symbol hex code>&0&000000"
1753
1833
  // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/instance-ids
1754
- if (parts.length == 2 && parts[1] === '0') {
1755
- serialNum = parts[0]
1834
+ if (parts.length === 2 && parts[1] === '0') {
1835
+ serialNum = parts[0];
1756
1836
  }
1757
1837
  }
1758
1838
  }
package/lib/network.js CHANGED
@@ -203,6 +203,8 @@ function getMacAddresses() {
203
203
  return result;
204
204
  }
205
205
 
206
+ exports.getMacAddresses = getMacAddresses;
207
+
206
208
  function networkInterfaceDefault(callback) {
207
209
  return new Promise((resolve) => {
208
210
  process.nextTick(() => {
package/lib/osinfo.js CHANGED
@@ -1164,16 +1164,30 @@ function shell(callback) {
1164
1164
 
1165
1165
  exports.shell = shell;
1166
1166
 
1167
+ // macOS 26+ and some hardened linux kernels mask MACs in getifaddrs()
1168
+ const MASKED_MACS = ['00:00:00:00:00:00', '02:00:00:00:00:00'];
1169
+
1167
1170
  function getUniqueMacAdresses() {
1168
1171
  let macs = [];
1169
1172
  try {
1170
1173
  const ifaces = os.networkInterfaces();
1174
+ let fallbackMacs = null;
1171
1175
  for (let dev in ifaces) {
1172
1176
  if ({}.hasOwnProperty.call(ifaces, dev)) {
1173
1177
  ifaces[dev].forEach((details) => {
1174
- if (details && details.mac && details.mac !== '00:00:00:00:00:00') {
1175
- const mac = details.mac.toLowerCase();
1176
- if (macs.indexOf(mac) === -1) {
1178
+ if (details && details.mac) {
1179
+ let mac = details.mac.toLowerCase();
1180
+ if (MASKED_MACS.indexOf(mac) >= 0) {
1181
+ if (fallbackMacs === null) {
1182
+ try {
1183
+ fallbackMacs = require('./network').getMacAddresses();
1184
+ } catch {
1185
+ fallbackMacs = {};
1186
+ }
1187
+ }
1188
+ mac = (fallbackMacs[dev] || '').toLowerCase();
1189
+ }
1190
+ if (mac && MASKED_MACS.indexOf(mac) === -1 && macs.indexOf(mac) === -1) {
1177
1191
  macs.push(mac);
1178
1192
  }
1179
1193
  }
package/lib/processes.js CHANGED
@@ -261,12 +261,20 @@ function services(srv, callback) {
261
261
  });
262
262
  if (_linux) {
263
263
  // calc process_cpu - ps is not accurate in linux!
264
+ // ps pcpu is a lifetime average, the /proc values below are an interval share -
265
+ // drop the ps seed instead of adding both (#1007)
266
+ result.forEach((item) => {
267
+ item.cpu = 0;
268
+ });
264
269
  let cmd = 'cat /proc/stat | grep "cpu "';
265
270
  for (let i in result) {
266
271
  for (let j in result[i].pids) {
267
272
  cmd += ';cat /proc/' + result[i].pids[j] + '/stat';
268
273
  }
269
274
  }
275
+ // freeze the baseline before the async call - a concurrent call overwrites _services_cpu
276
+ // and would leave this one dividing by a few jiffies (#1007)
277
+ const cpuBaseline = Object.assign({}, _services_cpu);
270
278
  exec(cmd, { maxBuffer: 1024 * 102400 }, function (error, stdout) {
271
279
  let curr_processes = stdout.toString().split('\n');
272
280
 
@@ -277,7 +285,7 @@ function services(srv, callback) {
277
285
  let list_new = {};
278
286
  let resultProcess = {};
279
287
  curr_processes.forEach((element) => {
280
- resultProcess = calcProcStatLinux(element, all, _services_cpu);
288
+ resultProcess = calcProcStatLinux(element, all, cpuBaseline);
281
289
 
282
290
  if (resultProcess.pid) {
283
291
  let listPos = -1;
@@ -297,9 +305,7 @@ function services(srv, callback) {
297
305
  cpuu: resultProcess.cpuu,
298
306
  cpus: resultProcess.cpus,
299
307
  utime: resultProcess.utime,
300
- stime: resultProcess.stime,
301
- cutime: resultProcess.cutime,
302
- cstime: resultProcess.cstime
308
+ stime: resultProcess.stime
303
309
  };
304
310
  }
305
311
  });
@@ -474,6 +480,19 @@ function parseProcStat(line) {
474
480
  return user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice;
475
481
  }
476
482
 
483
+ // drops NaN/Infinity/negative values and scales cpuu + cpus down proportionally
484
+ // if their sum exceeds 100 (normalized against all cores)
485
+ function clampCpuPair(cpuu, cpus) {
486
+ if (!isFinite(cpuu) || cpuu < 0) { cpuu = 0; }
487
+ if (!isFinite(cpus) || cpus < 0) { cpus = 0; }
488
+ const total = cpuu + cpus;
489
+ if (total > 100) {
490
+ cpuu = (cpuu / total) * 100;
491
+ cpus = (cpus / total) * 100;
492
+ }
493
+ return { cpuu: cpuu, cpus: cpus };
494
+ }
495
+
477
496
  function calcProcStatLinux(line, all, _cpu_old) {
478
497
  let statparts = line.replace(/ +/g, ' ').split(')');
479
498
  if (statparts.length >= 2) {
@@ -482,35 +501,34 @@ function calcProcStatLinux(line, all, _cpu_old) {
482
501
  let pid = parseInt(statparts[0].split(' ')[0]);
483
502
  let utime = parseInt(parts[12]);
484
503
  let stime = parseInt(parts[13]);
485
- let cutime = parseInt(parts[14]);
486
- let cstime = parseInt(parts[15]);
487
504
 
488
- // calc
505
+ // calc - child times (cutime/cstime) are deliberately left out: reaping a child adds its
506
+ // whole lifetime in one interval, which is what produced the >100% spikes in #1007.
507
+ // top, htop and Task Manager exclude them too.
489
508
  let cpuu = 0;
490
509
  let cpus = 0;
491
510
  if (_cpu_old.all > 0 && _cpu_old.list[pid]) {
492
- cpuu = ((utime + cutime - _cpu_old.list[pid].utime - _cpu_old.list[pid].cutime) / (all - _cpu_old.all)) * 100; // user
493
- cpus = ((stime + cstime - _cpu_old.list[pid].stime - _cpu_old.list[pid].cstime) / (all - _cpu_old.all)) * 100; // system
511
+ const delta = all - _cpu_old.all;
512
+ cpuu = delta > 0 ? ((utime - _cpu_old.list[pid].utime) / delta) * 100 : 0; // user
513
+ cpus = delta > 0 ? ((stime - _cpu_old.list[pid].stime) / delta) * 100 : 0; // system
494
514
  } else {
495
- cpuu = ((utime + cutime) / all) * 100; // user
496
- cpus = ((stime + cstime) / all) * 100; // system
515
+ cpuu = all > 0 ? (utime / all) * 100 : 0; // user
516
+ cpus = all > 0 ? (stime / all) * 100 : 0; // system
497
517
  }
518
+ // normalized against all cores, so 100 is the ceiling for cpuu + cpus
519
+ const clamped = clampCpuPair(cpuu, cpus);
498
520
  return {
499
521
  pid: pid,
500
522
  utime: utime,
501
523
  stime: stime,
502
- cutime: cutime,
503
- cstime: cstime,
504
- cpuu: cpuu,
505
- cpus: cpus
524
+ cpuu: clamped.cpuu,
525
+ cpus: clamped.cpus
506
526
  };
507
527
  } else {
508
528
  return {
509
529
  pid: 0,
510
530
  utime: 0,
511
531
  stime: 0,
512
- cutime: 0,
513
- cstime: 0,
514
532
  cpuu: 0,
515
533
  cpus: 0
516
534
  };
@@ -520,8 +538,6 @@ function calcProcStatLinux(line, all, _cpu_old) {
520
538
  pid: 0,
521
539
  utime: 0,
522
540
  stime: 0,
523
- cutime: 0,
524
- cstime: 0,
525
541
  cpuu: 0,
526
542
  cpus: 0
527
543
  };
@@ -533,18 +549,21 @@ function calcProcStatWin(procStat, all, _cpu_old) {
533
549
  let cpuu = 0;
534
550
  let cpus = 0;
535
551
  if (_cpu_old.all > 0 && _cpu_old.list[procStat.pid]) {
536
- cpuu = ((procStat.utime - _cpu_old.list[procStat.pid].utime) / (all - _cpu_old.all)) * 100; // user
537
- cpus = ((procStat.stime - _cpu_old.list[procStat.pid].stime) / (all - _cpu_old.all)) * 100; // system
552
+ const delta = all - _cpu_old.all;
553
+ cpuu = delta > 0 ? ((procStat.utime - _cpu_old.list[procStat.pid].utime) / delta) * 100 : 0; // user
554
+ cpus = delta > 0 ? ((procStat.stime - _cpu_old.list[procStat.pid].stime) / delta) * 100 : 0; // system
538
555
  } else {
539
- cpuu = (procStat.utime / all) * 100; // user
540
- cpus = (procStat.stime / all) * 100; // system
556
+ cpuu = all > 0 ? (procStat.utime / all) * 100 : 0; // user
557
+ cpus = all > 0 ? (procStat.stime / all) * 100 : 0; // system
541
558
  }
559
+ // same ceiling as the linux path - cpuu + cpus stays inside [0, 100] (#1007)
560
+ const clamped = clampCpuPair(cpuu, cpus);
542
561
  return {
543
562
  pid: procStat.pid,
544
563
  utime: procStat.utime,
545
564
  stime: procStat.stime,
546
- cpuu: cpuu > 0 ? cpuu : 0,
547
- cpus: cpus > 0 ? cpus : 0
565
+ cpuu: clamped.cpuu,
566
+ cpus: clamped.cpus
548
567
  };
549
568
  }
550
569
 
@@ -637,6 +656,11 @@ function processes(callback) {
637
656
  let command = '';
638
657
  let params = '';
639
658
  let fullcommand = line.substring(parsedhead[12].from + offset, parsedhead[12].to + offset2).trim();
659
+ // zombies are printed as "[name] <defunct>" - drop the marker so the bracket handling below
660
+ // sees a plain "[name]" and does not leak "] <defunct>" into command and name
661
+ if (fullcommand.endsWith(' <defunct>')) {
662
+ fullcommand = fullcommand.slice(0, -10).trim();
663
+ }
640
664
  if (fullcommand.substr(fullcommand.length - 1) === ']') {
641
665
  fullcommand = fullcommand.slice(0, -1);
642
666
  }
@@ -855,6 +879,9 @@ function processes(callback) {
855
879
  result.list.forEach((element) => {
856
880
  cmd += ';cat /proc/' + element.pid + '/stat';
857
881
  });
882
+ // freeze the baseline before the async call - a concurrent call overwrites _processes_cpu
883
+ // and would leave this one dividing by a few jiffies (#1007)
884
+ const cpuBaseline = Object.assign({}, _processes_cpu);
858
885
  exec(cmd, { maxBuffer: 1024 * 102400 }, (error, stdout) => {
859
886
  let curr_processes = stdout.toString().split('\n');
860
887
 
@@ -865,7 +892,7 @@ function processes(callback) {
865
892
  let list_new = {};
866
893
  let resultProcess = {};
867
894
  curr_processes.forEach((element) => {
868
- resultProcess = calcProcStatLinux(element, all, _processes_cpu);
895
+ resultProcess = calcProcStatLinux(element, all, cpuBaseline);
869
896
 
870
897
  if (resultProcess.pid) {
871
898
  // store pcpu in outer array
@@ -885,9 +912,7 @@ function processes(callback) {
885
912
  cpuu: resultProcess.cpuu,
886
913
  cpus: resultProcess.cpus,
887
914
  utime: resultProcess.utime,
888
- stime: resultProcess.stime,
889
- cutime: resultProcess.cutime,
890
- cstime: resultProcess.cstime
915
+ stime: resultProcess.stime
891
916
  };
892
917
  }
893
918
  });
@@ -950,6 +975,9 @@ function processes(callback) {
950
975
  }
951
976
  } else if (_windows) {
952
977
  try {
978
+ // freeze the baseline before the async call - a concurrent call overwrites _processes_cpu
979
+ // and would leave this one with a non positive delta (#1007)
980
+ const cpuBaseline = Object.assign({}, _processes_cpu);
953
981
  util
954
982
  .powerShell(
955
983
  `Get-CimInstance Win32_Process | select-Object ProcessId,ParentProcessId,ExecutionState,Caption,CommandLine,ExecutablePath,UserModeTime,KernelModeTime,WorkingSetSize,Priority,PageFileUsage,
@@ -960,8 +988,10 @@ function processes(callback) {
960
988
  const procs = [];
961
989
  const procStats = [];
962
990
  const list_new = {};
963
- let allcpuu = 0;
964
- let allcpus = 0;
991
+ // accumulate from the previous totals and add deltas only - a process that exited
992
+ // must not lower the total, otherwise the denominator turns negative (#559)
993
+ let allcpuu = cpuBaseline.all_utime;
994
+ let allcpus = cpuBaseline.all_stime;
965
995
  let processArray = [];
966
996
  try {
967
997
  stdout = stdout.trim().replace(/^\uFEFF/, '');
@@ -981,8 +1011,9 @@ function processes(callback) {
981
1011
  const utime = element.UserModeTime;
982
1012
  const stime = element.KernelModeTime;
983
1013
  const memw = element.WorkingSetSize;
984
- allcpuu = allcpuu + utime;
985
- allcpus = allcpus + stime;
1014
+ const cpuOld = cpuBaseline.list[pid];
1015
+ allcpuu += utime - (cpuOld ? cpuOld.utime : 0);
1016
+ allcpus += stime - (cpuOld ? cpuOld.stime : 0);
986
1017
  result.all++;
987
1018
  if (!statusValue) {
988
1019
  result.unknown++;
@@ -1027,7 +1058,7 @@ function processes(callback) {
1027
1058
  result.sleeping = result.all - result.running - result.blocked - result.unknown;
1028
1059
  result.list = procs;
1029
1060
  procStats.forEach((element) => {
1030
- let resultProcess = calcProcStatWin(element, allcpuu + allcpus, _processes_cpu);
1061
+ let resultProcess = calcProcStatWin(element, allcpuu + allcpus, cpuBaseline);
1031
1062
 
1032
1063
  // store pcpu in outer array
1033
1064
  let listPos = result.list.map((e) => e.pid).indexOf(resultProcess.pid);
@@ -1150,12 +1181,16 @@ function processLoad(proc, callback) {
1150
1181
  if (procSanitized && processes.length && processes[0] !== '------') {
1151
1182
  if (_windows) {
1152
1183
  try {
1184
+ // freeze the baseline before the async call - a concurrent call overwrites _process_cpu
1185
+ // and would leave this one with a non positive delta (#1007)
1186
+ const cpuBaseline = Object.assign({}, _process_cpu);
1153
1187
  util.powerShell('Get-CimInstance Win32_Process | select ProcessId,Caption,UserModeTime,KernelModeTime,WorkingSetSize | ConvertTo-Json -compress').then((stdout, error) => {
1154
1188
  if (!error) {
1155
1189
  const procStats = [];
1156
1190
  const list_new = {};
1157
- let allcpuu = 0;
1158
- let allcpus = 0;
1191
+ // see processes() - never lower the total when a process exits (#559)
1192
+ let allcpuu = cpuBaseline.all_utime;
1193
+ let allcpus = cpuBaseline.all_stime;
1159
1194
  let processArray = [];
1160
1195
  try {
1161
1196
  stdout = stdout.trim().replace(/^\uFEFF/, '');
@@ -1172,8 +1207,9 @@ function processLoad(proc, callback) {
1172
1207
  const utime = element.UserModeTime;
1173
1208
  const stime = element.KernelModeTime;
1174
1209
  const mem = element.WorkingSetSize;
1175
- allcpuu = allcpuu + utime;
1176
- allcpus = allcpus + stime;
1210
+ const cpuOld = cpuBaseline.list[pid];
1211
+ allcpuu += utime - (cpuOld ? cpuOld.utime : 0);
1212
+ allcpus += stime - (cpuOld ? cpuOld.stime : 0);
1177
1213
 
1178
1214
  procStats.push({
1179
1215
  pid: pid,
@@ -1231,7 +1267,7 @@ function processLoad(proc, callback) {
1231
1267
 
1232
1268
  // calculate proc stats for each proc
1233
1269
  procStats.forEach((element) => {
1234
- let resultProcess = calcProcStatWin(element, allcpuu + allcpus, _process_cpu);
1270
+ let resultProcess = calcProcStatWin(element, allcpuu + allcpus, cpuBaseline);
1235
1271
 
1236
1272
  let listPos = -1;
1237
1273
  for (let j = 0; j < result.length; j++) {
@@ -1377,6 +1413,9 @@ function processLoad(proc, callback) {
1377
1413
  cmd += ';cat /proc/' + result[i].pids[j] + '/stat';
1378
1414
  }
1379
1415
  }
1416
+ // freeze the baseline before the async call - a concurrent call overwrites _process_cpu
1417
+ // and would leave this one dividing by a few jiffies (#1007)
1418
+ const cpuBaseline = Object.assign({}, _process_cpu);
1380
1419
  exec(cmd, { maxBuffer: 1024 * 102400 }, (error, stdout) => {
1381
1420
  let curr_processes = stdout.toString().split('\n');
1382
1421
 
@@ -1387,7 +1426,7 @@ function processLoad(proc, callback) {
1387
1426
  let list_new = {};
1388
1427
  let resultProcess = {};
1389
1428
  curr_processes.forEach((element) => {
1390
- resultProcess = calcProcStatLinux(element, all, _process_cpu);
1429
+ resultProcess = calcProcStatLinux(element, all, cpuBaseline);
1391
1430
 
1392
1431
  if (resultProcess.pid) {
1393
1432
  // find result item
@@ -1407,9 +1446,7 @@ function processLoad(proc, callback) {
1407
1446
  cpuu: resultProcess.cpuu,
1408
1447
  cpus: resultProcess.cpus,
1409
1448
  utime: resultProcess.utime,
1410
- stime: resultProcess.stime,
1411
- cutime: resultProcess.cutime,
1412
- cstime: resultProcess.cstime
1449
+ stime: resultProcess.stime
1413
1450
  };
1414
1451
  }
1415
1452
  });
package/lib/util.js CHANGED
@@ -772,6 +772,12 @@ function sanitizeContainerID(str) {
772
772
  return s.indexOf('..') === -1 ? s : '';
773
773
  }
774
774
 
775
+ // windows drive letter whitelist: only "<letter>:" survives, everything else is dropped
776
+ function sanitizeDriveLetter(str) {
777
+ const match = /^\s*([a-zA-Z]):?[\\/]?\s*$/.exec(String(str || ''));
778
+ return match ? match[1] + ':' : '';
779
+ }
780
+
775
781
  function sanitizeImageID(str) {
776
782
  const s = String(str || '')
777
783
  .substring(0, 2000)
@@ -2805,6 +2811,7 @@ exports.sanitizeContainerID = sanitizeContainerID;
2805
2811
  exports.sanitizeImageID = sanitizeImageID;
2806
2812
  exports.isPrototypePolluted = isPrototypePolluted;
2807
2813
  exports.sanitizeString = sanitizeString;
2814
+ exports.sanitizeDriveLetter = sanitizeDriveLetter;
2808
2815
  exports.decodePiCpuinfo = decodePiCpuinfo;
2809
2816
  exports.getRpiGpu = getRpiGpu;
2810
2817
  exports.promiseAll = promiseAll;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/systeminformation",
3
- "version": "5.33.8-depup.0",
3
+ "version": "5.33.12-depup.0",
4
4
  "description": "Advanced, lightweight system and OS information library (with updated dependencies)",
5
5
  "license": "MIT",
6
6
  "author": "Sebastian Hildebrandt <hildebrandt@plus-innovations.com> (https://plus-innovations.com)",
@@ -111,8 +111,8 @@
111
111
  "changes": {},
112
112
  "depsUpdated": 0,
113
113
  "originalPackage": "systeminformation",
114
- "originalVersion": "5.33.8",
115
- "processedAt": "2026-09-06T01:00:19.560Z",
114
+ "originalVersion": "5.33.12",
115
+ "processedAt": "2026-09-20T01:02:37.142Z",
116
116
  "smokeTest": "passed"
117
117
  }
118
118
  }