This repository has been archived by the owner on Mar 9, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #135 from benbjohnson/bench
Bench
- Loading branch information
Showing
7 changed files
with
308 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/binary" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"runtime" | ||
"runtime/pprof" | ||
"time" | ||
|
||
"github.com/boltdb/bolt" | ||
) | ||
|
||
// File handlers for the various profiles. | ||
var cpuprofile, memprofile, blockprofile *os.File | ||
|
||
var benchBucketName = []byte("bench") | ||
|
||
// Bench executes a customizable, synthetic benchmark against Bolt. | ||
func Bench(options *BenchOptions) { | ||
var results BenchResults | ||
|
||
// Find temporary location. | ||
path := tempfile() | ||
defer os.Remove(path) | ||
|
||
// Create database. | ||
db, err := bolt.Open(path, 0600) | ||
if err != nil { | ||
fatal(err) | ||
return | ||
} | ||
defer db.Close() | ||
|
||
// Start profiling for writes. | ||
if options.ProfileMode == "rw" || options.ProfileMode == "w" { | ||
benchStartProfiling(options) | ||
} | ||
|
||
// Write to the database. | ||
if err := benchWrite(db, options, &results); err != nil { | ||
fatal("bench: write: ", err) | ||
} | ||
|
||
// Stop profiling for writes only. | ||
if options.ProfileMode == "w" { | ||
benchStopProfiling() | ||
} | ||
|
||
// Start profiling for reads. | ||
if options.ProfileMode == "r" { | ||
benchStartProfiling(options) | ||
} | ||
|
||
// Read from the database. | ||
if err := benchRead(db, options, &results); err != nil { | ||
fatal("bench: read: ", err) | ||
} | ||
|
||
// Stop profiling for writes only. | ||
if options.ProfileMode == "rw" || options.ProfileMode == "r" { | ||
benchStopProfiling() | ||
} | ||
|
||
// Print results. | ||
fmt.Printf("# Write\t%v\t(%v/op)\t(%v op/sec)\n", results.WriteDuration, results.WriteOpDuration(), results.WriteOpsPerSecond()) | ||
fmt.Printf("# Read\t%v\t(%v/op)\t(%v op/sec)\n", results.ReadDuration, results.ReadOpDuration(), results.ReadOpsPerSecond()) | ||
fmt.Println("") | ||
} | ||
|
||
// Writes to the database. | ||
func benchWrite(db *bolt.DB, options *BenchOptions, results *BenchResults) error { | ||
var err error | ||
var t = time.Now() | ||
|
||
switch options.WriteMode { | ||
case "seq": | ||
err = benchWriteSequential(db, options, results) | ||
default: | ||
return fmt.Errorf("invalid write mode: %s", options.WriteMode) | ||
} | ||
|
||
results.WriteDuration = time.Since(t) | ||
|
||
return err | ||
} | ||
|
||
func benchWriteSequential(db *bolt.DB, options *BenchOptions, results *BenchResults) error { | ||
results.WriteOps = options.Iterations | ||
|
||
return db.Update(func(tx *bolt.Tx) error { | ||
b, _ := tx.CreateBucketIfNotExists(benchBucketName) | ||
|
||
for i := 0; i < options.Iterations; i++ { | ||
var key = make([]byte, options.KeySize) | ||
var value = make([]byte, options.ValueSize) | ||
binary.BigEndian.PutUint32(key, uint32(i)) | ||
if err := b.Put(key, value); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
|
||
// Reads from the database. | ||
func benchRead(db *bolt.DB, options *BenchOptions, results *BenchResults) error { | ||
var err error | ||
var t = time.Now() | ||
|
||
switch options.ReadMode { | ||
case "seq": | ||
err = benchReadSequential(db, options, results) | ||
default: | ||
return fmt.Errorf("invalid read mode: %s", options.ReadMode) | ||
} | ||
|
||
results.ReadDuration = time.Since(t) | ||
|
||
return err | ||
} | ||
|
||
func benchReadSequential(db *bolt.DB, options *BenchOptions, results *BenchResults) error { | ||
return db.View(func(tx *bolt.Tx) error { | ||
var t = time.Now() | ||
|
||
for { | ||
c := tx.Bucket(benchBucketName).Cursor() | ||
var count int | ||
for k, v := c.First(); k != nil; k, v = c.Next() { | ||
if v == nil { | ||
return errors.New("invalid value") | ||
} | ||
count++ | ||
} | ||
|
||
if count != options.Iterations { | ||
return fmt.Errorf("read seq: iter mismatch: expected %d, got %d", options.Iterations, count) | ||
} | ||
|
||
results.ReadOps += count | ||
|
||
// Make sure we do this for at least a second. | ||
if time.Since(t) >= time.Second { | ||
break | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
|
||
// Starts all profiles set on the options. | ||
func benchStartProfiling(options *BenchOptions) { | ||
var err error | ||
|
||
// Start CPU profiling. | ||
if options.CPUProfile != "" { | ||
cpuprofile, err = os.Create(options.CPUProfile) | ||
if err != nil { | ||
fatal("bench: could not create cpu profile %q: %v", options.CPUProfile, err) | ||
} | ||
pprof.StartCPUProfile(cpuprofile) | ||
} | ||
|
||
// Start memory profiling. | ||
if options.MemProfile != "" { | ||
memprofile, err = os.Create(options.MemProfile) | ||
if err != nil { | ||
fatal("bench: could not create memory profile %q: %v", options.MemProfile, err) | ||
} | ||
runtime.MemProfileRate = 4096 | ||
} | ||
|
||
// Start fatal profiling. | ||
if options.BlockProfile != "" { | ||
blockprofile, err = os.Create(options.BlockProfile) | ||
if err != nil { | ||
fatal("bench: could not create block profile %q: %v", options.BlockProfile, err) | ||
} | ||
runtime.SetBlockProfileRate(1) | ||
} | ||
} | ||
|
||
// Stops all profiles. | ||
func benchStopProfiling() { | ||
if cpuprofile != nil { | ||
pprof.StopCPUProfile() | ||
cpuprofile.Close() | ||
cpuprofile = nil | ||
} | ||
|
||
if memprofile != nil { | ||
pprof.Lookup("heap").WriteTo(memprofile, 0) | ||
memprofile.Close() | ||
memprofile = nil | ||
} | ||
|
||
if blockprofile != nil { | ||
pprof.Lookup("block").WriteTo(blockprofile, 0) | ||
blockprofile.Close() | ||
blockprofile = nil | ||
runtime.SetBlockProfileRate(0) | ||
} | ||
} | ||
|
||
// BenchOptions represents the set of options that can be passed to Bench(). | ||
type BenchOptions struct { | ||
ProfileMode string | ||
WriteMode string | ||
ReadMode string | ||
Iterations int | ||
KeySize int | ||
ValueSize int | ||
CPUProfile string | ||
MemProfile string | ||
BlockProfile string | ||
} | ||
|
||
// BenchResults represents the performance results of the benchmark. | ||
type BenchResults struct { | ||
WriteOps int | ||
WriteDuration time.Duration | ||
ReadOps int | ||
ReadDuration time.Duration | ||
} | ||
|
||
// Returns the duration for a single write operation. | ||
func (r *BenchResults) WriteOpDuration() time.Duration { | ||
if r.WriteOps == 0 { | ||
return 0 | ||
} | ||
return r.WriteDuration / time.Duration(r.WriteOps) | ||
} | ||
|
||
// Returns average number of write operations that can be performed per second. | ||
func (r *BenchResults) WriteOpsPerSecond() int { | ||
var op = r.WriteOpDuration() | ||
if op == 0 { | ||
return 0 | ||
} | ||
return int(time.Second) / int(op) | ||
} | ||
|
||
// Returns the duration for a single read operation. | ||
func (r *BenchResults) ReadOpDuration() time.Duration { | ||
if r.ReadOps == 0 { | ||
return 0 | ||
} | ||
return r.ReadDuration / time.Duration(r.ReadOps) | ||
} | ||
|
||
// Returns average number of read operations that can be performed per second. | ||
func (r *BenchResults) ReadOpsPerSecond() int { | ||
var op = r.ReadOpDuration() | ||
if op == 0 { | ||
return 0 | ||
} | ||
return int(time.Second) / int(op) | ||
} | ||
|
||
// tempfile returns a temporary file path. | ||
func tempfile() string { | ||
f, _ := ioutil.TempFile("", "bolt-bench-") | ||
f.Close() | ||
os.Remove(f.Name()) | ||
return f.Name() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.