yangtb24 commited on
Commit
bd144df
·
verified ·
1 Parent(s): e0d8145

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +259 -287
app.py CHANGED
@@ -1,8 +1,8 @@
1
- from flask import Flask, render_template, Response, render_template_string
2
  import requests
3
- import json
4
- from threading import Lock, Thread
5
  import time
 
6
 
7
  app = Flask(__name__)
8
 
@@ -197,298 +197,270 @@ body {
197
  }
198
  """
199
 
200
- # 全局变量,用于存储服务器数据
201
- servers_data = {}
202
- servers_data_lock = Lock() # 数据锁
203
- username = 'yangtb24' # 替换为你的 Hugging Face 用户名
204
- instances = [] # 全局变量存储实例列表
205
- event_sources = {} # 全局变量存储 SSE 连接
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  def fetch_instances(username):
208
- """获取 Hugging Face Space 实例列表"""
209
  try:
210
- print(f"Fetching instances for user: {username}")
211
  response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
212
- print(f"API response status code: {response.status_code}")
213
- response.raise_for_status()
214
  user_instances = response.json()
215
- print(f"API response data: {user_instances}")
216
- _instances = [ # 使用临时变量
217
- {"id": instance["id"].split("/")[1], "owner": username}
218
- for instance in user_instances
219
- ]
220
- print(f"Parsed instances: {_instances}")
221
- return _instances
222
- except requests.RequestException as e:
223
- print(f"获取实例列表失败:{e}")
224
  return []
225
 
 
 
 
226
 
227
- def format_bytes(bytes_num):
228
- """格式化字节数"""
229
- if bytes_num == 0:
230
- return '0 B'
231
- k = 1024
232
- sizes = ['B', 'KB', 'MB', 'GB', 'TB']
233
- i = 0
234
- while bytes_num >= k:
235
- bytes_num /= k
236
- i += 1
237
- return f"{bytes_num:.2f} {sizes[i]}"
238
-
239
-
240
- def update_server_data(data, space_id, owner):
241
- """更新服务器数据"""
242
- global servers_data
243
- print(f"Received data in update_server_data: {data}") # 调试输出
244
- server_id = data["replica"]
245
- with servers_data_lock:
246
- if server_id not in servers_data:
247
- servers_data[server_id] = {
248
- "space_id": space_id,
249
- "owner": owner,
250
- "last_seen": time.time(),
251
- "cpu_usage": 0,
252
- "memory_usage": 0,
253
- "upload": 0,
254
- "download": 0,
255
- }
256
-
257
- servers_data[server_id]["last_seen"] = time.time()
258
- servers_data[server_id]["cpu_usage"] = data["cpu_usage_pct"]
259
- servers_data[server_id]["memory_usage"] = (
260
- (data["memory_used_bytes"] / data["memory_total_bytes"]) * 100
261
- if data["memory_total_bytes"] > 0 else 0
262
- )
263
- servers_data[server_id]["upload"] = data["tx_bps"]
264
- servers_data[server_id]["download"] = data["rx_bps"]
265
-
266
-
267
- def get_server_cards_html():
268
- """生成服务器卡片的 HTML"""
269
- global servers_data
270
-
271
- server_cards_html = ""
272
- with servers_data_lock:
273
- for server_id, data in servers_data.items():
274
- is_online = (time.time() - data["last_seen"]) < 10
275
- status_class = "status-online" if is_online else "status-offline"
276
-
277
- server_cards_html += f"""
278
- <div class="server-card" id="server-{server_id}">
279
- <div class="server-header">
280
- <div class="server-name">
281
- <div class="status-dot {status_class}"></div>
282
- <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
283
- <path d="M21 3H3C1.9 3 1 3.9 1 5v3c0 1.1.9 2 2 10h18c1.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"/>
284
- </svg>
285
- <div>{server_id} ({data['owner']}/{data['space_id']})</div>
286
- </div>
287
- </div>
288
- <div class="metric-grid">
289
- <div class="metric-item">
290
- <div class="metric-label">CPU</div>
291
- <div class="progress-bar-container">
292
- <div class="cpu-progress-bar" style="width: {data['cpu_usage']:.2f}%"></div>
293
- </div>
294
- <div class="metric-value cpu-usage">{data['cpu_usage']:.2f}%</div>
295
- </div>
296
- <div class="metric-item">
297
- <div class="metric-label">内存</div>
298
- <div class="progress-bar-container">
299
- <div class="memory-progress-bar" style="width: {data['memory_usage']:.2f}%"></div>
300
- </div>
301
- <div class="metric-value memory-usage">{data['memory_usage']:.2f}%</div>
302
- </div>
303
- <div class="metric-item">
304
- <div class="metric-label">上传</div>
305
- <div class="metric-value upload">{format_bytes(data['upload'])}/s</div>
306
- </div>
307
- <div class="metric-item">
308
- <div class="metric-label">下载</div>
309
- <div class="metric-value download">{format_bytes(data['download'])}/s</div>
310
- </div>
311
- </div>
312
- </div>
313
- """
314
- return server_cards_html
315
-
316
-
317
- def get_summary_html():
318
- """生成概览的 HTML"""
319
- global servers_data
320
-
321
- total_servers = 0
322
- online_servers = 0
323
- offline_servers = 0
324
- total_upload = 0
325
- total_download = 0
326
-
327
- with servers_data_lock:
328
- total_servers = len(servers_data)
329
- for server_id, data in servers_data.items():
330
- if (time.time() - data["last_seen"]) < 10:
331
- online_servers += 1
332
- total_upload += data["upload"]
333
- total_download += data["download"]
334
- else:
335
- offline_servers += 1
336
-
337
- return f"""
338
- <div>总实例数: <span id="totalServers">{total_servers}</span></div>
339
- <div>在线实例: <span id="onlineServers">{online_servers}</span></div>
340
- <div>离线实例: <span id="offlineServers">{offline_servers}</span></div>
341
- <div>总上传: <span id="totalUpload">{format_bytes(total_upload)}/s</span></div>
342
- <div>总下载: <span id="totalDownload">{format_bytes(total_download)}/s</span></div>
343
- """
344
-
345
- def connect_and_listen(instance):
346
- """连接到 SSE 并持续监听"""
347
- global event_sources, username
348
- space_id = instance['id']
349
- url = f"https://api.hf.space/v1/{username}/{space_id}/live-metrics/sse"
350
- print(f"Connecting to SSE for space: {space_id}")
351
-
352
- while True: # 持续重连循环
353
- try:
354
- # Add the Accept header for SSE
355
- headers = {'Accept': 'text/event-stream'}
356
- response = requests.get(url, stream=True, headers=headers)
357
- response.raise_for_status() # 检查请求是否成功, 4xx or 5xx errors
358
- event_sources[space_id] = response
359
-
360
- # Check if the response is valid before accessing .raw
361
- if response and hasattr(response, 'raw'):
362
- for line in response.iter_lines():
363
- if line:
364
- line = line.decode("utf-8").strip()
365
- # print(f"Received line from SSE ({space_id}): {line}") # 调试
366
- if line.startswith("event: metric"):
367
- data_line = next(response.iter_lines()).decode("utf-8").strip()
368
- if data_line.startswith("data: "):
369
- try:
370
- data = json.loads(data_line[6:])
371
- update_server_data(data, space_id, username)
372
- except json.JSONDecodeError:
373
- print(f"Error decoding JSON for {space_id}: {data_line}")
374
- else:
375
- print(f"Invalid response from SSE for {space_id}")
376
-
377
- except requests.exceptions.RequestException as e:
378
- print(f"Connection error for {space_id}: {e}, retrying in 5 seconds...")
379
- time.sleep(5) # 重连间隔
380
- except Exception as e: # Catch other potential errors
381
- print(f"Unexpected error for {space_id}: {e}, retrying in 5 seconds...")
382
- time.sleep(5)
383
-
384
- def background_task():
385
- """后台任务:获取实例、连接 SSE、更新数据"""
386
- global instances, username
387
-
388
- while True: # 持续获取实例并更新
389
- new_instances = fetch_instances(username)
390
- if new_instances != instances:
391
- print("Instances changed, reconnecting...")
392
- # 关闭旧的连接
393
- for es in event_sources.values():
394
- es.close()
395
- event_sources.clear()
396
- instances = new_instances
397
-
398
- # 为每个实例创建并启动监听线程
399
- for instance in instances:
400
- thread = Thread(target=connect_and_listen, args=(instance,), daemon=True)
401
- thread.start()
402
-
403
- time.sleep(300) # 每 5 分钟检查一次实例列表
404
-
405
-
406
- @app.route("/")
407
- def home():
408
- """主页"""
409
- global servers_data, username
410
-
411
- # 构建 HTML 页面
412
- html = f"""
413
- <!DOCTYPE html>
414
- <html lang="zh">
415
- <head>
416
- <meta charset="UTF-8">
417
- <title>HF Space Monitor</title>
418
- <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>">
419
- <style>{lightModeStyle}</style>
420
- <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>
421
- <script>
422
- function updateData() {{
423
- fetch('/') // 重新请求主页
424
- .then(response => response.text())
425
- .then(html => {{
426
- const parser = new DOMParser();
427
- const doc = parser.parseFromString(html, 'text/html');
428
-
429
- // 更新概览
430
- document.getElementById('summary').innerHTML = doc.getElementById('summary').innerHTML;
431
-
432
- // 更新服务器卡片 (更有效率的方式)
433
- const newServers = doc.getElementById('servers');
434
- const currentServers = document.getElementById('servers');
435
-
436
- // 1. 移除不再存在的卡片
437
- let currentCards = currentServers.querySelectorAll('.server-card');
438
- currentCards.forEach(card => {{
439
- if (!newServers.querySelector(`#${{card.id}}`)) {{ // Corrected string interpolation
440
- card.remove();
441
- }}
442
- }});
443
-
444
- // 2. 添加/更新卡片
445
- let newCards = newServers.querySelectorAll('.server-card');
446
- newCards.forEach(newCard => {{
447
- let currentCard = currentServers.querySelector(`#${{newCard.id}}`);
448
- if (currentCard) {{
449
- // 更新现有卡片
450
- currentCard.innerHTML = newCard.innerHTML;
451
- }} else {{
452
- // 添加新卡片
453
- currentServers.appendChild(newCard.cloneNode(true));
454
- }}
455
- }});
456
- }});
457
- }}
458
- setInterval(updateData, 2000); // 每 2 秒更新一次
459
- </script>
460
- </head>
461
- <body>
462
- <div class="container">
463
- <div class="overview">
464
- <div class="overview-title"><i class="fas fa-chart-line"></i>系统概览</div>
465
- <div id="summary">
466
- {get_summary_html()}
467
- </div>
468
- </div>
469
- <div id="servers" class="stats-container">
470
- {get_server_cards_html()}
471
- </div>
472
- </div>
473
-
474
- </body>
475
- </html>
476
- """
477
- return render_template_string(html)
478
 
479
 
480
- @app.route("/metrics")
481
- def metrics():
482
- """SSE 端点 (这个路由现在主要用于调试,实际数据更新在后台线程)"""
 
 
483
  def generate():
484
- while True:
485
- yield "data: {}\n\n".format(json.dumps({"status": "alive"})) #心跳
486
- time.sleep(10)
487
-
488
- return Response(generate(), mimetype="text/event-stream")
489
-
490
- if __name__ == "__main__":
491
- # 启动后台任务
492
- thread = Thread(target=background_task, daemon=True)
493
- thread.start()
494
- app.run(debug=True, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template_string, jsonify
2
  import requests
3
+ import threading
 
4
  import time
5
+ from sseclient import SSEClient
6
 
7
  app = Flask(__name__)
8
 
 
197
  }
198
  """
199
 
200
+ htmlTemplate = f"""
201
+ <!DOCTYPE html>
202
+ <html lang="zh">
203
+ <head>
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>
210
+ <div class="container">
211
+ <div class="overview">
212
+ <div class="overview-title"><i class="fas fa-chart-line"></i>系统概览</div>
213
+ <div id="summary">
214
+ <div>总实例数: <span id="totalServers">0</span></div>
215
+ <div>在线实例: <span id="onlineServers">0</span></div>
216
+ <div>离线实例: <span id="offlineServers">0</span></div>
217
+ <div>总上传: <span id="totalUpload">0 B/s</span></div>
218
+ <div>总下载: <span id="totalDownload">0 B/s</span></div>
219
+ </div>
220
+ </div>
221
+ <div id="servers" class="stats-container">
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 = 'yangtb24';
229
+
230
+ async function fetchInstances() {{
231
+ try {{
232
+ const response = await fetch(`/instances`);
233
+ const userInstances = await response.json();
234
+ return userInstances;
235
+ }} catch (error) {{
236
+ console.error("获取实例列表失败:", error);
237
+ return [];
238
+ }}
239
+ }}
240
+
241
+ class MetricsManager {{
242
+ constructor() {{
243
+ this.eventSources = new Map();
244
+ this.servers = new Map();
245
+ this.instanceOwners = new Map();
246
+ this.spaceIds = new Map();
247
+ }}
248
+
249
+ async connect(instanceId, username) {{
250
+ if (this.eventSources.has(instanceId)) return;
251
+
252
+ try {{
253
+ const eventSource = new EventSource(`/metrics/${{username}}/${{instanceId}}`);
254
+
255
+ this.spaceIds.set(instanceId, instanceId);
256
+ this.instanceOwners.set(instanceId, username);
257
+
258
+ eventSource.addEventListener("metric", (event) => {{
259
+ try {{
260
+ const data = JSON.parse(event.data);
261
+ // console.log("Received data:", data); // Debugging line
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
+ this.eventSources.delete(instanceId); // Remove on error
272
+ }};
273
+
274
+ this.eventSources.set(instanceId, eventSource);
275
+ }} catch (error) {{
276
+ console.error(`连接失败 (${{username}}/${{instanceId}}):`, error);
277
+ }}
278
+ }}
279
+
280
+
281
+ disconnectAll() {{
282
+ this.eventSources.forEach(es => es.close());
283
+ this.eventSources.clear();
284
+ }}
285
+ }}
286
+
287
+ const metricsManager = new MetricsManager();
288
+ const servers = new Map();
289
+
290
+ async function initialize() {{
291
+ const instances = await fetchInstances();
292
+ instances.forEach(instance => {{
293
+ metricsManager.connect(instance.id, instance.owner);
294
+ }});
295
+ }}
296
+
297
+
298
+ function updateServerCard(data, spaceId) {{
299
+ const serverId = data.replica;
300
+ const serverElement = document.getElementById(`server-${{serverId}}`);
301
+ const owner = metricsManager.instanceOwners.get(spaceId);
302
+
303
+ if (!serverElement) {{
304
+ const card = document.createElement('div');
305
+ card.id = `server-${{serverId}}`;
306
+ card.className = 'server-card';
307
+ card.innerHTML = `
308
+ <div class="server-header">
309
+ <div class="server-name">
310
+ <div class="status-dot status-online"></div>
311
+ <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
312
+ <path d="M21 3H3C1.9 3 1 3.9 1 5v3c0 1.1.9 2 2 10h18c1.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"/>
313
+ </svg>
314
+ <div>${{serverId}} (${{owner}}/${{spaceId}})</div>
315
+ </div>
316
+ </div>
317
+ <div class="metric-grid">
318
+ <div class="metric-item">
319
+ <div class="metric-label">CPU</div>
320
+ <div class="progress-bar-container">
321
+ <div class="cpu-progress-bar"></div>
322
+ </div>
323
+ <div class="metric-value cpu-usage">0%</div>
324
+ </div>
325
+ <div class="metric-item">
326
+ <div class="metric-label">内存</div>
327
+ <div class="progress-bar-container">
328
+ <div class="memory-progress-bar"></div>
329
+ </div>
330
+ <div class="metric-value memory-usage">0%</div>
331
+ </div>
332
+ <div class="metric-item">
333
+ <div class="metric-label">上传</div>
334
+ <div class="metric-value upload">0 KB/s</div>
335
+ </div>
336
+ <div class="metric-item">
337
+ <div class="metric-label">下载</div>
338
+ <div class="metric-value download">0 KB/s</div>
339
+ </div>
340
+ </div>
341
+ `;
342
+ document.getElementById('servers').appendChild(card);
343
+ }}
344
+
345
+ const card = document.getElementById(`server-${{serverId}}`);
346
+ const cpuUsage = data.cpu_usage_pct;
347
+ const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
348
+ const uploadBps = data.tx_bps;
349
+ const downloadBps = data.rx_bps;
350
+
351
+ card.querySelector('.cpu-usage').textContent = `${{cpuUsage.toFixed(2)}}%`;
352
+ card.querySelector('.cpu-progress-bar').style.width = `${{cpuUsage}}%`;
353
+
354
+ card.querySelector('.memory-usage').textContent = `${{memoryUsage.toFixed(2)}}%`;
355
+ card.querySelector('.memory-progress-bar').style.width = `${{memoryUsage}}%`;
356
+
357
+ card.querySelector('.upload').textContent = `${{formatBytes(uploadBps)}}/s`;
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'}}`;
377
+
378
+ if (isOnline) {{
379
+ const uploadText = serverCard.querySelector('.upload').textContent;
380
+ const downloadText = serverCard.querySelector('.download').textContent;
381
+ totalUpload += parseFloat(uploadText) || 0;
382
+ totalDownload += parseFloat(downloadText) || 0;
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;
398
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
399
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
400
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
401
+ }}
402
+
403
+
404
+ initialize(); // Initial load
405
+
406
+ setInterval(updateSummary, 2000);
407
+
408
+ setInterval(async () => {{
409
+ metricsManager.disconnectAll();
410
+ await initialize();
411
+ }}, 300000);
412
+
413
+ </script>
414
+ </body>
415
+ </html>
416
+ """
417
+
418
+ USERNAME = 'yangtb24' # Replace with your Hugging Face username
419
+
420
 
421
  def fetch_instances(username):
 
422
  try:
 
423
  response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
424
+ response.raise_for_status() # Raise an exception for bad status codes
 
425
  user_instances = response.json()
426
+ return [{"id": instance["id"].split('/')[1], "owner": username} for instance in user_instances]
427
+ except requests.exceptions.RequestException as e:
428
+ print(f"Error fetching instances: {e}")
 
 
 
 
 
 
429
  return []
430
 
431
+ @app.route('/')
432
+ def index():
433
+ return render_template_string(htmlTemplate)
434
 
435
+ @app.route('/instances')
436
+ def get_instances():
437
+ instances = fetch_instances(USERNAME)
438
+ return jsonify(instances)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
 
440
 
441
+ @app.route('/metrics/<username>/<instance_id>')
442
+ def stream_metrics(username, instance_id):
443
+ # Use requests.get with stream=True for SSE
444
+ url = f"https://api.hf.space/v1/{username}/{instance_id}/live-metrics/sse"
445
+
446
  def generate():
447
+ try:
448
+ response = requests.get(url, stream=True, headers={"Accept": "text/event-stream"})
449
+ response.raise_for_status() # Important: raise HTTP errors
450
+ client = SSEClient(response)
451
+ for event in client.events():
452
+ if event.event == 'metric':
453
+ yield f"event: {event.event}\\ndata: {event.data}\\n\\n"
454
+ except requests.exceptions.RequestException as e:
455
+ print(f"Request Exception: {e}")
456
+ yield f"event: error\ndata: Connection error\n\n" # Send an error event
457
+ except Exception as e:
458
+ print(f"An error occurred: {e}")
459
+ yield f"event: error\ndata: An error occurred\n\n"
460
+
461
+
462
+ return Response(generate(), mimetype='text/event-stream')
463
+
464
+
465
+ if __name__ == '__main__':
466
+ app.run(debug=True, host='0.0.0.0', port=7860) #Huggingface use 7860 port