@appkit/llamacpp-cli 1.11.0 → 1.12.1

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.
Files changed (126) hide show
  1. package/README.md +572 -170
  2. package/dist/cli.js +99 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/admin/config.d.ts +10 -0
  5. package/dist/commands/admin/config.d.ts.map +1 -0
  6. package/dist/commands/admin/config.js +100 -0
  7. package/dist/commands/admin/config.js.map +1 -0
  8. package/dist/commands/admin/logs.d.ts +10 -0
  9. package/dist/commands/admin/logs.d.ts.map +1 -0
  10. package/dist/commands/admin/logs.js +114 -0
  11. package/dist/commands/admin/logs.js.map +1 -0
  12. package/dist/commands/admin/restart.d.ts +2 -0
  13. package/dist/commands/admin/restart.d.ts.map +1 -0
  14. package/dist/commands/admin/restart.js +29 -0
  15. package/dist/commands/admin/restart.js.map +1 -0
  16. package/dist/commands/admin/start.d.ts +2 -0
  17. package/dist/commands/admin/start.d.ts.map +1 -0
  18. package/dist/commands/admin/start.js +30 -0
  19. package/dist/commands/admin/start.js.map +1 -0
  20. package/dist/commands/admin/status.d.ts +2 -0
  21. package/dist/commands/admin/status.d.ts.map +1 -0
  22. package/dist/commands/admin/status.js +82 -0
  23. package/dist/commands/admin/status.js.map +1 -0
  24. package/dist/commands/admin/stop.d.ts +2 -0
  25. package/dist/commands/admin/stop.d.ts.map +1 -0
  26. package/dist/commands/admin/stop.js +21 -0
  27. package/dist/commands/admin/stop.js.map +1 -0
  28. package/dist/commands/logs.d.ts +1 -0
  29. package/dist/commands/logs.d.ts.map +1 -1
  30. package/dist/commands/logs.js +22 -0
  31. package/dist/commands/logs.js.map +1 -1
  32. package/dist/lib/admin-manager.d.ts +111 -0
  33. package/dist/lib/admin-manager.d.ts.map +1 -0
  34. package/dist/lib/admin-manager.js +413 -0
  35. package/dist/lib/admin-manager.js.map +1 -0
  36. package/dist/lib/admin-server.d.ts +148 -0
  37. package/dist/lib/admin-server.d.ts.map +1 -0
  38. package/dist/lib/admin-server.js +1161 -0
  39. package/dist/lib/admin-server.js.map +1 -0
  40. package/dist/lib/download-job-manager.d.ts +64 -0
  41. package/dist/lib/download-job-manager.d.ts.map +1 -0
  42. package/dist/lib/download-job-manager.js +164 -0
  43. package/dist/lib/download-job-manager.js.map +1 -0
  44. package/dist/tui/MultiServerMonitorApp.js +1 -1
  45. package/dist/types/admin-config.d.ts +19 -0
  46. package/dist/types/admin-config.d.ts.map +1 -0
  47. package/dist/types/admin-config.js +3 -0
  48. package/dist/types/admin-config.js.map +1 -0
  49. package/dist/utils/log-parser.d.ts +9 -0
  50. package/dist/utils/log-parser.d.ts.map +1 -1
  51. package/dist/utils/log-parser.js +11 -0
  52. package/dist/utils/log-parser.js.map +1 -1
  53. package/package.json +10 -2
  54. package/web/README.md +429 -0
  55. package/web/dist/assets/index-Bin89Lwr.css +1 -0
  56. package/web/dist/assets/index-CVmonw3T.js +17 -0
  57. package/web/dist/index.html +14 -0
  58. package/web/dist/vite.svg +1 -0
  59. package/.versionrc.json +0 -16
  60. package/CHANGELOG.md +0 -203
  61. package/MONITORING-ACCURACY-FIX.md +0 -199
  62. package/PER-PROCESS-METRICS.md +0 -190
  63. package/docs/images/.gitkeep +0 -1
  64. package/src/cli.ts +0 -423
  65. package/src/commands/config-global.ts +0 -38
  66. package/src/commands/config.ts +0 -323
  67. package/src/commands/create.ts +0 -183
  68. package/src/commands/delete.ts +0 -74
  69. package/src/commands/list.ts +0 -37
  70. package/src/commands/logs-all.ts +0 -251
  71. package/src/commands/logs.ts +0 -321
  72. package/src/commands/monitor.ts +0 -110
  73. package/src/commands/ps.ts +0 -84
  74. package/src/commands/pull.ts +0 -44
  75. package/src/commands/rm.ts +0 -107
  76. package/src/commands/router/config.ts +0 -116
  77. package/src/commands/router/logs.ts +0 -256
  78. package/src/commands/router/restart.ts +0 -36
  79. package/src/commands/router/start.ts +0 -60
  80. package/src/commands/router/status.ts +0 -119
  81. package/src/commands/router/stop.ts +0 -33
  82. package/src/commands/run.ts +0 -233
  83. package/src/commands/search.ts +0 -107
  84. package/src/commands/server-show.ts +0 -161
  85. package/src/commands/show.ts +0 -207
  86. package/src/commands/start.ts +0 -101
  87. package/src/commands/stop.ts +0 -39
  88. package/src/commands/tui.ts +0 -25
  89. package/src/lib/config-generator.ts +0 -130
  90. package/src/lib/history-manager.ts +0 -172
  91. package/src/lib/launchctl-manager.ts +0 -225
  92. package/src/lib/metrics-aggregator.ts +0 -257
  93. package/src/lib/model-downloader.ts +0 -328
  94. package/src/lib/model-scanner.ts +0 -157
  95. package/src/lib/model-search.ts +0 -114
  96. package/src/lib/models-dir-setup.ts +0 -46
  97. package/src/lib/port-manager.ts +0 -80
  98. package/src/lib/router-logger.ts +0 -201
  99. package/src/lib/router-manager.ts +0 -414
  100. package/src/lib/router-server.ts +0 -538
  101. package/src/lib/state-manager.ts +0 -206
  102. package/src/lib/status-checker.ts +0 -113
  103. package/src/lib/system-collector.ts +0 -315
  104. package/src/tui/ConfigApp.ts +0 -1085
  105. package/src/tui/HistoricalMonitorApp.ts +0 -587
  106. package/src/tui/ModelsApp.ts +0 -368
  107. package/src/tui/MonitorApp.ts +0 -386
  108. package/src/tui/MultiServerMonitorApp.ts +0 -1833
  109. package/src/tui/RootNavigator.ts +0 -74
  110. package/src/tui/SearchApp.ts +0 -511
  111. package/src/tui/SplashScreen.ts +0 -149
  112. package/src/types/global-config.ts +0 -26
  113. package/src/types/history-types.ts +0 -39
  114. package/src/types/model-info.ts +0 -8
  115. package/src/types/monitor-types.ts +0 -162
  116. package/src/types/router-config.ts +0 -25
  117. package/src/types/server-config.ts +0 -46
  118. package/src/utils/downsample-utils.ts +0 -128
  119. package/src/utils/file-utils.ts +0 -146
  120. package/src/utils/format-utils.ts +0 -98
  121. package/src/utils/log-parser.ts +0 -271
  122. package/src/utils/log-utils.ts +0 -178
  123. package/src/utils/process-utils.ts +0 -316
  124. package/src/utils/prompt-utils.ts +0 -47
  125. package/test-load.sh +0 -100
  126. package/tsconfig.json +0 -20
@@ -1,587 +0,0 @@
1
- import blessed from 'blessed';
2
- import * as asciichart from 'asciichart';
3
- import { ServerConfig } from '../types/server-config.js';
4
- import { HistoryManager } from '../lib/history-manager.js';
5
- import { HistorySnapshot } from '../types/history-types.js';
6
- import {
7
- downsampleMaxTimeWithFullHour,
8
- downsampleMeanTimeWithFullHour,
9
- getDownsampleRatio,
10
- TimeSeriesPoint
11
- } from '../utils/downsample-utils.js';
12
-
13
- type ViewMode = 'recent' | 'hour';
14
-
15
- // Shared view mode across both history screens - persists for the session
16
- let sharedViewMode: ViewMode = 'recent';
17
-
18
- interface ChartStats {
19
- avg: number;
20
- max: number;
21
- min: number;
22
- stddev: number;
23
- }
24
-
25
- interface ChartConfig {
26
- title: string;
27
- color: typeof asciichart.cyan;
28
- formatValue: (x: number) => string;
29
- isPercentage: boolean;
30
- noDataMessage: string;
31
- }
32
-
33
- /**
34
- * Calculate statistics for a set of values.
35
- */
36
- function calculateStats(values: number[]): ChartStats {
37
- if (values.length === 0) {
38
- return { avg: 0, max: 0, min: 0, stddev: 0 };
39
- }
40
-
41
- const avg = values.reduce((a, b) => a + b, 0) / values.length;
42
- const max = Math.max(...values);
43
- const min = Math.min(...values);
44
- const variance = values.reduce((sum, val) => sum + Math.pow(val - avg, 2), 0) / values.length;
45
- const stddev = Math.sqrt(variance);
46
-
47
- return { avg, max, min, stddev };
48
- }
49
-
50
- /**
51
- * Calculate expanded range for chart y-axis to prevent duplicate labels.
52
- */
53
- function getExpandedRange(data: number[], isPercentage: boolean): { min: number; max: number } {
54
- if (isPercentage) return { min: 0, max: 100 };
55
- if (data.length === 0) return { min: 0, max: 10 };
56
-
57
- const dataMin = Math.min(...data);
58
- const dataMax = Math.max(...data);
59
- const range = dataMax - dataMin;
60
- const padding = Math.max(range * 0.3, 5);
61
-
62
- return {
63
- min: Math.max(0, Math.floor(dataMin - padding)),
64
- max: Math.ceil(dataMax + padding)
65
- };
66
- }
67
-
68
- /**
69
- * Create a scrollable content box for historical charts.
70
- */
71
- function createContentBox(): blessed.Widgets.BoxElement {
72
- return blessed.box({
73
- top: 0,
74
- left: 0,
75
- width: '100%',
76
- height: '100%',
77
- tags: true,
78
- scrollable: true,
79
- alwaysScroll: true,
80
- keys: true,
81
- vi: true,
82
- mouse: true,
83
- scrollbar: {
84
- ch: '\u2588',
85
- style: { fg: 'blue' },
86
- },
87
- });
88
- }
89
-
90
- /**
91
- * Render a single chart with statistics.
92
- */
93
- function renderChart(
94
- values: number[],
95
- rawValues: TimeSeriesPoint[],
96
- config: ChartConfig,
97
- chartHeight: number
98
- ): string {
99
- let content = `{bold}${config.title}{/bold}\n`;
100
- const validValues = values.filter(v => !isNaN(v) && v > 0);
101
- const plotData = values.map(v => isNaN(v) ? 0 : v);
102
-
103
- try {
104
- if (validValues.length >= 2) {
105
- const range = getExpandedRange(validValues, config.isPercentage);
106
- content += asciichart.plot(plotData, {
107
- height: chartHeight,
108
- colors: [config.color],
109
- format: config.formatValue,
110
- min: range.min,
111
- max: range.max,
112
- });
113
- content += '\n';
114
-
115
- const stats = calculateStats(validValues);
116
- const lastValue = rawValues[rawValues.length - 1]?.value ?? 0;
117
-
118
- if (config.title.includes('GB')) {
119
- content += ` Avg: ${stats.avg.toFixed(2)} GB (\u00b1${stats.stddev.toFixed(2)}) `;
120
- content += `Max: ${stats.max.toFixed(2)} GB `;
121
- content += `Min: ${stats.min.toFixed(2)} GB `;
122
- content += `Last: ${lastValue.toFixed(2)} GB\n\n`;
123
- } else if (config.isPercentage) {
124
- content += ` Avg: ${stats.avg.toFixed(1)}% (\u00b1${stats.stddev.toFixed(1)}) `;
125
- content += `Max: ${stats.max.toFixed(1)}% `;
126
- content += `Min: ${stats.min.toFixed(1)}% `;
127
- content += `Last: ${lastValue.toFixed(1)}%\n\n`;
128
- } else {
129
- content += ` Avg: ${stats.avg.toFixed(1)} tok/s (\u00b1${stats.stddev.toFixed(1)}) `;
130
- content += `Max: ${stats.max.toFixed(1)} tok/s `;
131
- content += `Last: ${lastValue.toFixed(1)} tok/s\n\n`;
132
- }
133
- } else {
134
- const defaultRange = config.isPercentage ? { min: 0, max: 100 } : { min: 0, max: 10 };
135
- content += asciichart.plot(plotData, {
136
- height: chartHeight,
137
- colors: [config.color],
138
- format: config.formatValue,
139
- min: defaultRange.min,
140
- max: defaultRange.max,
141
- });
142
- content += `\n{gray-fg} ${config.noDataMessage}{/gray-fg}\n\n`;
143
- }
144
- } catch {
145
- content += '{red-fg} Error rendering chart{/red-fg}\n\n';
146
- }
147
-
148
- return content;
149
- }
150
-
151
- export async function createHistoricalUI(
152
- screen: blessed.Widgets.Screen,
153
- server: ServerConfig,
154
- onBack: () => void
155
- ): Promise<void> {
156
- const historyManager = new HistoryManager(server.id);
157
- let refreshIntervalId: NodeJS.Timeout | null = null;
158
- const REFRESH_INTERVAL = 1000;
159
- let lastGoodRender: string | null = null;
160
- let consecutiveErrors = 0;
161
-
162
- const contentBox = createContentBox();
163
- screen.append(contentBox);
164
-
165
- // Chart configurations
166
- const chartConfigs: Record<string, ChartConfig> = {
167
- tokenSpeed: {
168
- title: 'Server Token Generation Speed (tok/s)',
169
- color: asciichart.cyan,
170
- formatValue: (x: number) => Math.round(x).toFixed(0).padStart(6, ' '),
171
- isPercentage: false,
172
- noDataMessage: 'No generation activity in this time window',
173
- },
174
- cpu: {
175
- title: 'Server CPU Usage (%)',
176
- color: asciichart.blue,
177
- formatValue: (x: number) => Math.round(x).toFixed(0).padStart(6, ' '),
178
- isPercentage: true,
179
- noDataMessage: 'No CPU data in this time window',
180
- },
181
- memory: {
182
- title: 'Server Memory Usage (GB)',
183
- color: asciichart.magenta,
184
- formatValue: (x: number) => x.toFixed(2).padStart(6, ' '),
185
- isPercentage: false,
186
- noDataMessage: 'No memory data in this time window',
187
- },
188
- gpu: {
189
- title: 'System GPU Usage (%)',
190
- color: asciichart.green,
191
- formatValue: (x: number) => Math.round(x).toFixed(0).padStart(6, ' '),
192
- isPercentage: true,
193
- noDataMessage: 'No GPU data in this time window',
194
- },
195
- };
196
-
197
- async function render(): Promise<void> {
198
- try {
199
- const termWidth = (screen.width as number) || 80;
200
- const divider = '\u2500'.repeat(termWidth - 2);
201
- const chartHeight = 5;
202
- const chartWidth = Math.min(Math.max(termWidth - 20, 40), 80);
203
-
204
- if (chartWidth <= 0 || !Number.isFinite(chartWidth)) {
205
- contentBox.setContent('{red-fg}Error: Invalid chart width{/red-fg}\n');
206
- screen.render();
207
- return;
208
- }
209
-
210
- // Header
211
- const modeLabel = sharedViewMode === 'recent' ? 'Minute' : 'Hour';
212
- const modeColor = sharedViewMode === 'recent' ? 'cyan' : 'magenta';
213
- let content = `{bold}{blue-fg}\u2550\u2550\u2550 ${server.modelName} (${server.port}) {/blue-fg} `;
214
- content += `{${modeColor}-fg}[${modeLabel}]{/${modeColor}-fg}{/bold}\n\n`;
215
-
216
- const snapshots = await historyManager.loadHistoryByWindow('1h');
217
-
218
- if (snapshots.length === 0) {
219
- content += '{yellow-fg}No historical data available.{/yellow-fg}\n\n';
220
- content += 'Historical data is collected when you run the monitor command.\n';
221
- content += 'Start monitoring to begin collecting history.\n\n';
222
- content += divider + '\n';
223
- content += '{gray-fg}[ESC] Back [Q]uit{/gray-fg}';
224
- contentBox.setContent(content);
225
- screen.render();
226
- return;
227
- }
228
-
229
- const maxChartPoints = Math.min(chartWidth, 80);
230
- const displaySnapshots = sharedViewMode === 'recent' && snapshots.length > maxChartPoints
231
- ? snapshots.slice(-maxChartPoints)
232
- : snapshots;
233
-
234
- // Extract time-series data
235
- const rawData = {
236
- tokenSpeed: [] as TimeSeriesPoint[],
237
- gpu: [] as TimeSeriesPoint[],
238
- cpu: [] as TimeSeriesPoint[],
239
- memory: [] as TimeSeriesPoint[],
240
- };
241
-
242
- for (const snapshot of displaySnapshots) {
243
- const ts = snapshot.timestamp;
244
- rawData.tokenSpeed.push({ timestamp: ts, value: snapshot.server.avgGenerateSpeed || 0 });
245
- rawData.gpu.push({ timestamp: ts, value: snapshot.system?.gpuUsage || 0 });
246
- rawData.cpu.push({ timestamp: ts, value: snapshot.server.processCpuUsage || 0 });
247
- rawData.memory.push({
248
- timestamp: ts,
249
- value: snapshot.server.processMemory ? snapshot.server.processMemory / (1024 ** 3) : 0
250
- });
251
- }
252
-
253
- // Apply downsampling based on view mode
254
- const useDownsampling = sharedViewMode === 'hour';
255
- const values = {
256
- tokenSpeed: useDownsampling
257
- ? downsampleMaxTimeWithFullHour(rawData.tokenSpeed, maxChartPoints)
258
- : rawData.tokenSpeed.map(p => p.value),
259
- cpu: useDownsampling
260
- ? downsampleMaxTimeWithFullHour(rawData.cpu, maxChartPoints)
261
- : rawData.cpu.map(p => p.value),
262
- memory: useDownsampling
263
- ? downsampleMeanTimeWithFullHour(rawData.memory, maxChartPoints)
264
- : rawData.memory.map(p => p.value),
265
- gpu: useDownsampling
266
- ? downsampleMaxTimeWithFullHour(rawData.gpu, maxChartPoints)
267
- : rawData.gpu.map(p => p.value),
268
- };
269
-
270
- // Render all charts
271
- content += renderChart(values.tokenSpeed, rawData.tokenSpeed, chartConfigs.tokenSpeed, chartHeight);
272
- content += renderChart(values.cpu, rawData.cpu, chartConfigs.cpu, chartHeight);
273
- content += renderChart(values.memory, rawData.memory, chartConfigs.memory, chartHeight);
274
- content += renderChart(values.gpu, rawData.gpu, chartConfigs.gpu, chartHeight);
275
-
276
- // Footer
277
- content += divider + '\n';
278
- content += `{gray-fg}[T]oggle Hour View [ESC] Back [Q]uit{/gray-fg}`;
279
-
280
- contentBox.setContent(content);
281
- screen.render();
282
-
283
- lastGoodRender = content;
284
- consecutiveErrors = 0;
285
- } catch (error) {
286
- consecutiveErrors++;
287
- if (lastGoodRender && consecutiveErrors < 5) {
288
- contentBox.setContent(lastGoodRender);
289
- } else {
290
- const errorMsg = error instanceof Error ? error.message : String(error);
291
- contentBox.setContent(
292
- '{bold}{red-fg}Render Error{/red-fg}{/bold}\n\n' +
293
- `{red-fg}${errorMsg}{/red-fg}\n\n` +
294
- `Consecutive errors: ${consecutiveErrors}\n\n` +
295
- '{gray-fg}[ESC] Back [Q]uit{/gray-fg}'
296
- );
297
- }
298
- screen.render();
299
- }
300
- }
301
-
302
- function cleanup(): void {
303
- if (refreshIntervalId) {
304
- clearInterval(refreshIntervalId);
305
- refreshIntervalId = null;
306
- }
307
- unregisterHandlers();
308
- }
309
-
310
- // Key handler functions (stored for unregistration)
311
- const keyHandlers = {
312
- toggle: () => {
313
- sharedViewMode = sharedViewMode === 'recent' ? 'hour' : 'recent';
314
- render();
315
- },
316
- escape: () => {
317
- cleanup();
318
- screen.remove(contentBox);
319
- onBack();
320
- },
321
- quit: () => {
322
- cleanup();
323
- screen.destroy();
324
- process.exit(0);
325
- },
326
- };
327
-
328
- function registerHandlers(): void {
329
- screen.key(['t', 'T'], keyHandlers.toggle);
330
- screen.key(['escape'], keyHandlers.escape);
331
- screen.key(['q', 'Q', 'C-c'], keyHandlers.quit);
332
- }
333
-
334
- function unregisterHandlers(): void {
335
- screen.unkey('t', keyHandlers.toggle);
336
- screen.unkey('T', keyHandlers.toggle);
337
- screen.unkey('escape', keyHandlers.escape);
338
- screen.unkey('q', keyHandlers.quit);
339
- screen.unkey('Q', keyHandlers.quit);
340
- screen.unkey('C-c', keyHandlers.quit);
341
- }
342
-
343
- registerHandlers();
344
-
345
- contentBox.setContent('{cyan-fg}\u23f3 Loading historical data...{/cyan-fg}');
346
- screen.render();
347
- await render();
348
-
349
- refreshIntervalId = setInterval(render, REFRESH_INTERVAL);
350
- }
351
-
352
- // Multi-server historical view
353
- export async function createMultiServerHistoricalUI(
354
- screen: blessed.Widgets.Screen,
355
- servers: ServerConfig[],
356
- _selectedIndex: number,
357
- onBack: () => void
358
- ): Promise<void> {
359
- let refreshIntervalId: NodeJS.Timeout | null = null;
360
- const REFRESH_INTERVAL = 3000;
361
- let lastGoodRender: string | null = null;
362
- let consecutiveErrors = 0;
363
-
364
- const contentBox = createContentBox();
365
- screen.append(contentBox);
366
-
367
- // Chart configurations for multi-server view
368
- const chartConfigs: Record<string, ChartConfig> = {
369
- tokenSpeed: {
370
- title: 'Total Server Token Generation Speed (tok/s)',
371
- color: asciichart.cyan,
372
- formatValue: (x: number) => Math.round(x).toFixed(0).padStart(6, ' '),
373
- isPercentage: false,
374
- noDataMessage: 'No generation activity in this time window',
375
- },
376
- cpu: {
377
- title: 'Total Server CPU Usage (%)',
378
- color: asciichart.blue,
379
- formatValue: (x: number) => Math.round(x).toFixed(0).padStart(6, ' '),
380
- isPercentage: true,
381
- noDataMessage: 'No CPU data in this time window',
382
- },
383
- memory: {
384
- title: 'Total Server Memory Usage (GB)',
385
- color: asciichart.magenta,
386
- formatValue: (x: number) => x.toFixed(2).padStart(6, ' '),
387
- isPercentage: false,
388
- noDataMessage: 'No memory data in this time window',
389
- },
390
- gpu: {
391
- title: 'System GPU Usage (%)',
392
- color: asciichart.green,
393
- formatValue: (x: number) => Math.round(x).toFixed(0).padStart(6, ' '),
394
- isPercentage: true,
395
- noDataMessage: 'No GPU data in this time window',
396
- },
397
- };
398
-
399
- async function render(): Promise<void> {
400
- try {
401
- const termWidth = (screen.width as number) || 80;
402
- const divider = '\u2500'.repeat(termWidth - 2);
403
- const chartWidth = Math.min(Math.max(40, termWidth - 20), 80);
404
- const chartHeight = 5;
405
-
406
- // Header
407
- const modeLabel = sharedViewMode === 'recent' ? 'Minute' : 'Hour';
408
- const modeColor = sharedViewMode === 'recent' ? 'cyan' : 'magenta';
409
- let content = `{bold}{blue-fg}\u2550\u2550\u2550 All servers (${servers.length}){/blue-fg} `;
410
- content += `{${modeColor}-fg}[${modeLabel}]{/${modeColor}-fg}{/bold}\n\n`;
411
-
412
- // Load and aggregate history for all servers
413
- const serverHistories = await Promise.all(
414
- servers.map(async (server) => {
415
- const manager = new HistoryManager(server.id);
416
- return manager.loadHistoryByWindow('1h');
417
- })
418
- );
419
-
420
- // Aggregate data across all servers at each timestamp
421
- const ALIGNMENT_INTERVAL = 2000;
422
- const timestampMap = new Map<number, {
423
- tokensPerSec: number[];
424
- gpuUsage: number[];
425
- cpuUsage: number[];
426
- memoryGB: number[];
427
- }>();
428
-
429
- for (const snapshots of serverHistories) {
430
- for (const snapshot of snapshots) {
431
- const timestamp = Math.round(snapshot.timestamp / ALIGNMENT_INTERVAL) * ALIGNMENT_INTERVAL;
432
-
433
- if (!timestampMap.has(timestamp)) {
434
- timestampMap.set(timestamp, {
435
- tokensPerSec: [],
436
- gpuUsage: [],
437
- cpuUsage: [],
438
- memoryGB: [],
439
- });
440
- }
441
-
442
- const data = timestampMap.get(timestamp)!;
443
-
444
- if (snapshot.server.avgGenerateSpeed && snapshot.server.avgGenerateSpeed > 0) {
445
- data.tokensPerSec.push(snapshot.server.avgGenerateSpeed);
446
- }
447
- if (snapshot.system?.gpuUsage !== undefined) {
448
- data.gpuUsage.push(snapshot.system.gpuUsage);
449
- }
450
- if (snapshot.server.processCpuUsage !== undefined) {
451
- data.cpuUsage.push(snapshot.server.processCpuUsage);
452
- }
453
- if (snapshot.server.processMemory) {
454
- data.memoryGB.push(snapshot.server.processMemory / (1024 ** 3));
455
- }
456
- }
457
- }
458
-
459
- // Sort timestamps and aggregate
460
- const timestamps = Array.from(timestampMap.keys()).sort((a, b) => a - b);
461
- const aggregatedData = timestamps.map(ts => {
462
- const data = timestampMap.get(ts)!;
463
- return {
464
- timestamp: ts,
465
- totalTokS: data.tokensPerSec.reduce((a, b) => a + b, 0),
466
- avgGpu: data.gpuUsage.length > 0
467
- ? data.gpuUsage.reduce((a, b) => a + b, 0) / data.gpuUsage.length
468
- : 0,
469
- totalCpu: data.cpuUsage.reduce((a, b) => a + b, 0),
470
- totalMemoryGB: data.memoryGB.reduce((a, b) => a + b, 0),
471
- };
472
- });
473
-
474
- if (aggregatedData.length > 0) {
475
- const maxPoints = Math.min(chartWidth, 80);
476
- const displayData = sharedViewMode === 'recent' && aggregatedData.length > maxPoints
477
- ? aggregatedData.slice(-maxPoints)
478
- : aggregatedData;
479
-
480
- const useDownsampling = sharedViewMode === 'hour';
481
-
482
- // Extract time-series data
483
- const rawData = {
484
- tokenSpeed: displayData.map(d => ({ timestamp: d.timestamp, value: d.totalTokS })),
485
- cpu: displayData.map(d => ({ timestamp: d.timestamp, value: d.totalCpu })),
486
- memory: displayData.map(d => ({ timestamp: d.timestamp, value: d.totalMemoryGB })),
487
- gpu: displayData.map(d => ({ timestamp: d.timestamp, value: d.avgGpu })),
488
- };
489
-
490
- // Apply downsampling
491
- const values = {
492
- tokenSpeed: useDownsampling
493
- ? downsampleMaxTimeWithFullHour(rawData.tokenSpeed, chartWidth)
494
- : rawData.tokenSpeed.map(d => d.value),
495
- cpu: useDownsampling
496
- ? downsampleMaxTimeWithFullHour(rawData.cpu, chartWidth)
497
- : rawData.cpu.map(d => d.value),
498
- memory: useDownsampling
499
- ? downsampleMeanTimeWithFullHour(rawData.memory, chartWidth)
500
- : rawData.memory.map(d => d.value),
501
- gpu: useDownsampling
502
- ? downsampleMaxTimeWithFullHour(rawData.gpu, chartWidth)
503
- : rawData.gpu.map(d => d.value),
504
- };
505
-
506
- // Render all charts
507
- content += renderChart(values.tokenSpeed, rawData.tokenSpeed, chartConfigs.tokenSpeed, chartHeight);
508
- content += renderChart(values.cpu, rawData.cpu, chartConfigs.cpu, chartHeight);
509
- content += renderChart(values.memory, rawData.memory, chartConfigs.memory, chartHeight);
510
- content += renderChart(values.gpu, rawData.gpu, chartConfigs.gpu, chartHeight);
511
- }
512
-
513
- // Footer
514
- content += divider + '\n';
515
- content += `{gray-fg}[T]oggle Hour View [ESC] Back [Q]uit{/gray-fg}`;
516
-
517
- contentBox.setContent(content);
518
- screen.render();
519
-
520
- lastGoodRender = content;
521
- consecutiveErrors = 0;
522
- } catch (error) {
523
- consecutiveErrors++;
524
- if (lastGoodRender && consecutiveErrors < 5) {
525
- contentBox.setContent(lastGoodRender);
526
- } else {
527
- const errorMsg = error instanceof Error ? error.message : String(error);
528
- contentBox.setContent(
529
- '{bold}{red-fg}Render Error{/red-fg}{/bold}\n\n' +
530
- `{red-fg}${errorMsg}{/red-fg}\n\n` +
531
- `Consecutive errors: ${consecutiveErrors}\n\n` +
532
- '{gray-fg}[ESC] Back [Q]uit{/gray-fg}'
533
- );
534
- }
535
- screen.render();
536
- }
537
- }
538
-
539
- function cleanup(): void {
540
- if (refreshIntervalId) {
541
- clearInterval(refreshIntervalId);
542
- refreshIntervalId = null;
543
- }
544
- unregisterHandlers();
545
- }
546
-
547
- // Key handler functions (stored for unregistration)
548
- const keyHandlers = {
549
- toggle: () => {
550
- sharedViewMode = sharedViewMode === 'recent' ? 'hour' : 'recent';
551
- render();
552
- },
553
- escape: () => {
554
- cleanup();
555
- screen.remove(contentBox);
556
- onBack();
557
- },
558
- quit: () => {
559
- cleanup();
560
- screen.destroy();
561
- process.exit(0);
562
- },
563
- };
564
-
565
- function registerHandlers(): void {
566
- screen.key(['t', 'T'], keyHandlers.toggle);
567
- screen.key(['escape'], keyHandlers.escape);
568
- screen.key(['q', 'Q', 'C-c'], keyHandlers.quit);
569
- }
570
-
571
- function unregisterHandlers(): void {
572
- screen.unkey('t', keyHandlers.toggle);
573
- screen.unkey('T', keyHandlers.toggle);
574
- screen.unkey('escape', keyHandlers.escape);
575
- screen.unkey('q', keyHandlers.quit);
576
- screen.unkey('Q', keyHandlers.quit);
577
- screen.unkey('C-c', keyHandlers.quit);
578
- }
579
-
580
- registerHandlers();
581
-
582
- contentBox.setContent('{cyan-fg}\u23f3 Loading historical data...{/cyan-fg}');
583
- screen.render();
584
- await render();
585
-
586
- refreshIntervalId = setInterval(render, REFRESH_INTERVAL);
587
- }