From 3646d94e137effae02c901f94f430ff351557c99 Mon Sep 17 00:00:00 2001 From: Brian Cunnie Date: Sat, 24 Feb 2018 17:16:30 -0800 Subject: [PATCH] Like any good library, `bench` doesn't `panic()` Instead, it bubbles up the error to the calling program, which can decide whether or not to `panic()` --- bench/bench.go | 157 +++++++++++++++++++++++++++++---------- gobonniego/gobonniego.go | 8 +- 2 files changed, 122 insertions(+), 43 deletions(-) diff --git a/bench/bench.go b/bench/bench.go index 6e2fc96..99c6394 100644 --- a/bench/bench.go +++ b/bench/bench.go @@ -33,11 +33,16 @@ type Result struct { IOPSDuration time.Duration } +type ThreadResult struct { + Result int // bytes written, bytes read, or number of I/O operations + Error error +} + // the Sequential Write test must be called before the other two tests, for it creates the files -func (bm *Mark) RunSequentialWriteTest() { +func (bm *Mark) RunSequentialWriteTest() error { bm.fileSize = (int(bm.AggregateTestFilesSizeInGiB*(1<<10)) << 20) / bm.NumReadersWriters - bytesWritten := make(chan int) + bytesWritten := make(chan ThreadResult) start := time.Now() for i := 0; i < bm.NumReadersWriters; i++ { @@ -45,14 +50,19 @@ func (bm *Mark) RunSequentialWriteTest() { } bm.Result.WrittenBytes = 0 for i := 0; i < bm.NumReadersWriters; i++ { - bm.Result.WrittenBytes += <-bytesWritten + result := <-bytesWritten + if result.Error != nil { + return result.Error + } + bm.Result.WrittenBytes += result.Result } bm.Result.WrittenDuration = time.Now().Sub(start) + return nil } -func (bm *Mark) RunSequentialReadTest() { - bytesRead := make(chan int) +func (bm *Mark) RunSequentialReadTest() error { + bytesRead := make(chan ThreadResult) start := time.Now() for i := 0; i < bm.NumReadersWriters; i++ { @@ -60,14 +70,19 @@ func (bm *Mark) RunSequentialReadTest() { } bm.Result.ReadBytes = 0 for i := 0; i < bm.NumReadersWriters; i++ { - bm.Result.ReadBytes += <-bytesRead + result := <-bytesRead + if result.Error != nil { + return result.Error + } + bm.Result.ReadBytes += result.Result } bm.Result.ReadDuration = time.Now().Sub(start) + return nil } -func (bm *Mark) RunIOPSTest() { - opsPerformed := make(chan int) +func (bm *Mark) RunIOPSTest() error { + opsPerformed := make(chan ThreadResult) start := time.Now() for i := 0; i < bm.NumReadersWriters; i++ { @@ -75,10 +90,15 @@ func (bm *Mark) RunIOPSTest() { } bm.Result.IOPSOperations = 0 for i := 0; i < bm.NumReadersWriters; i++ { - bm.Result.IOPSOperations += <-opsPerformed + result := <-opsPerformed + if result.Error != nil { + return result.Error + } + bm.Result.IOPSOperations += result.Result } bm.Result.IOPSDuration = time.Now().Sub(start) + return nil } // calling program should `defer os.RemoveAll(bm.BonnieDir)` to clean up after run @@ -88,7 +108,7 @@ func (bm *Mark) SetBonnieDir(parentDir string) error { if err != nil { err = os.Mkdir(parentDir, 0755) if err != nil { - return fmt.Errorf("SetBonnieDir(): %s", err) + return err } } if !fileInfo.IsDir() { @@ -97,7 +117,7 @@ func (bm *Mark) SetBonnieDir(parentDir string) error { bm.BonnieDir = path.Join(parentDir, "gobonniego") err = os.Mkdir(bm.BonnieDir, 0755) if err != nil { - return fmt.Errorf("SetBonnieDir(): %s", err) + return err } return nil } @@ -107,7 +127,7 @@ func (bm *Mark) CreateRandomBlock() error { bm.randomBlock = make([]byte, Blocksize) lenRandom, err := rand.Read(bm.randomBlock) if err != nil { - return fmt.Errorf("CreateRandomBlock(): %s", err) + return err } if len(bm.randomBlock) != lenRandom { return fmt.Errorf("CreateRandomBlock(): RandomBlock didn't get the correct number of bytes, %d != %d", @@ -116,9 +136,14 @@ func (bm *Mark) CreateRandomBlock() error { return nil } -func (bm *Mark) singleThreadWriteTest(filename string, bytesWrittenChannel chan<- int) { +func (bm *Mark) singleThreadWriteTest(filename string, bytesWrittenChannel chan<- ThreadResult) { f, err := os.Create(path.Join(bm.BonnieDir, filename)) - check(err) + if err != nil { + bytesWrittenChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } defer f.Close() w := bufio.NewWriter(f) @@ -126,18 +151,40 @@ func (bm *Mark) singleThreadWriteTest(filename string, bytesWrittenChannel chan< bytesWritten := 0 for i := 0; i < bm.fileSize; i += len(bm.randomBlock) { n, err := w.Write(bm.randomBlock) - check(err) + if err != nil { + bytesWrittenChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } bytesWritten += n } - w.Flush() - f.Close() - bytesWrittenChannel <- bytesWritten + err = w.Flush() + if err != nil { + bytesWrittenChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } + err = f.Close() + if err != nil { + bytesWrittenChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } + bytesWrittenChannel <- ThreadResult{Result: bytesWritten, Error: nil} } -func (bm *Mark) singleThreadReadTest(filename string, bytesReadChannel chan<- int) { +func (bm *Mark) singleThreadReadTest(filename string, bytesReadChannel chan<- ThreadResult) { f, err := os.Open(path.Join(bm.BonnieDir, filename)) - check(err) + if err != nil { + bytesReadChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } defer f.Close() bytesRead := 0 @@ -145,48 +192,70 @@ func (bm *Mark) singleThreadReadTest(filename string, bytesReadChannel chan<- in for { n, err := f.Read(data) - bytesRead += n if err != nil { if err == io.EOF { break } - panic(err) + bytesReadChannel <- ThreadResult{ + Result: 0, Error: err, + } + return } + bytesRead += n // once every 127 blocks, do a sanity check. 127 is prime to avoid collisions // e.g. 128 would check every block, not every 128th block if bytesRead%127 == 0 { if !bytes.Equal(bm.randomBlock, data) { - panic("last block didn't match") + bytesReadChannel <- ThreadResult{ + Result: 0, Error: fmt.Errorf( + "Most recent block didn't match random block, bytes read (includes corruption): %d", + bytesRead), + } + return } } } - bytesReadChannel <- bytesRead - f.Close() + bytesReadChannel <- ThreadResult{Result: bytesRead, Error: nil} } -func (bm *Mark) singleThreadIOPSTest(filename string, numOpsChannel chan<- int) { +func (bm *Mark) singleThreadIOPSTest(filename string, numOpsChannel chan<- ThreadResult) { diskBlockSize := 0x1 << 9 // 512 bytes, nostalgia: in the olden (System V) days, disk blocks were 512 bytes fileInfo, err := os.Stat(path.Join(bm.BonnieDir, filename)) - check(err) + if err != nil { + numOpsChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } fileSizeLessOneDiskBlock := fileInfo.Size() - int64(diskBlockSize) // give myself room to not read past EOF numOperations := 0 f, err := os.OpenFile(path.Join(bm.BonnieDir, filename), os.O_RDWR, 0644) - check(err) + if err != nil { + numOpsChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } defer f.Close() data := make([]byte, diskBlockSize) checksum := make([]byte, diskBlockSize) start := time.Now() - for i := 0; time.Now().Sub(start).Seconds() < 15.0; i++ { // run for 15 seconds then blow this taco joint + for i := 0; time.Now().Sub(start).Seconds() < 15.0; i++ { // run for 15 seconds then blow this taco stand f.Seek(rand.Int63n(fileSizeLessOneDiskBlock), 0) // TPC-E has a reads:writes ratio of 9.7:1 http://www.cs.cmu.edu/~chensm/papers/TPCE-sigmod-record10.pdf // we round to 10:1 if i%10 != 0 { length, err := f.Read(data) - check(err) + if err != nil { + numOpsChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } if length != diskBlockSize { panic(fmt.Sprintf("I expected to read %d bytes, instead I read %d bytes!", diskBlockSize, length)) } @@ -195,19 +264,29 @@ func (bm *Mark) singleThreadIOPSTest(filename string, numOpsChannel chan<- int) } } else { length, err := f.Write(checksum) - check(err) + if err != nil { + numOpsChannel <- ThreadResult{ + Result: 0, Error: err, + } + return + } if length != diskBlockSize { - panic(fmt.Sprintf("I expected to write %d bytes, instead I wrote %d bytes!", diskBlockSize, length)) + numOpsChannel <- ThreadResult{ + Result: 0, + Error: fmt.Errorf("I expected to write %d bytes, instead I wrote %d bytes!", + diskBlockSize, length), + } + return } } numOperations++ } - f.Close() // redundant, I know. I want to make sure writes are flushed - numOpsChannel <- int(numOperations) -} - -func check(e error) { - if e != nil { - panic(e) + err = f.Close() // redundant, I know. I want to make sure writes are flushed + if err != nil { + numOpsChannel <- ThreadResult{ + Result: 0, Error: err, + } + return } + numOpsChannel <- ThreadResult{Result: int(numOperations), Error: nil} } diff --git a/gobonniego/gobonniego.go b/gobonniego/gobonniego.go index e4b78ce..eb5b82d 100644 --- a/gobonniego/gobonniego.go +++ b/gobonniego/gobonniego.go @@ -55,9 +55,9 @@ func main() { log.Printf("Bonnie working directory: %s", bonnieParentDir) } - bm.CreateRandomBlock() + check(bm.CreateRandomBlock()) - bm.RunSequentialWriteTest() + check(bm.RunSequentialWriteTest()) if verbose { log.Printf("Written (MiB): %d\n", bm.Result.WrittenBytes>>20) log.Printf("Written (MB): %f\n", float64(bm.Result.WrittenBytes)/1000000) @@ -66,7 +66,7 @@ func main() { fmt.Printf("Sequential Write MB/s: %0.2f\n", float64(bm.Result.WrittenBytes)/float64(bm.Result.WrittenDuration.Seconds())/1000000) - bm.RunSequentialReadTest() + check(bm.RunSequentialReadTest()) if verbose { log.Printf("Read (MiB): %d\n", bm.Result.ReadBytes>>20) log.Printf("Read (MB): %f\n", float64(bm.Result.ReadBytes)/1000000) @@ -75,7 +75,7 @@ func main() { fmt.Printf("Sequential Read MB/s: %0.2f\n", float64(bm.Result.ReadBytes)/float64(bm.Result.ReadDuration.Seconds())/1000000) - bm.RunIOPSTest() + check(bm.RunIOPSTest()) if verbose { log.Printf("operations %d\n", bm.Result.IOPSOperations) log.Printf("Duration (seconds): %f\n", bm.Result.IOPSDuration.Seconds())