package connectsrv import ( "sync" "time" ) // activeUsers tracks distinct authenticated accounts by last-action time, backing // the in-memory active_users gauge. It is single-process by design (the gateway is // single-instance in the MVP, docs/ARCHITECTURE.md ยง10): the distinct count is // correct for one process, resets on restart, and is a live operational gauge, not // a billing figure. Memory is bounded by the number of distinct accounts active // within the longest window; stale entries are pruned on observation. type activeUsers struct { mu sync.Mutex lastSeen map[string]time.Time now func() time.Time } // newActiveUsers returns an empty tracker using the wall clock. func newActiveUsers() *activeUsers { return &activeUsers{lastSeen: make(map[string]time.Time), now: time.Now} } // seen records that account uid performed an authenticated action now. func (a *activeUsers) seen(uid string) { if uid == "" { return } a.mu.Lock() a.lastSeen[uid] = a.now() a.mu.Unlock() } // counts returns, for each window, the number of distinct accounts last seen // within it, pruning entries older than the longest window in the same pass. func (a *activeUsers) counts(windows []time.Duration) []int { a.mu.Lock() defer a.mu.Unlock() now := a.now() var longest time.Duration for _, w := range windows { if w > longest { longest = w } } res := make([]int, len(windows)) for uid, ts := range a.lastSeen { age := now.Sub(ts) if age > longest { delete(a.lastSeen, uid) continue } for i, w := range windows { if age <= w { res[i]++ } } } return res }