Skip to content

Commit 5114a43

Browse files
committed
feat(#142 T2.1): per-agent process telemetry — RSS / CPU% / uptime / in-flight count
v0.10.0 Track 2 / T2.1 — agent-node side per-agent process telemetry. Extends #119's host-telemetry.ts with metrics scoped to THIS agent-node process (vs the host machine). Aggregated by commhub-server T2.2 (派 通信牛) and rendered by dashboard sidebar/hover T2.3 (派 N站马). New module: agent-node/src/process-telemetry.ts (~75 LOC) Public API: getProcessTelemetry(): ProcessTelemetry { rss_bytes: number, // process.memoryUsage().rss rss_mb: number, // rss_bytes / 1MB, rounded to 1 decimal cpu_pct: number | null, // delta cpuUsage / wall delta, % (null on first sample) uptime_seconds: number, // Math.round(process.uptime()) in_flight_count: number, // current think() invocations in flight } incrementInFlight() / decrementInFlight() / getInFlightCount() _resetProcessTelemetry() // test-only, resets sampler state Wiring in cli.ts: - register() + reportStatus() payloads now include process_telemetry field alongside the existing host field (additive, ignored by old commhub servers per the same conservative-additive principle as #119) - think() wraps the run() body with incrementInFlight() / try / decrementInFlight() in finally so the counter accurately reflects active task processing even on error paths CPU% calculation: cpu.user_us + cpu.system_us delta over wall-clock delta, expressed as percent. Can exceed 100 on multi-core systems (e.g. async I/O parallelism). First sample returns null because there's no prior reference point. Unit smoke (local /tmp/p142-telemetry-smoke.mjs) PASS: sample 1 (no prev): rss_mb=34, cpu_pct=null ✓ busy-loop 100ms + 3 incr + 1 decr → sample 2: rss_mb=42.6, cpu_pct=109.3, in_flight_count=2 ✓ Bundle clean: bun 84 modules → 0.32 MB cli.js. Refs #142 (T2.1 of 4) Refs #140 (v0.10.0 tracker) Author-Agent: 通信SDK马
1 parent 3b29e84 commit 5114a43

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

agent-node/src/cli.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { join } from "path";
1414
import { hostname as osHostname, homedir } from "os";
1515
import { createCommhubSdkMcpServer } from "./commhub-mcp";
1616
import { getHostTelemetry } from "./host-telemetry";
17+
import { getProcessTelemetry, incrementInFlight, decrementInFlight } from "./process-telemetry";
1718

1819
const home = homedir();
1920

@@ -451,6 +452,7 @@ const register = () => callCommHub("report_status", {
451452
model: MODEL || undefined,
452453
network_id: NETWORK_ID || undefined,
453454
host: getHostTelemetry(),
455+
process_telemetry: getProcessTelemetry(),
454456
});
455457
const reportStatus = (status: string, task?: string) => callCommHub("report_status", {
456458
resume_id: RESUME_ID, alias: ALIAS, status, task,
@@ -460,6 +462,7 @@ const reportStatus = (status: string, task?: string) => callCommHub("report_stat
460462
channels: channelSpecs.length ? JSON.stringify(channelSpecs) : undefined,
461463
network_id: NETWORK_ID || undefined,
462464
host: getHostTelemetry(),
465+
process_telemetry: getProcessTelemetry(),
463466
});
464467
const getInbox = async () => (await callCommHub("get_inbox", { alias: ALIAS, limit: 20 }))?.messages || [];
465468
const ackMessage = (id: string) => callCommHub("ack_inbox", { alias: ALIAS, message_id: id });
@@ -1031,6 +1034,10 @@ function think(task: string, from: string, taskId: string | null, images?: strin
10311034
// is more reliable for multi-task interleavings.
10321035
const prev = process.env.CURRENT_TASK_ID;
10331036
if (taskId) process.env.CURRENT_TASK_ID = taskId; else delete process.env.CURRENT_TASK_ID;
1037+
// #142 — track in-flight task count for per-agent telemetry. thinkQueue
1038+
// serializes so the counter is mostly 0 or 1, but the increment/decrement
1039+
// pattern is still correct under future concurrency changes.
1040+
incrementInFlight();
10341041
try {
10351042
if (RUNTIME === "codex") {
10361043
// #141 Phase 1.3 — opt-in to direct app-server stdio.
@@ -1046,6 +1053,7 @@ function think(task: string, from: string, taskId: string | null, images?: strin
10461053
return await processWithClaude(task, from);
10471054
} finally {
10481055
if (prev !== undefined) process.env.CURRENT_TASK_ID = prev; else delete process.env.CURRENT_TASK_ID;
1056+
decrementInFlight();
10491057
}
10501058
};
10511059
const next = thinkQueue.then(run, run);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Per-agent process telemetry for #142 (Track 2 of v0.10.0).
2+
//
3+
// Extends #119's host-telemetry.ts with metrics scoped to this agent-node
4+
// process — RSS, CPU%, uptime, in-flight task count. Aggregated by commhub-
5+
// server (T2.2, 通信牛) and rendered by dashboard sidebar/hover (T2.3,
6+
// N站马) alongside the existing per-host fields.
7+
//
8+
// All fields are nullable independently. CPU% is null on the first sample
9+
// because it needs a delta against a previous sample. After that it tracks
10+
// the rate of cpuUsage growth over wall-clock time.
11+
//
12+
// The in-flight counter is incremented when think() begins processing a
13+
// task and decremented when it returns (success or error). Race-free
14+
// because think() serializes through thinkQueue.
15+
16+
export interface ProcessTelemetry {
17+
rss_bytes: number;
18+
rss_mb: number;
19+
cpu_pct: number | null;
20+
uptime_seconds: number;
21+
in_flight_count: number;
22+
}
23+
24+
interface CpuSample {
25+
ts_ms: number;
26+
user_us: number;
27+
system_us: number;
28+
}
29+
30+
let _lastCpu: CpuSample | null = null;
31+
let _inFlight = 0;
32+
33+
export function incrementInFlight(): void {
34+
_inFlight++;
35+
}
36+
37+
export function decrementInFlight(): void {
38+
_inFlight = Math.max(0, _inFlight - 1);
39+
}
40+
41+
export function getInFlightCount(): number {
42+
return _inFlight;
43+
}
44+
45+
export function getProcessTelemetry(): ProcessTelemetry {
46+
const now = Date.now();
47+
const cpu = process.cpuUsage();
48+
49+
let cpu_pct: number | null = null;
50+
if (_lastCpu) {
51+
const wallDeltaMs = now - _lastCpu.ts_ms;
52+
const userDeltaUs = cpu.user - _lastCpu.user_us;
53+
const systemDeltaUs = cpu.system - _lastCpu.system_us;
54+
const cpuDeltaMs = (userDeltaUs + systemDeltaUs) / 1000;
55+
if (wallDeltaMs > 0) {
56+
// cpu% can exceed 100 on multi-core systems (e.g. parallel I/O).
57+
// Round to 1 decimal for readability.
58+
cpu_pct = Math.round((cpuDeltaMs / wallDeltaMs) * 100 * 10) / 10;
59+
}
60+
}
61+
_lastCpu = { ts_ms: now, user_us: cpu.user, system_us: cpu.system };
62+
63+
const mem = process.memoryUsage();
64+
const rss_bytes = mem.rss;
65+
const rss_mb = Math.round((rss_bytes / (1024 * 1024)) * 10) / 10;
66+
67+
return {
68+
rss_bytes,
69+
rss_mb,
70+
cpu_pct,
71+
uptime_seconds: Math.round(process.uptime()),
72+
in_flight_count: _inFlight,
73+
};
74+
}
75+
76+
// Test-only: reset sampler state (cpu delta history + in-flight counter).
77+
// Not part of public API but available for unit tests.
78+
export function _resetProcessTelemetry(): void {
79+
_lastCpu = null;
80+
_inFlight = 0;
81+
}

0 commit comments

Comments
 (0)