Skip to content

Commit

Permalink
Like any good library, bench doesn't panic()
Browse files Browse the repository at this point in the history
Instead, it bubbles up the error to the calling program, which
can decide whether or not to `panic()`
  • Loading branch information
cunnie committed Feb 25, 2018
1 parent caa075a commit 3646d94
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 43 deletions.
157 changes: 118 additions & 39 deletions bench/bench.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,52 +33,72 @@ 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++ {
go bm.singleThreadWriteTest(path.Join(fmt.Sprintf("bonnie.%d", i)), bytesWritten)
}
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++ {
go bm.singleThreadReadTest(path.Join(fmt.Sprintf("bonnie.%d", i)), bytesRead)
}
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++ {
go bm.singleThreadIOPSTest(path.Join(fmt.Sprintf("bonnie.%d", i)), opsPerformed)
}
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
Expand All @@ -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() {
Expand All @@ -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
}
Expand All @@ -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",
Expand All @@ -116,77 +136,126 @@ 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)

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
data := make([]byte, Blocksize)

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))
}
Expand All @@ -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}
}
8 changes: 4 additions & 4 deletions gobonniego/gobonniego.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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())
Expand Down

0 comments on commit 3646d94

Please sign in to comment.