fix(schedulerclient): node memory_bytes_total no longer ×1e6 (bug-1514)
The apiserver's host max_mem is already in bytes (the worker reads sysinfo().totalram × mem_unit → bytes and passes it through unchanged via controller.capnp), but ListNodes treated it as MB and multiplied by mbToBytes, inflating each node's reported memory to ~8.2e15 (~7.3 PiB). Now reports the raw bytes (~7.7 GiB per node). - httpclient.go: drop ×mbToBytes for node max_mem; clarify mbToBytes is job-submit-path only (job max_mem is never populated → stays 0) - ordermap_test.go: correct assertion that had encoded the buggy value - httpclient_test.go: regression test TestListNodeMemoryBytesKeepsByteUnit - buglog.json: bug-1514 + close-tags for the bug-1494 family (prior session) Also add MCP demo deliverables: - docs/claude-code-demo-guide.md: live Claude Code demo guide (7 scenes) - docs/mcp-demo-design.md: leadership demo design - docs/mcp-demo-run.py: add --slow/--color flags for screen recording Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bbe8ed3335
commit
06887985b4
7 changed files with 423 additions and 36 deletions
|
|
@ -195,7 +195,7 @@ func (hc *HTTPClient) ListNodes(ctx context.Context) ([]*URMNode, error) {
|
|||
Status: NodeStatusFromString(rawStatus),
|
||||
CoresTotal: h.rowInt("num_cpus"),
|
||||
CoresUsed: numRun, // running jobs ≈ cores used
|
||||
MemoryBytesTotal: int64(h.rowInt("max_mem")) * mbToBytes,
|
||||
MemoryBytesTotal: int64(h.rowInt("max_mem")), // max_mem is already bytes (worker reports sysinfo().totalram bytes); do NOT ×1e6
|
||||
MemoryBytesUsed: 0, // apiserver does not expose live used-memory for hosts
|
||||
GPUCount: 0, // TODO: parse from extensions/topology when available
|
||||
Features: nil,
|
||||
|
|
@ -393,7 +393,10 @@ func (hc *HTTPClient) getJobs(req *resty.Request, url string, filter JobFilter)
|
|||
|
||||
// ── internal types for JSON decoding (apiserver response shapes) ────────────
|
||||
|
||||
const mbToBytes = 1_000_000 // MaxMem from apiserver is in MB → stored as bytes in URM
|
||||
// mbToBytes scales a value that is genuinely in MB to bytes. It applies ONLY
|
||||
// to the job submit path where mem_limit is formatted as "<MB> MB"
|
||||
// (write.go). Node host max_mem is already in bytes — do NOT scale it.
|
||||
const mbToBytes = 1_000_000
|
||||
|
||||
// apiEnvelope is the standard top-level response: {"code":200,"msg":"ok","data":...}
|
||||
type apiEnvelope struct {
|
||||
|
|
|
|||
51
src/frontend/schedulerclient/httpclient_test.go
Normal file
51
src/frontend/schedulerclient/httpclient_test.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package schedulerclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestListNodeMemoryBytesKeepsByteUnit verifies the node total memory is NOT
|
||||
// re-scaled by the client: the apiserver's `max_mem` field is already in bytes
|
||||
// (the worker reads sysinfo().totalram * mem_unit → bytes and passes it through
|
||||
// unchanged), so scheduler_list_nodes must report memory_bytes_total ≈ 8.2e9
|
||||
// for a ~7.7 GiB node — not the ×1e6-inflated ~8.2e15 that httpclient.go:198
|
||||
// produced when it wrongly treated apiserver bytes as MB.
|
||||
func TestListNodeMemoryBytesKeepsByteUnit(t *testing.T) {
|
||||
// 8224825344 bytes = the real MemTotal of the t1 node (7.66 GiB).
|
||||
const realMemBytes = 8224825344
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got := r.Header.Get("Authorization"); got == "" {
|
||||
t.Errorf("expected Authorization header, got empty")
|
||||
}
|
||||
if got := r.URL.Path; got != "/xce/v1/hosts" {
|
||||
t.Errorf("unexpected path %q", got)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
body := `{"code":200,"msg":"ok","data":{"count":1,"rows":[` +
|
||||
`{"fields":["host_name","host_status","num_cpus","num_run","max_mem"],` +
|
||||
`"data":{"host_name":"t1","host_status":"idle","num_cpus":8,"num_run":0,"max_mem":8224825344}}` +
|
||||
`]}}`
|
||||
_, _ = w.Write([]byte(body))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
hc := NewClient(srv.URL, "xce_ak_test")
|
||||
nodes, err := hc.ListNodes(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("ListNodes: %v", err)
|
||||
}
|
||||
if len(nodes) != 1 {
|
||||
t.Fatalf("expected 1 node, got %d", len(nodes))
|
||||
}
|
||||
n := nodes[0]
|
||||
if n.Name != "t1" || n.CoresTotal != 8 {
|
||||
t.Fatalf("unexpected node: %+v", n)
|
||||
}
|
||||
if n.MemoryBytesTotal != realMemBytes {
|
||||
t.Fatalf("MemoryBytesTotal = %d, want %d (apiserver max_mem is already bytes; must not be ×1e6)", n.MemoryBytesTotal, realMemBytes)
|
||||
}
|
||||
}
|
||||
|
|
@ -56,8 +56,10 @@ func TestOrderMap_RowsUseColumnarShape(t *testing.T) {
|
|||
if n.CoresTotal != 8 {
|
||||
t.Errorf("node.CoresTotal = %d, want 8", n.CoresTotal)
|
||||
}
|
||||
if n.MemoryBytesTotal != 8224821248*mbToBytes {
|
||||
t.Errorf("node.MemoryBytesTotal = %d, want %d", n.MemoryBytesTotal, 8224821248*mbToBytes)
|
||||
// max_mem from apiserver is already bytes (worker reports sysinfo bytes);
|
||||
// the client must NOT scale it by mbToBytes (see httpclient.go ListNodes).
|
||||
if n.MemoryBytesTotal != 8224821248 {
|
||||
t.Errorf("node.MemoryBytesTotal = %d, want %d", n.MemoryBytesTotal, 8224821248)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue