initial commit
This commit is contained in:
1 parent
5b935301e5
commit
85453852d5
105 files changed
+24846
No files matched your search
@@ -0,0 +1,148 @@
|
||||
package qlog
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// randFloat is a 0..1 sample draw, isolated so tests can reason about
|
||||
// sampling without seeding the global source.
|
||||
func randFloat() float64 { return rand.Float64() }
|
||||
|
||||
// domainCount is a rolling per-domain counter.
|
||||
type domainCount struct {
|
||||
name string
|
||||
qtype uint16
|
||||
qtypeS string
|
||||
total uint64
|
||||
blocked uint64
|
||||
last time.Time
|
||||
}
|
||||
|
||||
// domainCounts is a bounded, TTL-evicting map of domain counters. It replaces
|
||||
// the hot half of "what is everyone asking for" without the cost of a sorted
|
||||
// set on every query.
|
||||
type domainCounts struct {
|
||||
mu sync.Mutex
|
||||
counts map[string]*domainCount
|
||||
limit int
|
||||
ttl time.Duration
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func newDomainCounts(limit int, ttl time.Duration) *domainCounts {
|
||||
return &domainCounts{
|
||||
counts: make(map[string]*domainCount, 1024),
|
||||
limit: limit,
|
||||
ttl: ttl,
|
||||
now: time.Now,
|
||||
}
|
||||
}
|
||||
|
||||
func countKey(name string, qtype uint16) string {
|
||||
return name + "|" + itoa(int(qtype))
|
||||
}
|
||||
|
||||
func itoa(n int) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
var buf [8]byte
|
||||
i := len(buf)
|
||||
for n > 0 {
|
||||
i--
|
||||
buf[i] = byte('0' + n%10)
|
||||
n /= 10
|
||||
}
|
||||
return string(buf[i:])
|
||||
}
|
||||
|
||||
// observe records a query against its domain counter.
|
||||
func (d *domainCounts) observe(e Entry) {
|
||||
if e.Name == "" {
|
||||
return
|
||||
}
|
||||
key := countKey(e.Name, e.QType)
|
||||
now := d.now()
|
||||
d.mu.Lock()
|
||||
c, ok := d.counts[key]
|
||||
if !ok {
|
||||
if len(d.counts) >= d.limit {
|
||||
d.evictLocked(now)
|
||||
}
|
||||
c = &domainCount{name: trimDot(e.Name), qtype: e.QType, qtypeS: e.QTypeStr}
|
||||
d.counts[key] = c
|
||||
}
|
||||
c.total++
|
||||
if e.Blocked {
|
||||
c.blocked++
|
||||
}
|
||||
c.last = now
|
||||
d.mu.Unlock()
|
||||
}
|
||||
|
||||
// evictLocked removes expired counters, and if that is not enough, the oldest
|
||||
// quarter. Callers hold d.mu.
|
||||
func (d *domainCounts) evictLocked(now time.Time) {
|
||||
cutoff := now.Add(-d.ttl)
|
||||
removed := 0
|
||||
for k, c := range d.counts {
|
||||
if c.last.Before(cutoff) {
|
||||
delete(d.counts, k)
|
||||
removed++
|
||||
}
|
||||
}
|
||||
if removed == 0 && len(d.counts) > 0 {
|
||||
// Nothing has expired (a very busy window): drop the oldest quarter.
|
||||
type kv struct {
|
||||
k string
|
||||
t time.Time
|
||||
}
|
||||
all := make([]kv, 0, len(d.counts))
|
||||
for k, c := range d.counts {
|
||||
all = append(all, kv{k, c.last})
|
||||
}
|
||||
sort.Slice(all, func(i, j int) bool { return all[i].t.Before(all[j].t) })
|
||||
n := len(all) / 4
|
||||
if n < 1 {
|
||||
n = 1
|
||||
}
|
||||
for i := 0; i < n && i < len(all); i++ {
|
||||
delete(d.counts, all[i].k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// top returns the n hottest domains.
|
||||
func (d *domainCounts) top(n int, blockedOnly bool) []DomainCount {
|
||||
if n <= 0 {
|
||||
n = 25
|
||||
}
|
||||
d.mu.Lock()
|
||||
out := make([]DomainCount, 0, n)
|
||||
for _, c := range d.counts {
|
||||
if blockedOnly && c.blocked == 0 {
|
||||
continue
|
||||
}
|
||||
dc := DomainCount{Name: c.name, Count: c.total, Blocked: c.blocked, QType: c.qtype, QTypeS: c.qtypeS}
|
||||
if blockedOnly {
|
||||
dc.Count = c.blocked
|
||||
}
|
||||
out = append(out, dc)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Count > out[j].Count })
|
||||
if len(out) > n {
|
||||
out = out[:n]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func trimDot(s string) string {
|
||||
if n := len(s); n > 1 && s[n-1] == '.' {
|
||||
return s[:n-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in new issue
Block a user