Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ has a matching GitHub release whose body comes from its section below.

### Fixed

- `berth vfio status ADDR` asks berthd for its view instead of calling it unreachable, and while the
card is in a VM it says so instead of printing the last memory reading as current.
- The wake page and start errors show an engine's last log line without terminal color codes
(`[32m[INFO][0m`), and a progress bar's line as its last redraw.
- Windows: a process the GPU counters report but nvidia-smi does not list, such as dwm.exe
Expand Down
41 changes: 32 additions & 9 deletions internal/cli/vfio.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,20 @@ type vfioCard struct {
}

// vfioCardAt resolves the governed card. With an explicit address (status
// takes one as its argument) no daemon or nvidia-smi is asked at all, so the
// state stays readable while the card is bound to vfio-pci and nvidia-smi
// cannot see it. Otherwise the status berthd reports names the scheduled
// takes one as its argument) nvidia-smi is not asked, so the state stays
// readable while the card is bound to vfio-pci and nvidia-smi cannot see it;
// berthd is asked either way. Otherwise the status berthd reports names the scheduled
// index, and nvidia-smi names that card's bus id; when berthd is down or
// has no reading yet, index 0.
func vfioCardAt(sock, explicit, smiBin string) (vfioCard, *Error) {
if explicit != "" {
addr := vfio.SysAddr(explicit)
return vfioCard{Addr: addr, BusID: explicitNvidiaForm(addr)}, nil
}
var card vfioCard
st, err := vfioStatusOf(sock)
card.Daemon, card.DialErr = st, err
if explicit != "" {
card.Addr = vfio.SysAddr(explicit)
card.BusID = explicitNvidiaForm(card.Addr)
return card, nil
}
if err == nil {
card.Index = vfioIndex(st)
}
Expand Down Expand Up @@ -150,6 +151,10 @@ type vfioBerthdDoc struct {
ProbeErr string `json:"probe_error,omitempty"`
GPU *vfioGPUDoc `json:"gpu,omitempty"`
Hold string `json:"hold,omitempty"` // the granted vfio hold, lease and engine
// Passthrough is berthd's card state while the card is in a VM or
// switching ("passthrough", "detaching"); its memory figures are then the
// last reading before the hold, not the card now.
Passthrough string `json:"passthrough,omitempty"`
}

type vfioGPUDoc struct {
Expand Down Expand Up @@ -240,8 +245,13 @@ func (v vfioStatusView) Human(c *Context, ok bool) error {
return nil
}
if g := d.Berthd.GPU; g != nil {
c.Printf("berthd gpu%d %s: %s used of %s (read %.0f s ago)\n",
g.Index, g.Name, server.MiB(g.Used), server.MiB(g.Total), g.Age)
if d.Berthd.Passthrough != "" {
c.Printf("berthd gpu%d %s: %s (last memory reading %.0f s ago)\n",
g.Index, g.Name, cardLine(&proto.Passthrough{State: d.Berthd.Passthrough, Driver: vfioDriverOf(d)}), g.Age)
} else {
c.Printf("berthd gpu%d %s: %s used of %s (read %.0f s ago)\n",
g.Index, g.Name, server.MiB(g.Used), server.MiB(g.Total), g.Age)
}
}
if d.Berthd.ProbeErr != "" {
c.Printf("probe %s\n", c.Style.Err(d.Berthd.ProbeErr))
Expand Down Expand Up @@ -339,6 +349,9 @@ func vfioBerthdView(card vfioCard) *vfioBerthdDoc {
return &vfioBerthdDoc{}
}
b := &vfioBerthdDoc{Reachable: true, ProbeErr: card.Daemon.ProbeErr}
if p := card.Daemon.Passthrough; p != nil {
b.Passthrough = p.State
}
for _, g := range card.Daemon.GPUs {
if g.Index == card.Index {
b.GPU = &vfioGPUDoc{Index: g.Index, Name: g.Name, Used: g.Used, Total: g.Total, Age: g.Age}
Expand All @@ -351,3 +364,13 @@ func vfioBerthdView(card vfioCard) *vfioBerthdDoc {
}
return b
}

// vfioDriverOf is the driver bound to the card's own function in the reading.
func vfioDriverOf(d vfioStatusDoc) string {
for _, f := range d.Group {
if f.Addr == d.Addr {
return f.Driver
}
}
return ""
}
37 changes: 37 additions & 0 deletions internal/cli/vfio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,24 @@ func TestVfioStatusReportsPassthroughWithAnAddress(t *testing.T) {
}
}

// TestVfioStatusWithAnAddressStillAsksBerthd: an address skips nvidia-smi,
// which cannot see a card on vfio-pci, but berthd still answers and its view
// is shown; it is not reported unreachable.
func TestVfioStatusWithAnAddressStillAsksBerthd(t *testing.T) {
isolate(t)
sock, _ := startVfioDaemon(t)
tr := newVfioTree(t)
tr.function(vfioAddr, "vfio-pci")
tr.group()
code, out, errOut := runVfioStatusIn(t, tr, tr.smi(), "--socket", sock, vfioAddr)
if code != ExitOK {
t.Fatalf("exit = %d, want 0 (stderr %q)", code, errOut)
}
if strings.Contains(out, "unreachable") {
t.Errorf("berthd answers but the report says unreachable: %q", out)
}
}

// TestVfioRunRefusesIOMMUOff: exit 1 with the hint, and nothing is acquired.
func TestVfioRunRefusesIOMMUOff(t *testing.T) {
isolate(t)
Expand Down Expand Up @@ -632,3 +650,22 @@ func TestVfioRunScriptExit127StillWaitsForTheCard(t *testing.T) {
}
waitUntil(t, none(b))
}

// TestVfioStatusNamesTheVMNotAStaleReading: while berthd reports the card in a
// VM, its memory figures are the last reading before the hold; the report
// says where the card is instead of printing them as current.
func TestVfioStatusNamesTheVMNotAStaleReading(t *testing.T) {
var out bytes.Buffer
c := &Context{Out: &out, Err: io.Discard, Format: FormatHuman, Style: newStyle(&out), errStyle: newStyle(io.Discard)}
doc := vfioStatusDoc{Addr: vfioAddr, IOMMU: true, Passed: true,
Group: []vfio.Function{{Addr: vfioAddr, Driver: "vfio-pci"}},
Berthd: &vfioBerthdDoc{Reachable: true, Passthrough: "passthrough",
GPU: &vfioGPUDoc{Index: 0, Name: "RTX 4090", Used: 4 << 20, Total: 24 << 30, Age: 1400}}}
if err := (vfioStatusView{doc}).Human(c, true); err != nil {
t.Fatal(err)
}
got := out.String()
if !strings.Contains(got, "passed through to a VM (vfio-pci) (last memory reading 1400 s ago)") || strings.Contains(got, "used of") {
t.Errorf("berthd's line = %q, want the VM state and no current-looking memory figure", got)
}
}
Loading