badmfck-api-server 4.1.24 → 4.1.26
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/dist/apiServer/documentation/index.html +46 -46
- package/dist/apiServer/helper/Validator.js +2 -1
- package/dist/apiServer/http/Http.d.ts +19 -0
- package/dist/apiServer/http/Http.js +56 -4
- package/dist/apiServer/monitor/Monitor.d.ts +3 -0
- package/dist/apiServer/monitor/Monitor.js +17 -0
- package/dist/apiServer/monitor/ProcessMonitor.d.ts +117 -0
- package/dist/apiServer/monitor/ProcessMonitor.js +308 -0
- package/dist/apiServer/monitor/SystemInfo.d.ts +71 -0
- package/dist/apiServer/monitor/SystemInfo.js +218 -0
- package/dist/apiServer/monitor/index.html +78 -44
- package/package.json +1 -1
|
@@ -253,7 +253,8 @@ class Validator {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
else {
|
|
256
|
-
|
|
256
|
+
const isEmpty = object[i] === "" || object[i] === null || object[i] === undefined;
|
|
257
|
+
if (isEmpty && structureOptions.default !== undefined && structureOptions.values.includes(structureOptions.default)) {
|
|
257
258
|
object[i] = structureOptions.default;
|
|
258
259
|
continue;
|
|
259
260
|
}
|
|
@@ -13,6 +13,19 @@ export type THttpErr<E = any> = {
|
|
|
13
13
|
details?: any;
|
|
14
14
|
};
|
|
15
15
|
export type THttpResult<T, E = any> = THttpOk<T> | THttpErr<E>;
|
|
16
|
+
export type THttpOutgoingLogItem = {
|
|
17
|
+
id: number;
|
|
18
|
+
created: number;
|
|
19
|
+
time: number;
|
|
20
|
+
method: HttpMethod;
|
|
21
|
+
url: string;
|
|
22
|
+
status?: number;
|
|
23
|
+
ok: boolean;
|
|
24
|
+
attempts: number;
|
|
25
|
+
request: any;
|
|
26
|
+
response: any;
|
|
27
|
+
error?: string;
|
|
28
|
+
};
|
|
16
29
|
export type TRetryPolicy = {
|
|
17
30
|
enabled?: boolean;
|
|
18
31
|
maxAttempts?: number;
|
|
@@ -71,6 +84,12 @@ export declare class Http {
|
|
|
71
84
|
private static _activeConnectionsByHost;
|
|
72
85
|
private static _countedSockets;
|
|
73
86
|
private static _socketHostKey;
|
|
87
|
+
private static _outgoingLog;
|
|
88
|
+
private static _nextOutgoingLogID;
|
|
89
|
+
private static _outgoingLogLimit;
|
|
90
|
+
private static _truncate;
|
|
91
|
+
private static _pushOutgoingLog;
|
|
92
|
+
static getOutgoingLogSnapshot(): THttpOutgoingLogItem[];
|
|
74
93
|
private static ensureInit;
|
|
75
94
|
static configure(partial: Partial<THttpConfig>): void;
|
|
76
95
|
static createAgent(opts?: ConstructorParameters<typeof Agent>[0]): Agent;
|
|
@@ -88,6 +88,40 @@ class Http {
|
|
|
88
88
|
static _activeConnectionsByHost = new Map();
|
|
89
89
|
static _countedSockets = new WeakSet();
|
|
90
90
|
static _socketHostKey = new WeakMap();
|
|
91
|
+
static _outgoingLog = [];
|
|
92
|
+
static _nextOutgoingLogID = 0;
|
|
93
|
+
static _outgoingLogLimit = 500;
|
|
94
|
+
static _truncate(v) {
|
|
95
|
+
if (v === undefined || v === null)
|
|
96
|
+
return v;
|
|
97
|
+
if (Buffer.isBuffer(v))
|
|
98
|
+
return `[Buffer ${v.length} bytes]`;
|
|
99
|
+
let json = "";
|
|
100
|
+
try {
|
|
101
|
+
json = JSON.stringify(v, (_, val) => {
|
|
102
|
+
if (typeof val === "string" && val.length > 255)
|
|
103
|
+
return val.slice(0, 255) + `...(${val.length})`;
|
|
104
|
+
if (Buffer.isBuffer(val))
|
|
105
|
+
return `[Buffer ${val.length} bytes]`;
|
|
106
|
+
return val;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return "[unserializable]";
|
|
111
|
+
}
|
|
112
|
+
return json.length > 500 ? json.slice(0, 500) + `...(total ${json.length})` : json;
|
|
113
|
+
}
|
|
114
|
+
static _pushOutgoingLog(item) {
|
|
115
|
+
const entry = { id: this._nextOutgoingLogID++, ...item };
|
|
116
|
+
this._outgoingLog.push(entry);
|
|
117
|
+
if (this._outgoingLog.length > this._outgoingLogLimit)
|
|
118
|
+
this._outgoingLog.shift();
|
|
119
|
+
if (this._nextOutgoingLogID > Number.MAX_SAFE_INTEGER - 1000)
|
|
120
|
+
this._nextOutgoingLogID = 0;
|
|
121
|
+
}
|
|
122
|
+
static getOutgoingLogSnapshot() {
|
|
123
|
+
return this._outgoingLog.slice();
|
|
124
|
+
}
|
|
91
125
|
static ensureInit() {
|
|
92
126
|
if (this._inited)
|
|
93
127
|
return;
|
|
@@ -230,6 +264,7 @@ class Http {
|
|
|
230
264
|
static async request(opts) {
|
|
231
265
|
MonitorService_1.S_STAT_REGISTRATE_HTTP_REQUEST.invoke({ url: opts.url });
|
|
232
266
|
this.ensureInit();
|
|
267
|
+
const overallStarted = Date.now();
|
|
233
268
|
const cfg = this.config;
|
|
234
269
|
const retryCfg = {
|
|
235
270
|
enabled: opts.retry?.enabled ?? cfg.retry?.enabled ?? true,
|
|
@@ -263,7 +298,24 @@ class Http {
|
|
|
263
298
|
}
|
|
264
299
|
const responseType = opts.responseType ?? "json";
|
|
265
300
|
const throwOnJsonParseError = opts.throwOnJsonParseError ?? false;
|
|
301
|
+
let attemptsMade = 0;
|
|
302
|
+
const finish = (result, networkErr) => {
|
|
303
|
+
Http._pushOutgoingLog({
|
|
304
|
+
created: overallStarted,
|
|
305
|
+
time: Date.now() - overallStarted,
|
|
306
|
+
method: opts.method,
|
|
307
|
+
url,
|
|
308
|
+
status: result.status,
|
|
309
|
+
ok: result.ok,
|
|
310
|
+
attempts: attemptsMade,
|
|
311
|
+
request: Http._truncate(opts.body),
|
|
312
|
+
response: Http._truncate(result.ok ? result.data : result.error),
|
|
313
|
+
error: networkErr ? String(networkErr?.message ?? networkErr) : undefined,
|
|
314
|
+
});
|
|
315
|
+
return result;
|
|
316
|
+
};
|
|
266
317
|
for (let attempt = 1; attempt <= retryCfg.maxAttempts; attempt++) {
|
|
318
|
+
attemptsMade = attempt;
|
|
267
319
|
const started = Date.now();
|
|
268
320
|
cfg.onRequest?.({ method: opts.method, url, attempt });
|
|
269
321
|
const controller = new AbortController();
|
|
@@ -307,7 +359,7 @@ class Http {
|
|
|
307
359
|
}
|
|
308
360
|
}
|
|
309
361
|
if (status >= 200 && status < 300) {
|
|
310
|
-
return { ok: true, status, data: parsed, headers: hdrs };
|
|
362
|
+
return finish({ ok: true, status, data: parsed, headers: hdrs });
|
|
311
363
|
}
|
|
312
364
|
const retryable = retryCfg.enabled &&
|
|
313
365
|
isRetryableStatus(status, retryCfg.retryOnStatuses) &&
|
|
@@ -317,7 +369,7 @@ class Http {
|
|
|
317
369
|
await sleep(backoff);
|
|
318
370
|
continue;
|
|
319
371
|
}
|
|
320
|
-
return { ok: false, status, error: parsed, details: { headers: hdrs } };
|
|
372
|
+
return finish({ ok: false, status, error: parsed, details: { headers: hdrs } });
|
|
321
373
|
}
|
|
322
374
|
catch (err) {
|
|
323
375
|
const ms = Date.now() - started;
|
|
@@ -331,13 +383,13 @@ class Http {
|
|
|
331
383
|
await sleep(backoff);
|
|
332
384
|
continue;
|
|
333
385
|
}
|
|
334
|
-
return { ok: false, error: err, details: { network: true } };
|
|
386
|
+
return finish({ ok: false, error: err, details: { network: true } }, err);
|
|
335
387
|
}
|
|
336
388
|
finally {
|
|
337
389
|
clearTimeout(t);
|
|
338
390
|
}
|
|
339
391
|
}
|
|
340
|
-
return { ok: false, error: "UNKNOWN" };
|
|
392
|
+
return finish({ ok: false, error: "UNKNOWN" });
|
|
341
393
|
}
|
|
342
394
|
static get(url, opts) {
|
|
343
395
|
return this.request({ ...(opts || {}), method: "GET", url });
|
|
@@ -20,6 +20,9 @@ export declare class Monitor extends BaseEndpoint {
|
|
|
20
20
|
changeLogLevel(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
21
21
|
logs(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
22
22
|
netlog(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
23
|
+
outgoingNetlog(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
24
|
+
sysinfo(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
25
|
+
procinfo(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
23
26
|
metrics(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
24
27
|
connections(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
25
28
|
db(req: HTTPRequestVO): Promise<TransferPacketVO>;
|
|
@@ -15,6 +15,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
15
15
|
const Http_1 = require("../http/Http");
|
|
16
16
|
const MonitorService_1 = require("../MonitorService");
|
|
17
17
|
const DBService_1 = require("../DBService");
|
|
18
|
+
const SystemInfo_1 = require("./SystemInfo");
|
|
19
|
+
const ProcessMonitor_1 = require("./ProcessMonitor");
|
|
18
20
|
exports.S_MONITOR_REGISTRATE_ACTION = new badmfck_signal_1.Signal();
|
|
19
21
|
const logMap = {
|
|
20
22
|
ALL: 10,
|
|
@@ -39,6 +41,9 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
|
|
|
39
41
|
{ ignoreInterceptor: true, endpoint: "html", handler: this.html },
|
|
40
42
|
{ ignoreInterceptor: true, endpoint: "log", handler: this.logs },
|
|
41
43
|
{ ignoreInterceptor: true, endpoint: "netlog", handler: this.netlog },
|
|
44
|
+
{ ignoreInterceptor: true, endpoint: "outgoingnetlog", handler: this.outgoingNetlog },
|
|
45
|
+
{ ignoreInterceptor: true, endpoint: "sysinfo", handler: this.sysinfo },
|
|
46
|
+
{ ignoreInterceptor: true, endpoint: "procinfo", handler: this.procinfo },
|
|
42
47
|
{ ignoreInterceptor: true, endpoint: "metrics", handler: this.metrics, validationModel: {
|
|
43
48
|
range: "",
|
|
44
49
|
$__range_values: ["week", "day", "hour"]
|
|
@@ -120,6 +125,18 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
|
|
|
120
125
|
const log = await APIService_1.REQ_HTTP_LOG.request(req.data);
|
|
121
126
|
return { data: log };
|
|
122
127
|
}
|
|
128
|
+
async outgoingNetlog(req) {
|
|
129
|
+
await this.checkAuthentication(req);
|
|
130
|
+
return { data: Http_1.Http.getOutgoingLogSnapshot() };
|
|
131
|
+
}
|
|
132
|
+
async sysinfo(req) {
|
|
133
|
+
await this.checkAuthentication(req);
|
|
134
|
+
return { data: await (0, SystemInfo_1.collectSystemInfo)() };
|
|
135
|
+
}
|
|
136
|
+
async procinfo(req) {
|
|
137
|
+
await this.checkAuthentication(req);
|
|
138
|
+
return { data: await ProcessMonitor_1.ProcessMonitor.getSnapshot() };
|
|
139
|
+
}
|
|
123
140
|
async metrics(req) {
|
|
124
141
|
await this.checkAuthentication(req);
|
|
125
142
|
const result = await MonitorService_1.REQ_STAT.request(req.data);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare class ProcessMonitor {
|
|
3
|
+
private static _inited;
|
|
4
|
+
private static _eld;
|
|
5
|
+
private static _gcObserver;
|
|
6
|
+
private static _gc;
|
|
7
|
+
private static _prevCpu;
|
|
8
|
+
private static _prevCpuAt;
|
|
9
|
+
private static _cpuPct;
|
|
10
|
+
private static _eluPrev;
|
|
11
|
+
private static _elu;
|
|
12
|
+
private static _processStartedAt;
|
|
13
|
+
static init(): void;
|
|
14
|
+
private static _sampleCpu;
|
|
15
|
+
private static _sampleELU;
|
|
16
|
+
private static _mapGcKind;
|
|
17
|
+
static getSnapshot(): Promise<{
|
|
18
|
+
identity: {
|
|
19
|
+
pid: number;
|
|
20
|
+
ppid: number;
|
|
21
|
+
title: string;
|
|
22
|
+
argv: string[];
|
|
23
|
+
execPath: string;
|
|
24
|
+
cwd: string;
|
|
25
|
+
nodeVersion: string;
|
|
26
|
+
v8Version: string;
|
|
27
|
+
platform: NodeJS.Platform;
|
|
28
|
+
arch: NodeJS.Architecture;
|
|
29
|
+
hostname: string;
|
|
30
|
+
user: {
|
|
31
|
+
uid: number;
|
|
32
|
+
gid: number;
|
|
33
|
+
} | null;
|
|
34
|
+
startedAtUnix: number;
|
|
35
|
+
uptimeSec: number;
|
|
36
|
+
availableParallelism: number;
|
|
37
|
+
};
|
|
38
|
+
memory: {
|
|
39
|
+
rss: number;
|
|
40
|
+
heapTotal: number;
|
|
41
|
+
heapUsed: number;
|
|
42
|
+
external: number;
|
|
43
|
+
arrayBuffers: any;
|
|
44
|
+
heap: {
|
|
45
|
+
sizeLimit: number;
|
|
46
|
+
usedHeap: number;
|
|
47
|
+
totalAvailable: number;
|
|
48
|
+
totalPhysical: number;
|
|
49
|
+
mallocedMemory: number;
|
|
50
|
+
peakMallocedMemory: number;
|
|
51
|
+
numberOfNativeContexts: number;
|
|
52
|
+
numberOfDetachedContexts: number;
|
|
53
|
+
};
|
|
54
|
+
heapSpaces: {
|
|
55
|
+
name: string;
|
|
56
|
+
size: number;
|
|
57
|
+
used: number;
|
|
58
|
+
available: number;
|
|
59
|
+
physical: number;
|
|
60
|
+
}[];
|
|
61
|
+
procStatus: Record<string, number> | null;
|
|
62
|
+
};
|
|
63
|
+
cpu: {
|
|
64
|
+
userPct: number;
|
|
65
|
+
systemPct: number;
|
|
66
|
+
userMicros: number;
|
|
67
|
+
systemMicros: number;
|
|
68
|
+
resource: any;
|
|
69
|
+
};
|
|
70
|
+
eventLoop: {
|
|
71
|
+
minMs: number;
|
|
72
|
+
maxMs: number;
|
|
73
|
+
meanMs: number;
|
|
74
|
+
stddevMs: number;
|
|
75
|
+
p50Ms: number;
|
|
76
|
+
p90Ms: number;
|
|
77
|
+
p99Ms: number;
|
|
78
|
+
utilizationPct: number;
|
|
79
|
+
activeHandles: number;
|
|
80
|
+
activeRequests: number;
|
|
81
|
+
spotLagMs: number;
|
|
82
|
+
} | null;
|
|
83
|
+
gc: {
|
|
84
|
+
totalCount: number;
|
|
85
|
+
totalTimeMs: number;
|
|
86
|
+
maxPauseMs: number;
|
|
87
|
+
byKind: Record<string, {
|
|
88
|
+
count: number;
|
|
89
|
+
timeMs: number;
|
|
90
|
+
}>;
|
|
91
|
+
lastPauses: {
|
|
92
|
+
kind: string;
|
|
93
|
+
ms: number;
|
|
94
|
+
timestamp: number;
|
|
95
|
+
}[];
|
|
96
|
+
};
|
|
97
|
+
fileDescriptors: {
|
|
98
|
+
open: number | null;
|
|
99
|
+
limitSoft: number | null;
|
|
100
|
+
limitHard: number | null;
|
|
101
|
+
};
|
|
102
|
+
io: Record<string, number> | null;
|
|
103
|
+
limits: Record<string, {
|
|
104
|
+
soft: string;
|
|
105
|
+
hard: string;
|
|
106
|
+
unit: string;
|
|
107
|
+
}> | null;
|
|
108
|
+
}>;
|
|
109
|
+
private static _readProcStatus;
|
|
110
|
+
private static _readProcIo;
|
|
111
|
+
private static _readProcLimits;
|
|
112
|
+
private static _countFds;
|
|
113
|
+
private static _parseLimitValue;
|
|
114
|
+
private static _toMsFinite;
|
|
115
|
+
private static _safePercentileMs;
|
|
116
|
+
private static _safeCount;
|
|
117
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProcessMonitor = void 0;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const v8_1 = __importDefault(require("v8"));
|
|
9
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
class ProcessMonitor {
|
|
12
|
+
static _inited = false;
|
|
13
|
+
static _eld = null;
|
|
14
|
+
static _gcObserver = null;
|
|
15
|
+
static _gc = {
|
|
16
|
+
totalCount: 0,
|
|
17
|
+
totalTimeMs: 0,
|
|
18
|
+
maxPauseMs: 0,
|
|
19
|
+
byKind: {},
|
|
20
|
+
lastPauses: [],
|
|
21
|
+
};
|
|
22
|
+
static _prevCpu = process.cpuUsage();
|
|
23
|
+
static _prevCpuAt = Date.now();
|
|
24
|
+
static _cpuPct = { userPct: 0, systemPct: 0 };
|
|
25
|
+
static _eluPrev = perf_hooks_1.performance.eventLoopUtilization();
|
|
26
|
+
static _elu = { idle: 0, active: 0, utilizationPct: 0 };
|
|
27
|
+
static _processStartedAt = Date.now() - process.uptime() * 1000;
|
|
28
|
+
static init() {
|
|
29
|
+
if (this._inited)
|
|
30
|
+
return;
|
|
31
|
+
this._inited = true;
|
|
32
|
+
this._eld = (0, perf_hooks_1.monitorEventLoopDelay)({ resolution: 20 });
|
|
33
|
+
this._eld.enable();
|
|
34
|
+
this._gcObserver = new perf_hooks_1.PerformanceObserver((list) => {
|
|
35
|
+
for (const entry of list.getEntries()) {
|
|
36
|
+
const kind = this._mapGcKind(entry.detail?.kind ?? entry.kind);
|
|
37
|
+
this._gc.totalCount++;
|
|
38
|
+
this._gc.totalTimeMs += entry.duration;
|
|
39
|
+
if (entry.duration > this._gc.maxPauseMs)
|
|
40
|
+
this._gc.maxPauseMs = entry.duration;
|
|
41
|
+
const bucket = this._gc.byKind[kind] ?? { count: 0, timeMs: 0 };
|
|
42
|
+
bucket.count++;
|
|
43
|
+
bucket.timeMs += entry.duration;
|
|
44
|
+
this._gc.byKind[kind] = bucket;
|
|
45
|
+
this._gc.lastPauses.push({ kind, ms: entry.duration, timestamp: Date.now() });
|
|
46
|
+
if (this._gc.lastPauses.length > 20)
|
|
47
|
+
this._gc.lastPauses.shift();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
this._gcObserver.observe({ entryTypes: ["gc"], buffered: false });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
try {
|
|
55
|
+
this._gcObserver.observe({ type: "gc" });
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
58
|
+
}
|
|
59
|
+
const cpuTimer = setInterval(() => this._sampleCpu(), 5000);
|
|
60
|
+
if (cpuTimer.unref)
|
|
61
|
+
cpuTimer.unref();
|
|
62
|
+
const eluTimer = setInterval(() => this._sampleELU(), 5000);
|
|
63
|
+
if (eluTimer.unref)
|
|
64
|
+
eluTimer.unref();
|
|
65
|
+
}
|
|
66
|
+
static _sampleCpu() {
|
|
67
|
+
const now = process.cpuUsage();
|
|
68
|
+
const nowAt = Date.now();
|
|
69
|
+
const dtMs = nowAt - this._prevCpuAt;
|
|
70
|
+
if (dtMs <= 0)
|
|
71
|
+
return;
|
|
72
|
+
const userPct = (now.user - this._prevCpu.user) / (dtMs * 1000) * 100;
|
|
73
|
+
const systemPct = (now.system - this._prevCpu.system) / (dtMs * 1000) * 100;
|
|
74
|
+
this._cpuPct = {
|
|
75
|
+
userPct: Math.round(userPct * 10) / 10,
|
|
76
|
+
systemPct: Math.round(systemPct * 10) / 10,
|
|
77
|
+
};
|
|
78
|
+
this._prevCpu = now;
|
|
79
|
+
this._prevCpuAt = nowAt;
|
|
80
|
+
}
|
|
81
|
+
static _sampleELU() {
|
|
82
|
+
const cur = perf_hooks_1.performance.eventLoopUtilization();
|
|
83
|
+
const delta = perf_hooks_1.performance.eventLoopUtilization(cur, this._eluPrev);
|
|
84
|
+
this._elu = {
|
|
85
|
+
idle: delta.idle,
|
|
86
|
+
active: delta.active,
|
|
87
|
+
utilizationPct: Math.round(delta.utilization * 10000) / 100,
|
|
88
|
+
};
|
|
89
|
+
this._eluPrev = cur;
|
|
90
|
+
}
|
|
91
|
+
static _mapGcKind(kind) {
|
|
92
|
+
if (typeof kind === "string")
|
|
93
|
+
return kind;
|
|
94
|
+
switch (kind) {
|
|
95
|
+
case 1: return "minor";
|
|
96
|
+
case 2: return "major";
|
|
97
|
+
case 4: return "incremental";
|
|
98
|
+
case 8: return "weakcb";
|
|
99
|
+
case 15: return "all";
|
|
100
|
+
default: return "unknown";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
static async getSnapshot() {
|
|
104
|
+
const tStart = process.hrtime.bigint();
|
|
105
|
+
await new Promise(r => setImmediate(() => r()));
|
|
106
|
+
const spotLagMs = Number(process.hrtime.bigint() - tStart) / 1e6;
|
|
107
|
+
const mem = process.memoryUsage();
|
|
108
|
+
const heap = v8_1.default.getHeapStatistics();
|
|
109
|
+
const heapSpaces = v8_1.default.getHeapSpaceStatistics().map(s => ({
|
|
110
|
+
name: s.space_name,
|
|
111
|
+
size: s.space_size,
|
|
112
|
+
used: s.space_used_size,
|
|
113
|
+
available: s.space_available_size,
|
|
114
|
+
physical: s.physical_space_size,
|
|
115
|
+
}));
|
|
116
|
+
const procStatus = await this._readProcStatus();
|
|
117
|
+
const io = await this._readProcIo();
|
|
118
|
+
const limitsParsed = await this._readProcLimits();
|
|
119
|
+
const cpuTotal = process.cpuUsage();
|
|
120
|
+
let resource = null;
|
|
121
|
+
try {
|
|
122
|
+
const r = process.resourceUsage();
|
|
123
|
+
resource = {
|
|
124
|
+
maxRSS: r.maxRSS,
|
|
125
|
+
voluntaryContextSwitches: r.voluntaryContextSwitches,
|
|
126
|
+
involuntaryContextSwitches: r.involuntaryContextSwitches,
|
|
127
|
+
fsRead: r.fsRead,
|
|
128
|
+
fsWrite: r.fsWrite,
|
|
129
|
+
signalsCount: r.signalsCount,
|
|
130
|
+
ipcSent: r.ipcSent,
|
|
131
|
+
ipcReceived: r.ipcReceived,
|
|
132
|
+
userCPUTimeMs: Math.round(r.userCPUTime / 1000),
|
|
133
|
+
systemCPUTimeMs: Math.round(r.systemCPUTime / 1000),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
137
|
+
const fdOpen = await this._countFds();
|
|
138
|
+
const fdSoft = limitsParsed?.["Max open files"]?.soft;
|
|
139
|
+
const fdHard = limitsParsed?.["Max open files"]?.hard;
|
|
140
|
+
const h = this._eld;
|
|
141
|
+
const eventLoop = h ? {
|
|
142
|
+
minMs: this._toMsFinite(h.min),
|
|
143
|
+
maxMs: this._toMsFinite(h.max),
|
|
144
|
+
meanMs: this._toMsFinite(h.mean),
|
|
145
|
+
stddevMs: this._toMsFinite(h.stddev),
|
|
146
|
+
p50Ms: this._safePercentileMs(h, 50),
|
|
147
|
+
p90Ms: this._safePercentileMs(h, 90),
|
|
148
|
+
p99Ms: this._safePercentileMs(h, 99),
|
|
149
|
+
utilizationPct: this._elu.utilizationPct,
|
|
150
|
+
activeHandles: this._safeCount(() => process._getActiveHandles?.()?.length),
|
|
151
|
+
activeRequests: this._safeCount(() => process._getActiveRequests?.()?.length),
|
|
152
|
+
spotLagMs: Math.round(spotLagMs * 100) / 100,
|
|
153
|
+
} : null;
|
|
154
|
+
return {
|
|
155
|
+
identity: {
|
|
156
|
+
pid: process.pid,
|
|
157
|
+
ppid: process.ppid,
|
|
158
|
+
title: process.title,
|
|
159
|
+
argv: process.argv,
|
|
160
|
+
execPath: process.execPath,
|
|
161
|
+
cwd: process.cwd(),
|
|
162
|
+
nodeVersion: process.version,
|
|
163
|
+
v8Version: process.versions.v8,
|
|
164
|
+
platform: process.platform,
|
|
165
|
+
arch: process.arch,
|
|
166
|
+
hostname: os_1.default.hostname(),
|
|
167
|
+
user: typeof process.getuid === "function"
|
|
168
|
+
? { uid: process.getuid(), gid: process.getgid?.() ?? -1 }
|
|
169
|
+
: null,
|
|
170
|
+
startedAtUnix: this._processStartedAt,
|
|
171
|
+
uptimeSec: Math.round(process.uptime()),
|
|
172
|
+
availableParallelism: typeof os_1.default.availableParallelism === "function"
|
|
173
|
+
? os_1.default.availableParallelism()
|
|
174
|
+
: os_1.default.cpus().length,
|
|
175
|
+
},
|
|
176
|
+
memory: {
|
|
177
|
+
rss: mem.rss,
|
|
178
|
+
heapTotal: mem.heapTotal,
|
|
179
|
+
heapUsed: mem.heapUsed,
|
|
180
|
+
external: mem.external,
|
|
181
|
+
arrayBuffers: mem.arrayBuffers ?? 0,
|
|
182
|
+
heap: {
|
|
183
|
+
sizeLimit: heap.heap_size_limit,
|
|
184
|
+
usedHeap: heap.used_heap_size,
|
|
185
|
+
totalAvailable: heap.total_available_size,
|
|
186
|
+
totalPhysical: heap.total_physical_size,
|
|
187
|
+
mallocedMemory: heap.malloced_memory,
|
|
188
|
+
peakMallocedMemory: heap.peak_malloced_memory,
|
|
189
|
+
numberOfNativeContexts: heap.number_of_native_contexts,
|
|
190
|
+
numberOfDetachedContexts: heap.number_of_detached_contexts,
|
|
191
|
+
},
|
|
192
|
+
heapSpaces,
|
|
193
|
+
procStatus,
|
|
194
|
+
},
|
|
195
|
+
cpu: {
|
|
196
|
+
userPct: this._cpuPct.userPct,
|
|
197
|
+
systemPct: this._cpuPct.systemPct,
|
|
198
|
+
userMicros: cpuTotal.user,
|
|
199
|
+
systemMicros: cpuTotal.system,
|
|
200
|
+
resource,
|
|
201
|
+
},
|
|
202
|
+
eventLoop,
|
|
203
|
+
gc: {
|
|
204
|
+
totalCount: this._gc.totalCount,
|
|
205
|
+
totalTimeMs: Math.round(this._gc.totalTimeMs * 100) / 100,
|
|
206
|
+
maxPauseMs: Math.round(this._gc.maxPauseMs * 100) / 100,
|
|
207
|
+
byKind: this._gc.byKind,
|
|
208
|
+
lastPauses: this._gc.lastPauses.slice(-20),
|
|
209
|
+
},
|
|
210
|
+
fileDescriptors: {
|
|
211
|
+
open: fdOpen,
|
|
212
|
+
limitSoft: fdSoft ? this._parseLimitValue(fdSoft) : null,
|
|
213
|
+
limitHard: fdHard ? this._parseLimitValue(fdHard) : null,
|
|
214
|
+
},
|
|
215
|
+
io,
|
|
216
|
+
limits: limitsParsed,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
static async _readProcStatus() {
|
|
220
|
+
try {
|
|
221
|
+
const txt = await promises_1.default.readFile(`/proc/${process.pid}/status`, "utf-8");
|
|
222
|
+
const keys = ["VmPeak", "VmSize", "VmHWM", "VmRSS", "VmData", "VmStk", "VmExe", "VmSwap", "Threads"];
|
|
223
|
+
const result = {};
|
|
224
|
+
for (const line of txt.split("\n")) {
|
|
225
|
+
for (const k of keys) {
|
|
226
|
+
if (line.startsWith(k + ":")) {
|
|
227
|
+
const m = line.match(new RegExp(`${k}:\\s+(\\d+)`));
|
|
228
|
+
if (m) {
|
|
229
|
+
const v = parseInt(m[1], 10);
|
|
230
|
+
result[k] = k === "Threads" ? v : v * 1024;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
static async _readProcIo() {
|
|
242
|
+
try {
|
|
243
|
+
const txt = await promises_1.default.readFile(`/proc/${process.pid}/io`, "utf-8");
|
|
244
|
+
const result = {};
|
|
245
|
+
for (const line of txt.split("\n")) {
|
|
246
|
+
const m = line.match(/^(\w+):\s+(\d+)$/);
|
|
247
|
+
if (m)
|
|
248
|
+
result[m[1]] = parseInt(m[2], 10);
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
static async _readProcLimits() {
|
|
257
|
+
try {
|
|
258
|
+
const txt = await promises_1.default.readFile(`/proc/${process.pid}/limits`, "utf-8");
|
|
259
|
+
const result = {};
|
|
260
|
+
const lines = txt.split("\n").slice(1);
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
const m = line.match(/^(.+?)\s{2,}(\S+)\s+(\S+)\s+(\S+)\s*$/);
|
|
263
|
+
if (m)
|
|
264
|
+
result[m[1].trim()] = { soft: m[2], hard: m[3], unit: m[4] };
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
static async _countFds() {
|
|
273
|
+
try {
|
|
274
|
+
const entries = await promises_1.default.readdir(`/proc/${process.pid}/fd`);
|
|
275
|
+
return entries.length;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
static _parseLimitValue(v) {
|
|
282
|
+
if (v === "unlimited")
|
|
283
|
+
return -1;
|
|
284
|
+
const n = parseInt(v, 10);
|
|
285
|
+
return Number.isFinite(n) ? n : null;
|
|
286
|
+
}
|
|
287
|
+
static _toMsFinite(ns) {
|
|
288
|
+
return Number.isFinite(ns) ? Math.round((ns / 1e6) * 100) / 100 : 0;
|
|
289
|
+
}
|
|
290
|
+
static _safePercentileMs(h, p) {
|
|
291
|
+
try {
|
|
292
|
+
return this._toMsFinite(h.percentile(p));
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return 0;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
static _safeCount(fn) {
|
|
299
|
+
try {
|
|
300
|
+
return fn() ?? 0;
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
exports.ProcessMonitor = ProcessMonitor;
|
|
308
|
+
ProcessMonitor.init();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare function collectSystemInfo(): Promise<{
|
|
3
|
+
cpu: {
|
|
4
|
+
usage: {
|
|
5
|
+
userPct: number;
|
|
6
|
+
systemPct: number;
|
|
7
|
+
idlePct: number;
|
|
8
|
+
iowaitPct: number;
|
|
9
|
+
} | null;
|
|
10
|
+
count: number;
|
|
11
|
+
models: Record<string, number>;
|
|
12
|
+
loadAvg: number[];
|
|
13
|
+
platform: NodeJS.Platform;
|
|
14
|
+
arch: NodeJS.Architecture;
|
|
15
|
+
uptimeSec: number;
|
|
16
|
+
};
|
|
17
|
+
memory: {
|
|
18
|
+
total: number;
|
|
19
|
+
free: number;
|
|
20
|
+
used: number;
|
|
21
|
+
usedPct: number;
|
|
22
|
+
};
|
|
23
|
+
disk: {
|
|
24
|
+
filesystem: string;
|
|
25
|
+
size: number;
|
|
26
|
+
used: number;
|
|
27
|
+
available: number;
|
|
28
|
+
usePct: string;
|
|
29
|
+
mount: string;
|
|
30
|
+
}[] | null;
|
|
31
|
+
connections: {
|
|
32
|
+
ports: number[];
|
|
33
|
+
total: number;
|
|
34
|
+
topRemote: {
|
|
35
|
+
ip: string;
|
|
36
|
+
count: number;
|
|
37
|
+
}[];
|
|
38
|
+
} | null;
|
|
39
|
+
nginx: {
|
|
40
|
+
installed: boolean;
|
|
41
|
+
version?: undefined;
|
|
42
|
+
status?: undefined;
|
|
43
|
+
listening?: undefined;
|
|
44
|
+
workerCount?: undefined;
|
|
45
|
+
} | {
|
|
46
|
+
installed: boolean;
|
|
47
|
+
version: string;
|
|
48
|
+
status: string;
|
|
49
|
+
listening: string[];
|
|
50
|
+
workerCount: number;
|
|
51
|
+
} | {
|
|
52
|
+
installed: boolean;
|
|
53
|
+
error: string;
|
|
54
|
+
};
|
|
55
|
+
pm2: {
|
|
56
|
+
installed: boolean;
|
|
57
|
+
error?: undefined;
|
|
58
|
+
processes?: undefined;
|
|
59
|
+
} | {
|
|
60
|
+
installed: boolean;
|
|
61
|
+
error: string;
|
|
62
|
+
processes?: undefined;
|
|
63
|
+
} | {
|
|
64
|
+
installed: boolean;
|
|
65
|
+
processes: any;
|
|
66
|
+
error?: undefined;
|
|
67
|
+
} | {
|
|
68
|
+
installed: boolean;
|
|
69
|
+
error: string;
|
|
70
|
+
};
|
|
71
|
+
}>;
|