Skip to content

Commit

Permalink
mkbench: emit and parse write amplification in write benchmarks
Browse files Browse the repository at this point in the history
To better understand the write amplification over the duration of a
write-throughput benchmark, emit the write amp after each interval.

Update `mkbench` to parse the write amplification and expose in the
output files.

Make `(*Metrics).Total()` return a `LevelMetrics` pointer, to allow
calling `WriteAmp()`, which has a pointer receiver.
  • Loading branch information
nicktrav committed Dec 1, 2021
1 parent f6167e3 commit afaa43d
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 30 deletions.
34 changes: 20 additions & 14 deletions cmd/pebble/write_bench.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,26 @@ func initWriteBench(cmd *cobra.Command) {
// whether the test passed or failed. Additional metadata associated with the
// test run is also captured.
type writeBenchResult struct {
name string
rate int // The rate at which the test is currently running.
passed bool // Was the test successful at this rate.
elapsed time.Duration // The total elapsed time of the test.
bytes uint64 // The size of the LSM.
levels int // The number of levels occupied in the LSM.
name string
rate int // The rate at which the test is currently running.
passed bool // Was the test successful at this rate.
elapsed time.Duration // The total elapsed time of the test.
bytes uint64 // The size of the LSM.
levels int // The number of levels occupied in the LSM.
writeAmp float64 // The write amplification.
}

// String implements fmt.Stringer, printing a raw benchmark line. These lines
// are used when performing analysis on a given benchmark run.
func (r writeBenchResult) String() string {
return fmt.Sprintf("BenchmarkRaw%s %d ops/sec %v pass %s elapsed %d bytes %d levels",
return fmt.Sprintf("BenchmarkRaw%s %d ops/sec %v pass %s elapsed %d bytes %d levels %.2f writeAmp",
r.name,
r.rate,
r.passed,
r.elapsed,
r.bytes,
r.levels,
r.writeAmp,
)
}

Expand Down Expand Up @@ -287,7 +289,7 @@ func runWriteBenchmark(_ *cobra.Command, args []string) error {
if writeBenchConfig.debug && i > 0 {
fmt.Printf("%s\n", m)
}
fmt.Println("___elapsed___clock___rate(desired)___rate(actual)___L0files___L0levels___levels______lsmBytes")
fmt.Println("___elapsed___clock___rate(desired)___rate(actual)___L0files___L0levels___levels______lsmBytes___writeAmp")
}

// Print the current stats.
Expand All @@ -300,6 +302,8 @@ func runWriteBenchmark(_ *cobra.Command, args []string) error {
}
}
lsmBytes := m.DiskSpaceUsage()
total := m.Total()
writeAmp := (&total).WriteAmp()

var currRate float64
var stalled bool
Expand All @@ -321,7 +325,7 @@ func runWriteBenchmark(_ *cobra.Command, args []string) error {
int(l0Sublevels) > writeBenchConfig.targetL0SubLevels

// Print the result for this tick.
fmt.Printf("%10s %7s %15d %14.1f %9d %10d %8d %13d\n",
fmt.Printf("%10s %7s %15d %14.1f %9d %10d %8d %13d %10.2f\n",
time.Duration(elapsed.Seconds()+0.5)*time.Second,
time.Duration(time.Since(clockStart).Seconds()+0.5)*time.Second,
desiredRate,
Expand All @@ -330,6 +334,7 @@ func runWriteBenchmark(_ *cobra.Command, args []string) error {
l0Sublevels,
nLevels,
lsmBytes,
writeAmp,
)

// If we're in cool-off mode, allow it to complete before resuming
Expand All @@ -351,11 +356,12 @@ func runWriteBenchmark(_ *cobra.Command, args []string) error {
}

r := writeBenchResult{
name: name,
rate: desiredRate,
elapsed: time.Duration(elapsed.Seconds()+0.5) * time.Second,
bytes: lsmBytes,
levels: nLevels,
name: name,
rate: desiredRate,
elapsed: time.Duration(elapsed.Seconds()+0.5) * time.Second,
bytes: lsmBytes,
levels: nLevels,
writeAmp: writeAmp,
}

if failed {
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions internal/mkbench/testdata/write-throughput/summary.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
"name": "write/values=1024",
"date": "20211027",
"opsSec": 58825,
"writeAmp": 1.17,
"summaryPath": "20211027-pebble-write-size=1024-run_1-summary.json"
},
{
"name": "write/values=1024",
"date": "20211028",
"opsSec": 65525,
"writeAmp": 1.39,
"summaryPath": "20211028-pebble-write-size=1024-run_1-summary.json"
}
]
Expand Down
39 changes: 27 additions & 12 deletions internal/mkbench/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -76,7 +77,7 @@ const (
summaryFilename = "summary.json"

// rawRunFmt is the format string for raw benchmark data.
rawRunFmt = "BenchmarkRaw%s %d ops/sec %v pass %s elapsed %d bytes %d levels"
rawRunFmt = "BenchmarkRaw%s %d ops/sec %v pass %s elapsed %d bytes %d levels %f writeAmp"
)

func getWriteCommand() *cobra.Command {
Expand Down Expand Up @@ -137,13 +138,14 @@ type writePoint struct {
passed bool
size uint64
levels int
writeAmp float64
}

// formatCSV returns a comma-separated string representation of the datapoint.
func (p writePoint) formatCSV() string {
return fmt.Sprintf(
"%d,%d,%v,%d,%d",
p.elapsedSecs, p.opsSec, p.passed, p.size, p.levels)
"%d,%d,%v,%d,%d,%.2f",
p.elapsedSecs, p.opsSec, p.passed, p.size, p.levels, p.writeAmp)
}

// rawWriteRun is a collection of datapoints from a single instance of a
Expand Down Expand Up @@ -178,6 +180,11 @@ func (r *rawWriteRun) opsPerSecSplit() int {
return split
}

// writeAmp returns the value of the write-amplification at the end of the run.
func (r *rawWriteRun) writeAmp() float64 {
return r.points[len(r.points)-1].writeAmp
}

// formatCSV returns a comma-separated string representation of the rawWriteRun.
// The value itself is a newline-delimited string value comprised of the CSV
// representation of the individual writePoints.
Expand All @@ -194,10 +201,11 @@ func (r rawWriteRun) formatCSV() string {
// value, in addition to a path to the summary.json file with the combined data
// for the run.
type writeRunSummary struct {
Name string `json:"name"`
Date string `json:"date"`
OpsSec int `json:"opsSec"`
SummaryPath string `json:"summaryPath"`
Name string `json:"name"`
Date string `json:"date"`
OpsSec int `json:"opsSec"`
WriteAmp float64 `json:"writeAmp"`
SummaryPath string `json:"summaryPath"`
}

// writeWorkloadSummary is an alias for a slice of writeRunSummaries.
Expand Down Expand Up @@ -232,18 +240,25 @@ func (r writeRun) summaryFilename() string {

// summarize computes a writeRunSummary datapoint for the writeRun.
func (r writeRun) summarize() writeRunSummary {
var sum int
var (
sumOpsSec int
sumWriteAmp float64
)
for _, rr := range r.rawRuns {
sum += rr.opsPerSecSplit()
sumOpsSec += rr.opsPerSecSplit()
sumWriteAmp += rr.writeAmp()
}
l := len(r.rawRuns)

return writeRunSummary{
Name: r.name,
Date: r.date,
SummaryPath: r.summaryFilename(),
// Calculate an average across all raw runs in this run.
// TODO(travers): test how this works in practice, after we have
// gathered enough data.
OpsSec: sum / len(r.rawRuns),
OpsSec: sumOpsSec / l,
WriteAmp: math.Round(100*sumWriteAmp/float64(l)) / 100, // round to 2dp.
}
}

Expand Down Expand Up @@ -405,8 +420,8 @@ func (l *writeLoader) loadRaw() error {
var p writePoint
var nameInner, elapsed string
n, err := fmt.Sscanf(line, rawRunFmt,
&nameInner, &p.opsSec, &p.passed, &elapsed, &p.size, &p.levels)
if err != nil || n != 6 {
&nameInner, &p.opsSec, &p.passed, &elapsed, &p.size, &p.levels, &p.writeAmp)
if err != nil || n != 7 {
// Stumble forward on error.
_, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", s.Text(), err)
continue
Expand Down

0 comments on commit afaa43d

Please sign in to comment.