@appkit/llamacpp-cli 1.4.0 → 1.5.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 +15 -0
- package/README.md +87 -1
- package/dist/cli.js +14 -0
- package/dist/cli.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 +76 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/lib/metrics-aggregator.d.ts +39 -0
- package/dist/lib/metrics-aggregator.d.ts.map +1 -0
- package/dist/lib/metrics-aggregator.js +200 -0
- package/dist/lib/metrics-aggregator.js.map +1 -0
- package/dist/lib/system-collector.d.ts +75 -0
- package/dist/lib/system-collector.d.ts.map +1 -0
- package/dist/lib/system-collector.js +310 -0
- package/dist/lib/system-collector.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 +293 -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 +496 -0
- package/dist/tui/MultiServerMonitorApp.js.map +1 -0
- package/dist/tui/components/ErrorState.d.ts +8 -0
- package/dist/tui/components/ErrorState.d.ts.map +1 -0
- package/dist/tui/components/ErrorState.js +22 -0
- package/dist/tui/components/ErrorState.js.map +1 -0
- package/dist/tui/components/LoadingState.d.ts +8 -0
- package/dist/tui/components/LoadingState.d.ts.map +1 -0
- package/dist/tui/components/LoadingState.js +21 -0
- package/dist/tui/components/LoadingState.js.map +1 -0
- package/dist/types/monitor-types.d.ts +122 -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/utils/process-utils.d.ts +16 -1
- package/dist/utils/process-utils.d.ts.map +1 -1
- package/dist/utils/process-utils.js +144 -27
- package/dist/utils/process-utils.js.map +1 -1
- package/package.json +4 -2
- package/src/cli.ts +14 -0
- package/src/commands/monitor.ts +90 -0
- package/src/lib/metrics-aggregator.ts +244 -0
- package/src/lib/system-collector.ts +312 -0
- package/src/tui/MonitorApp.ts +361 -0
- package/src/tui/MultiServerMonitorApp.ts +547 -0
- package/src/types/monitor-types.ts +161 -0
- package/src/utils/process-utils.ts +160 -26
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ServerConfig } from './server-config.js';
|
|
2
|
+
export interface HealthResponse {
|
|
3
|
+
status: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PropsResponse {
|
|
7
|
+
default_generation_settings: {
|
|
8
|
+
n_ctx: number;
|
|
9
|
+
n_predict: number;
|
|
10
|
+
model: string;
|
|
11
|
+
seed: number;
|
|
12
|
+
temperature: number;
|
|
13
|
+
top_k: number;
|
|
14
|
+
top_p: number;
|
|
15
|
+
min_p: number;
|
|
16
|
+
n_keep: number;
|
|
17
|
+
stream: boolean;
|
|
18
|
+
};
|
|
19
|
+
total_slots: number;
|
|
20
|
+
model_loaded: boolean;
|
|
21
|
+
model_path: string;
|
|
22
|
+
model_alias?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SlotInfo {
|
|
25
|
+
id: number;
|
|
26
|
+
state: 'idle' | 'processing';
|
|
27
|
+
task_id?: number;
|
|
28
|
+
prompt?: string;
|
|
29
|
+
n_prompt_tokens?: number;
|
|
30
|
+
n_decoded?: number;
|
|
31
|
+
n_ctx: number;
|
|
32
|
+
truncated?: boolean;
|
|
33
|
+
stopped_eos?: boolean;
|
|
34
|
+
stopped_word?: boolean;
|
|
35
|
+
stopped_limit?: boolean;
|
|
36
|
+
stopping_word?: string;
|
|
37
|
+
tokens_predicted?: number;
|
|
38
|
+
tokens_evaluated?: number;
|
|
39
|
+
generation_settings?: {
|
|
40
|
+
n_ctx: number;
|
|
41
|
+
n_predict: number;
|
|
42
|
+
seed: number;
|
|
43
|
+
temperature: number;
|
|
44
|
+
top_k: number;
|
|
45
|
+
top_p: number;
|
|
46
|
+
};
|
|
47
|
+
prompt_tokens_processed?: number;
|
|
48
|
+
t_prompt_processing?: number;
|
|
49
|
+
t_token_generation?: number;
|
|
50
|
+
timings?: {
|
|
51
|
+
prompt_n: number;
|
|
52
|
+
prompt_ms: number;
|
|
53
|
+
prompt_per_token_ms: number;
|
|
54
|
+
prompt_per_second: number;
|
|
55
|
+
predicted_n: number;
|
|
56
|
+
predicted_ms: number;
|
|
57
|
+
predicted_per_token_ms: number;
|
|
58
|
+
predicted_per_second: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface SlotsResponse {
|
|
62
|
+
slots: SlotInfo[];
|
|
63
|
+
}
|
|
64
|
+
export interface SystemMetrics {
|
|
65
|
+
gpuUsage?: number;
|
|
66
|
+
cpuUsage?: number;
|
|
67
|
+
cpuCores?: number;
|
|
68
|
+
aneUsage?: number;
|
|
69
|
+
temperature?: number;
|
|
70
|
+
memoryUsed: number;
|
|
71
|
+
memoryTotal: number;
|
|
72
|
+
swapUsed?: number;
|
|
73
|
+
processMemory?: number;
|
|
74
|
+
timestamp: number;
|
|
75
|
+
source: 'macmon' | 'vm_stat' | 'none';
|
|
76
|
+
warnings?: string[];
|
|
77
|
+
}
|
|
78
|
+
export interface ServerMetrics {
|
|
79
|
+
server: ServerConfig;
|
|
80
|
+
healthy: boolean;
|
|
81
|
+
uptime?: string;
|
|
82
|
+
error?: string;
|
|
83
|
+
modelLoaded: boolean;
|
|
84
|
+
modelName: string;
|
|
85
|
+
contextSize: number;
|
|
86
|
+
totalSlots: number;
|
|
87
|
+
activeSlots: number;
|
|
88
|
+
idleSlots: number;
|
|
89
|
+
slots: SlotInfo[];
|
|
90
|
+
avgPromptSpeed?: number;
|
|
91
|
+
avgGenerateSpeed?: number;
|
|
92
|
+
requestsPerMinute?: number;
|
|
93
|
+
avgLatency?: number;
|
|
94
|
+
cacheHitRate?: number;
|
|
95
|
+
processMemory?: number;
|
|
96
|
+
timestamp: number;
|
|
97
|
+
stale: boolean;
|
|
98
|
+
}
|
|
99
|
+
export interface MonitorData {
|
|
100
|
+
server: ServerMetrics;
|
|
101
|
+
system?: SystemMetrics;
|
|
102
|
+
lastUpdated: Date;
|
|
103
|
+
updateInterval: number;
|
|
104
|
+
consecutiveFailures: number;
|
|
105
|
+
}
|
|
106
|
+
export interface ErrorState {
|
|
107
|
+
error: string;
|
|
108
|
+
canRetry: boolean;
|
|
109
|
+
suggestions?: string[];
|
|
110
|
+
}
|
|
111
|
+
export interface LoadingState {
|
|
112
|
+
message: string;
|
|
113
|
+
progress?: number;
|
|
114
|
+
}
|
|
115
|
+
export interface CollectionResult<T> {
|
|
116
|
+
success: boolean;
|
|
117
|
+
data: T | null;
|
|
118
|
+
error?: string;
|
|
119
|
+
warnings?: string[];
|
|
120
|
+
stale?: boolean;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=monitor-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor-types.d.ts","sourceRoot":"","sources":["../../src/types/monitor-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIlD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,2BAA2B,EAAE;QAC3B,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,oBAAoB,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAID,MAAM,WAAW,aAAa;IAE5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAID,MAAM,WAAW,aAAa;IAE5B,MAAM,EAAE,YAAY,CAAC;IAGrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAGlB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAID,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor-types.js","sourceRoot":"","sources":["../../src/types/monitor-types.ts"],"names":[],"mappings":""}
|
|
@@ -25,9 +25,24 @@ export declare function isProcessRunning(pid: number): Promise<boolean>;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function isPortInUse(port: number): Promise<boolean>;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Spawn a streaming command, read one line, and kill it
|
|
29
|
+
* Useful for commands like 'macmon pipe' that stream indefinitely
|
|
30
|
+
* Ensures the process is killed to prevent leaks
|
|
31
|
+
*/
|
|
32
|
+
export declare function spawnAndReadOneLine(command: string, args: string[], timeoutMs?: number): Promise<string | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Batch get memory usage for multiple processes in one top call
|
|
35
|
+
* Much more efficient than calling getProcessMemory() multiple times
|
|
36
|
+
* Returns Map<pid, bytes> for all requested PIDs
|
|
37
|
+
*/
|
|
38
|
+
export declare function getBatchProcessMemory(pids: number[]): Promise<Map<number, number | null>>;
|
|
39
|
+
/**
|
|
40
|
+
* Get memory usage for a single process in bytes
|
|
29
41
|
* Uses 'top' on macOS which includes GPU/Metal memory (more accurate for llama-server)
|
|
30
42
|
* Returns null if process not found or error occurs
|
|
43
|
+
* Caches results for 3 seconds to prevent spawning too many top processes
|
|
44
|
+
*
|
|
45
|
+
* Note: For multiple PIDs, use getBatchProcessMemory() instead - much more efficient
|
|
31
46
|
*/
|
|
32
47
|
export declare function getProcessMemory(pid: number): Promise<number | null>;
|
|
33
48
|
//# sourceMappingURL=process-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-utils.d.ts","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"process-utils.d.ts","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAS,MAAM,eAAe,CAAC;AAG5C,eAAO,MAAM,SAAS,2BAAkB,CAAC;AAEzC;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAMlG;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOpE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOhE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,GAAE,MAAa,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqExB;AAOD;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAmE/F;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG1E"}
|
|
@@ -6,6 +6,8 @@ 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;
|
|
10
12
|
const child_process_1 = require("child_process");
|
|
11
13
|
const util_1 = require("util");
|
|
@@ -65,37 +67,152 @@ async function isPortInUse(port) {
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
70
|
+
* Spawn a streaming command, read one line, and kill it
|
|
71
|
+
* Useful for commands like 'macmon pipe' that stream indefinitely
|
|
72
|
+
* Ensures the process is killed to prevent leaks
|
|
71
73
|
*/
|
|
72
|
-
async function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Convert to bytes
|
|
89
|
-
const multipliers = {
|
|
90
|
-
K: 1024,
|
|
91
|
-
M: 1024 * 1024,
|
|
92
|
-
G: 1024 * 1024 * 1024,
|
|
93
|
-
T: 1024 * 1024 * 1024 * 1024,
|
|
74
|
+
async function spawnAndReadOneLine(command, args, timeoutMs = 2000) {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
77
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
78
|
+
detached: false, // Keep in same process group for easier cleanup
|
|
79
|
+
});
|
|
80
|
+
let resolved = false;
|
|
81
|
+
let output = '';
|
|
82
|
+
const cleanup = () => {
|
|
83
|
+
try {
|
|
84
|
+
// Try SIGKILL immediately (SIGTERM may not work for macmon)
|
|
85
|
+
child.kill('SIGKILL');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Process might already be dead
|
|
89
|
+
}
|
|
94
90
|
};
|
|
95
|
-
|
|
91
|
+
// Set timeout to kill process if it doesn't produce output
|
|
92
|
+
const timeout = setTimeout(() => {
|
|
93
|
+
if (!resolved) {
|
|
94
|
+
resolved = true;
|
|
95
|
+
cleanup();
|
|
96
|
+
resolve(null);
|
|
97
|
+
}
|
|
98
|
+
}, timeoutMs);
|
|
99
|
+
// Read stdout line by line
|
|
100
|
+
child.stdout?.on('data', (data) => {
|
|
101
|
+
if (resolved)
|
|
102
|
+
return;
|
|
103
|
+
output += data.toString();
|
|
104
|
+
// Check if we have a complete line
|
|
105
|
+
const newlineIndex = output.indexOf('\n');
|
|
106
|
+
if (newlineIndex !== -1) {
|
|
107
|
+
const line = output.substring(0, newlineIndex).trim();
|
|
108
|
+
if (line.length > 0) {
|
|
109
|
+
resolved = true;
|
|
110
|
+
clearTimeout(timeout);
|
|
111
|
+
cleanup();
|
|
112
|
+
resolve(line);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// Handle process errors
|
|
117
|
+
child.on('error', () => {
|
|
118
|
+
if (!resolved) {
|
|
119
|
+
resolved = true;
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
resolve(null);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Handle process exit
|
|
125
|
+
child.on('exit', () => {
|
|
126
|
+
if (!resolved) {
|
|
127
|
+
resolved = true;
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
// Return partial output if we have any
|
|
130
|
+
const line = output.trim();
|
|
131
|
+
resolve(line.length > 0 ? line : null);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Process memory cache to prevent spawning too many 'top' processes
|
|
137
|
+
// Cache per PID with 3-second TTL
|
|
138
|
+
const processMemoryCache = new Map();
|
|
139
|
+
const PROCESS_MEMORY_CACHE_TTL = 3000; // 3 seconds
|
|
140
|
+
/**
|
|
141
|
+
* Batch get memory usage for multiple processes in one top call
|
|
142
|
+
* Much more efficient than calling getProcessMemory() multiple times
|
|
143
|
+
* Returns Map<pid, bytes> for all requested PIDs
|
|
144
|
+
*/
|
|
145
|
+
async function getBatchProcessMemory(pids) {
|
|
146
|
+
const result = new Map();
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
// Check cache and collect PIDs that need fetching
|
|
149
|
+
const pidsToFetch = [];
|
|
150
|
+
for (const pid of pids) {
|
|
151
|
+
const cached = processMemoryCache.get(pid);
|
|
152
|
+
if (cached && (now - cached.timestamp) < PROCESS_MEMORY_CACHE_TTL) {
|
|
153
|
+
result.set(pid, cached.value);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
pidsToFetch.push(pid);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// If all PIDs were cached, return early
|
|
160
|
+
if (pidsToFetch.length === 0) {
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
// Build top command with all PIDs: top -l 1 -pid X -pid Y -pid Z -stats pid,mem
|
|
165
|
+
const pidArgs = pidsToFetch.map(pid => `-pid ${pid}`).join(' ');
|
|
166
|
+
const output = await execCommand(`top -l 1 ${pidArgs} -stats pid,mem 2>/dev/null`);
|
|
167
|
+
// Parse output: each line is "PID MEM" (e.g., "1438 299M")
|
|
168
|
+
const lines = output.split('\n');
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
const match = line.trim().match(/^(\d+)\s+([\d.]+)([KMGT])\s*$/);
|
|
171
|
+
if (!match)
|
|
172
|
+
continue;
|
|
173
|
+
const pid = parseInt(match[1], 10);
|
|
174
|
+
const value = parseFloat(match[2]);
|
|
175
|
+
const unit = match[3];
|
|
176
|
+
// Convert to bytes
|
|
177
|
+
const multipliers = {
|
|
178
|
+
K: 1024,
|
|
179
|
+
M: 1024 * 1024,
|
|
180
|
+
G: 1024 * 1024 * 1024,
|
|
181
|
+
T: 1024 * 1024 * 1024 * 1024,
|
|
182
|
+
};
|
|
183
|
+
const bytes = Math.round(value * multipliers[unit]);
|
|
184
|
+
// Cache and store result
|
|
185
|
+
processMemoryCache.set(pid, { value: bytes, timestamp: now });
|
|
186
|
+
result.set(pid, bytes);
|
|
187
|
+
}
|
|
188
|
+
// For any PIDs that weren't in the output, cache null
|
|
189
|
+
for (const pid of pidsToFetch) {
|
|
190
|
+
if (!result.has(pid)) {
|
|
191
|
+
processMemoryCache.set(pid, { value: null, timestamp: now });
|
|
192
|
+
result.set(pid, null);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
96
196
|
}
|
|
97
197
|
catch {
|
|
98
|
-
|
|
198
|
+
// On error, cache null for all requested PIDs
|
|
199
|
+
for (const pid of pidsToFetch) {
|
|
200
|
+
processMemoryCache.set(pid, { value: null, timestamp: now });
|
|
201
|
+
result.set(pid, null);
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
99
204
|
}
|
|
100
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Get memory usage for a single process in bytes
|
|
208
|
+
* Uses 'top' on macOS which includes GPU/Metal memory (more accurate for llama-server)
|
|
209
|
+
* Returns null if process not found or error occurs
|
|
210
|
+
* Caches results for 3 seconds to prevent spawning too many top processes
|
|
211
|
+
*
|
|
212
|
+
* Note: For multiple PIDs, use getBatchProcessMemory() instead - much more efficient
|
|
213
|
+
*/
|
|
214
|
+
async function getProcessMemory(pid) {
|
|
215
|
+
const result = await getBatchProcessMemory([pid]);
|
|
216
|
+
return result.get(pid) ?? null;
|
|
217
|
+
}
|
|
101
218
|
//# 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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appkit/llamacpp-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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,13 @@
|
|
|
40
40
|
"url": "https://github.com/appkitstudio/llamacpp-cli/issues"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"
|
|
43
|
+
"blessed": "^0.1.81",
|
|
44
|
+
"chalk": "^4.1.2",
|
|
44
45
|
"cli-table3": "^0.6.5",
|
|
45
46
|
"commander": "^13.0.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
49
|
+
"@types/blessed": "^0.1.27",
|
|
48
50
|
"@types/node": "^20.12.7",
|
|
49
51
|
"commit-and-tag-version": "^12.6.1",
|
|
50
52
|
"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();
|
|
@@ -268,5 +269,18 @@ server
|
|
|
268
269
|
}
|
|
269
270
|
});
|
|
270
271
|
|
|
272
|
+
// Monitor server
|
|
273
|
+
server
|
|
274
|
+
.command('monitor [identifier]')
|
|
275
|
+
.description('Monitor server with real-time metrics TUI')
|
|
276
|
+
.action(async (identifier?: string) => {
|
|
277
|
+
try {
|
|
278
|
+
await monitorCommand(identifier);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error(chalk.red('❌ Error:'), (error as Error).message);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
271
285
|
// Parse arguments
|
|
272
286
|
program.parse();
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
// Check if server is running
|
|
40
|
+
if (server.status !== 'running') {
|
|
41
|
+
screen.destroy();
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Server ${server.modelName} is not running.\n\n` +
|
|
44
|
+
`Start it first: llamacpp server start ${server.id}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Launch single-server TUI
|
|
49
|
+
await createMonitorUI(screen, server);
|
|
50
|
+
} else if (allServers.length === 1) {
|
|
51
|
+
// Only one server - single server mode
|
|
52
|
+
const server = allServers[0];
|
|
53
|
+
|
|
54
|
+
// Check if server is running
|
|
55
|
+
if (server.status !== 'running') {
|
|
56
|
+
screen.destroy();
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Server ${server.modelName} is not running.\n\n` +
|
|
59
|
+
`Start it first: llamacpp server start ${server.id}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Launch single-server TUI
|
|
64
|
+
await createMonitorUI(screen, server);
|
|
65
|
+
} else {
|
|
66
|
+
// Multiple servers - multi-server mode
|
|
67
|
+
// Filter to only running servers for monitoring
|
|
68
|
+
const runningServers = allServers.filter(s => s.status === 'running');
|
|
69
|
+
|
|
70
|
+
if (runningServers.length === 0) {
|
|
71
|
+
screen.destroy();
|
|
72
|
+
throw new Error(
|
|
73
|
+
`No servers are currently running.\n\n` +
|
|
74
|
+
`Start a server first:\n` +
|
|
75
|
+
allServers
|
|
76
|
+
.map((s) => ` llamacpp server start ${s.id} # ${s.modelName}`)
|
|
77
|
+
.join('\n')
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Launch multi-server TUI
|
|
82
|
+
await createMultiServerMonitorUI(screen, allServers);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Render the screen
|
|
86
|
+
screen.render();
|
|
87
|
+
|
|
88
|
+
// Note: TUI functions handle their own key events and exit directly
|
|
89
|
+
// The process will stay alive until user presses Q/Ctrl+C
|
|
90
|
+
}
|