package shhh import ( "sync" "sync/atomic" ) // Stats tracks aggregate counts for the sentinel. type Stats struct { totalScans atomic.Uint64 totalFindings atomic.Uint64 perRule sync.Map // string -> *atomic.Uint64 } // NewStats constructs a Stats collector. func NewStats() *Stats { return &Stats{} } // IncScan increments the total scan counter. func (s *Stats) IncScan() { if s == nil { return } s.totalScans.Add(1) } // AddFindings records findings for a rule. func (s *Stats) AddFindings(rule string, count int) { if s == nil || count <= 0 { return } s.totalFindings.Add(uint64(count)) counterAny, _ := s.perRule.LoadOrStore(rule, new(atomic.Uint64)) counter := counterAny.(*atomic.Uint64) counter.Add(uint64(count)) } // Snapshot returns a point-in-time view of the counters. func (s *Stats) Snapshot() StatsSnapshot { if s == nil { return StatsSnapshot{} } snapshot := StatsSnapshot{ TotalScans: s.totalScans.Load(), TotalFindings: s.totalFindings.Load(), PerRuleFindings: make(map[string]uint64), } s.perRule.Range(func(key, value any) bool { name, ok := key.(string) if !ok { return true } if counter, ok := value.(*atomic.Uint64); ok { snapshot.PerRuleFindings[name] = counter.Load() } return true }) return snapshot }