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
|
|
|
}
|