mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-26 21:47:53 +00:00
Red commit: 929da3c6dcc1b619c27478291125d1c91323db8f — CI: https://github.com/Kpa-clawbot/CoreScope/commit/929da3c6dcc1b619c27478291125d1c91323db8f/checks Fixes #1010. ## What Adds `GOMEMLIMIT` support to both `cmd/server` and `cmd/ingestor` per the locked triage scope on #1010. Precedence (env wins): 1. `GOMEMLIMIT` env var 2. `runtime.maxMemoryMB` config field (new) 3. Server only: implicit `packetStore.maxMemoryMB * 1.5` (existing #836 behavior, unchanged when `runtime.maxMemoryMB` is absent) 4. Otherwise unset — default Go behavior preserved (backwards compatible) Each startup logs a `[memlimit]` line echoing the effective source/limit, or an "unset → default" note when neither is set. ## Changes - `cmd/ingestor/memlimit.go` — new, `applyMemoryLimit(runtimeMaxMB, envSet)`. - `cmd/ingestor/memlimit_test.go` — new, env/config/none/precedence assertions. - `cmd/ingestor/config.go` — new `RuntimeConfig{MaxMemoryMB int}` field. - `cmd/ingestor/main.go` — wires `applyMemoryLimit` into startup right after `LoadConfig`. - `cmd/server/config.go` — new `RuntimeConfig` + `cfg.Runtime` field. - `cmd/server/main.go` — adds explicit `runtime.maxMemoryMB` precedence over packetStore-derived; existing `warnIfMemlimitUnderprovisioned` (#1264) unchanged. - `config.example.json` — new `runtime` block with `_comment_runtime_maxMemoryMB` per the Config Documentation Rule. - `README.md` — sizing-table row with ≥1.5× working set floor + death-spiral warning. ## TDD - Red: `929da3c6` — ingestor `applyMemoryLimit` stub returns `(0,"none")`; four tests fail on assertions (`expected source=env, got "none"`, etc.) — no compile errors. - Green: `953ec9d8` — implements ingestor `applyMemoryLimit`, wires startup, threads `runtime.maxMemoryMB` through server too. ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → clean (all gates pass, all warnings pass). ## Out of scope - `pprof`-verified GC-trigger acceptance criterion from the original issue — requires production tracing; the triage scope is the operator-tunable plumbing. - Container auto-detection of cgroup memory limit (already covered by #1264's `warnIfMemlimitUnderprovisioned`). --------- Co-authored-by: corescope-bot <bot@corescope>
72 lines
2.2 KiB
Go
72 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"runtime/debug"
|
|
"testing"
|
|
)
|
|
|
|
// TestApplyMemoryLimit_FromEnv: when GOMEMLIMIT env var is set, the runtime
|
|
// already parsed it. Our function MUST NOT override and MUST report env source.
|
|
func TestApplyMemoryLimit_FromEnv(t *testing.T) {
|
|
t.Setenv("GOMEMLIMIT", "850MiB")
|
|
defer debug.SetMemoryLimit(-1)
|
|
|
|
limit, source := applyMemoryLimit(512, true /* envSet */)
|
|
if source != "env" {
|
|
t.Fatalf("expected source=env, got %q", source)
|
|
}
|
|
if limit != 0 {
|
|
t.Fatalf("expected limit=0 (not set by us), got %d", limit)
|
|
}
|
|
}
|
|
|
|
// TestApplyMemoryLimit_FromConfig: when env is unset and runtime.maxMemoryMB
|
|
// is set, derive a limit of exactly runtimeMaxMB * 1 MiB (no headroom — the
|
|
// ingestor's working set is bounded by MQTT batch decode, not packet store).
|
|
func TestApplyMemoryLimit_FromConfig(t *testing.T) {
|
|
defer debug.SetMemoryLimit(-1)
|
|
|
|
limit, source := applyMemoryLimit(512, false /* envSet */)
|
|
if source != "config" {
|
|
t.Fatalf("expected source=config, got %q", source)
|
|
}
|
|
want := int64(512) * 1024 * 1024
|
|
if limit != want {
|
|
t.Fatalf("expected limit=%d, got %d", want, limit)
|
|
}
|
|
cur := debug.SetMemoryLimit(-1)
|
|
if cur != want {
|
|
t.Fatalf("runtime memory limit not set: want=%d got=%d", want, cur)
|
|
}
|
|
}
|
|
|
|
// TestApplyMemoryLimit_None: neither env nor config — no limit applied,
|
|
// default behavior preserved.
|
|
func TestApplyMemoryLimit_None(t *testing.T) {
|
|
defer debug.SetMemoryLimit(-1)
|
|
debug.SetMemoryLimit(int64(1<<63 - 1)) // math.MaxInt64 = "no limit"
|
|
|
|
limit, source := applyMemoryLimit(0, false)
|
|
if source != "none" {
|
|
t.Fatalf("expected source=none, got %q", source)
|
|
}
|
|
if limit != 0 {
|
|
t.Fatalf("expected limit=0, got %d", limit)
|
|
}
|
|
}
|
|
|
|
// TestApplyMemoryLimit_EnvWinsOverConfig: env set AND config set → env wins,
|
|
// our function does not override. Locks the precedence triage specified.
|
|
func TestApplyMemoryLimit_EnvWinsOverConfig(t *testing.T) {
|
|
t.Setenv("GOMEMLIMIT", "1GiB")
|
|
defer debug.SetMemoryLimit(-1)
|
|
|
|
limit, source := applyMemoryLimit(512, true /* envSet */)
|
|
if source != "env" {
|
|
t.Fatalf("expected source=env when both set, got %q", source)
|
|
}
|
|
if limit != 0 {
|
|
t.Fatalf("expected limit=0 when env wins, got %d", limit)
|
|
}
|
|
}
|