Files
meshcore-analyzer/cmd/server/node_reach_bench_test.go
T
e2212f5015 feat(nodes): per-node Reach page + GET /api/nodes/{pubkey}/reach (v2, review-complete) (#1627)
Re-submission of #1625 (which was merged early, then reverted in #1626)
— now with **all three round-1 reviews addressed** so it lands in one
hardened state instead of as post-merge follow-ups.

## What

Per-node **Reach** view: a standalone page (`#/nodes/{pubkey}/reach`) +
a node-detail section + `GET /api/nodes/{pubkey}/reach`. It shows which
nodes a node has a **stable two-way RF link** with, derived from raw
`path_json` adjacency (a path travels origin→observer, so `[A,B]` ⇒ B
heard A). A link is bidirectional when both directions have
observations; the **bottleneck** (weaker direction) rates two-way
reliability. Nodes are identified only by **unique 2–3 byte** path
prefixes (1-byte collides → excluded).

## Review fixes folded in vs #1625

**Performance (Carmack):** hard scan LIMIT (200k) + modest prealloc;
`json.Unmarshal` replaced by a single-pass `parsePathTokens` (100k-row
scan 2.2M→1.3M allocs, 344→203ms); memoized resolver; size-hinted maps
(attribution over 100k rows: 102 allocs); `context.Context` plumbed;
cache `RWMutex` + evict-oldest (no full wipe); singleflight dedup;
degree/rank from a 60s shared snapshot; bench rewritten (ReportAllocs,
1k/10k/100k, mixed-payload, isolated attribution).

**Correctness/safety + tests (Independent + Kent Beck):** pubkey
validation → 400; error logging instead of silent swallow (first_seen /
degree / marshal→500 / discarded rows); `public_key=?` index use;
canonical `PayloadADVERT`; `min()` builtin; documented cache-slice
immutability; mux ordering comment. New tests: scanReachRows decode,
3-byte token branch, non-advert first-hop guard, observer SNR
aggregation across rows, HTTP-level attribution (asserts non-zero
we_hear/they_hear), 400/404/blacklist/cache-hit.

**UI / a11y / Tufte:** in-map legend (tiers + thresholds); dropped the
colour+width double-encoding (constant width, colour-only); colour-blind
glyphs (●●●/●●/●) + tier title beside the bottleneck number; dark-theme
`--link-*`; lighter table (horizontal rules, sentence-case headers); map
built once + link layer updated in place on toggle (no flicker);
time-range no longer flashes a loader; `destroy()` generation guard;
statCard escaping; scoped `@media print` to `#nq-report`;
`fieldset/legend` + `for/id` toggles; `aria-pressed` / `aria-live` /
back-link `aria-label`; "distance (km)" + bottleneck tooltip + no-GPS
note; inline styles → CSS; decorative emoji removed.

**Docs:** api-spec documents the 5-min cache, 200k scan cap, and 400.

## Testing
- `cmd/server` full suite green; reach unit + endpoint + bench all pass.
- `eslint public/*.js` (no-undef) and the XSS-sink gate clean.
- E2E updated: request status checks + exact (non-tautological) toggle
assertions + hard map-render assert.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


---

## TDD-history note (Kent Beck gate)

This branch carries production + tests together, not a fabricated
red→green sequence. That's deliberate: the branch was rebased onto
upstream and the intermediate SHAs were squashed, so reconstructing a
"failing-test-first" commit after the fact would be theatre, not
evidence — and rewriting history to stage it would be dishonest. The
behaviour is instead covered by a comprehensive, anti-tautological suite
(directional attribution edges, 3-byte token branch, non-advert
first-hop guard, observer SNR aggregation, HTTP-level attribution
asserting non-zero counts, scan-cap truncation, zero-reach 200-not-404,
companion mis-attribution, cache eviction). Requesting maintainer
acceptance of the work on test *substance* rather than commit
*choreography*; the net-new-UI exemption is not claimed for the server
endpoint.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: meshcore-bot <bot@meshcore>
2026-06-08 22:13:02 -07:00

152 lines
4.6 KiB
Go

package main
import (
"context"
"database/sql"
"fmt"
"testing"
_ "modernc.org/sqlite"
)
// benchReachDB builds an in-memory DB with nObs observations. matchEvery
// controls payload mix: 1 = every row contains the "01FA" token (worst case),
// 2 = every other row matches (the rest carry an unrelated path), etc. This
// lets benches measure the scan over a realistic mix, not just all-matching.
func benchReachDB(b *testing.B, nObs, matchEvery int, lowerHops bool) *DB {
b.Helper()
if matchEvery < 1 {
matchEvery = 1
}
matchPath, fillerPath := `["AA","01FA","BB"]`, `["AA","CC","BB"]`
if lowerHops {
// Lowercase hops force parsePathTokens' ToUpper to allocate (production
// path_json is uppercase; this measures the worst case Carmack flagged).
matchPath, fillerPath = `["aa","01fa","bb"]`, `["aa","cc","bb"]`
}
conn, err := sql.Open("sqlite", ":memory:")
if err != nil {
b.Fatal(err)
}
schema := []string{
`CREATE TABLE transmissions (id INTEGER PRIMARY KEY, hash TEXT, first_seen TEXT, payload_type INTEGER, from_pubkey TEXT)`,
`CREATE TABLE observers (id TEXT PRIMARY KEY, name TEXT)`,
`CREATE TABLE observations (id INTEGER PRIMARY KEY, transmission_id INTEGER, observer_idx INTEGER, snr REAL, path_json TEXT, timestamp INTEGER)`,
`CREATE INDEX idx_obs_ts ON observations(timestamp)`,
}
for _, s := range schema {
if _, err := conn.Exec(s); err != nil {
b.Fatal(err)
}
}
tx, err := conn.Begin()
if err != nil {
b.Fatal(err)
}
if _, err := tx.Exec(`INSERT INTO observers (id, name) VALUES ('OBS', 'o')`); err != nil {
b.Fatal(err)
}
for i := 0; i < nObs; i++ {
if _, err := tx.Exec(`INSERT INTO transmissions (id, hash, first_seen, payload_type, from_pubkey) VALUES (?,?,?,5,'')`,
i, fmt.Sprintf("h%d", i), "2026-06-07T00:00:00Z"); err != nil {
b.Fatal(err)
}
path := fillerPath // non-matching filler
if i%matchEvery == 0 {
path = matchPath
}
if _, err := tx.Exec(`INSERT INTO observations (id, transmission_id, observer_idx, snr, path_json, timestamp) VALUES (?,?,1,-7.0,?,?)`,
i, i, path, 1000); err != nil {
b.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
b.Fatal(err)
}
return &DB{conn: conn}
}
// BenchmarkNodeReachScan measures the windowed scan + path-decode at increasing
// scale, all-matching (worst case for memory/allocs).
func BenchmarkNodeReachScan(b *testing.B) {
tokens := map[string]bool{"01FA": true}
for _, n := range []int{1000, 10000, 100000} {
b.Run(fmt.Sprintf("rows=%d", n), func(b *testing.B) {
db := benchReachDB(b, n, 1, false)
srv := &Server{db: db}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
rows := srv.scanReachRows(context.Background(), tokens, 0)
if len(rows) == 0 {
b.Fatal("expected rows")
}
}
})
}
}
// BenchmarkNodeReachScanMixed measures the scan when only half the windowed
// rows actually contain the token — closer to production path mixes.
func BenchmarkNodeReachScanMixed(b *testing.B) {
tokens := map[string]bool{"01FA": true}
db := benchReachDB(b, 100000, 2, false)
srv := &Server{db: db}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
rows := srv.scanReachRows(context.Background(), tokens, 0)
if len(rows) == 0 {
b.Fatal("expected rows")
}
}
}
// BenchmarkNodeReachScanLowerCase measures the worst case for path decoding:
// lowercase hops force parsePathTokens' ToUpper to allocate a new string per
// hop (production path_json is uppercase, where ToUpper is a no-op). Publishing
// this alongside the all-uppercase numbers keeps the perf claims honest.
func BenchmarkNodeReachScanLowerCase(b *testing.B) {
tokens := map[string]bool{"01FA": true}
db := benchReachDB(b, 100000, 1, true)
srv := &Server{db: db}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
rows := srv.scanReachRows(context.Background(), tokens, 0)
if len(rows) == 0 {
b.Fatal("expected rows")
}
}
}
// BenchmarkNodeReachAttribute measures the directional attribution pass over an
// already-scanned row set (the in-memory hot loop + map building), isolated
// from DB I/O.
func BenchmarkNodeReachAttribute(b *testing.B) {
tokens := map[string]bool{"01FA": true}
db := benchReachDB(b, 100000, 1, false)
srv := &Server{db: db}
rows := srv.scanReachRows(context.Background(), tokens, 0)
if len(rows) == 0 {
b.Fatal("expected rows")
}
resolve := func(tok string) string {
switch tok {
case "AA":
return "aa00000000000000"
case "BB":
return "bb00000000000000"
}
return ""
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := attributeDirections(rows, tokens, "01fa326b", resolve)
if d.relay == 0 {
b.Fatal("expected relay hits")
}
}
}