Skip to content

Commit

Permalink
Add benchmarks.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmalloc committed Mar 16, 2024
1 parent fceb043 commit a06b0be
Show file tree
Hide file tree
Showing 13 changed files with 579 additions and 141 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ The format is based on [Keep a Changelog], and this project adheres to

## Unreleased

### Added

- Added `journal.RunBenchmarks()` and `kv.RunBenchmarks()` to run generic
benchmarks for a journal or key-value store implementation.

### Changed

- **[BC]** The PostgreSQL drivers `pgjournal` and `pgkv` now assign each journal
Expand Down
45 changes: 33 additions & 12 deletions driver/aws/dynamojournal/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,57 @@ import (
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/service/dynamodb"
. "github.com/dogmatiq/persistencekit/driver/aws/dynamojournal"
"github.com/dogmatiq/persistencekit/driver/aws/internal/dynamox"
"github.com/dogmatiq/persistencekit/journal"
)

func TestStore(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
client, table := setup(t)
journal.RunTests(
t,
func(t *testing.T) journal.Store {
return &Store{
Client: client,
Table: table,
}
},
)
}

func BenchmarkStore(b *testing.B) {
client, table := setup(b)
journal.RunBenchmarks(
b,
func(b *testing.B) journal.Store {
return &Store{
Client: client,
Table: table,
}
},
)
}

func setup(t testing.TB) (*dynamodb.Client, string) {
client := dynamox.NewTestClient(t)
table := "journal"

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

if err := CreateTable(ctx, client, table); err != nil {
t.Fatal(err)
}

t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

if err := dynamox.DeleteTableIfNotExists(ctx, client, table); err != nil {
t.Fatal(err)
}

cancel()
})

journal.RunTests(
t,
func(t *testing.T) journal.Store {
return &Store{
Client: client,
Table: table,
}
},
)
return client, table
}
9 changes: 9 additions & 0 deletions driver/memory/memoryjournal/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ func TestStore(t *testing.T) {
},
)
}

func BenchmarkStore(b *testing.B) {
journal.RunBenchmarks(
b,
func(b *testing.B) journal.Store {
return &Store{}
},
)
}
39 changes: 30 additions & 9 deletions driver/sql/postgres/pgjournal/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,43 @@ package pgjournal_test

import (
"context"
"database/sql"
"testing"
"time"

. "github.com/dogmatiq/persistencekit/driver/sql/postgres/pgjournal"
"github.com/dogmatiq/persistencekit/journal"
"github.com/dogmatiq/sqltest"
)

func TestStore(t *testing.T) {
ctx := context.Background()
db := setup(t)
journal.RunTests(
t,
func(t *testing.T) journal.Store {
return &Store{
DB: db,
}
},
)
}

func BenchmarkStore(b *testing.B) {
db := setup(b)
journal.RunBenchmarks(
b,
func(b *testing.B) journal.Store {
return &Store{
DB: db,
}
},
)
}

func setup(t testing.TB) *sql.DB {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

database, err := sqltest.NewDatabase(ctx, sqltest.PGXDriver, sqltest.PostgreSQL)
if err != nil {
t.Fatalf("cannot create test database: %s", err)
Expand All @@ -31,12 +59,5 @@ func TestStore(t *testing.T) {
}
})

journal.RunTests(
t,
func(t *testing.T) journal.Store {
return &Store{
DB: db,
}
},
)
return db
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ require (
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.2 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/lib/pq v1.10.2 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.10.0/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
Expand Down
2 changes: 2 additions & 0 deletions internal/benchmark/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package benchmark provides utilities for running benchmarks.
package benchmark
72 changes: 72 additions & 0 deletions internal/benchmark/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package benchmark

import (
"context"
"testing"
"time"
)

// Run benchmarks fn.
func Run(
b *testing.B,
setup func(context.Context) error,
before func(context.Context) error,
fn func(context.Context) error,
after func(context.Context) error,
) {
b.StopTimer()
checkIterationThreshold(b)

if setup != nil {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

err := setup(ctx)
if err != nil {
b.Fatal(err)
}
}

for i := 0; i < b.N; i++ {
if before != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
err := before(ctx)
cancel()
if err != nil {
b.Fatal(err)
}
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

b.StartTimer()
err := fn(ctx)
b.StopTimer()

cancel()

if err != nil {
b.Fatal(err)
}

if after != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
err := after(ctx)
cancel()
if err != nil {
b.Fatal(err)
}
}
}
}

// checkIterationThreshold skips the benchmark if the number of iterations is
// too high. This usually occurs when the benchmarking framework is unable to
// measure the duration of each iteration, typically because the benchmarked
// code is "too fast".
func checkIterationThreshold(b *testing.B) {
const threshold = 1_000_000
if b.N >= threshold {
b.Skipf("benchmark skipped, too many iterations (%d); benchmarked code is likely too fast to measure meaningfully", b.N)
}
}
Loading

0 comments on commit a06b0be

Please sign in to comment.