@orkify/cli 1.0.0-beta.5
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/LICENSE +191 -0
- package/README.md +1701 -0
- package/bin/orkify +3 -0
- package/boot/systemd/orkify@.service +30 -0
- package/dist/agent-name.d.ts +4 -0
- package/dist/agent-name.js +42 -0
- package/dist/alerts/AlertEvaluator.d.ts +14 -0
- package/dist/alerts/AlertEvaluator.js +135 -0
- package/dist/cli/commands/autostart.d.ts +3 -0
- package/dist/cli/commands/autostart.js +11 -0
- package/dist/cli/commands/crash-test.d.ts +3 -0
- package/dist/cli/commands/crash-test.js +17 -0
- package/dist/cli/commands/daemon-reload.d.ts +3 -0
- package/dist/cli/commands/daemon-reload.js +72 -0
- package/dist/cli/commands/delete.d.ts +3 -0
- package/dist/cli/commands/delete.js +37 -0
- package/dist/cli/commands/deploy.d.ts +6 -0
- package/dist/cli/commands/deploy.js +266 -0
- package/dist/cli/commands/down.d.ts +3 -0
- package/dist/cli/commands/down.js +36 -0
- package/dist/cli/commands/flush.d.ts +3 -0
- package/dist/cli/commands/flush.js +28 -0
- package/dist/cli/commands/kill.d.ts +3 -0
- package/dist/cli/commands/kill.js +35 -0
- package/dist/cli/commands/list.d.ts +14 -0
- package/dist/cli/commands/list.js +361 -0
- package/dist/cli/commands/logs.d.ts +3 -0
- package/dist/cli/commands/logs.js +107 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.js +151 -0
- package/dist/cli/commands/reload.d.ts +3 -0
- package/dist/cli/commands/reload.js +54 -0
- package/dist/cli/commands/restart.d.ts +3 -0
- package/dist/cli/commands/restart.js +43 -0
- package/dist/cli/commands/restore.d.ts +3 -0
- package/dist/cli/commands/restore.js +88 -0
- package/dist/cli/commands/run.d.ts +8 -0
- package/dist/cli/commands/run.js +212 -0
- package/dist/cli/commands/snap.d.ts +3 -0
- package/dist/cli/commands/snap.js +30 -0
- package/dist/cli/commands/up.d.ts +3 -0
- package/dist/cli/commands/up.js +125 -0
- package/dist/cli/crash-recovery.d.ts +2 -0
- package/dist/cli/crash-recovery.js +67 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +46 -0
- package/dist/cli/parse.d.ts +28 -0
- package/dist/cli/parse.js +97 -0
- package/dist/cluster/ClusterWrapper.d.ts +18 -0
- package/dist/cluster/ClusterWrapper.js +602 -0
- package/dist/config/ConfigStore.d.ts +11 -0
- package/dist/config/ConfigStore.js +21 -0
- package/dist/config/schema.d.ts +103 -0
- package/dist/config/schema.js +49 -0
- package/dist/constants.d.ts +83 -0
- package/dist/constants.js +289 -0
- package/dist/cron/CronScheduler.d.ts +25 -0
- package/dist/cron/CronScheduler.js +149 -0
- package/dist/daemon/GracefulManager.d.ts +8 -0
- package/dist/daemon/GracefulManager.js +29 -0
- package/dist/daemon/ManagedProcess.d.ts +71 -0
- package/dist/daemon/ManagedProcess.js +1020 -0
- package/dist/daemon/Orchestrator.d.ts +51 -0
- package/dist/daemon/Orchestrator.js +416 -0
- package/dist/daemon/RotatingWriter.d.ts +27 -0
- package/dist/daemon/RotatingWriter.js +264 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +106 -0
- package/dist/daemon/startDaemon.d.ts +30 -0
- package/dist/daemon/startDaemon.js +693 -0
- package/dist/deploy/CommandPoller.d.ts +13 -0
- package/dist/deploy/CommandPoller.js +53 -0
- package/dist/deploy/DeployExecutor.d.ts +33 -0
- package/dist/deploy/DeployExecutor.js +340 -0
- package/dist/deploy/config.d.ts +20 -0
- package/dist/deploy/config.js +161 -0
- package/dist/deploy/env.d.ts +2 -0
- package/dist/deploy/env.js +17 -0
- package/dist/deploy/tarball.d.ts +32 -0
- package/dist/deploy/tarball.js +243 -0
- package/dist/detect/framework.d.ts +2 -0
- package/dist/detect/framework.js +24 -0
- package/dist/ipc/DaemonClient.d.ts +31 -0
- package/dist/ipc/DaemonClient.js +248 -0
- package/dist/ipc/DaemonServer.d.ts +28 -0
- package/dist/ipc/DaemonServer.js +166 -0
- package/dist/ipc/MultiUserClient.d.ts +27 -0
- package/dist/ipc/MultiUserClient.js +203 -0
- package/dist/ipc/protocol.d.ts +7 -0
- package/dist/ipc/protocol.js +53 -0
- package/dist/ipc/restoreDaemon.d.ts +8 -0
- package/dist/ipc/restoreDaemon.js +19 -0
- package/dist/machine-id.d.ts +11 -0
- package/dist/machine-id.js +51 -0
- package/dist/mcp/auth.d.ts +118 -0
- package/dist/mcp/auth.js +245 -0
- package/dist/mcp/http.d.ts +20 -0
- package/dist/mcp/http.js +229 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/server.d.ts +37 -0
- package/dist/mcp/server.js +413 -0
- package/dist/probe/compute-fingerprint.d.ts +27 -0
- package/dist/probe/compute-fingerprint.js +65 -0
- package/dist/probe/parse-frames.d.ts +21 -0
- package/dist/probe/parse-frames.js +57 -0
- package/dist/probe/resolve-sourcemaps.d.ts +25 -0
- package/dist/probe/resolve-sourcemaps.js +281 -0
- package/dist/state/StateStore.d.ts +11 -0
- package/dist/state/StateStore.js +78 -0
- package/dist/telemetry/TelemetryReporter.d.ts +49 -0
- package/dist/telemetry/TelemetryReporter.js +451 -0
- package/dist/types/index.d.ts +373 -0
- package/dist/types/index.js +2 -0
- package/package.json +148 -0
- package/packages/cache/README.md +114 -0
- package/packages/cache/dist/CacheClient.d.ts +26 -0
- package/packages/cache/dist/CacheClient.d.ts.map +1 -0
- package/packages/cache/dist/CacheClient.js +174 -0
- package/packages/cache/dist/CacheClient.js.map +1 -0
- package/packages/cache/dist/CacheFileStore.d.ts +45 -0
- package/packages/cache/dist/CacheFileStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheFileStore.js +446 -0
- package/packages/cache/dist/CacheFileStore.js.map +1 -0
- package/packages/cache/dist/CachePersistence.d.ts +9 -0
- package/packages/cache/dist/CachePersistence.d.ts.map +1 -0
- package/packages/cache/dist/CachePersistence.js +67 -0
- package/packages/cache/dist/CachePersistence.js.map +1 -0
- package/packages/cache/dist/CachePrimary.d.ts +25 -0
- package/packages/cache/dist/CachePrimary.d.ts.map +1 -0
- package/packages/cache/dist/CachePrimary.js +155 -0
- package/packages/cache/dist/CachePrimary.js.map +1 -0
- package/packages/cache/dist/CacheStore.d.ts +50 -0
- package/packages/cache/dist/CacheStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheStore.js +271 -0
- package/packages/cache/dist/CacheStore.js.map +1 -0
- package/packages/cache/dist/constants.d.ts +6 -0
- package/packages/cache/dist/constants.d.ts.map +1 -0
- package/packages/cache/dist/constants.js +9 -0
- package/packages/cache/dist/constants.js.map +1 -0
- package/packages/cache/dist/index.d.ts +16 -0
- package/packages/cache/dist/index.d.ts.map +1 -0
- package/packages/cache/dist/index.js +86 -0
- package/packages/cache/dist/index.js.map +1 -0
- package/packages/cache/dist/serialize.d.ts +9 -0
- package/packages/cache/dist/serialize.d.ts.map +1 -0
- package/packages/cache/dist/serialize.js +40 -0
- package/packages/cache/dist/serialize.js.map +1 -0
- package/packages/cache/dist/types.d.ts +123 -0
- package/packages/cache/dist/types.d.ts.map +1 -0
- package/packages/cache/dist/types.js +2 -0
- package/packages/cache/dist/types.js.map +1 -0
- package/packages/cache/package.json +27 -0
- package/packages/cache/src/CacheClient.ts +227 -0
- package/packages/cache/src/CacheFileStore.ts +528 -0
- package/packages/cache/src/CachePersistence.ts +89 -0
- package/packages/cache/src/CachePrimary.ts +172 -0
- package/packages/cache/src/CacheStore.ts +308 -0
- package/packages/cache/src/constants.ts +10 -0
- package/packages/cache/src/index.ts +100 -0
- package/packages/cache/src/serialize.ts +49 -0
- package/packages/cache/src/types.ts +156 -0
- package/packages/cache/tsconfig.json +18 -0
- package/packages/cache/tsconfig.tsbuildinfo +1 -0
- package/packages/next/README.md +166 -0
- package/packages/next/dist/error-capture.d.ts +34 -0
- package/packages/next/dist/error-capture.d.ts.map +1 -0
- package/packages/next/dist/error-capture.js +130 -0
- package/packages/next/dist/error-capture.js.map +1 -0
- package/packages/next/dist/error-handler.d.ts +10 -0
- package/packages/next/dist/error-handler.d.ts.map +1 -0
- package/packages/next/dist/error-handler.js +186 -0
- package/packages/next/dist/error-handler.js.map +1 -0
- package/packages/next/dist/isr-cache.d.ts +9 -0
- package/packages/next/dist/isr-cache.d.ts.map +1 -0
- package/packages/next/dist/isr-cache.js +86 -0
- package/packages/next/dist/isr-cache.js.map +1 -0
- package/packages/next/dist/stream.d.ts +5 -0
- package/packages/next/dist/stream.d.ts.map +1 -0
- package/packages/next/dist/stream.js +22 -0
- package/packages/next/dist/stream.js.map +1 -0
- package/packages/next/dist/types.d.ts +33 -0
- package/packages/next/dist/types.d.ts.map +1 -0
- package/packages/next/dist/types.js +6 -0
- package/packages/next/dist/types.js.map +1 -0
- package/packages/next/dist/use-cache.d.ts +4 -0
- package/packages/next/dist/use-cache.d.ts.map +1 -0
- package/packages/next/dist/use-cache.js +86 -0
- package/packages/next/dist/use-cache.js.map +1 -0
- package/packages/next/dist/utils.d.ts +32 -0
- package/packages/next/dist/utils.d.ts.map +1 -0
- package/packages/next/dist/utils.js +88 -0
- package/packages/next/dist/utils.js.map +1 -0
- package/packages/next/package.json +52 -0
- package/packages/next/src/error-capture.ts +177 -0
- package/packages/next/src/error-handler.ts +221 -0
- package/packages/next/src/isr-cache.ts +100 -0
- package/packages/next/src/stream.ts +23 -0
- package/packages/next/src/types.ts +33 -0
- package/packages/next/src/use-cache.ts +99 -0
- package/packages/next/src/utils.ts +102 -0
- package/packages/next/tsconfig.json +19 -0
- package/packages/next/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { arch, cpus, hostname, platform, totalmem } from 'node:os';
|
|
3
|
+
import { getAgentName } from '../agent-name.js';
|
|
4
|
+
import { TELEMETRY_FLUSH_TIMEOUT, TELEMETRY_LOG_FLUSH_MAX_LINES, TELEMETRY_LOG_MAX_LINE_LENGTH, TELEMETRY_LOG_RING_SIZE, TELEMETRY_MAX_BATCH_SIZE, TELEMETRY_METRICS_INTERVAL, TELEMETRY_REQUEST_TIMEOUT, } from '../constants.js';
|
|
5
|
+
import { getMachineId } from '../machine-id.js';
|
|
6
|
+
import { computeFingerprint, parseFunctionName } from '../probe/compute-fingerprint.js';
|
|
7
|
+
import { resolveSourceMaps } from '../probe/resolve-sourcemaps.js';
|
|
8
|
+
export class TelemetryReporter extends EventEmitter {
|
|
9
|
+
config;
|
|
10
|
+
orchestrator;
|
|
11
|
+
events = [];
|
|
12
|
+
metrics = [];
|
|
13
|
+
errors = [];
|
|
14
|
+
logRings = new Map();
|
|
15
|
+
logFlushBuffer = [];
|
|
16
|
+
logFlushDropped = 0;
|
|
17
|
+
timer = null;
|
|
18
|
+
hostName;
|
|
19
|
+
agentName;
|
|
20
|
+
machineId;
|
|
21
|
+
hostInfo;
|
|
22
|
+
_deployStatus = null;
|
|
23
|
+
configStore;
|
|
24
|
+
alertEvaluator;
|
|
25
|
+
mcpCapable = false;
|
|
26
|
+
/** Previous cumulative cache counters per worker — keyed by "processName:workerId" */
|
|
27
|
+
prevCacheCounters = new Map();
|
|
28
|
+
constructor(config, orchestrator, configStore, alertEvaluator) {
|
|
29
|
+
super();
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.orchestrator = orchestrator;
|
|
32
|
+
this.configStore = configStore ?? null;
|
|
33
|
+
this.alertEvaluator = alertEvaluator ?? null;
|
|
34
|
+
this.hostName = hostname();
|
|
35
|
+
this.agentName = getAgentName();
|
|
36
|
+
this.machineId = getMachineId();
|
|
37
|
+
this.hostInfo = {
|
|
38
|
+
os: platform(),
|
|
39
|
+
arch: arch(),
|
|
40
|
+
nodeVersion: process.version,
|
|
41
|
+
cpuCount: cpus().length,
|
|
42
|
+
totalMemory: totalmem(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
start() {
|
|
46
|
+
this.bindEvents();
|
|
47
|
+
this.timer = setInterval(() => {
|
|
48
|
+
void this.collectAndFlush();
|
|
49
|
+
}, TELEMETRY_METRICS_INTERVAL);
|
|
50
|
+
this.timer.unref();
|
|
51
|
+
}
|
|
52
|
+
async shutdown() {
|
|
53
|
+
if (this.timer) {
|
|
54
|
+
clearInterval(this.timer);
|
|
55
|
+
this.timer = null;
|
|
56
|
+
}
|
|
57
|
+
await Promise.race([
|
|
58
|
+
this.collectAndFlush(),
|
|
59
|
+
new Promise((resolve) => setTimeout(resolve, TELEMETRY_FLUSH_TIMEOUT).unref()),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
bindEvents() {
|
|
63
|
+
this.orchestrator.on('process:start', (data) => {
|
|
64
|
+
this.pushEvent('process:start', data.processName, { processId: data.processId });
|
|
65
|
+
});
|
|
66
|
+
this.orchestrator.on('process:stop', (data) => {
|
|
67
|
+
this.pushEvent('process:stop', data.processName, { processId: data.processId });
|
|
68
|
+
});
|
|
69
|
+
this.orchestrator.on('reload:start', (data) => {
|
|
70
|
+
this.pushEvent('process:reload', data.processName, { processId: data.processId });
|
|
71
|
+
});
|
|
72
|
+
this.orchestrator.on('reload:complete', (data) => {
|
|
73
|
+
this.pushEvent('process:reloaded', data.processName, { processId: data.processId });
|
|
74
|
+
});
|
|
75
|
+
this.orchestrator.on('worker:ready', (data) => {
|
|
76
|
+
this.pushEvent('worker:ready', data.processName, { workerId: data.workerId });
|
|
77
|
+
});
|
|
78
|
+
this.orchestrator.on('worker:exit', (data) => {
|
|
79
|
+
if (data.code !== 0 && data.code !== null) {
|
|
80
|
+
const lastLogs = this.getWorkerLogs(data.processName, data.workerId);
|
|
81
|
+
this.pushEvent('worker:crash', data.processName, {
|
|
82
|
+
workerId: data.workerId,
|
|
83
|
+
exitCode: data.code,
|
|
84
|
+
signal: data.signal,
|
|
85
|
+
lastLogs,
|
|
86
|
+
});
|
|
87
|
+
this.clearWorkerLogs(data.processName, data.workerId);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.pushEvent('worker:exit', data.processName, {
|
|
91
|
+
workerId: data.workerId,
|
|
92
|
+
exitCode: data.code,
|
|
93
|
+
signal: data.signal,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
this.orchestrator.on('worker:maxRestarts', (data) => {
|
|
98
|
+
this.pushEvent('worker:maxRestarts', data.processName, { workerId: data.workerId });
|
|
99
|
+
});
|
|
100
|
+
this.orchestrator.on('worker:memoryRestart', (data) => {
|
|
101
|
+
this.pushEvent('worker:memoryRestart', data.processName, {
|
|
102
|
+
workerId: data.workerId,
|
|
103
|
+
details: { memory: data.memory, limit: data.limit },
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
this.orchestrator.on('log', (data) => {
|
|
107
|
+
// Ring buffer for crash/error context (last N lines per worker)
|
|
108
|
+
let processRings = this.logRings.get(data.processName);
|
|
109
|
+
if (!processRings) {
|
|
110
|
+
processRings = new Map();
|
|
111
|
+
this.logRings.set(data.processName, processRings);
|
|
112
|
+
}
|
|
113
|
+
let ring = processRings.get(data.workerId);
|
|
114
|
+
if (!ring) {
|
|
115
|
+
ring = [];
|
|
116
|
+
processRings.set(data.workerId, ring);
|
|
117
|
+
}
|
|
118
|
+
ring.push(data.data);
|
|
119
|
+
if (ring.length > TELEMETRY_LOG_RING_SIZE) {
|
|
120
|
+
ring.shift();
|
|
121
|
+
}
|
|
122
|
+
// Flush buffer for log ingestion (drained each flush cycle)
|
|
123
|
+
// Skip primary process logs (workerId -1) — they're internal ClusterWrapper messages
|
|
124
|
+
if (data.workerId < 0)
|
|
125
|
+
return;
|
|
126
|
+
const line = data.data.length > TELEMETRY_LOG_MAX_LINE_LENGTH
|
|
127
|
+
? data.data.slice(0, TELEMETRY_LOG_MAX_LINE_LENGTH) + '...'
|
|
128
|
+
: data.data;
|
|
129
|
+
// Detect level: stderr defaults to error, but downgrade to warn if the
|
|
130
|
+
// line contains warn/warning without error (e.g. console.warn output)
|
|
131
|
+
let level = data.type === 'err' ? 'error' : 'info';
|
|
132
|
+
if (level === 'error') {
|
|
133
|
+
const prefix = line.slice(0, 80).toLowerCase();
|
|
134
|
+
if (/\bwarn(ing)?\b/.test(prefix) && !/\berror\b/.test(prefix)) {
|
|
135
|
+
level = 'warn';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this.logFlushBuffer.push({
|
|
139
|
+
processName: data.processName,
|
|
140
|
+
workerId: data.workerId,
|
|
141
|
+
timestamp: Date.now(),
|
|
142
|
+
level,
|
|
143
|
+
message: line,
|
|
144
|
+
});
|
|
145
|
+
// If buffer grows too large between flushes, start dropping oldest
|
|
146
|
+
const maxBuffer = TELEMETRY_LOG_FLUSH_MAX_LINES * 10;
|
|
147
|
+
if (this.logFlushBuffer.length > maxBuffer) {
|
|
148
|
+
const excess = this.logFlushBuffer.length - maxBuffer;
|
|
149
|
+
this.logFlushBuffer.splice(0, excess);
|
|
150
|
+
this.logFlushDropped += excess;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
this.orchestrator.on('worker:error:captured', (data) => {
|
|
154
|
+
const err = data.error;
|
|
155
|
+
const lastLogs = this.getWorkerLogs(data.processName, data.workerId);
|
|
156
|
+
const message = err.message || '';
|
|
157
|
+
const errorEvent = {
|
|
158
|
+
processName: data.processName,
|
|
159
|
+
workerId: data.workerId,
|
|
160
|
+
timestamp: err.timestamp || Date.now(),
|
|
161
|
+
errorType: err.errorType,
|
|
162
|
+
name: err.name || 'Error',
|
|
163
|
+
message,
|
|
164
|
+
stack: err.stack || '',
|
|
165
|
+
fingerprint: err.fingerprint || '',
|
|
166
|
+
sourceContext: err.sourceContext || null,
|
|
167
|
+
topFrame: err.topFrame || null,
|
|
168
|
+
diagnostics: err.diagnostics || null,
|
|
169
|
+
nodeVersion: err.nodeVersion || '',
|
|
170
|
+
pid: err.pid || 0,
|
|
171
|
+
lastLogs,
|
|
172
|
+
url: err.url || undefined,
|
|
173
|
+
userAgent: err.userAgent || undefined,
|
|
174
|
+
};
|
|
175
|
+
// Resolve source maps for bundled/minified code
|
|
176
|
+
const originalTopFrame = errorEvent.topFrame;
|
|
177
|
+
const resolved = resolveSourceMaps(errorEvent.sourceContext, errorEvent.topFrame);
|
|
178
|
+
errorEvent.sourceContext = resolved.sourceContext;
|
|
179
|
+
errorEvent.topFrame = resolved.topFrame;
|
|
180
|
+
errorEvent.resolved = resolved.resolved;
|
|
181
|
+
// Compute stable fingerprint: prefer source map function name, fall back to raw stack
|
|
182
|
+
let functionName = resolved.resolvedFunctionName;
|
|
183
|
+
if (!functionName && originalTopFrame) {
|
|
184
|
+
functionName = parseFunctionName(errorEvent.stack, originalTopFrame.file, originalTopFrame.line);
|
|
185
|
+
}
|
|
186
|
+
const effectiveTop = errorEvent.topFrame;
|
|
187
|
+
errorEvent.fingerprint = computeFingerprint({
|
|
188
|
+
errorName: errorEvent.name,
|
|
189
|
+
message,
|
|
190
|
+
file: effectiveTop?.file ?? '',
|
|
191
|
+
line: effectiveTop?.line ?? 0,
|
|
192
|
+
functionName,
|
|
193
|
+
});
|
|
194
|
+
this.errors.push(errorEvent);
|
|
195
|
+
if (this.errors.length >= TELEMETRY_MAX_BATCH_SIZE) {
|
|
196
|
+
void this.collectAndFlush();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get logs for a specific worker. In cluster mode, worker stdout/stderr flows
|
|
202
|
+
* through the primary process (workerId -1), so if the worker-specific ring
|
|
203
|
+
* is empty we fall back to the primary's ring.
|
|
204
|
+
*/
|
|
205
|
+
getWorkerLogs(processName, workerId) {
|
|
206
|
+
const processRings = this.logRings.get(processName);
|
|
207
|
+
if (!processRings)
|
|
208
|
+
return [];
|
|
209
|
+
const workerRing = processRings.get(workerId);
|
|
210
|
+
if (workerRing && workerRing.length > 0)
|
|
211
|
+
return workerRing.slice();
|
|
212
|
+
// Fallback: cluster primary ring (workerId -1) captures all worker output
|
|
213
|
+
const primaryRing = processRings.get(-1);
|
|
214
|
+
return primaryRing?.slice() ?? [];
|
|
215
|
+
}
|
|
216
|
+
clearWorkerLogs(processName, workerId) {
|
|
217
|
+
const processRings = this.logRings.get(processName);
|
|
218
|
+
if (!processRings)
|
|
219
|
+
return;
|
|
220
|
+
processRings.delete(workerId);
|
|
221
|
+
// Also clear primary ring on crash — it contained this worker's logs
|
|
222
|
+
if (workerId !== -1)
|
|
223
|
+
processRings.delete(-1);
|
|
224
|
+
}
|
|
225
|
+
pushEvent(type, processName, fields) {
|
|
226
|
+
this.events.push({
|
|
227
|
+
type,
|
|
228
|
+
processName,
|
|
229
|
+
timestamp: Date.now(),
|
|
230
|
+
...fields,
|
|
231
|
+
});
|
|
232
|
+
if (this.events.length >= TELEMETRY_MAX_BATCH_SIZE) {
|
|
233
|
+
void this.collectAndFlush();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
emitEvent(type, processName, fields) {
|
|
237
|
+
this.pushEvent(type, processName, fields);
|
|
238
|
+
}
|
|
239
|
+
setDeployStatus(status) {
|
|
240
|
+
this._deployStatus = status;
|
|
241
|
+
}
|
|
242
|
+
setMcpCapable(v) {
|
|
243
|
+
this.mcpCapable = v;
|
|
244
|
+
}
|
|
245
|
+
async collectAndFlush() {
|
|
246
|
+
// Collect metrics snapshot
|
|
247
|
+
const processList = this.orchestrator.list();
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
const metricsSnapshots = processList.map((p) => ({
|
|
250
|
+
processName: p.name,
|
|
251
|
+
processId: p.id,
|
|
252
|
+
execMode: p.execMode,
|
|
253
|
+
status: p.status,
|
|
254
|
+
workers: p.workers.map((w) => {
|
|
255
|
+
const base = {
|
|
256
|
+
id: w.id,
|
|
257
|
+
pid: w.pid,
|
|
258
|
+
cpu: w.cpu,
|
|
259
|
+
memory: w.memory,
|
|
260
|
+
uptime: w.uptime,
|
|
261
|
+
restarts: w.restarts,
|
|
262
|
+
crashes: w.crashes,
|
|
263
|
+
status: w.status,
|
|
264
|
+
stale: w.stale,
|
|
265
|
+
heapUsed: w.heapUsed,
|
|
266
|
+
heapTotal: w.heapTotal,
|
|
267
|
+
external: w.external,
|
|
268
|
+
arrayBuffers: w.arrayBuffers,
|
|
269
|
+
eventLoopLag: w.eventLoopLag,
|
|
270
|
+
eventLoopLagP95: w.eventLoopLagP95,
|
|
271
|
+
activeHandles: w.activeHandles,
|
|
272
|
+
};
|
|
273
|
+
if (w.cacheSize === undefined)
|
|
274
|
+
return base;
|
|
275
|
+
// Compute per-flush deltas for cumulative counters
|
|
276
|
+
const key = `${p.name}:${w.id}`;
|
|
277
|
+
const prev = this.prevCacheCounters.get(key);
|
|
278
|
+
const curHits = w.cacheHits ?? 0;
|
|
279
|
+
const curMisses = w.cacheMisses ?? 0;
|
|
280
|
+
const deltaHits = prev ? Math.max(0, curHits - prev.hits) : curHits;
|
|
281
|
+
const deltaMisses = prev ? Math.max(0, curMisses - prev.misses) : curMisses;
|
|
282
|
+
this.prevCacheCounters.set(key, { hits: curHits, misses: curMisses });
|
|
283
|
+
return {
|
|
284
|
+
...base,
|
|
285
|
+
cacheSize: w.cacheSize,
|
|
286
|
+
cacheTotalBytes: w.cacheTotalBytes,
|
|
287
|
+
cacheHits: deltaHits,
|
|
288
|
+
cacheMisses: deltaMisses,
|
|
289
|
+
cacheHitRate: w.cacheHitRate,
|
|
290
|
+
};
|
|
291
|
+
}),
|
|
292
|
+
timestamp: now,
|
|
293
|
+
}));
|
|
294
|
+
// Drain flush buffer — cap at TELEMETRY_LOG_FLUSH_MAX_LINES per worker
|
|
295
|
+
const logsToSend = this.drainLogFlushBuffer();
|
|
296
|
+
// Buffer swap
|
|
297
|
+
const eventsToSend = this.events;
|
|
298
|
+
const metricsToSend = [...this.metrics, ...metricsSnapshots];
|
|
299
|
+
const errorsToSend = this.errors;
|
|
300
|
+
this.events = [];
|
|
301
|
+
this.metrics = [];
|
|
302
|
+
this.errors = [];
|
|
303
|
+
// Drain alert events before the emptiness check
|
|
304
|
+
const alertsToSend = this.alertEvaluator?.drainAlerts() ?? [];
|
|
305
|
+
if (eventsToSend.length === 0 &&
|
|
306
|
+
metricsToSend.length === 0 &&
|
|
307
|
+
errorsToSend.length === 0 &&
|
|
308
|
+
logsToSend.length === 0 &&
|
|
309
|
+
alertsToSend.length === 0) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const daemonStatus = this.orchestrator.getDaemonStatus();
|
|
313
|
+
const payload = {
|
|
314
|
+
daemonPid: daemonStatus.pid,
|
|
315
|
+
daemonUptime: daemonStatus.uptime,
|
|
316
|
+
hostname: this.hostName,
|
|
317
|
+
agentName: this.agentName,
|
|
318
|
+
machineId: this.machineId,
|
|
319
|
+
host: this.hostInfo,
|
|
320
|
+
events: eventsToSend,
|
|
321
|
+
metrics: metricsToSend,
|
|
322
|
+
errors: errorsToSend,
|
|
323
|
+
logs: logsToSend,
|
|
324
|
+
sentAt: Date.now(),
|
|
325
|
+
};
|
|
326
|
+
// Include config hash for sync
|
|
327
|
+
const configHash = this.configStore?.getHash();
|
|
328
|
+
if (configHash !== undefined) {
|
|
329
|
+
payload.configHash = configHash;
|
|
330
|
+
}
|
|
331
|
+
// Include MCP capability flag
|
|
332
|
+
if (this.mcpCapable) {
|
|
333
|
+
payload.mcpCapable = true;
|
|
334
|
+
}
|
|
335
|
+
// Include alert events
|
|
336
|
+
if (alertsToSend.length > 0) {
|
|
337
|
+
payload.alerts = alertsToSend;
|
|
338
|
+
}
|
|
339
|
+
// Include deploy status if active
|
|
340
|
+
if (this._deployStatus) {
|
|
341
|
+
payload.deployStatus = this._deployStatus;
|
|
342
|
+
const terminalPhases = ['success', 'failed', 'rolled_back'];
|
|
343
|
+
if (terminalPhases.includes(this._deployStatus.phase)) {
|
|
344
|
+
this._deployStatus = null; // Clear after terminal state is sent
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
const response = await fetch(`${this.config.apiHost}/api/v1/ingest/telemetry`, {
|
|
349
|
+
method: 'POST',
|
|
350
|
+
headers: {
|
|
351
|
+
'Content-Type': 'application/json',
|
|
352
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
353
|
+
},
|
|
354
|
+
body: JSON.stringify(payload),
|
|
355
|
+
signal: AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT),
|
|
356
|
+
});
|
|
357
|
+
if (!response.ok) {
|
|
358
|
+
const body = await response.text().catch(() => '');
|
|
359
|
+
console.error(`Telemetry flush failed: ${response.status} ${response.statusText} ${body}`);
|
|
360
|
+
this.restoreBuffers(eventsToSend, metricsToSend, errorsToSend, logsToSend, alertsToSend);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// Parse response for pending commands and config sync
|
|
364
|
+
try {
|
|
365
|
+
const responseBody = (await response.json());
|
|
366
|
+
if (responseBody.has_commands) {
|
|
367
|
+
this.emit('commands:pending');
|
|
368
|
+
}
|
|
369
|
+
if (responseBody.config && responseBody.config_hash) {
|
|
370
|
+
this.configStore?.update(responseBody.config, responseBody.config_hash);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// Ignore JSON parse errors on response
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
console.error('Telemetry flush error:', err instanceof Error ? err.message : err);
|
|
380
|
+
this.restoreBuffers(eventsToSend, metricsToSend, errorsToSend, logsToSend, alertsToSend);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Drain the log flush buffer, capping at TELEMETRY_LOG_FLUSH_MAX_LINES per
|
|
385
|
+
* worker. When lines are dropped, prepend a truncation marker.
|
|
386
|
+
*/
|
|
387
|
+
drainLogFlushBuffer() {
|
|
388
|
+
const buffer = this.logFlushBuffer;
|
|
389
|
+
const dropped = this.logFlushDropped;
|
|
390
|
+
this.logFlushBuffer = [];
|
|
391
|
+
this.logFlushDropped = 0;
|
|
392
|
+
if (buffer.length === 0)
|
|
393
|
+
return [];
|
|
394
|
+
// Group by processName:workerId, keep the last N lines per worker
|
|
395
|
+
const groups = new Map();
|
|
396
|
+
for (const entry of buffer) {
|
|
397
|
+
const key = `${entry.processName}:${entry.workerId}`;
|
|
398
|
+
let group = groups.get(key);
|
|
399
|
+
if (!group) {
|
|
400
|
+
group = [];
|
|
401
|
+
groups.set(key, group);
|
|
402
|
+
}
|
|
403
|
+
group.push(entry);
|
|
404
|
+
}
|
|
405
|
+
const result = [];
|
|
406
|
+
for (const [, entries] of groups) {
|
|
407
|
+
if (entries.length > TELEMETRY_LOG_FLUSH_MAX_LINES) {
|
|
408
|
+
const excess = entries.length - TELEMETRY_LOG_FLUSH_MAX_LINES;
|
|
409
|
+
const kept = entries.slice(-TELEMETRY_LOG_FLUSH_MAX_LINES);
|
|
410
|
+
// Prepend truncation marker
|
|
411
|
+
const first = kept[0];
|
|
412
|
+
result.push({
|
|
413
|
+
processName: first.processName,
|
|
414
|
+
workerId: first.workerId,
|
|
415
|
+
timestamp: first.timestamp - 1,
|
|
416
|
+
level: 'info',
|
|
417
|
+
message: `\u22EF ${excess} log lines truncated`,
|
|
418
|
+
});
|
|
419
|
+
result.push(...kept);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
result.push(...entries);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// If there were globally dropped lines (buffer overflow between flushes)
|
|
426
|
+
if (dropped > 0 && result.length > 0) {
|
|
427
|
+
const first = result[0];
|
|
428
|
+
result.unshift({
|
|
429
|
+
processName: first.processName,
|
|
430
|
+
workerId: first.workerId,
|
|
431
|
+
timestamp: first.timestamp - 1,
|
|
432
|
+
level: 'info',
|
|
433
|
+
message: `\u22EF ${dropped} log lines dropped (buffer overflow)`,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
restoreBuffers(events, metrics, errors, logs, alerts) {
|
|
439
|
+
const maxSize = TELEMETRY_MAX_BATCH_SIZE * 2;
|
|
440
|
+
this.events = [...events, ...this.events].slice(-maxSize);
|
|
441
|
+
this.metrics = [...metrics, ...this.metrics].slice(-maxSize);
|
|
442
|
+
this.errors = [...errors, ...this.errors].slice(-maxSize);
|
|
443
|
+
// Restore logs back to flush buffer (prepend so they'll be sent first next time)
|
|
444
|
+
this.logFlushBuffer = [...logs, ...this.logFlushBuffer].slice(-maxSize);
|
|
445
|
+
// Re-queue unsent alerts back into the evaluator's buffer
|
|
446
|
+
if (alerts && alerts.length > 0 && this.alertEvaluator) {
|
|
447
|
+
this.alertEvaluator.restoreAlerts(alerts);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
//# sourceMappingURL=TelemetryReporter.js.map
|