-
Notifications
You must be signed in to change notification settings - Fork 1
Showcase: DRA GPU status controller across languages #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: demo-base
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| FROM golang:1.24 AS build | ||
| WORKDIR /src | ||
| COPY . . | ||
| RUN go build -o /out/inventory ./... | ||
|
|
||
| FROM gcr.io/distroless/static | ||
| COPY --from=build /out/inventory /inventory | ||
| ENTRYPOINT ["/inventory"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| example.com/dra v1.3.0 h1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa= | ||
| example.com/dra v1.3.0/go.mod h1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb= | ||
| example.com/dra v1.4.0 h1:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee= | ||
| example.com/dra v1.4.0/go.mod h1:fffffffffffffffffffffffffffffffffffffffffff= | ||
| k8s.io/api v0.30.0 h1:ccccccccccccccccccccccccccccccccccccccccccc= | ||
| k8s.io/api v0.30.0/go.mod h1:ddddddddddddddddddddddddddddddddddddddddddd= | ||
| k8s.io/klog/v2 v2.120.1 h1:ggggggggggggggggggggggggggggggggggggggggggg= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,14 +3,19 @@ package gpu | |
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| ) | ||
|
|
||
| // ErrNodeNotFound is returned when a node has no inventory yet. | ||
| var ErrNodeNotFound = errors.New("node not found") | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call extracting a sentinel error — now callers can |
||
|
|
||
| // DeviceInfo describes a single GPU exposed to a pod. | ||
| type DeviceInfo struct { | ||
| Name string | ||
| Driver string | ||
| UUID string | ||
| Shared bool | ||
| } | ||
|
|
||
| // StatusController reconciles GPU claims against node inventory. | ||
|
|
@@ -21,16 +26,24 @@ type StatusController struct { | |
| func (c *StatusController) getGPUStatus(info DeviceInfo, node string) (string, error) { | ||
| devices, ok := c.nodes[node] | ||
| if !ok { | ||
| return "", fmt.Errorf("node %q not found", node) | ||
| return "", fmt.Errorf("%w: %q", ErrNodeNotFound, node) | ||
| } | ||
| for _, d := range devices { | ||
| if d.UUID == info.UUID { | ||
| if d.Shared { | ||
| return "shared", nil | ||
| } | ||
| return "allocated", nil | ||
| } | ||
|
Comment on lines
+33
to
37
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This shared-vs-allocated branch reads cleanly. Consider a small table test covering both statuses. |
||
| } | ||
| return "pending", nil | ||
| } | ||
|
|
||
| // Count returns how many devices a node exposes. | ||
| func (c *StatusController) Count(node string) int { | ||
| return len(c.nodes[node]) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Zero value is fine — |
||
| } | ||
|
|
||
| func (c *StatusController) Reconcile(ctx context.Context, node string) error { | ||
| _, err := c.getGPUStatus(DeviceInfo{}, node) | ||
| return err | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import Foundation | ||
|
|
||
| struct GPUMetrics { | ||
| let uuid: String | ||
| let utilization: Double | ||
| let memoryUsed: UInt64 | ||
|
|
||
| var isSaturated: Bool { | ||
| utilization > 0.9 | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||
| } | ||
| } | ||
|
|
||
| func summarize(_ samples: [GPUMetrics]) -> String { | ||
| let busy = samples.filter { $0.isSaturated }.count | ||
| return "\(busy)/\(samples.count) GPUs saturated" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| SELECT node, COUNT(*) AS gpus, SUM(memory_gb) AS total_memory | ||
| FROM inventory | ||
| WHERE status = 'ready' | ||
| GROUP BY node | ||
| ORDER BY total_memory DESC; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,13 +1,14 @@ | ||||||
| """A tiny async status server for the DRA controller.""" | ||||||
| import asyncio | ||||||
| from dataclasses import dataclass | ||||||
| from dataclasses import dataclass, field | ||||||
|
|
||||||
|
|
||||||
| @dataclass | ||||||
| class Claim: | ||||||
| name: str | ||||||
| driver: str | ||||||
| ready: bool = False | ||||||
| devices: list[str] = field(default_factory=list) | ||||||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer an immutable default so the dataclass instances don't share state:
Suggested change
|
||||||
|
|
||||||
|
|
||||||
| class Server: | ||||||
|
|
@@ -18,5 +19,10 @@ async def add(self, claim: Claim) -> None: | |||||
| await asyncio.sleep(0) | ||||||
| self.claims.append(claim) | ||||||
|
|
||||||
| async def ready(self, name: str) -> None: | ||||||
| for c in self.claims: | ||||||
| if c.name == name: | ||||||
| c.ready = True | ||||||
|
|
||||||
| def pending(self) -> list[Claim]: | ||||||
| return [c for c in self.claims if not c.ready] | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright 2026 Daniil Antoshin | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| import { createSignal, For } from "solid-js"; | ||
|
|
||
| interface Device { | ||
| uuid: string; | ||
| status: "allocated" | "pending" | "shared"; | ||
| } | ||
|
|
||
| export function DeviceList(props: { devices: Device[] }) { | ||
| const [filter, setFilter] = createSignal(""); | ||
| const shown = () => | ||
| props.devices.filter((d) => d.uuid.includes(filter())); | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice — the filter reads well. |
||
| return ( | ||
| <section class="devices"> | ||
| <input placeholder="filter…" onInput={(e) => setFilter(e.currentTarget.value)} /> | ||
| <ul> | ||
| <For each={shown()}>{(d) => <li data-status={d.status}>{d.uuid}</li>}</For> | ||
| </ul> | ||
| </section> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.