Skip to content

Commit e044c56

Browse files
authored
Persist benchmark results in CI (#461)
Benchmark results are now collected to a json file which will be appended to a file in s3. Another action will generate static HTML from these results and push them to GitHub Pages.
1 parent 30d131e commit e044c56

File tree

4 files changed

+136
-7
lines changed

4 files changed

+136
-7
lines changed

.github/workflows/benchmark.yaml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
name: Benchmark
22
on:
3+
workflow_dispatch:
34
push:
45
branches:
56
- main
67
permissions:
8+
id-token: write # For getting AWS permissions
79
contents: read
810
packages: read
911
jobs:
@@ -25,4 +27,37 @@ jobs:
2527
- name: Run benchmarks
2628
run: make bench
2729
env:
28-
POSTGRES_VERSION: ${{ matrix.pgVersion }}
30+
POSTGRES_VERSION: ${{ matrix.pgVersion }}
31+
32+
- name: Upload results
33+
uses: actions/upload-artifact@v4
34+
with:
35+
name: benchmark_result_${{ matrix.pgVersion }}.json
36+
path: internal/benchmarks/benchmark_result_${{ matrix.pgVersion }}.json
37+
38+
gather:
39+
name: 'Gather results'
40+
runs-on: ubuntu-latest
41+
needs: [benchmark]
42+
43+
steps:
44+
- uses: actions/download-artifact@v4
45+
with:
46+
path: ./results/
47+
merge-multiple: true
48+
49+
- name: Configure AWS Credentials
50+
uses: aws-actions/configure-aws-credentials@v2
51+
with:
52+
role-to-assume: arn:aws:iam::493985724844:role/pgroll-benchmark-results-access
53+
aws-region: us-east-1
54+
mask-aws-account-id: 'no'
55+
56+
- name: Download current results from S3
57+
run: aws s3 cp s3://pgroll-benchmark-results/benchmark-results.json ./benchmark-results.json
58+
59+
- name: Append new results
60+
run: cat results/*.json >> benchmark-results.json
61+
62+
- name: Upload combined results
63+
run: aws s3 cp ./benchmark-results.json s3://pgroll-benchmark-results/benchmark-results.json

internal/benchmarks/benchmarks.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package benchmarks
4+
5+
import (
6+
"os"
7+
"time"
8+
)
9+
10+
type Reports struct {
11+
GitSHA string
12+
PostgresVersion string
13+
Timestamp int64
14+
Reports []Report
15+
}
16+
17+
func (r *Reports) AddReport(report Report) {
18+
r.Reports = append(r.Reports, report)
19+
}
20+
21+
func getPostgresVersion() string {
22+
return os.Getenv("POSTGRES_VERSION")
23+
}
24+
25+
func newReports() *Reports {
26+
return &Reports{
27+
GitSHA: os.Getenv("GITHUB_SHA"),
28+
PostgresVersion: getPostgresVersion(),
29+
Timestamp: time.Now().Unix(),
30+
Reports: []Report{},
31+
}
32+
}
33+
34+
type Report struct {
35+
Name string
36+
RowCount int
37+
Unit string
38+
Result float64
39+
}

internal/benchmarks/benchmarks_test.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ package benchmarks
55
import (
66
"context"
77
"database/sql"
8+
"encoding/json"
9+
"fmt"
10+
"os"
811
"strconv"
912
"testing"
1013

@@ -19,10 +22,32 @@ import (
1922

2023
const unitRowsPerSecond = "rows/s"
2124

22-
var rowCounts = []int{10_000, 100_000, 300_000}
25+
var (
26+
rowCounts = []int{10_000, 100_000, 300_000}
27+
reports = newReports()
28+
)
2329

2430
func TestMain(m *testing.M) {
25-
testutils.SharedTestMain(m)
31+
testutils.SharedTestMain(m, func() (err error) {
32+
// Only run in GitHub actions
33+
if os.Getenv("GITHUB_ACTIONS") != "true" {
34+
return nil
35+
}
36+
37+
w, err := os.Create(fmt.Sprintf("benchmark_result_%s.json", getPostgresVersion()))
38+
if err != nil {
39+
return fmt.Errorf("creating report file: %w", err)
40+
}
41+
defer func() {
42+
err = w.Close()
43+
}()
44+
45+
encoder := json.NewEncoder(w)
46+
if err := encoder.Encode(reports); err != nil {
47+
return fmt.Errorf("encoding report file: %w", err)
48+
}
49+
return nil
50+
})
2651
}
2752

2853
func BenchmarkBackfill(b *testing.B) {
@@ -48,6 +73,8 @@ func BenchmarkBackfill(b *testing.B) {
4873
b.Logf("Backfilled %d rows in %s", rowCount, b.Elapsed())
4974
rowsPerSecond := float64(rowCount) / b.Elapsed().Seconds()
5075
b.ReportMetric(rowsPerSecond, unitRowsPerSecond)
76+
77+
addRowsPerSecond(b, rowCount, rowsPerSecond)
5178
})
5279
})
5380
}
@@ -87,6 +114,8 @@ func BenchmarkWriteAmplification(b *testing.B) {
87114
b.StopTimer()
88115
rowsPerSecond := float64(rowCount) / b.Elapsed().Seconds()
89116
b.ReportMetric(rowsPerSecond, unitRowsPerSecond)
117+
118+
addRowsPerSecond(b, rowCount, rowsPerSecond)
90119
})
91120
})
92121
}
@@ -116,6 +145,8 @@ func BenchmarkWriteAmplification(b *testing.B) {
116145
b.StopTimer()
117146
rowsPerSecond := float64(rowCount) / b.Elapsed().Seconds()
118147
b.ReportMetric(rowsPerSecond, unitRowsPerSecond)
148+
149+
addRowsPerSecond(b, rowCount, rowsPerSecond)
119150
})
120151
})
121152
}
@@ -149,6 +180,15 @@ func setupInitialTable(tb testing.TB, ctx context.Context, testSchema string, mi
149180
seed(tb, rowCount, db)
150181
}
151182

183+
func addRowsPerSecond(b *testing.B, rowCount int, perSecond float64) {
184+
reports.AddReport(Report{
185+
Name: b.Name(),
186+
Unit: unitRowsPerSecond,
187+
RowCount: rowCount,
188+
Result: perSecond,
189+
})
190+
}
191+
152192
// Simple table with a nullable `name` field.
153193
var migCreateTable = migrations.Migration{
154194
Name: "01_create_table",

internal/testutils/util.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ const defaultPostgresVersion = "15.3"
2828
// tConnStr holds the connection string to the test container created in TestMain.
2929
var tConnStr string
3030

31-
// SharedTestMain starts a postgres container to be used by all tests in a package.
32-
// Each test then connects to the container and creates a new database.
33-
func SharedTestMain(m *testing.M) {
31+
// SharedTestMain starts a postgres container to be used by all tests in a package. Each test then
32+
// connects to the container and creates a new database. Optional functions that will run after all
33+
// tests can be added and should return a nil error to indicate they ran successfully. If they return
34+
// an error all subsequent functions will be skipped.
35+
func SharedTestMain(m *testing.M, postRunHooks ...func() error) {
3436
ctx := context.Background()
3537

3638
waitForLogs := wait.
@@ -73,7 +75,20 @@ func SharedTestMain(m *testing.M) {
7375
log.Printf("Failed to terminate container: %v", err)
7476
}
7577

76-
os.Exit(exitCode)
78+
if exitCode != 0 {
79+
log.Printf("Non zero exit code (%d), skipping post run hooks", exitCode)
80+
os.Exit(exitCode)
81+
}
82+
83+
for _, hook := range postRunHooks {
84+
err := hook()
85+
if err != nil {
86+
log.Printf("Post-run hook failed: %v", err)
87+
os.Exit(1)
88+
}
89+
}
90+
91+
os.Exit(0)
7792
}
7893

7994
// TestSchema returns the schema in which migration tests apply migrations. By

0 commit comments

Comments
 (0)