@remcp/runtime 0.2.201 → 0.2.203
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/tools/system-telemetry.mjs +100 -7
package/package.json
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import { readFile, readdir } from 'node:fs/promises';
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import os from 'node:os';
|
|
6
7
|
import process from 'node:process';
|
|
8
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
9
|
import { darwinTemperatureReadings } from './darwin-temperature.mjs';
|
|
8
10
|
import { linuxTemperatureReadings } from './linux-temperature.mjs';
|
|
9
11
|
import { windowsTemperatureReadings } from './windows-temperature.mjs';
|
|
@@ -27,6 +29,52 @@ export function parseNetworkCounters(input) {
|
|
|
27
29
|
return interfaces;
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
// netstat -ib has multiple address rows per interface on macOS. Only the
|
|
33
|
+
// <Link#N> row contains the canonical byte counters; never sum duplicates.
|
|
34
|
+
export function parseDarwinNetworkCounters(input, activeNames = null) {
|
|
35
|
+
const lines = String(input).split(/\r?\n/);
|
|
36
|
+
const header = lines[0]?.trim().split(/\s+/) ?? [];
|
|
37
|
+
const rxIndex = header.indexOf('Ibytes');
|
|
38
|
+
const txIndex = header.indexOf('Obytes');
|
|
39
|
+
if (rxIndex < 0 || txIndex < 0) return [];
|
|
40
|
+
const found = new Map();
|
|
41
|
+
for (const line of lines.slice(1, 160)) {
|
|
42
|
+
const parts = line.trim().split(/\s+/);
|
|
43
|
+
const name = parts[0];
|
|
44
|
+
if (!safeName(name) || !/^<Link#\d+>$/.test(parts[2]) ||
|
|
45
|
+
/^(?:lo\d*|awdl\d*|llw\d*|utun\d*|bridge\d*|gif\d*|stf\d*|ap\d*|anpi\d*)$/.test(name) ||
|
|
46
|
+
(activeNames && !activeNames.has(name))) continue;
|
|
47
|
+
const rx = Number(parts[rxIndex]);
|
|
48
|
+
const tx = Number(parts[txIndex]);
|
|
49
|
+
if (!numeric(rx) || !numeric(tx)) continue;
|
|
50
|
+
found.set(name, { name, rx, tx });
|
|
51
|
+
if (found.size >= 24) break;
|
|
52
|
+
}
|
|
53
|
+
return [...found.values()];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Windows adapter statistics are real cumulative counters, not link-speed estimates.
|
|
57
|
+
export function parseWindowsNetworkCounters(input, activeNames = null) {
|
|
58
|
+
let parsed;
|
|
59
|
+
try { parsed = JSON.parse(String(input)); } catch { return []; }
|
|
60
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
61
|
+
const found = new Map();
|
|
62
|
+
for (const row of rows.slice(0, 128)) {
|
|
63
|
+
const name = row?.Name;
|
|
64
|
+
if (typeof name !== 'string' || !/^[\p{L}\p{N} _().:#-]{1,64}$/u.test(name) ||
|
|
65
|
+
(activeNames && !activeNames.has(name))) continue;
|
|
66
|
+
const parseCounter = value => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0
|
|
67
|
+
? value : typeof value === 'string' && /^\d{1,16}$/.test(value) && numeric(Number(value))
|
|
68
|
+
? Number(value) : null;
|
|
69
|
+
const rx = parseCounter(row?.ReceivedBytes);
|
|
70
|
+
const tx = parseCounter(row?.SentBytes);
|
|
71
|
+
if (rx === null || tx === null) continue;
|
|
72
|
+
found.set(name, { name, rx, tx });
|
|
73
|
+
if (found.size >= 24) break;
|
|
74
|
+
}
|
|
75
|
+
return [...found.values()];
|
|
76
|
+
}
|
|
77
|
+
|
|
30
78
|
export function calculateNetworkRates(current, previous, elapsedSeconds) {
|
|
31
79
|
if (!Array.isArray(current) || !Array.isArray(previous) || !Number.isFinite(elapsedSeconds) ||
|
|
32
80
|
elapsedSeconds < 1 || elapsedSeconds > 300) return null;
|
|
@@ -47,21 +95,66 @@ export function calculateNetworkRates(current, previous, elapsedSeconds) {
|
|
|
47
95
|
}
|
|
48
96
|
|
|
49
97
|
let lastNetwork = null;
|
|
50
|
-
|
|
51
|
-
|
|
98
|
+
let pendingNetwork = null;
|
|
99
|
+
|
|
100
|
+
async function readNetworkCounters() {
|
|
101
|
+
if (process.platform === 'linux') {
|
|
102
|
+
const active = new Set(Object.entries(os.networkInterfaces())
|
|
103
|
+
.filter(([, addresses]) => addresses?.some(address => !address.internal))
|
|
104
|
+
.map(([name]) => name));
|
|
105
|
+
return parseNetworkCounters(await readFile('/proc/net/dev', 'utf8'))
|
|
106
|
+
.filter(row => active.has(row.name) && !/^(?:veth|docker|br-|virbr|tun|tap|wg|lo)/.test(row.name));
|
|
107
|
+
}
|
|
108
|
+
if (process.platform === 'darwin') {
|
|
109
|
+
const { stdout } = await run('/usr/sbin/netstat', ['-ibn'],
|
|
110
|
+
{ timeout: 2500, maxBuffer: 128 * 1024 });
|
|
111
|
+
const active = new Set(Object.entries(os.networkInterfaces())
|
|
112
|
+
.filter(([, addresses]) => addresses?.some(address => !address.internal &&
|
|
113
|
+
!address.address.startsWith('fe80:'))).map(([name]) => name));
|
|
114
|
+
return parseDarwinNetworkCounters(stdout, active);
|
|
115
|
+
}
|
|
116
|
+
if (process.platform === 'win32') {
|
|
117
|
+
const script = 'Get-NetAdapterStatistics | Select-Object Name,ReceivedBytes,SentBytes | ConvertTo-Json -Compress';
|
|
118
|
+
const { stdout } = await run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script],
|
|
119
|
+
{ windowsHide: true, timeout: 5000, maxBuffer: 128 * 1024 });
|
|
120
|
+
const active = new Set(Object.entries(os.networkInterfaces())
|
|
121
|
+
.filter(([, addresses]) => addresses?.some(address => !address.internal))
|
|
122
|
+
.map(([name]) => name));
|
|
123
|
+
return parseWindowsNetworkCounters(stdout, active);
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function sampleNetworkRates() {
|
|
52
129
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
130
|
+
let current = await readNetworkCounters();
|
|
131
|
+
let now = performance.now();
|
|
132
|
+
let previous = lastNetwork;
|
|
133
|
+
const elapsed = previous ? (now - previous.at) / 1000 : 0;
|
|
134
|
+
// The first diagnostics request must return a measured rate, not null.
|
|
135
|
+
// Bootstrap from two real counters; never manufacture a zero-rate sample.
|
|
136
|
+
if (elapsed < 1 || elapsed > 300) {
|
|
137
|
+
previous = { at: now, interfaces: current };
|
|
138
|
+
await delay(1100);
|
|
139
|
+
current = await readNetworkCounters();
|
|
140
|
+
now = performance.now();
|
|
141
|
+
}
|
|
57
142
|
lastNetwork = { at: now, interfaces: current };
|
|
58
|
-
return
|
|
143
|
+
return calculateNetworkRates(current, previous.interfaces, (now - previous.at) / 1000);
|
|
59
144
|
} catch {
|
|
60
145
|
lastNetwork = null;
|
|
61
146
|
return null;
|
|
62
147
|
}
|
|
63
148
|
}
|
|
64
149
|
|
|
150
|
+
export async function networkRates() {
|
|
151
|
+
if (!['linux', 'darwin', 'win32'].includes(process.platform)) return null;
|
|
152
|
+
if (pendingNetwork) return pendingNetwork;
|
|
153
|
+
pendingNetwork = sampleNetworkRates();
|
|
154
|
+
try { return await pendingNetwork; }
|
|
155
|
+
finally { pendingNetwork = null; }
|
|
156
|
+
}
|
|
157
|
+
|
|
65
158
|
export async function thermalReadings() {
|
|
66
159
|
if (process.platform === 'darwin') return darwinTemperatureReadings();
|
|
67
160
|
if (process.platform === 'linux') return linuxTemperatureReadings();
|