mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-06 12:21:38 +00:00
f4cf2acbc0
Red commit: e964ec9c46 (CI run: pending —
workflow only triggers on PR open)
Partial fix for #1120 — finishes the four follow-up items left open
after PR #1123 (cancelled writes, ingestor I/O, threshold-flag tests,
docs).
## What's done
- **`cancelledWriteBytesPerSec`** — server `/proc/self/io` parser
handles `cancelled_write_bytes`; `/api/perf/io` exposes the per-second
rate; Perf page renders it next to Read/Write with ⚠️ when sustained >1
MB/s.
- **Ingestor `/proc/<pid>/io`** — `cmd/ingestor/stats_file.go` samples
its own `/proc/self/io` each tick and includes `procIO` in the snapshot.
The server's `/api/perf/io` reads it and surfaces `.ingestor`. Frontend
renders an `Ingestor process` Disk I/O block alongside the existing
`server process` block (issue mockup: "Both ingestor and server").
- **Threshold + anomaly tests** — `test-perf-disk-io-1120.js` now
asserts ⚠️ fires/suppresses on WAL>100MB, cache_hit<90%, and the
backfill-rate-vs-tx-rate guard with the `tx_inserted >= 100` baseline
floor. Drops the tautological `|| ... === false` short-circuits flagged
in MINOR m4.
- **Docs (m8)** — `config.example.json` adds `_comment_ingestorStats`
(env var, default path, shared-tmp security note);
`cmd/ingestor/README.md` adds `CORESCOPE_INGESTOR_STATS` to the env-var
table plus a `Stats file` section.
## What's NOT done (deferred)
m1 sync.Map → map+RWMutex, m2 perfIOMu rate caching, m3 negative
cacheSize translation, m5 deterministic-write test, m7 ctx-aware
shutdown — pure polish; will file a follow-up issue if the operator
wants them tracked.
## TDD
- Red: `e964ec9` — adds failing tests + stub field/handler shape
(cancelled missing from struct, ingestor stub returns nil, ingestor
procIO absent).
- Green: `1240703` — wires up the parser case, ingestor sampler,
frontend rendering, docs.
E2E assertion added: test-perf-disk-io-1120.js:108
---------
Co-authored-by: clawbot <clawbot@users.noreply.github.com>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot>
68 lines
2.0 KiB
Go
68 lines
2.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestStatsFileWriter_PublishesProcIO asserts the ingestor's published
|
|
// stats snapshot includes a `procIO` block with the per-process I/O rate
|
|
// fields required by issue #1120 ("Both ingestor and server").
|
|
func TestStatsFileWriter_PublishesProcIO(t *testing.T) {
|
|
if _, err := os.Stat("/proc/self/io"); err != nil {
|
|
t.Skip("skip: /proc/self/io unavailable on this host")
|
|
}
|
|
dir := t.TempDir()
|
|
statsPath := filepath.Join(dir, "ingestor-stats.json")
|
|
t.Setenv("CORESCOPE_INGESTOR_STATS", statsPath)
|
|
|
|
store, err := OpenStore(filepath.Join(dir, "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("OpenStore: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
StartStatsFileWriter(store, 50*time.Millisecond)
|
|
|
|
// Wait for at least 2 ticks so the writer has had a chance to populate
|
|
// procIO rates from a delta.
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
var snap map[string]interface{}
|
|
for time.Now().Before(deadline) {
|
|
time.Sleep(75 * time.Millisecond)
|
|
b, err := os.ReadFile(statsPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if err := json.Unmarshal(b, &snap); err != nil {
|
|
continue
|
|
}
|
|
if _, ok := snap["procIO"]; ok {
|
|
break
|
|
}
|
|
}
|
|
|
|
pio, ok := snap["procIO"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected procIO block in stats snapshot, got: %v", snap)
|
|
}
|
|
for _, field := range []string{"readBytesPerSec", "writeBytesPerSec", "cancelledWriteBytesPerSec", "syscallsRead", "syscallsWrite"} {
|
|
v, present := pio[field]
|
|
if !present {
|
|
t.Errorf("procIO missing field %q", field)
|
|
continue
|
|
}
|
|
// #1167 must-fix #5: assert the field actually decodes as a JSON
|
|
// number, not just that the key exists. An empty PerfIOSample{}
|
|
// substruct would still serialise the keys since the inner numeric
|
|
// fields lack omitempty — without this Kind check the test would
|
|
// silently pass on an empty struct regression.
|
|
if _, isFloat := v.(float64); !isFloat {
|
|
t.Errorf("procIO[%q] expected JSON number (float64), got %T (%v)", field, v, v)
|
|
}
|
|
}
|
|
}
|