Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>GPU Monitor</title> | |
| <style> | |
| body { | |
| font-family: monospace; | |
| background: #1e1e1e; | |
| color: #cfcfcf; | |
| padding: 2rem; | |
| } | |
| h1, h2 { | |
| color: #4CAF50; | |
| } | |
| .container { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .card { | |
| background: #2d2d2d; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin-bottom: 20px; | |
| } | |
| pre { | |
| overflow-x: auto; | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| } | |
| #refresh { | |
| background-color: #4CAF50; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| margin-bottom: 20px; | |
| font-size: 16px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| } | |
| #refresh:hover { | |
| background-color: #45a049; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| th, td { | |
| text-align: left; | |
| padding: 8px; | |
| border-bottom: 1px solid #444; | |
| } | |
| th { | |
| background-color: #333; | |
| color: #4CAF50; | |
| } | |
| .user-row { | |
| background-color: #2a2a2a; | |
| } | |
| .user-row:nth-child(even) { | |
| background-color: #303030; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>π Remote GPU Status</h1> | |
| <button id="refresh">π Refresh</button> | |
| <div class="container"> | |
| <div class="card"> | |
| <h2>π₯οΈ GPU Information</h2> | |
| <pre id="gpu-info">Loading GPU information...</pre> | |
| </div> | |
| <div class="card"> | |
| <h2>β±οΈ User GPU Hours - not super accurate, check activity timeline to track your process</h2> | |
| <div id="user-hours">Loading user hours...</div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>π Activity Timeline</h2> | |
| <pre id="activity-timeline">Loading activity timeline...</pre> | |
| </div> | |
| <script> | |
| const fetchGpuInfo = async () => { | |
| try { | |
| const res = await fetch("gpustat_output.txt"); | |
| if (!res.ok) throw new Error("Failed to fetch GPU info."); | |
| const text = await res.text(); | |
| document.getElementById("gpu-info").textContent = text; | |
| } catch (e) { | |
| document.getElementById("gpu-info").textContent = "Error loading GPU info."; | |
| } | |
| }; | |
| const fetchUserData = async () => { | |
| try { | |
| const res = await fetch("gpu_usage_db.json"); | |
| if (!res.ok) throw new Error("Failed to fetch user data."); | |
| const data = await res.json(); | |
| displayUserHours(data.user_gpu_hours); | |
| displayActivityTimeline(data.samples); | |
| } catch (e) { | |
| document.getElementById("user-hours").textContent = "Error loading user data."; | |
| document.getElementById("activity-timeline").textContent = "Error loading timeline data."; | |
| } | |
| }; | |
| const displayUserHours = (userHours) => { | |
| const container = document.getElementById("user-hours"); | |
| // Clear previous content | |
| container.innerHTML = ""; | |
| if (Object.keys(userHours).length === 0) { | |
| container.textContent = "No user data available."; | |
| return; | |
| } | |
| // Create table | |
| const table = document.createElement("table"); | |
| // Create header | |
| const thead = document.createElement("thead"); | |
| const headerRow = document.createElement("tr"); | |
| ["User", "GPU ID", "Hours Used"].forEach(text => { | |
| const th = document.createElement("th"); | |
| th.textContent = text; | |
| headerRow.appendChild(th); | |
| }); | |
| thead.appendChild(headerRow); | |
| table.appendChild(thead); | |
| // Create body | |
| const tbody = document.createElement("tbody"); | |
| Object.entries(userHours).forEach(([key, hours]) => { | |
| const [user, gpuId] = key.split(":"); | |
| const row = document.createElement("tr"); | |
| row.className = "user-row"; | |
| const userCell = document.createElement("td"); | |
| userCell.textContent = user; | |
| const gpuCell = document.createElement("td"); | |
| gpuCell.textContent = gpuId; | |
| const hoursCell = document.createElement("td"); | |
| hoursCell.textContent = hours.toFixed(2); | |
| row.appendChild(userCell); | |
| row.appendChild(gpuCell); | |
| row.appendChild(hoursCell); | |
| tbody.appendChild(row); | |
| }); | |
| table.appendChild(tbody); | |
| container.appendChild(table); | |
| }; | |
| const displayActivityTimeline = (samples) => { | |
| const container = document.getElementById("activity-timeline"); | |
| if (!samples || samples.length === 0) { | |
| container.textContent = "No activity data available."; | |
| return; | |
| } | |
| // Format the data for display | |
| const formattedData = samples.map(sample => { | |
| const date = new Date(sample.timestamp); | |
| const timeString = date.toLocaleTimeString(); | |
| const users = sample.users.join(", "); | |
| return `${timeString}: Active users - ${users}`; | |
| }).join("\n"); | |
| container.textContent = formattedData; | |
| }; | |
| // Refresh function that updates all data | |
| const refreshAll = () => { | |
| fetchGpuInfo(); | |
| fetchUserData(); | |
| }; | |
| // Add event listener | |
| document.getElementById("refresh").addEventListener("click", refreshAll); | |
| // Initial load | |
| refreshAll(); | |
| </script> | |
| </body> | |
| </html> |