forked from anubhavgupta/llama-cpp-manager
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
364 lines (323 loc) · 11.9 KB
/
Copy pathserver.js
File metadata and controls
364 lines (323 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
const express = require('express');
const { spawn, execSync } = require('child_process');
const path = require('path');
const fs = require('fs').promises;
const os = require('os');
const osUtils = require('os-utils');
const app = express();
const PORT = 3001;
// Middleware
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Store the running process
let runningProcess = null;
let connectedClients = [];
// Create WebSocket server for log streaming
const httpServer = require('http').createServer(app);
const io = require('socket.io')(httpServer, {
cors: {
origin: "*"
}
});
// System monitoring variables
let systemMetrics = {
cpu: { usage: 0, history: [] },
ram: { usage: 0, total: 0, free: 0, history: [] },
gpu: { usage: 0, memory: 0, history: [] },
vram: { usage: 0, total: 0, free: 0, history: [] }
};
// Function to get system metrics
async function getSystemMetrics() {
// CPU Usage using os-utils for more accurate readings
let cpuUsage = 0;
try {
// Use os-utils for better CPU monitoring
cpuUsage = await new Promise((res, rej)=>{
osUtils.cpuUsage((usage)=>{
res(usage * 100);
});
});
} catch (error) {
// Fallback to manual calculation if os-utils fails
const cpus = os.cpus();
let totalIdle = 0;
let totalTick = 0;
cpus.forEach(cpu => {
const times = cpu.times;
totalIdle += times.idle;
totalTick += Object.values(times).reduce((a, b) => a + b, 0);
});
const idlePercentage = (totalIdle / cpus.length) / (totalTick / cpus.length) * 100;
cpuUsage = Math.max(0, 100 - idlePercentage);
}
// RAM Usage
const totalMemory = os.totalmem();
const freeMemory = os.freemem();
const usedMemory = totalMemory - freeMemory;
const ramUsage = (usedMemory / totalMemory) * 100;
// GPU and VRAM Usage - Parse nvidia-smi output
let gpuUsage = 0;
let vramUsage = 0;
let vramTotal = 0;
let vramFree = 0;
try {
// Parse the output to extract VRAM usage for the current process
// For now, we'll use a simpler approach - get general GPU info
const smiOutput = execSync('nvidia-smi --query-gpu=utilization.gpu,memory.total,memory.used --format=csv,noheader,nounits', { encoding: 'utf8' });
if (smiOutput) {
const lines = smiOutput.trim().split('\n');
if (lines.length > 0) {
const line = lines[0].trim();
const parts = line.split(',').map(p => p.trim());
if (parts.length >= 3) {
gpuUsage = parseFloat(parts[0]) || 0;
vramTotal = parseFloat(parts[1]) || 0;
const vramUsed = parseFloat(parts[2]) || 0;
vramFree = vramTotal - vramUsed;
vramUsage = (vramUsed / vramTotal) * 100 || 0;
}
}
}
} catch (error) {
// If nvidia-smi fails, fall back to simulated values
console.log('Failed to get GPU/VRAM data from nvidia-smi:', error.message);
gpuUsage = Math.random() * 100; // Simulated fallback
vramUsage = Math.random() * 100; // Simulated fallback
vramTotal = 8 * 1024 * 1024 * 1024 / (1024 * 1024); // 8GB in MB
vramFree = vramTotal * (1 - vramUsage / 100);
}
return {
cpu: cpuUsage,
ram: ramUsage,
gpu: gpuUsage,
vram: vramUsage,
totalMemory: totalMemory,
freeMemory: freeMemory,
vramTotal: vramTotal,
vramFree: vramFree
};
}
// Function to update system metrics history
async function updateSystemMetricsHistory() {
const metrics = await getSystemMetrics();
// Update CPU history (keep last 50 points)
systemMetrics.cpu.usage = metrics.cpu;
if (systemMetrics.cpu.history.length >= 50) {
systemMetrics.cpu.history.shift();
}
systemMetrics.cpu.history.push(metrics.cpu);
// Update RAM history
systemMetrics.ram.usage = metrics.ram;
systemMetrics.ram.total = metrics.totalMemory;
systemMetrics.ram.free = metrics.freeMemory;
if (systemMetrics.ram.history.length >= 50) {
systemMetrics.ram.history.shift();
}
systemMetrics.ram.history.push(metrics.ram);
// Update GPU history
systemMetrics.gpu.usage = metrics.gpu;
systemMetrics.gpu.memory = metrics.vramTotal - metrics.vramFree; // Used VRAM in bytes
if (systemMetrics.gpu.history.length >= 50) {
systemMetrics.gpu.history.shift();
}
systemMetrics.gpu.history.push(metrics.gpu);
// Update VRAM history
systemMetrics.vram.usage = metrics.vram;
systemMetrics.vram.total = metrics.vramTotal;
systemMetrics.vram.free = metrics.vramFree;
if (systemMetrics.vram.history.length >= 50) {
systemMetrics.vram.history.shift();
}
systemMetrics.vram.history.push(metrics.vram);
}
// Start periodic system metrics collection
setInterval(updateSystemMetricsHistory, 1000); // Update every second
// Function to recursively find GGUF files
async function findGGUFFiles(directory) {
const ggufFiles = [];
const basePath = directory || "C:\\Users\\anubh\\.lmstudio\\models";
try {
// Check if directory exists
await fs.access(basePath);
async function searchDirectory(dir) {
try {
const items = await fs.readdir(dir, { withFileTypes: true });
for (const item of items) {
const itemPath = path.join(dir, item.name);
if (item.isDirectory()) {
// Recursively search subdirectories
await searchDirectory(itemPath);
} else if (item.isFile() && item.name.toLowerCase().endsWith('.gguf')) {
// Add GGUF file with relative path
const relativePath = path.relative(basePath, itemPath);
ggufFiles.push({
name: item.name,
path: itemPath,
relativePath: relativePath
});
}
}
} catch (error) {
console.error(`Error reading directory ${dir}:`, error);
}
}
await searchDirectory(basePath);
} catch (error) {
console.error('Error accessing models directory:', error);
}
return ggufFiles;
}
// Serve the main page
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// API endpoint to start the llama server
app.post('/start', (req, res) => {
const { serverPath, args = [] } = req.body;
if (!serverPath) {
return res.status(400).json({
success: false,
error: 'Server path is required'
});
}
// Check if process is already running
if (runningProcess) {
return res.json({
success: false,
error: 'Server is already running'
});
}
// Start the server using spawn for better process control
try {
console.log('Starting server with args:', args);
runningProcess = spawn(serverPath, args, { stdio: 'pipe' });
// Handle process events
runningProcess.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
runningProcess = null;
// Notify clients that the process has ended
connectedClients.forEach(client => {
client.emit('server-ended', { message: 'Server process has ended' });
});
});
runningProcess.on('error', (error) => {
console.error(`Failed to start process: ${error}`);
runningProcess = null;
// Notify clients of error
connectedClients.forEach(client => {
client.emit('server-error', { message: 'Failed to start server: ' + error.message });
});
});
// Stream stdout and stderr to connected clients
if (runningProcess.stdout) {
runningProcess.stdout.on('data', (data) => {
const logData = data.toString();
console.log('STDOUT:', logData);
// Broadcast to all connected clients
connectedClients.forEach(client => {
client.emit('log-stream', { type: 'stdout', data: logData });
});
});
}
if (runningProcess.stderr) {
runningProcess.stderr.on('data', (data) => {
const logData = data.toString();
console.log('STDERR:', logData);
// Broadcast to all connected clients
connectedClients.forEach(client => {
client.emit('log-stream', { type: 'stderr', data: logData });
});
});
}
res.json({
success: true,
message: 'Server started successfully'
});
} catch (error) {
res.status(500).json({
success: false,
error: `Failed to start server: ${error.message}`
});
}
});
// API endpoint to stop the llama server
app.post('/stop', (req, res) => {
if (!runningProcess) {
return res.json({
success: false,
error: 'No server is currently running'
});
}
// Kill the process gracefully
try {
// Check if process is still running before attempting to kill
if (runningProcess && !runningProcess.killed) {
runningProcess.kill('SIGTERM'); // Try graceful shutdown first
setTimeout(() => {
if (runningProcess && !runningProcess.killed) {
runningProcess.kill('SIGKILL'); // Force kill if still running
}
}, 1000);
}
runningProcess = null;
res.json({
success: true,
message: 'Server stopped successfully'
});
} catch (error) {
res.status(500).json({
success: false,
error: `Failed to stop server: ${error.message}`
});
}
});
// API endpoint to get available GGUF models
app.get('/models', async (req, res) => {
try {
const models = await findGGUFFiles();
res.json({
success: true,
models: models
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Failed to fetch models: ' + error.message
});
}
});
// API endpoint to get system metrics
app.get('/metrics', (req, res) => {
// Return just the current values for CPU, RAM, GPU, and VRAM
res.json({
cpu: systemMetrics.cpu.usage,
ram: systemMetrics.ram.usage,
gpu: systemMetrics.gpu.usage,
vram: systemMetrics.vram.usage,
vramUsage: `${systemMetrics.vram.total - systemMetrics.vram.free}/${systemMetrics.vram.total}`,
});
});
// API endpoint to check if server is running
app.get('/status', (req, res) => {
res.json({
running: !!runningProcess && !runningProcess.killed
});
});
// WebSocket connection handling for log streaming
io.on('connection', (socket) => {
console.log('Client connected for log streaming');
connectedClients.push(socket);
// Remove client when disconnected
socket.on('disconnect', () => {
console.log('Client disconnected from log streaming');
const index = connectedClients.indexOf(socket);
if (index > -1) {
connectedClients.splice(index, 1);
}
});
});
// Start the server with WebSocket support
httpServer.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});