package index
import (
"fmt"
"path/filepath"
"testing"
"time"
)
// Budgets from RFC 0013 §9, measured against this fixture rather than
// assumed: Activity on the tree axis under 200ms, ActivityDays cold
// under 300ms. They are not asserted — a benchmark that fails on a busy
// machine teaches people to ignore it — but a regression shows up as a
// number nobody has to hunt for.
// benchIndex builds a store the size of sqlite's: 40,000 check-ins over
// ~2,000 days by 12 people, one in ten a merge, five files each, with
// the ancestor directory rows the fold writes (~240,000 filechange
// rows in all).
func benchIndex(b *testing.B) *Index {
b.Helper()
ix, err := Open(filepath.Join(b.TempDir(), "index.db"))
if err != nil {
b.Fatal(err)
}
b.Cleanup(func() { ix.Close() })
const checkins = 40000
dirs := []string{"src", "test", "doc", "tool", "ext"}
users := make([]string, 12)
for i := range users {
users[i] = fmt.Sprintf("user%02d", i)
}
start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
tx, err := ix.db.Begin()
if err != nil {
b.Fatal(err)
}
ci, err := tx.Prepare(`INSERT INTO checkin (hash, date, user, comment, branch)
VALUES (?, ?, ?, '', ?)`)
if err != nil {
b.Fatal(err)
}
par, err := tx.Prepare(`INSERT INTO checkin_parent (child, parent, ord) VALUES (?, ?, ?)`)
if err != nil {
b.Fatal(err)
}
fc, err := tx.Prepare(`INSERT INTO filechange (path, checkin, date, dir) VALUES (?, ?, ?, ?)`)
if err != nil {
b.Fatal(err)
}
for i := range checkins {
hash := fmt.Sprintf("c%06d", i)
date := start.AddDate(0, 0, i/20).Format("2006-01-02") +
fmt.Sprintf("T%02d:%02d:00.000", i%24, i%60)
branch := "trunk"
if i%7 == 0 {
branch = "feat"
}
if _, err := ci.Exec(hash, date, users[i%len(users)], branch); err != nil {
b.Fatal(err)
}
if i > 0 {
if _, err := par.Exec(hash, fmt.Sprintf("c%06d", i-1), 0); err != nil {
b.Fatal(err)
}
if i%10 == 0 {
if _, err := par.Exec(hash, fmt.Sprintf("c%06d", i/2), 1); err != nil {
b.Fatal(err)
}
}
}
seen := map[string]bool{}
for k := range 5 {
d := dirs[(i+k)%len(dirs)]
if _, err := fc.Exec(fmt.Sprintf("%s/f%04d.go", d, (i*5+k)%800),
hash, date, 0); err != nil {
b.Fatal(err)
}
if !seen[d] {
seen[d] = true
if _, err := fc.Exec(d, hash, date, 1); err != nil {
b.Fatal(err)
}
}
}
}
if err := tx.Commit(); err != nil {
b.Fatal(err)
}
return ix
}
func BenchmarkActivity(b *testing.B) {
ix := benchIndex(b)
b.ResetTimer()
for b.Loop() {
got, err := ix.Activity(ActivityQuery{})
if err != nil {
b.Fatal(err)
}
if len(got.Rows) == 0 || len(got.Touches) == 0 {
b.Fatalf("empty window: %d rows %d touches", len(got.Rows), len(got.Touches))
}
}
}
func BenchmarkActivityDays(b *testing.B) {
ix := benchIndex(b)
b.ResetTimer()
for b.Loop() {
// Cold each time: the memo is the point of the warm path, and
// the number that matters is what a fold makes somebody pay.
ix.invalidateCounts()
got, err := ix.ActivityDays("")
if err != nil {
b.Fatal(err)
}
if len(got.Days) == 0 {
b.Fatal("no days")
}
}
}
func BenchmarkActivityDaysWarm(b *testing.B) {
ix := benchIndex(b)
if _, err := ix.ActivityDays(""); err != nil {
b.Fatal(err)
}
b.ResetTimer()
for b.Loop() {
if _, err := ix.ActivityDays(""); err != nil {
b.Fatal(err)
}
}
}