@appkit/llamacpp-cli 1.4.1 → 1.6.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/CHANGELOG.md +21 -0
- package/MONITORING-ACCURACY-FIX.md +199 -0
- package/PER-PROCESS-METRICS.md +190 -0
- package/README.md +136 -1
- package/dist/cli.js +21 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +12 -3
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/monitor.d.ts +2 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +126 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/ps.d.ts +3 -1
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +75 -5
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/server-show.d.ts.map +1 -1
- package/dist/commands/server-show.js +10 -3
- package/dist/commands/server-show.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +14 -2
- package/dist/commands/start.js.map +1 -1
- package/dist/lib/history-manager.d.ts +46 -0
- package/dist/lib/history-manager.d.ts.map +1 -0
- package/dist/lib/history-manager.js +157 -0
- package/dist/lib/history-manager.js.map +1 -0
- package/dist/lib/metrics-aggregator.d.ts +40 -0
- package/dist/lib/metrics-aggregator.d.ts.map +1 -0
- package/dist/lib/metrics-aggregator.js +211 -0
- package/dist/lib/metrics-aggregator.js.map +1 -0
- package/dist/lib/system-collector.d.ts +80 -0
- package/dist/lib/system-collector.d.ts.map +1 -0
- package/dist/lib/system-collector.js +311 -0
- package/dist/lib/system-collector.js.map +1 -0
- package/dist/tui/HistoricalMonitorApp.d.ts +5 -0
- package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -0
- package/dist/tui/HistoricalMonitorApp.js +490 -0
- package/dist/tui/HistoricalMonitorApp.js.map +1 -0
- package/dist/tui/MonitorApp.d.ts +4 -0
- package/dist/tui/MonitorApp.d.ts.map +1 -0
- package/dist/tui/MonitorApp.js +315 -0
- package/dist/tui/MonitorApp.js.map +1 -0
- package/dist/tui/MultiServerMonitorApp.d.ts +4 -0
- package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -0
- package/dist/tui/MultiServerMonitorApp.js +712 -0
- package/dist/tui/MultiServerMonitorApp.js.map +1 -0
- package/dist/types/history-types.d.ts +30 -0
- package/dist/types/history-types.d.ts.map +1 -0
- package/dist/types/history-types.js +11 -0
- package/dist/types/history-types.js.map +1 -0
- package/dist/types/monitor-types.d.ts +123 -0
- package/dist/types/monitor-types.d.ts.map +1 -0
- package/dist/types/monitor-types.js +3 -0
- package/dist/types/monitor-types.js.map +1 -0
- package/dist/types/server-config.d.ts +1 -0
- package/dist/types/server-config.d.ts.map +1 -1
- package/dist/types/server-config.js.map +1 -1
- package/dist/utils/downsample-utils.d.ts +35 -0
- package/dist/utils/downsample-utils.d.ts.map +1 -0
- package/dist/utils/downsample-utils.js +107 -0
- package/dist/utils/downsample-utils.js.map +1 -0
- package/dist/utils/file-utils.d.ts +6 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +38 -0
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/process-utils.d.ts +35 -2
- package/dist/utils/process-utils.d.ts.map +1 -1
- package/dist/utils/process-utils.js +220 -25
- package/dist/utils/process-utils.js.map +1 -1
- package/docs/images/.gitkeep +1 -0
- package/package.json +5 -1
- package/src/cli.ts +21 -4
- package/src/commands/create.ts +14 -4
- package/src/commands/monitor.ts +110 -0
- package/src/commands/ps.ts +88 -5
- package/src/commands/server-show.ts +10 -3
- package/src/commands/start.ts +15 -2
- package/src/lib/history-manager.ts +172 -0
- package/src/lib/metrics-aggregator.ts +257 -0
- package/src/lib/system-collector.ts +315 -0
- package/src/tui/HistoricalMonitorApp.ts +548 -0
- package/src/tui/MonitorApp.ts +386 -0
- package/src/tui/MultiServerMonitorApp.ts +792 -0
- package/src/types/history-types.ts +39 -0
- package/src/types/monitor-types.ts +162 -0
- package/src/types/server-config.ts +1 -0
- package/src/utils/downsample-utils.ts +128 -0
- package/src/utils/file-utils.ts +40 -0
- package/src/utils/process-utils.ts +243 -25
- package/test-load.sh +100 -0
|
@@ -6,7 +6,11 @@ exports.execCommandFull = execCommandFull;
|
|
|
6
6
|
exports.commandExists = commandExists;
|
|
7
7
|
exports.isProcessRunning = isProcessRunning;
|
|
8
8
|
exports.isPortInUse = isPortInUse;
|
|
9
|
+
exports.spawnAndReadOneLine = spawnAndReadOneLine;
|
|
10
|
+
exports.getBatchProcessMemory = getBatchProcessMemory;
|
|
9
11
|
exports.getProcessMemory = getProcessMemory;
|
|
12
|
+
exports.getBatchProcessCpu = getBatchProcessCpu;
|
|
13
|
+
exports.getProcessCpu = getProcessCpu;
|
|
10
14
|
const child_process_1 = require("child_process");
|
|
11
15
|
const util_1 = require("util");
|
|
12
16
|
exports.execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
@@ -65,37 +69,228 @@ async function isPortInUse(port) {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
72
|
+
* Spawn a streaming command, read one line, and kill it
|
|
73
|
+
* Useful for commands like 'macmon pipe' that stream indefinitely
|
|
74
|
+
* Ensures the process is killed to prevent leaks
|
|
75
|
+
*/
|
|
76
|
+
async function spawnAndReadOneLine(command, args, timeoutMs = 2000) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
79
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
80
|
+
detached: false, // Keep in same process group for easier cleanup
|
|
81
|
+
});
|
|
82
|
+
let resolved = false;
|
|
83
|
+
let output = '';
|
|
84
|
+
const cleanup = () => {
|
|
85
|
+
try {
|
|
86
|
+
// Try SIGKILL immediately (SIGTERM may not work for macmon)
|
|
87
|
+
child.kill('SIGKILL');
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Process might already be dead
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
// Set timeout to kill process if it doesn't produce output
|
|
94
|
+
const timeout = setTimeout(() => {
|
|
95
|
+
if (!resolved) {
|
|
96
|
+
resolved = true;
|
|
97
|
+
cleanup();
|
|
98
|
+
resolve(null);
|
|
99
|
+
}
|
|
100
|
+
}, timeoutMs);
|
|
101
|
+
// Read stdout line by line
|
|
102
|
+
child.stdout?.on('data', (data) => {
|
|
103
|
+
if (resolved)
|
|
104
|
+
return;
|
|
105
|
+
output += data.toString();
|
|
106
|
+
// Check if we have a complete line
|
|
107
|
+
const newlineIndex = output.indexOf('\n');
|
|
108
|
+
if (newlineIndex !== -1) {
|
|
109
|
+
const line = output.substring(0, newlineIndex).trim();
|
|
110
|
+
if (line.length > 0) {
|
|
111
|
+
resolved = true;
|
|
112
|
+
clearTimeout(timeout);
|
|
113
|
+
cleanup();
|
|
114
|
+
resolve(line);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Handle process errors
|
|
119
|
+
child.on('error', () => {
|
|
120
|
+
if (!resolved) {
|
|
121
|
+
resolved = true;
|
|
122
|
+
clearTimeout(timeout);
|
|
123
|
+
resolve(null);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Handle process exit
|
|
127
|
+
child.on('exit', () => {
|
|
128
|
+
if (!resolved) {
|
|
129
|
+
resolved = true;
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
// Return partial output if we have any
|
|
132
|
+
const line = output.trim();
|
|
133
|
+
resolve(line.length > 0 ? line : null);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Process memory cache to prevent spawning too many 'top' processes
|
|
139
|
+
// Cache per PID with 3-second TTL
|
|
140
|
+
const processMemoryCache = new Map();
|
|
141
|
+
const PROCESS_MEMORY_CACHE_TTL = 3000; // 3 seconds
|
|
142
|
+
/**
|
|
143
|
+
* Batch get memory usage for multiple processes in one top call
|
|
144
|
+
* Much more efficient than calling getProcessMemory() multiple times
|
|
145
|
+
* Returns Map<pid, bytes> for all requested PIDs
|
|
146
|
+
*/
|
|
147
|
+
async function getBatchProcessMemory(pids) {
|
|
148
|
+
const result = new Map();
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
// Check cache and collect PIDs that need fetching
|
|
151
|
+
const pidsToFetch = [];
|
|
152
|
+
for (const pid of pids) {
|
|
153
|
+
const cached = processMemoryCache.get(pid);
|
|
154
|
+
if (cached && (now - cached.timestamp) < PROCESS_MEMORY_CACHE_TTL) {
|
|
155
|
+
result.set(pid, cached.value);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
pidsToFetch.push(pid);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// If all PIDs were cached, return early
|
|
162
|
+
if (pidsToFetch.length === 0) {
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
// Build top command with all PIDs: top -l 1 -pid X -pid Y -pid Z -stats pid,mem
|
|
167
|
+
const pidArgs = pidsToFetch.map(pid => `-pid ${pid}`).join(' ');
|
|
168
|
+
const output = await execCommand(`top -l 1 ${pidArgs} -stats pid,mem 2>/dev/null`);
|
|
169
|
+
// Parse output: each line is "PID MEM" (e.g., "1438 299M")
|
|
170
|
+
const lines = output.split('\n');
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
const match = line.trim().match(/^(\d+)\s+([\d.]+)([KMGT])\s*$/);
|
|
173
|
+
if (!match)
|
|
174
|
+
continue;
|
|
175
|
+
const pid = parseInt(match[1], 10);
|
|
176
|
+
const value = parseFloat(match[2]);
|
|
177
|
+
const unit = match[3];
|
|
178
|
+
// Convert to bytes
|
|
179
|
+
const multipliers = {
|
|
180
|
+
K: 1024,
|
|
181
|
+
M: 1024 * 1024,
|
|
182
|
+
G: 1024 * 1024 * 1024,
|
|
183
|
+
T: 1024 * 1024 * 1024 * 1024,
|
|
184
|
+
};
|
|
185
|
+
const bytes = Math.round(value * multipliers[unit]);
|
|
186
|
+
// Cache and store result
|
|
187
|
+
processMemoryCache.set(pid, { value: bytes, timestamp: now });
|
|
188
|
+
result.set(pid, bytes);
|
|
189
|
+
}
|
|
190
|
+
// For any PIDs that weren't in the output, cache null
|
|
191
|
+
for (const pid of pidsToFetch) {
|
|
192
|
+
if (!result.has(pid)) {
|
|
193
|
+
processMemoryCache.set(pid, { value: null, timestamp: now });
|
|
194
|
+
result.set(pid, null);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// On error, cache null for all requested PIDs
|
|
201
|
+
for (const pid of pidsToFetch) {
|
|
202
|
+
processMemoryCache.set(pid, { value: null, timestamp: now });
|
|
203
|
+
result.set(pid, null);
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get memory usage for a single process in bytes
|
|
210
|
+
* Uses 'top' on macOS which reports CPU memory only (NOT GPU/Metal memory)
|
|
70
211
|
* Returns null if process not found or error occurs
|
|
212
|
+
* Caches results for 3 seconds to prevent spawning too many top processes
|
|
213
|
+
*
|
|
214
|
+
* Note: For llama-server processes with GPU offloading, use ServerConfig.metalMemoryMB
|
|
215
|
+
* to get GPU memory allocation (parsed from logs during server startup)
|
|
216
|
+
*
|
|
217
|
+
* Note: For multiple PIDs, use getBatchProcessMemory() instead - much more efficient
|
|
71
218
|
*/
|
|
72
219
|
async function getProcessMemory(pid) {
|
|
220
|
+
const result = await getBatchProcessMemory([pid]);
|
|
221
|
+
return result.get(pid) ?? null;
|
|
222
|
+
}
|
|
223
|
+
// Process CPU cache to prevent spawning too many 'ps' processes
|
|
224
|
+
// Cache per PID with 3-second TTL
|
|
225
|
+
const processCpuCache = new Map();
|
|
226
|
+
const PROCESS_CPU_CACHE_TTL = 3000; // 3 seconds
|
|
227
|
+
/**
|
|
228
|
+
* Batch get CPU usage for multiple processes in one ps call
|
|
229
|
+
* Much more efficient than calling getProcessCpu() multiple times
|
|
230
|
+
* Returns Map<pid, percentage> for all requested PIDs
|
|
231
|
+
*/
|
|
232
|
+
async function getBatchProcessCpu(pids) {
|
|
233
|
+
const result = new Map();
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
// Check cache and collect PIDs that need fetching
|
|
236
|
+
const pidsToFetch = [];
|
|
237
|
+
for (const pid of pids) {
|
|
238
|
+
const cached = processCpuCache.get(pid);
|
|
239
|
+
if (cached && (now - cached.timestamp) < PROCESS_CPU_CACHE_TTL) {
|
|
240
|
+
result.set(pid, cached.value);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
pidsToFetch.push(pid);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// If all PIDs were cached, return early
|
|
247
|
+
if (pidsToFetch.length === 0) {
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
73
250
|
try {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
const output = await execCommand(`
|
|
77
|
-
//
|
|
78
|
-
const lines = output.split('\n')
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
251
|
+
// Build ps command with all PIDs: ps -p X,Y,Z -o pid=,%cpu=
|
|
252
|
+
const pidList = pidsToFetch.join(',');
|
|
253
|
+
const output = await execCommand(`ps -p ${pidList} -o pid=,%cpu= 2>/dev/null`);
|
|
254
|
+
// Parse output: each line is "PID %CPU" (e.g., "1438 45.2")
|
|
255
|
+
const lines = output.split('\n');
|
|
256
|
+
for (const line of lines) {
|
|
257
|
+
const match = line.trim().match(/^(\d+)\s+([\d.]+)\s*$/);
|
|
258
|
+
if (!match)
|
|
259
|
+
continue;
|
|
260
|
+
const pid = parseInt(match[1], 10);
|
|
261
|
+
const cpuPercent = parseFloat(match[2]);
|
|
262
|
+
// Cache and store result
|
|
263
|
+
processCpuCache.set(pid, { value: cpuPercent, timestamp: now });
|
|
264
|
+
result.set(pid, cpuPercent);
|
|
265
|
+
}
|
|
266
|
+
// For any PIDs that weren't in the output, cache null (process not running)
|
|
267
|
+
for (const pid of pidsToFetch) {
|
|
268
|
+
if (!result.has(pid)) {
|
|
269
|
+
processCpuCache.set(pid, { value: null, timestamp: now });
|
|
270
|
+
result.set(pid, null);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
96
274
|
}
|
|
97
275
|
catch {
|
|
98
|
-
|
|
276
|
+
// On error, cache null for all requested PIDs
|
|
277
|
+
for (const pid of pidsToFetch) {
|
|
278
|
+
processCpuCache.set(pid, { value: null, timestamp: now });
|
|
279
|
+
result.set(pid, null);
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
99
282
|
}
|
|
100
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Get CPU usage for a single process as percentage (0-100+)
|
|
286
|
+
* Uses 'ps -o %cpu' on macOS
|
|
287
|
+
* Returns null if process not found or error occurs
|
|
288
|
+
* Caches results for 3 seconds to prevent spawning too many ps processes
|
|
289
|
+
*
|
|
290
|
+
* Note: For multiple PIDs, use getBatchProcessCpu() instead - much more efficient
|
|
291
|
+
*/
|
|
292
|
+
async function getProcessCpu(pid) {
|
|
293
|
+
const result = await getBatchProcessCpu([pid]);
|
|
294
|
+
return result.get(pid) ?? null;
|
|
295
|
+
}
|
|
101
296
|
//# sourceMappingURL=process-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-utils.js","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":";;;AASA,kCAGC;AAKD,0CAMC;AAKD,sCAOC;AAKD,4CAOC;AAKD,kCAOC;AAOD,
|
|
1
|
+
{"version":3,"file":"process-utils.js","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Screenshots directory
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appkit/llamacpp-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "CLI tool to manage local llama.cpp servers on macOS",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,11 +40,15 @@
|
|
|
40
40
|
"url": "https://github.com/appkitstudio/llamacpp-cli/issues"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"asciichart": "^1.5.25",
|
|
44
|
+
"blessed": "^0.1.81",
|
|
43
45
|
"chalk": "^4.1.2",
|
|
44
46
|
"cli-table3": "^0.6.5",
|
|
45
47
|
"commander": "^13.0.0"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
50
|
+
"@types/asciichart": "^1.5.8",
|
|
51
|
+
"@types/blessed": "^0.1.27",
|
|
48
52
|
"@types/node": "^20.12.7",
|
|
49
53
|
"commit-and-tag-version": "^12.6.1",
|
|
50
54
|
"tsx": "^4.7.2",
|
package/src/cli.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { showCommand } from './commands/show';
|
|
|
17
17
|
import { serverShowCommand } from './commands/server-show';
|
|
18
18
|
import { serverConfigCommand } from './commands/config';
|
|
19
19
|
import { configGlobalCommand } from './commands/config-global';
|
|
20
|
+
import { monitorCommand } from './commands/monitor';
|
|
20
21
|
import packageJson from '../package.json';
|
|
21
22
|
|
|
22
23
|
const program = new Command();
|
|
@@ -41,11 +42,12 @@ program
|
|
|
41
42
|
|
|
42
43
|
// List running servers
|
|
43
44
|
program
|
|
44
|
-
.command('ps')
|
|
45
|
-
.description('
|
|
46
|
-
.
|
|
45
|
+
.command('ps [identifier]')
|
|
46
|
+
.description('Interactive server monitoring dashboard')
|
|
47
|
+
.option('--table', 'Show static table instead of TUI (for scripting)')
|
|
48
|
+
.action(async (identifier?: string, options?: { table?: boolean }) => {
|
|
47
49
|
try {
|
|
48
|
-
await psCommand();
|
|
50
|
+
await psCommand(identifier, options);
|
|
49
51
|
} catch (error) {
|
|
50
52
|
console.error(chalk.red('❌ Error:'), (error as Error).message);
|
|
51
53
|
process.exit(1);
|
|
@@ -268,5 +270,20 @@ server
|
|
|
268
270
|
}
|
|
269
271
|
});
|
|
270
272
|
|
|
273
|
+
// Monitor server (deprecated - redirects to ps)
|
|
274
|
+
server
|
|
275
|
+
.command('monitor [identifier]')
|
|
276
|
+
.description('Monitor server with real-time metrics TUI (deprecated: use "llamacpp ps" instead)')
|
|
277
|
+
.action(async (identifier?: string) => {
|
|
278
|
+
try {
|
|
279
|
+
console.log(chalk.yellow('⚠️ The "monitor" command is deprecated and will be removed in a future version.'));
|
|
280
|
+
console.log(chalk.dim(' Please use "llamacpp ps" instead for the same functionality.\n'));
|
|
281
|
+
await monitorCommand(identifier);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error(chalk.red('❌ Error:'), (error as Error).message);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
271
288
|
// Parse arguments
|
|
272
289
|
program.parse();
|
package/src/commands/create.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { launchctlManager } from '../lib/launchctl-manager';
|
|
|
9
9
|
import { statusChecker } from '../lib/status-checker';
|
|
10
10
|
import { commandExists } from '../utils/process-utils';
|
|
11
11
|
import { formatBytes } from '../utils/format-utils';
|
|
12
|
-
import { ensureDir } from '../utils/file-utils';
|
|
12
|
+
import { ensureDir, parseMetalMemoryFromLog } from '../utils/file-utils';
|
|
13
13
|
import { ensureModelsDirectory } from '../lib/models-dir-setup';
|
|
14
14
|
|
|
15
15
|
interface CreateOptions {
|
|
@@ -159,12 +159,22 @@ export async function createCommand(model: string, options: CreateOptions): Prom
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
// 12. Update config with running status
|
|
162
|
-
|
|
162
|
+
let updatedConfig = await statusChecker.updateServerStatus(config);
|
|
163
|
+
|
|
164
|
+
// 13. Parse Metal (GPU) memory allocation from logs
|
|
165
|
+
// Wait a few seconds for model to start loading (large models take time)
|
|
166
|
+
console.log(chalk.dim('Detecting Metal (GPU) memory allocation...'));
|
|
167
|
+
await new Promise(resolve => setTimeout(resolve, 8000)); // 8 second delay
|
|
168
|
+
const metalMemoryMB = await parseMetalMemoryFromLog(updatedConfig.stderrPath);
|
|
169
|
+
if (metalMemoryMB) {
|
|
170
|
+
updatedConfig = { ...updatedConfig, metalMemoryMB };
|
|
171
|
+
console.log(chalk.dim(`Metal memory: ${metalMemoryMB.toFixed(0)} MB`));
|
|
172
|
+
}
|
|
163
173
|
|
|
164
|
-
//
|
|
174
|
+
// 14. Save server config
|
|
165
175
|
await stateManager.saveServerConfig(updatedConfig);
|
|
166
176
|
|
|
167
|
-
//
|
|
177
|
+
// 15. Display success message
|
|
168
178
|
console.log();
|
|
169
179
|
console.log(chalk.green('✅ Server created and started successfully!'));
|
|
170
180
|
console.log();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import blessed from 'blessed';
|
|
3
|
+
import { stateManager } from '../lib/state-manager.js';
|
|
4
|
+
import { createMonitorUI } from '../tui/MonitorApp.js';
|
|
5
|
+
import { createMultiServerMonitorUI } from '../tui/MultiServerMonitorApp.js';
|
|
6
|
+
|
|
7
|
+
export async function monitorCommand(identifier?: string): Promise<void> {
|
|
8
|
+
// Initialize state manager
|
|
9
|
+
await stateManager.initialize();
|
|
10
|
+
|
|
11
|
+
// Get all servers
|
|
12
|
+
const allServers = await stateManager.getAllServers();
|
|
13
|
+
if (allServers.length === 0) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`No servers configured.\n\n` +
|
|
16
|
+
`Create a server first: llamacpp server create <model>`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create blessed screen
|
|
21
|
+
const screen = blessed.screen({
|
|
22
|
+
smartCSR: true,
|
|
23
|
+
title: 'llama.cpp Server Monitor',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Determine which UI to launch
|
|
27
|
+
if (identifier) {
|
|
28
|
+
// User specified a server - single server mode
|
|
29
|
+
const server = await stateManager.findServer(identifier);
|
|
30
|
+
if (!server) {
|
|
31
|
+
screen.destroy();
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Server not found: ${identifier}\n\n` +
|
|
34
|
+
`Use: llamacpp ps\n` +
|
|
35
|
+
`Or create a new server: llamacpp server create <model>`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Update server status to reflect actual launchctl state
|
|
40
|
+
const { statusChecker } = await import('../lib/status-checker.js');
|
|
41
|
+
const status = await statusChecker.checkServer(server);
|
|
42
|
+
server.status = status.isRunning ? 'running' : 'stopped';
|
|
43
|
+
server.pid = status.pid || undefined;
|
|
44
|
+
|
|
45
|
+
// Check if server is running
|
|
46
|
+
if (server.status !== 'running') {
|
|
47
|
+
screen.destroy();
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Server ${server.modelName} is not running.\n\n` +
|
|
50
|
+
`Start it first: llamacpp server start ${server.id}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Launch single-server TUI
|
|
55
|
+
await createMonitorUI(screen, server);
|
|
56
|
+
} else if (allServers.length === 1) {
|
|
57
|
+
// Only one server - single server mode
|
|
58
|
+
const server = allServers[0];
|
|
59
|
+
|
|
60
|
+
// Update server status to reflect actual launchctl state
|
|
61
|
+
const { statusChecker } = await import('../lib/status-checker.js');
|
|
62
|
+
const status = await statusChecker.checkServer(server);
|
|
63
|
+
server.status = status.isRunning ? 'running' : 'stopped';
|
|
64
|
+
server.pid = status.pid || undefined;
|
|
65
|
+
|
|
66
|
+
// Check if server is running
|
|
67
|
+
if (server.status !== 'running') {
|
|
68
|
+
screen.destroy();
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Server ${server.modelName} is not running.\n\n` +
|
|
71
|
+
`Start it first: llamacpp server start ${server.id}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Launch single-server TUI
|
|
76
|
+
await createMonitorUI(screen, server);
|
|
77
|
+
} else {
|
|
78
|
+
// Multiple servers - multi-server mode
|
|
79
|
+
// Update all server statuses to reflect actual launchctl state
|
|
80
|
+
const { statusChecker } = await import('../lib/status-checker.js');
|
|
81
|
+
for (const server of allServers) {
|
|
82
|
+
const status = await statusChecker.checkServer(server);
|
|
83
|
+
server.status = status.isRunning ? 'running' : 'stopped';
|
|
84
|
+
server.pid = status.pid || undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Filter to only running servers for monitoring
|
|
88
|
+
const runningServers = allServers.filter(s => s.status === 'running');
|
|
89
|
+
|
|
90
|
+
if (runningServers.length === 0) {
|
|
91
|
+
screen.destroy();
|
|
92
|
+
throw new Error(
|
|
93
|
+
`No servers are currently running.\n\n` +
|
|
94
|
+
`Start a server first:\n` +
|
|
95
|
+
allServers
|
|
96
|
+
.map((s) => ` llamacpp server start ${s.id} # ${s.modelName}`)
|
|
97
|
+
.join('\n')
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Launch multi-server TUI (pass all servers so we can see stopped ones too)
|
|
102
|
+
await createMultiServerMonitorUI(screen, allServers);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Render the screen
|
|
106
|
+
screen.render();
|
|
107
|
+
|
|
108
|
+
// Note: TUI functions handle their own key events and exit directly
|
|
109
|
+
// The process will stay alive until user presses Q/Ctrl+C
|
|
110
|
+
}
|
package/src/commands/ps.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import Table from 'cli-table3';
|
|
3
|
+
import blessed from 'blessed';
|
|
3
4
|
import { stateManager } from '../lib/state-manager';
|
|
4
5
|
import { statusChecker } from '../lib/status-checker';
|
|
5
6
|
import { formatUptime, formatBytes } from '../utils/format-utils';
|
|
6
7
|
import { getProcessMemory } from '../utils/process-utils';
|
|
8
|
+
import { createMultiServerMonitorUI } from '../tui/MultiServerMonitorApp.js';
|
|
9
|
+
import { ServerConfig } from '../types/server-config.js';
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
async function showStaticTable(): Promise<void> {
|
|
9
12
|
const servers = await stateManager.getAllServers();
|
|
10
13
|
|
|
11
14
|
if (servers.length === 0) {
|
|
@@ -52,12 +55,14 @@ export async function psCommand(): Promise<void> {
|
|
|
52
55
|
? formatUptime(server.lastStarted)
|
|
53
56
|
: '-';
|
|
54
57
|
|
|
55
|
-
// Get memory usage for running servers
|
|
58
|
+
// Get memory usage for running servers (CPU + Metal GPU memory)
|
|
56
59
|
let memoryText = '-';
|
|
57
60
|
if (server.status === 'running' && server.pid) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
+
const cpuMemoryBytes = await getProcessMemory(server.pid);
|
|
62
|
+
if (cpuMemoryBytes !== null) {
|
|
63
|
+
const metalMemoryBytes = server.metalMemoryMB ? server.metalMemoryMB * 1024 * 1024 : 0;
|
|
64
|
+
const totalMemoryBytes = cpuMemoryBytes + metalMemoryBytes;
|
|
65
|
+
memoryText = formatBytes(totalMemoryBytes);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -88,3 +93,81 @@ export async function psCommand(): Promise<void> {
|
|
|
88
93
|
console.log(chalk.red('\n⚠️ Some servers have crashed. Check logs with: llamacpp server logs <id> --errors'));
|
|
89
94
|
}
|
|
90
95
|
}
|
|
96
|
+
|
|
97
|
+
export async function psCommand(identifier?: string, options?: { table?: boolean }): Promise<void> {
|
|
98
|
+
// If --table flag is set, show static table (backward compatibility)
|
|
99
|
+
if (options?.table) {
|
|
100
|
+
await showStaticTable();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Get all servers and update their statuses
|
|
105
|
+
const servers = await stateManager.getAllServers();
|
|
106
|
+
|
|
107
|
+
if (servers.length === 0) {
|
|
108
|
+
console.log(chalk.yellow('No servers configured.'));
|
|
109
|
+
console.log(chalk.dim('\nCreate a server: llamacpp server create <model-filename>'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Update all server statuses
|
|
114
|
+
const updated = await statusChecker.updateAllServerStatuses();
|
|
115
|
+
|
|
116
|
+
// If identifier is provided, find the server and jump to detail view
|
|
117
|
+
if (identifier) {
|
|
118
|
+
const server = await findServer(identifier, updated);
|
|
119
|
+
if (!server) {
|
|
120
|
+
console.log(chalk.red(`❌ Server not found: ${identifier}`));
|
|
121
|
+
console.log(chalk.dim('\nAvailable servers:'));
|
|
122
|
+
updated.forEach((s: ServerConfig) => {
|
|
123
|
+
console.log(chalk.dim(` - ${s.id} (port ${s.port})`));
|
|
124
|
+
});
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Find the server index for direct jump
|
|
129
|
+
const serverIndex = updated.findIndex(s => s.id === server.id);
|
|
130
|
+
|
|
131
|
+
// Launch multi-server TUI with direct jump to detail view
|
|
132
|
+
const screen = blessed.screen({
|
|
133
|
+
smartCSR: true,
|
|
134
|
+
title: 'llama.cpp Multi-Server Monitor',
|
|
135
|
+
fullUnicode: true,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await createMultiServerMonitorUI(screen, updated, true, serverIndex); // fromPs = true, directJumpIndex
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// No identifier - launch multi-server TUI
|
|
143
|
+
const runningServers = updated.filter((s: ServerConfig) => s.status === 'running');
|
|
144
|
+
|
|
145
|
+
// Launch multi-server TUI (shows all servers, not just running ones)
|
|
146
|
+
const screen = blessed.screen({
|
|
147
|
+
smartCSR: true,
|
|
148
|
+
title: 'llama.cpp Multi-Server Monitor',
|
|
149
|
+
fullUnicode: true,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await createMultiServerMonitorUI(screen, updated, true); // fromPs = true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Helper function to find server by identifier
|
|
156
|
+
async function findServer(identifier: string, servers: ServerConfig[]): Promise<ServerConfig | null> {
|
|
157
|
+
// Try by port
|
|
158
|
+
const port = parseInt(identifier);
|
|
159
|
+
if (!isNaN(port)) {
|
|
160
|
+
const server = servers.find(s => s.port === port);
|
|
161
|
+
if (server) return server;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Try by exact ID
|
|
165
|
+
const byId = servers.find(s => s.id === identifier);
|
|
166
|
+
if (byId) return byId;
|
|
167
|
+
|
|
168
|
+
// Try by partial model name
|
|
169
|
+
const byModel = servers.find(s => s.modelName.toLowerCase().includes(identifier.toLowerCase()));
|
|
170
|
+
if (byModel) return byModel;
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
@@ -68,9 +68,16 @@ export async function serverShowCommand(identifier: string): Promise<void> {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
if (updatedServer.pid) {
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
71
|
+
const cpuMemoryBytes = await getProcessMemory(updatedServer.pid);
|
|
72
|
+
if (cpuMemoryBytes !== null) {
|
|
73
|
+
const metalMemoryBytes = updatedServer.metalMemoryMB ? updatedServer.metalMemoryMB * 1024 * 1024 : 0;
|
|
74
|
+
const totalMemoryBytes = cpuMemoryBytes + metalMemoryBytes;
|
|
75
|
+
|
|
76
|
+
if (metalMemoryBytes > 0) {
|
|
77
|
+
console.log(`${chalk.bold('Memory:')} ${formatBytes(totalMemoryBytes)} (CPU: ${formatBytes(cpuMemoryBytes)}, GPU: ${formatBytes(metalMemoryBytes)})`);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`${chalk.bold('Memory:')} ${formatBytes(cpuMemoryBytes)} (CPU only)`);
|
|
80
|
+
}
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
}
|