Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0384fb148 | ||
|
|
6e815ca21a |
No files matched your search
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user