GPU Inventory
Part of the Scheduler feature area (axap-gpu-volcano-ui, MFE id 'scheduler'). Sibling docs: gpu-overview, workload, timetable.
Overview
GPU Inventory (schedulerGpuInventory) is a cluster > node > GPU chip inventory
screen with Explorer / Topology / List views, a KPI row, filters, and a GPU detail drawer.
It lives at
modules/volcano/dashboard/pages/GpuInventory/index.vue
with siblings GpuExplorer.vue, NodeTile.vue, GpuDetailDrawer.vue, inventoryNav.ts.
Its primary real-data source is zcp-mcm-go (/mcm/resource/v1beta1/gpu/instances), a DCGM-telemetry service — unlike GPU Overview/Time Table, this page does not go through the Volcano scheduler backend directly for its GPU list.
A demo mode is baked into this codebase and is ON by default
(modules/volcano/dashboard/demo/isDemo.ts)
— disabled only per-tab via ?demo=0. In demo mode this page uses
DEMO_FULL_NODES/DEMO_MIG_NODES/DEMO_TS_NODES/DEMO_TOPO_SUMMARY from
src/modules/volcano/dashboard/demo/demoData.ts instead of calling the backend.
Route Design
Route constants: constants/routeNames/index.ts,
paths: constants/routes/index.ts, lazy components:
pages/index.ts. Registered in both the system and project route
tables built by buildRoutes() in router/index.ts.
| Route name | Raw path | System path | Project path | Component |
|---|---|---|---|---|
| ROUTE_NAMES.GPU_INVENTORY (schedulerGpuInventory) | /gpu-inventory | /system/scheduler/gpu-inventory | /proj/:projectId/scheduler/gpu-inventory | GpuInventory/index.vue — router/index.ts |
No hidden/drill-down routes belong to this screen; the GPU detail view is an in-page GpuDetailDrawer.vue (not a route), so selectedGpu state alone controls visibility.
Route mechanics common to the whole Scheduler MFE (system vs. project route table selection, getHostProjectId() scope-locking) are the same as documented in gpu-overview and apply here too — this page reads getHostProjectId()-equivalent scope info indirectly via resolveCluster() (useProfileStore), not from its own route meta.
UI Structure
GPU Inventory → GpuInventory/index.vue
├── Page head: title + description + 3-way view toggle (익스플로러 / 토폴로지 / 목록)
├── KPI row: 노드 / 총 GPU / 가동 / 유휴 점유 / 미할당 / 가동률 (+ bar)
├── Filter bar: 클러스터 / GPU 모델 / GPU 상태 / 타입(FULL/MIG/TS) selects + 검색 + active-filter tags
├── Loading / Error states
├── Explorer view → GpuExplorer.vue (cluster>node>GPU tree + tabbed detail, resizable tree pane)
├── Topology view → legend card + NodeTile.vue grid (per-node GPU cell grid)
├── List view → inline <table> of all GPUs (node/model/type/uuid/status/util/pool/workload)
└── GpuDetailDrawer.vue (side drawer, opened from any of the 3 views via openDrawer/cell click)
Component Design
- GpuInventory/index.vue: owns allGpus (flat list of NormalizedGpu), filters (reactive: cluster/model/status/type/text), viewMode ('explorer'|'topology'|'list'), loading/error, selectedGpu. KPI values (summary.nodes/total/allocated/idle/free/ allocPct) and filtered lists (filteredGpus, filteredNodes) are computed from allGpus + filters (computed definitions not fully traced in this pass — see Assumptions).
- GpuExplorer.vue: left resizable tree (treeWidth, flatRows, treeQuery filter, toggle()/select()) showing Cluster → Node → GPU rows with expand/collapse chevrons; selecting a row drives a right-hand tabbed detail panel (tabs not enumerated in this pass).
- NodeTile.vue: renders one node’s GPUs as a cell grid (cells prop = node’s GPU list); emits cell-click up to the page’s openDrawer().
- GpuDetailDrawer.vue: side drawer bound to :gpu="selectedGpu", closes via @close="selectedGpu = null".
- inventoryNav.ts (takePendingInventoryNav()): a one-shot, in-memory (non-URL)
hand-off channel — other pages (e.g. GPU Overview’s topology canvas “open inventory”
action) can push a
{gpu, tab, node, status, view}context that this page consumes once on mount, falling back to route.query (gpu/tab/node/status) if no pending nav context exists. On mount: if initialGpu is set, viewMode is forced to 'explorer' (and filters.text seeded with the node name); if initialStatus is set, filters.status is seeded and viewMode is set from navCtx.view (defaults 'list') — this is how a KPI count click elsewhere deep-links into a pre-filtered inventory list.
API Design
All calls use services.axiosInstance from @cloudz-mp/zmp-base-ui.
Primary data source — modules/volcano/dashboard/apis/mcmPodMetrics.ts
| Function | Path | Notes |
|---|---|---|
| fetchGpuInstances(cluster?) | GET {mcmBase}/gpu/instances?cluster&page=1&size=500&sort=index,instanceId | Sole real-data call made by this page’s reload(); mcmBase = getMcmBasePath() = "/mcm/resource/v1beta1" |
| matchInstanceByPod(instances, namespace, podName) | client-side helper, no HTTP | matches an instance to a workload pod (used elsewhere, e.g. PodVgpuDrawer) |
resolveCluster() (from modules/volcano/dashboard/apis/index.ts) is
imported directly into this page (import { resolveCluster } from "../../apis") to resolve
the cluster passed to fetchGpuInstances.
GpuInstanceView response fields consumed: gpuUtilization, memoryUsed/memoryTotal, temperature, powerUsage, isMigInstance/isMigMode/isTimeSlicingMode, allocatedPods[] (name/namespace/project) — mapped by a page-local normalizeInstance() into the NormalizedGpu shape (util, memTotalGb/memUsedGb, tempC, powerW, pool, workload, workloads[], type).
No mutation endpoints (create/update/delete) are called from this screen — it is read-only.
Dev-time proxy: /api/mcm and /mcm/resource → zcp-mcm-go (localhost:8080) —
vite.config.ts.
State Management
- No Pinia store is used by this page directly — all inventory data (allGpus, filters, viewMode, loading, error, selectedGpu) is page-local ref/reactive state in GpuInventory/index.vue.
- useProfileStore is used only indirectly, through resolveCluster(), to pick the active cluster for the fetchGpuInstances call — no direct read/write of profile state from this page’s own script beyond that import chain.
- inventoryNav.ts acts as an ad hoc, module-scoped (non-reactive, one-shot) state channel for cross-page deep-linking — not a Pinia store, and not persisted.
Business Logic
- GPU status classification — GPU cells/rows are classified into running/idle/free/error states for filtering and KPI aggregation (filters.status options: running, idle(유휴 점유), free(미할당), error(오류)); exact derivation logic (e.g. thresholds distinguishing “idle” allocated-but-unused GPUs from “free” unallocated ones) was not traced to a specific function in this pass — see Assumptions.
- Type classification — type: "MIG" | "TS" | "FULL" is derived per-instance from inst.isMigInstance || inst.isMigMode → MIG, inst.isTimeSlicingMode → TS, else FULL (normalizeInstance()).
- Multi-occupancy exposure for shared GPUs — MIG/Time-Slicing GPUs can have multiple allocatedPods; the FE exposes all of them via a workloads[] array on each NormalizedGpu, not just the first occupant (comment: “공유 GPU(MIG/TS)는 allocatedPods 가 다수 — 전부 노출”).
- Deep-link view forcing — arriving via inventoryNav/route.query with a gpu value forces viewMode = 'explorer'; arriving with a status value forces filters.status and switches to the nav-specified view (default 'list') — see Component Design.
- Demo mode is default-ON — same cross-cutting rule as the other three screens; isDemoMode() is checked at the top of reload(), short-circuiting the real fetchGpuInstances call entirely.
Data Flow
flowchart TD
R[/system/scheduler/gpu-inventory/] --> P[GpuInventory/index.vue]
P -->|onMounted -> reload| DEMO{isDemoMode?}
DEMO -->|yes| D1[normalizeDemoData -> allGpus]
DEMO -->|no| A1[apis/mcmPodMetrics: fetchGpuInstances]
A1 --> BE[(zcp-mcm-go /mcm/resource/v1beta1)]
BE --> A1
A1 -->|instances.map normalizeInstance| P
P --> V1[Explorer view]
P --> V2[Topology view]
P --> V3[List view]
V1 & V2 & V3 -->|click| Drawer[GpuDetailDrawer.vue]
reload() is the single source of truth for this page’s GPU list; there is no polling timer in this page (unlike GPU Overview/Time Table) — data is fetched once on mount and on explicit “다시 시도” retry after an error.
Dependency Graph
flowchart LR
GpuInventory --> GpuExplorer
GpuInventory --> NodeTile
GpuInventory --> GpuDetailDrawer
GpuInventory --> inventoryNav["inventoryNav.ts"]
GpuInventory --> apisMcm["apis/mcmPodMetrics.ts (mcm)"]
GpuInventory --> apisIndex["apis/index.ts (resolveCluster)"]
apisMcm --> apisIndex
apisIndex --> useProfileStore
Sequence Flow
GPU Inventory load
sequenceDiagram
actor User
participant Page as GpuInventory/index.vue
participant API as apis/mcmPodMetrics.ts
participant BE as zcp-mcm-go
User->>Page: navigate to /system/scheduler/gpu-inventory
Page->>Page: onMounted -> reload()
alt demo mode
Page->>Page: allGpus = normalizeDemoData()
else real backend
Page->>API: fetchGpuInstances(resolveCluster())
API->>BE: GET /mcm/resource/v1beta1/gpu/instances?cluster&page&size&sort
BE-->>API: GpuInstanceView[]
API-->>Page: instances
Page->>Page: allGpus = instances.map(normalizeInstance)
end
Page-->>User: renders KPI row + Explorer/Topology/List view
GPU detail drawer open (User Action → UI Update)
sequenceDiagram
actor User
participant View as GpuExplorer.vue / NodeTile.vue / List <table>
participant Page as GpuInventory/index.vue
participant Drawer as GpuDetailDrawer.vue
User->>View: click a GPU row/tile/tree-node
View->>Page: emit cell-click(gpu) / select(row)
Page->>Page: openDrawer(gpu) -> selectedGpu = gpu
Page->>Drawer: :gpu="selectedGpu"
Drawer-->>User: renders GPU detail panel
User->>Drawer: click close
Drawer->>Page: emit close
Page->>Page: selectedGpu = null
Key Findings
- This is the only one of the four Scheduler screens whose primary data source is zcp-mcm-go rather than the Volcano scheduler backend (/scheduler/v1) — it reads DCGM-telemetry-enriched instance data directly, and does not call fetchVolcanoQueues/fetchWorkloads at all.
- No polling — unlike GPU Overview (configurable interval) and Time Table (fixed 30s), this page fetches once on mount; staleness requires a manual “다시 시도”/navigation refresh.
- inventoryNav.ts is a deliberate non-URL deep-link mechanism — it avoids round-tripping navigation context through query params, but this also means a hard-refresh loses that context (falls back to route.query only if present).
Risks / Technical Debt
- KPI/status derivation logic (idle vs. free vs. error thresholds) was not located in this pass — if this logic diverges from GPU Overview’s stuck/idle definitions, the two screens could report inconsistent “idle” counts for the same cluster. Flagged as Not Found in Code below pending a full read of the KPI summary computed.
- No error normalizer — error.value = e instanceof Error ? e.message : "알 수 없는 오류" surfaces raw messages; no per-status (403/404/5xx) handling like GPU Pool CRUD has.
- Single-cluster fetch only — fetchGpuInstances(cluster?) takes one cluster (default resolveCluster()), so this page cannot show an “All clusters” combined inventory the way Time Table’s “All” fan-out does.
Assumptions
- The exact computation of summary (노드/총 GPU/가동/유휴 점유/미할당/가동률) and filteredGpus/filteredNodes computed properties was Not Found in Code in this pass (template bindings were observed, but the computed definitions themselves were not read) — treat the precise idle/free/error boundary rules as unconfirmed.
- GpuExplorer.vue’s tabbed detail panel contents (what each tab shows) were Not Found in Code in this pass — only the tree/selection mechanics were inspected.
- Backend service zcp-mcm-go’s actual endpoint implementation is external to this repo and was Not Found in Code — inferred only from the FE request shape and the GpuInstanceView TypeScript interface declared in this repo.