Skip to content

Commit a9365e8

Browse files
committed
test(benchmark): initial benchmarks for ledger and database
Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
1 parent 1819090 commit a9365e8

File tree

7 files changed

+1756
-5
lines changed

7 files changed

+1756
-5
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ GOMODULE=$(shell grep ^module $(ROOT_DIR)/go.mod | awk '{ print $$2 }')
1313
# Set version strings based on git tag and current ref
1414
GO_LDFLAGS=-ldflags "-s -w -X '$(GOMODULE)/internal/version.Version=$(shell git describe --tags --exact-match 2>/dev/null)' -X '$(GOMODULE)/internal/version.CommitHash=$(shell git rev-parse --short HEAD)'"
1515

16-
.PHONY: build mod-tidy clean format golines test
16+
.PHONY: build mod-tidy clean format golines test bench test-load-profile
1717

1818
# Alias for building program binary
1919
build: $(BINARIES)
@@ -43,10 +43,19 @@ golines:
4343
test: mod-tidy
4444
go test -v -race ./...
4545

46+
bench: mod-tidy
47+
go test -run=^$$ -bench=. -benchmem ./...
48+
4649
test-load:
4750
rm -rf .dingo
4851
go run ./cmd/dingo load database/immutable/testdata
4952

53+
test-load-profile:
54+
rm -rf .dingo dingo
55+
go build -o dingo ./cmd/dingo
56+
./dingo --cpuprofile=cpu.prof --memprofile=mem.prof load database/immutable/testdata
57+
@echo "Profiling complete. Run 'go tool pprof cpu.prof' or 'go tool pprof mem.prof' to analyze"
58+
5059
# Build our program binaries
5160
# Depends on GO_FILES to determine when rebuild is needed
5261
$(BINARIES): mod-tidy $(GO_FILES)

cmd/dingo/main.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"fmt"
1919
"log/slog"
2020
"os"
21+
"runtime/pprof"
22+
"strings"
2123

2224
"github.com/blinklabs-io/dingo/internal/config"
2325
"github.com/blinklabs-io/dingo/internal/version"
@@ -37,7 +39,9 @@ func slogPrintf(format string, v ...any) {
3739

3840
var (
3941
globalFlags = struct {
40-
debug bool
42+
cpuprofile string
43+
memprofile string
44+
debug bool
4145
}{}
4246
configFile string
4347
)
@@ -72,6 +76,45 @@ func commonRun() *slog.Logger {
7276
}
7377

7478
func main() {
79+
// Parse profiling flags before cobra setup (handle both --flag=value and --flag value syntax)
80+
cpuprofile := ""
81+
memprofile := ""
82+
args := os.Args[1:] // Skip program name
83+
for i := 0; i < len(args); i++ {
84+
arg := args[i]
85+
switch {
86+
case strings.HasPrefix(arg, "--cpuprofile="):
87+
cpuprofile = strings.TrimPrefix(arg, "--cpuprofile=")
88+
case arg == "--cpuprofile" && i+1 < len(args):
89+
cpuprofile = args[i+1]
90+
i++ // Skip next arg
91+
case strings.HasPrefix(arg, "--memprofile="):
92+
memprofile = strings.TrimPrefix(arg, "--memprofile=")
93+
case arg == "--memprofile" && i+1 < len(args):
94+
memprofile = args[i+1]
95+
i++ // Skip next arg
96+
}
97+
}
98+
99+
// Initialize CPU profiling (starts immediately, stops on exit)
100+
if cpuprofile != "" {
101+
fmt.Fprintf(os.Stderr, "Starting CPU profiling to %s\n", cpuprofile)
102+
f, err := os.Create(cpuprofile)
103+
if err != nil {
104+
fmt.Fprintf(os.Stderr, "could not create CPU profile: %v\n", err)
105+
os.Exit(1)
106+
}
107+
defer f.Close()
108+
if err := pprof.StartCPUProfile(f); err != nil {
109+
fmt.Fprintf(os.Stderr, "could not start CPU profile: %v\n", err)
110+
os.Exit(1)
111+
}
112+
defer func() {
113+
pprof.StopCPUProfile()
114+
fmt.Fprintf(os.Stderr, "CPU profiling stopped\n")
115+
}()
116+
}
117+
75118
rootCmd := &cobra.Command{
76119
Use: programName,
77120
Run: func(cmd *cobra.Command, args []string) {
@@ -89,6 +132,10 @@ func main() {
89132
BoolVarP(&globalFlags.debug, "debug", "D", false, "enable debug logging")
90133
rootCmd.PersistentFlags().
91134
StringVar(&configFile, "config", "", "path to config file")
135+
rootCmd.PersistentFlags().
136+
StringVar(&globalFlags.cpuprofile, "cpuprofile", "", "write cpu profile to file")
137+
rootCmd.PersistentFlags().
138+
StringVar(&globalFlags.memprofile, "memprofile", "", "write memory profile to file")
92139

93140
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
94141
cfg, err := config.LoadConfig(configFile)
@@ -109,4 +156,20 @@ func main() {
109156
// NOTE: we purposely don't display the error, since cobra will have already displayed it
110157
os.Exit(1)
111158
}
159+
160+
// Finalize memory profiling (captures heap state at program end)
161+
if memprofile != "" {
162+
f, err := os.Create(memprofile)
163+
if err != nil {
164+
fmt.Fprintf(os.Stderr, "could not create memory profile: %v\n", err)
165+
os.Exit(1)
166+
}
167+
defer f.Close()
168+
if err := pprof.WriteHeapProfile(f); err != nil {
169+
fmt.Fprintf(os.Stderr, "could not write memory profile: %v\n", err)
170+
f.Close() // Explicitly close before exit
171+
os.Exit(1)
172+
}
173+
fmt.Fprintf(os.Stderr, "Memory profiling complete\n")
174+
}
112175
}

database/benchmark_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package database
16+
17+
import (
18+
"testing"
19+
)
20+
21+
// BenchmarkTransactionCreate benchmarks creating a read-only transaction
22+
func BenchmarkTransactionCreate(b *testing.B) {
23+
// Create a temporary database
24+
config := &Config{
25+
DataDir: "", // In-memory
26+
}
27+
db, err := New(config)
28+
if err != nil {
29+
b.Fatal(err)
30+
}
31+
defer db.Close()
32+
33+
b.ResetTimer() // Reset timer after setup
34+
for b.Loop() {
35+
txn := db.Transaction(false)
36+
txn.Commit() // Close the transaction to prevent leaks
37+
}
38+
}

database/plugin/blob/aws/commit_timestamp.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ func (b *BlobStoreS3) GetCommitTimestamp(ctx context.Context) (int64, error) {
3838
err,
3939
)
4040
if migrateErr := b.SetCommitTimestamp(ctx, ts); migrateErr != nil {
41-
b.logger.Errorf("failed to migrate plaintext commit timestamp: %v", migrateErr)
41+
b.logger.Errorf(
42+
"failed to migrate plaintext commit timestamp: %v",
43+
migrateErr,
44+
)
4245
}
4346
return ts, nil
4447
}

database/plugin/blob/gcs/commit_timestamp.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ func (b *BlobStoreGCS) GetCommitTimestamp(ctx context.Context) (int64, error) {
4848
err,
4949
)
5050
if migrateErr := b.SetCommitTimestamp(ctx, ts); migrateErr != nil {
51-
b.logger.Errorf("failed to migrate plaintext commit timestamp: %v", migrateErr)
51+
b.logger.Errorf(
52+
"failed to migrate plaintext commit timestamp: %v",
53+
migrateErr,
54+
)
5255
}
5356
return ts, nil
5457
}

database/plugin/metadata/sqlite/transaction.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func saveAccountIfNew(account *models.Account, txn *gorm.DB) error {
105105
}
106106

107107
// saveCertRecord saves a certificate record and returns any error
108-
func saveCertRecord(record interface{}, txn *gorm.DB) error {
108+
func saveCertRecord(record any, txn *gorm.DB) error {
109109
result := txn.Create(record)
110110
return result.Error
111111
}

0 commit comments

Comments
 (0)