AdGuardDNS/internal/metrics/usercount_test.go

259 lines
7.1 KiB
Go
Raw Permalink Normal View History

2024-10-14 17:44:24 +03:00
package metrics_test
2022-12-29 15:36:26 +03:00
import (
2023-08-08 18:31:48 +03:00
"net"
2022-12-29 15:36:26 +03:00
"net/netip"
2022-12-30 15:10:23 +03:00
"strconv"
2022-12-29 15:36:26 +03:00
"testing"
"time"
2024-10-14 17:44:24 +03:00
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
2023-08-08 18:31:48 +03:00
"github.com/AdguardTeam/golibs/netutil"
2024-10-14 17:44:24 +03:00
"github.com/prometheus/client_golang/prometheus"
2022-12-29 15:36:26 +03:00
"github.com/stretchr/testify/assert"
2023-08-08 18:31:48 +03:00
"github.com/stretchr/testify/require"
2024-10-14 17:44:24 +03:00
"golang.org/x/exp/rand"
2022-12-29 15:36:26 +03:00
)
2023-08-08 18:31:48 +03:00
// Use a constant seed to make the test reproducible.
const randSeed = 1234
2024-10-14 17:44:24 +03:00
// Gauges for tests.
var (
testLastHour = prometheus.NewGauge(prometheus.GaugeOpts{})
testLastDay = prometheus.NewGauge(prometheus.GaugeOpts{})
)
// randIPBytes is a test helper that returns a pseudorandomly generated
// IP-address bytes. fam must be either [netutil.AddrFamilyIPv4] or
// [netutil.AddrFamilyIPv6].
func randIPBytes(t testing.TB, r *rand.Rand, fam netutil.AddrFamily) (ipBytes []byte) {
2023-08-08 18:31:48 +03:00
t.Helper()
switch fam {
case netutil.AddrFamilyIPv4:
2024-10-14 17:44:24 +03:00
ipBytes = make([]byte, net.IPv4len)
2023-08-08 18:31:48 +03:00
case netutil.AddrFamilyIPv6:
2024-10-14 17:44:24 +03:00
ipBytes = make([]byte, net.IPv6len)
2023-08-08 18:31:48 +03:00
default:
t.Fatalf("unexpected address family %q", fam)
}
2024-10-14 17:44:24 +03:00
n, err := r.Read(ipBytes)
2023-08-08 18:31:48 +03:00
require.NoError(t, err)
2024-10-14 17:44:24 +03:00
require.Equal(t, len(ipBytes), n)
2023-08-08 18:31:48 +03:00
2024-10-14 17:44:24 +03:00
return ipBytes
2023-08-08 18:31:48 +03:00
}
func TestUserCounter_Estimate(t *testing.T) {
// TODO(e.burkov): Add tests for more than 48 hours gaps, when it will be
// supported.
testCases := []struct {
name string
nows []time.Time
wantDaily uint64
wantHourly uint64
}{{
name: "empty",
nows: nil,
wantDaily: 0,
wantHourly: 0,
}, {
name: "each_minute",
nows: []time.Time{
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 3, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 4, 0, 0, time.UTC),
},
wantDaily: 4,
wantHourly: 4,
}, {
name: "each_hour",
nows: []time.Time{
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 2, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 3, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 4, 0, 0, 0, time.UTC),
},
wantDaily: 4,
wantHourly: 1,
}, {
name: "each_day",
nows: []time.Time{
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 4, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 5, 0, 0, 0, 0, time.UTC),
},
wantDaily: 0,
wantHourly: 0,
}, {
name: "few_per_minute",
nows: []time.Time{
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 0, 1, 0, time.UTC),
time.Date(2023, 1, 1, 0, 0, 2, 0, time.UTC),
time.Date(2023, 1, 1, 0, 0, 3, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 1, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 2, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 3, 0, time.UTC),
time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC),
},
wantDaily: 8,
wantHourly: 8,
}, {
name: "few_per_hour",
nows: []time.Time{
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 3, 0, 0, time.UTC),
time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 1, 1, 0, 0, time.UTC),
time.Date(2023, 1, 1, 1, 2, 0, 0, time.UTC),
time.Date(2023, 1, 1, 1, 3, 0, 0, time.UTC),
time.Date(2023, 1, 1, 2, 0, 0, 0, time.UTC),
},
wantDaily: 8,
wantHourly: 4,
}, {
name: "few_hours_gap",
nows: []time.Time{
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC),
time.Date(2023, 1, 1, 4, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 4, 1, 0, 0, time.UTC),
time.Date(2023, 1, 1, 4, 2, 0, 0, time.UTC),
},
wantDaily: 5,
wantHourly: 3,
}, {
name: "few_per_day",
nows: []time.Time{
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC),
time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC),
time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 2, 0, 1, 0, 0, time.UTC),
time.Date(2023, 1, 2, 0, 2, 0, 0, time.UTC),
},
wantDaily: 5,
wantHourly: 3,
}, {
name: "day_and_hour_gap",
nows: []time.Time{
time.Date(2023, 1, 1, 23, 0, 0, 0, time.UTC),
time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
},
wantDaily: 1,
wantHourly: 1,
}, {
name: "day_and_minute_gap",
nows: []time.Time{
time.Date(2023, 1, 1, 23, 59, 0, 0, time.UTC),
time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
},
wantDaily: 1,
wantHourly: 1,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := rand.New(rand.NewSource(randSeed))
2024-10-14 17:44:24 +03:00
c := metrics.NewUserCounter(testLastHour, testLastDay)
2023-08-08 18:31:48 +03:00
for _, now := range tc.nows {
2024-10-14 17:44:24 +03:00
c.Record(now, randIPBytes(t, r, netutil.AddrFamilyIPv6), true)
2023-08-08 18:31:48 +03:00
}
2024-10-14 17:44:24 +03:00
hourly, daily := c.Estimate()
2023-08-08 18:31:48 +03:00
assert.Equal(t, tc.wantHourly, hourly)
assert.Equal(t, tc.wantDaily, daily)
})
}
}
func TestUserCounter_simple(t *testing.T) {
2022-12-30 15:10:23 +03:00
const ipsPerMinute = 2
2022-12-29 15:36:26 +03:00
2023-08-08 18:31:48 +03:00
src := rand.NewSource(randSeed)
2022-12-29 15:36:26 +03:00
r := rand.New(src)
2024-10-14 17:44:24 +03:00
c := metrics.NewUserCounter(testLastHour, testLastDay)
2022-12-29 15:36:26 +03:00
2022-12-30 15:10:23 +03:00
now := time.Unix(0, 0).UTC()
2023-08-08 18:31:48 +03:00
for d, h := now.Day(), now.Hour(); now.Day() == d; h = now.Hour() {
t.Run(strconv.Itoa(now.Hour()), func(t *testing.T) {
for ; now.Hour() == h; now = now.Add(1 * time.Minute) {
2024-06-07 14:27:46 +03:00
for range ipsPerMinute {
2024-10-14 17:44:24 +03:00
c.Record(now, randIPBytes(t, r, netutil.AddrFamilyIPv4), true)
2022-12-30 15:10:23 +03:00
}
}
2024-10-14 17:44:24 +03:00
hourly, _ := c.Estimate()
2022-12-30 15:10:23 +03:00
assert.InEpsilon(t, uint64(ipsPerMinute*60), hourly, 0.02)
})
2022-12-29 15:36:26 +03:00
}
2024-10-14 17:44:24 +03:00
_, daily := c.Estimate()
2022-12-30 15:10:23 +03:00
assert.InEpsilon(t, uint64(ipsPerMinute*24*60), daily, 0.02)
2022-12-29 15:36:26 +03:00
}
2023-08-08 18:31:48 +03:00
// uint64Sink is a sink for uint64 values returned from benchmarks.
var uint64Sink uint64
func BenchmarkUserCounter_Estimate(b *testing.B) {
const n = 100
zeroTime := time.Unix(0, 0).UTC()
2024-10-14 17:44:24 +03:00
sparseCounter := metrics.NewUserCounter(testLastHour, testLastDay)
2023-08-08 18:31:48 +03:00
for d, now := zeroTime.Day(), zeroTime; d == now.Day(); now = now.Add(time.Minute) {
r := rand.New(rand.NewSource(randSeed))
2024-06-07 14:27:46 +03:00
for range n {
2024-10-14 17:44:24 +03:00
sparseCounter.Record(now, randIPBytes(b, r, netutil.AddrFamilyIPv6), true)
2023-08-08 18:31:48 +03:00
}
}
2024-10-14 17:44:24 +03:00
seqCounter := metrics.NewUserCounter(testLastHour, testLastDay)
2023-08-08 18:31:48 +03:00
for d, now := zeroTime.Day(), zeroTime; d == now.Day(); now = now.Add(time.Minute) {
addr := netip.AddrFrom16([16]byte{})
2024-06-07 14:27:46 +03:00
for range n {
2023-08-08 18:31:48 +03:00
addr = addr.Next()
2024-10-14 17:44:24 +03:00
addrArr := addr.As16()
seqCounter.Record(now, addrArr[:], true)
2023-08-08 18:31:48 +03:00
}
}
b.Run("sparse", func(b *testing.B) {
b.ReportAllocs()
2023-09-06 08:22:07 +03:00
b.ResetTimer()
2024-06-07 14:27:46 +03:00
for range b.N {
2024-10-14 17:44:24 +03:00
uint64Sink, uint64Sink = sparseCounter.Estimate()
2023-08-08 18:31:48 +03:00
}
})
b.Run("sequential", func(b *testing.B) {
b.ReportAllocs()
2023-09-06 08:22:07 +03:00
b.ResetTimer()
2024-06-07 14:27:46 +03:00
for range b.N {
2024-10-14 17:44:24 +03:00
uint64Sink, uint64Sink = seqCounter.Estimate()
2023-08-08 18:31:48 +03:00
}
2022-12-29 15:36:26 +03:00
})
2024-10-14 17:44:24 +03:00
// Most recent results:
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/metrics
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkUserCounter_Estimate/sparse-16 3166 354701 ns/op 6052 B/op 50 allocs/op
// BenchmarkUserCounter_Estimate/sequential-16 4057 346637 ns/op 6054 B/op 50 allocs/op
2022-12-29 15:36:26 +03:00
}