yangtb24 commited on
Commit
e13ea2e
·
verified ·
1 Parent(s): e1df466

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -148
app.py CHANGED
@@ -1,5 +1,8 @@
1
  from flask import Flask, render_template_string
2
- import os
 
 
 
3
 
4
  app = Flask(__name__)
5
 
@@ -201,7 +204,6 @@ htmlTemplate = f"""
201
  <meta charset="UTF-8">
202
  <title>HF Space Monitor</title>
203
  <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>">
204
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" integrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
205
  <style>{lightModeStyle}</style>
206
  </head>
207
  <body>
@@ -220,157 +222,90 @@ htmlTemplate = f"""
220
  <!-- 服务器卡片将在这里动态生成 -->
221
  </div>
222
  </div>
223
- <script>
224
- const username = 'yangtb24';
225
-
226
- async function fetchInstances() {
227
- try {
228
- const response = await fetch(`https://huggingface.co/api/spaces?author=${username}`);
229
- const userInstances = await response.json();
230
- return userInstances.map(instance => ({{
231
- id: instance.id.split('/')[1],
232
- owner: username
233
- }}));
234
- } catch (error) {
235
- console.error("获取实例列表失败:", error);
236
- return [];
237
- }
238
- }}
239
-
240
- class MetricsManager {{
241
- constructor() {{
242
- this.eventSources = new Map();
243
- this.servers = new Map();
244
- this.instanceOwners = new Map();
245
- this.spaceIds = new Map();
246
- }}
247
-
248
- async connect(instanceId, username) {{
249
- if (this.eventSources.has(instanceId)) return;
250
-
251
- try {{
252
- const eventSource = new EventSource(
253
- `https://api.hf.space/v1/${{username}}/${{instanceId}}/live-metrics/sse`
254
- );
255
-
256
- this.spaceIds.set(instanceId, instanceId);
257
- this.instanceOwners.set(instanceId, username);
258
-
259
- eventSource.addEventListener("metric", (event) => {{
260
- try {{
261
- const data = JSON.parse(event.data);
262
- updateServerCard(data, instanceId);
263
- }} catch (error) {{
264
- console.error(`解析数据失败 (${{instanceId}}):`, error);
265
- }}
266
- }});
267
-
268
- eventSource.onerror = (error) => {{
269
- console.error(`EventSource 错误 (${{instanceId}}):`, error);
270
- eventSource.close();
271
- }};
272
-
273
- this.eventSources.set(instanceId, eventSource);
274
- }} catch (error) {{
275
- console.error(`连接失败 (${{username}}/${{instanceId}}):`, error);
276
- }}
277
- }}
278
-
279
- disconnectAll() {{
280
- this.eventSources.forEach(es => es.close());
281
- this.eventSources.clear();
282
- }}
283
- }}
284
 
285
- const metricsManager = new MetricsManager();
286
- const servers = new Map();
287
-
288
- async function initialize() {{
289
- const instances = await fetchInstances();
290
- instances.forEach(instance => {{
291
- metricsManager.connect(instance.id, instance.owner);
292
- }});
293
- }}
294
-
295
- initialize();
296
 
297
  function updateServerCard(data, spaceId) {{
298
- const serverId = data.replica;
299
- const serverElement = document.getElementById(`server-${{serverId}}`);
300
- const owner = metricsManager.instanceOwners.get(spaceId);
301
-
302
- if (!serverElement) {{
303
- const card = document.createElement('div');
304
- card.id = `server-${{serverId}}`;
305
- card.className = 'server-card';
306
- card.innerHTML = `
307
- <div class="server-header">
308
- <div class="server-name">
309
- <div class="status-dot status-online"></div>
310
- <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
311
- <path d="M21 3H3C1.9 3 1 3.9 1 5v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 5H4V6h16v2zm1 4H3c-1.1 0-2 .9-2 2v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-3c0-1.1-.9-2-2-2zm-1 5H4v-2h16v2zm1 4H3c-1.1 0-2 .9-2 2v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-3c0-1.1-.9-2-2-2zm-1 5H4v-2h16v2z"/>
312
- </svg>
313
- <div>${{serverId}} (${{owner}}/${{spaceId}})</div>
314
- </div>
315
- </div>
316
- <div class="metric-grid">
317
- <div class="metric-item">
318
- <div class="metric-label">CPU</div>
319
- <div class="progress-bar-container">
320
- <div class="cpu-progress-bar"></div>
321
  </div>
322
- <div class="metric-value cpu-usage">0%</div>
323
- </div>
324
- <div class="metric-item">
325
- <div class="metric-label">内存</div>
326
- <div class="progress-bar-container">
327
- <div class="memory-progress-bar"></div>
 
 
328
  </div>
329
- <div class="metric-value memory-usage">0%</div>
330
- </div>
331
- <div class="metric-item">
332
- <div class="metric-label">上传</div>
333
- <div class="metric-value upload">0 KB/s</div>
334
- </div>
335
- <div class="metric-item">
336
- <div class="metric-label">下载</div>
337
- <div class="metric-value download">0 KB/s</div>
338
- </div>
339
- </div>
340
- `;
341
- document.getElementById('servers').appendChild(card);
342
- }}
343
-
344
- const card = document.getElementById(`server-${{serverId}}`);
345
- const cpuUsage = data.cpu_usage_pct;
346
- const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
347
- const uploadBps = data.tx_bps;
348
- const downloadBps = data.rx_bps;
349
 
350
- card.querySelector('.cpu-usage').textContent = `${{cpuUsage.toFixed(2)}}%`;
351
- card.querySelector('.cpu-progress-bar').style.width = `${{cpuUsage}}%`;
 
 
 
352
 
353
- card.querySelector('.memory-usage').textContent = `${{memoryUsage.toFixed(2)}}%`;
354
- card.querySelector('.memory-progress-bar').style.width = `${{memoryUsage}}%`;
355
 
356
- card.querySelector('.upload').textContent = `${{formatBytes(uploadBps)}}/s`;
 
357
 
358
- card.querySelector('.download').textContent = `${{formatBytes(downloadBps)}}/s`;
 
359
 
360
- servers.set(serverId, Date.now());
361
- updateSummary();
362
  }}
363
 
364
  function updateSummary() {{
365
- const now = Date.now();
366
- let online = 0;
367
- let offline = 0;
368
- let totalUpload = 0;
369
- let totalDownload = 0;
370
-
371
- servers.forEach((lastSeen, serverId) => {{
 
372
  const isOnline = (now - lastSeen) < 10000;
373
- const serverCard = document.getElementById(`server-${{serverId}}`);
 
374
  if (serverCard) {{
375
  const statusDot = serverCard.querySelector('.status-dot');
376
  statusDot.className = `status-dot status-${{isOnline ? 'online' : 'offline'}}`;
@@ -383,15 +318,16 @@ htmlTemplate = f"""
383
  }}
384
  }}
385
  isOnline ? online++ : offline++;
386
- }});
387
 
388
- document.getElementById('totalServers').textContent = servers.size;
389
  document.getElementById('onlineServers').textContent = online;
390
  document.getElementById('offlineServers').textContent = offline;
391
  document.getElementById('totalUpload').textContent = `${{formatBytes(totalUpload)}}/s`;
392
  document.getElementById('totalDownload').textContent = `${{formatBytes(totalDownload)}}/s`;
393
  }}
394
 
 
395
  function formatBytes(bytes) {{
396
  if (bytes === 0) return '0 B';
397
  const k = 1024;
@@ -400,21 +336,98 @@ htmlTemplate = f"""
400
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
401
  }}
402
 
403
- setInterval(updateSummary, 2000);
404
 
405
- setInterval(async () => {{
406
- metricsManager.disconnectAll();
407
- await initialize();
408
- }}, 300000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
  </script>
411
  </body>
412
  </html>
413
  """
414
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  @app.route("/")
416
  def home():
417
- return render_template_string(htmlTemplate)
 
 
 
 
 
 
 
 
 
 
 
 
418
 
419
  if __name__ == "__main__":
420
- app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))
 
 
1
  from flask import Flask, render_template_string
2
+ import requests
3
+ import threading
4
+ import time
5
+ from concurrent.futures import ThreadPoolExecutor
6
 
7
  app = Flask(__name__)
8
 
 
204
  <meta charset="UTF-8">
205
  <title>HF Space Monitor</title>
206
  <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>">
 
207
  <style>{lightModeStyle}</style>
208
  </head>
209
  <body>
 
222
  <!-- 服务器卡片将在这里动态生成 -->
223
  </div>
224
  </div>
225
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/js/all.min.js" integrity="sha512-yFjZbTYRCJodnuyGlsKamNE/LlEaEA/3uWCGാരി7eIq7jWqVl3J8jL/kof/tfu9Xqzh/y/VM5sJd/tq5iEew==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ <script>
228
+ const username = '{{ username }}';
229
+ const serversData = {{ servers_data|tojson }};
 
 
 
 
 
 
 
 
230
 
231
  function updateServerCard(data, spaceId) {{
232
+ const serverId = data.replica;
233
+ const serverElement = document.getElementById(`server-${serverId}`);
234
+ const owner = '{{ username }}';
235
+
236
+ if (!serverElement) {{
237
+ const card = document.createElement('div');
238
+ card.id = `server-${serverId}`;
239
+ card.className = 'server-card';
240
+ card.innerHTML = `
241
+ <div class="server-header">
242
+ <div class="server-name">
243
+ <div class="status-dot status-online"></div>
244
+ <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
245
+ <path d="M21 3H3C1.9 3 1 3.9 1 5v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 5H4V6h16v2zm1 4H3c-1.1 0-2 .9-2 2v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-3c0-1.1-.9-2-2-2zm-1 5H4v-2h16v2zm1 4H3c-1.1 0-2 .9-2 2v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-3c0-1.1-.9-2-2-2zm-1 5H4v-2h16v2z"/>
246
+ </svg>
247
+ <div>${serverId} (${owner}/${spaceId})</div>
 
 
 
 
 
 
 
248
  </div>
249
+ </div>
250
+ <div class="metric-grid">
251
+ <div class="metric-item">
252
+ <div class="metric-label">CPU</div>
253
+ <div class="progress-bar-container">
254
+ <div class="cpu-progress-bar"></div>
255
+ </div>
256
+ <div class="metric-value cpu-usage">0%</div>
257
  </div>
258
+ <div class="metric-item">
259
+ <div class="metric-label">内存</div>
260
+ <div class="progress-bar-container">
261
+ <div class="memory-progress-bar"></div>
262
+ </div>
263
+ <div class="metric-value memory-usage">0%</div>
264
+ </div>
265
+ <div class="metric-item">
266
+ <div class="metric-label">上传</div>
267
+ <div class="metric-value upload">0 KB/s</div>
268
+ </div>
269
+ <div class="metric-item">
270
+ <div class="metric-label">下载</div>
271
+ <div class="metric-value download">0 KB/s</div>
272
+ </div>
273
+ </div>
274
+ `;
275
+ document.getElementById('servers').appendChild(card);
276
+ }}
 
277
 
278
+ const card = document.getElementById(`server-${serverId}`);
279
+ const cpuUsage = data.cpu_usage_pct;
280
+ const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
281
+ const uploadBps = data.tx_bps;
282
+ const downloadBps = data.rx_bps;
283
 
284
+ card.querySelector('.cpu-usage').textContent = `${cpuUsage.toFixed(2)}%`;
285
+ card.querySelector('.cpu-progress-bar').style.width = `${cpuUsage}%`;
286
 
287
+ card.querySelector('.memory-usage').textContent = `${memoryUsage.toFixed(2)}%`;
288
+ card.querySelector('.memory-progress-bar').style.width = `${memoryUsage}%`;
289
 
290
+ card.querySelector('.upload').textContent = `${formatBytes(uploadBps)}/s`;
291
+ card.querySelector('.download').textContent = `${formatBytes(downloadBps)}/s`;
292
 
293
+ serversData[serverId] = {{ last_seen: Date.now() }}; // Update last seen
294
+ updateSummary();
295
  }}
296
 
297
  function updateSummary() {{
298
+ const now = Date.now();
299
+ let online = 0;
300
+ let offline = 0;
301
+ let totalUpload = 0;
302
+ let totalDownload = 0;
303
+
304
+ for (const serverId in serversData) {{
305
+ const lastSeen = serversData[serverId].last_seen;
306
  const isOnline = (now - lastSeen) < 10000;
307
+ const serverCard = document.getElementById(`server-${serverId}`);
308
+
309
  if (serverCard) {{
310
  const statusDot = serverCard.querySelector('.status-dot');
311
  statusDot.className = `status-dot status-${{isOnline ? 'online' : 'offline'}}`;
 
318
  }}
319
  }}
320
  isOnline ? online++ : offline++;
321
+ }}
322
 
323
+ document.getElementById('totalServers').textContent = Object.keys(serversData).length;
324
  document.getElementById('onlineServers').textContent = online;
325
  document.getElementById('offlineServers').textContent = offline;
326
  document.getElementById('totalUpload').textContent = `${{formatBytes(totalUpload)}}/s`;
327
  document.getElementById('totalDownload').textContent = `${{formatBytes(totalDownload)}}/s`;
328
  }}
329
 
330
+
331
  function formatBytes(bytes) {{
332
  if (bytes === 0) return '0 B';
333
  const k = 1024;
 
336
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
337
  }}
338
 
 
339
 
340
+ // Initial rendering of server cards (from server-side data)
341
+ for (const spaceId in serversData) {{
342
+ for (const replicaId in serversData[spaceId].replicas) {{
343
+ updateServerCard(serversData[spaceId].replicas[replicaId], spaceId);
344
+ }}
345
+ }}
346
+ updateSummary(); // Initial summary update
347
+
348
+
349
+ // Long-polling (simulated SSE with fetch)
350
+ async function fetchMetrics() {{
351
+ try {{
352
+ const response = await fetch('/metrics');
353
+ const updatedServersData = await response.json();
354
+
355
+ for (const spaceId in updatedServersData) {{
356
+ for(const replicaId in updatedServersData[spaceId].replicas) {
357
+ updateServerCard(updatedServersData[spaceId].replicas[replicaId], spaceId);
358
+ }
359
+ }}
360
+ updateSummary(); // Update summary after each fetch
361
+ }} catch (error) {{
362
+ console.error("Error fetching metrics:", error);
363
+ }}
364
+ setTimeout(fetchMetrics, 2000); // Poll every 2 seconds
365
+ }}
366
+
367
+ fetchMetrics(); // Start fetching metrics
368
 
369
  </script>
370
  </body>
371
  </html>
372
  """
373
 
374
+
375
+ def fetch_instances(username):
376
+ """Fetches the list of instances for a given username."""
377
+ try:
378
+ response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
379
+ response.raise_for_status() # Raise an exception for bad status codes
380
+ user_instances = response.json()
381
+ return [{"id": instance["id"].split("/")[1], "owner": username} for instance in user_instances]
382
+ except requests.exceptions.RequestException as e:
383
+ print(f"Error fetching instances: {e}")
384
+ return []
385
+
386
+ def fetch_metrics_for_instance(username, instance_id):
387
+ """Fetches metrics for a single instance using long-polling."""
388
+ try:
389
+ response = requests.get(f"https://api.hf.space/v1/{username}/{instance_id}/live-metrics", timeout=30) # Use a timeout
390
+ response.raise_for_status()
391
+ return response.json()
392
+ except requests.exceptions.RequestException as e:
393
+ print(f"Error fetching metrics for {username}/{instance_id}: {e}")
394
+ return None
395
+
396
+ def get_all_metrics(username, instances):
397
+ """Fetches metrics for all instances concurrently."""
398
+ all_servers_data = {}
399
+ with ThreadPoolExecutor(max_workers=10) as executor: # Adjust max_workers as needed
400
+ futures = {executor.submit(fetch_metrics_for_instance, username, instance['id']): instance for instance in instances}
401
+ for future in futures:
402
+ instance = futures[future]
403
+ try:
404
+ metrics = future.result()
405
+ if metrics:
406
+ all_servers_data[instance['id']] = {
407
+ "last_seen": int(time.time() * 1000),
408
+ "replicas": {replica["replica"]: replica for replica in metrics}
409
+ }
410
+
411
+ except Exception as e:
412
+ print(f"Error processing metrics for {instance['id']}: {e}")
413
+ return all_servers_data
414
+
415
  @app.route("/")
416
  def home():
417
+ username = "yangtb24" # Replace with your desired username
418
+ instances = fetch_instances(username)
419
+ initial_servers_data = get_all_metrics(username, instances)
420
+
421
+ return render_template_string(htmlTemplate, username=username, servers_data=initial_servers_data)
422
+
423
+ @app.route("/metrics")
424
+ def metrics():
425
+ username = "yangtb24"
426
+ instances = fetch_instances(username)
427
+ updated_servers_data = get_all_metrics(username, instances)
428
+ return updated_servers_data, 200, {'Content-Type': 'application/json'}
429
+
430
 
431
  if __name__ == "__main__":
432
+ app.run(debug=True, host="0.0.0.0", port=7860) # Use port 7860 for HF Spaces
433
+