---
phase: "06"
plan: "02"
subsystem: observe
tags: [kernel, dmesg, observability, collect, filter, classification]
dependency_graph:
  requires: [classify_line, parse_dmesg_line, insert_marker, dmesg_mark]
  provides: [collect_dmesg, dmesg_collect]
  affects: [vmcluster_mcp/observe/dmesg.py, vmcluster_mcp/observe/__init__.py]
tech_stack:
  added: [asyncio.gather (stdlib), logging (stdlib)]
  patterns: [ISO/raw dmesg fallback, marker-based slicing, syslog level filtering, concurrent fan-out, best-effort per-node]
key_files:
  created:
    - tests/test_dmesg_collect.py
  modified:
    - vmcluster_mcp/observe/dmesg.py
    - vmcluster_mcp/observe/__init__.py
decisions:
  - Filter validation done in tool handler (not collect_dmesg) so the pure function stays simple
  - Per-node SSH failure → empty DmesgResult (not omitted), so callers always see all nodes in result dict
  - filter_level validation uses FILTER_THRESHOLDS keys imported locally to avoid circular at module load
metrics:
  duration_s: 420
  completed_date: "2026-03-23"
  tasks_completed: 2
  files_changed: 3
requirements-completed: [OBS-02, OBS-03]
---

# Phase 06 Plan 02: dmesg_collect Summary

JWT-style one-liner: **concurrent dmesg collection with ISO/raw fallback, marker-based slicing, level filtering, and classification-derived summary fields**

## What Was Built

### Task 1: collect_dmesg function

- **`vmcluster_mcp/observe/dmesg.py`** additions:
  - `LEVEL_VALUES: dict[str, int]` — syslog level → numeric (0=emerg … 7=debug)
  - `FILTER_THRESHOLDS: dict[str, int]` — preset → max LEVEL_VALUES threshold (`all=7`, `warn+=4`, `err+=3`)
  - `collect_dmesg(ssh_manager, ip, port, user, key_path, since_marker=None, filter_level="all") -> DmesgResult`:
    - Attempts `dmesg -r --time-format=iso` first; falls back to `dmesg -r` on non-zero exit
    - Parses output with `parse_dmesg_line()`, silently skipping unparseable lines
    - Marker slicing: finds first line containing `since_marker` in text, discards it and all prior lines; returns empty DmesgResult if marker not found
    - Level filtering: includes line if `LEVEL_VALUES[line.level] <= FILTER_THRESHOLDS[filter_level]`
    - Derives: `oops_detected`, `panic_detected`, `warning_count`, `error_count` from filtered lines

### Task 2: dmesg_collect MCP tool

- **`vmcluster_mcp/observe/__init__.py`** additions:
  - Imports: `collect_dmesg` from `observe.dmesg`, `DmesgResult` from `models`
  - `dmesg_collect(cluster_name, nodes=None, since_marker=None, filter_level="all") -> ToolResult[dict[str, DmesgResult]]`:
    - Validates `filter_level` against `FILTER_THRESHOLDS` keys → INVALID_FILTER_LEVEL
    - Validates node names → NODE_NOT_FOUND
    - Concurrent collection via `asyncio.gather(*[collect_dmesg(...)], return_exceptions=True)`
    - Per-node SSH failure → empty DmesgResult (best-effort, D-17), not global failure
    - Returns `ToolResult(success=True, result={node_name: DmesgResult})`

- **`tests/test_dmesg_collect.py`**: 17 tests — 10 unit (collect_dmesg function) + 7 tool-level (dmesg_collect tool):
  - Unit: basic parsing, marker filtering, missing marker, warn+/err+/all level filters, classification, summary fields, ISO fallback, garbage line skipping
  - Tool: all-nodes dict result, single-node subset, since_marker pass-through, invalid filter_level, invalid node name, SSH failure best-effort, warn+ filter

## Success Criteria Verification

- [x] `dmesg_collect` returns structured `DmesgResult` per node with parsed, filtered, classified lines
- [x] Marker-based filtering excludes all lines before `since_marker` (inclusive)
- [x] Level filtering (`"all"`, `"warn+"`, `"err+"`) correctly filters by syslog level
- [x] Classification fields (oops_detected, panic_detected, warning_count, error_count) derived from lines
- [x] ISO timestamp fallback works (tries `--time-format=iso`, falls back to raw on non-zero exit)
- [x] Concurrent collection across nodes via `asyncio.gather`
- [x] Per-node failures don't abort the entire operation
- [x] All 46 phase 6 tests pass; full 274-test suite has no regressions

## Test Results

```
tests/test_dmesg_collect.py  17 passed
tests/test_dmesg_mark.py      9 passed
tests/test_classifier.py     20 passed
Total (full suite): 274 passed in 2.64s
```
