bgrun 3.10.1 → 3.11.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/README.md +76 -2
- package/dashboard/app/api/deps/route.ts +49 -0
- package/dashboard/app/api/guard/route.ts +50 -0
- package/dashboard/app/api/guard-all/route.ts +50 -0
- package/dashboard/app/api/logs/rotate/route.ts +45 -0
- package/dashboard/app/api/processes/route.ts +67 -10
- package/dashboard/app/globals.css +386 -6
- package/dashboard/app/page.client.tsx +257 -8
- package/dashboard/app/page.tsx +20 -1
- package/dist/index.js +462 -30
- package/package.json +61 -60
- package/src/api.ts +3 -3
- package/src/commands/list.ts +3 -3
- package/src/commands/run.ts +17 -0
- package/src/db.ts +8 -0
- package/src/deps.ts +126 -0
- package/src/guard.ts +157 -0
- package/src/index.ts +108 -3
- package/src/log-rotation.ts +93 -0
- package/src/logger.ts +4 -3
- package/src/platform.ts +39 -23
- package/src/server.ts +55 -11
package/README.md
CHANGED
|
@@ -75,7 +75,8 @@ Features:
|
|
|
75
75
|
- Real-time process status via SSE (no polling)
|
|
76
76
|
- Start, stop, restart, and delete processes from the UI
|
|
77
77
|
- Live stdout/stderr log viewer with search
|
|
78
|
-
- Memory, PID, port, and
|
|
78
|
+
- Memory, PID, port, runtime, and guard restarts at a glance
|
|
79
|
+
- Guard toggle per-process (auto-restart on crash)
|
|
79
80
|
- Responsive mobile layout
|
|
80
81
|
- Collapsible directory groups
|
|
81
82
|
|
|
@@ -91,6 +92,9 @@ Features:
|
|
|
91
92
|
- [Caddy Reverse Proxy](#caddy-reverse-proxy)
|
|
92
93
|
- [TOML Configuration](#toml-configuration)
|
|
93
94
|
- [Programmatic API](#programmatic-api)
|
|
95
|
+
- [Process Dependencies](#process-dependencies)
|
|
96
|
+
- [Log Rotation](#log-rotation)
|
|
97
|
+
- [Guard (Auto-Restart)](#guard-auto-restart)
|
|
94
98
|
- [Migrating from PM2](#migrating-from-pm2)
|
|
95
99
|
- [Edge Cases & Behaviors](#edge-cases--behaviors)
|
|
96
100
|
- [Full CLI Reference](#full-cli-reference)
|
|
@@ -555,12 +559,80 @@ bgrun --name worker --directory ./workers --command "node worker.js" --force
|
|
|
555
559
|
WantedBy=multi-user.target
|
|
556
560
|
```
|
|
557
561
|
|
|
558
|
-
3. **
|
|
562
|
+
3. **Built-in log rotation** — bgrun automatically rotates logs when they exceed 10MB, keeping the last 5000 lines. Manual rotation is also available via the dashboard API.
|
|
559
563
|
|
|
560
564
|
4. **Bun required** — bgrun runs on Bun, but the *processes it manages* can be anything: Node.js, Python, Ruby, Go, Docker, shell scripts.
|
|
561
565
|
|
|
562
566
|
---
|
|
563
567
|
|
|
568
|
+
## Process Dependencies
|
|
569
|
+
|
|
570
|
+
bgrun supports declaring process startup dependencies via the `BGR_DEPENDS_ON` environment variable:
|
|
571
|
+
|
|
572
|
+
```bash
|
|
573
|
+
# Start a price listener (no dependencies)
|
|
574
|
+
bgrun --name price-feed --command "bun run sqd.ts" --force
|
|
575
|
+
|
|
576
|
+
# Start a worker that depends on the price feed
|
|
577
|
+
BGR_DEPENDS_ON=price-feed bgrun --name mm-worker --command "bun run worker.ts" --force
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
When you start `mm-worker`, bgrun will automatically start `price-feed` first if it's not already running.
|
|
581
|
+
|
|
582
|
+
### Features
|
|
583
|
+
|
|
584
|
+
- **Topological sort** — processes start in correct dependency order
|
|
585
|
+
- **Cycle detection** — bgrun warns if dependencies form a cycle
|
|
586
|
+
- **Auto-start** — unmet dependencies are started automatically
|
|
587
|
+
- **API** — `GET /api/deps` returns the full dependency graph with startup order
|
|
588
|
+
|
|
589
|
+
### Setting dependencies via API
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
curl -X POST http://localhost:3001/api/deps \
|
|
593
|
+
-H 'Content-Type: application/json' \
|
|
594
|
+
-d '{"name": "mm-worker", "dependsOn": ["price-feed", "redis"]}'
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
## Log Rotation
|
|
600
|
+
|
|
601
|
+
bgrun includes automatic log rotation to prevent unbounded log file growth:
|
|
602
|
+
|
|
603
|
+
- **Size-based rotation**: Files exceeding 10MB are truncated, keeping the last 5000 lines
|
|
604
|
+
- **Automatic checks**: Rotation runs every 60 seconds alongside the dashboard
|
|
605
|
+
- **Rotation header**: Rotated files include a timestamp header for auditability
|
|
606
|
+
- **Manual rotation**: Trigger via the dashboard API
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
# Check log sizes
|
|
610
|
+
curl http://localhost:3001/api/logs/rotate
|
|
611
|
+
|
|
612
|
+
# Force rotation now
|
|
613
|
+
curl -X POST http://localhost:3001/api/logs/rotate
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## Guard (Auto-Restart)
|
|
619
|
+
|
|
620
|
+
bgrun includes a standalone guard process that monitors and auto-restarts crashed processes:
|
|
621
|
+
|
|
622
|
+
```bash
|
|
623
|
+
# Enable guard for a process
|
|
624
|
+
BGR_KEEP_ALIVE=true bgrun --name my-api --command "bun run server.ts" --force
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
The guard checks processes every 30 seconds and restarts any that have stopped. Features:
|
|
628
|
+
|
|
629
|
+
- **Exponential backoff** — avoids restart storms for processes that keep crashing
|
|
630
|
+
- **Per-process toggle** — enable/disable guard via the dashboard shield icon
|
|
631
|
+
- **Guard sentinel** — dashboard shows a pulsing green dot when the guard is active
|
|
632
|
+
- **Restart counter** — dashboard tracks total guard restarts across all processes
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
564
636
|
## Edge Cases & Behaviors
|
|
565
637
|
|
|
566
638
|
### What happens when a process crashes?
|
|
@@ -702,6 +774,8 @@ All state lives in `~/.bgr/`. To reset everything, delete this directory.
|
|
|
702
774
|
|----------|-------------|---------|
|
|
703
775
|
| `DB_NAME` | Custom database file name | `bgr` |
|
|
704
776
|
| `BGR_GROUP` | Assign process to a group | *(none)* |
|
|
777
|
+
| `BGR_KEEP_ALIVE` | Enable guard auto-restart for this process | `false` |
|
|
778
|
+
| `BGR_DEPENDS_ON` | Comma-separated list of process dependencies | *(none)* |
|
|
705
779
|
| `BUN_PORT` | Dashboard port (explicit, no fallback) | *(auto: 3000+)* |
|
|
706
780
|
|
|
707
781
|
---
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/deps — Get the process dependency graph
|
|
3
|
+
* POST /api/deps — Set dependencies for a process
|
|
4
|
+
* Body: { name: string, dependsOn: string[] }
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getProcess, updateProcessEnv } from '../../../../src/db'
|
|
8
|
+
import { buildDepGraph } from '../../../../src/deps'
|
|
9
|
+
import { parseEnvString } from '../../../../src/utils'
|
|
10
|
+
|
|
11
|
+
export async function GET() {
|
|
12
|
+
const graph = await buildDepGraph()
|
|
13
|
+
return Response.json(graph)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function POST(req: Request) {
|
|
17
|
+
try {
|
|
18
|
+
const body = await req.json() as { name: string; dependsOn: string[] }
|
|
19
|
+
if (!body.name) {
|
|
20
|
+
return Response.json({ error: 'Missing process name' }, { status: 400 })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const proc = getProcess(body.name)
|
|
24
|
+
if (!proc) {
|
|
25
|
+
return Response.json({ error: `Process "${body.name}" not found` }, { status: 404 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Parse existing env
|
|
29
|
+
let env: Record<string, string> = {}
|
|
30
|
+
try { env = JSON.parse(proc.env) } catch { env = parseEnvString(proc.env) }
|
|
31
|
+
|
|
32
|
+
// Update dependencies
|
|
33
|
+
if (body.dependsOn && body.dependsOn.length > 0) {
|
|
34
|
+
env.BGR_DEPENDS_ON = body.dependsOn.join(',')
|
|
35
|
+
} else {
|
|
36
|
+
delete env.BGR_DEPENDS_ON
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
updateProcessEnv(body.name, JSON.stringify(env))
|
|
40
|
+
|
|
41
|
+
return Response.json({
|
|
42
|
+
ok: true,
|
|
43
|
+
name: body.name,
|
|
44
|
+
dependsOn: body.dependsOn || [],
|
|
45
|
+
})
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
return Response.json({ error: err.message }, { status: 500 })
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/guard — Toggle BGR_KEEP_ALIVE for a process
|
|
3
|
+
* Body: { name: string, enabled: boolean }
|
|
4
|
+
*
|
|
5
|
+
* When enabled=true, the built-in guard will auto-restart this process if it dies.
|
|
6
|
+
* When enabled=false, the process is left alone.
|
|
7
|
+
*/
|
|
8
|
+
import { getProcess, updateProcessEnv } from '../../../../src/db';
|
|
9
|
+
|
|
10
|
+
export async function POST(req: Request) {
|
|
11
|
+
try {
|
|
12
|
+
const body = await req.json() as { name: string; enabled: boolean };
|
|
13
|
+
if (!body.name) {
|
|
14
|
+
return Response.json({ error: 'Missing process name' }, { status: 400 });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const proc = getProcess(body.name);
|
|
18
|
+
if (!proc) {
|
|
19
|
+
return Response.json({ error: `Process "${body.name}" not found` }, { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Parse existing env
|
|
23
|
+
let env: Record<string, string> = {};
|
|
24
|
+
if (proc.env) {
|
|
25
|
+
try { env = JSON.parse(proc.env); } catch { env = {}; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Toggle BGR_KEEP_ALIVE
|
|
29
|
+
if (body.enabled) {
|
|
30
|
+
env.BGR_KEEP_ALIVE = 'true';
|
|
31
|
+
} else {
|
|
32
|
+
delete env.BGR_KEEP_ALIVE;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Save back
|
|
36
|
+
updateProcessEnv(body.name, JSON.stringify(env));
|
|
37
|
+
|
|
38
|
+
return Response.json({
|
|
39
|
+
ok: true,
|
|
40
|
+
name: body.name,
|
|
41
|
+
guarded: body.enabled
|
|
42
|
+
});
|
|
43
|
+
} catch (err: any) {
|
|
44
|
+
return Response.json({ error: err.message }, { status: 500 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function GET() {
|
|
49
|
+
return Response.json({ error: 'Use POST to toggle guard' }, { status: 405 });
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/guard-all — Bulk toggle guard for all processes
|
|
3
|
+
* Body: { enabled: boolean }
|
|
4
|
+
*
|
|
5
|
+
* When enabled=true, sets BGR_KEEP_ALIVE=true for ALL processes (except bgr-dashboard).
|
|
6
|
+
* When enabled=false, removes BGR_KEEP_ALIVE from ALL processes.
|
|
7
|
+
*/
|
|
8
|
+
import { getAllProcesses, getProcess, updateProcessEnv } from '../../../../src/db';
|
|
9
|
+
|
|
10
|
+
const SKIP = new Set(['bgr-dashboard']);
|
|
11
|
+
|
|
12
|
+
export async function POST(req: Request) {
|
|
13
|
+
try {
|
|
14
|
+
const body = await req.json() as { enabled: boolean };
|
|
15
|
+
const processes = getAllProcesses();
|
|
16
|
+
let count = 0;
|
|
17
|
+
|
|
18
|
+
for (const proc of processes) {
|
|
19
|
+
if (SKIP.has(proc.name)) continue;
|
|
20
|
+
|
|
21
|
+
// Parse existing env
|
|
22
|
+
let env: Record<string, string> = {};
|
|
23
|
+
if (proc.env) {
|
|
24
|
+
try { env = JSON.parse(proc.env); } catch { env = {}; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const alreadyGuarded = env.BGR_KEEP_ALIVE === 'true';
|
|
28
|
+
if (body.enabled && alreadyGuarded) continue;
|
|
29
|
+
if (!body.enabled && !alreadyGuarded) continue;
|
|
30
|
+
|
|
31
|
+
if (body.enabled) {
|
|
32
|
+
env.BGR_KEEP_ALIVE = 'true';
|
|
33
|
+
} else {
|
|
34
|
+
delete env.BGR_KEEP_ALIVE;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
updateProcessEnv(proc.name, JSON.stringify(env));
|
|
38
|
+
count++;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return Response.json({
|
|
42
|
+
ok: true,
|
|
43
|
+
action: body.enabled ? 'guarded' : 'unguarded',
|
|
44
|
+
count,
|
|
45
|
+
total: processes.length,
|
|
46
|
+
});
|
|
47
|
+
} catch (err: any) {
|
|
48
|
+
return Response.json({ error: err.message }, { status: 500 });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/logs/rotate — Rotate all log files
|
|
3
|
+
* GET /api/logs/rotate — Get log file sizes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getAllProcesses } from '../../../../../src/db'
|
|
7
|
+
import { rotateAllLogs } from '../../../../../src/log-rotation'
|
|
8
|
+
import { existsSync, statSync } from 'fs'
|
|
9
|
+
|
|
10
|
+
export function POST() {
|
|
11
|
+
const result = rotateAllLogs(() => getAllProcesses())
|
|
12
|
+
return Response.json({
|
|
13
|
+
ok: true,
|
|
14
|
+
rotated: result.rotated,
|
|
15
|
+
checked: result.checked,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function GET() {
|
|
20
|
+
const processes = getAllProcesses()
|
|
21
|
+
const files: Array<{ name: string; type: string; path: string; sizeBytes: number; sizeMB: string }> = []
|
|
22
|
+
|
|
23
|
+
for (const proc of processes) {
|
|
24
|
+
for (const [type, path] of [['stdout', proc.stdout_path], ['stderr', proc.stderr_path]] as const) {
|
|
25
|
+
if (path && existsSync(path)) {
|
|
26
|
+
const stat = statSync(path)
|
|
27
|
+
files.push({
|
|
28
|
+
name: proc.name,
|
|
29
|
+
type,
|
|
30
|
+
path,
|
|
31
|
+
sizeBytes: stat.size,
|
|
32
|
+
sizeMB: (stat.size / 1_000_000).toFixed(2),
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const totalBytes = files.reduce((sum, f) => sum + f.sizeBytes, 0)
|
|
39
|
+
|
|
40
|
+
return Response.json({
|
|
41
|
+
files,
|
|
42
|
+
totalBytes,
|
|
43
|
+
totalMB: (totalBytes / 1_000_000).toFixed(2),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getAllProcesses, updateProcessPid } from '../../../../src/db';
|
|
2
2
|
import { calculateRuntime } from '../../../../src/utils';
|
|
3
|
-
import {
|
|
3
|
+
import { getProcessBatchResources, reconcileProcessPids } from '../../../../src/platform';
|
|
4
|
+
import { guardRestartCounts } from '../../../../src/server';
|
|
4
5
|
import { measure, createMeasure } from 'measure-fn';
|
|
5
6
|
import { $ } from 'bun';
|
|
6
7
|
|
|
@@ -14,7 +15,12 @@ const g = globalThis as any;
|
|
|
14
15
|
if (!g.__bgrProcessCache) {
|
|
15
16
|
g.__bgrProcessCache = { data: null, timestamp: 0, inflight: null };
|
|
16
17
|
}
|
|
18
|
+
if (!g.__bgrResourceHistory) {
|
|
19
|
+
// Map of process name -> { memory: number[], cpu: number[], lastCpuTime: number, lastCheck: number }
|
|
20
|
+
g.__bgrResourceHistory = new Map<string, { memory: number[], cpu: number[], lastCpuTime: number, lastCheck: number }>();
|
|
21
|
+
}
|
|
17
22
|
const cache = g.__bgrProcessCache;
|
|
23
|
+
const history = g.__bgrResourceHistory;
|
|
18
24
|
|
|
19
25
|
function withTimeout<T>(promise: Promise<T>, fallback: T): Promise<T> {
|
|
20
26
|
return Promise.race([
|
|
@@ -109,10 +115,10 @@ async function fetchProcesses(): Promise<any[]> {
|
|
|
109
115
|
const pids = procs.map((p: any) => p.pid);
|
|
110
116
|
|
|
111
117
|
// Three subprocess calls total (not 3×N)
|
|
112
|
-
let [runningPids, portMap,
|
|
118
|
+
let [runningPids, portMap, resourceMap] = await Promise.all([
|
|
113
119
|
m('Running PIDs', () => withTimeout(getRunningPids(pids), new Set<number>())),
|
|
114
120
|
m('Port map', () => withTimeout(getPortsByPid(pids), new Map<number, number[]>())),
|
|
115
|
-
m('
|
|
121
|
+
m('Resource map', () => withTimeout(getProcessBatchResources(pids), new Map<number, { memory: number, cpu: number }>())),
|
|
116
122
|
]);
|
|
117
123
|
|
|
118
124
|
// PID reconciliation: if stored PIDs are dead, try to find the real process
|
|
@@ -139,22 +145,69 @@ async function fetchProcesses(): Promise<any[]> {
|
|
|
139
145
|
if (!runningPids) runningPids = new Set();
|
|
140
146
|
for (const pid of newPids) runningPids.add(pid);
|
|
141
147
|
|
|
142
|
-
// Re-fetch ports and
|
|
143
|
-
const [newPorts,
|
|
148
|
+
// Re-fetch ports and resources for the new PIDs
|
|
149
|
+
const [newPorts, newResources] = await Promise.all([
|
|
144
150
|
withTimeout(getPortsByPid(newPids), new Map<number, number[]>()),
|
|
145
|
-
withTimeout(
|
|
151
|
+
withTimeout(getProcessBatchResources(newPids), new Map<number, { memory: number, cpu: number }>()),
|
|
146
152
|
]);
|
|
147
153
|
if (!portMap) portMap = new Map();
|
|
148
|
-
if (!
|
|
154
|
+
if (!resourceMap) resourceMap = new Map();
|
|
149
155
|
for (const [pid, ports] of newPorts) portMap.set(pid, ports);
|
|
150
|
-
for (const [pid,
|
|
156
|
+
for (const [pid, res] of newResources) resourceMap.set(pid, res);
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
const isWin = process.platform === 'win32';
|
|
162
|
+
|
|
154
163
|
return procs.map((p: any) => {
|
|
155
164
|
const running = runningPids?.has(p.pid) ?? false;
|
|
156
165
|
const ports = running ? (portMap?.get(p.pid) || []) : [];
|
|
157
|
-
const
|
|
166
|
+
const res = running ? (resourceMap?.get(p.pid) || { memory: 0, cpu: 0 }) : { memory: 0, cpu: 0 };
|
|
167
|
+
|
|
168
|
+
// Manage history tracking for sparklines (up to 60 points = 5 minutes at 5s polling)
|
|
169
|
+
let h = history.get(p.name);
|
|
170
|
+
if (!h) {
|
|
171
|
+
h = { memory: [], cpu: [], lastCpuTime: 0, lastCheck: 0 };
|
|
172
|
+
history.set(p.name, h);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let cpuPercent = 0;
|
|
176
|
+
if (running) {
|
|
177
|
+
if (isWin) {
|
|
178
|
+
// Windows: cpu is cumulative seconds. Calculate delta percentage across time.
|
|
179
|
+
if (h.lastCheck > 0 && h.lastCpuTime > 0) {
|
|
180
|
+
const timeDeltaSec = (now - h.lastCheck) / 1000;
|
|
181
|
+
const cpuDeltaSec = res.cpu - h.lastCpuTime;
|
|
182
|
+
if (timeDeltaSec > 0 && cpuDeltaSec >= 0) {
|
|
183
|
+
// Max it at 100% per core? We'll just display the total usage
|
|
184
|
+
cpuPercent = (cpuDeltaSec / timeDeltaSec) * 100;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
h.lastCpuTime = res.cpu;
|
|
188
|
+
} else {
|
|
189
|
+
// Unix: it's already a percentage from \`ps\`
|
|
190
|
+
cpuPercent = res.cpu;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add points
|
|
194
|
+
h.memory.push(res.memory);
|
|
195
|
+
h.cpu.push(cpuPercent);
|
|
196
|
+
if (h.memory.length > 60) h.memory.shift();
|
|
197
|
+
if (h.cpu.length > 60) h.cpu.shift();
|
|
198
|
+
} else {
|
|
199
|
+
// Not running, push zeros if not already zeroed out to bring graphs down
|
|
200
|
+
if (h.memory.length > 0 && h.memory[h.memory.length - 1] !== 0) {
|
|
201
|
+
h.memory.push(0);
|
|
202
|
+
h.cpu.push(0);
|
|
203
|
+
if (h.memory.length > 60) h.memory.shift();
|
|
204
|
+
if (h.cpu.length > 60) h.cpu.shift();
|
|
205
|
+
}
|
|
206
|
+
h.lastCheck = 0;
|
|
207
|
+
h.lastCpuTime = 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (running) h.lastCheck = now;
|
|
158
211
|
|
|
159
212
|
return {
|
|
160
213
|
name: p.name,
|
|
@@ -164,7 +217,10 @@ async function fetchProcesses(): Promise<any[]> {
|
|
|
164
217
|
running,
|
|
165
218
|
port: ports.length > 0 ? ports[0] : null,
|
|
166
219
|
ports,
|
|
167
|
-
memory, // Bytes
|
|
220
|
+
memory: res.memory, // Bytes
|
|
221
|
+
cpu: cpuPercent, // Percentage
|
|
222
|
+
memoryHistory: [...h.memory],
|
|
223
|
+
cpuHistory: [...h.cpu],
|
|
168
224
|
group: getProcessGroup(p.env),
|
|
169
225
|
runtime: calculateRuntime(p.timestamp),
|
|
170
226
|
timestamp: p.timestamp,
|
|
@@ -172,6 +228,7 @@ async function fetchProcesses(): Promise<any[]> {
|
|
|
172
228
|
configPath: p.configPath || '',
|
|
173
229
|
stdoutPath: p.stdout_path || '',
|
|
174
230
|
stderrPath: p.stderr_path || '',
|
|
231
|
+
guardRestarts: guardRestartCounts.get(p.name) || 0,
|
|
175
232
|
};
|
|
176
233
|
});
|
|
177
234
|
}) ?? [];
|