process-watchdog 1.0.0 → 1.1.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 +115 -0
- package/dashboard/watchdog.html +406 -0
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +45 -0
- package/dist/api/routes.js.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/index.js +7 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/linux.d.ts +14 -0
- package/dist/platform/linux.d.ts.map +1 -0
- package/dist/platform/linux.js +235 -0
- package/dist/platform/linux.js.map +1 -0
- package/dist/platform/macos.d.ts +14 -0
- package/dist/platform/macos.d.ts.map +1 -0
- package/dist/platform/macos.js +255 -0
- package/dist/platform/macos.js.map +1 -0
- package/dist/plugins/plugin-loader.d.ts +1 -1
- package/dist/plugins/plugin-loader.d.ts.map +1 -1
- package/dist/plugins/plugin-loader.js +46 -1
- package/dist/plugins/plugin-loader.js.map +1 -1
- package/dist/plugins/process-guard.d.ts.map +1 -1
- package/dist/plugins/process-guard.js +0 -15
- package/dist/plugins/process-guard.js.map +1 -1
- package/package.json +1 -1
- package/src/api/routes.ts +56 -0
- package/src/index.ts +5 -5
- package/src/platform/index.ts +7 -1
- package/src/platform/linux.ts +255 -0
- package/src/platform/macos.ts +259 -0
- package/src/plugins/plugin-loader.ts +66 -1
- package/src/plugins/process-guard.ts +0 -15
- package/tests/plugins/cpu-monitor.test.ts +5 -5
- package/tests/plugins/disk-health.test.ts +1 -1
- package/tests/plugins/fixtures/broken-plugin.js +5 -0
- package/tests/plugins/fixtures/custom-plugin.js +36 -0
- package/tests/plugins/fixtures/named-plugin.js +31 -0
- package/tests/plugins/plugin-loader.test.ts +124 -7
- package/tests/plugins/process-guard.test.ts +1 -1
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
1
5
|
import { ProcessGuardPlugin } from './process-guard.js';
|
|
2
6
|
import { MemoryMonitorPlugin } from './memory-monitor.js';
|
|
3
7
|
import { DiskHealthPlugin } from './disk-health.js';
|
|
@@ -10,8 +14,9 @@ const pluginRegistry = {
|
|
|
10
14
|
'startup-optimizer': StartupOptimizerPlugin,
|
|
11
15
|
'cpu-monitor': CpuMonitorPlugin,
|
|
12
16
|
};
|
|
13
|
-
export function loadPlugins(config) {
|
|
17
|
+
export async function loadPlugins(config, customPluginDir) {
|
|
14
18
|
const plugins = [];
|
|
19
|
+
// Load built-in plugins from the registry
|
|
15
20
|
for (const [name, PluginClass] of Object.entries(pluginRegistry)) {
|
|
16
21
|
const pluginConfig = config.plugins[name];
|
|
17
22
|
if (!pluginConfig || !pluginConfig.enabled)
|
|
@@ -20,6 +25,46 @@ export function loadPlugins(config) {
|
|
|
20
25
|
plugin.configure(pluginConfig);
|
|
21
26
|
plugins.push(plugin);
|
|
22
27
|
}
|
|
28
|
+
// Load custom plugins from ~/.aidev/watchdog-plugins/ (or override for testing)
|
|
29
|
+
const resolvedCustomDir = customPluginDir ?? join(os.homedir(), '.aidev', 'watchdog-plugins');
|
|
30
|
+
if (existsSync(resolvedCustomDir)) {
|
|
31
|
+
let entries;
|
|
32
|
+
try {
|
|
33
|
+
entries = readdirSync(resolvedCustomDir).filter((f) => f.endsWith('.js'));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
entries = [];
|
|
37
|
+
}
|
|
38
|
+
for (const filename of entries) {
|
|
39
|
+
const filePath = join(resolvedCustomDir, filename);
|
|
40
|
+
try {
|
|
41
|
+
// Dynamic import to support both `export default` and `export { Plugin }`
|
|
42
|
+
const mod = await import(filePath);
|
|
43
|
+
const PluginClass = mod.default ?? mod.Plugin;
|
|
44
|
+
if (typeof PluginClass !== 'function') {
|
|
45
|
+
console.warn(`[watchdog] Custom plugin "${filename}" has no default or Plugin export — skipping.`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const plugin = new PluginClass();
|
|
49
|
+
if (typeof plugin.name !== 'string' ||
|
|
50
|
+
typeof plugin.check !== 'function' ||
|
|
51
|
+
typeof plugin.fix !== 'function' ||
|
|
52
|
+
typeof plugin.configure !== 'function') {
|
|
53
|
+
console.warn(`[watchdog] Custom plugin "${filename}" does not implement WatchdogPlugin — skipping.`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const pluginConfig = config.plugins[plugin.name];
|
|
57
|
+
if (pluginConfig) {
|
|
58
|
+
plugin.configure(pluginConfig);
|
|
59
|
+
}
|
|
60
|
+
plugins.push(plugin);
|
|
61
|
+
console.log(`[watchdog] Loaded custom plugin: ${plugin.name} (${filename})`);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`[watchdog] Failed to load custom plugin "${filename}": ${err.message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
23
68
|
return plugins;
|
|
24
69
|
}
|
|
25
70
|
//# sourceMappingURL=plugin-loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../src/plugins/plugin-loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../src/plugins/plugin-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-guard.d.ts","sourceRoot":"","sources":["../../src/plugins/process-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAe7F,qBAAa,kBAAmB,YAAW,cAAc;IACvD,IAAI,SAAmB;IACvB,WAAW,SAA+D;IAC1E,eAAe,SAAU;IACzB,OAAO,CAAC,MAAM,CAKZ;IAEF,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAI7B,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAwD7B,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"process-guard.d.ts","sourceRoot":"","sources":["../../src/plugins/process-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAe7F,qBAAa,kBAAmB,YAAW,cAAc;IACvD,IAAI,SAAmB;IACvB,WAAW,SAA+D;IAC1E,eAAe,SAAU;IACzB,OAAO,CAAC,MAAM,CAKZ;IAEF,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAI7B,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAwD7B,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;CA+DhC"}
|
|
@@ -103,21 +103,6 @@ export class ProcessGuardPlugin {
|
|
|
103
103
|
actionsKept.push(`Killed ${killed}/${orphans.length} orphaned ${name} processes`);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
// Best-effort: clean up low-CPU conhost processes (skip any that were running at snapshot time)
|
|
107
|
-
try {
|
|
108
|
-
const conhosts = await safeGetProcessesByName(platform, 'conhost');
|
|
109
|
-
for (const proc of conhosts) {
|
|
110
|
-
if (proc.cpu < 0.1 && !protectedPids.has(proc.pid)) {
|
|
111
|
-
const success = await platform.killProcess(proc.pid);
|
|
112
|
-
if (success) {
|
|
113
|
-
totalMemoryFreedMB += proc.memoryMB ?? 0;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
// best-effort, ignore errors
|
|
120
|
-
}
|
|
121
106
|
// Capture after metrics (best-effort — system may be in transition after kills)
|
|
122
107
|
let afterListeningPids;
|
|
123
108
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-guard.js","sourceRoot":"","sources":["../../src/plugins/process-guard.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"process-guard.js","sourceRoot":"","sources":["../../src/plugins/process-guard.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/api/routes.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import os from 'os';
|
|
2
5
|
import { HistoryStore } from '../store/history.js';
|
|
3
6
|
import { WatchdogPlugin, HealthStatus } from '../plugins/plugin.interface.js';
|
|
4
7
|
import { WatchdogConfig } from '../config.js';
|
|
@@ -119,5 +122,58 @@ export function createRoutes(
|
|
|
119
122
|
res.json(config);
|
|
120
123
|
});
|
|
121
124
|
|
|
125
|
+
// PUT /config — deep-merge partial config and persist to ~/.aidev/watchdog.json
|
|
126
|
+
router.put('/config', async (req, res) => {
|
|
127
|
+
const body = req.body;
|
|
128
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
129
|
+
res.status(400).json({ error: 'Request body must be a JSON object' });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
// Deep-merge the incoming partial config into the live config object
|
|
135
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
136
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function deepMerge(
|
|
140
|
+
target: Record<string, unknown>,
|
|
141
|
+
source: Record<string, unknown>,
|
|
142
|
+
): Record<string, unknown> {
|
|
143
|
+
const result: Record<string, unknown> = { ...target };
|
|
144
|
+
for (const key of Object.keys(source)) {
|
|
145
|
+
const src = source[key];
|
|
146
|
+
const tgt = result[key];
|
|
147
|
+
if (isPlainObject(src) && isPlainObject(tgt)) {
|
|
148
|
+
result[key] = deepMerge(tgt, src);
|
|
149
|
+
} else {
|
|
150
|
+
result[key] = src;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const merged = deepMerge(
|
|
157
|
+
config as unknown as Record<string, unknown>,
|
|
158
|
+
body as Record<string, unknown>,
|
|
159
|
+
) as unknown as WatchdogConfig;
|
|
160
|
+
|
|
161
|
+
// Apply the merged values back onto the live config object so GET /config
|
|
162
|
+
// reflects the change immediately (without restart)
|
|
163
|
+
Object.assign(config, merged);
|
|
164
|
+
|
|
165
|
+
// Persist user overrides to ~/.aidev/watchdog.json
|
|
166
|
+
const aidevDir = join(os.homedir(), '.aidev');
|
|
167
|
+
await mkdir(aidevDir, { recursive: true });
|
|
168
|
+
const userConfigPath = join(aidevDir, 'watchdog.json');
|
|
169
|
+
await writeFile(userConfigPath, JSON.stringify(body, null, 2), 'utf-8');
|
|
170
|
+
|
|
171
|
+
res.json(config);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
174
|
+
res.status(500).json({ error: message });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
122
178
|
return router;
|
|
123
179
|
}
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ program
|
|
|
23
23
|
.description('Load config, start scheduler, and watch continuously')
|
|
24
24
|
.action(async () => {
|
|
25
25
|
const config = await loadConfig();
|
|
26
|
-
const plugins = loadPlugins(config);
|
|
26
|
+
const plugins = await loadPlugins(config);
|
|
27
27
|
|
|
28
28
|
const home = process.env.HOME || process.env.USERPROFILE || '.';
|
|
29
29
|
const aidevDir = join(home, '.aidev');
|
|
@@ -58,7 +58,7 @@ program
|
|
|
58
58
|
.description('Run check() on each plugin and print a summary line')
|
|
59
59
|
.action(async () => {
|
|
60
60
|
const config = await loadConfig();
|
|
61
|
-
const plugins = loadPlugins(config);
|
|
61
|
+
const plugins = await loadPlugins(config);
|
|
62
62
|
|
|
63
63
|
for (const plugin of plugins) {
|
|
64
64
|
try {
|
|
@@ -84,7 +84,7 @@ program
|
|
|
84
84
|
.description('Run check() for all or a specific plugin and print detailed metrics')
|
|
85
85
|
.action(async (pluginArg?: string) => {
|
|
86
86
|
const config = await loadConfig();
|
|
87
|
-
const plugins = loadPlugins(config);
|
|
87
|
+
const plugins = await loadPlugins(config);
|
|
88
88
|
|
|
89
89
|
const targets = pluginArg
|
|
90
90
|
? plugins.filter((p) => p.name === pluginArg)
|
|
@@ -125,7 +125,7 @@ program
|
|
|
125
125
|
.description('Run fix() for all or a specific plugin and print actions and resources freed')
|
|
126
126
|
.action(async (pluginArg?: string) => {
|
|
127
127
|
const config = await loadConfig();
|
|
128
|
-
const plugins = loadPlugins(config);
|
|
128
|
+
const plugins = await loadPlugins(config);
|
|
129
129
|
|
|
130
130
|
const targets = pluginArg
|
|
131
131
|
? plugins.filter((p) => p.name === pluginArg)
|
|
@@ -171,7 +171,7 @@ apiCmd
|
|
|
171
171
|
.description('Start the HTTP API server')
|
|
172
172
|
.action(async () => {
|
|
173
173
|
const config = await loadConfig();
|
|
174
|
-
const plugins = loadPlugins(config);
|
|
174
|
+
const plugins = await loadPlugins(config);
|
|
175
175
|
|
|
176
176
|
const home = process.env.HOME || process.env.USERPROFILE || '.';
|
|
177
177
|
const aidevDir = join(home, '.aidev');
|
package/src/platform/index.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { PlatformAdapter } from './platform.interface.js';
|
|
2
2
|
import { WindowsPlatform } from './windows.js';
|
|
3
|
+
import { MacOSPlatform } from './macos.js';
|
|
4
|
+
import { LinuxPlatform } from './linux.js';
|
|
3
5
|
|
|
4
6
|
export function getPlatform(): PlatformAdapter {
|
|
5
7
|
switch (process.platform) {
|
|
6
8
|
case 'win32':
|
|
7
9
|
return new WindowsPlatform();
|
|
10
|
+
case 'darwin':
|
|
11
|
+
return new MacOSPlatform();
|
|
12
|
+
case 'linux':
|
|
13
|
+
return new LinuxPlatform();
|
|
8
14
|
default:
|
|
9
|
-
throw new Error(`Unsupported platform: ${process.platform}
|
|
15
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
10
16
|
}
|
|
11
17
|
}
|
|
12
18
|
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import type {
|
|
6
|
+
PlatformAdapter,
|
|
7
|
+
ProcessInfo,
|
|
8
|
+
MemoryInfo,
|
|
9
|
+
DiskInfo,
|
|
10
|
+
CpuSample,
|
|
11
|
+
StartupItem,
|
|
12
|
+
} from './platform.interface.js';
|
|
13
|
+
|
|
14
|
+
function run(cmd: string, args: string[], timeoutMs = 60000): string {
|
|
15
|
+
return execFileSync(cmd, args, { encoding: 'utf-8', timeout: timeoutMs }).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Read /proc/stat and return raw CPU counters for all CPUs combined (first line). */
|
|
19
|
+
function readProcStatCpu(): {
|
|
20
|
+
user: number;
|
|
21
|
+
nice: number;
|
|
22
|
+
system: number;
|
|
23
|
+
idle: number;
|
|
24
|
+
iowait: number;
|
|
25
|
+
irq: number;
|
|
26
|
+
softirq: number;
|
|
27
|
+
total: number;
|
|
28
|
+
} {
|
|
29
|
+
const stat = fs.readFileSync('/proc/stat', 'utf-8');
|
|
30
|
+
const cpuLine = stat.split('\n').find((l) => l.startsWith('cpu '));
|
|
31
|
+
if (!cpuLine) throw new Error('Cannot parse /proc/stat');
|
|
32
|
+
// cpu user nice system idle iowait irq softirq steal guest guest_nice
|
|
33
|
+
const parts = cpuLine.trim().split(/\s+/).slice(1).map(Number);
|
|
34
|
+
const [user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0] = parts;
|
|
35
|
+
const total = parts.reduce((a, b) => a + b, 0);
|
|
36
|
+
return { user, nice, system, idle, iowait, irq, softirq, total };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class LinuxPlatform implements PlatformAdapter {
|
|
40
|
+
async getProcesses(): Promise<ProcessInfo[]> {
|
|
41
|
+
// ps aux columns: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
|
42
|
+
const raw = run('ps', ['aux']);
|
|
43
|
+
const lines = raw.split('\n').slice(1); // skip header
|
|
44
|
+
const result: ProcessInfo[] = [];
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
if (!line.trim()) continue;
|
|
47
|
+
const parts = line.trim().split(/\s+/);
|
|
48
|
+
if (parts.length < 11) continue;
|
|
49
|
+
const pid = parseInt(parts[1], 10);
|
|
50
|
+
const cpuPct = parseFloat(parts[2]) || 0;
|
|
51
|
+
const rssKB = parseInt(parts[5], 10) || 0;
|
|
52
|
+
const name = parts.slice(10).join(' ');
|
|
53
|
+
if (isNaN(pid)) continue;
|
|
54
|
+
result.push({
|
|
55
|
+
pid,
|
|
56
|
+
name,
|
|
57
|
+
memoryMB: Math.round(rssKB / 1024),
|
|
58
|
+
cpu: cpuPct,
|
|
59
|
+
responding: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getProcessesByName(name: string): Promise<ProcessInfo[]> {
|
|
66
|
+
// pgrep -a <name> => "<pid> <full command>"
|
|
67
|
+
let pgrepOutput = '';
|
|
68
|
+
try {
|
|
69
|
+
pgrepOutput = run('pgrep', ['-a', name]);
|
|
70
|
+
} catch {
|
|
71
|
+
// pgrep exits non-zero when no matches
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pids: number[] = [];
|
|
76
|
+
for (const line of pgrepOutput.split('\n')) {
|
|
77
|
+
if (!line.trim()) continue;
|
|
78
|
+
const pid = parseInt(line.trim().split(/\s+/)[0], 10);
|
|
79
|
+
if (!isNaN(pid)) pids.push(pid);
|
|
80
|
+
}
|
|
81
|
+
if (pids.length === 0) return [];
|
|
82
|
+
|
|
83
|
+
const allProcs = await this.getProcesses();
|
|
84
|
+
return allProcs.filter((p) => pids.includes(p.pid));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async getListeningPids(): Promise<number[]> {
|
|
88
|
+
// ss -tlnp — shows TCP listening sockets with process info
|
|
89
|
+
// State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
|
|
90
|
+
// LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
|
|
91
|
+
let raw = '';
|
|
92
|
+
try {
|
|
93
|
+
raw = run('ss', ['-tlnp']);
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const pids = new Set<number>();
|
|
98
|
+
for (const line of raw.split('\n').slice(1)) {
|
|
99
|
+
// Extract pid=NNNN from users:((...,pid=NNNN,...))
|
|
100
|
+
const matches = line.matchAll(/pid=(\d+)/g);
|
|
101
|
+
for (const m of matches) {
|
|
102
|
+
const pid = parseInt(m[1], 10);
|
|
103
|
+
if (!isNaN(pid)) pids.add(pid);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Array.from(pids);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async killProcess(pid: number): Promise<boolean> {
|
|
110
|
+
try {
|
|
111
|
+
run('kill', ['-9', String(pid)]);
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getMemoryInfo(): Promise<MemoryInfo> {
|
|
119
|
+
// /proc/meminfo keys are in kB
|
|
120
|
+
const raw = fs.readFileSync('/proc/meminfo', 'utf-8');
|
|
121
|
+
const getValue = (key: string): number => {
|
|
122
|
+
const match = raw.match(new RegExp(`^${key}:\\s+(\\d+)`, 'm'));
|
|
123
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const totalKB = getValue('MemTotal');
|
|
127
|
+
const freeKB = getValue('MemFree');
|
|
128
|
+
const buffersKB = getValue('Buffers');
|
|
129
|
+
const cachedKB = getValue('Cached'); // includes "Cached" but not "SwapCached"
|
|
130
|
+
const sreclaimKB = getValue('SReclaimable');
|
|
131
|
+
const swapTotalKB = getValue('SwapTotal');
|
|
132
|
+
const swapFreeKB = getValue('SwapFree');
|
|
133
|
+
|
|
134
|
+
const totalMB = Math.round(totalKB / 1024);
|
|
135
|
+
// Available memory = free + buffers + cached (page cache) + slab reclaimable
|
|
136
|
+
const availableKB = freeKB + buffersKB + cachedKB + sreclaimKB;
|
|
137
|
+
const freeMB = Math.round(availableKB / 1024);
|
|
138
|
+
const usedMB = totalMB - freeMB;
|
|
139
|
+
const usedPct = totalMB > 0 ? Math.round((usedMB / totalMB) * 100) : 0;
|
|
140
|
+
const pageFileUsageMB = Math.round((swapTotalKB - swapFreeKB) / 1024);
|
|
141
|
+
|
|
142
|
+
return { totalMB, freeMB, usedPct, pageFileUsageMB };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getDiskInfo(): Promise<DiskInfo[]> {
|
|
146
|
+
// df --block-size=G produces sizes in gigabytes (with trailing G suffix)
|
|
147
|
+
let raw = '';
|
|
148
|
+
try {
|
|
149
|
+
raw = run('df', ['--block-size=G']);
|
|
150
|
+
} catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const result: DiskInfo[] = [];
|
|
154
|
+
for (const line of raw.split('\n').slice(1)) {
|
|
155
|
+
if (!line.trim()) continue;
|
|
156
|
+
const parts = line.trim().split(/\s+/);
|
|
157
|
+
if (parts.length < 6) continue;
|
|
158
|
+
// Strip the trailing "G" added by --block-size=G
|
|
159
|
+
const totalGB = parseInt(parts[1].replace('G', ''), 10);
|
|
160
|
+
const usedGB = parseInt(parts[2].replace('G', ''), 10);
|
|
161
|
+
const freeGB = parseInt(parts[3].replace('G', ''), 10);
|
|
162
|
+
const drive = parts[0];
|
|
163
|
+
if (isNaN(totalGB) || totalGB <= 0) continue;
|
|
164
|
+
const usedPct = Math.round((usedGB / totalGB) * 100);
|
|
165
|
+
result.push({ drive, totalGB, freeGB, usedPct });
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async getCpuUsage(samples: number, intervalMs: number): Promise<CpuSample[]> {
|
|
171
|
+
const result: CpuSample[] = [];
|
|
172
|
+
const count = Math.max(1, samples);
|
|
173
|
+
|
|
174
|
+
let prev = readProcStatCpu();
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < count; i++) {
|
|
177
|
+
await new Promise<void>((resolve) => setTimeout(resolve, intervalMs));
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const curr = readProcStatCpu();
|
|
181
|
+
const totalDelta = curr.total - prev.total;
|
|
182
|
+
const idleDelta = curr.idle - prev.idle + (curr.iowait - prev.iowait);
|
|
183
|
+
const usedPct =
|
|
184
|
+
totalDelta > 0 ? Math.round(((totalDelta - idleDelta) / totalDelta) * 100) : 0;
|
|
185
|
+
result.push({ usedPct, timestamp: new Date() });
|
|
186
|
+
prev = curr;
|
|
187
|
+
} catch {
|
|
188
|
+
result.push({ usedPct: 0, timestamp: new Date() });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async getStartupItems(): Promise<StartupItem[]> {
|
|
195
|
+
const autostartDir = path.join(os.homedir(), '.config', 'autostart');
|
|
196
|
+
const items: StartupItem[] = [];
|
|
197
|
+
|
|
198
|
+
let files: string[] = [];
|
|
199
|
+
try {
|
|
200
|
+
files = fs.readdirSync(autostartDir).filter((f) => f.endsWith('.desktop'));
|
|
201
|
+
} catch {
|
|
202
|
+
return items;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const file of files) {
|
|
206
|
+
const fullPath = path.join(autostartDir, file);
|
|
207
|
+
let name = path.basename(file, '.desktop');
|
|
208
|
+
let command = '';
|
|
209
|
+
let enabled = true;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
213
|
+
for (const line of content.split('\n')) {
|
|
214
|
+
const trimmed = line.trim();
|
|
215
|
+
if (trimmed.startsWith('Name=')) {
|
|
216
|
+
name = trimmed.slice('Name='.length);
|
|
217
|
+
} else if (trimmed.startsWith('Exec=')) {
|
|
218
|
+
command = trimmed.slice('Exec='.length);
|
|
219
|
+
} else if (trimmed.toLowerCase() === 'x-gnome-autostart-enabled=false') {
|
|
220
|
+
enabled = false;
|
|
221
|
+
} else if (trimmed.startsWith('Hidden=') && trimmed.split('=')[1]?.toLowerCase() === 'true') {
|
|
222
|
+
enabled = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// Failed to read .desktop file — use filename-based defaults
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
items.push({ name, command, location: fullPath, enabled });
|
|
230
|
+
}
|
|
231
|
+
return items;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async disableStartupItem(name: string): Promise<boolean> {
|
|
235
|
+
const autostartDir = path.join(os.homedir(), '.config', 'autostart');
|
|
236
|
+
// Accept both the entry name and the filename (with or without .desktop)
|
|
237
|
+
const desktopName = name.endsWith('.desktop') ? name : `${name}.desktop`;
|
|
238
|
+
const desktopPath = path.join(autostartDir, desktopName);
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
fs.unlinkSync(desktopPath);
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
getTempDirs(): string[] {
|
|
249
|
+
return [
|
|
250
|
+
'/tmp',
|
|
251
|
+
path.join(os.homedir(), '.cache'),
|
|
252
|
+
path.join(os.homedir(), '.npm', '_cacache'),
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
}
|