mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-11-11 14:17:52 +00:00
efiten-patch-1
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe58dbd7d9 |
fix(server): read StoreTx state under s.mu in grouped packets and other readers (#2237) (#2246)
Fixes #2237. Ingest appends to `tx.Observations` and rewrites a transmission's header fields (`LatestSeen`, observer, path, counts) under `s.mu.Lock`. Several readers touched that state without the lock. This PR follows the approach of dborup/CoreScope#475, the reporter's fix: copy what each reader needs under the RLock it already takes, and sort or encode outside it. That fix does not apply to upstream as is (the fork has diverged), so this is a rewrite against current master. ## What changes, per site | Site | Before | After | |---|---|---| | `QueryGroupedPackets` (`store.go`) | Sort read `tx.LatestSeen` after `RUnlock`; the page (`groupedTxsToPage`, `storeTxDistinctIatas`) was built from the shared cache with no lock | The sort key is copied under the existing RLock (`groupedEntry`). The cache holds those entries. `groupedPage` copies only the requested page's rows under a short RLock; the maps are built after it | | `txGetParsedPath` (`store.go`) | Filled `tx.parsedPath` lazily under RLock, so concurrent readers wrote the same fields | `pickBestObservation` fills it through `setTxPath` where the path is written, under the write lock or before the tx is shared | | `BuildFromStoreWithOptions` (`neighbor_graph.go`) | Ranged `tx.Observations` after releasing the lock | Each tx's `Observations` slice header is copied with the packet list | | `/api/audio-lab/buckets` (`routes.go`) | Read path, observer and count after `RUnlock` | The sort stays outside the lock (`RawHex` never changes); the picked rows are read under a second short RLock | | `trackedBytes` (`/api/stats`, perf stats) | Read with no lock | Read under `s.mu` | | Observer analytics (`routes.go`, `observer_analytics.go`) | Read `tx.Hash` after `RUnlock`; the content-hash migration rewrites it | `txByID` holds copies made under the existing RLock (`txAnalyticsFields`) | `/api/nodes/{pk}/paths` and `GetNodeHopAnalytics`, items 2 in the issue, are not changed: on current master they already copy under the lock (`snapshotRPObs`) or run inside it. Appending to `tx.Observations` never changes the elements an already copied slice header covers, so copying the header is enough there. ## Tests `store_read_race_2237_test.go` adds one `-race` regression test per site. Each runs an ingest-style writer under `s.mu.Lock` against the reader: - `TestQueryGroupedPacketsIngestRace2237` (cold and cached paths) - `TestTxGetParsedPathReadersRace2237` - `TestBuildFromStoreIngestRace2237` - `TestAudioLabBucketsIngestRace2237` - `TestPerfStoreStatsTrackedBytesRace2237` - `TestStatsTrackedBytesRace2237` - `TestObserverAnalyticsHashMigrationRace2237` Plus `TestGroupedPageReflectsHeaderAfterIngest2237` (no race detector): header fields, `distinct_iatas`, count and order after ingest. How I checked them: - With the fix, `go test -race -count=3 -run 'Race2237'` passes. - Each site reverted on its own, one at a time, makes its test report `DATA RACE` (between 1 and 24 reports per site). - `go test ./...` in `cmd/server` passes; `go vet` and `gofmt -l` are clean. ## Performance `QueryGroupedPackets` is on the packets page's hot path. `BenchmarkQueryGroupedPackets2237`, 30K transmissions, median of 8 alternating runs per binary (Windows, noisy: single runs varied up to 2x): | Case | Before (ns/op) | After (ns/op) | B/op before → after | |---|---|---|---| | cold, limit 50 | 1,015,512 | 1,022,879 (+1%) | 1.62 MB → 2.10 MB | | cold, limit 1000 | 1,711,237 | 1,793,032 (+5%) | 2.93 MB → 3.64 MB | | cached, limit 50 | 29,439 | 32,608 (+11%, about 3 µs) | 69 KB → 82 KB | | cached, limit 1000 | 606,970 | 662,204 (+9%) | 1.38 MB → 1.61 MB | The extra cost is copying the page's rows. The lock is held for that copy only (at most `limit` rows), never for the sort or for building the maps. ## Not done - `BuildFromStoreWithOptions` still calls `tx.ParsedDecoded()` outside the lock, and `tx_rewrite_feed.go` resets `decodedOnce` / `parsedDecoded` under the write lock. That pair is still a race. Fixing it means parsing under the RLock on the first build, so I left it for a follow-up rather than widen this PR. - I ran the new tests under `-race`, not the whole package. - No load test against a live server; backend only, no browser check. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1720060284 |
fix(#1827): avoid per-observation SQL fetch in handleObserverAnalytics hot loop (#1829)
## Summary Fixes the CPU/DoS issue in #1827: observer detail pages were saturating CPU on busy observers — 6-7 concurrently loaded tabs pegged 12 cores for seconds, and auto-refresh made it self-sustaining. ## Root cause `handleObserverAnalytics` iterated every observation in the requested window and called `enrichObs()` per observation just to read `payload_type` and `decoded_json` for the `packetTypes`/`nodesTimeline` aggregates. `enrichObs()` also runs an on-demand SQL `SELECT resolved_path FROM observations WHERE id=?` (`fetchResolvedPathForObs`) and builds a full response map — both of which are unused by this aggregation loop. `resolved_path` is only actually consumed by the `<=20` kept `recentPackets` entries. Per the triage in #1827 (@carmack): *"Replacing `enrichObs(obs)` with a direct `s.store.byTxID[obs.TransmissionID].PayloadType` read (as sketched in the body) drops a map alloc + interface boxes per obs on the loop that saturated the operator's 12 cores. Byte-identical output. That's ~90% of the value."* This PR implements exactly that fast-path. ## Change - Aggregate loop (`packetTypes`, `nodesTimeline`): read `payload_type`/`decoded_json` directly off the transmission via `s.store.byTxID[obs.TransmissionID]` — no SQL, no per-obs map allocation. - `recentPackets` (`<=20` entries): unchanged, still calls `enrichObs()` since it needs `resolved_path`/`raw_hex`/etc. for display. - Output is unchanged: `packetTypes`/`nodesTimeline` are computed from the exact same underlying fields (`tx.PayloadType`, `tx.DecodedJSON`), just without the O(N) SQL round-trips. ## Scope This is the concrete hot-path fix from #1827's triage — not the broader `/api/observers/{id}/analytics` endpoint-split proposal in #1828, which (per that issue's discussion) is a separate P3 follow-up. #1828's own triage converged on this same `byTxID` fast-path as "the ground-work minimum" before any endpoint splitting. ## Testing - Existing `TestObserverAnalytics` passes unchanged. - Extended `TestObserverAnalytics/default` to assert `packetTypes` counts come out correct (`{"4":2,"5":1}` for the seeded fixture) via the new `byTxID` path, and that `recentPackets` still carries `resolved_path` where present (confirming the `enrichObs()` path for those 20 entries is untouched). - `go build ./...` and `go vet ./...` clean in `cmd/server`. - Full `go test ./...` in `cmd/server`: passes except 4 pre-existing test-order-dependent failures in `TestHandleNodePaths_*` (unrelated to this change — reproduced identically on a fresh, unpatched clone of `upstream/master`). --------- Co-authored-by: SaarMesh-Bot <300107934+SaarMesh-Bot@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d60188e481 |
refactor(1828): split handleObserverAnalytics into 5 helpers + byTxID fast-path (#1839)
## Summary Phase A of #1828: extract the 5 aggregate builders in `handleObserverAnalytics` into pure helpers in a new `cmd/server/observer_analytics.go`. Handler becomes a snapshot + filter + 5 composed calls. Also adopts the `byTxID` direct-read in `buildPacketTypes` (issue body's core observation): the payload-type histogram no longer allocates a full `enrichObs` map + interface-boxed fields just to read `tx.PayloadType`. That's the ~90% perf win the triage called out. Scope is exactly Phase A per the second triage comment. Phase B (sub-endpoints, caching, SQL migration) is deferred to a follow-up. ## Byte-identical output - Timeline / NodesTimeline: same key set, same sort, same labels. - PacketTypes: same keys/counts. Both legacy (`enriched["payload_type"].(int)`) and new (`tx.PayloadType == nil` guard) skip obs whose tx is missing or `PayloadType` is `nil`. - SnrDistribution: same 2-unit floor bucketing (negative-side rounding preserved), same ascending sort. - RecentPackets: still the first 20 enriched observations (`enrichObs` kept only here, where the extra fields are actually needed). ## TDD - Red commit: `9dc62f43` — 7 unit tests fail on assertions (not build errors) against stubs. - Green commit: `8d41011d` — implementations + handler rewire. All new tests + existing `TestObserverAnalytics*` handler tests pass. ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → clean (all 8 hard gates + 3 warnings pass). ## Non-goals - No new endpoints. - No SQL migration. - No public API signature change. - Snapshot count unchanged (still one under RLock, per #1481 P0-2). Fixes #1828. --------- Co-authored-by: fix-1828-bot <bot@corescope.local> Co-authored-by: clawbot <bot@corescope> |