Compare commits

...
2 Commits
Author SHA1 Message Date
Ilker ManapandClaude Opus 5.5 e0384fb148 Release v0.11.1
Release / build (push) Successful in 58s
Release / notes (push) Successful in 3s
CI / test (push) Successful in 4m0s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 23:10:20 +02:00
Ilker ManapandClaude Opus 5.5 6e815ca21a Draw the console's overview in its panel, not over the screen
The summary's GPU, fabric and idle tables were written to standard output
regardless of the writer they were given, so on a cluster with a GPU the
console printed the GPU table across its full screen every three seconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 23:09:40 +02:00
4 changed files with 53 additions and 5 deletions

No files matched your search

+11
View File
@@ -21,6 +21,17 @@ is only released once its **exit criterion** is met, not when its code lands.
Until v1.0.0 the minor version is the breaking one: configuration formats and Go
APIs change between minors without a deprecation period.
## v0.11.1 — 2026-10-03
### The console's overview no longer spills over the screen
On a cluster with a GPU, `sudo llmgw` printed the overview's GPU table onto the
terminal under the console every three seconds, scrolling across the full
screen. The summary's tables were written to standard output whatever they
were given; they are now written where the summary is, which for the console
is its panel. The test cluster the console was tried on had no GPU, which is
why it was not seen there.
## v0.11.0 — 2026-10-03
A minor version: the store can be replicated and the control plane doubled,
+6 -2
View File
@@ -504,8 +504,12 @@ func newNodeStateCmd(client func() *ctlClient, use, state, short string) *cobra.
// --- output helpers ---
func tab() *tabwriter.Writer {
return tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
func tab() *tabwriter.Writer { return tabTo(os.Stdout) }
// tabTo is tab writing where it is told: a summary drawn into the console
// went to the terminal underneath it instead, every three seconds.
func tabTo(w io.Writer) *tabwriter.Writer {
return tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
}
func emitJSON(v any) error {
+33
View File
@@ -2,8 +2,10 @@ package cli
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
@@ -51,3 +53,34 @@ func TestStatusSaysWhoLeadsTheStoreAndWhetherTheRestKeepUp(t *testing.T) {
t.Error("a single database got a store line")
}
}
func TestTheSummaryIsWrittenWhereItIsToldNotToTheTerminal(t *testing.T) {
// The console draws the summary into its panel. Its GPU table went to
// the terminal instead, through the full screen, every three seconds —
// on a cluster with a GPU, which the test cluster did not have.
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
stdout := os.Stdout
os.Stdout = w
var b bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&b)
printSummary(cmd, adminapi.Summary{
GPUs: []adminapi.ClassUse{{Class: "6000-24g", Total: 1, Used: 1}},
Fabrics: []adminapi.FabricUse{{Fabric: "ib-a", Nodes: 2}},
IdleReplicas: []adminapi.IdleReplica{{ID: "r1", Model: "m", IdleSec: 3600, GPUs: 1}},
})
os.Stdout = stdout
_ = w.Close()
leaked, _ := io.ReadAll(r)
if len(leaked) > 0 {
t.Errorf("written to the terminal: %q", leaked)
}
for _, want := range []string{"6000-24g", "ib-a", "r1"} {
if !strings.Contains(b.String(), want) {
t.Errorf("the summary lacks %q:\n%s", want, b.String())
}
}
}
+3 -3
View File
@@ -56,7 +56,7 @@ func printSummary(cmd *cobra.Command, s adminapi.Summary) {
if len(s.GPUs) > 0 {
fmt.Fprintln(out, "\nGPUS")
w := tab()
w := tabTo(out)
fmt.Fprintln(w, " CLASS\tIN USE\tFREE\tUNHEALTHY\tUSAGE")
for _, g := range s.GPUs {
fmt.Fprintf(w, " %s\t%d/%d\t%d\t%s\t%s\n",
@@ -67,7 +67,7 @@ func printSummary(cmd *cobra.Command, s adminapi.Summary) {
if len(s.Fabrics) > 0 {
fmt.Fprintln(out, "\nFABRICS")
w := tab()
w := tabTo(out)
// The free-block column is the one that decides whether a multi-node
// replica can land: free cards on the wrong fabric are not capacity for
// a model that spans nodes, because a replica may never cross one.
@@ -138,7 +138,7 @@ func printSummary(cmd *cobra.Command, s adminapi.Summary) {
if len(s.IdleReplicas) > 0 {
fmt.Fprintln(out, "\nIDLE")
w := tab()
w := tabTo(out)
fmt.Fprintln(w, " REPLICA\tMODEL\tGPUS\tUNUSED FOR")
for _, r := range s.IdleReplicas {
fmt.Fprintf(w, " %s\t%s\t%d\t%s\n", r.ID, r.Model, r.GPUs,