// Package analytics is the pure half of the Analytics tab (spec // 2026-09-07-errors-and-analytics-design.md §3.3): the visitor and top-key // sketches, the in-memory meter the edge feeds, the day-file wire shape // and its idempotent CAS merge, the daily salt, the bot list and the // cross-box range read. internal/edge observes, internal/host flushes, // internal/console reads — the applog/apperr split. package analytics import ( "encoding/base64" "errors" "math" "math/bits" ) // HLLPrecision is the register-index width: 2^11 registers, 2 KB, ~2.3% // standard error (spec §3.3). One sketch per app per day per box. const HLLPrecision = 11 const hllRegisters = 1 << HLLPrecision // HLL is a HyperLogLog cardinality sketch over 64-bit hashes. type HLL struct{ reg [hllRegisters]uint8 } // mix64 is MurmurHash3's fmix64 finalizer. Add uses it to avalanche the // incoming hash before slicing out the register index and run bits: the // register index is the hash's top HLLPrecision bits, and callers' hash // functions (FNV-1a included — see h64 in hll_test.go) do not reliably // mix entropy into their high bits, especially for short, sequential // inputs sharing a common prefix ("v0".."v99999"), which otherwise // starves most registers and skews Count. func mix64(x uint64) uint64 { x ^= x >> 33 x *= 0xff51afd7ed558ccd x ^= x >> 33 x *= 0xc4ceb9fe1a85ec53 x ^= x >> 33 return x } // Add records one hashed element. func (h *HLL) Add(hash uint64) { hash = mix64(hash) idx := hash >> (64 - HLLPrecision) rest := hash< h.reg[idx] { h.reg[idx] = rank } } // Merge folds o into h by register max — the union of the two sets. func (h *HLL) Merge(o *HLL) { for i := range h.reg { if o.reg[i] > h.reg[i] { h.reg[i] = o.reg[i] } } } // Empty reports whether nothing was added. func (h *HLL) Empty() bool { for _, r := range h.reg { if r != 0 { return false } } return true } // Count estimates the cardinality (Flajolet et al. 2007 with the small- // range linear-counting correction). func (h *HLL) Count() float64 { m := float64(hllRegisters) alpha := 0.7213 / (1 + 1.079/m) var sum float64 zeros := 0 for _, r := range h.reg { sum += math.Pow(2, -float64(r)) if r == 0 { zeros++ } } est := alpha * m * m / sum if est <= 2.5*m && zeros > 0 { est = m * math.Log(m/float64(zeros)) } return est } const hllVersion = 1 // Encode renders the sketch as base64 of a version byte plus the registers. func (h *HLL) Encode() string { b := make([]byte, 1+hllRegisters) b[0] = hllVersion copy(b[1:], h.reg[:]) return base64.StdEncoding.EncodeToString(b) } // DecodeHLL parses Encode's output; "" is an empty sketch, an unknown // version is an error (a future shape this build cannot read). func DecodeHLL(s string) (*HLL, error) { h := &HLL{} if s == "" { return h, nil } b, err := base64.StdEncoding.DecodeString(s) if err != nil { return nil, err } if len(b) != 1+hllRegisters || b[0] != hllVersion { return nil, errors.New("analytics: unknown visitors sketch version or size") } copy(h.reg[:], b[1:]) return h, nil }