From 859ef3b16d74c11020389059eff8dc5fa446ea94 Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Fri, 22 May 2026 21:45:25 +0000 Subject: [PATCH 01/13] add VM-internal failure telemetry (OOM + service crashes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new telemetry event types surface VM-level failures alongside the existing browser events: - `system_oom_kill` — emitted by an in-process /dev/kmsg reader in the api server whenever the kernel OOM-killer terminates a process, including unsupervised Chrome renderer subprocesses. - `service_crashed` — emitted by a tiny supervisord eventlistener binary that POSTs to the local /telemetry/events endpoint whenever a supervised service unexpectedly exits (PROCESS_STATE_EXITED with expected=0, or PROCESS_STATE_FATAL). Both events flow through the existing EventStream and inherit the SSE and S2 sinks for free. Categorized as `system` so they're always-on. The shim is shipped in both the chromium-headful and chromium-headless images and registered as `[eventlistener:supervisord-shim]`. Co-Authored-By: Claude Opus 4.7 --- images/chromium-headful/Dockerfile | 7 + .../supervisor/services/supervisord-shim.conf | 7 + images/chromium-headless/image/Dockerfile | 7 + .../supervisor/services/supervisord-shim.conf | 7 + server/cmd/api/main.go | 6 + server/cmd/supervisord-shim/main.go | 228 +++++ server/cmd/supervisord-shim/main_test.go | 128 +++ server/lib/oapi/oapi.go | 840 +++++++++++------- server/lib/sysmon/kmsg.go | 129 +++ server/lib/sysmon/kmsg_test.go | 100 +++ server/lib/sysmon/sysmon.go | 55 ++ server/lib/sysmon/sysmon_test.go | 93 ++ server/openapi.yaml | 97 ++ 13 files changed, 1382 insertions(+), 322 deletions(-) create mode 100644 images/chromium-headful/supervisor/services/supervisord-shim.conf create mode 100644 images/chromium-headless/image/supervisor/services/supervisord-shim.conf create mode 100644 server/cmd/supervisord-shim/main.go create mode 100644 server/cmd/supervisord-shim/main_test.go create mode 100644 server/lib/sysmon/kmsg.go create mode 100644 server/lib/sysmon/kmsg_test.go create mode 100644 server/lib/sysmon/sysmon.go create mode 100644 server/lib/sysmon/sysmon_test.go diff --git a/images/chromium-headful/Dockerfile b/images/chromium-headful/Dockerfile index 6f1f7409..a40d012b 100644 --- a/images/chromium-headful/Dockerfile +++ b/images/chromium-headful/Dockerfile @@ -33,6 +33,12 @@ RUN --mount=type=cache,target=/root/.cache/go-build,id=$CACHEIDPREFIX-go-build \ GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \ go build -ldflags="-s -w" -o /out/wrapper ./cmd/wrapper +# Build supervisord eventlistener shim +RUN --mount=type=cache,target=/root/.cache/go-build,id=$CACHEIDPREFIX-go-build \ + --mount=type=cache,target=/go/pkg/mod,id=$CACHEIDPREFIX-go-pkg-mod \ + GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \ + go build -ldflags="-s -w" -o /out/kernel-images-supervisord-shim ./cmd/supervisord-shim + # webrtc client FROM node:22-bullseye-slim AS client WORKDIR /src @@ -378,6 +384,7 @@ RUN chmod +x /usr/local/bin/init-envoy.sh # copy the kernel-images API binary built in the builder stage COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api COPY --from=server-builder /out/chromium-launcher /usr/local/bin/chromium-launcher +COPY --from=server-builder /out/kernel-images-supervisord-shim /usr/local/bin/kernel-images-supervisord-shim COPY --from=server-builder /out/wrapper /wrapper # Copy and compile the Playwright daemon diff --git a/images/chromium-headful/supervisor/services/supervisord-shim.conf b/images/chromium-headful/supervisor/services/supervisord-shim.conf new file mode 100644 index 00000000..484cd89e --- /dev/null +++ b/images/chromium-headful/supervisor/services/supervisord-shim.conf @@ -0,0 +1,7 @@ +[eventlistener:supervisord-shim] +command=/usr/local/bin/kernel-images-supervisord-shim +events=PROCESS_STATE_EXITED,PROCESS_STATE_FATAL +autostart=true +autorestart=true +stderr_logfile=/var/log/supervisord/supervisord-shim +; stdout is the eventlistener protocol channel; do not redirect. diff --git a/images/chromium-headless/image/Dockerfile b/images/chromium-headless/image/Dockerfile index 5348c3f2..2a26e0f0 100644 --- a/images/chromium-headless/image/Dockerfile +++ b/images/chromium-headless/image/Dockerfile @@ -34,6 +34,12 @@ RUN --mount=type=cache,target=/root/.cache/go-build,id=$CACHEIDPREFIX-go-build \ GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \ go build -ldflags="-s -w" -o /out/wrapper ./cmd/wrapper +# Build supervisord eventlistener shim +RUN --mount=type=cache,target=/root/.cache/go-build,id=$CACHEIDPREFIX-go-build \ + --mount=type=cache,target=/go/pkg/mod,id=$CACHEIDPREFIX-go-pkg-mod \ + GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \ + go build -ldflags="-s -w" -o /out/kernel-images-supervisord-shim ./cmd/supervisord-shim + FROM docker.io/ubuntu:22.04 AS ffmpeg-downloader # Allow cross-compilation when building with BuildKit platforms @@ -256,6 +262,7 @@ RUN chmod +x /usr/local/bin/bake-certs.sh && /usr/local/bin/bake-certs.sh && rm # Copy the kernel-images API binary built in the builder stage COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api COPY --from=server-builder /out/chromium-launcher /usr/local/bin/chromium-launcher +COPY --from=server-builder /out/kernel-images-supervisord-shim /usr/local/bin/kernel-images-supervisord-shim # Copy and compile the Playwright daemon COPY server/runtime/playwright-daemon.ts /tmp/playwright-daemon.ts diff --git a/images/chromium-headless/image/supervisor/services/supervisord-shim.conf b/images/chromium-headless/image/supervisor/services/supervisord-shim.conf new file mode 100644 index 00000000..484cd89e --- /dev/null +++ b/images/chromium-headless/image/supervisor/services/supervisord-shim.conf @@ -0,0 +1,7 @@ +[eventlistener:supervisord-shim] +command=/usr/local/bin/kernel-images-supervisord-shim +events=PROCESS_STATE_EXITED,PROCESS_STATE_FATAL +autostart=true +autorestart=true +stderr_logfile=/var/log/supervisord/supervisord-shim +; stdout is the eventlistener protocol channel; do not redirect. diff --git a/server/cmd/api/main.go b/server/cmd/api/main.go index c226e5a7..0540b606 100644 --- a/server/cmd/api/main.go +++ b/server/cmd/api/main.go @@ -30,6 +30,7 @@ import ( oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/kernel/kernel-images/server/lib/recorder" "github.com/kernel/kernel-images/server/lib/scaletozero" + "github.com/kernel/kernel-images/server/lib/sysmon" "github.com/kernel/kernel-images/server/lib/telemetry" ) @@ -103,6 +104,11 @@ func main() { } telemetrySession := telemetry.NewTelemetrySession(eventStream) + // VM-internal failure telemetry (OOM kills via /dev/kmsg). + // service_crashed events arrive via POST /telemetry/events from the + // supervisord-shim child process, not through this monitor. + sysmon.New(eventStream, slogger).Start(ctx) + // Optional S2 storage sink. var s2Writer *events.S2StorageWriter if config.S2Basin != "" && config.S2AccessToken != "" && config.S2Stream != "" { diff --git a/server/cmd/supervisord-shim/main.go b/server/cmd/supervisord-shim/main.go new file mode 100644 index 00000000..1cd0fb25 --- /dev/null +++ b/server/cmd/supervisord-shim/main.go @@ -0,0 +1,228 @@ +// Command supervisord-shim is a tiny supervisord eventlistener that +// translates PROCESS_STATE_EXITED (expected=0) and PROCESS_STATE_FATAL events +// into BrowserServiceCrashedEvent payloads and POSTs them to the local +// kernel-images-api telemetry endpoint. +// +// All schema-mapping and event publishing logic lives here; lib/sysmon does +// not handle supervisord events. Keeping the shim as the sole owner of the +// supervisord protocol means lib/sysmon stays single-purpose (kmsg only). +// +// Wire protocol per supervisord docs: +// +// stdout: "READY\n" +// stdin: header line ("ver:3.0 ... eventname:PROCESS_STATE_EXITED len:54\n") +// stdin: payload of `len` bytes (no trailing newline) +// stdout: "RESULT 2\nOK\n" (always; ACK regardless of downstream success) +// +// We always ACK with OK so supervisord doesn't quarantine us when the +// downstream HTTP target is briefly unavailable. The events are +// best-effort; if the API is down, we drop and log. +// +// All logging goes to stderr — stdout is the supervisord protocol channel. +package main + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "strconv" + "strings" + "time" +) + +const ( + defaultTelemetryURL = "http://127.0.0.1:10001/telemetry/events" + httpTimeout = 2 * time.Second +) + +func main() { + log.SetOutput(os.Stderr) + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + + telemetryURL := os.Getenv("KERNEL_IMAGES_TELEMETRY_URL") + if telemetryURL == "" { + telemetryURL = defaultTelemetryURL + } + + pub := &publisher{ + url: telemetryURL, + client: &http.Client{Timeout: httpTimeout}, + } + + in := bufio.NewReader(os.Stdin) + out := bufio.NewWriter(os.Stdout) + + for { + if _, err := out.WriteString("READY\n"); err != nil { + log.Fatalf("write READY: %v", err) + } + if err := out.Flush(); err != nil { + log.Fatalf("flush READY: %v", err) + } + + header, payload, err := readEvent(in) + if err != nil { + if err == io.EOF { + return + } + log.Fatalf("read event: %v", err) + } + + // Try to publish but always ACK supervisord. + if ev, ok := mapEvent(header, payload); ok { + if perr := pub.publish(context.Background(), ev); perr != nil { + log.Printf("publish telemetry event: %v", perr) + } + } + + if _, err := out.WriteString("RESULT 2\nOK\n"); err != nil { + log.Fatalf("write RESULT: %v", err) + } + if err := out.Flush(); err != nil { + log.Fatalf("flush RESULT: %v", err) + } + } +} + +// readEvent reads one supervisord event: a header line followed by a payload +// of declared length. +func readEvent(in *bufio.Reader) (map[string]string, map[string]string, error) { + headerLine, err := in.ReadString('\n') + if err != nil { + return nil, nil, err + } + header := parseFields(strings.TrimRight(headerLine, "\n")) + + lenStr, ok := header["len"] + if !ok { + return nil, nil, fmt.Errorf("missing len in header: %q", headerLine) + } + n, err := strconv.Atoi(lenStr) + if err != nil { + return nil, nil, fmt.Errorf("invalid len %q: %w", lenStr, err) + } + + buf := make([]byte, n) + if _, err := io.ReadFull(in, buf); err != nil { + return nil, nil, fmt.Errorf("read payload: %w", err) + } + payload := parseFields(string(buf)) + return header, payload, nil +} + +// parseFields parses supervisord's "key:value key:value" tokenization. +// Values are split on the first colon; supervisord does not escape colons in +// values, but in practice the values we care about (process names, states, +// ints) never contain them. +func parseFields(s string) map[string]string { + out := make(map[string]string) + for _, tok := range strings.Fields(s) { + i := strings.IndexByte(tok, ':') + if i < 0 { + continue + } + out[tok[:i]] = tok[i+1:] + } + return out +} + +// telemetryEventBody mirrors oapi.TelemetryEvent but is duplicated here so the +// shim does not pull in the entire server module — keeps the binary tiny. +type telemetryEventBody struct { + Type string `json:"type"` + Category string `json:"category"` + Source telemetryEventSource `json:"source"` + Data serviceCrashedPayload `json:"data"` +} + +type telemetryEventSource struct { + Kind string `json:"kind"` + Event string `json:"event"` +} + +type serviceCrashedPayload struct { + ServiceName string `json:"service_name"` + FromState string `json:"from_state"` + Pid *int `json:"pid,omitempty"` +} + +// mapEvent decides whether to publish and constructs the event payload. +// Returns ok=false for events we deliberately skip (intentional stops, +// non-crash event types). +func mapEvent(header, payload map[string]string) (telemetryEventBody, bool) { + eventName := header["eventname"] + switch eventName { + case "PROCESS_STATE_EXITED": + // expected=0 means the exit was not in `exitcodes` — i.e. a crash. + // expected=1 means clean shutdown (supervisorctl stop, or a configured + // exitcode). Skip the latter. + if payload["expected"] != "0" { + return telemetryEventBody{}, false + } + case "PROCESS_STATE_FATAL": + // FATAL: supervisord exhausted startretries. Always a crash. + default: + return telemetryEventBody{}, false + } + + name := payload["processname"] + if name == "" { + return telemetryEventBody{}, false + } + fromState := payload["from_state"] + if fromState == "" { + return telemetryEventBody{}, false + } + + body := telemetryEventBody{ + Type: "service_crashed", + Category: "system", + Source: telemetryEventSource{ + Kind: "local_process", + Event: "supervisord.process_" + strings.ToLower(strings.TrimPrefix(eventName, "PROCESS_STATE_")), + }, + Data: serviceCrashedPayload{ + ServiceName: name, + FromState: fromState, + }, + } + if pidStr := payload["pid"]; pidStr != "" { + if pid, err := strconv.Atoi(pidStr); err == nil { + body.Data.Pid = &pid + } + } + return body, true +} + +type publisher struct { + url string + client *http.Client +} + +func (p *publisher) publish(ctx context.Context, body telemetryEventBody) error { + buf, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("marshal: %w", err) + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(buf)) + if err != nil { + return fmt.Errorf("new request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + resp, err := p.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode >= 300 { + b, _ := io.ReadAll(resp.Body) + return fmt.Errorf("status %d: %s", resp.StatusCode, bytes.TrimSpace(b)) + } + return nil +} diff --git a/server/cmd/supervisord-shim/main_test.go b/server/cmd/supervisord-shim/main_test.go new file mode 100644 index 00000000..7a268174 --- /dev/null +++ b/server/cmd/supervisord-shim/main_test.go @@ -0,0 +1,128 @@ +package main + +import ( + "bufio" + "reflect" + "strings" + "testing" +) + +func TestParseFields(t *testing.T) { + in := "processname:mutter groupname:mutter from_state:RUNNING expected:0 pid:1234" + got := parseFields(in) + want := map[string]string{ + "processname": "mutter", + "groupname": "mutter", + "from_state": "RUNNING", + "expected": "0", + "pid": "1234", + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("parseFields = %v, want %v", got, want) + } +} + +func TestReadEvent(t *testing.T) { + payload := "processname:cat groupname:cat from_state:RUNNING expected:0 pid:2766" + header := "ver:3.0 server:supervisor serial:21 pool:listener poolserial:10 eventname:PROCESS_STATE_EXITED len:" + + itoa(len(payload)) + "\n" + in := bufio.NewReader(strings.NewReader(header + payload)) + + hdr, pl, err := readEvent(in) + if err != nil { + t.Fatalf("readEvent: %v", err) + } + if hdr["eventname"] != "PROCESS_STATE_EXITED" { + t.Errorf("eventname = %q", hdr["eventname"]) + } + if pl["pid"] != "2766" || pl["processname"] != "cat" || pl["expected"] != "0" { + t.Errorf("payload = %v", pl) + } +} + +func TestMapEventExitedUnexpected(t *testing.T) { + hdr := map[string]string{"eventname": "PROCESS_STATE_EXITED"} + pl := map[string]string{ + "processname": "mutter", + "from_state": "RUNNING", + "expected": "0", + "pid": "1234", + } + body, ok := mapEvent(hdr, pl) + if !ok { + t.Fatal("expected publish") + } + if body.Type != "service_crashed" { + t.Errorf("Type = %q", body.Type) + } + if body.Category != "system" { + t.Errorf("Category = %q", body.Category) + } + if body.Source.Kind != "local_process" { + t.Errorf("Source.Kind = %q", body.Source.Kind) + } + if body.Source.Event != "supervisord.process_exited" { + t.Errorf("Source.Event = %q", body.Source.Event) + } + if body.Data.ServiceName != "mutter" || body.Data.FromState != "RUNNING" { + t.Errorf("Data = %+v", body.Data) + } + if body.Data.Pid == nil || *body.Data.Pid != 1234 { + t.Errorf("Pid = %v", body.Data.Pid) + } +} + +func TestMapEventExitedExpectedSkipped(t *testing.T) { + hdr := map[string]string{"eventname": "PROCESS_STATE_EXITED"} + pl := map[string]string{ + "processname": "mutter", + "from_state": "RUNNING", + "expected": "1", + "pid": "1234", + } + if _, ok := mapEvent(hdr, pl); ok { + t.Fatal("expected skip for expected=1") + } +} + +func TestMapEventFatal(t *testing.T) { + hdr := map[string]string{"eventname": "PROCESS_STATE_FATAL"} + pl := map[string]string{ + "processname": "chromium", + "from_state": "BACKOFF", + } + body, ok := mapEvent(hdr, pl) + if !ok { + t.Fatal("expected publish") + } + if body.Source.Event != "supervisord.process_fatal" { + t.Errorf("Source.Event = %q", body.Source.Event) + } + if body.Data.ServiceName != "chromium" || body.Data.FromState != "BACKOFF" { + t.Errorf("Data = %+v", body.Data) + } + if body.Data.Pid != nil { + t.Errorf("Pid should be nil for FATAL, got %v", *body.Data.Pid) + } +} + +func TestMapEventUnrelatedSkipped(t *testing.T) { + hdr := map[string]string{"eventname": "PROCESS_STATE_STARTING"} + if _, ok := mapEvent(hdr, map[string]string{"processname": "x", "from_state": "STOPPED"}); ok { + t.Fatal("expected skip for non-crash event") + } +} + +func itoa(n int) string { + if n == 0 { + return "0" + } + var b [20]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + return string(b[i:]) +} diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 9b350bf0..e8cfba75 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -578,6 +578,57 @@ func (e BrowserPageTabOpenedEventType) Valid() bool { } } +// Defines values for BrowserServiceCrashedEventType. +const ( + ServiceCrashed BrowserServiceCrashedEventType = "service_crashed" +) + +// Valid indicates whether the value is a known member of the BrowserServiceCrashedEventType enum. +func (e BrowserServiceCrashedEventType) Valid() bool { + switch e { + case ServiceCrashed: + return true + default: + return false + } +} + +// Defines values for BrowserServiceCrashedEventDataFromState. +const ( + BACKOFF BrowserServiceCrashedEventDataFromState = "BACKOFF" + RUNNING BrowserServiceCrashedEventDataFromState = "RUNNING" + STARTING BrowserServiceCrashedEventDataFromState = "STARTING" +) + +// Valid indicates whether the value is a known member of the BrowserServiceCrashedEventDataFromState enum. +func (e BrowserServiceCrashedEventDataFromState) Valid() bool { + switch e { + case BACKOFF: + return true + case RUNNING: + return true + case STARTING: + return true + default: + return false + } +} + +// Defines values for BrowserSystemOomKillEventType. +const ( + SystemOomKill BrowserSystemOomKillEventType = "system_oom_kill" +) + +// Valid indicates whether the value is a known member of the BrowserSystemOomKillEventType enum. +func (e BrowserSystemOomKillEventType) Valid() bool { + switch e { + case SystemOomKill: + return true + default: + return false + } +} + // Defines values for BrowserTargetType. const ( BrowserTargetTypeBackgroundPage BrowserTargetType = "background_page" @@ -2198,6 +2249,77 @@ type BrowserPageTabOpenedEventData struct { Url string `json:"url"` } +// BrowserServiceCrashedEvent A supervisord-managed service exited unexpectedly. Only fires when supervisord reports the exit as unexpected (`expected=0`); intentional stops via `supervisorctl stop` do not produce this event. +type BrowserServiceCrashedEvent struct { + // Data Per-crash payload for `service_crashed` events. Fields are derived from the supervisord eventlistener wire protocol, which exposes the process name, the state the process exited from, and (for EXITED but not FATAL transitions) the PID. Exit code and signal are not surfaced by supervisord on this channel. + Data *BrowserServiceCrashedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserServiceCrashedEventType `json:"type"` +} + +// BrowserServiceCrashedEventType defines model for BrowserServiceCrashedEvent.Type. +type BrowserServiceCrashedEventType string + +// BrowserServiceCrashedEventData Per-crash payload for `service_crashed` events. Fields are derived from the supervisord eventlistener wire protocol, which exposes the process name, the state the process exited from, and (for EXITED but not FATAL transitions) the PID. Exit code and signal are not surfaced by supervisord on this channel. +type BrowserServiceCrashedEventData struct { + // FromState Supervisord state the process was in before the unexpected exit. `RUNNING` means a healthy process died; `STARTING` means it died during startup; `BACKOFF` is paired with FATAL and means supervisord gave up restarting it. + FromState BrowserServiceCrashedEventDataFromState `json:"from_state"` + + // Pid PID of the crashed process. Absent for PROCESS_STATE_FATAL transitions, which fire after all start attempts are exhausted. + Pid *int `json:"pid,omitempty"` + + // ServiceName Supervisord program name (e.g. `chromium`, `mutter`, `kernel-images-api`). + ServiceName string `json:"service_name"` +} + +// BrowserServiceCrashedEventDataFromState Supervisord state the process was in before the unexpected exit. `RUNNING` means a healthy process died; `STARTING` means it died during startup; `BACKOFF` is paired with FATAL and means supervisord gave up restarting it. +type BrowserServiceCrashedEventDataFromState string + +// BrowserSystemOomKillEvent The Linux kernel OOM-killer terminated a process inside the VM. Sourced from `/dev/kmsg`. Fires for any process killed by the kernel due to memory exhaustion, including Chrome renderer subprocesses that are not supervised. +type BrowserSystemOomKillEvent struct { + // Data Per-kill payload for `system_oom_kill` events. + Data *BrowserSystemOomKillEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserSystemOomKillEventType `json:"type"` +} + +// BrowserSystemOomKillEventType defines model for BrowserSystemOomKillEvent.Type. +type BrowserSystemOomKillEventType string + +// BrowserSystemOomKillEventData Per-kill payload for `system_oom_kill` events. +type BrowserSystemOomKillEventData struct { + // OomScoreAdj oom_score_adj of the killed process at the time of kill. + OomScoreAdj *int `json:"oom_score_adj,omitempty"` + + // Pid PID of the killed process. + Pid int `json:"pid"` + + // ProcessName Comm of the killed process as reported by the kernel (max 15 chars, truncated by the kernel). + ProcessName string `json:"process_name"` + + // RssKb Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and shmem-rss). + RssKb int `json:"rss_kb"` + + // TotalVmKb Total virtual memory of the killed process in KiB. + TotalVmKb *int `json:"total_vm_kb,omitempty"` +} + // BrowserTargetType CDP target type of the page that produced the event. type BrowserTargetType string @@ -3940,6 +4062,62 @@ func (t *KnownBrowserTelemetryEvent) MergeBrowserCaptchaSolveResultEvent(v Brows return err } +// AsBrowserSystemOomKillEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserSystemOomKillEvent +func (t KnownBrowserTelemetryEvent) AsBrowserSystemOomKillEvent() (BrowserSystemOomKillEvent, error) { + var body BrowserSystemOomKillEvent + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBrowserSystemOomKillEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserSystemOomKillEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserSystemOomKillEvent(v BrowserSystemOomKillEvent) error { + v.Type = "system_oom_kill" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBrowserSystemOomKillEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserSystemOomKillEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserSystemOomKillEvent(v BrowserSystemOomKillEvent) error { + v.Type = "system_oom_kill" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsBrowserServiceCrashedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserServiceCrashedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserServiceCrashedEvent() (BrowserServiceCrashedEvent, error) { + var body BrowserServiceCrashedEvent + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBrowserServiceCrashedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserServiceCrashedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserServiceCrashedEvent(v BrowserServiceCrashedEvent) error { + v.Type = "service_crashed" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBrowserServiceCrashedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserServiceCrashedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserServiceCrashedEvent(v BrowserServiceCrashedEvent) error { + v.Type = "service_crashed" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + func (t KnownBrowserTelemetryEvent) Discriminator() (string, error) { var discriminator struct { Discriminator string `json:"type"` @@ -4010,6 +4188,10 @@ func (t KnownBrowserTelemetryEvent) ValueByDiscriminator() (interface{}, error) return t.AsBrowserPageNavigationSettledEvent() case "page_tab_opened": return t.AsBrowserPageTabOpenedEvent() + case "service_crashed": + return t.AsBrowserServiceCrashedEvent() + case "system_oom_kill": + return t.AsBrowserSystemOomKillEvent() default: return nil, errors.New("unknown discriminator value: " + discriminator) } @@ -17915,328 +18097,342 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "IvFk6Ub/vwEAAP//O92XVQ+aAQA=", + "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", + "DbtEh+Dg0hGJJ0s3+v8PAAD//1Hf9WWfpwEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/lib/sysmon/kmsg.go b/server/lib/sysmon/kmsg.go new file mode 100644 index 00000000..57786048 --- /dev/null +++ b/server/lib/sysmon/kmsg.go @@ -0,0 +1,129 @@ +package sysmon + +import ( + "context" + "encoding/json" + "errors" + "io" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" +) + +// oomKillRe matches the canonical kernel OOM-killer line. Example: +// +// Out of memory: Killed process 1234 (chromium) total-vm:5234572kB, anon-rss:4823900kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:8000kB oom_score_adj:0 +// +// The comm is bounded to 15 chars by the kernel (TASK_COMM_LEN-1) but may +// contain parens internally for a few cases (e.g. `(sd-pam)`); we deliberately +// match a lazy non-paren-aware group since the more-permissive form has bitten +// other parsers — a comm with `)` would be exceptional and the line still +// parses for everything except the comm field. +var oomKillRe = regexp.MustCompile( + `Out of memory: Killed process (\d+) \(([^)]+)\) ` + + `total-vm:(\d+)kB, anon-rss:(\d+)kB, file-rss:(\d+)kB, shmem-rss:(\d+)kB` + + `.*?oom_score_adj:(-?\d+)`, +) + +// parseKmsgLine strips the kmsg envelope `;` +// and returns the message portion. Returns the original line if no envelope +// is found (which can happen on truncated reads, though that's rare). +func parseKmsgLine(line string) string { + if i := strings.IndexByte(line, ';'); i >= 0 { + return line[i+1:] + } + return line +} + +// parseOomKill extracts OOM-kill data from a kmsg message body. Returns nil if +// the line is not an OOM-kill record. +func parseOomKill(msg string) *oapi.BrowserSystemOomKillEventData { + m := oomKillRe.FindStringSubmatch(msg) + if m == nil { + return nil + } + pid, _ := strconv.Atoi(m[1]) + totalVM, _ := strconv.Atoi(m[3]) + anonRSS, _ := strconv.Atoi(m[4]) + fileRSS, _ := strconv.Atoi(m[5]) + shmemRSS, _ := strconv.Atoi(m[6]) + scoreAdj, _ := strconv.Atoi(m[7]) + rss := anonRSS + fileRSS + shmemRSS + return &oapi.BrowserSystemOomKillEventData{ + ProcessName: m[2], + Pid: pid, + TotalVmKb: &totalVM, + RssKb: rss, + OomScoreAdj: &scoreAdj, + } +} + +func (m *Monitor) runKmsg(ctx context.Context) { + f, err := os.OpenFile(m.kmsgPath, os.O_RDONLY, 0) + if err != nil { + m.logger.Warn("sysmon: failed to open kmsg, OOM events disabled", "err", err, "path", m.kmsgPath) + return + } + + // Closing f unblocks the read loop on shutdown. + go func() { + <-ctx.Done() + f.Close() + }() + + m.logger.Info("sysmon: kmsg reader started", "path", m.kmsgPath) + + // Each /dev/kmsg read() returns at most one record. The kernel guarantees + // EINVAL if the buffer is too small, so use a generous fixed buffer. + buf := make([]byte, 8192) + for { + n, err := f.Read(buf) + if err != nil { + if ctx.Err() != nil || errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) { + return + } + // EPIPE can occur if a process between us and the ring buffer + // dies; we keep going. + m.logger.Warn("sysmon: kmsg read error", "err", err) + continue + } + line := string(buf[:n]) + msg := parseKmsgLine(line) + data := parseOomKill(msg) + if data == nil { + continue + } + m.publishOomKill(*data) + } +} + +func (m *Monitor) publishOomKill(data oapi.BrowserSystemOomKillEventData) { + payload, err := json.Marshal(data) + if err != nil { + m.logger.Warn("sysmon: marshal oom kill payload", "err", err) + return + } + ev := events.Event{ + Ts: time.Now().UnixMicro(), + Type: string(oapi.SystemOomKill), + Category: events.System, + Source: oapi.BrowserEventSource{ + Kind: oapi.LocalProcess, + Event: stringPtr("linux.oom_kill"), + }, + Data: json.RawMessage(payload), + } + m.es.Publish(events.Envelope{Event: ev}) + m.logger.Info("sysmon: oom kill", + "process", data.ProcessName, + "pid", data.Pid, + "rss_kb", data.RssKb, + ) +} + +func stringPtr(s string) *string { return &s } diff --git a/server/lib/sysmon/kmsg_test.go b/server/lib/sysmon/kmsg_test.go new file mode 100644 index 00000000..82b34692 --- /dev/null +++ b/server/lib/sysmon/kmsg_test.go @@ -0,0 +1,100 @@ +package sysmon + +import ( + "testing" +) + +func TestParseKmsgLine(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + { + "standard envelope", + "<4,123,456789,->;Out of memory: Killed process 1 (init) total-vm:1kB", + "Out of memory: Killed process 1 (init) total-vm:1kB", + }, + { + "no envelope", + "naked message", + "naked message", + }, + { + "empty after semicolon", + "<3,1,2,->;", + "", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := parseKmsgLine(tc.in); got != tc.want { + t.Fatalf("parseKmsgLine = %q, want %q", got, tc.want) + } + }) + } +} + +func TestParseOomKill(t *testing.T) { + t.Run("canonical line", func(t *testing.T) { + msg := "Out of memory: Killed process 1234 (chromium) total-vm:5234572kB, anon-rss:4823900kB, file-rss:100kB, shmem-rss:200kB, UID:0 pgtables:8000kB oom_score_adj:0" + data := parseOomKill(msg) + if data == nil { + t.Fatal("expected match") + } + if data.Pid != 1234 { + t.Errorf("Pid = %d, want 1234", data.Pid) + } + if data.ProcessName != "chromium" { + t.Errorf("ProcessName = %q, want chromium", data.ProcessName) + } + if data.TotalVmKb == nil || *data.TotalVmKb != 5234572 { + t.Errorf("TotalVmKb = %v, want 5234572", data.TotalVmKb) + } + // rss_kb = anon + file + shmem = 4823900 + 100 + 200 + if data.RssKb != 4824200 { + t.Errorf("RssKb = %d, want 4824200", data.RssKb) + } + if data.OomScoreAdj == nil || *data.OomScoreAdj != 0 { + t.Errorf("OomScoreAdj = %v, want 0", data.OomScoreAdj) + } + }) + + t.Run("negative oom_score_adj", func(t *testing.T) { + msg := "Out of memory: Killed process 999 (sshd) total-vm:10kB, anon-rss:1kB, file-rss:2kB, shmem-rss:0kB, UID:0 pgtables:1kB oom_score_adj:-1000" + data := parseOomKill(msg) + if data == nil { + t.Fatal("expected match") + } + if data.OomScoreAdj == nil || *data.OomScoreAdj != -1000 { + t.Errorf("OomScoreAdj = %v, want -1000", data.OomScoreAdj) + } + }) + + t.Run("comm with internal space", func(t *testing.T) { + // Kernel preserves spaces in comm; bounded by TASK_COMM_LEN. + msg := "Out of memory: Killed process 42 (kworker u4:1) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0" + data := parseOomKill(msg) + if data == nil { + t.Fatal("expected match") + } + if data.ProcessName != "kworker u4:1" { + t.Errorf("ProcessName = %q, want %q", data.ProcessName, "kworker u4:1") + } + }) + + t.Run("no match", func(t *testing.T) { + if got := parseOomKill("just some other kernel log line"); got != nil { + t.Fatalf("expected nil, got %+v", got) + } + }) + + t.Run("preamble (oom dump task list)", func(t *testing.T) { + // We must NOT match the per-process audit lines the kernel emits + // before the canonical "Killed process" decision. + msg := "[1234] 0 1234 1308611 1205975 9678848 0 0 chromium" + if got := parseOomKill(msg); got != nil { + t.Fatalf("expected nil for preamble line, got %+v", got) + } + }) +} diff --git a/server/lib/sysmon/sysmon.go b/server/lib/sysmon/sysmon.go new file mode 100644 index 00000000..926ab00c --- /dev/null +++ b/server/lib/sysmon/sysmon.go @@ -0,0 +1,55 @@ +// Package sysmon emits VM-internal failure telemetry — OOM kills surfaced +// through /dev/kmsg, and (via the supervisord-shim binary POSTing to the +// telemetry HTTP endpoint) supervised-service crashes. +// +// The package only owns the in-process kmsg reader; service crashes are +// delivered as ordinary caller-published events via POST /telemetry/events +// from the shim. Both paths terminate in the same EventStream. +package sysmon + +import ( + "context" + "log/slog" + + "github.com/kernel/kernel-images/server/lib/events" +) + +// DefaultKmsgPath is the standard kernel log device. +const DefaultKmsgPath = "/dev/kmsg" + +// Monitor runs the in-process sysmon goroutines and publishes events directly +// to the EventStream. System-category events are always captured regardless +// of any active TelemetrySession config, so we deliberately bypass +// TelemetrySession here. +type Monitor struct { + es *events.EventStream + logger *slog.Logger + kmsgPath string +} + +// Option configures a Monitor. +type Option func(*Monitor) + +// WithKmsgPath overrides the default /dev/kmsg path. Intended for tests. +func WithKmsgPath(path string) Option { + return func(m *Monitor) { m.kmsgPath = path } +} + +// New constructs a Monitor. The Monitor does nothing until Start is called. +func New(es *events.EventStream, logger *slog.Logger, opts ...Option) *Monitor { + m := &Monitor{ + es: es, + logger: logger, + kmsgPath: DefaultKmsgPath, + } + for _, opt := range opts { + opt(m) + } + return m +} + +// Start launches background goroutines. It returns immediately; goroutines +// shut down when ctx is cancelled. +func (m *Monitor) Start(ctx context.Context) { + go m.runKmsg(ctx) +} diff --git a/server/lib/sysmon/sysmon_test.go b/server/lib/sysmon/sysmon_test.go new file mode 100644 index 00000000..432f4a53 --- /dev/null +++ b/server/lib/sysmon/sysmon_test.go @@ -0,0 +1,93 @@ +package sysmon + +import ( + "context" + "encoding/json" + "io" + "log/slog" + "os" + "path/filepath" + "syscall" + "testing" + "time" + + "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" +) + +// TestKmsgRoundTrip pipes a synthetic OOM line through a FIFO and verifies an +// event lands in the EventStream with the right schema. Linux only — /dev/kmsg +// semantics don't apply here, but we open a regular FIFO so the goroutine just +// reads bytes; the parser is what we exercise. +func TestKmsgRoundTrip(t *testing.T) { + dir := t.TempDir() + fifo := filepath.Join(dir, "kmsg") + if err := syscall.Mkfifo(fifo, 0o600); err != nil { + t.Skipf("mkfifo unsupported: %v", err) + } + + es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: 16}) + if err != nil { + t.Fatalf("event stream: %v", err) + } + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + mon := New(es, logger, WithKmsgPath(fifo)) + mon.Start(ctx) + + // Open writer side after the goroutine has had time to open the reader. + // Opening the FIFO for write blocks until a reader is present, which + // gives us synchronization for free. + w, err := os.OpenFile(fifo, os.O_WRONLY, 0) + if err != nil { + t.Fatalf("open writer: %v", err) + } + defer w.Close() + + line := "<4,123,456789,->;Out of memory: Killed process 4242 (renderer) total-vm:200kB, anon-rss:150kB, file-rss:10kB, shmem-rss:5kB, UID:1000 pgtables:1kB oom_score_adj:300\n" + if _, err := w.Write([]byte(line)); err != nil { + t.Fatalf("write: %v", err) + } + + // Poll the stream until the event arrives or we time out. + reader := es.NewReader(0) + readCtx, readCancel := context.WithTimeout(ctx, 2*time.Second) + defer readCancel() + res, err := reader.Read(readCtx) + if err != nil { + t.Fatalf("read envelope: %v", err) + } + ev := res.Envelope.Event + if ev.Type != string(oapi.SystemOomKill) { + t.Fatalf("Type = %q, want %q", ev.Type, oapi.SystemOomKill) + } + if ev.Category != events.System { + t.Errorf("Category = %q, want system", ev.Category) + } + if ev.Source.Kind != oapi.LocalProcess { + t.Errorf("Source.Kind = %q", ev.Source.Kind) + } + if ev.Source.Event == nil || *ev.Source.Event != "linux.oom_kill" { + t.Errorf("Source.Event = %v", ev.Source.Event) + } + + var data oapi.BrowserSystemOomKillEventData + if err := json.Unmarshal(ev.Data, &data); err != nil { + t.Fatalf("unmarshal data: %v", err) + } + if data.Pid != 4242 { + t.Errorf("Pid = %d", data.Pid) + } + if data.ProcessName != "renderer" { + t.Errorf("ProcessName = %q", data.ProcessName) + } + if data.RssKb != 165 { // 150+10+5 + t.Errorf("RssKb = %d, want 165", data.RssKb) + } + if data.OomScoreAdj == nil || *data.OomScoreAdj != 300 { + t.Errorf("OomScoreAdj = %v", data.OomScoreAdj) + } +} diff --git a/server/openapi.yaml b/server/openapi.yaml index 7f56f440..3a2aed32 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -2688,6 +2688,99 @@ components: truncated: type: boolean description: True if the data field was truncated due to size limits. + BrowserSystemOomKillEventData: + type: object + description: Per-kill payload for `system_oom_kill` events. + additionalProperties: false + required: [process_name, pid, rss_kb] + properties: + process_name: + type: string + description: Comm of the killed process as reported by the kernel (max 15 chars, truncated by the kernel). + pid: + type: integer + description: PID of the killed process. + total_vm_kb: + type: integer + description: Total virtual memory of the killed process in KiB. + rss_kb: + type: integer + description: Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and shmem-rss). + oom_score_adj: + type: integer + description: oom_score_adj of the killed process at the time of kill. + BrowserSystemOomKillEvent: + type: object + description: > + The Linux kernel OOM-killer terminated a process inside the VM. Sourced from + `/dev/kmsg`. Fires for any process killed by the kernel due to memory + exhaustion, including Chrome renderer subprocesses that are not supervised. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: system_oom_kill + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserSystemOomKillEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserServiceCrashedEventData: + type: object + description: > + Per-crash payload for `service_crashed` events. Fields are derived + from the supervisord eventlistener wire protocol, which exposes the + process name, the state the process exited from, and (for EXITED but + not FATAL transitions) the PID. Exit code and signal are not surfaced + by supervisord on this channel. + additionalProperties: false + required: [service_name, from_state] + properties: + service_name: + type: string + description: Supervisord program name (e.g. `chromium`, `mutter`, `kernel-images-api`). + pid: + type: integer + description: PID of the crashed process. Absent for PROCESS_STATE_FATAL transitions, which fire after all start attempts are exhausted. + from_state: + type: string + description: > + Supervisord state the process was in before the unexpected exit. + `RUNNING` means a healthy process died; `STARTING` means it died + during startup; `BACKOFF` is paired with FATAL and means + supervisord gave up restarting it. + enum: + - RUNNING + - STARTING + - BACKOFF + BrowserServiceCrashedEvent: + type: object + description: > + A supervisord-managed service exited unexpectedly. Only fires when supervisord + reports the exit as unexpected (`expected=0`); intentional stops via + `supervisorctl stop` do not produce this event. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: service_crashed + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserServiceCrashedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. KnownBrowserTelemetryEvent: description: > Discriminated union of browser telemetry events emitted by the Kernel @@ -2726,6 +2819,8 @@ components: - $ref: "#/components/schemas/BrowserLiveViewConnectEvent" - $ref: "#/components/schemas/BrowserLiveViewDisconnectEvent" - $ref: "#/components/schemas/BrowserCaptchaSolveResultEvent" + - $ref: "#/components/schemas/BrowserSystemOomKillEvent" + - $ref: "#/components/schemas/BrowserServiceCrashedEvent" discriminator: propertyName: type mapping: @@ -2757,6 +2852,8 @@ components: live_view_connect: "#/components/schemas/BrowserLiveViewConnectEvent" live_view_disconnect: "#/components/schemas/BrowserLiveViewDisconnectEvent" captcha_solve_result: "#/components/schemas/BrowserCaptchaSolveResultEvent" + system_oom_kill: "#/components/schemas/BrowserSystemOomKillEvent" + service_crashed: "#/components/schemas/BrowserServiceCrashedEvent" TelemetryState: type: object description: Current telemetry configuration. From 4ecdc0d4b06163fcefd8671b583c3b2ee9b0d0c7 Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Fri, 22 May 2026 22:27:11 +0000 Subject: [PATCH 02/13] shim: bump eventlistener buffer_size to 100 The default supervisord eventlistener buffer is 10. When several supervised services flap in close succession (which is exactly what happens during a real failure cascade) supervisord drops events before the shim has a chance to drain them. --- .../supervisor/services/supervisord-shim.conf | 4 ++++ .../image/supervisor/services/supervisord-shim.conf | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/images/chromium-headful/supervisor/services/supervisord-shim.conf b/images/chromium-headful/supervisor/services/supervisord-shim.conf index 484cd89e..2239b11e 100644 --- a/images/chromium-headful/supervisor/services/supervisord-shim.conf +++ b/images/chromium-headful/supervisor/services/supervisord-shim.conf @@ -1,6 +1,10 @@ [eventlistener:supervisord-shim] command=/usr/local/bin/kernel-images-supervisord-shim events=PROCESS_STATE_EXITED,PROCESS_STATE_FATAL +; buffer_size defaults to 10 which overflows when several supervised +; services flap in quick succession. Bump it so a burst of crashes +; doesn't cause supervisord to drop events before the shim drains them. +buffer_size=100 autostart=true autorestart=true stderr_logfile=/var/log/supervisord/supervisord-shim diff --git a/images/chromium-headless/image/supervisor/services/supervisord-shim.conf b/images/chromium-headless/image/supervisor/services/supervisord-shim.conf index 484cd89e..2239b11e 100644 --- a/images/chromium-headless/image/supervisor/services/supervisord-shim.conf +++ b/images/chromium-headless/image/supervisor/services/supervisord-shim.conf @@ -1,6 +1,10 @@ [eventlistener:supervisord-shim] command=/usr/local/bin/kernel-images-supervisord-shim events=PROCESS_STATE_EXITED,PROCESS_STATE_FATAL +; buffer_size defaults to 10 which overflows when several supervised +; services flap in quick succession. Bump it so a burst of crashes +; doesn't cause supervisord to drop events before the shim drains them. +buffer_size=100 autostart=true autorestart=true stderr_logfile=/var/log/supervisord/supervisord-shim From e0d008b5afe00116d5e62144ad3a30791220cd1b Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Tue, 26 May 2026 19:34:22 -0700 Subject: [PATCH 03/13] oapi spec --- server/openapi.yaml | 100 +++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/server/openapi.yaml b/server/openapi.yaml index 3a2aed32..249f455c 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -2700,15 +2700,63 @@ components: pid: type: integer description: PID of the killed process. - total_vm_kb: - type: integer - description: Total virtual memory of the killed process in KiB. rss_kb: type: integer - description: Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and shmem-rss). - oom_score_adj: + description: Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and shmem-rss). This is the physical memory the process was using at the time of the kill. + constraint: + type: string + description: > + Why the kernel decided to OOM-kill. `none` means global memory + exhaustion; `memcg` means a cgroup memory limit was hit; + `cpuset` / `memory_policy` are NUMA/policy-driven kills. + Absent on kernels older than 5.0 which did not emit the + structured `oom-kill:` line. + enum: + - none + - memcg + - cpuset + - memory_policy + mem_total_kb: + type: integer + description: > + Total system memory in KiB at the time of the kill, derived + from the `N pages RAM` line in the kernel's Mem-Info dump. + Assumes a 4 KiB page size. Absent if the kernel did not + emit a parseable Mem-Info section. + mem_free_kb: + type: integer + description: > + Free system memory in KiB at the time of the kill, derived + from the `free:N` field in the kernel's Mem-Info dump. + Assumes a 4 KiB page size. Does not include reclaimable + caches, so a small value with a large `mem_total_kb` may + still mean the system was not under hard pressure. Absent + if the kernel did not emit a parseable Mem-Info section. + top_tasks: + type: array + maxItems: 5 + description: > + Top processes by resident-set-size at the moment of the + kill, sorted descending. Sourced from the kernel's + `Tasks state` table. Empty if the kernel did not emit the + table. Capped at 5 entries to bound payload size. + items: + $ref: "#/components/schemas/BrowserSystemOomKillTask" + BrowserSystemOomKillTask: + type: object + description: A single process entry from the kernel's `Tasks state` dump. + additionalProperties: false + required: [pid, name, rss_kb] + properties: + pid: + type: integer + description: PID of the process. + name: + type: string + description: Comm of the process (max 15 chars, truncated by the kernel). + rss_kb: type: integer - description: oom_score_adj of the killed process at the time of kill. + description: Resident set size in KiB at the moment of the kill. BrowserSystemOomKillEvent: type: object description: > @@ -2734,37 +2782,39 @@ components: BrowserServiceCrashedEventData: type: object description: > - Per-crash payload for `service_crashed` events. Fields are derived - from the supervisord eventlistener wire protocol, which exposes the - process name, the state the process exited from, and (for EXITED but - not FATAL transitions) the PID. Exit code and signal are not surfaced - by supervisord on this channel. + Per-crash payload for `service_crashed` events. Exit code and + signal are not exposed by the underlying process manager on this + channel, so only the service identity, the lifecycle phase the + crash occurred in, and (when available) the PID are reported. additionalProperties: false - required: [service_name, from_state] + required: [service_name, phase] properties: service_name: type: string - description: Supervisord program name (e.g. `chromium`, `mutter`, `kernel-images-api`). + description: Program name of the crashed service (e.g. `chromium`, `mutter`, `kernel-images-api`). pid: type: integer - description: PID of the crashed process. Absent for PROCESS_STATE_FATAL transitions, which fire after all start attempts are exhausted. - from_state: + description: PID of the crashed process. Absent when the process manager gave up after exhausting restart attempts and is no longer tracking a live PID. + phase: type: string description: > - Supervisord state the process was in before the unexpected exit. - `RUNNING` means a healthy process died; `STARTING` means it died - during startup; `BACKOFF` is paired with FATAL and means - supervisord gave up restarting it. + Lifecycle phase the crash occurred in. + `startup` means the process died before it ever reached a + healthy running state. `running` means a previously healthy + process died unexpectedly. `gave_up` means the process + manager exhausted its restart attempts and stopped trying; + no further `service_crashed` events will fire for this + service until something restarts it. enum: - - RUNNING - - STARTING - - BACKOFF + - startup + - running + - gave_up BrowserServiceCrashedEvent: type: object description: > - A supervisord-managed service exited unexpectedly. Only fires when supervisord - reports the exit as unexpected (`expected=0`); intentional stops via - `supervisorctl stop` do not produce this event. + A managed service exited unexpectedly. Intentional stops (e.g. + operator-initiated shutdown) do not produce this event — only + unexpected exits and terminal restart-give-up transitions do. required: [ts, type, source] properties: ts: From a44813d297c389ac1190c27beaa7dbb04922a6d3 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Tue, 26 May 2026 19:34:28 -0700 Subject: [PATCH 04/13] generated --- server/lib/oapi/oapi.go | 779 +++++++++++++++++++++------------------- 1 file changed, 416 insertions(+), 363 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index e8cfba75..a5c05701 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -593,21 +593,21 @@ func (e BrowserServiceCrashedEventType) Valid() bool { } } -// Defines values for BrowserServiceCrashedEventDataFromState. +// Defines values for BrowserServiceCrashedEventDataPhase. const ( - BACKOFF BrowserServiceCrashedEventDataFromState = "BACKOFF" - RUNNING BrowserServiceCrashedEventDataFromState = "RUNNING" - STARTING BrowserServiceCrashedEventDataFromState = "STARTING" + BrowserServiceCrashedEventDataPhaseGaveUp BrowserServiceCrashedEventDataPhase = "gave_up" + BrowserServiceCrashedEventDataPhaseRunning BrowserServiceCrashedEventDataPhase = "running" + BrowserServiceCrashedEventDataPhaseStartup BrowserServiceCrashedEventDataPhase = "startup" ) -// Valid indicates whether the value is a known member of the BrowserServiceCrashedEventDataFromState enum. -func (e BrowserServiceCrashedEventDataFromState) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserServiceCrashedEventDataPhase enum. +func (e BrowserServiceCrashedEventDataPhase) Valid() bool { switch e { - case BACKOFF: + case BrowserServiceCrashedEventDataPhaseGaveUp: return true - case RUNNING: + case BrowserServiceCrashedEventDataPhaseRunning: return true - case STARTING: + case BrowserServiceCrashedEventDataPhaseStartup: return true default: return false @@ -629,6 +629,30 @@ func (e BrowserSystemOomKillEventType) Valid() bool { } } +// Defines values for BrowserSystemOomKillEventDataConstraint. +const ( + Cpuset BrowserSystemOomKillEventDataConstraint = "cpuset" + Memcg BrowserSystemOomKillEventDataConstraint = "memcg" + MemoryPolicy BrowserSystemOomKillEventDataConstraint = "memory_policy" + None BrowserSystemOomKillEventDataConstraint = "none" +) + +// Valid indicates whether the value is a known member of the BrowserSystemOomKillEventDataConstraint enum. +func (e BrowserSystemOomKillEventDataConstraint) Valid() bool { + switch e { + case Cpuset: + return true + case Memcg: + return true + case MemoryPolicy: + return true + case None: + return true + default: + return false + } +} + // Defines values for BrowserTargetType. const ( BrowserTargetTypeBackgroundPage BrowserTargetType = "background_page" @@ -886,16 +910,16 @@ func (e ProcessKillRequestSignal) Valid() bool { // Defines values for ProcessStatusState. const ( - Exited ProcessStatusState = "exited" - Running ProcessStatusState = "running" + ProcessStatusStateExited ProcessStatusState = "exited" + ProcessStatusStateRunning ProcessStatusState = "running" ) // Valid indicates whether the value is a known member of the ProcessStatusState enum. func (e ProcessStatusState) Valid() bool { switch e { - case Exited: + case ProcessStatusStateExited: return true - case Running: + case ProcessStatusStateRunning: return true default: return false @@ -2249,9 +2273,9 @@ type BrowserPageTabOpenedEventData struct { Url string `json:"url"` } -// BrowserServiceCrashedEvent A supervisord-managed service exited unexpectedly. Only fires when supervisord reports the exit as unexpected (`expected=0`); intentional stops via `supervisorctl stop` do not produce this event. +// BrowserServiceCrashedEvent A managed service exited unexpectedly. Intentional stops (e.g. operator-initiated shutdown) do not produce this event — only unexpected exits and terminal restart-give-up transitions do. type BrowserServiceCrashedEvent struct { - // Data Per-crash payload for `service_crashed` events. Fields are derived from the supervisord eventlistener wire protocol, which exposes the process name, the state the process exited from, and (for EXITED but not FATAL transitions) the PID. Exit code and signal are not surfaced by supervisord on this channel. + // Data Per-crash payload for `service_crashed` events. Exit code and signal are not exposed by the underlying process manager on this channel, so only the service identity, the lifecycle phase the crash occurred in, and (when available) the PID are reported. Data *BrowserServiceCrashedEventData `json:"data,omitempty"` // Source Provenance metadata identifying which producer emitted the event. @@ -2268,20 +2292,20 @@ type BrowserServiceCrashedEvent struct { // BrowserServiceCrashedEventType defines model for BrowserServiceCrashedEvent.Type. type BrowserServiceCrashedEventType string -// BrowserServiceCrashedEventData Per-crash payload for `service_crashed` events. Fields are derived from the supervisord eventlistener wire protocol, which exposes the process name, the state the process exited from, and (for EXITED but not FATAL transitions) the PID. Exit code and signal are not surfaced by supervisord on this channel. +// BrowserServiceCrashedEventData Per-crash payload for `service_crashed` events. Exit code and signal are not exposed by the underlying process manager on this channel, so only the service identity, the lifecycle phase the crash occurred in, and (when available) the PID are reported. type BrowserServiceCrashedEventData struct { - // FromState Supervisord state the process was in before the unexpected exit. `RUNNING` means a healthy process died; `STARTING` means it died during startup; `BACKOFF` is paired with FATAL and means supervisord gave up restarting it. - FromState BrowserServiceCrashedEventDataFromState `json:"from_state"` + // Phase Lifecycle phase the crash occurred in. `startup` means the process died before it ever reached a healthy running state. `running` means a previously healthy process died unexpectedly. `gave_up` means the process manager exhausted its restart attempts and stopped trying; no further `service_crashed` events will fire for this service until something restarts it. + Phase BrowserServiceCrashedEventDataPhase `json:"phase"` - // Pid PID of the crashed process. Absent for PROCESS_STATE_FATAL transitions, which fire after all start attempts are exhausted. + // Pid PID of the crashed process. Absent when the process manager gave up after exhausting restart attempts and is no longer tracking a live PID. Pid *int `json:"pid,omitempty"` - // ServiceName Supervisord program name (e.g. `chromium`, `mutter`, `kernel-images-api`). + // ServiceName Program name of the crashed service (e.g. `chromium`, `mutter`, `kernel-images-api`). ServiceName string `json:"service_name"` } -// BrowserServiceCrashedEventDataFromState Supervisord state the process was in before the unexpected exit. `RUNNING` means a healthy process died; `STARTING` means it died during startup; `BACKOFF` is paired with FATAL and means supervisord gave up restarting it. -type BrowserServiceCrashedEventDataFromState string +// BrowserServiceCrashedEventDataPhase Lifecycle phase the crash occurred in. `startup` means the process died before it ever reached a healthy running state. `running` means a previously healthy process died unexpectedly. `gave_up` means the process manager exhausted its restart attempts and stopped trying; no further `service_crashed` events will fire for this service until something restarts it. +type BrowserServiceCrashedEventDataPhase string // BrowserSystemOomKillEvent The Linux kernel OOM-killer terminated a process inside the VM. Sourced from `/dev/kmsg`. Fires for any process killed by the kernel due to memory exhaustion, including Chrome renderer subprocesses that are not supervised. type BrowserSystemOomKillEvent struct { @@ -2304,8 +2328,14 @@ type BrowserSystemOomKillEventType string // BrowserSystemOomKillEventData Per-kill payload for `system_oom_kill` events. type BrowserSystemOomKillEventData struct { - // OomScoreAdj oom_score_adj of the killed process at the time of kill. - OomScoreAdj *int `json:"oom_score_adj,omitempty"` + // Constraint Why the kernel decided to OOM-kill. `none` means global memory exhaustion; `memcg` means a cgroup memory limit was hit; `cpuset` / `memory_policy` are NUMA/policy-driven kills. Absent on kernels older than 5.0 which did not emit the structured `oom-kill:` line. + Constraint *BrowserSystemOomKillEventDataConstraint `json:"constraint,omitempty"` + + // MemFreeKb Free system memory in KiB at the time of the kill, derived from the `free:N` field in the kernel's Mem-Info dump. Assumes a 4 KiB page size. Does not include reclaimable caches, so a small value with a large `mem_total_kb` may still mean the system was not under hard pressure. Absent if the kernel did not emit a parseable Mem-Info section. + MemFreeKb *int `json:"mem_free_kb,omitempty"` + + // MemTotalKb Total system memory in KiB at the time of the kill, derived from the `N pages RAM` line in the kernel's Mem-Info dump. Assumes a 4 KiB page size. Absent if the kernel did not emit a parseable Mem-Info section. + MemTotalKb *int `json:"mem_total_kb,omitempty"` // Pid PID of the killed process. Pid int `json:"pid"` @@ -2313,11 +2343,26 @@ type BrowserSystemOomKillEventData struct { // ProcessName Comm of the killed process as reported by the kernel (max 15 chars, truncated by the kernel). ProcessName string `json:"process_name"` - // RssKb Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and shmem-rss). + // RssKb Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and shmem-rss). This is the physical memory the process was using at the time of the kill. RssKb int `json:"rss_kb"` - // TotalVmKb Total virtual memory of the killed process in KiB. - TotalVmKb *int `json:"total_vm_kb,omitempty"` + // TopTasks Top processes by resident-set-size at the moment of the kill, sorted descending. Sourced from the kernel's `Tasks state` table. Empty if the kernel did not emit the table. Capped at 5 entries to bound payload size. + TopTasks *[]BrowserSystemOomKillTask `json:"top_tasks,omitempty"` +} + +// BrowserSystemOomKillEventDataConstraint Why the kernel decided to OOM-kill. `none` means global memory exhaustion; `memcg` means a cgroup memory limit was hit; `cpuset` / `memory_policy` are NUMA/policy-driven kills. Absent on kernels older than 5.0 which did not emit the structured `oom-kill:` line. +type BrowserSystemOomKillEventDataConstraint string + +// BrowserSystemOomKillTask A single process entry from the kernel's `Tasks state` dump. +type BrowserSystemOomKillTask struct { + // Name Comm of the process (max 15 chars, truncated by the kernel). + Name string `json:"name"` + + // Pid PID of the process. + Pid int `json:"pid"` + + // RssKb Resident set size in KiB at the moment of the kill. + RssKb int `json:"rss_kb"` } // BrowserTargetType CDP target type of the page that produced the event. @@ -18097,342 +18142,350 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "DbtEh+Dg0hGJJ0s3+v8PAAD//1Hf9WWfpwEA", + "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", + "AQAA//8NYU7Tkq4BAA==", } // GetSwagger returns the content of the embedded swagger specification file From ce8977da4ec4143e8aa65c3103ad535484f669fd Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Tue, 26 May 2026 19:34:38 -0700 Subject: [PATCH 05/13] go mod --- server/go.mod | 1 + server/go.sum | 3 +++ 2 files changed, 4 insertions(+) diff --git a/server/go.mod b/server/go.mod index 1bdae079..d7ae2a36 100644 --- a/server/go.mod +++ b/server/go.mod @@ -10,6 +10,7 @@ require ( github.com/creack/pty v1.1.24 github.com/docker/docker v28.5.1+incompatible github.com/docker/go-connections v0.6.0 + github.com/euank/go-kmsg-parser/v2 v2.1.0 github.com/fsnotify/fsnotify v1.9.0 github.com/getkin/kin-openapi v0.133.0 github.com/ghodss/yaml v1.0.0 diff --git a/server/go.sum b/server/go.sum index b26c03c9..bb8925ce 100644 --- a/server/go.sum +++ b/server/go.sum @@ -52,6 +52,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/euank/go-kmsg-parser/v2 v2.1.0 h1:G3QuOjQgrC1lNUgArlLLnvq/8S9kksQW/fk26LfPDew= +github.com/euank/go-kmsg-parser/v2 v2.1.0/go.mod h1:829LX1BxwHvmThOJ2AIy+b42Ku7VdX7lgVQFwmo5zdY= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -216,6 +218,7 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= From aebf9392e794c1613701736792d7554a243bb272 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Tue, 26 May 2026 19:34:44 -0700 Subject: [PATCH 06/13] vibe code --- server/cmd/api/main.go | 9 +- server/cmd/supervisord-shim/main.go | 112 +++++--- server/cmd/supervisord-shim/main_test.go | 201 +++++++------- server/lib/sysmon/kmsg.go | 306 ++++++++++++++------- server/lib/sysmon/kmsg_linux.go | 66 +++++ server/lib/sysmon/kmsg_other.go | 18 ++ server/lib/sysmon/kmsg_test.go | 323 +++++++++++++++++------ server/lib/sysmon/sysmon.go | 180 ++++++++++--- server/lib/sysmon/sysmon_test.go | 149 ++++++----- 9 files changed, 962 insertions(+), 402 deletions(-) create mode 100644 server/lib/sysmon/kmsg_linux.go create mode 100644 server/lib/sysmon/kmsg_other.go diff --git a/server/cmd/api/main.go b/server/cmd/api/main.go index 0540b606..2f73dd24 100644 --- a/server/cmd/api/main.go +++ b/server/cmd/api/main.go @@ -104,10 +104,13 @@ func main() { } telemetrySession := telemetry.NewTelemetrySession(eventStream) - // VM-internal failure telemetry (OOM kills via /dev/kmsg). + // VM-internal failure telemetry. OOM kills come from /dev/kmsg here; // service_crashed events arrive via POST /telemetry/events from the - // supervisord-shim child process, not through this monitor. - sysmon.New(eventStream, slogger).Start(ctx) + // supervisord-shim child process. Failure to open /dev/kmsg is not + // fatal — the rest of the API should stay usable without CAP_SYSLOG. + if err := sysmon.New(eventStream, slogger).Start(ctx); err != nil { + slogger.Error("sysmon: kmsg OOM monitor disabled", "err", err) + } // Optional S2 storage sink. var s2Writer *events.S2StorageWriter diff --git a/server/cmd/supervisord-shim/main.go b/server/cmd/supervisord-shim/main.go index 1cd0fb25..0638d5f5 100644 --- a/server/cmd/supervisord-shim/main.go +++ b/server/cmd/supervisord-shim/main.go @@ -1,18 +1,24 @@ // Command supervisord-shim is a tiny supervisord eventlistener that -// translates PROCESS_STATE_EXITED (expected=0) and PROCESS_STATE_FATAL events -// into BrowserServiceCrashedEvent payloads and POSTs them to the local -// kernel-images-api telemetry endpoint. +// translates PROCESS_STATE_EXITED (expected=0) and PROCESS_STATE_FATAL +// events into BrowserServiceCrashedEvent payloads and POSTs them to the +// local kernel-images-api telemetry endpoint. // -// All schema-mapping and event publishing logic lives here; lib/sysmon does -// not handle supervisord events. Keeping the shim as the sole owner of the -// supervisord protocol means lib/sysmon stays single-purpose (kmsg only). +// All schema-mapping and event publishing logic lives here; lib/sysmon +// does not handle supervisord events. Keeping the shim as the sole owner +// of the supervisord protocol means lib/sysmon stays single-purpose +// (kmsg only). // -// Wire protocol per supervisord docs: +// Wire protocol per supervisord docs (http://supervisord.org/events.html): // // stdout: "READY\n" // stdin: header line ("ver:3.0 ... eventname:PROCESS_STATE_EXITED len:54\n") // stdin: payload of `len` bytes (no trailing newline) -// stdout: "RESULT 2\nOK\n" (always; ACK regardless of downstream success) +// stdout: "RESULT 2\nOK" (always; ACK regardless of downstream success) +// +// The result frame intentionally has NO trailing newline: supervisord +// reads exactly the declared number of bytes after the header newline, +// and a trailing newline would leak into the buffer and corrupt the +// subsequent READY token, deadlocking the listener after one event. // // We always ACK with OK so supervisord doesn't quarantine us when the // downstream HTTP target is briefly unavailable. The events are @@ -75,23 +81,42 @@ func main() { } // Try to publish but always ACK supervisord. - if ev, ok := mapEvent(header, payload); ok { + ev, ok := mapEvent(header, payload) + switch { + case ok: if perr := pub.publish(context.Background(), ev); perr != nil { log.Printf("publish telemetry event: %v", perr) } + case isCrashEvent(header["eventname"]): + // We subscribed to this event type but couldn't map it. + // Most likely cause: supervisord emitted a from_state we + // don't have a public phase for. Logging means a future + // supervisord behavior change shows up in stderr instead + // of silent telemetry loss. + log.Printf("skipped crash event: eventname=%q from_state=%q processname=%q expected=%q", + header["eventname"], payload["from_state"], payload["processname"], payload["expected"]) } - if _, err := out.WriteString("RESULT 2\nOK\n"); err != nil { + if err := writeResultOK(out); err != nil { log.Fatalf("write RESULT: %v", err) } - if err := out.Flush(); err != nil { - log.Fatalf("flush RESULT: %v", err) - } } } -// readEvent reads one supervisord event: a header line followed by a payload -// of declared length. +// writeResultOK writes the supervisord eventlistener "RESULT" frame +// indicating success. The body is exactly "OK" (2 bytes) with NO trailing +// newline — supervisord reads exactly `len` bytes after the header +// newline, and a trailing newline would leak into the buffer and corrupt +// the subsequent READY token. +func writeResultOK(out *bufio.Writer) error { + if _, err := out.WriteString("RESULT 2\nOK"); err != nil { + return err + } + return out.Flush() +} + +// readEvent reads one supervisord event: a header line followed by a +// payload of declared length. func readEvent(in *bufio.Reader) (map[string]string, map[string]string, error) { headerLine, err := in.ReadString('\n') if err != nil { @@ -117,9 +142,9 @@ func readEvent(in *bufio.Reader) (map[string]string, map[string]string, error) { } // parseFields parses supervisord's "key:value key:value" tokenization. -// Values are split on the first colon; supervisord does not escape colons in -// values, but in practice the values we care about (process names, states, -// ints) never contain them. +// Values are split on the first colon; supervisord does not escape colons +// in values, but in practice the values we care about (process names, +// states, ints) never contain them. func parseFields(s string) map[string]string { out := make(map[string]string) for _, tok := range strings.Fields(s) { @@ -132,8 +157,9 @@ func parseFields(s string) map[string]string { return out } -// telemetryEventBody mirrors oapi.TelemetryEvent but is duplicated here so the -// shim does not pull in the entire server module — keeps the binary tiny. +// telemetryEventBody mirrors oapi.TelemetryEvent but is duplicated here +// so the shim does not pull in the entire server module — keeps the +// binary tiny. Field names track openapi.yaml by convention. type telemetryEventBody struct { Type string `json:"type"` Category string `json:"category"` @@ -148,25 +174,51 @@ type telemetryEventSource struct { type serviceCrashedPayload struct { ServiceName string `json:"service_name"` - FromState string `json:"from_state"` + Phase string `json:"phase"` Pid *int `json:"pid,omitempty"` } +// phaseFromSupervisordState maps the process manager's pre-exit state to +// the neutral lifecycle phase exposed in the public event schema. The +// goal is to keep the supervisord vocabulary out of the API contract. +func phaseFromSupervisordState(fromState string) string { + switch fromState { + case "RUNNING": + return "running" + case "STARTING": + return "startup" + case "BACKOFF": + // PROCESS_STATE_FATAL transitions out of BACKOFF after the + // process manager exhausts its restart attempts. + return "gave_up" + default: + return "" + } +} + +// isCrashEvent reports whether the supervisord eventname is one we +// subscribed to. Used by the main loop to log when a target event was +// dropped instead of silently skipping it. +func isCrashEvent(eventName string) bool { + return eventName == "PROCESS_STATE_EXITED" || eventName == "PROCESS_STATE_FATAL" +} + // mapEvent decides whether to publish and constructs the event payload. // Returns ok=false for events we deliberately skip (intentional stops, -// non-crash event types). +// non-crash event types, or unknown lifecycle transitions). func mapEvent(header, payload map[string]string) (telemetryEventBody, bool) { eventName := header["eventname"] switch eventName { case "PROCESS_STATE_EXITED": - // expected=0 means the exit was not in `exitcodes` — i.e. a crash. - // expected=1 means clean shutdown (supervisorctl stop, or a configured - // exitcode). Skip the latter. + // expected=0 means the exit was not in `exitcodes` — i.e. a + // crash. expected=1 means clean shutdown (operator-initiated + // stop, or a configured exit code). Skip the latter. if payload["expected"] != "0" { return telemetryEventBody{}, false } case "PROCESS_STATE_FATAL": - // FATAL: supervisord exhausted startretries. Always a crash. + // FATAL: the process manager exhausted startretries. Always a + // crash from the user's perspective. default: return telemetryEventBody{}, false } @@ -175,8 +227,8 @@ func mapEvent(header, payload map[string]string) (telemetryEventBody, bool) { if name == "" { return telemetryEventBody{}, false } - fromState := payload["from_state"] - if fromState == "" { + phase := phaseFromSupervisordState(payload["from_state"]) + if phase == "" { return telemetryEventBody{}, false } @@ -185,11 +237,11 @@ func mapEvent(header, payload map[string]string) (telemetryEventBody, bool) { Category: "system", Source: telemetryEventSource{ Kind: "local_process", - Event: "supervisord.process_" + strings.ToLower(strings.TrimPrefix(eventName, "PROCESS_STATE_")), + Event: "service.crashed", }, Data: serviceCrashedPayload{ ServiceName: name, - FromState: fromState, + Phase: phase, }, } if pidStr := payload["pid"]; pidStr != "" { diff --git a/server/cmd/supervisord-shim/main_test.go b/server/cmd/supervisord-shim/main_test.go index 7a268174..b03977cc 100644 --- a/server/cmd/supervisord-shim/main_test.go +++ b/server/cmd/supervisord-shim/main_test.go @@ -2,127 +2,142 @@ package main import ( "bufio" - "reflect" + "bytes" + "strconv" "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestWriteResultOKHasNoTrailingNewline(t *testing.T) { + // Regression: supervisord's eventlistener protocol reads exactly the + // declared byte count after the header newline. A trailing newline + // here misaligns the following READY frame and deadlocks the + // listener after the first event. + var buf bytes.Buffer + bw := bufio.NewWriter(&buf) + require.NoError(t, writeResultOK(bw)) + assert.Equal(t, "RESULT 2\nOK", buf.String()) +} + func TestParseFields(t *testing.T) { - in := "processname:mutter groupname:mutter from_state:RUNNING expected:0 pid:1234" - got := parseFields(in) - want := map[string]string{ + got := parseFields("processname:mutter groupname:mutter from_state:RUNNING expected:0 pid:1234") + assert.Equal(t, map[string]string{ "processname": "mutter", "groupname": "mutter", "from_state": "RUNNING", "expected": "0", "pid": "1234", - } - if !reflect.DeepEqual(got, want) { - t.Fatalf("parseFields = %v, want %v", got, want) - } + }, got) } func TestReadEvent(t *testing.T) { payload := "processname:cat groupname:cat from_state:RUNNING expected:0 pid:2766" header := "ver:3.0 server:supervisor serial:21 pool:listener poolserial:10 eventname:PROCESS_STATE_EXITED len:" + - itoa(len(payload)) + "\n" + strconv.Itoa(len(payload)) + "\n" in := bufio.NewReader(strings.NewReader(header + payload)) hdr, pl, err := readEvent(in) - if err != nil { - t.Fatalf("readEvent: %v", err) - } - if hdr["eventname"] != "PROCESS_STATE_EXITED" { - t.Errorf("eventname = %q", hdr["eventname"]) - } - if pl["pid"] != "2766" || pl["processname"] != "cat" || pl["expected"] != "0" { - t.Errorf("payload = %v", pl) - } + require.NoError(t, err) + assert.Equal(t, "PROCESS_STATE_EXITED", hdr["eventname"]) + assert.Equal(t, "2766", pl["pid"]) + assert.Equal(t, "cat", pl["processname"]) + assert.Equal(t, "0", pl["expected"]) } -func TestMapEventExitedUnexpected(t *testing.T) { - hdr := map[string]string{"eventname": "PROCESS_STATE_EXITED"} - pl := map[string]string{ - "processname": "mutter", - "from_state": "RUNNING", - "expected": "0", - "pid": "1234", - } - body, ok := mapEvent(hdr, pl) - if !ok { - t.Fatal("expected publish") - } - if body.Type != "service_crashed" { - t.Errorf("Type = %q", body.Type) - } - if body.Category != "system" { - t.Errorf("Category = %q", body.Category) - } - if body.Source.Kind != "local_process" { - t.Errorf("Source.Kind = %q", body.Source.Kind) - } - if body.Source.Event != "supervisord.process_exited" { - t.Errorf("Source.Event = %q", body.Source.Event) - } - if body.Data.ServiceName != "mutter" || body.Data.FromState != "RUNNING" { - t.Errorf("Data = %+v", body.Data) - } - if body.Data.Pid == nil || *body.Data.Pid != 1234 { - t.Errorf("Pid = %v", body.Data.Pid) - } +func TestMapEventExitedUnexpectedFromRunning(t *testing.T) { + body, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_EXITED"}, + map[string]string{ + "processname": "mutter", + "from_state": "RUNNING", + "expected": "0", + "pid": "1234", + }, + ) + require.True(t, ok) + assert.Equal(t, "service_crashed", body.Type) + assert.Equal(t, "system", body.Category) + assert.Equal(t, "local_process", body.Source.Kind) + assert.Equal(t, "service.crashed", body.Source.Event) + assert.Equal(t, "mutter", body.Data.ServiceName) + assert.Equal(t, "running", body.Data.Phase) + require.NotNil(t, body.Data.Pid) + assert.Equal(t, 1234, *body.Data.Pid) +} + +func TestMapEventExitedUnexpectedFromStarting(t *testing.T) { + // A crash during startup must surface as the "startup" phase, not + // "running" — operators triage these differently (config bug vs + // runtime bug). + body, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_EXITED"}, + map[string]string{ + "processname": "envoy", + "from_state": "STARTING", + "expected": "0", + "pid": "55", + }, + ) + require.True(t, ok) + assert.Equal(t, "startup", body.Data.Phase) } func TestMapEventExitedExpectedSkipped(t *testing.T) { - hdr := map[string]string{"eventname": "PROCESS_STATE_EXITED"} - pl := map[string]string{ - "processname": "mutter", - "from_state": "RUNNING", - "expected": "1", - "pid": "1234", - } - if _, ok := mapEvent(hdr, pl); ok { - t.Fatal("expected skip for expected=1") - } + _, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_EXITED"}, + map[string]string{ + "processname": "mutter", + "from_state": "RUNNING", + "expected": "1", + "pid": "1234", + }, + ) + assert.False(t, ok, "expected=1 (clean exit) must not produce an event") } -func TestMapEventFatal(t *testing.T) { - hdr := map[string]string{"eventname": "PROCESS_STATE_FATAL"} - pl := map[string]string{ - "processname": "chromium", - "from_state": "BACKOFF", - } - body, ok := mapEvent(hdr, pl) - if !ok { - t.Fatal("expected publish") - } - if body.Source.Event != "supervisord.process_fatal" { - t.Errorf("Source.Event = %q", body.Source.Event) - } - if body.Data.ServiceName != "chromium" || body.Data.FromState != "BACKOFF" { - t.Errorf("Data = %+v", body.Data) - } - if body.Data.Pid != nil { - t.Errorf("Pid should be nil for FATAL, got %v", *body.Data.Pid) - } +func TestMapEventFatalFromBackoff(t *testing.T) { + body, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_FATAL"}, + map[string]string{ + "processname": "chromium", + "from_state": "BACKOFF", + }, + ) + require.True(t, ok) + assert.Equal(t, "gave_up", body.Data.Phase) + assert.Nil(t, body.Data.Pid, "FATAL transitions do not carry a live PID") } func TestMapEventUnrelatedSkipped(t *testing.T) { - hdr := map[string]string{"eventname": "PROCESS_STATE_STARTING"} - if _, ok := mapEvent(hdr, map[string]string{"processname": "x", "from_state": "STOPPED"}); ok { - t.Fatal("expected skip for non-crash event") - } + _, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_STARTING"}, + map[string]string{"processname": "x", "from_state": "STOPPED"}, + ) + assert.False(t, ok) +} + +func TestIsCrashEvent(t *testing.T) { + assert.True(t, isCrashEvent("PROCESS_STATE_EXITED")) + assert.True(t, isCrashEvent("PROCESS_STATE_FATAL")) + assert.False(t, isCrashEvent("PROCESS_STATE_STARTING")) + assert.False(t, isCrashEvent("PROCESS_STATE_RUNNING")) + assert.False(t, isCrashEvent("")) } -func itoa(n int) string { - if n == 0 { - return "0" - } - var b [20]byte - i := len(b) - for n > 0 { - i-- - b[i] = byte('0' + n%10) - n /= 10 - } - return string(b[i:]) +func TestMapEventUnknownFromStateSkipped(t *testing.T) { + // If supervisord emits a crash transition out of a state we have no + // public mapping for (e.g. STOPPED, which shouldn't happen with the + // events we subscribe to), drop the event rather than invent a phase. + _, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_EXITED"}, + map[string]string{ + "processname": "x", + "from_state": "STOPPED", + "expected": "0", + }, + ) + assert.False(t, ok) } diff --git a/server/lib/sysmon/kmsg.go b/server/lib/sysmon/kmsg.go index 57786048..585c787d 100644 --- a/server/lib/sysmon/kmsg.go +++ b/server/lib/sysmon/kmsg.go @@ -1,129 +1,237 @@ package sysmon import ( - "context" - "encoding/json" - "errors" - "io" - "os" "regexp" + "sort" "strconv" "strings" "time" - - "github.com/kernel/kernel-images/server/lib/events" - oapi "github.com/kernel/kernel-images/server/lib/oapi" ) -// oomKillRe matches the canonical kernel OOM-killer line. Example: -// -// Out of memory: Killed process 1234 (chromium) total-vm:5234572kB, anon-rss:4823900kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:8000kB oom_score_adj:0 +// pageSizeKB is the kernel page size in KiB used to convert page-denominated +// fields from the OOM dump (RSS in `Tasks state`, free/total in Mem-Info) +// into KiB. The kernel-images target is x86_64 with the standard 4 KiB page; +// this constant is wrong on architectures with a different page size. +const pageSizeKB = 4 + +// topTasksN bounds the number of process entries from the kernel's Tasks +// state table that we surface in the OOM event payload. Five is enough to +// answer "what consumed memory" for a typical Chromium-on-VM workload +// without bloating the event. +const topTasksN = 5 + +// oomScannerWatchdog bounds the number of UNRECOGNIZED kmsg messages we +// will tolerate inside a single OOM section before abandoning it. +// Recognised lines (Mem-Info, Tasks state, constraint, killed) don't +// count toward the budget, so the watchdog only trips when the section +// diverges from the expected kernel format. A busy VM can emit several +// hundred Tasks state rows during a single dump; this budget leaves +// headroom for that plus the full Mem-Info block. +const oomScannerWatchdog = 2000 + +// OomInstance is a parsed kernel OOM-killer event extracted from /dev/kmsg. // -// The comm is bounded to 15 chars by the kernel (TASK_COMM_LEN-1) but may -// contain parens internally for a few cases (e.g. `(sd-pam)`); we deliberately -// match a lazy non-paren-aware group since the more-permissive form has bitten -// other parsers — a comm with `)` would be exceptional and the line still -// parses for everything except the comm field. -var oomKillRe = regexp.MustCompile( - `Out of memory: Killed process (\d+) \(([^)]+)\) ` + - `total-vm:(\d+)kB, anon-rss:(\d+)kB, file-rss:(\d+)kB, shmem-rss:(\d+)kB` + - `.*?oom_score_adj:(-?\d+)`, +// Fields map to BrowserSystemOomKillEventData. Optional fields use the +// zero value when the kernel did not emit the corresponding kmsg line; +// the publisher decides whether to encode them. +type OomInstance struct { + // ProcessName is the comm of the killed process, bounded to 15 chars + // by the kernel (TASK_COMM_LEN-1). May contain spaces. + ProcessName string + // Pid is the PID of the killed process. + Pid int + // RssKb is the sum of anon-rss, file-rss, and shmem-rss in KiB. + // Zero if the kernel format did not include the per-class breakdown. + RssKb int + // Constraint is one of "none", "memcg", "cpuset", "memory_policy", + // extracted from the structured `oom-kill:` line that kernels >= 5.0 + // emit. Empty on older kernels. + Constraint string + // MemTotalKb is the total system memory at the time of the kill, + // derived from the `N pages RAM` line. Zero if not parseable. + MemTotalKb int + // MemFreeKb is free memory at the time of the kill, derived from the + // `free:N` field in Mem-Info. Zero if not parseable. + MemFreeKb int + // TopTasks is up to topTasksN processes from the Tasks state table, + // sorted by RSS descending. Nil if the kernel did not emit the table. + TopTasks []TaskMemSnapshot + // TimeOfDeath is the timestamp of the closing "Killed process" line + // as reported by the kmsg envelope. + TimeOfDeath time.Time +} + +// TaskMemSnapshot is one row from the kernel's Tasks state dump, +// representing a single process's memory footprint at the moment of the +// OOM kill. +type TaskMemSnapshot struct { + Pid int + Name string + RssKb int +} + +// KmsgMessage is the minimal subset of a /dev/kmsg record that the OOM +// state machine consumes. Decoupling from the underlying kmsg library +// lets the parser run portably under unit tests; the production wiring +// lives in kmsg_linux.go. +type KmsgMessage struct { + Timestamp time.Time + Body string +} + +var ( + oomStartRe = regexp.MustCompile(`invoked oom-killer:`) + // Modern (Linux >= 5.0) structured constraint+task line. + // Example: + // oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/,task=chromium,pid=1234,uid=0 + oomConstraintRe = regexp.MustCompile(`oom-kill:constraint=CONSTRAINT_(\w+)`) + // Closing line. Example: + // Out of memory: Killed process 1234 (chromium) total-vm:5234572kB, anon-rss:4823900kB, file-rss:100kB, shmem-rss:200kB, UID:0 pgtables:8000kB oom_score_adj:0 + oomKilledRe = regexp.MustCompile(`Killed process (\d+) \(([^)]+)\)`) + // RSS breakdown is matched independently so the killed-line match + // remains useful on kernels that omit per-class RSS fields. + oomAnonRssRe = regexp.MustCompile(`anon-rss:(\d+)kB`) + oomFileRssRe = regexp.MustCompile(`file-rss:(\d+)kB`) + oomShmemRssRe = regexp.MustCompile(`shmem-rss:(\d+)kB`) + // Mem-Info / total RAM. Example: `524288 pages RAM`. + oomTotalPagesRe = regexp.MustCompile(`(\d+)\s+pages\s+RAM`) + // Mem-Info free-pages triple. Example: ` free:4560 free_pcp:0 free_cma:0`. + // The trailing `free_pcp:` anchor distinguishes this from per-zone + // lines like `Node 0 DMA free:11264kB boost:0kB`, which carry kB + // units rather than raw page counts. + oomFreePagesRe = regexp.MustCompile(`(?:^|\s)free:(\d+)\s+free_pcp:`) + // Tasks state row. Columns (post-bracket): uid tgid total_vm rss + // pgtables_bytes swapents oom_score_adj name. RSS is in pages. + // Example: + // [ 1234] 1000 1234 1308611 1205975 9678848 0 0 chromium + oomTaskEntryRe = regexp.MustCompile(`^\[\s*(\d+)\]\s+\d+\s+\d+\s+\d+\s+(\d+)\s+\d+\s+\d+\s+-?\d+\s+(\S.*?)\s*$`) ) -// parseKmsgLine strips the kmsg envelope `;` -// and returns the message portion. Returns the original line if no envelope -// is found (which can happen on truncated reads, though that's rare). -func parseKmsgLine(line string) string { - if i := strings.IndexByte(line, ';'); i >= 0 { - return line[i+1:] - } - return line +// oomScanner is a state machine that turns a stream of kmsg message +// bodies into completed OomInstance values. It tolerates the kernel +// emitting many intermediate lines (stack traces, Mem-Info zone +// breakdowns, the Tasks state table) between the opening "invoked +// oom-killer" line and the closing "Killed process" line, and recovers +// if a section never closes. +type oomScanner struct { + pending *OomInstance + noiseBuf int } -// parseOomKill extracts OOM-kill data from a kmsg message body. Returns nil if -// the line is not an OOM-kill record. -func parseOomKill(msg string) *oapi.BrowserSystemOomKillEventData { - m := oomKillRe.FindStringSubmatch(msg) - if m == nil { +// feed consumes a single kmsg message body and returns a completed +// OomInstance when the scanner observes the closing "Killed process" +// line. All other inputs return nil; the scanner accumulates partial +// state internally. +func (s *oomScanner) feed(body string, ts time.Time) *OomInstance { + if oomStartRe.MatchString(body) { + // New section. If a previous section is still pending, the + // kernel either failed to emit the closing line or the kmsg + // ring buffer dropped it; abandon and start fresh. + s.pending = &OomInstance{TimeOfDeath: ts} + s.noiseBuf = 0 return nil } - pid, _ := strconv.Atoi(m[1]) - totalVM, _ := strconv.Atoi(m[3]) - anonRSS, _ := strconv.Atoi(m[4]) - fileRSS, _ := strconv.Atoi(m[5]) - shmemRSS, _ := strconv.Atoi(m[6]) - scoreAdj, _ := strconv.Atoi(m[7]) - rss := anonRSS + fileRSS + shmemRSS - return &oapi.BrowserSystemOomKillEventData{ - ProcessName: m[2], - Pid: pid, - TotalVmKb: &totalVM, - RssKb: rss, - OomScoreAdj: &scoreAdj, + + if s.pending == nil { + return nil } -} -func (m *Monitor) runKmsg(ctx context.Context) { - f, err := os.OpenFile(m.kmsgPath, os.O_RDONLY, 0) - if err != nil { - m.logger.Warn("sysmon: failed to open kmsg, OOM events disabled", "err", err, "path", m.kmsgPath) - return + if m := oomKilledRe.FindStringSubmatch(body); m != nil { + pid, _ := strconv.Atoi(m[1]) + s.pending.Pid = pid + s.pending.ProcessName = m[2] + s.pending.RssKb = sumRss(body) + s.pending.TimeOfDeath = ts + trimTopTasks(s.pending) + out := s.pending + s.pending = nil + s.noiseBuf = 0 + return out } - // Closing f unblocks the read loop on shutdown. - go func() { - <-ctx.Done() - f.Close() - }() - - m.logger.Info("sysmon: kmsg reader started", "path", m.kmsgPath) - - // Each /dev/kmsg read() returns at most one record. The kernel guarantees - // EINVAL if the buffer is too small, so use a generous fixed buffer. - buf := make([]byte, 8192) - for { - n, err := f.Read(buf) - if err != nil { - if ctx.Err() != nil || errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) { - return - } - // EPIPE can occur if a process between us and the ring buffer - // dies; we keep going. - m.logger.Warn("sysmon: kmsg read error", "err", err) - continue + // Each recognised line resets the noise watchdog; only unparseable + // intermediate lines erode the budget. + matched := false + if m := oomConstraintRe.FindStringSubmatch(body); m != nil { + s.pending.Constraint = constraintFromKernel(m[1]) + matched = true + } + if m := oomTotalPagesRe.FindStringSubmatch(body); m != nil { + if n, err := strconv.Atoi(m[1]); err == nil { + s.pending.MemTotalKb = n * pageSizeKB } - line := string(buf[:n]) - msg := parseKmsgLine(line) - data := parseOomKill(msg) - if data == nil { - continue + matched = true + } + if m := oomFreePagesRe.FindStringSubmatch(body); m != nil { + if n, err := strconv.Atoi(m[1]); err == nil { + s.pending.MemFreeKb = n * pageSizeKB + } + matched = true + } + if m := oomTaskEntryRe.FindStringSubmatch(body); m != nil { + pid, _ := strconv.Atoi(m[1]) + rss, _ := strconv.Atoi(m[2]) + s.pending.TopTasks = append(s.pending.TopTasks, TaskMemSnapshot{ + Pid: pid, + Name: m[3], + RssKb: rss * pageSizeKB, + }) + matched = true + } + + if !matched { + s.noiseBuf++ + if s.noiseBuf > oomScannerWatchdog { + s.pending = nil + s.noiseBuf = 0 } - m.publishOomKill(*data) } + return nil } -func (m *Monitor) publishOomKill(data oapi.BrowserSystemOomKillEventData) { - payload, err := json.Marshal(data) - if err != nil { - m.logger.Warn("sysmon: marshal oom kill payload", "err", err) +// trimTopTasks sorts the accumulated Tasks state entries by RSS +// descending and caps the slice at topTasksN. +func trimTopTasks(o *OomInstance) { + if len(o.TopTasks) == 0 { return } - ev := events.Event{ - Ts: time.Now().UnixMicro(), - Type: string(oapi.SystemOomKill), - Category: events.System, - Source: oapi.BrowserEventSource{ - Kind: oapi.LocalProcess, - Event: stringPtr("linux.oom_kill"), - }, - Data: json.RawMessage(payload), + sort.Slice(o.TopTasks, func(i, j int) bool { + return o.TopTasks[i].RssKb > o.TopTasks[j].RssKb + }) + if len(o.TopTasks) > topTasksN { + o.TopTasks = o.TopTasks[:topTasksN] + } +} + +// constraintFromKernel converts the kernel's CONSTRAINT_* identifier +// into the lowercase form used in the public event schema. Unknown +// values are passed through lowercased so the kernel's exact label still +// reaches logs even if it falls outside the schema enum. +func constraintFromKernel(raw string) string { + switch raw { + case "NONE": + return "none" + case "CPUSET": + return "cpuset" + case "MEMCG": + return "memcg" + case "MEMORY_POLICY": + return "memory_policy" + default: + return strings.ToLower(raw) } - m.es.Publish(events.Envelope{Event: ev}) - m.logger.Info("sysmon: oom kill", - "process", data.ProcessName, - "pid", data.Pid, - "rss_kb", data.RssKb, - ) } -func stringPtr(s string) *string { return &s } +// sumRss extracts anon-rss, file-rss, and shmem-rss from the canonical +// "Killed process" line and returns their sum in KiB. Missing fields +// contribute zero, so older kernels that omit the breakdown yield 0. +func sumRss(body string) int { + var total int + for _, re := range []*regexp.Regexp{oomAnonRssRe, oomFileRssRe, oomShmemRssRe} { + if m := re.FindStringSubmatch(body); m != nil { + n, _ := strconv.Atoi(m[1]) + total += n + } + } + return total +} diff --git a/server/lib/sysmon/kmsg_linux.go b/server/lib/sysmon/kmsg_linux.go new file mode 100644 index 00000000..ed6e2b27 --- /dev/null +++ b/server/lib/sysmon/kmsg_linux.go @@ -0,0 +1,66 @@ +//go:build linux + +package sysmon + +import ( + "fmt" + "log/slog" + + "github.com/euank/go-kmsg-parser/v2/kmsgparser" +) + +// openKmsgSource opens /dev/kmsg and seeks past the existing ring buffer +// so the scanner only sees events that occur after this call. Without the +// seek, each process restart would replay the entire historical buffer +// and emit stale events with current timestamps. +func openKmsgSource(logger *slog.Logger) (kmsgSource, error) { + p, err := kmsgparser.NewParser() + if err != nil { + return nil, fmt.Errorf("open /dev/kmsg: %w", err) + } + p.SetLogger(kmsgLogger{logger: logger}) + if err := p.SeekEnd(); err != nil { + p.Close() + return nil, fmt.Errorf("seek to end of /dev/kmsg: %w", err) + } + return &kmsgparserSource{p: p}, nil +} + +// kmsgparserSource adapts the third-party kmsgparser.Parser to the +// internal kmsgSource interface so the rest of sysmon stays decoupled +// from the library type. +type kmsgparserSource struct { + p kmsgparser.Parser +} + +func (s *kmsgparserSource) Messages() <-chan KmsgMessage { + in := s.p.Parse() + out := make(chan KmsgMessage) + go func() { + defer close(out) + for m := range in { + out <- KmsgMessage{Timestamp: m.Timestamp, Body: m.Message} + } + }() + return out +} + +func (s *kmsgparserSource) Close() error { return s.p.Close() } + +// kmsgLogger routes the kmsgparser library's diagnostic output through our +// structured logger. +type kmsgLogger struct { + logger *slog.Logger +} + +func (l kmsgLogger) Infof(format string, args ...any) { + l.logger.Info(fmt.Sprintf("sysmon/kmsg: "+format, args...)) +} + +func (l kmsgLogger) Warningf(format string, args ...any) { + l.logger.Warn(fmt.Sprintf("sysmon/kmsg: "+format, args...)) +} + +func (l kmsgLogger) Errorf(format string, args ...any) { + l.logger.Error(fmt.Sprintf("sysmon/kmsg: "+format, args...)) +} diff --git a/server/lib/sysmon/kmsg_other.go b/server/lib/sysmon/kmsg_other.go new file mode 100644 index 00000000..98111a79 --- /dev/null +++ b/server/lib/sysmon/kmsg_other.go @@ -0,0 +1,18 @@ +//go:build !linux + +package sysmon + +import ( + "errors" + "log/slog" +) + +// errKmsgUnsupported indicates that /dev/kmsg-based OOM telemetry is not +// available on this platform. The production target is Linux; non-Linux +// builds compile so developer machines can run unit tests, but the +// monitor itself is inert there. +var errKmsgUnsupported = errors.New("sysmon: /dev/kmsg OOM monitoring is only supported on Linux") + +func openKmsgSource(_ *slog.Logger) (kmsgSource, error) { + return nil, errKmsgUnsupported +} diff --git a/server/lib/sysmon/kmsg_test.go b/server/lib/sysmon/kmsg_test.go index 82b34692..accf206f 100644 --- a/server/lib/sysmon/kmsg_test.go +++ b/server/lib/sysmon/kmsg_test.go @@ -2,99 +2,248 @@ package sysmon import ( "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestParseKmsgLine(t *testing.T) { - tests := []struct { - name string - in string - want string - }{ - { - "standard envelope", - "<4,123,456789,->;Out of memory: Killed process 1 (init) total-vm:1kB", - "Out of memory: Killed process 1 (init) total-vm:1kB", - }, - { - "no envelope", - "naked message", - "naked message", - }, - { - "empty after semicolon", - "<3,1,2,->;", - "", - }, +// canonicalOomDump is a representative slice of the kmsg lines the kernel +// emits during a global OOM kill on Linux 5.x. The Mem-Info and Tasks +// state sections are abbreviated but preserve the field layout the +// parser depends on. +var canonicalOomDump = []string{ + `chromium invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0`, + `CPU: 2 PID: 1234 Comm: chromium Not tainted 5.15.0-1-amd64 #1`, + `Call Trace:`, + ` dump_stack_lvl+0x44/0x57`, + `Mem-Info:`, + `active_anon:123456 inactive_anon:78901 isolated_anon:0`, + ` slab_reclaimable:2340 slab_unreclaimable:5670`, + ` mapped:8901 shmem:120 pagetables:340`, + ` free:4560 free_pcp:0 free_cma:0`, + `Node 0 active_anon:493824kB inactive_anon:315604kB`, + `Node 0 DMA free:11264kB boost:0kB min:64kB`, + `Node 0 DMA32 free:6976kB boost:0kB min:8120kB`, + `524288 pages RAM`, + `0 pages HighMem/MovableOnly`, + `21465 pages reserved`, + `Tasks state (memory values in pages):`, + `[ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name`, + `[ 111] 0 111 1234 234 45056 0 -250 systemd`, + `[ 234] 0 234 65432 12345 200704 0 0 sshd`, + `[ 1234] 1000 1234 1308611 1205975 9678848 0 0 chromium`, + `[ 5678] 1000 5678 123456 34567 331776 0 0 mutter`, + `oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/,task=chromium,pid=1234,uid=1000`, + `Out of memory: Killed process 1234 (chromium) total-vm:5234572kB, anon-rss:4823900kB, file-rss:100kB, shmem-rss:200kB, UID:1000 pgtables:9678848kB oom_score_adj:0`, +} + +// feedAll runs every line in dump through the scanner and returns every +// completed OomInstance, in order. Used by tests that need to verify a +// whole dump's parsing. +func feedAll(s *oomScanner, dump []string, base time.Time) []OomInstance { + var out []OomInstance + for i, line := range dump { + ts := base.Add(time.Duration(i) * time.Millisecond) + if oom := s.feed(line, ts); oom != nil { + out = append(out, *oom) + } } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - if got := parseKmsgLine(tc.in); got != tc.want { - t.Fatalf("parseKmsgLine = %q, want %q", got, tc.want) - } - }) + return out +} + +func TestOomScannerCanonicalDump(t *testing.T) { + var s oomScanner + base := time.Unix(1_700_000_000, 0) + got := feedAll(&s, canonicalOomDump, base) + require.Len(t, got, 1) + + oom := got[0] + assert.Equal(t, "chromium", oom.ProcessName) + assert.Equal(t, 1234, oom.Pid) + assert.Equal(t, 4823900+100+200, oom.RssKb) + assert.Equal(t, "none", oom.Constraint) + + // 524288 pages * 4 KiB = 2 GiB total + assert.Equal(t, 524288*4, oom.MemTotalKb) + // 4560 pages * 4 KiB = ~17.8 MiB free + assert.Equal(t, 4560*4, oom.MemFreeKb) + + // Top tasks sorted by RSS desc; the 4 tasks in the fixture all fit + // under the cap so the slice contains all of them in the right order. + require.Len(t, oom.TopTasks, 4) + assert.Equal(t, "chromium", oom.TopTasks[0].Name) + assert.Equal(t, 1234, oom.TopTasks[0].Pid) + assert.Equal(t, 1205975*4, oom.TopTasks[0].RssKb) + assert.Equal(t, "mutter", oom.TopTasks[1].Name) + assert.Equal(t, "sshd", oom.TopTasks[2].Name) + assert.Equal(t, "systemd", oom.TopTasks[3].Name) +} + +func TestOomScannerLegacyKernelNoMemInfoNoTasks(t *testing.T) { + // Pre-5.0 kernels emit just the opening line, some stack trace, and + // the closing line. The scanner must still produce a baseline event + // with the new fields left at their zero values. + dump := []string{ + `chromium invoked oom-killer: gfp_mask=0x14000c0, order=0, oom_score_adj=0`, + `Mem-Info:`, + `Out of memory: Killed process 9 (mutter) total-vm:200kB, anon-rss:150kB, file-rss:10kB, shmem-rss:5kB, UID:1000 pgtables:1kB oom_score_adj:0`, } + var s oomScanner + got := feedAll(&s, dump, time.Now()) + require.Len(t, got, 1) + oom := got[0] + assert.Equal(t, "mutter", oom.ProcessName) + assert.Equal(t, 9, oom.Pid) + assert.Equal(t, 165, oom.RssKb) + assert.Empty(t, oom.Constraint) + assert.Zero(t, oom.MemTotalKb) + assert.Zero(t, oom.MemFreeKb) + assert.Empty(t, oom.TopTasks) } -func TestParseOomKill(t *testing.T) { - t.Run("canonical line", func(t *testing.T) { - msg := "Out of memory: Killed process 1234 (chromium) total-vm:5234572kB, anon-rss:4823900kB, file-rss:100kB, shmem-rss:200kB, UID:0 pgtables:8000kB oom_score_adj:0" - data := parseOomKill(msg) - if data == nil { - t.Fatal("expected match") - } - if data.Pid != 1234 { - t.Errorf("Pid = %d, want 1234", data.Pid) - } - if data.ProcessName != "chromium" { - t.Errorf("ProcessName = %q, want chromium", data.ProcessName) - } - if data.TotalVmKb == nil || *data.TotalVmKb != 5234572 { - t.Errorf("TotalVmKb = %v, want 5234572", data.TotalVmKb) - } - // rss_kb = anon + file + shmem = 4823900 + 100 + 200 - if data.RssKb != 4824200 { - t.Errorf("RssKb = %d, want 4824200", data.RssKb) - } - if data.OomScoreAdj == nil || *data.OomScoreAdj != 0 { - t.Errorf("OomScoreAdj = %v, want 0", data.OomScoreAdj) - } - }) +func TestOomScannerTasksTableCappedAtTopN(t *testing.T) { + // Build a synthetic Tasks state with more than topTasksN entries to + // verify the scanner sorts by RSS and trims. + dump := []string{`chromium invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`} + rssVals := []int{100, 900, 50, 800, 700, 200, 600, 300, 400, 500} // 10 procs + for i, rss := range rssVals { + dump = append(dump, + "[ "+itoa(i+1)+"] 0 "+itoa(i+1)+" 1000 "+itoa(rss)+" 1024 0 0 proc"+itoa(i+1)) + } + dump = append(dump, + `Out of memory: Killed process 1 (proc1) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, + ) - t.Run("negative oom_score_adj", func(t *testing.T) { - msg := "Out of memory: Killed process 999 (sshd) total-vm:10kB, anon-rss:1kB, file-rss:2kB, shmem-rss:0kB, UID:0 pgtables:1kB oom_score_adj:-1000" - data := parseOomKill(msg) - if data == nil { - t.Fatal("expected match") - } - if data.OomScoreAdj == nil || *data.OomScoreAdj != -1000 { - t.Errorf("OomScoreAdj = %v, want -1000", data.OomScoreAdj) - } - }) - - t.Run("comm with internal space", func(t *testing.T) { - // Kernel preserves spaces in comm; bounded by TASK_COMM_LEN. - msg := "Out of memory: Killed process 42 (kworker u4:1) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0" - data := parseOomKill(msg) - if data == nil { - t.Fatal("expected match") - } - if data.ProcessName != "kworker u4:1" { - t.Errorf("ProcessName = %q, want %q", data.ProcessName, "kworker u4:1") - } - }) + var s oomScanner + got := feedAll(&s, dump, time.Now()) + require.Len(t, got, 1) + require.Len(t, got[0].TopTasks, topTasksN, "should cap at topTasksN") + // Top 5 by RSS: 900, 800, 700, 600, 500 + wantRss := []int{900 * pageSizeKB, 800 * pageSizeKB, 700 * pageSizeKB, 600 * pageSizeKB, 500 * pageSizeKB} + for i, w := range wantRss { + assert.Equal(t, w, got[0].TopTasks[i].RssKb, "position %d", i) + } +} - t.Run("no match", func(t *testing.T) { - if got := parseOomKill("just some other kernel log line"); got != nil { - t.Fatalf("expected nil, got %+v", got) - } - }) - - t.Run("preamble (oom dump task list)", func(t *testing.T) { - // We must NOT match the per-process audit lines the kernel emits - // before the canonical "Killed process" decision. - msg := "[1234] 0 1234 1308611 1205975 9678848 0 0 chromium" - if got := parseOomKill(msg); got != nil { - t.Fatalf("expected nil for preamble line, got %+v", got) - } - }) +func TestOomScannerCommWithInternalSpace(t *testing.T) { + var s oomScanner + s.feed("invoked oom-killer:", time.Now()) + got := s.feed(`Out of memory: Killed process 42 (kworker u4:1) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) + require.NotNil(t, got) + assert.Equal(t, "kworker u4:1", got.ProcessName) + assert.Equal(t, 42, got.Pid) +} + +func TestOomScannerIgnoresPreambleWhenIdle(t *testing.T) { + // A "Killed process" line in isolation must NOT emit. Without the + // section delimiter we cannot attribute it reliably; the kernel + // occasionally surfaces orphaned OOM lines when the ring buffer + // wraps. + var s oomScanner + got := s.feed(`Out of memory: Killed process 1 (init) total-vm:1kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) + assert.Nil(t, got) +} + +func TestOomScannerIgnoresUnrelatedLines(t *testing.T) { + var s oomScanner + assert.Nil(t, s.feed("usb 1-1: new high-speed USB device number 5", time.Now())) + // A bracketed line that LOOKS like a task entry but has the wrong + // column count should not crash or misparse. + assert.Nil(t, s.feed("[1234] only one field", time.Now())) +} + +func TestOomScannerSecondStartAbandonsFirst(t *testing.T) { + // If a section never completes and a new one starts, the new section + // must not inherit state from the abandoned one. + var s oomScanner + s.feed("invoked oom-killer:", time.Now()) + s.feed("oom-kill:constraint=CONSTRAINT_MEMCG,task=stale,pid=1,uid=0", time.Now()) + s.feed("[ 1] 0 1 100 900 1024 0 0 stale", time.Now()) + s.feed("invoked oom-killer:", time.Now()) + got := s.feed(`Out of memory: Killed process 7 (real) total-vm:0kB, anon-rss:10kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) + require.NotNil(t, got) + assert.Equal(t, "real", got.ProcessName) + assert.Equal(t, 7, got.Pid) + assert.Empty(t, got.Constraint, "stale section's constraint must not leak") + assert.Empty(t, got.TopTasks, "stale section's tasks must not leak") +} + +func TestOomScannerSequentialKills(t *testing.T) { + // Two complete dumps back-to-back must emit two independent events. + // Real systems do cascade OOM kills when memory pressure is severe. + second := []string{ + `mutter invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, + `Mem-Info:`, + `oom-kill:constraint=CONSTRAINT_MEMCG,task=mutter,pid=99,uid=1000`, + `Out of memory: Killed process 99 (mutter) total-vm:1kB, anon-rss:50kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:0kB oom_score_adj:0`, + } + combined := append([]string{}, canonicalOomDump...) + combined = append(combined, second...) + + var s oomScanner + got := feedAll(&s, combined, time.Now()) + require.Len(t, got, 2) + assert.Equal(t, "chromium", got[0].ProcessName) + assert.Equal(t, "none", got[0].Constraint) + assert.Equal(t, "mutter", got[1].ProcessName) + assert.Equal(t, "memcg", got[1].Constraint) +} + +func TestOomScannerNoiseWatchdogReleasesStuckSection(t *testing.T) { + var s oomScanner + s.feed("invoked oom-killer:", time.Now()) + for i := 0; i < oomScannerWatchdog+10; i++ { + s.feed("filler line that matches no pattern", time.Now()) + } + // Scanner should have reset; a closing line now lands as orphaned + // preamble and is ignored. + got := s.feed(`Out of memory: Killed process 1 (x) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) + assert.Nil(t, got) +} + +func TestOomScannerRecognisedLinesDoNotErodeWatchdog(t *testing.T) { + // A Tasks state table with hundreds of entries should not trip the + // watchdog — recognised lines are productive parsing, not noise. + dump := []string{`chromium invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`} + for i := 0; i < oomScannerWatchdog+100; i++ { + dump = append(dump, + "[ "+itoa(i)+"] 0 "+itoa(i)+" 1234 567 45056 0 0 proc"+itoa(i)) + } + dump = append(dump, + `Out of memory: Killed process 1 (x) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, + ) + + var s oomScanner + got := feedAll(&s, dump, time.Now()) + require.Len(t, got, 1, "watchdog must not abandon a section composed only of recognised lines") +} + +func TestConstraintFromKernel(t *testing.T) { + cases := map[string]string{ + "NONE": "none", + "CPUSET": "cpuset", + "MEMCG": "memcg", + "MEMORY_POLICY": "memory_policy", + "SOMETHING_FUTURE": "something_future", + } + for raw, want := range cases { + assert.Equal(t, want, constraintFromKernel(raw), raw) + } +} + +// itoa is a tiny test helper so the fixture builders don't pull in +// strconv noise. Bounded to non-negative ints. +func itoa(n int) string { + if n == 0 { + return "0" + } + var b [20]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + return string(b[i:]) } diff --git a/server/lib/sysmon/sysmon.go b/server/lib/sysmon/sysmon.go index 926ab00c..14a8517b 100644 --- a/server/lib/sysmon/sysmon.go +++ b/server/lib/sysmon/sysmon.go @@ -1,55 +1,175 @@ -// Package sysmon emits VM-internal failure telemetry — OOM kills surfaced -// through /dev/kmsg, and (via the supervisord-shim binary POSTing to the -// telemetry HTTP endpoint) supervised-service crashes. +// Package sysmon emits VM-internal failure telemetry — OOM kills +// surfaced through /dev/kmsg, and (via the supervisord-shim binary +// POSTing to the telemetry HTTP endpoint) supervised-service crashes. // // The package only owns the in-process kmsg reader; service crashes are -// delivered as ordinary caller-published events via POST /telemetry/events -// from the shim. Both paths terminate in the same EventStream. +// delivered as ordinary caller-published events via POST +// /telemetry/events from the shim. Both paths terminate in the same +// EventStream. package sysmon import ( "context" + "encoding/json" "log/slog" + "sync" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) -// DefaultKmsgPath is the standard kernel log device. -const DefaultKmsgPath = "/dev/kmsg" +// kmsgSource abstracts a /dev/kmsg-shaped stream of kernel ring buffer +// messages. The production implementation lives in kmsg_linux.go; tests +// supply a stub via Monitor.kmsgSource. +type kmsgSource interface { + Messages() <-chan KmsgMessage + Close() error +} -// Monitor runs the in-process sysmon goroutines and publishes events directly -// to the EventStream. System-category events are always captured regardless -// of any active TelemetrySession config, so we deliberately bypass -// TelemetrySession here. +// Monitor runs the in-process sysmon goroutine and publishes events +// directly to the EventStream. System-category events are always +// captured regardless of any active TelemetrySession config, so we +// deliberately bypass TelemetrySession here. type Monitor struct { - es *events.EventStream - logger *slog.Logger - kmsgPath string + es *events.EventStream + logger *slog.Logger + + // kmsgSource lets tests inject a stub stream of kmsg messages. + // Production callers leave this nil; Start() then opens /dev/kmsg. + kmsgSource kmsgSource + + wg sync.WaitGroup } -// Option configures a Monitor. -type Option func(*Monitor) +// option configures a Monitor. +type option func(*Monitor) -// WithKmsgPath overrides the default /dev/kmsg path. Intended for tests. -func WithKmsgPath(path string) Option { - return func(m *Monitor) { m.kmsgPath = path } +// withKmsgSource overrides the kmsg source. Test-only. +func withKmsgSource(src kmsgSource) option { + return func(m *Monitor) { m.kmsgSource = src } } -// New constructs a Monitor. The Monitor does nothing until Start is called. -func New(es *events.EventStream, logger *slog.Logger, opts ...Option) *Monitor { - m := &Monitor{ - es: es, - logger: logger, - kmsgPath: DefaultKmsgPath, - } +// New constructs a Monitor. The Monitor does nothing until Start is +// called. +func New(es *events.EventStream, logger *slog.Logger, opts ...option) *Monitor { + m := &Monitor{es: es, logger: logger} for _, opt := range opts { opt(m) } return m } -// Start launches background goroutines. It returns immediately; goroutines -// shut down when ctx is cancelled. -func (m *Monitor) Start(ctx context.Context) { - go m.runKmsg(ctx) +// Start opens the kmsg source (validating that it is usable) and +// launches the background OOM reader goroutine. It returns an error if +// the kmsg source cannot be opened; the goroutine then never starts and +// the caller can decide whether the failure is fatal. +// +// Start must be called at most once per Monitor. Calling it twice would +// spawn two readers racing on the same kmsg channel and corrupt the OOM +// state machine. Callers needing a restart should construct a new +// Monitor. +// +// The goroutine shuts down when ctx is cancelled; Wait blocks until it +// returns. +func (m *Monitor) Start(ctx context.Context) error { + if m.kmsgSource == nil { + src, err := openKmsgSource(m.logger) + if err != nil { + return err + } + m.kmsgSource = src + } + m.wg.Add(1) + go func() { + defer m.wg.Done() + m.runOomLoop(ctx) + }() + return nil +} + +// Wait blocks until all goroutines launched by Start have returned. Safe +// to call from tests after cancelling the Start context. +func (m *Monitor) Wait() { m.wg.Wait() } + +// runOomLoop consumes the kmsg stream, drives the OOM state machine, and +// publishes a system_oom_kill event for each completed instance. +func (m *Monitor) runOomLoop(ctx context.Context) { + src := m.kmsgSource + // Closing the source unblocks any read in Messages() so the range + // terminates cleanly on shutdown. + go func() { + <-ctx.Done() + _ = src.Close() + }() + + m.logger.Info("sysmon: kmsg OOM reader started") + + var s oomScanner + for msg := range src.Messages() { + oom := s.feed(msg.Body, msg.Timestamp) + if oom == nil { + continue + } + m.publishOomKill(*oom) + } + + m.logger.Info("sysmon: kmsg OOM reader stopped") +} + +func (m *Monitor) publishOomKill(oom OomInstance) { + data := oapi.BrowserSystemOomKillEventData{ + ProcessName: oom.ProcessName, + Pid: oom.Pid, + RssKb: oom.RssKb, + } + if oom.Constraint != "" { + c := oapi.BrowserSystemOomKillEventDataConstraint(oom.Constraint) + data.Constraint = &c + } + if oom.MemTotalKb > 0 { + v := oom.MemTotalKb + data.MemTotalKb = &v + } + if oom.MemFreeKb > 0 { + v := oom.MemFreeKb + data.MemFreeKb = &v + } + if len(oom.TopTasks) > 0 { + tasks := make([]oapi.BrowserSystemOomKillTask, len(oom.TopTasks)) + for i, t := range oom.TopTasks { + tasks[i] = oapi.BrowserSystemOomKillTask{ + Pid: t.Pid, + Name: t.Name, + RssKb: t.RssKb, + } + } + data.TopTasks = &tasks + } + + payload, err := json.Marshal(data) + if err != nil { + m.logger.Warn("sysmon: marshal oom kill payload", "err", err) + return + } + srcEvent := "linux.oom_kill" + ev := events.Event{ + Ts: oom.TimeOfDeath.UnixMicro(), + Type: string(oapi.SystemOomKill), + Category: events.System, + Source: oapi.BrowserEventSource{ + Kind: oapi.LocalProcess, + Event: &srcEvent, + }, + Data: json.RawMessage(payload), + } + m.es.Publish(events.Envelope{Event: ev}) + m.logger.Info("sysmon: oom kill", + "process", oom.ProcessName, + "pid", oom.Pid, + "rss_kb", oom.RssKb, + "constraint", oom.Constraint, + "mem_total_kb", oom.MemTotalKb, + "mem_free_kb", oom.MemFreeKb, + "top_tasks", len(oom.TopTasks), + ) } diff --git a/server/lib/sysmon/sysmon_test.go b/server/lib/sysmon/sysmon_test.go index 432f4a53..8316c153 100644 --- a/server/lib/sysmon/sysmon_test.go +++ b/server/lib/sysmon/sysmon_test.go @@ -5,89 +5,118 @@ import ( "encoding/json" "io" "log/slog" - "os" - "path/filepath" - "syscall" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/kernel/kernel-images/server/lib/events" oapi "github.com/kernel/kernel-images/server/lib/oapi" ) -// TestKmsgRoundTrip pipes a synthetic OOM line through a FIFO and verifies an -// event lands in the EventStream with the right schema. Linux only — /dev/kmsg -// semantics don't apply here, but we open a regular FIFO so the goroutine just -// reads bytes; the parser is what we exercise. -func TestKmsgRoundTrip(t *testing.T) { - dir := t.TempDir() - fifo := filepath.Join(dir, "kmsg") - if err := syscall.Mkfifo(fifo, 0o600); err != nil { - t.Skipf("mkfifo unsupported: %v", err) +// stubKmsgSource pushes synthetic kmsg messages through an in-memory +// channel. Closing the source via Close() (typically triggered by the +// Monitor's ctx-done watcher) terminates the message channel so the +// reader goroutine exits cleanly. +type stubKmsgSource struct { + ch chan KmsgMessage + closed chan struct{} +} + +func newStubKmsgSource() *stubKmsgSource { + return &stubKmsgSource{ + ch: make(chan KmsgMessage, 32), + closed: make(chan struct{}), } +} - es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: 16}) - if err != nil { - t.Fatalf("event stream: %v", err) +func (s *stubKmsgSource) Messages() <-chan KmsgMessage { return s.ch } + +func (s *stubKmsgSource) Close() error { + select { + case <-s.closed: + default: + close(s.closed) + close(s.ch) } + return nil +} + +func (s *stubKmsgSource) send(body string, ts time.Time) { + s.ch <- KmsgMessage{Body: body, Timestamp: ts} +} + +func TestMonitorPublishesOomKillEnd2End(t *testing.T) { + es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: 16}) + require.NoError(t, err) logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + src := newStubKmsgSource() + mon := New(es, logger, withKmsgSource(src)) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() + require.NoError(t, mon.Start(ctx)) - mon := New(es, logger, WithKmsgPath(fifo)) - mon.Start(ctx) - - // Open writer side after the goroutine has had time to open the reader. - // Opening the FIFO for write blocks until a reader is present, which - // gives us synchronization for free. - w, err := os.OpenFile(fifo, os.O_WRONLY, 0) - if err != nil { - t.Fatalf("open writer: %v", err) + ts := time.Unix(1_700_000_000, 0) + for _, line := range canonicalOomDump { + src.send(line, ts) } - defer w.Close() - line := "<4,123,456789,->;Out of memory: Killed process 4242 (renderer) total-vm:200kB, anon-rss:150kB, file-rss:10kB, shmem-rss:5kB, UID:1000 pgtables:1kB oom_score_adj:300\n" - if _, err := w.Write([]byte(line)); err != nil { - t.Fatalf("write: %v", err) - } - - // Poll the stream until the event arrives or we time out. reader := es.NewReader(0) readCtx, readCancel := context.WithTimeout(ctx, 2*time.Second) defer readCancel() res, err := reader.Read(readCtx) - if err != nil { - t.Fatalf("read envelope: %v", err) - } + require.NoError(t, err) + ev := res.Envelope.Event - if ev.Type != string(oapi.SystemOomKill) { - t.Fatalf("Type = %q, want %q", ev.Type, oapi.SystemOomKill) - } - if ev.Category != events.System { - t.Errorf("Category = %q, want system", ev.Category) - } - if ev.Source.Kind != oapi.LocalProcess { - t.Errorf("Source.Kind = %q", ev.Source.Kind) - } - if ev.Source.Event == nil || *ev.Source.Event != "linux.oom_kill" { - t.Errorf("Source.Event = %v", ev.Source.Event) - } + assert.Equal(t, string(oapi.SystemOomKill), ev.Type) + assert.Equal(t, events.System, ev.Category) + assert.Equal(t, oapi.LocalProcess, ev.Source.Kind) + require.NotNil(t, ev.Source.Event) + assert.Equal(t, "linux.oom_kill", *ev.Source.Event) + assert.Equal(t, ts.UnixMicro(), ev.Ts) var data oapi.BrowserSystemOomKillEventData - if err := json.Unmarshal(ev.Data, &data); err != nil { - t.Fatalf("unmarshal data: %v", err) - } - if data.Pid != 4242 { - t.Errorf("Pid = %d", data.Pid) - } - if data.ProcessName != "renderer" { - t.Errorf("ProcessName = %q", data.ProcessName) - } - if data.RssKb != 165 { // 150+10+5 - t.Errorf("RssKb = %d, want 165", data.RssKb) - } - if data.OomScoreAdj == nil || *data.OomScoreAdj != 300 { - t.Errorf("OomScoreAdj = %v", data.OomScoreAdj) + require.NoError(t, json.Unmarshal(ev.Data, &data)) + assert.Equal(t, "chromium", data.ProcessName) + assert.Equal(t, 1234, data.Pid) + assert.Equal(t, 4823900+100+200, data.RssKb) + require.NotNil(t, data.Constraint) + assert.Equal(t, oapi.BrowserSystemOomKillEventDataConstraint("none"), *data.Constraint) + + // Mem-Info and Tasks-state fields round-trip through json correctly. + require.NotNil(t, data.MemTotalKb) + assert.Equal(t, 524288*4, *data.MemTotalKb) + require.NotNil(t, data.MemFreeKb) + assert.Equal(t, 4560*4, *data.MemFreeKb) + require.NotNil(t, data.TopTasks) + require.Len(t, *data.TopTasks, 4) + assert.Equal(t, "chromium", (*data.TopTasks)[0].Name) +} + +func TestMonitorShutsDownOnContextCancel(t *testing.T) { + es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: 4}) + require.NoError(t, err) + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + + src := newStubKmsgSource() + mon := New(es, logger, withKmsgSource(src)) + + ctx, cancel := context.WithCancel(context.Background()) + require.NoError(t, mon.Start(ctx)) + + cancel() + + done := make(chan struct{}) + go func() { + mon.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("monitor did not shut down within 2s of context cancellation") } } From ec172f9553a0795b7893f20075d7131fc4e4db5b Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Tue, 26 May 2026 19:34:59 -0700 Subject: [PATCH 07/13] shim conf --- .../supervisor/services/supervisord-shim.conf | 6 ++++++ .../image/supervisor/services/supervisord-shim.conf | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/images/chromium-headful/supervisor/services/supervisord-shim.conf b/images/chromium-headful/supervisor/services/supervisord-shim.conf index 2239b11e..5bc94034 100644 --- a/images/chromium-headful/supervisor/services/supervisord-shim.conf +++ b/images/chromium-headful/supervisor/services/supervisord-shim.conf @@ -7,5 +7,11 @@ events=PROCESS_STATE_EXITED,PROCESS_STATE_FATAL buffer_size=100 autostart=true autorestart=true +; Effectively infinite restart attempts. Supervisord does not emit +; events about its own eventlisteners, so if the shim ever enters FATAL +; state we lose all service_crashed telemetry with no signal. The shim +; is tiny and side-effect-free; a transient failure should never reach +; the default startretries=3 cap. +startretries=999999 stderr_logfile=/var/log/supervisord/supervisord-shim ; stdout is the eventlistener protocol channel; do not redirect. diff --git a/images/chromium-headless/image/supervisor/services/supervisord-shim.conf b/images/chromium-headless/image/supervisor/services/supervisord-shim.conf index 2239b11e..5bc94034 100644 --- a/images/chromium-headless/image/supervisor/services/supervisord-shim.conf +++ b/images/chromium-headless/image/supervisor/services/supervisord-shim.conf @@ -7,5 +7,11 @@ events=PROCESS_STATE_EXITED,PROCESS_STATE_FATAL buffer_size=100 autostart=true autorestart=true +; Effectively infinite restart attempts. Supervisord does not emit +; events about its own eventlisteners, so if the shim ever enters FATAL +; state we lose all service_crashed telemetry with no signal. The shim +; is tiny and side-effect-free; a transient failure should never reach +; the default startretries=3 cap. +startretries=999999 stderr_logfile=/var/log/supervisord/supervisord-shim ; stdout is the eventlistener protocol channel; do not redirect. From c7d70f78f3bf064bae889c42a752aa4baf03cd09 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Wed, 27 May 2026 09:05:17 -0700 Subject: [PATCH 08/13] vibe code --- server/lib/oapi/oapi.go | 696 ++++++++++++++++--------------- server/lib/sysmon/kmsg.go | 37 +- server/lib/sysmon/kmsg_test.go | 56 ++- server/lib/sysmon/sysmon.go | 8 + server/lib/sysmon/sysmon_test.go | 7 + server/openapi.yaml | 13 + 6 files changed, 466 insertions(+), 351 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index a5c05701..a0a6e5e2 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -2348,6 +2348,12 @@ type BrowserSystemOomKillEventData struct { // TopTasks Top processes by resident-set-size at the moment of the kill, sorted descending. Sourced from the kernel's `Tasks state` table. Empty if the kernel did not emit the table. Capped at 5 entries to bound payload size. TopTasks *[]BrowserSystemOomKillTask `json:"top_tasks,omitempty"` + + // TriggerPid PID of the triggering process. Absent if the kernel did not emit the standard `CPU: N PID: N Comm:` header line. + TriggerPid *int `json:"trigger_pid,omitempty"` + + // TriggerProcessName Comm of the process whose allocation request caused the kernel to invoke the OOM-killer. Often the same as `process_name` (the kernel killed the requester) but can differ when the kernel chose a different victim. Max 15 chars, truncated by the kernel. + TriggerProcessName *string `json:"trigger_process_name,omitempty"` } // BrowserSystemOomKillEventDataConstraint Why the kernel decided to OOM-kill. `none` means global memory exhaustion; `memcg` means a cgroup memory limit was hit; `cpuset` / `memory_policy` are NUMA/policy-driven kills. Absent on kernels older than 5.0 which did not emit the structured `oom-kill:` line. @@ -18142,350 +18148,352 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AQAA//8NYU7Tkq4BAA==", + "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", + "D4KDS0cknizd6P83AAD//5toftdtsAEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/lib/sysmon/kmsg.go b/server/lib/sysmon/kmsg.go index 585c787d..1e6156fd 100644 --- a/server/lib/sysmon/kmsg.go +++ b/server/lib/sysmon/kmsg.go @@ -56,6 +56,15 @@ type OomInstance struct { // TopTasks is up to topTasksN processes from the Tasks state table, // sorted by RSS descending. Nil if the kernel did not emit the table. TopTasks []TaskMemSnapshot + // TriggerProcessName is the comm of the process whose allocation + // failed and caused the OOM-killer to run. Captured from the prefix + // of the "invoked oom-killer:" line. Often equal to ProcessName but + // can differ when the kernel selected a different victim. + TriggerProcessName string + // TriggerPid is the PID of the triggering process, captured from + // the standard "CPU: N PID: N Comm: ..." header line. Zero if the + // kernel did not emit that header. + TriggerPid int // TimeOfDeath is the timestamp of the closing "Killed process" line // as reported by the kmsg envelope. TimeOfDeath time.Time @@ -80,7 +89,15 @@ type KmsgMessage struct { } var ( - oomStartRe = regexp.MustCompile(`invoked oom-killer:`) + // Opening line. Captures the triggering process's comm (the prefix). + // Example: `chromium invoked oom-killer: gfp_mask=0x100cca, order=0, oom_score_adj=0`. + // Comm can contain spaces (e.g. `kworker u4:1`), so the lazy match + // keeps the entire prefix up to the literal `invoked oom-killer:`. + oomStartRe = regexp.MustCompile(`^(.+?)\s+invoked oom-killer:`) + // Standard kernel printk header that immediately follows the opening + // line. Source of the triggering PID. Example: + // `CPU: 2 PID: 1234 Comm: chromium Not tainted 5.15.0-1-amd64 #1` + oomTriggerPidRe = regexp.MustCompile(`^CPU:\s+\d+\s+PID:\s+(\d+)\s+Comm:`) // Modern (Linux >= 5.0) structured constraint+task line. // Example: // oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/,task=chromium,pid=1234,uid=0 @@ -123,11 +140,14 @@ type oomScanner struct { // line. All other inputs return nil; the scanner accumulates partial // state internally. func (s *oomScanner) feed(body string, ts time.Time) *OomInstance { - if oomStartRe.MatchString(body) { + if m := oomStartRe.FindStringSubmatch(body); m != nil { // New section. If a previous section is still pending, the // kernel either failed to emit the closing line or the kmsg // ring buffer dropped it; abandon and start fresh. - s.pending = &OomInstance{TimeOfDeath: ts} + s.pending = &OomInstance{ + TimeOfDeath: ts, + TriggerProcessName: m[1], + } s.noiseBuf = 0 return nil } @@ -152,6 +172,17 @@ func (s *oomScanner) feed(body string, ts time.Time) *OomInstance { // Each recognised line resets the noise watchdog; only unparseable // intermediate lines erode the budget. matched := false + if m := oomTriggerPidRe.FindStringSubmatch(body); m != nil { + // Only set on first match — a kernel oops can include multiple + // CPU/PID lines in stack traces and we want the one that opened + // the section, which is always emitted first. + if s.pending.TriggerPid == 0 { + if n, err := strconv.Atoi(m[1]); err == nil { + s.pending.TriggerPid = n + } + } + matched = true + } if m := oomConstraintRe.FindStringSubmatch(body); m != nil { s.pending.Constraint = constraintFromKernel(m[1]) matched = true diff --git a/server/lib/sysmon/kmsg_test.go b/server/lib/sysmon/kmsg_test.go index accf206f..548db417 100644 --- a/server/lib/sysmon/kmsg_test.go +++ b/server/lib/sysmon/kmsg_test.go @@ -64,6 +64,11 @@ func TestOomScannerCanonicalDump(t *testing.T) { assert.Equal(t, 4823900+100+200, oom.RssKb) assert.Equal(t, "none", oom.Constraint) + // Trigger comes from the opening line + the CPU/PID header. In this + // canonical case the trigger and the victim are the same process. + assert.Equal(t, "chromium", oom.TriggerProcessName) + assert.Equal(t, 1234, oom.TriggerPid) + // 524288 pages * 4 KiB = 2 GiB total assert.Equal(t, 524288*4, oom.MemTotalKb) // 4560 pages * 4 KiB = ~17.8 MiB free @@ -127,12 +132,15 @@ func TestOomScannerTasksTableCappedAtTopN(t *testing.T) { } func TestOomScannerCommWithInternalSpace(t *testing.T) { + // Kernel comms with internal spaces (e.g. kworker threads) must + // survive both the start-line capture and the killed-line capture. var s oomScanner - s.feed("invoked oom-killer:", time.Now()) + s.feed(`kworker u4:1 invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, time.Now()) got := s.feed(`Out of memory: Killed process 42 (kworker u4:1) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) require.NotNil(t, got) assert.Equal(t, "kworker u4:1", got.ProcessName) assert.Equal(t, 42, got.Pid) + assert.Equal(t, "kworker u4:1", got.TriggerProcessName) } func TestOomScannerIgnoresPreambleWhenIdle(t *testing.T) { @@ -157,16 +165,56 @@ func TestOomScannerSecondStartAbandonsFirst(t *testing.T) { // If a section never completes and a new one starts, the new section // must not inherit state from the abandoned one. var s oomScanner - s.feed("invoked oom-killer:", time.Now()) + s.feed(`stale-proc invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, time.Now()) + s.feed(`CPU: 1 PID: 999 Comm: stale-proc`, time.Now()) s.feed("oom-kill:constraint=CONSTRAINT_MEMCG,task=stale,pid=1,uid=0", time.Now()) s.feed("[ 1] 0 1 100 900 1024 0 0 stale", time.Now()) - s.feed("invoked oom-killer:", time.Now()) + s.feed(`fresh-proc invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, time.Now()) got := s.feed(`Out of memory: Killed process 7 (real) total-vm:0kB, anon-rss:10kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) require.NotNil(t, got) assert.Equal(t, "real", got.ProcessName) assert.Equal(t, 7, got.Pid) assert.Empty(t, got.Constraint, "stale section's constraint must not leak") assert.Empty(t, got.TopTasks, "stale section's tasks must not leak") + assert.Equal(t, "fresh-proc", got.TriggerProcessName, "trigger from abandoned section must not leak") + assert.Zero(t, got.TriggerPid, "trigger PID from abandoned section must not leak") +} + +func TestOomScannerTriggerDiffersFromKilled(t *testing.T) { + // A chrome renderer allocates and trips the OOM-killer; the kernel + // chooses mutter (the largest non-essential victim) to kill. The + // event must surface both the trigger AND the killed process so the + // customer can distinguish "the process that consumed memory" from + // "the process the kernel decided to sacrifice". + dump := []string{ + `chromium-render invoked oom-killer: gfp_mask=0x100cca, order=0, oom_score_adj=0`, + `CPU: 2 PID: 9999 Comm: chromium-render`, + `Mem-Info:`, + `oom-kill:constraint=CONSTRAINT_NONE,task=mutter,pid=5678,uid=0`, + `Out of memory: Killed process 5678 (mutter) total-vm:1234kB, anon-rss:50kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, + } + var s oomScanner + got := feedAll(&s, dump, time.Now()) + require.Len(t, got, 1) + assert.Equal(t, "mutter", got[0].ProcessName) + assert.Equal(t, 5678, got[0].Pid) + assert.Equal(t, "chromium-render", got[0].TriggerProcessName) + assert.Equal(t, 9999, got[0].TriggerPid) +} + +func TestOomScannerTriggerHeaderAbsentLeavesPidZero(t *testing.T) { + // On kernels (or kmsg-drop scenarios) where the CPU/PID header is + // missing, the trigger NAME still comes from the opening line, but + // the trigger PID stays zero — the publisher then omits it. + dump := []string{ + `firefox invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, + `Out of memory: Killed process 42 (firefox) total-vm:0kB, anon-rss:10kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, + } + var s oomScanner + got := feedAll(&s, dump, time.Now()) + require.Len(t, got, 1) + assert.Equal(t, "firefox", got[0].TriggerProcessName) + assert.Zero(t, got[0].TriggerPid) } func TestOomScannerSequentialKills(t *testing.T) { @@ -192,7 +240,7 @@ func TestOomScannerSequentialKills(t *testing.T) { func TestOomScannerNoiseWatchdogReleasesStuckSection(t *testing.T) { var s oomScanner - s.feed("invoked oom-killer:", time.Now()) + s.feed(`x invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, time.Now()) for i := 0; i < oomScannerWatchdog+10; i++ { s.feed("filler line that matches no pattern", time.Now()) } diff --git a/server/lib/sysmon/sysmon.go b/server/lib/sysmon/sysmon.go index 14a8517b..26784fc1 100644 --- a/server/lib/sysmon/sysmon.go +++ b/server/lib/sysmon/sysmon.go @@ -145,6 +145,14 @@ func (m *Monitor) publishOomKill(oom OomInstance) { } data.TopTasks = &tasks } + if oom.TriggerProcessName != "" { + v := oom.TriggerProcessName + data.TriggerProcessName = &v + } + if oom.TriggerPid > 0 { + v := oom.TriggerPid + data.TriggerPid = &v + } payload, err := json.Marshal(data) if err != nil { diff --git a/server/lib/sysmon/sysmon_test.go b/server/lib/sysmon/sysmon_test.go index 8316c153..3393880e 100644 --- a/server/lib/sysmon/sysmon_test.go +++ b/server/lib/sysmon/sysmon_test.go @@ -94,6 +94,13 @@ func TestMonitorPublishesOomKillEnd2End(t *testing.T) { require.NotNil(t, data.TopTasks) require.Len(t, *data.TopTasks, 4) assert.Equal(t, "chromium", (*data.TopTasks)[0].Name) + + // Trigger fields round-trip too. In the canonical dump the trigger + // and the victim are the same process. + require.NotNil(t, data.TriggerProcessName) + assert.Equal(t, "chromium", *data.TriggerProcessName) + require.NotNil(t, data.TriggerPid) + assert.Equal(t, 1234, *data.TriggerPid) } func TestMonitorShutsDownOnContextCancel(t *testing.T) { diff --git a/server/openapi.yaml b/server/openapi.yaml index 249f455c..fa1200be 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -2703,6 +2703,19 @@ components: rss_kb: type: integer description: Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and shmem-rss). This is the physical memory the process was using at the time of the kill. + trigger_process_name: + type: string + description: > + Comm of the process whose allocation request caused the + kernel to invoke the OOM-killer. Often the same as + `process_name` (the kernel killed the requester) but can + differ when the kernel chose a different victim. Max 15 + chars, truncated by the kernel. + trigger_pid: + type: integer + description: > + PID of the triggering process. Absent if the kernel did + not emit the standard `CPU: N PID: N Comm:` header line. constraint: type: string description: > From faa2e9c1f7b553e14b975b1a2fd9cf745f6fac36 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Wed, 27 May 2026 10:21:17 -0700 Subject: [PATCH 09/13] self review --- server/cmd/supervisord-shim/main.go | 103 ++++++++++------------- server/cmd/supervisord-shim/main_test.go | 72 +++++++++++++--- server/lib/sysmon/kmsg_test.go | 22 ++--- server/lib/sysmon/sysmon.go | 22 ++++- server/lib/sysmon/sysmon_test.go | 38 +++++++++ 5 files changed, 167 insertions(+), 90 deletions(-) diff --git a/server/cmd/supervisord-shim/main.go b/server/cmd/supervisord-shim/main.go index 0638d5f5..4445c228 100644 --- a/server/cmd/supervisord-shim/main.go +++ b/server/cmd/supervisord-shim/main.go @@ -40,6 +40,8 @@ import ( "strconv" "strings" "time" + + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) const ( @@ -157,42 +159,21 @@ func parseFields(s string) map[string]string { return out } -// telemetryEventBody mirrors oapi.TelemetryEvent but is duplicated here -// so the shim does not pull in the entire server module — keeps the -// binary tiny. Field names track openapi.yaml by convention. -type telemetryEventBody struct { - Type string `json:"type"` - Category string `json:"category"` - Source telemetryEventSource `json:"source"` - Data serviceCrashedPayload `json:"data"` -} - -type telemetryEventSource struct { - Kind string `json:"kind"` - Event string `json:"event"` -} - -type serviceCrashedPayload struct { - ServiceName string `json:"service_name"` - Phase string `json:"phase"` - Pid *int `json:"pid,omitempty"` -} - -// phaseFromSupervisordState maps the process manager's pre-exit state to -// the neutral lifecycle phase exposed in the public event schema. The -// goal is to keep the supervisord vocabulary out of the API contract. -func phaseFromSupervisordState(fromState string) string { +// phaseForExited maps the supervisord state a process exited from to the +// public lifecycle phase. EXITED in supervisord always originates from +// RUNNING (post-startsecs); STARTING-during-startsecs-violation routes +// through BACKOFF→FATAL, not EXITED. We still defend against STARTING +// here in case a future supervisord version changes the state machine, +// and we treat anything else as "unknown" so the caller logs and skips +// rather than inventing a phase. +func phaseForExited(fromState string) (oapi.BrowserServiceCrashedEventDataPhase, bool) { switch fromState { case "RUNNING": - return "running" + return oapi.BrowserServiceCrashedEventDataPhaseRunning, true case "STARTING": - return "startup" - case "BACKOFF": - // PROCESS_STATE_FATAL transitions out of BACKOFF after the - // process manager exhausts its restart attempts. - return "gave_up" + return oapi.BrowserServiceCrashedEventDataPhaseStartup, true default: - return "" + return "", false } } @@ -206,50 +187,56 @@ func isCrashEvent(eventName string) bool { // mapEvent decides whether to publish and constructs the event payload. // Returns ok=false for events we deliberately skip (intentional stops, // non-crash event types, or unknown lifecycle transitions). -func mapEvent(header, payload map[string]string) (telemetryEventBody, bool) { - eventName := header["eventname"] - switch eventName { +func mapEvent(header, payload map[string]string) (oapi.PublishEventRequest, bool) { + var phase oapi.BrowserServiceCrashedEventDataPhase + switch header["eventname"] { case "PROCESS_STATE_EXITED": // expected=0 means the exit was not in `exitcodes` — i.e. a // crash. expected=1 means clean shutdown (operator-initiated // stop, or a configured exit code). Skip the latter. if payload["expected"] != "0" { - return telemetryEventBody{}, false + return oapi.PublishEventRequest{}, false } + p, ok := phaseForExited(payload["from_state"]) + if !ok { + return oapi.PublishEventRequest{}, false + } + phase = p case "PROCESS_STATE_FATAL": - // FATAL: the process manager exhausted startretries. Always a - // crash from the user's perspective. + // FATAL is reached exclusively by the BACKOFF→FATAL edge after + // supervisord exhausts startretries. The from_state is always + // BACKOFF here, and the semantic is "gave up trying to start". + phase = oapi.BrowserServiceCrashedEventDataPhaseGaveUp default: - return telemetryEventBody{}, false + return oapi.PublishEventRequest{}, false } name := payload["processname"] if name == "" { - return telemetryEventBody{}, false - } - phase := phaseFromSupervisordState(payload["from_state"]) - if phase == "" { - return telemetryEventBody{}, false + return oapi.PublishEventRequest{}, false } - body := telemetryEventBody{ - Type: "service_crashed", - Category: "system", - Source: telemetryEventSource{ - Kind: "local_process", - Event: "service.crashed", - }, - Data: serviceCrashedPayload{ - ServiceName: name, - Phase: phase, - }, + data := oapi.BrowserServiceCrashedEventData{ + ServiceName: name, + Phase: phase, } if pidStr := payload["pid"]; pidStr != "" { if pid, err := strconv.Atoi(pidStr); err == nil { - body.Data.Pid = &pid + data.Pid = &pid } } - return body, true + + category := oapi.PublishEventRequestCategory(oapi.TelemetryEventCategorySystem) + sourceEvent := "service.crashed" + return oapi.PublishEventRequest{ + Type: string(oapi.ServiceCrashed), + Category: &category, + Source: &oapi.BrowserEventSource{ + Kind: oapi.LocalProcess, + Event: &sourceEvent, + }, + Data: data, + }, true } type publisher struct { @@ -257,7 +244,7 @@ type publisher struct { client *http.Client } -func (p *publisher) publish(ctx context.Context, body telemetryEventBody) error { +func (p *publisher) publish(ctx context.Context, body oapi.PublishEventRequest) error { buf, err := json.Marshal(body) if err != nil { return fmt.Errorf("marshal: %w", err) diff --git a/server/cmd/supervisord-shim/main_test.go b/server/cmd/supervisord-shim/main_test.go index b03977cc..35a5afd3 100644 --- a/server/cmd/supervisord-shim/main_test.go +++ b/server/cmd/supervisord-shim/main_test.go @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) func TestWriteResultOKHasNoTrailingNewline(t *testing.T) { @@ -47,6 +49,17 @@ func TestReadEvent(t *testing.T) { assert.Equal(t, "0", pl["expected"]) } +// crashedData unwraps the BrowserServiceCrashedEventData payload from a +// mapped PublishEventRequest. The shim builds the request with Data set +// to a concrete struct (not interface{}); this helper keeps the test +// assertions short. +func crashedData(t *testing.T, body oapi.PublishEventRequest) oapi.BrowserServiceCrashedEventData { + t.Helper() + data, ok := body.Data.(oapi.BrowserServiceCrashedEventData) + require.True(t, ok, "Data is %T, want BrowserServiceCrashedEventData", body.Data) + return data +} + func TestMapEventExitedUnexpectedFromRunning(t *testing.T) { body, ok := mapEvent( map[string]string{"eventname": "PROCESS_STATE_EXITED"}, @@ -58,14 +71,19 @@ func TestMapEventExitedUnexpectedFromRunning(t *testing.T) { }, ) require.True(t, ok) - assert.Equal(t, "service_crashed", body.Type) - assert.Equal(t, "system", body.Category) - assert.Equal(t, "local_process", body.Source.Kind) - assert.Equal(t, "service.crashed", body.Source.Event) - assert.Equal(t, "mutter", body.Data.ServiceName) - assert.Equal(t, "running", body.Data.Phase) - require.NotNil(t, body.Data.Pid) - assert.Equal(t, 1234, *body.Data.Pid) + assert.Equal(t, string(oapi.ServiceCrashed), body.Type) + require.NotNil(t, body.Category) + assert.Equal(t, oapi.PublishEventRequestCategory("system"), *body.Category) + require.NotNil(t, body.Source) + assert.Equal(t, oapi.LocalProcess, body.Source.Kind) + require.NotNil(t, body.Source.Event) + assert.Equal(t, "service.crashed", *body.Source.Event) + + data := crashedData(t, body) + assert.Equal(t, "mutter", data.ServiceName) + assert.Equal(t, oapi.BrowserServiceCrashedEventDataPhaseRunning, data.Phase) + require.NotNil(t, data.Pid) + assert.Equal(t, 1234, *data.Pid) } func TestMapEventExitedUnexpectedFromStarting(t *testing.T) { @@ -82,7 +100,7 @@ func TestMapEventExitedUnexpectedFromStarting(t *testing.T) { }, ) require.True(t, ok) - assert.Equal(t, "startup", body.Data.Phase) + assert.Equal(t, oapi.BrowserServiceCrashedEventDataPhaseStartup, crashedData(t, body).Phase) } func TestMapEventExitedExpectedSkipped(t *testing.T) { @@ -98,6 +116,23 @@ func TestMapEventExitedExpectedSkipped(t *testing.T) { assert.False(t, ok, "expected=1 (clean exit) must not produce an event") } +func TestMapEventExitedFromBackoffSkipped(t *testing.T) { + // supervisord's state machine does not normally produce EXITED out + // of BACKOFF — the BACKOFF→FATAL edge fires instead once + // startretries is exhausted. If a future supervisord version routes + // it differently, we must not silently invent a phase: skip and let + // the caller log. + _, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_EXITED"}, + map[string]string{ + "processname": "chromium", + "from_state": "BACKOFF", + "expected": "0", + }, + ) + assert.False(t, ok) +} + func TestMapEventFatalFromBackoff(t *testing.T) { body, ok := mapEvent( map[string]string{"eventname": "PROCESS_STATE_FATAL"}, @@ -107,8 +142,23 @@ func TestMapEventFatalFromBackoff(t *testing.T) { }, ) require.True(t, ok) - assert.Equal(t, "gave_up", body.Data.Phase) - assert.Nil(t, body.Data.Pid, "FATAL transitions do not carry a live PID") + data := crashedData(t, body) + assert.Equal(t, oapi.BrowserServiceCrashedEventDataPhaseGaveUp, data.Phase) + assert.Nil(t, data.Pid, "FATAL transitions do not carry a live PID") +} + +func TestMapEventFatalIgnoresFromState(t *testing.T) { + // FATAL is reached exclusively via the BACKOFF→FATAL edge per + // supervisord docs, so the from_state lookup is intentionally not + // consulted for FATAL events. This test pins that behaviour so a + // future refactor doesn't reintroduce a silent drop if supervisord + // ever omits from_state. + body, ok := mapEvent( + map[string]string{"eventname": "PROCESS_STATE_FATAL"}, + map[string]string{"processname": "chromium"}, + ) + require.True(t, ok) + assert.Equal(t, oapi.BrowserServiceCrashedEventDataPhaseGaveUp, crashedData(t, body).Phase) } func TestMapEventUnrelatedSkipped(t *testing.T) { diff --git a/server/lib/sysmon/kmsg_test.go b/server/lib/sysmon/kmsg_test.go index 548db417..c9cc9979 100644 --- a/server/lib/sysmon/kmsg_test.go +++ b/server/lib/sysmon/kmsg_test.go @@ -1,6 +1,7 @@ package sysmon import ( + "strconv" "testing" "time" @@ -113,8 +114,9 @@ func TestOomScannerTasksTableCappedAtTopN(t *testing.T) { dump := []string{`chromium invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`} rssVals := []int{100, 900, 50, 800, 700, 200, 600, 300, 400, 500} // 10 procs for i, rss := range rssVals { + pid := strconv.Itoa(i + 1) dump = append(dump, - "[ "+itoa(i+1)+"] 0 "+itoa(i+1)+" 1000 "+itoa(rss)+" 1024 0 0 proc"+itoa(i+1)) + "[ "+pid+"] 0 "+pid+" 1000 "+strconv.Itoa(rss)+" 1024 0 0 proc"+pid) } dump = append(dump, `Out of memory: Killed process 1 (proc1) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, @@ -255,8 +257,9 @@ func TestOomScannerRecognisedLinesDoNotErodeWatchdog(t *testing.T) { // watchdog — recognised lines are productive parsing, not noise. dump := []string{`chromium invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`} for i := 0; i < oomScannerWatchdog+100; i++ { + pid := strconv.Itoa(i) dump = append(dump, - "[ "+itoa(i)+"] 0 "+itoa(i)+" 1234 567 45056 0 0 proc"+itoa(i)) + "[ "+pid+"] 0 "+pid+" 1234 567 45056 0 0 proc"+pid) } dump = append(dump, `Out of memory: Killed process 1 (x) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, @@ -280,18 +283,3 @@ func TestConstraintFromKernel(t *testing.T) { } } -// itoa is a tiny test helper so the fixture builders don't pull in -// strconv noise. Bounded to non-negative ints. -func itoa(n int) string { - if n == 0 { - return "0" - } - var b [20]byte - i := len(b) - for n > 0 { - i-- - b[i] = byte('0' + n%10) - n /= 10 - } - return string(b[i:]) -} diff --git a/server/lib/sysmon/sysmon.go b/server/lib/sysmon/sysmon.go index 26784fc1..a63f29e8 100644 --- a/server/lib/sysmon/sysmon.go +++ b/server/lib/sysmon/sysmon.go @@ -96,10 +96,17 @@ func (m *Monitor) Wait() { m.wg.Wait() } func (m *Monitor) runOomLoop(ctx context.Context) { src := m.kmsgSource // Closing the source unblocks any read in Messages() so the range - // terminates cleanly on shutdown. + // terminates cleanly on shutdown. The done channel lets the + // watcher exit if the source closes on its own (e.g. /dev/kmsg fd + // dropped) so we don't leak the goroutine past loop exit. + done := make(chan struct{}) + defer close(done) go func() { - <-ctx.Done() - _ = src.Close() + select { + case <-ctx.Done(): + _ = src.Close() + case <-done: + } }() m.logger.Info("sysmon: kmsg OOM reader started") @@ -124,7 +131,14 @@ func (m *Monitor) publishOomKill(oom OomInstance) { } if oom.Constraint != "" { c := oapi.BrowserSystemOomKillEventDataConstraint(oom.Constraint) - data.Constraint = &c + // Drop unknown constraint values from the payload rather than + // emitting a non-enum string that SDKs may reject. The raw + // kernel label still reaches structured logs below. + if c.Valid() { + data.Constraint = &c + } else { + m.logger.Warn("sysmon: unknown OOM constraint, omitting from payload", "constraint", oom.Constraint) + } } if oom.MemTotalKb > 0 { v := oom.MemTotalKb diff --git a/server/lib/sysmon/sysmon_test.go b/server/lib/sysmon/sysmon_test.go index 3393880e..94589beb 100644 --- a/server/lib/sysmon/sysmon_test.go +++ b/server/lib/sysmon/sysmon_test.go @@ -103,6 +103,44 @@ func TestMonitorPublishesOomKillEnd2End(t *testing.T) { assert.Equal(t, 1234, *data.TriggerPid) } +func TestMonitorOmitsUnknownConstraint(t *testing.T) { + // constraintFromKernel passes through unknown labels lowercased so + // they reach logs, but they would violate the openapi enum if + // emitted on the wire. The publisher must drop them rather than + // produce a non-enum value that SDKs may reject. + dump := []string{ + `x invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, + `oom-kill:constraint=CONSTRAINT_FUTURE_THING,task=x,pid=1,uid=0`, + `Out of memory: Killed process 1 (x) total-vm:0kB, anon-rss:1kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, + } + + es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: 4}) + require.NoError(t, err) + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + + src := newStubKmsgSource() + mon := New(es, logger, withKmsgSource(src)) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + require.NoError(t, mon.Start(ctx)) + + ts := time.Unix(1_700_000_000, 0) + for _, line := range dump { + src.send(line, ts) + } + + reader := es.NewReader(0) + readCtx, readCancel := context.WithTimeout(ctx, 2*time.Second) + defer readCancel() + res, err := reader.Read(readCtx) + require.NoError(t, err) + + var data oapi.BrowserSystemOomKillEventData + require.NoError(t, json.Unmarshal(res.Envelope.Event.Data, &data)) + assert.Nil(t, data.Constraint, "unknown constraint must be omitted from the payload") +} + func TestMonitorShutsDownOnContextCancel(t *testing.T) { es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: 4}) require.NoError(t, err) From da5fe8d1280a605c51605d0827a820548d36a4e5 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Wed, 27 May 2026 11:44:52 -0700 Subject: [PATCH 10/13] testing finds --- server/cmd/supervisord-shim/main_test.go | 2 +- server/lib/sysmon/kmsg.go | 37 +++++++++--- server/lib/sysmon/kmsg_test.go | 77 ++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 14 deletions(-) diff --git a/server/cmd/supervisord-shim/main_test.go b/server/cmd/supervisord-shim/main_test.go index 35a5afd3..dc48df1f 100644 --- a/server/cmd/supervisord-shim/main_test.go +++ b/server/cmd/supervisord-shim/main_test.go @@ -150,7 +150,7 @@ func TestMapEventFatalFromBackoff(t *testing.T) { func TestMapEventFatalIgnoresFromState(t *testing.T) { // FATAL is reached exclusively via the BACKOFF→FATAL edge per // supervisord docs, so the from_state lookup is intentionally not - // consulted for FATAL events. This test pins that behaviour so a + // consulted for FATAL events. This test pins that behavior so a // future refactor doesn't reintroduce a silent drop if supervisord // ever omits from_state. body, ok := mapEvent( diff --git a/server/lib/sysmon/kmsg.go b/server/lib/sysmon/kmsg.go index 1e6156fd..b45d831a 100644 --- a/server/lib/sysmon/kmsg.go +++ b/server/lib/sysmon/kmsg.go @@ -1,3 +1,21 @@ +// kmsg.go parses the kernel OOM-dump text inside /dev/kmsg messages. +// The wire envelope is handled by euank/go-kmsg-parser; body text +// parsing is ours. +// +// Format stability across kernel versions: +// - "Killed process N (name) anon-rss:... file-rss:... shmem-rss:..." +// is unchanged since 2.6.x. +// - "oom-kill:constraint=CONSTRAINT_X" appeared in 5.0 and is +// stable since. Absent on older kernels; Constraint is omitted. +// - "N pages RAM" and "free:N free_pcp:N free_cma:N" are stable. +// - Tasks-state row gained rss_anon/rss_file/rss_shmem in 5.14 +// (9-col → 12-col). We anchor on bracketed pid + rss-as-5th-col +// + trailing-token name so both layouts parse. Production is +// Linux 6.12; the older layout is kept for dev environments. +// +// On format breakage the failure mode is graceful: missing fields +// are omitted from the published event, and oomScannerWatchdog +// abandons a stuck section without leaking memory. package sysmon import ( @@ -22,7 +40,7 @@ const topTasksN = 5 // oomScannerWatchdog bounds the number of UNRECOGNIZED kmsg messages we // will tolerate inside a single OOM section before abandoning it. -// Recognised lines (Mem-Info, Tasks state, constraint, killed) don't +// Recognized lines (Mem-Info, Tasks state, constraint, killed) don't // count toward the budget, so the watchdog only trips when the section // diverges from the expected kernel format. A busy VM can emit several // hundred Tasks state rows during a single dump; this budget leaves @@ -117,11 +135,12 @@ var ( // lines like `Node 0 DMA free:11264kB boost:0kB`, which carry kB // units rather than raw page counts. oomFreePagesRe = regexp.MustCompile(`(?:^|\s)free:(\d+)\s+free_pcp:`) - // Tasks state row. Columns (post-bracket): uid tgid total_vm rss - // pgtables_bytes swapents oom_score_adj name. RSS is in pages. - // Example: - // [ 1234] 1000 1234 1308611 1205975 9678848 0 0 chromium - oomTaskEntryRe = regexp.MustCompile(`^\[\s*(\d+)\]\s+\d+\s+\d+\s+\d+\s+(\d+)\s+\d+\s+\d+\s+-?\d+\s+(\S.*?)\s*$`) + // Tasks state row. The column count varies across kernel versions + // (see the file header) so we anchor on the three invariants: + // bracketed pid, rss as the 5th numeric column, and name as the + // trailing token. Example (Linux 5.14+): + // [ 1234] 1000 1234 1308611 1205975 1205675 200 100 9678848 0 0 chromium + oomTaskEntryRe = regexp.MustCompile(`^\[\s*(\d+)\]\s+\d+\s+\d+\s+\d+\s+(\d+)\s+.+\s+(\S+)\s*$`) ) // oomScanner is a state machine that turns a stream of kmsg message @@ -169,8 +188,10 @@ func (s *oomScanner) feed(body string, ts time.Time) *OomInstance { return out } - // Each recognised line resets the noise watchdog; only unparseable - // intermediate lines erode the budget. + // Only unparseable intermediate lines erode the watchdog budget; + // recognized lines are free. The budget is a per-section total + // (see oomScannerWatchdog) — it does not reset on productive + // matches. matched := false if m := oomTriggerPidRe.FindStringSubmatch(body); m != nil { // Only set on first match — a kernel oops can include multiple diff --git a/server/lib/sysmon/kmsg_test.go b/server/lib/sysmon/kmsg_test.go index c9cc9979..ae2c9715 100644 --- a/server/lib/sysmon/kmsg_test.go +++ b/server/lib/sysmon/kmsg_test.go @@ -10,9 +10,10 @@ import ( ) // canonicalOomDump is a representative slice of the kmsg lines the kernel -// emits during a global OOM kill on Linux 5.x. The Mem-Info and Tasks +// emits during a global OOM kill, using the pre-5.14 Tasks-state layout +// (9 columns, no rss_anon/rss_file/rss_shmem). The Mem-Info and Tasks // state sections are abbreviated but preserve the field layout the -// parser depends on. +// parser depends on. See modernKernelOomDump for the post-5.14 shape. var canonicalOomDump = []string{ `chromium invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0`, `CPU: 2 PID: 1234 Comm: chromium Not tainted 5.15.0-1-amd64 #1`, @@ -133,6 +134,53 @@ func TestOomScannerTasksTableCappedAtTopN(t *testing.T) { } } +// modernKernelOomDump represents the Tasks-state format emitted by +// any Linux 5.14+ kernel — including Docker Desktop's bundled +// LinuxKit (6.10 at time of writing) and production VMs (6.12). The +// kernel added rss_anon/rss_file/rss_shmem columns between rss and +// pgtables_bytes, giving 12 numeric+name columns vs. 9 in the +// pre-5.14 layout. The parser must handle both shapes. +var modernKernelOomDump = []string{ + `chromium invoked oom-killer: gfp_mask=0x100cca, order=0, oom_score_adj=0`, + `Mem-Info:`, + `Tasks state (memory values in pages):`, + `[ pid ] uid tgid total_vm rss rss_anon rss_file rss_shmem pgtables_bytes swapents oom_score_adj name`, + `[ 34512] 0 34512 379985 4730 3330 1400 0 188416 0 0 wrapper`, + `[ 34556] 0 34556 126162 23649 11063 12586 0 462848 0 0 Xvfb`, + `[ 34560] 103 34560 73393 1872 819 1053 0 110592 0 0 dbus-daemon`, + `[ 34561] 0 34561 12670355 5635 1775 3860 0 208896 0 0 chromedriver`, + `[ 36183] 1000 36183 302080 72705 71925 780 0 1208320 0 0 chromium`, + `oom-kill:constraint=CONSTRAINT_MEMCG,task=chromium,pid=36183,uid=1000`, + `Out of memory: Killed process 36183 (chromium) total-vm:1208320kB, anon-rss:287700kB, file-rss:3120kB, shmem-rss:0kB, UID:1000 pgtables:1208320kB oom_score_adj:0`, +} + +func TestOomScannerModernKernelTaskColumns(t *testing.T) { + // Regression: real Linux 5.14+ kmsg dumps have 3 extra columns + // (rss_anon, rss_file, rss_shmem) compared to the legacy layout. + // An overly-rigid regex captures those trailing numeric columns as + // part of the `name` field, producing top_tasks entries like + // "1208320 0 0 chromium" instead of "chromium". + var s oomScanner + got := feedAll(&s, modernKernelOomDump, time.Now()) + require.Len(t, got, 1) + oom := got[0] + + assert.Equal(t, "chromium", oom.ProcessName) + assert.Equal(t, 36183, oom.Pid) + assert.Equal(t, "memcg", oom.Constraint) + + require.Len(t, oom.TopTasks, 5) + // Top task by RSS is chromium (72705 pages). Name must be just + // "chromium", not the leading-pgtables-padded "1208320 ... chromium". + assert.Equal(t, "chromium", oom.TopTasks[0].Name) + assert.Equal(t, 36183, oom.TopTasks[0].Pid) + assert.Equal(t, 72705*pageSizeKB, oom.TopTasks[0].RssKb) + + for _, task := range oom.TopTasks { + assert.NotContains(t, task.Name, " ", "task name must be a single token, got %q", task.Name) + } +} + func TestOomScannerCommWithInternalSpace(t *testing.T) { // Kernel comms with internal spaces (e.g. kworker threads) must // survive both the start-line capture and the killed-line capture. @@ -252,9 +300,28 @@ func TestOomScannerNoiseWatchdogReleasesStuckSection(t *testing.T) { assert.Nil(t, got) } -func TestOomScannerRecognisedLinesDoNotErodeWatchdog(t *testing.T) { +func TestOomScannerNoiseBudgetIsTotalNotConsecutive(t *testing.T) { + // The watchdog budget is a per-section TOTAL, not "consecutive + // noise since the last recognized line". A section that interleaves + // noise with sporadic productive matches still trips once cumulative + // noise exceeds the budget. + var s oomScanner + s.feed(`chromium invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, time.Now()) + // Alternate: 1 recognized task entry, then 1001 noise lines, repeat. + // Two cycles -> 2002 noise > oomScannerWatchdog -> abandoned. + for cycle := 0; cycle < 2; cycle++ { + s.feed("[ 1] 0 1 100 200 1024 0 0 proc1", time.Now()) + for i := 0; i < oomScannerWatchdog/2+1; i++ { + s.feed("filler line that matches no pattern", time.Now()) + } + } + got := s.feed(`Out of memory: Killed process 1 (x) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) + assert.Nil(t, got, "interleaved noise must accumulate toward the watchdog total") +} + +func TestOomScannerRecognizedLinesDoNotErodeWatchdog(t *testing.T) { // A Tasks state table with hundreds of entries should not trip the - // watchdog — recognised lines are productive parsing, not noise. + // watchdog — recognized lines are productive parsing, not noise. dump := []string{`chromium invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`} for i := 0; i < oomScannerWatchdog+100; i++ { pid := strconv.Itoa(i) @@ -267,7 +334,7 @@ func TestOomScannerRecognisedLinesDoNotErodeWatchdog(t *testing.T) { var s oomScanner got := feedAll(&s, dump, time.Now()) - require.Len(t, got, 1, "watchdog must not abandon a section composed only of recognised lines") + require.Len(t, got, 1, "watchdog must not abandon a section composed only of recognized lines") } func TestConstraintFromKernel(t *testing.T) { From b7ff02a6febcb4fd5aaf84bae1ce64b697b0af83 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Wed, 27 May 2026 11:56:06 -0700 Subject: [PATCH 11/13] vibe code --- server/lib/sysmon/README.md | 206 ++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 server/lib/sysmon/README.md diff --git a/server/lib/sysmon/README.md b/server/lib/sysmon/README.md new file mode 100644 index 00000000..dee96e9a --- /dev/null +++ b/server/lib/sysmon/README.md @@ -0,0 +1,206 @@ +# sysmon + +VM-internal failure telemetry for the kernel-images browser VM. Surfaces two event types onto the existing `EventStream` → SSE / S2 pipeline: + +| Event type | Source | Owned by | +| ------------------ | ------------------------------------------ | --------------------- | +| `system_oom_kill` | Linux kernel OOM-killer via `/dev/kmsg` | `lib/sysmon` (in-process goroutine) | +| `service_crashed` | supervisord eventlistener protocol | `cmd/supervisord-shim` (separate binary, POSTs to `/telemetry/events`) | + +Both paths terminate in the same `events.EventStream` so downstream consumers (SSE clients, the S2 sink) see them like any other browser telemetry event. + +## Why two binaries + +| Concern | sysmon (in-process) | supervisord-shim (separate process) | +| --- | --- | --- | +| Why separate | n/a | supervisord's eventlistener protocol requires a separate process talking over stdin/stdout | +| Triggers | kernel OOM-killer writes to `/dev/kmsg` | supervised service exits unexpectedly or FATALs | +| Transport | direct call to `EventStream.Publish` | `POST /telemetry/events` over localhost HTTP | +| Failure mode | open of `/dev/kmsg` may fail (no CAP_SYSLOG); API logs and continues without OOM telemetry | API may be down during shim's POST; shim logs the failure, always ACKs supervisord, and the event is lost | + +## Event taxonomy + +### `system_oom_kill` + +Parsed from one kernel OOM dump in `/dev/kmsg`. Payload (see `BrowserSystemOomKillEventData` in `openapi.yaml` for the authoritative schema): + +| Field | Meaning | Absent when | +| --- | --- | --- | +| `process_name` | comm of the killed process (max 15 chars, kernel TASK_COMM_LEN limit) | never | +| `pid` | PID of the killed process | never | +| `rss_kb` | sum of anon-rss + file-rss + shmem-rss in KiB | never | +| `constraint` | `none` / `memcg` / `cpuset` / `memory_policy` | pre-Linux-5.0 kernels (no structured `oom-kill:` line) | +| `mem_total_kb` | total RAM from `N pages RAM` × 4 KiB | kernel did not emit Mem-Info (e.g. memcg OOM) | +| `mem_free_kb` | free RAM from `free:N free_pcp:N` × 4 KiB | as above | +| `top_tasks` | up to 5 processes from `Tasks state` table, sorted by RSS desc | kernel did not emit the table | +| `trigger_process_name` | comm of the process whose allocation triggered the OOM-killer | sysrq-triggered OOMs (no opener line) | +| `trigger_pid` | PID of the trigger | as above; pre-CPU/PID header kernels | + +### `service_crashed` + +Mapped from supervisord `PROCESS_STATE_EXITED` (with `expected=0`) or `PROCESS_STATE_FATAL`. Schema in `BrowserServiceCrashedEventData`: + +| Field | Meaning | +| --- | --- | +| `service_name` | supervisord program name (e.g. `chromium`, `mutter`, `kernel-images-api`) | +| `pid` | live PID at exit (omitted for `gave_up` since supervisord no longer tracks one) | +| `phase` | `startup` (died during STARTING) / `running` (crashed after reaching RUNNING) / `gave_up` (FATAL via exhausted startretries) | + +Clean stops (`supervisorctl stop`, exit codes in the configured `exitcodes` list) do **not** produce events — supervisord marks them `expected=1` and the shim skips them. + +## File layout + +| File | Concern | +| --- | --- | +| `sysmon.go` | `Monitor` lifecycle (Start/Wait), goroutine wiring, `publishOomKill` | +| `kmsg.go` | OOM-dump text parser (regex + state machine) — see file header for format compatibility notes | +| `kmsg_linux.go` | Linux-only `/dev/kmsg` open via `euank/go-kmsg-parser`, SeekEnd on start so we don't replay history on API restart | +| `kmsg_other.go` | non-Linux stub so dev machines still compile | +| `kmsg_test.go` | parser fixtures + tests (both pre-5.14 and post-5.14 Tasks-state layouts) | +| `sysmon_test.go` | end-to-end test from stub kmsg source through EventStream | + +The supervisord-shim lives at `cmd/supervisord-shim/`. Its configuration is duplicated as `supervisord-shim.conf` under both `images/chromium-headless/image/supervisor/services/` and `images/chromium-headful/supervisor/services/`. + +## How to verify locally (Docker) + +These steps reproduce the smoke matrix from PR #254. Container image is built with `cd images/chromium-headless && ./build-docker.sh`. + +```bash +# Start the container detached (the script's run-docker.sh hardcodes -it). +docker run -d --rm --name chromium-headless-test \ + --platform linux/amd64 --privileged --tmpfs /dev/shm:size=128m \ + -p 9222:9222 -p 444:10001 \ + onkernel/chromium-headless-test:latest + +# Wait for the API. +sleep 10 && curl -sf http://localhost:444/spec.json >/dev/null && echo "API up" + +# Open the SSE stream in another shell to watch events in real time. +curl -sN http://localhost:444/telemetry/stream +``` + +### service_crashed (phase=running) + +```bash +# Kill the chromium browser process the launcher actually spawned. +docker exec chromium-headless-test bash -c 'kill -KILL $(pgrep -f /opt/chrome-for-testing/chromium | head -1)' +# Expect one service_crashed event with phase=running. +``` + +### service_crashed (phase=gave_up) + +```bash +# Install a deliberately failing service. +docker exec chromium-headless-test bash -c 'cat > /etc/supervisor/conf.d/services/flaky.conf <$line" > /dev/kmsg +done' +# Expect one system_oom_kill event with constraint=none, mem_total_kb=2097152, +# top_tasks[0].name="chromium", trigger_process_name="chromium". +``` + +### system_oom_kill (real cgroup OOM) + +```bash +docker rm -f chromium-headless-test +# 512 MB cap keeps the API itself alive while letting Chrome OOM. +docker run -d --rm --name chromium-headless-test \ + --platform linux/amd64 --privileged --tmpfs /dev/shm:size=128m \ + --memory 512m --memory-swap 512m \ + -p 9222:9222 -p 444:10001 \ + onkernel/chromium-headless-test:latest + +# Run a memory hog inside. +docker exec chromium-headless-test python3 -c ' +import sys, time +chunks=[] +while True: + chunks.append(b"x"*(60*1024*1024)); sys.stdout.write(f"{len(chunks)*60}MB\n"); sys.stdout.flush(); time.sleep(0.3) +' +# Expect system_oom_kill events with constraint=memcg, and mem_total_kb / +# mem_free_kb omitted (the kernel skips the global Mem-Info dump on memcg +# OOMs). Sanity-check top_tasks names: they should be single tokens +# (`chromium`, `python3`). If they include numbers or extra columns, the +# Tasks-state regex in kmsg.go needs updating for the current kernel. +``` + +## How to verify in production (real Linux 6.x VM) + +```bash +# Spin up a browser session. +kernel browsers create +# Note the session ID, then exec into the VM. + +# Confirm sysmon is running. +kernel browsers process exec -- /bin/bash -c \ + 'tail -50 /var/log/supervisord/kernel-images-api | grep sysmon' +# Look for: "sysmon: kmsg OOM reader started" + +# Trigger an OOM (kills the highest-oom_score process; expect chromium). +kernel browsers process exec -- /bin/bash -c \ + 'echo 1 > /proc/sys/kernel/sysrq; echo f > /proc/sysrq-trigger' + +# Verify the event hit the API stream. +kernel browsers process exec -- /bin/bash -c \ + 'tail -50 /var/log/supervisord/kernel-images-api | grep "sysmon: oom kill"' + +# Clean up. +kernel browsers delete +``` + +## Known limitations + +1. **API self-crash is invisible to sysmon.** If `kernel-images-api` itself dies, the shim's POST fails (connection refused) and that event is lost. The host platform's process/VM-level monitoring is the layer that catches it. Closing the gap inside this binary would require persistent shim-side buffering and is out of scope. +2. **`process_name` is truncated to 15 chars.** This is the kernel's `TASK_COMM_LEN-1` limit, not a parser bug. `kernel-images-api` shows up as `kernel-images-a`. +3. **Page size is hard-coded to 4 KiB.** Correct on x86_64; would be wrong on ARM 16K/64K page kernels. +4. **`mem_total_kb` / `mem_free_kb` are omitted on memcg OOMs.** The kernel does not emit the global `pages RAM` / `free:N` lines when the OOM is cgroup-scoped. This is correct behavior, documented in the openapi schema. +5. **`trigger_*` fields are absent on sysrq-triggered OOMs.** The `X invoked oom-killer:` opener line is only emitted on allocation-driven kills. Real allocation OOMs always populate these fields; only the synthetic `sysrq f` test path doesn't. +6. **No de-dup between kmsg and supervisord.** If a Chrome OOM both fires kmsg (`system_oom_kill`) and causes supervisord to notice the exit (`service_crashed`), both events fire. The overlap is itself a useful signal (RAM exhaustion vs. process bug). + +## Where to look when things break + +| Symptom | First place to check | +| --- | --- | +| No `system_oom_kill` events in prod | API logs for `sysmon: kmsg OOM monitor disabled` — indicates `/dev/kmsg` open failed | +| `system_oom_kill` events have corrupt `top_tasks` names | `oomTaskEntryRe` in `kmsg.go` — kernel changed the Tasks-state column layout again | +| `system_oom_kill` events missing fields after a kernel upgrade | each `oom*Re` regex in `kmsg.go` — sections may have been renamed in the kernel | +| No `service_crashed` events | `cat /var/log/supervisord/supervisord-shim` inside the container; check for `connection refused` to the API | +| Shim looping (supervisord shows repeated spawn) | the shim should never enter FATAL because `startretries=999999`; if it does, check `/var/log/supervisord.log` for spawn errors | +| Events fire locally but don't reach downstream consumers | check the SSE / S2 pipeline (`POST /telemetry/events` → `EventStream.Publish` → SSE / S2) — that's `lib/events` territory, not sysmon | From 3c7d3c13ffa00c3ae62cc56942707b3b5e795872 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Wed, 27 May 2026 19:00:47 -0700 Subject: [PATCH 12/13] review and clean up --- server/cmd/supervisord-shim/main.go | 59 ++++++++++++----------------- server/lib/sysmon/kmsg.go | 12 ++++-- server/lib/sysmon/kmsg_linux.go | 5 ++- server/lib/sysmon/kmsg_test.go | 8 +++- server/lib/sysmon/sysmon.go | 15 ++------ 5 files changed, 47 insertions(+), 52 deletions(-) diff --git a/server/cmd/supervisord-shim/main.go b/server/cmd/supervisord-shim/main.go index 4445c228..79959520 100644 --- a/server/cmd/supervisord-shim/main.go +++ b/server/cmd/supervisord-shim/main.go @@ -31,36 +31,44 @@ import ( "bufio" "bytes" "context" - "encoding/json" "fmt" "io" "log" "net/http" "os" + "os/signal" "strconv" "strings" + "syscall" "time" oapi "github.com/kernel/kernel-images/server/lib/oapi" ) const ( - defaultTelemetryURL = "http://127.0.0.1:10001/telemetry/events" - httpTimeout = 2 * time.Second + defaultAPIBaseURL = "http://127.0.0.1:10001" + httpTimeout = 2 * time.Second ) func main() { log.SetOutput(os.Stderr) log.SetFlags(log.LstdFlags | log.Lmicroseconds) - telemetryURL := os.Getenv("KERNEL_IMAGES_TELEMETRY_URL") - if telemetryURL == "" { - telemetryURL = defaultTelemetryURL + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + go func() { + <-ctx.Done() + _ = os.Stdin.Close() + }() + + baseURL := os.Getenv("KERNEL_IMAGES_API_BASE_URL") + if baseURL == "" { + baseURL = defaultAPIBaseURL } - pub := &publisher{ - url: telemetryURL, - client: &http.Client{Timeout: httpTimeout}, + client, err := oapi.NewClientWithResponses(baseURL, oapi.WithHTTPClient(&http.Client{Timeout: httpTimeout})) + if err != nil { + log.Fatalf("init oapi client: %v", err) } in := bufio.NewReader(os.Stdin) @@ -86,7 +94,7 @@ func main() { ev, ok := mapEvent(header, payload) switch { case ok: - if perr := pub.publish(context.Background(), ev); perr != nil { + if perr := publish(ctx, client, ev); perr != nil { log.Printf("publish telemetry event: %v", perr) } case isCrashEvent(header["eventname"]): @@ -105,11 +113,8 @@ func main() { } } -// writeResultOK writes the supervisord eventlistener "RESULT" frame -// indicating success. The body is exactly "OK" (2 bytes) with NO trailing -// newline — supervisord reads exactly `len` bytes after the header -// newline, and a trailing newline would leak into the buffer and corrupt -// the subsequent READY token. +// writeResultOK ACKs a single event. See the file header for why the +// frame body has no trailing newline. func writeResultOK(out *bufio.Writer) error { if _, err := out.WriteString("RESULT 2\nOK"); err != nil { return err @@ -239,29 +244,13 @@ func mapEvent(header, payload map[string]string) (oapi.PublishEventRequest, bool }, true } -type publisher struct { - url string - client *http.Client -} - -func (p *publisher) publish(ctx context.Context, body oapi.PublishEventRequest) error { - buf, err := json.Marshal(body) - if err != nil { - return fmt.Errorf("marshal: %w", err) - } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(buf)) - if err != nil { - return fmt.Errorf("new request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - resp, err := p.client.Do(req) +func publish(ctx context.Context, client *oapi.ClientWithResponses, body oapi.PublishEventRequest) error { + resp, err := client.PublishTelemetryEventWithResponse(ctx, body) if err != nil { return err } - defer resp.Body.Close() - if resp.StatusCode >= 300 { - b, _ := io.ReadAll(resp.Body) - return fmt.Errorf("status %d: %s", resp.StatusCode, bytes.TrimSpace(b)) + if resp.StatusCode() >= 300 { + return fmt.Errorf("status %d: %s", resp.StatusCode(), bytes.TrimSpace(resp.Body)) } return nil } diff --git a/server/lib/sysmon/kmsg.go b/server/lib/sysmon/kmsg.go index b45d831a..576b6b71 100644 --- a/server/lib/sysmon/kmsg.go +++ b/server/lib/sysmon/kmsg.go @@ -136,11 +136,15 @@ var ( // units rather than raw page counts. oomFreePagesRe = regexp.MustCompile(`(?:^|\s)free:(\d+)\s+free_pcp:`) // Tasks state row. The column count varies across kernel versions - // (see the file header) so we anchor on the three invariants: - // bracketed pid, rss as the 5th numeric column, and name as the - // trailing token. Example (Linux 5.14+): + // (see the file header) so we anchor on the invariants: bracketed + // pid, rss as the 5th numeric column, oom_score_adj as the last + // numeric column (always present, possibly negative), and the + // remainder of the line as the comm. Capturing the trailing comm + // lazily (rather than as a single \S+ token) preserves names with + // internal whitespace; this matches the behavior of the kill-line + // and trigger-line parsers. Example (Linux 5.14+): // [ 1234] 1000 1234 1308611 1205975 1205675 200 100 9678848 0 0 chromium - oomTaskEntryRe = regexp.MustCompile(`^\[\s*(\d+)\]\s+\d+\s+\d+\s+\d+\s+(\d+)\s+.+\s+(\S+)\s*$`) + oomTaskEntryRe = regexp.MustCompile(`^\[\s*(\d+)\]\s+\d+\s+\d+\s+\d+\s+(\d+)\s+.+\s+-?\d+\s+(.+?)\s*$`) ) // oomScanner is a state machine that turns a stream of kmsg message diff --git a/server/lib/sysmon/kmsg_linux.go b/server/lib/sysmon/kmsg_linux.go index ed6e2b27..55e3c2dc 100644 --- a/server/lib/sysmon/kmsg_linux.go +++ b/server/lib/sysmon/kmsg_linux.go @@ -54,7 +54,10 @@ type kmsgLogger struct { } func (l kmsgLogger) Infof(format string, args ...any) { - l.logger.Info(fmt.Sprintf("sysmon/kmsg: "+format, args...)) + // The library only logs Info on graceful shutdown ("kmsg reader + // closed, shutting down"). Treat it like sysmon.go's own + // start/stop signals: useful for debugging but not Info-worthy. + l.logger.Debug(fmt.Sprintf("sysmon/kmsg: "+format, args...)) } func (l kmsgLogger) Warningf(format string, args ...any) { diff --git a/server/lib/sysmon/kmsg_test.go b/server/lib/sysmon/kmsg_test.go index ae2c9715..9f8a1893 100644 --- a/server/lib/sysmon/kmsg_test.go +++ b/server/lib/sysmon/kmsg_test.go @@ -183,14 +183,20 @@ func TestOomScannerModernKernelTaskColumns(t *testing.T) { func TestOomScannerCommWithInternalSpace(t *testing.T) { // Kernel comms with internal spaces (e.g. kworker threads) must - // survive both the start-line capture and the killed-line capture. + // survive every capture path: the start line, the killed line, and + // the Tasks state row that feeds top_tasks. var s oomScanner s.feed(`kworker u4:1 invoked oom-killer: gfp_mask=0, order=0, oom_score_adj=0`, time.Now()) + s.feed(`[ 42] 0 42 100 100 1024 0 0 kworker u4:1`, time.Now()) got := s.feed(`Out of memory: Killed process 42 (kworker u4:1) total-vm:0kB, anon-rss:0kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:0kB oom_score_adj:0`, time.Now()) require.NotNil(t, got) assert.Equal(t, "kworker u4:1", got.ProcessName) assert.Equal(t, 42, got.Pid) assert.Equal(t, "kworker u4:1", got.TriggerProcessName) + require.Len(t, got.TopTasks, 1) + assert.Equal(t, "kworker u4:1", got.TopTasks[0].Name) + assert.Equal(t, 42, got.TopTasks[0].Pid) + assert.Equal(t, 100*pageSizeKB, got.TopTasks[0].RssKb) } func TestOomScannerIgnoresPreambleWhenIdle(t *testing.T) { diff --git a/server/lib/sysmon/sysmon.go b/server/lib/sysmon/sysmon.go index a63f29e8..7775385f 100644 --- a/server/lib/sysmon/sysmon.go +++ b/server/lib/sysmon/sysmon.go @@ -41,7 +41,6 @@ type Monitor struct { wg sync.WaitGroup } -// option configures a Monitor. type option func(*Monitor) // withKmsgSource overrides the kmsg source. Test-only. @@ -87,8 +86,7 @@ func (m *Monitor) Start(ctx context.Context) error { return nil } -// Wait blocks until all goroutines launched by Start have returned. Safe -// to call from tests after cancelling the Start context. +// Wait blocks until all goroutines launched by Start have returned. func (m *Monitor) Wait() { m.wg.Wait() } // runOomLoop consumes the kmsg stream, drives the OOM state machine, and @@ -109,7 +107,7 @@ func (m *Monitor) runOomLoop(ctx context.Context) { } }() - m.logger.Info("sysmon: kmsg OOM reader started") + m.logger.Debug("sysmon: kmsg OOM reader started") var s oomScanner for msg := range src.Messages() { @@ -120,7 +118,7 @@ func (m *Monitor) runOomLoop(ctx context.Context) { m.publishOomKill(*oom) } - m.logger.Info("sysmon: kmsg OOM reader stopped") + m.logger.Debug("sysmon: kmsg OOM reader stopped") } func (m *Monitor) publishOomKill(oom OomInstance) { @@ -185,13 +183,8 @@ func (m *Monitor) publishOomKill(oom OomInstance) { Data: json.RawMessage(payload), } m.es.Publish(events.Envelope{Event: ev}) - m.logger.Info("sysmon: oom kill", + m.logger.Debug("sysmon: emitted system_oom_kill", "process", oom.ProcessName, "pid", oom.Pid, - "rss_kb", oom.RssKb, - "constraint", oom.Constraint, - "mem_total_kb", oom.MemTotalKb, - "mem_free_kb", oom.MemFreeKb, - "top_tasks", len(oom.TopTasks), ) } From 5cdbd63ba706a7087ccd87e45d092637dfa95c77 Mon Sep 17 00:00:00 2001 From: Sayan Samanta Date: Wed, 27 May 2026 19:12:07 -0700 Subject: [PATCH 13/13] fix up --- server/lib/sysmon/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/sysmon/README.md b/server/lib/sysmon/README.md index dee96e9a..fece52cf 100644 --- a/server/lib/sysmon/README.md +++ b/server/lib/sysmon/README.md @@ -83,7 +83,7 @@ curl -sN http://localhost:444/telemetry/stream ```bash # Kill the chromium browser process the launcher actually spawned. -docker exec chromium-headless-test bash -c 'kill -KILL $(pgrep -f /opt/chrome-for-testing/chromium | head -1)' +docker exec chromium-headless-test supervisorctl signal KILL chromium # Expect one service_crashed event with phase=running. ```