Skip to content

Commit 0f71a44

Browse files
authored
Feature/compact view single server (#63)
* compact view on a single server with multiple gpus * update the unit tests for frontend --------- Co-authored-by: Panos <>
1 parent 9a07f45 commit 0f71a44

7 files changed

Lines changed: 589 additions & 98 deletions

File tree

static/js/gpu-cards.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,45 @@ function updateOverviewCard(gpuId, gpuInfo, shouldUpdateDOM = true) {
6262
updateEnhancedOverviewCard(gpuId, gpuInfo, shouldUpdateDOM);
6363
}
6464

65+
// ============================================
66+
// Compact GPU Overview Card (multi-GPU single-server)
67+
// ============================================
68+
69+
function createCompactOverviewCard(gpuId, gpuInfo) {
70+
const memory_used = getMetricValue(gpuInfo, 'memory_used', 0);
71+
const memory_total = getMetricValue(gpuInfo, 'memory_total', 1);
72+
const memPercent = (memory_used / memory_total) * 100;
73+
74+
return `
75+
<div class="overview-gpu-card" data-gpu-id="${gpuId}" onclick="switchToView('gpu-${gpuId}')">
76+
<div class="overview-gpu-name">
77+
<h2>GPU ${gpuId}</h2>
78+
<p>${getMetricValue(gpuInfo, 'name', 'Unknown GPU')}</p>
79+
</div>
80+
<div class="overview-metrics">
81+
<div class="overview-metric">
82+
<div class="overview-metric-value" id="overview-util-${gpuId}">${getMetricValue(gpuInfo, 'utilization', 0)}%</div>
83+
<div class="overview-metric-label">UTIL</div>
84+
</div>
85+
<div class="overview-metric">
86+
<div class="overview-metric-value" id="overview-temp-${gpuId}">${getMetricValue(gpuInfo, 'temperature', 0)}°</div>
87+
<div class="overview-metric-label">TEMP</div>
88+
</div>
89+
<div class="overview-metric">
90+
<div class="overview-metric-value" id="overview-mem-${gpuId}">${Math.round(memPercent)}%</div>
91+
<div class="overview-metric-label">MEM</div>
92+
</div>
93+
<div class="overview-metric">
94+
<div class="overview-metric-value" id="overview-power-${gpuId}">${getMetricValue(gpuInfo, 'power_draw', 0).toFixed(0)}W</div>
95+
<div class="overview-metric-label">POWER</div>
96+
</div>
97+
</div>
98+
<div class="overview-mini-chart">
99+
<canvas id="overview-chart-${gpuId}"></canvas>
100+
</div>
101+
</div>`;
102+
}
103+
65104
// ============================================
66105
// Single GPU Overview — Enhanced Dashboard
67106
// ============================================

static/js/socket-handlers.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,23 @@ function handleSocketMessage(event) {
226226
// Handle initial card creation (can't be batched since we need the DOM element)
227227
const existingOverview = overviewContainer.querySelector(`[data-gpu-id="${gpuId}"]`);
228228
if (!existingOverview) {
229-
overviewContainer.insertAdjacentHTML('beforeend', createEnhancedOverviewCard(gpuId, gpuInfo));
230-
initOverviewMiniChart(gpuId, gpuInfo.utilization);
231-
// Auto-expand processes for single GPU
232-
if (gpuCount === 1) {
229+
if (gpuCount >= 2) {
230+
// Compact layout for multi-GPU servers
231+
let nodeGrid = overviewContainer.querySelector('.node-grid');
232+
if (!nodeGrid) {
233+
const hostname = data.node_name || 'GPU Server';
234+
overviewContainer.insertAdjacentHTML('beforeend', `
235+
<div class="node-group" data-node="_local">
236+
<div class="node-label">${hostname}</div>
237+
<div class="node-grid"></div>
238+
</div>
239+
`);
240+
nodeGrid = overviewContainer.querySelector('.node-grid');
241+
}
242+
nodeGrid.insertAdjacentHTML('beforeend', createCompactOverviewCard(gpuId, gpuInfo));
243+
} else {
244+
overviewContainer.insertAdjacentHTML('beforeend', createEnhancedOverviewCard(gpuId, gpuInfo));
245+
// Auto-expand processes for single GPU
233246
setTimeout(() => {
234247
const content = document.getElementById('processes-content');
235248
const header = document.querySelector('.processes-header');
@@ -241,6 +254,7 @@ function handleSocketMessage(event) {
241254
}
242255
}, 100);
243256
}
257+
initOverviewMiniChart(gpuId, gpuInfo.utilization);
244258
lastDOMUpdate[gpuId] = now;
245259
}
246260
});

tests/README.md

Lines changed: 8 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,29 @@
11
# GPU Hot — tests
22

3-
This folder holds **unit tests** (Python + browser-style JS), **load-test / mock-cluster** tooling, and Docker definitions for both.
4-
5-
---
6-
7-
## Unit tests
8-
9-
Backend logic is covered with **pytest** (`tests/unit/`). Frontend **static JS** (charts, UI, WebSocket helpers, `app.js`) is covered with **Vitest** + **jsdom** (`tests/frontend/`).
10-
11-
| File | Role |
12-
|------|------|
13-
| `pytest.ini` | Pytest config (`testpaths = unit`, asyncio mode) |
14-
| `package.json` | Vitest + jsdom; scripts: `npm test`, `npm run test:watch` |
15-
| `vitest.config.js` | Vitest root = this directory; `frontend/setup.js` loads `static/js` under vm |
16-
| `Dockerfile.unittest` | Image: Python deps + Node, runs pytest then Vitest |
17-
| `docker-compose.unittest.yml` | Build/run the unittest image (context: repo root) |
18-
19-
From the **repository root**, the usual entry point is:
3+
All tests are run from the **repository root** with:
204

215
```bash
226
./run_tests.sh
237
```
248

25-
That builds and runs `docker compose -f tests/docker-compose.unittest.yml` (same as a manual `docker compose … build` / `run`).
26-
27-
### Unit tests without Docker
28-
29-
Requires Python with `pytest`, `pytest-asyncio`, `httpx` (see `requirements.txt` + extras used in `Dockerfile.unittest`), and Node 18+.
30-
31-
```bash
32-
# Backend
33-
python -m pytest -c tests/pytest.ini
9+
This builds and runs the unit-test Docker image (`docker compose -f tests/docker-compose.unittest.yml`), which executes **pytest** (backend) and **Vitest** (frontend) inside the container.
3410

35-
# Frontend (from repo root)
36-
npm install --prefix tests
37-
npm test --prefix tests
38-
```
11+
---
3912

40-
To show **Vitest** `console.*` output from app code (hidden by default), run:
13+
## What's tested
4114

42-
```bash
43-
VITEST_SILENT=0 npm test --prefix tests
44-
```
15+
- **Backend** (pytest): Python logic in `tests/unit/`
16+
- **Frontend** (Vitest + jsdom): Static JS (charts, UI, WebSocket helpers) in `tests/frontend/`
4517

4618
---
4719

4820
## Load testing (mock cluster)
4921

50-
Simple load testing for multi-node GPU monitoring with realistic async patterns.
51-
52-
### Quick start
22+
A mock GPU cluster for manual load testing. Edit `docker-compose.test.yml` to choose a preset (LIGHT / MEDIUM / HEAVY), then:
5323

5424
```bash
5525
cd tests
56-
docker compose -f docker-compose.test.yml up
57-
```
58-
59-
Open http://localhost:1312 to see the dashboard.
60-
61-
### Architecture
62-
63-
- **FastAPI + AsyncIO**: async Python for mock nodes
64-
- **Native WebSockets**: direct WebSocket protocol
65-
- **Concurrent mock nodes**: multiple nodes in parallel
66-
- **Realistic GPU patterns**: training-style utilization, warmup, validation
67-
68-
### Load test presets
69-
70-
Edit `docker-compose.test.yml` and uncomment the preset you want.
71-
72-
**LIGHT (3 nodes, 14 GPUs)** — development / quick runs:
73-
74-
```yaml
75-
- NODES=2,4,8
76-
- NODE_URLS=http://mock-cluster:13120,http://mock-cluster:13121,http://mock-cluster:13122
77-
```
78-
79-
**MEDIUM (8 nodes, 64 GPUs)** — default-style medium cluster:
80-
81-
```yaml
82-
- NODES=8,8,8,8,8,8,8,8
83-
- NODE_URLS=http://mock-cluster:13120,...,http://mock-cluster:13127
84-
```
85-
86-
**HEAVY (20 nodes, 160 GPUs)** — stress / large cluster:
87-
88-
```yaml
89-
- NODES=8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
90-
- NODE_URLS=http://mock-cluster:13120,...,http://mock-cluster:13139
91-
```
92-
93-
### What’s simulated
94-
95-
- Realistic GPU patterns (epochs, warmup, validation)
96-
- Idle + busy GPUs (~40% utilization typical of many clusters)
97-
- Stable memory, clock P-states, data-loading dips, temperature correlation
98-
99-
### Load-test files
100-
101-
| File | Role |
102-
|------|------|
103-
| `test_cluster.py` | Mock GPU node (FastAPI + AsyncIO) |
104-
| `docker-compose.test.yml` | Stack + presets |
105-
| `Dockerfile.test` | Image for mock nodes |
106-
107-
### Rebuild after changes
108-
109-
```bash
110-
docker compose -f docker-compose.test.yml down
11126
docker compose -f docker-compose.test.yml up --build
11227
```
11328

114-
(Run these from the `tests/` directory, or pass `-f tests/docker-compose.test.yml` from the repo root.)
29+
Open http://localhost:1312 to see the dashboard with simulated GPU nodes.

0 commit comments

Comments
 (0)