bm2 1.0.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/src/monitor.ts ADDED
@@ -0,0 +1,185 @@
1
+ /**
2
+ * BM2 — Bun Process Manager
3
+ * A production-grade process manager for Bun.
4
+ *
5
+ * Features:
6
+ * - Fork & cluster execution modes
7
+ * - Auto-restart & crash recovery
8
+ * - Health checks & monitoring
9
+ * - Log management & rotation
10
+ * - Deployment support
11
+ *
12
+ * https://github.com/your-org/bm2
13
+ * License: GPL-3.0-only
14
+ * Author: Zak <zak@maxxpainn.com>
15
+ */
16
+
17
+ import type { MetricSnapshot, ProcessState } from "./types";
18
+ import { getSystemInfo } from "./utils";
19
+ import { METRICS_DIR } from "./constants";
20
+ import { join } from "path";
21
+
22
+ export class Monitor {
23
+ private history: MetricSnapshot[] = [];
24
+ private maxHistory = 3600; // 1 hour at 1s intervals
25
+
26
+ async collectProcessMetrics(
27
+ pid: number
28
+ ): Promise<{ memory: number; cpu: number; handles?: number }> {
29
+ try {
30
+ if (process.platform === "linux") {
31
+ const statusFile = Bun.file(`/proc/${pid}/status`);
32
+ const statFile = Bun.file(`/proc/${pid}/stat`);
33
+
34
+ let memory = 0;
35
+ let cpu = 0;
36
+ let handles: number | undefined;
37
+
38
+ if (await statusFile.exists()) {
39
+ const content = await statusFile.text();
40
+ const vmRss = content.match(/VmRSS:\s+(\d+)\s+kB/);
41
+
42
+ if (vmRss) memory = parseInt(vmRss[1]!) * 1024;
43
+
44
+ // Count file descriptors
45
+ try {
46
+ const { readdirSync } = require("fs");
47
+ const fds = readdirSync(`/proc/${pid}/fd`);
48
+ handles = fds.length;
49
+ } catch {}
50
+ }
51
+
52
+ if (await statFile.exists()) {
53
+ const stat = await statFile.text();
54
+ const parts = stat.split(" ");
55
+
56
+ const utime = parseInt(parts[13]!) || 0;
57
+ const stime = parseInt(parts[14]!) || 0;
58
+
59
+ // Simplified CPU calculation
60
+ cpu = (utime + stime) / 100;
61
+ }
62
+
63
+ return { memory, cpu, handles };
64
+ } else {
65
+ // macOS / fallback
66
+ const ps = Bun.spawn(
67
+ ["ps", "-o", "rss=,pcpu=", "-p", String(pid)],
68
+ { stdout: "pipe", stderr: "pipe" }
69
+ );
70
+ const output = await new Response(ps.stdout).text();
71
+ const parts = output.trim().split(/\s+/);
72
+
73
+ if (parts.length >= 2) {
74
+ return {
75
+ memory: parseInt(parts[0]!) * 1024,
76
+ cpu: parseFloat(parts[1]!),
77
+ };
78
+ }
79
+ }
80
+ } catch {}
81
+
82
+ return { memory: 0, cpu: 0 };
83
+ }
84
+
85
+ async takeSnapshot(processes: ProcessState[]): Promise<MetricSnapshot> {
86
+ const system = getSystemInfo();
87
+ const snapshot: MetricSnapshot = {
88
+ timestamp: Date.now(),
89
+ processes: processes.map((p) => ({
90
+ id: p.id,
91
+ name: p.name,
92
+ pid: p.pid,
93
+ cpu: p.monit.cpu,
94
+ memory: p.monit.memory,
95
+ eventLoopLatency: p.monit.eventLoopLatency,
96
+ handles: p.monit.handles,
97
+ status: p.status,
98
+ restarts: p.pm2_env.restart_time,
99
+ uptime: p.pm2_env.status === "online" ? Date.now() - p.pm2_env.pm_uptime : 0,
100
+ })),
101
+ system: {
102
+ totalMemory: system.totalMemory,
103
+ freeMemory: system.freeMemory,
104
+ cpuCount: system.cpuCount,
105
+ loadAvg: system.loadAvg,
106
+ platform: system.platform,
107
+ },
108
+ };
109
+
110
+ this.history.push(snapshot);
111
+ if (this.history.length > this.maxHistory) {
112
+ this.history = this.history.slice(-this.maxHistory);
113
+ }
114
+
115
+ return snapshot;
116
+ }
117
+
118
+ getHistory(seconds: number = 300): MetricSnapshot[] {
119
+ const cutoff = Date.now() - seconds * 1000;
120
+ return this.history.filter((s) => s.timestamp >= cutoff);
121
+ }
122
+
123
+ getLatest(): MetricSnapshot | null {
124
+ return this.history.length > 0 ? this.history[this.history.length - 1]! : null;
125
+ }
126
+
127
+ async saveMetrics(): Promise<void> {
128
+ const filePath = join(METRICS_DIR, `metrics-${Date.now()}.json`);
129
+ await Bun.write(filePath, JSON.stringify(this.history.slice(-300)));
130
+ }
131
+
132
+ generatePrometheusMetrics(processes: ProcessState[]): string {
133
+ const lines: string[] = [];
134
+
135
+ lines.push("# HELP bm2_process_cpu CPU usage percentage");
136
+ lines.push("# TYPE bm2_process_cpu gauge");
137
+ for (const p of processes) {
138
+ lines.push(`bm2_process_cpu{name="${p.name}",id="${p.pm_id}"} ${p.monit.cpu}`);
139
+ }
140
+
141
+ lines.push("# HELP bm2_process_memory_bytes Memory usage in bytes");
142
+ lines.push("# TYPE bm2_process_memory_bytes gauge");
143
+ for (const p of processes) {
144
+ lines.push(`bm2_process_memory_bytes{name="${p.name}",id="${p.pm_id}"} ${p.monit.memory}`);
145
+ }
146
+
147
+ lines.push("# HELP bm2_process_restarts_total Total restart count");
148
+ lines.push("# TYPE bm2_process_restarts_total counter");
149
+ for (const p of processes) {
150
+ lines.push(`bm2_process_restarts_total{name="${p.name}",id="${p.pm_id}"} ${p.pm2_env.restart_time}`);
151
+ }
152
+
153
+ lines.push("# HELP bm2_process_uptime_seconds Process uptime in seconds");
154
+ lines.push("# TYPE bm2_process_uptime_seconds gauge");
155
+ for (const p of processes) {
156
+ const uptime = p.pm2_env.status === "online"
157
+ ? (Date.now() - p.pm2_env.pm_uptime) / 1000
158
+ : 0;
159
+ lines.push(`bm2_process_uptime_seconds{name="${p.name}",id="${p.pm_id}"} ${uptime.toFixed(0)}`);
160
+ }
161
+
162
+ lines.push("# HELP bm2_process_status Process status (1=online)");
163
+ lines.push("# TYPE bm2_process_status gauge");
164
+ for (const p of processes) {
165
+ lines.push(`bm2_process_status{name="${p.name}",id="${p.pm_id}",status="${p.status}"} ${p.status === "online" ? 1 : 0}`);
166
+ }
167
+
168
+ const sys = getSystemInfo();
169
+ lines.push("# HELP bm2_system_memory_total_bytes Total system memory");
170
+ lines.push("# TYPE bm2_system_memory_total_bytes gauge");
171
+ lines.push(`bm2_system_memory_total_bytes ${sys.totalMemory}`);
172
+
173
+ lines.push("# HELP bm2_system_memory_free_bytes Free system memory");
174
+ lines.push("# TYPE bm2_system_memory_free_bytes gauge");
175
+ lines.push(`bm2_system_memory_free_bytes ${sys.freeMemory}`);
176
+
177
+ lines.push("# HELP bm2_system_load_average System load average");
178
+ lines.push("# TYPE bm2_system_load_average gauge");
179
+ lines.push(`bm2_system_load_average{period="1m"} ${sys.loadAvg[0]}`);
180
+ lines.push(`bm2_system_load_average{period="5m"} ${sys.loadAvg[1]}`);
181
+ lines.push(`bm2_system_load_average{period="15m"} ${sys.loadAvg[2]}`);
182
+
183
+ return lines.join("\n") + "\n";
184
+ }
185
+ }
@@ -0,0 +1,508 @@
1
+ /**
2
+ * BM2 — Bun Process Manager
3
+ * A production-grade process manager for Bun.
4
+ *
5
+ * Features:
6
+ * - Fork & cluster execution modes
7
+ * - Auto-restart & crash recovery
8
+ * - Health checks & monitoring
9
+ * - Log management & rotation
10
+ * - Deployment support
11
+ *
12
+ * https://github.com/your-org/bm2
13
+ * License: GPL-3.0-only
14
+ * Author: Zak <zak@maxxpainn.com>
15
+ */
16
+ import type { Subprocess } from "bun";
17
+ import type {
18
+ ProcessDescription,
19
+ ProcessState,
20
+ ProcessStatus,
21
+ LogRotateOptions,
22
+ } from "./types";
23
+ import { LogManager } from "./log-manager";
24
+ import { ClusterManager } from "./cluster-manager";
25
+ import { HealthChecker } from "./health-checker";
26
+ import { CronManager } from "./cron-manager";
27
+ import { treeKill } from "./utils";
28
+ import { join } from "path";
29
+ import {
30
+ PID_DIR,
31
+ MONITOR_INTERVAL,
32
+ DEFAULT_LOG_MAX_SIZE,
33
+ DEFAULT_LOG_RETAIN,
34
+ } from "./constants";
35
+
36
+ export class ProcessContainer {
37
+ public id: number;
38
+ public name: string;
39
+ public config: ProcessDescription;
40
+ public status: ProcessStatus = "stopped";
41
+ public process: Subprocess | null = null;
42
+ public pid: number | undefined;
43
+ public restartCount: number = 0;
44
+ public unstableRestarts: number = 0;
45
+ public createdAt: number;
46
+ public startedAt: number = 0;
47
+ public memory: number = 0;
48
+ public cpu: number = 0;
49
+ public handles: number = 0;
50
+ public eventLoopLatency: number = 0;
51
+ public axmMonitor: Record<string, any> = {};
52
+
53
+ private logManager: LogManager;
54
+ private clusterManager: ClusterManager;
55
+ private healthChecker: HealthChecker;
56
+ private cronManager: CronManager;
57
+ private restartTimer: ReturnType<typeof setTimeout> | null = null;
58
+ private watcher: ReturnType<typeof import("fs").watch> | null = null;
59
+ private monitorInterval: ReturnType<typeof setInterval> | null = null;
60
+ private logRotateInterval: ReturnType<typeof setInterval> | null = null;
61
+ private isRestarting: boolean = false;
62
+
63
+ constructor(
64
+ id: number,
65
+ config: ProcessDescription,
66
+ logManager: LogManager,
67
+ clusterManager: ClusterManager,
68
+ healthChecker: HealthChecker,
69
+ cronManager: CronManager
70
+ ) {
71
+ this.id = id;
72
+ this.name = config.name;
73
+ this.config = config;
74
+ this.logManager = logManager;
75
+ this.clusterManager = clusterManager;
76
+ this.healthChecker = healthChecker;
77
+ this.cronManager = cronManager;
78
+ this.createdAt = Date.now();
79
+ }
80
+
81
+ async start(): Promise<void> {
82
+ if (this.status === "online") return;
83
+
84
+ this.status = "launching";
85
+ const logPaths = this.logManager.getLogPaths(
86
+ this.name,
87
+ this.id,
88
+ this.config.outFile,
89
+ this.config.errorFile
90
+ );
91
+
92
+ try {
93
+ // Ensure log files exist
94
+ for (const f of [logPaths.outFile, logPaths.errFile]) {
95
+ const file = Bun.file(f);
96
+ if (!(await file.exists())) await Bun.write(f, "");
97
+ }
98
+
99
+ if (this.config.execMode === "cluster" && this.config.instances > 1) {
100
+ await this.startCluster(logPaths);
101
+ } else {
102
+ await this.startFork(logPaths);
103
+ }
104
+
105
+ this.startedAt = Date.now();
106
+ this.status = "online";
107
+
108
+ // Write PID file
109
+ if (this.pid) {
110
+ await Bun.write(
111
+ join(PID_DIR, `${this.name}-${this.id}.pid`),
112
+ String(this.pid)
113
+ );
114
+ }
115
+
116
+ // Start monitoring
117
+ this.startMonitoring();
118
+
119
+ // Start log rotation
120
+ this.startLogRotation(logPaths);
121
+
122
+ // Setup watch mode
123
+ if (this.config.watch) {
124
+ this.setupWatch();
125
+ }
126
+
127
+ // Setup health checks
128
+ if (this.config.healthCheckUrl) {
129
+ this.healthChecker.startCheck(
130
+ this.id,
131
+ {
132
+ url: this.config.healthCheckUrl,
133
+ interval: this.config.healthCheckInterval || 30000,
134
+ timeout: this.config.healthCheckTimeout || 5000,
135
+ maxFails: this.config.healthCheckMaxFails || 3,
136
+ },
137
+ (_id, reason) => {
138
+ console.log(`[bm2] Health check failed for ${this.name}: ${reason}`);
139
+ this.restart();
140
+ }
141
+ );
142
+ }
143
+
144
+ // Setup cron restart
145
+ if (this.config.cronRestart) {
146
+ this.cronManager.schedule(this.id, this.config.cronRestart, () => {
147
+ console.log(`[bm2] Cron restart triggered for ${this.name}`);
148
+ this.restart();
149
+ });
150
+ }
151
+ } catch (err: any) {
152
+ this.status = "errored";
153
+ const timestamp = new Date().toISOString();
154
+ await this.logManager.appendLog(
155
+ logPaths.errFile,
156
+ `[${timestamp}] [bm2] Failed to start: ${err.message}\n`
157
+ );
158
+ throw err;
159
+ }
160
+ }
161
+
162
+ private async startFork(logPaths: { outFile: string; errFile: string }) {
163
+ const cmd = this.clusterManager.buildWorkerCommand(this.config);
164
+ const env: Record<string, string> = {
165
+ ...(process.env as Record<string, string>),
166
+ ...this.config.env,
167
+ BM2_ID: String(this.id),
168
+ BM2_NAME: this.name,
169
+ BM2_EXEC_MODE: "fork",
170
+ };
171
+
172
+ this.process = Bun.spawn(cmd, {
173
+ cwd: this.config.cwd || process.cwd(),
174
+ env,
175
+ stdout: "pipe",
176
+ stderr: "pipe",
177
+ stdin: "ignore",
178
+ });
179
+
180
+ this.pid = this.process.pid;
181
+ this.pipeOutput(logPaths);
182
+
183
+ this.process.exited.then((code) => {
184
+ if (!this.isRestarting) {
185
+ this.handleExit(code);
186
+ }
187
+ });
188
+ }
189
+
190
+ private async startCluster(logPaths: { outFile: string; errFile: string }) {
191
+ const proc = this.clusterManager.spawnWorker(
192
+ this.config,
193
+ 0,
194
+ this.config.instances,
195
+ { stdout: "pipe", stderr: "pipe" }
196
+ );
197
+
198
+ this.process = proc;
199
+ this.pid = proc.pid;
200
+
201
+ if (proc.stdout && typeof proc.stdout !== "number") {
202
+ this.pipeStream(proc.stdout, logPaths.outFile);
203
+ }
204
+ if (proc.stderr && typeof proc.stderr !== "number") {
205
+ this.pipeStream(proc.stderr, logPaths.errFile);
206
+ }
207
+
208
+ proc.exited.then((code) => {
209
+ if (!this.isRestarting) {
210
+ this.handleExit(code);
211
+ }
212
+ });
213
+ }
214
+
215
+ private pipeOutput(logPaths: { outFile: string; errFile: string }) {
216
+ if (!this.process) return;
217
+ if (this.process.stdout && typeof this.process.stdout !== "number") {
218
+ this.pipeStream(this.process.stdout, logPaths.outFile);
219
+ }
220
+ if (this.process.stderr && typeof this.process.stderr !== "number") {
221
+ this.pipeStream(this.process.stderr, logPaths.errFile);
222
+ }
223
+ }
224
+
225
+ private async pipeStream(stream: ReadableStream<Uint8Array>, filePath: string) {
226
+ const reader = stream.getReader();
227
+ try {
228
+ while (true) {
229
+ const { done, value } = await reader.read();
230
+ if (done) break;
231
+ const text = new TextDecoder().decode(value);
232
+ const timestamp = new Date().toISOString();
233
+ const lines = text.split("\n").filter(Boolean);
234
+ for (const line of lines) {
235
+ await this.logManager.appendLog(filePath, `[${timestamp}] ${line}\n`);
236
+ }
237
+ }
238
+ } catch {}
239
+ }
240
+
241
+ private startMonitoring() {
242
+ this.monitorInterval = setInterval(async () => {
243
+ if (!this.pid || this.status !== "online") return;
244
+
245
+ try {
246
+ if (process.platform === "linux") {
247
+ const statusFile = Bun.file(`/proc/${this.pid}/status`);
248
+ if (await statusFile.exists()) {
249
+ const content = await statusFile.text();
250
+ const vmRss = content.match(/VmRSS:\s+(\d+)\s+kB/);
251
+ if (vmRss) this.memory = parseInt(vmRss[1]!) * 1024;
252
+ }
253
+
254
+ try {
255
+ const { readdirSync } = require("fs");
256
+ this.handles = readdirSync(`/proc/${this.pid}/fd`).length;
257
+ } catch {}
258
+ } else {
259
+ const ps = Bun.spawn(["ps", "-o", "rss=,pcpu=", "-p", String(this.pid)], {
260
+ stdout: "pipe", stderr: "pipe",
261
+ });
262
+ const output = await new Response(ps.stdout).text();
263
+ const parts = output.trim().split(/\s+/);
264
+ if (parts.length >= 2) {
265
+ this.memory = parseInt(parts[0]!) * 1024;
266
+ this.cpu = parseFloat(parts[1]!);
267
+ }
268
+ }
269
+
270
+ // Max memory restart
271
+ if (this.config.maxMemoryRestart && this.memory > this.config.maxMemoryRestart) {
272
+ console.log(`[bm2] ${this.name} exceeded memory limit (${this.memory} > ${this.config.maxMemoryRestart}), restarting...`);
273
+ await this.restart();
274
+ }
275
+ } catch {}
276
+ }, MONITOR_INTERVAL);
277
+ }
278
+
279
+ private startLogRotation(logPaths: { outFile: string; errFile: string }) {
280
+ const rotateOpts: LogRotateOptions = {
281
+ maxSize: this.config.logMaxSize || DEFAULT_LOG_MAX_SIZE,
282
+ retain: this.config.logRetain || DEFAULT_LOG_RETAIN,
283
+ compress: this.config.logCompress || false,
284
+ };
285
+
286
+ this.logRotateInterval = setInterval(() => {
287
+ this.logManager.checkRotation(
288
+ this.name,
289
+ this.id,
290
+ rotateOpts,
291
+ this.config.outFile,
292
+ this.config.errorFile
293
+ );
294
+ }, 60000);
295
+ }
296
+
297
+ private setupWatch() {
298
+ const { watch } = require("fs");
299
+ const paths = this.config.watchPaths || [this.config.cwd || process.cwd()];
300
+ const ignorePatterns = this.config.ignoreWatch || ["node_modules", ".git", ".bm2"];
301
+
302
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
303
+
304
+ for (const watchPath of paths) {
305
+ try {
306
+ this.watcher = watch(
307
+ watchPath,
308
+ { recursive: true },
309
+ (_event: string, filename: string | null) => {
310
+ if (!filename) return;
311
+ if (ignorePatterns.some((p) => filename.includes(p))) return;
312
+
313
+ if (debounceTimer) clearTimeout(debounceTimer);
314
+ debounceTimer = setTimeout(() => {
315
+ console.log(`[bm2] ${filename} changed, restarting ${this.name}...`);
316
+ this.restart();
317
+ }, 1000);
318
+ }
319
+ );
320
+ } catch {}
321
+ }
322
+ }
323
+
324
+ private handleExit(code: number | null) {
325
+ const wasOnline = this.status === "online";
326
+ this.status = code === 0 ? "stopped" : "errored";
327
+ this.pid = undefined;
328
+ this.process = null;
329
+
330
+ this.cleanupTimers();
331
+
332
+ const uptime = Date.now() - this.startedAt;
333
+
334
+ if (wasOnline && this.config.autorestart && this.restartCount < this.config.maxRestarts) {
335
+ if (uptime < this.config.minUptime) {
336
+ this.unstableRestarts++;
337
+ }
338
+
339
+ this.status = "waiting-restart";
340
+ const delay = this.config.restartDelay || 0;
341
+
342
+ this.restartTimer = setTimeout(() => {
343
+ this.restartCount++;
344
+ console.log(`[bm2] Restarting ${this.name} (attempt ${this.restartCount}/${this.config.maxRestarts})`);
345
+ this.start().catch((err) => {
346
+ console.error(`[bm2] Failed to restart ${this.name}:`, err);
347
+ });
348
+ }, delay);
349
+ } else if (this.restartCount >= this.config.maxRestarts) {
350
+ console.log(`[bm2] ${this.name} reached max restarts (${this.config.maxRestarts}), not restarting`);
351
+ this.status = "errored";
352
+ }
353
+ }
354
+
355
+ private cleanupTimers() {
356
+ if (this.monitorInterval) {
357
+ clearInterval(this.monitorInterval);
358
+ this.monitorInterval = null;
359
+ }
360
+ if (this.logRotateInterval) {
361
+ clearInterval(this.logRotateInterval);
362
+ this.logRotateInterval = null;
363
+ }
364
+ this.healthChecker.stopCheck(this.id);
365
+ this.cronManager.cancel(this.id);
366
+ }
367
+
368
+ async stop(force: boolean = false): Promise<void> {
369
+ if (this.status !== "online" && this.status !== "launching" && this.status !== "waiting-restart") {
370
+ return;
371
+ }
372
+
373
+ this.isRestarting = false;
374
+ this.status = "stopping";
375
+ this.config.autorestart = false;
376
+
377
+ if (this.restartTimer) {
378
+ clearTimeout(this.restartTimer);
379
+ this.restartTimer = null;
380
+ }
381
+
382
+ this.cleanupTimers();
383
+
384
+ if (this.watcher) {
385
+ this.watcher.close();
386
+ this.watcher = null;
387
+ }
388
+
389
+ if (this.process && this.pid) {
390
+ if (this.config.treekill !== false) {
391
+ await treeKill(this.pid, "SIGTERM");
392
+ } else {
393
+ this.process.kill("SIGTERM" as any);
394
+ }
395
+
396
+ if (!force) {
397
+ const timeout = this.config.killTimeout || 5000;
398
+ const exited = await Promise.race([
399
+ this.process.exited.then(() => true),
400
+ new Promise<boolean>((r) => setTimeout(() => r(false), timeout)),
401
+ ]);
402
+
403
+ if (!exited && this.process) {
404
+ if (this.config.treekill !== false && this.pid) {
405
+ await treeKill(this.pid, "SIGKILL");
406
+ } else {
407
+ this.process.kill("SIGKILL" as any);
408
+ }
409
+ await this.process.exited;
410
+ }
411
+ } else {
412
+ if (this.config.treekill !== false && this.pid) {
413
+ await treeKill(this.pid, "SIGKILL");
414
+ } else {
415
+ this.process.kill("SIGKILL" as any);
416
+ }
417
+ await this.process.exited;
418
+ }
419
+ }
420
+
421
+ // Clean up cluster workers
422
+ this.clusterManager.removeAllWorkers(this.id);
423
+
424
+ this.status = "stopped";
425
+ this.pid = undefined;
426
+ this.process = null;
427
+ this.memory = 0;
428
+ this.cpu = 0;
429
+ }
430
+
431
+ async restart(): Promise<void> {
432
+ this.isRestarting = true;
433
+ const wasAutoRestart = this.config.autorestart;
434
+ await this.stop();
435
+ this.config.autorestart = wasAutoRestart;
436
+ this.isRestarting = false;
437
+ await this.start();
438
+ }
439
+
440
+ async reload(): Promise<void> {
441
+ const oldPid = this.pid;
442
+ const oldProcess = this.process;
443
+
444
+ this.isRestarting = true;
445
+ this.process = null;
446
+ this.pid = undefined;
447
+
448
+ await this.start();
449
+
450
+ // Wait for new process to be stable
451
+ await new Promise((resolve) => setTimeout(resolve, 2000));
452
+
453
+ // Kill old process
454
+ if (oldProcess && oldPid) {
455
+ try {
456
+ if (this.config.treekill !== false) {
457
+ await treeKill(oldPid, "SIGTERM");
458
+ } else {
459
+ oldProcess.kill("SIGTERM" as any);
460
+ }
461
+ } catch {}
462
+ }
463
+
464
+ this.isRestarting = false;
465
+ }
466
+
467
+ async sendSignal(signal: string): Promise<void> {
468
+ if (this.pid) {
469
+ process.kill(this.pid, signal as any);
470
+ }
471
+ }
472
+
473
+ getState(): ProcessState {
474
+ return {
475
+ id: this.id,
476
+ name: this.name,
477
+ namespace: this.config.namespace,
478
+ status: this.status,
479
+ pid: this.pid,
480
+ pm_id: this.id,
481
+ monit: {
482
+ memory: this.memory,
483
+ cpu: this.cpu,
484
+ handles: this.handles,
485
+ eventLoopLatency: this.eventLoopLatency,
486
+ },
487
+ pm2_env: {
488
+ ...this.config,
489
+ status: this.status,
490
+ pm_uptime: this.startedAt,
491
+ restart_time: this.restartCount,
492
+ unstable_restarts: this.unstableRestarts,
493
+ created_at: this.createdAt,
494
+ pm_id: this.id,
495
+ axm_monitor: this.axmMonitor,
496
+ },
497
+ };
498
+ }
499
+
500
+ toJSON() {
501
+ return {
502
+ id: this.id,
503
+ name: this.name,
504
+ config: this.config,
505
+ restartCount: this.restartCount,
506
+ };
507
+ }
508
+ }