Files
ingot/metrics_test.go
T
2026-07-04 18:51:08 -04:00

187 lines
4.7 KiB
Go

package ingot
import (
"math"
"testing"
"git.dvdt.dev/david/ingot/labels"
)
func TestCollectMetrics(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T, db *DB)
collectCalls int
wantHeadSeries float64 // expected value after last collectMetrics call
wantBlocksTotal float64
wantCompactions float64
wantMetricCount int // total ingot_* series
}{
{
name: "empty_db",
setup: func(t *testing.T, db *DB) {},
// First call snapshots 0 series, writes 5 metric series.
// Second call snapshots 5 series (the metrics themselves).
collectCalls: 2,
wantHeadSeries: 5,
wantBlocksTotal: 0,
wantCompactions: 0,
wantMetricCount: 5,
},
{
name: "with_user_series",
setup: func(t *testing.T, db *DB) {
app := db.Appender()
_, err := app.Append(0, labels.FromStrings("__name__", "temp", "room", "a"), 1000, 1.0)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = app.Append(0, labels.FromStrings("__name__", "temp", "room", "b"), 1000, 2.0)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := app.Commit(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
// 2 user series + 5 metric series = 7
collectCalls: 2,
wantHeadSeries: 7,
wantBlocksTotal: 0,
wantCompactions: 0,
wantMetricCount: 5,
},
{
name: "with_block",
setup: func(t *testing.T, db *DB) {
app := db.Appender()
ref, err := app.Append(0, labels.FromStrings("__name__", "temp"), 0, 0)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := 1; i < 250; i++ {
_, err = app.Append(ref, nil, int64(i*15000), float64(i))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
if err := app.Commit(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = db.FlushOlderThan(math.MaxInt64)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
},
collectCalls: 2,
wantHeadSeries: 6, // 1 user + 5 metric series
wantBlocksTotal: 1,
wantCompactions: 0,
wantMetricCount: 5,
},
{
name: "idempotent_five_calls",
setup: func(t *testing.T, db *DB) {},
collectCalls: 5,
wantHeadSeries: 5,
wantBlocksTotal: 0,
wantCompactions: 0,
wantMetricCount: 5,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
clock := &testClock{now: 100_000}
db, err := Open(t.TempDir(), Options{Clock: clock.fn()})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Cleanup(func() { db.Close() })
tc.setup(t, db)
for i := 0; i < tc.collectCalls; i++ {
clock.now = int64(200_000 + i*1000)
db.collectMetrics()
}
// Query each expected metric.
lastValue := func(name string) (float64, bool) {
q, err := db.Querier(math.MinInt64, math.MaxInt64)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer q.Close()
ss := q.Select(labels.MustNewMatcher(labels.MatchEqual, "__name__", name))
found := false
var last float64
for ss.Next() {
it := ss.At().Iterator()
for it.Next() {
_, last = it.At()
}
if err := it.Err(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
found = true
}
if err := ss.Err(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
return last, found
}
v, found := lastValue(MetricHeadSeries)
if !found {
t.Errorf("ingot_head_series not found")
}
if v != tc.wantHeadSeries {
t.Errorf("ingot_head_series: got %v, want %v", v, tc.wantHeadSeries)
}
v, found = lastValue(MetricBlocksTotal)
if !found {
t.Errorf("ingot_blocks_total not found")
}
if v != tc.wantBlocksTotal {
t.Errorf("ingot_blocks_total: got %v, want %v", v, tc.wantBlocksTotal)
}
v, found = lastValue(MetricCompactionsTotal)
if !found {
t.Errorf("ingot_compactions_total not found")
}
if v != tc.wantCompactions {
t.Errorf("ingot_compactions_total: got %v, want %v", v, tc.wantCompactions)
}
// Count total ingot_* series.
q, err := db.Querier(math.MinInt64, math.MaxInt64)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer q.Close()
ss := q.Select(labels.MustNewMatcher(labels.MatchRegexp, "__name__", "ingot_.*"))
count := 0
for ss.Next() {
count++
}
if err := ss.Err(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if count != tc.wantMetricCount {
t.Errorf("metric series count: got %v, want %v", count, tc.wantMetricCount)
}
})
}
}
type testClock struct {
now int64
}
func (c *testClock) fn() func() int64 {
return func() int64 { return c.now }
}