yangtb24 commited on
Commit
b80478e
·
verified ·
1 Parent(s): 6c3c69d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +305 -253
app.py CHANGED
@@ -1,8 +1,7 @@
1
- from flask import Flask, render_template_string, jsonify
2
  import requests
3
- import threading
4
- import time
5
- from concurrent.futures import ThreadPoolExecutor
6
 
7
  app = Flask(__name__)
8
 
@@ -197,14 +196,14 @@ body {
197
  }
198
  """
199
 
200
- htmlTemplate = """
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">
@@ -226,266 +225,319 @@ htmlTemplate = """
226
 
227
  <script>
228
  const username = 'yangtb24';
229
- const serversData = {{ servers_data|tojson }};
230
-
231
- function createServerCard(serverId, spaceId, owner) {
232
- const card = document.createElement('div');
233
- card.id = `server-${serverId}`;
234
- card.className = 'server-card';
235
- card.innerHTML = `
236
- <div class="server-header">
237
- <div class="server-name">
238
- <div class="status-dot status-offline"></div>
239
- <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
240
- <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"/>
241
- </svg>
242
- <div>${serverId} (${owner}/${spaceId})</div>
243
- </div>
244
- </div>
245
- <div class="metric-grid">
246
- <div class="metric-item">
247
- <div class="metric-label">CPU</div>
248
- <div class="progress-bar-container">
249
- <div class="cpu-progress-bar"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  </div>
251
- <div class="metric-value cpu-usage">0%</div>
252
- </div>
253
- <div class="metric-item">
254
- <div class="metric-label">内存</div>
255
- <div class="progress-bar-container">
256
- <div class="memory-progress-bar"></div>
 
 
257
  </div>
258
- <div class="metric-value memory-usage">0%</div>
259
- </div>
260
- <div class="metric-item">
261
- <div class="metric-label">上传</div>
262
- <div class="metric-value upload">0 KB/s</div>
263
- </div>
264
- <div class="metric-item">
265
- <div class="metric-label">下载</div>
266
- <div class="metric-value download">0 KB/s</div>
267
- </div>
268
- </div>
269
- `;
270
- document.getElementById('servers').appendChild(card);
271
- }
272
-
273
- function updateServerCard(data, spaceId) {
274
- const serverId = data.replica;
275
- const serverElement = document.getElementById(`server-${serverId}`);
276
- const owner = data.owner;
277
-
278
- if (!serverElement) {
279
- createServerCard(serverId, spaceId, owner);
280
- }
281
-
282
- const card = document.getElementById(`server-${serverId}`);
283
- const cpuUsage = data.cpu_usage_pct;
284
- const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
285
- const uploadBps = data.tx_bps;
286
- const downloadBps = data.rx_bps;
287
-
288
- card.querySelector('.cpu-usage').textContent = `${cpuUsage.toFixed(2)}%`;
289
- card.querySelector('.cpu-progress-bar').style.width = `${cpuUsage}%`;
290
-
291
- card.querySelector('.memory-usage').textContent = `${memoryUsage.toFixed(2)}%`;
292
- card.querySelector('.memory-progress-bar').style.width = `${memoryUsage}%`;
293
-
294
- card.querySelector('.upload').textContent = `${formatBytes(uploadBps)}/s`;
295
- card.querySelector('.download').textContent = `${formatBytes(downloadBps)}/s`;
296
- updateSummary(serverId, data);
297
- }
298
-
299
-
300
- function updateSummary(serverId, data) {
301
-
302
- const now = Date.now();
303
- let online = 0;
304
- let offline = 0;
305
- let totalUpload = 0;
306
- let totalDownload = 0;
307
-
308
- for (const serverId in serversData) {
309
- const server = serversData[serverId];
310
- const isOnline = server.isOnline;
311
- const serverCard = document.getElementById(`server-${serverId}`);
312
-
313
- if (serverCard) {
314
- const statusDot = serverCard.querySelector('.status-dot');
315
- statusDot.className = `status-dot status-${isOnline ? 'online' : 'offline'}`;
316
-
317
- if (isOnline && server.data) {
318
- const uploadText = formatBytes(server.data.tx_bps);
319
- const downloadText = formatBytes(server.data.rx_bps);
320
- totalUpload += server.data.tx_bps;
321
- totalDownload += server.data.rx_bps;
322
- }
323
- }
324
- isOnline ? online++ : offline++;
325
- }
326
- document.getElementById('totalServers').textContent = Object.keys(serversData).length;
327
- document.getElementById('onlineServers').textContent = online;
328
- document.getElementById('offlineServers').textContent = offline;
329
- document.getElementById('totalUpload').textContent = `${formatBytes(totalUpload)}/s`;
330
- document.getElementById('totalDownload').textContent = `${formatBytes(totalDownload)}/s`;
331
- }
332
-
333
-
334
-
335
- function formatBytes(bytes) {
336
- if (bytes === 0) return '0 B';
337
- const k = 1024;
338
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
339
- const i = Math.floor(Math.log(bytes) / Math.log(k));
340
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
341
- }
342
-
343
-
344
- // Initial render based on initial data
345
- for (const serverId in serversData) {
346
- const server = serversData[serverId];
347
- if (server.data) {
348
- updateServerCard(server.data, server.spaceId);
349
- } else {
350
- createServerCard(serverId, server.spaceId, server.owner)
351
- }
352
- }
353
- updateSummary();
354
-
 
355
 
356
  </script>
357
  </body>
358
  </html>
359
  """
360
 
361
-
362
- class MetricsManager:
363
- def __init__(self, username):
364
- self.username = username
365
- self.executor = ThreadPoolExecutor(max_workers=10) # Adjust as needed
366
- self.servers_data = {}
367
- self.lock = threading.Lock() # Protect shared data
368
- self.stop_event = threading.Event()
369
-
370
-
371
- def fetch_instances(self):
372
- try:
373
- response = requests.get(f"https://huggingface.co/api/spaces?author={self.username}")
374
- response.raise_for_status() # Raise an exception for bad status codes
375
- user_instances = response.json()
376
- return [
377
- {"id": instance["id"].split("/")[1], "owner": self.username}
378
- for instance in user_instances
379
- ]
380
- except requests.RequestException as e:
381
- print(f"Error fetching instances: {e}")
382
- return []
383
-
384
- def connect(self, instance_id, username):
385
- if self.stop_event.is_set():
386
- return
387
- url = f"https://api.hf.space/v1/{username}/{instance_id}/live-metrics/sse"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  try:
389
- # Use stream=True for streaming response
390
- with requests.get(url, stream=True, timeout=(5, 60)) as response: # Connect timeout, read timeout
391
- response.raise_for_status()
392
-
393
- if response.encoding is None:
394
- response.encoding = 'utf-8'
395
-
396
- lines_iter = response.iter_lines(decode_unicode=True)
397
-
398
- # Skip initial lines until we find the first "event: metric"
399
- for line in lines_iter:
400
- if line.strip() == "event: metric":
401
- break
402
-
403
- # Now process the actual metric data
404
- for line in lines_iter:
405
- if self.stop_event.is_set():
406
- break
407
- if line.strip().startswith("data:"):
408
- try:
409
- data_str = line.strip()[5:] # Remove "data:" prefix
410
- data = eval(data_str) # Use eval (safer than direct JSON for this case)
411
-
412
- server_id = data['replica']
413
- with self.lock:
414
- if server_id not in self.servers_data:
415
- self.servers_data[server_id] = {
416
- 'spaceId': instance_id,
417
- 'owner': username,
418
- 'data': None,
419
- 'last_seen': 0,
420
- 'isOnline': False,
421
  }
 
422
 
423
- self.servers_data[server_id]['data'] = data
424
- self.servers_data[server_id]['last_seen'] = time.time()
425
- self.servers_data[server_id]['isOnline'] = True
426
-
427
- except Exception as e:
428
- print(f"Error parsing data for {instance_id}: {e}")
429
- print(f" Problematic line: {line.strip()}") # Debugging
430
-
431
  except requests.exceptions.RequestException as e:
432
- print(f"Connection failed ({username}/{instance_id}): {e}")
433
- # Mark all replicas of this space as offline
434
- with self.lock:
435
- for server_id, server_info in list(self.servers_data.items()): # Iterate on a copy
436
- if server_info['spaceId'] == instance_id:
437
- server_info['isOnline'] = False
438
- # Optionally remove after a longer timeout:
439
- # if time.time() - server_info['last_seen'] > 30: # 30 seconds
440
- # del self.servers_data[server_id]
441
-
442
-
443
- def check_timeouts(self):
444
- while not self.stop_event.is_set():
445
- with self.lock:
446
- now = time.time()
447
- for server_id, server_info in list(self.servers_data.items()): # Iterate on a copy
448
- if now - server_info['last_seen'] > 10:
449
- server_info['isOnline'] = False
450
- # Optionally remove after a longer timeout:
451
- # if now - server_info['last_seen'] > 30: # 30 seconds
452
- # del self.servers_data[server_id]
453
- time.sleep(2)
454
-
455
- def start_monitoring(self):
456
- instances = self.fetch_instances()
457
- for instance in instances:
458
- self.executor.submit(self.connect, instance['id'], instance['owner'])
459
- self.timeout_thread = threading.Thread(target=self.check_timeouts, daemon=True)
460
- self.timeout_thread.start()
461
-
462
- def stop_monitoring(self):
463
- self.stop_event.set()
464
- self.executor.shutdown(wait=False)
465
- if hasattr(self, 'timeout_thread'):
466
- self.timeout_thread.join()
467
-
468
- def get_data(self):
469
- with self.lock:
470
- return self.servers_data.copy()
471
-
472
-
473
- metrics_manager = MetricsManager(username='yangtb24')
474
-
475
- @app.route("/")
476
- def home():
477
- servers_data = metrics_manager.get_data()
478
- return render_template_string(htmlTemplate, servers_data=servers_data)
479
 
480
- # Initialize and start monitoring *before* running the Flask app
481
- metrics_manager.start_monitoring()
482
 
483
- # Important: Use a context manager to cleanly shut down monitoring
484
- # when the Flask app stops. This prevents orphaned threads.
485
- @app.teardown_appcontext
486
- def shutdown_session(exception=None):
487
- metrics_manager.stop_monitoring()
488
 
 
 
489
 
490
- if __name__ == "__main__":
491
- app.run(debug=False, host="0.0.0.0", port=7860) # Use port 7860 for HF Spaces
 
1
+ from flask import Flask, render_template_string
2
  import requests
3
+ import json
4
+ from threading import Lock
 
5
 
6
  app = Flask(__name__)
7
 
 
196
  }
197
  """
198
 
199
+ htmlTemplate = f"""
200
  <!DOCTYPE html>
201
  <html lang="zh">
202
  <head>
203
  <meta charset="UTF-8">
204
  <title>HF Space Monitor</title>
205
  <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>">
206
+ <style>{lightModeStyle}</style>
207
  </head>
208
  <body>
209
  <div class="container">
 
225
 
226
  <script>
227
  const username = 'yangtb24';
228
+
229
+ async function fetchInstances() {{
230
+ try {{
231
+ const response = await fetch(`/instances`);
232
+ const data = await response.json();
233
+ return data.instances;
234
+ }} catch (error) {{
235
+ console.error("获取实例列表失败:", error);
236
+ return [];
237
+ }}
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
+ this.lock = false;
248
+ }}
249
+
250
+
251
+ async connect(instanceId, username) {{
252
+ if (this.eventSources.has(instanceId)) return;
253
+
254
+ // Fetch initial metrics
255
+ try {{
256
+ const initialResponse = await fetch(`/metrics?instanceId=${instanceId}&username=${username}`);
257
+ const initialData = await initialResponse.json();
258
+
259
+ if(initialData && initialData.metrics) {{
260
+ this.updateServerCard(initialData.metrics, instanceId, username);
261
+ }}
262
+ }}
263
+ catch (error) {{
264
+ console.error(`Initial metrics fetch failed (${instanceId}):`, error);
265
+ }}
266
+
267
+ try {{
268
+ const eventSource = new EventSource(
269
+ `/stream?instanceId=${instanceId}&username=${username}`
270
+ );
271
+
272
+ this.spaceIds.set(instanceId, instanceId);
273
+ this.instanceOwners.set(instanceId, username);
274
+
275
+ eventSource.addEventListener("metric", (event) => {{
276
+ try {{
277
+ const data = JSON.parse(event.data);
278
+ this.updateServerCard(data, instanceId, username);
279
+ }} catch (error) {{
280
+ console.error(`解析数据失败 (${instanceId}):`, error);
281
+ }}
282
+ }});
283
+
284
+ eventSource.onerror = (error) => {{
285
+ console.error(`EventSource 错误 (${instanceId}):`, error);
286
+ eventSource.close();
287
+ this.eventSources.delete(instanceId);
288
+ }};
289
+
290
+ this.eventSources.set(instanceId, eventSource);
291
+ }} catch (error) {{
292
+ console.error(`连接失败 (${username}/${instanceId}):`, error);
293
+ }}
294
+ }}
295
+
296
+
297
+ disconnectAll() {{
298
+ this.eventSources.forEach(es => es.close());
299
+ this.eventSources.clear();
300
+ }}
301
+
302
+ updateServerCard(data, spaceId, owner) {{
303
+ if (!data || !data.replica) return;
304
+
305
+ const serverId = data.replica;
306
+ const serverElement = document.getElementById(`server-${serverId}`);
307
+
308
+
309
+ if (!serverElement) {{
310
+ const card = document.createElement('div');
311
+ card.id = `server-${serverId}`;
312
+ card.className = 'server-card';
313
+ card.innerHTML = `
314
+ <div class="server-header">
315
+ <div class="server-name">
316
+ <div class="status-dot status-online"></div>
317
+ <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
318
+ <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"/>
319
+ </svg>
320
+ <div>${serverId} (${owner}/${spaceId})</div>
321
  </div>
322
+ </div>
323
+ <div class="metric-grid">
324
+ <div class="metric-item">
325
+ <div class="metric-label">CPU</div>
326
+ <div class="progress-bar-container">
327
+ <div class="cpu-progress-bar"></div>
328
+ </div>
329
+ <div class="metric-value cpu-usage">0%</div>
330
  </div>
331
+ <div class="metric-item">
332
+ <div class="metric-label">内存</div>
333
+ <div class="progress-bar-container">
334
+ <div class="memory-progress-bar"></div>
335
+ </div>
336
+ <div class="metric-value memory-usage">0%</div>
337
+ </div>
338
+ <div class="metric-item">
339
+ <div class="metric-label">上传</div>
340
+ <div class="metric-value upload">0 KB/s</div>
341
+ </div>
342
+ <div class="metric-item">
343
+ <div class="metric-label">下载</div>
344
+ <div class="metric-value download">0 KB/s</div>
345
+ </div>
346
+ </div>
347
+ `;
348
+ document.getElementById('servers').appendChild(card);
349
+ }}
350
+
351
+ const card = document.getElementById(`server-${serverId}`);
352
+ const cpuUsage = data.cpu_usage_pct;
353
+ const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
354
+ const uploadBps = data.tx_bps;
355
+ const downloadBps = data.rx_bps;
356
+
357
+ card.querySelector('.cpu-usage').textContent = `${cpuUsage.toFixed(2)}%`;
358
+ card.querySelector('.cpu-progress-bar').style.width = `${cpuUsage}%`;
359
+
360
+ card.querySelector('.memory-usage').textContent = `${memoryUsage.toFixed(2)}%`;
361
+ card.querySelector('.memory-progress-bar').style.width = `${memoryUsage}%`;
362
+
363
+ card.querySelector('.upload').textContent = `${this.formatBytes(uploadBps)}/s`;
364
+ card.querySelector('.download').textContent = `${this.formatBytes(downloadBps)}/s`;
365
+
366
+ this.servers.set(serverId, Date.now());
367
+ this.updateSummary();
368
+ }}
369
+
370
+ updateSummary() {{
371
+ const now = Date.now();
372
+ let online = 0;
373
+ let offline = 0;
374
+ let totalUpload = 0;
375
+ let totalDownload = 0;
376
+
377
+ this.servers.forEach((lastSeen, serverId) => {{
378
+ const isOnline = (now - lastSeen) < 10000;
379
+ const serverCard = document.getElementById(`server-${serverId}`);
380
+ if (serverCard) {{
381
+ const statusDot = serverCard.querySelector('.status-dot');
382
+ statusDot.className = `status-dot status-${{isOnline ? 'online' : 'offline'}}`;
383
+
384
+ if (isOnline) {{
385
+ const uploadText = serverCard.querySelector('.upload').textContent;
386
+ const downloadText = serverCard.querySelector('.download').textContent;
387
+ totalUpload += parseFloat(uploadText) || 0;
388
+ totalDownload += parseFloat(downloadText) || 0;
389
+ }}
390
+ }}
391
+ isOnline ? online++ : offline++;
392
+ }});
393
+
394
+ document.getElementById('totalServers').textContent = this.servers.size;
395
+ document.getElementById('onlineServers').textContent = online;
396
+ document.getElementById('offlineServers').textContent = offline;
397
+ document.getElementById('totalUpload').textContent = `${this.formatBytes(totalUpload)}/s`;
398
+ document.getElementById('totalDownload').textContent = `${this.formatBytes(totalDownload)}/s`;
399
+ }}
400
+
401
+ formatBytes(bytes) {{
402
+ if (bytes === 0) return '0 B';
403
+ const k = 1024;
404
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
405
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
406
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
407
+ }}
408
+ }}
409
+
410
+ const metricsManager = new MetricsManager();
411
+
412
+ async function initialize() {{
413
+ const instances = await fetchInstances();
414
+ if (instances) {{
415
+ instances.forEach(instance => {{
416
+ metricsManager.connect(instance.id, instance.owner);
417
+ }});
418
+ }}
419
+ }}
420
+
421
+ initialize();
422
+ setInterval(() => metricsManager.updateSummary(), 2000);
423
+
424
+ // Re-initialize every 5 minutes
425
+ setInterval(async () => {{
426
+ metricsManager.disconnectAll();
427
+ await initialize();
428
+ }}, 300000);
429
 
430
  </script>
431
  </body>
432
  </html>
433
  """
434
 
435
+ # Global variable to store instances and metrics. Using a dictionary is better
436
+ # than separate Maps, as it allows atomic updates with a lock.
437
+ data = {
438
+ "instances": [],
439
+ "metrics": {} # Key: instanceId, Value: {metrics: {}, last_updated: timestamp}
440
+ }
441
+ data_lock = Lock() # Use a lock for thread safety
442
+
443
+ USERNAME = 'yangtb24'
444
+
445
+ def fetch_instances(username):
446
+ try:
447
+ response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
448
+ response.raise_for_status() # Raise an exception for bad status codes
449
+ user_instances = response.json()
450
+ return [{"id": instance["id"].split('/')[1], "owner": username} for instance in user_instances]
451
+ except requests.exceptions.RequestException as e:
452
+ print(f"Error fetching instances: {e}")
453
+ return []
454
+
455
+ def fetch_initial_metrics(username, instance_id):
456
+ """Fetches initial metrics using a regular GET request."""
457
+ try:
458
+ url = f"https://api.hf.space/v1/{username}/{instance_id}/live-metrics"
459
+ response = requests.get(url)
460
+ response.raise_for_status()
461
+ return response.json()
462
+ except requests.exceptions.RequestException as e:
463
+ print(f"Error fetching initial metrics for {username}/{instance_id}: {e}")
464
+ return None
465
+
466
+
467
+ @app.route('/')
468
+ def home():
469
+ return render_template_string(htmlTemplate)
470
+
471
+
472
+ @app.route('/instances')
473
+ def get_instances():
474
+ global data
475
+ with data_lock:
476
+ # Refresh instances every time /instances is called, but only if
477
+ # they haven't been updated in the last 5 minutes.
478
+ if not data["instances"] or (time.time() - data.get("instances_last_updated", 0) > 300):
479
+ data["instances"] = fetch_instances(USERNAME)
480
+ data["instances_last_updated"] = time.time() # Add timestamp
481
+ return jsonify({"instances": data["instances"]})
482
+
483
+ @app.route('/metrics')
484
+ def get_metrics():
485
+ """Route to get initial metrics for an instance."""
486
+ instance_id = request.args.get('instanceId')
487
+ username = request.args.get('username')
488
+ if not instance_id or not username:
489
+ return jsonify({"error": "instanceId and username are required"}), 400
490
+
491
+ metrics = fetch_initial_metrics(username, instance_id)
492
+ if metrics:
493
+ return jsonify({"metrics": metrics})
494
+ else:
495
+ return jsonify({"error": "Failed to fetch initial metrics"}), 500
496
+
497
+
498
+ @app.route('/stream')
499
+ def stream():
500
+ instance_id = request.args.get('instanceId')
501
+ username = request.args.get('username')
502
+ if not instance_id or not username:
503
+ return "instanceId and username are required", 400
504
+
505
+ def generate():
506
+ global data
507
  try:
508
+ url = f"https://api.hf.space/v1/{username}/{instance_id}/live-metrics/sse"
509
+ with requests.get(url, stream=True) as r:
510
+ r.raise_for_status()
511
+ for line in r.iter_lines():
512
+ if line:
513
+ decoded_line = line.decode('utf-8')
514
+ # Check if the line starts with "data:"
515
+ if decoded_line.startswith("data:"):
516
+ try:
517
+ # Extract the JSON part of the message
518
+ json_str = decoded_line.split("data:", 1)[1].strip()
519
+ metric_data = json.loads(json_str)
520
+
521
+ with data_lock:
522
+ # Update the metrics and last_updated timestamp
523
+ data["metrics"][instance_id] = {
524
+ "metrics": metric_data,
525
+ "last_updated": time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  }
527
+ yield f"data: {json.dumps(metric_data)}\n\n"
528
 
529
+ except json.JSONDecodeError as e:
530
+ print(f"JSONDecodeError: {e}, Line: {decoded_line}")
531
+ # Optionally: yield an error message to the client
532
+ # yield "data: {{'error': 'Invalid JSON'}}\n\n"
 
 
 
 
533
  except requests.exceptions.RequestException as e:
534
+ print(f"RequestException during streaming: {e}")
535
+ # yield "data: {{'error': 'Connection lost'}}\n\n" # Notify client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
+ return app.response_class(generate(), mimetype='text/event-stream')
 
538
 
539
+ import time
 
 
 
 
540
 
541
+ if __name__ == '__main__':
542
+ app.run(debug=True, port=7860, host="0.0.0.0") # Hugging Face uses port 7860
543