From 3176094d3e12dadc8b64d24e404ef9cd6f7b214b Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Wed, 6 Jul 2016 09:49:33 -0400 Subject: [PATCH 1/7] Make iops file executable From 6084465a13d4b273da9714a1c2ed1b23200ce072 Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Wed, 6 Jul 2016 09:56:18 -0400 Subject: [PATCH 2/7] Use the word "single" for runs with one block size instead of "exact" --- README.md | 2 +- iops | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0682c1b..c7d0ffe 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ iops ===== iops is an IO benachmark tool that performs random reads on block devices. -If an exact block size is not specified using -b, the the size starts with +If a single block size is not specified using -b, the the size starts with the physical sector size (defaulting to 4k) and doubles every iteration of the loop. You can switch the read pattern using -p toggle. diff --git a/iops b/iops index bcbc9fe..c75fe28 100755 --- a/iops +++ b/iops @@ -39,7 +39,7 @@ USAGE = """Copyright (c) 2008-2016 Benjamin Schweizer and others. iops is an IO benchmark tool that performs random reads on block devices. -If an exact block size is not specified using -b, the the size starts with +If a single block size is not specified using -b, the the size starts with the physical sector size (defaulting to 4k) and doubles every iteration of the loop. You can switch the read pattern using -p toggle. @@ -231,7 +231,7 @@ if __name__ == '__main__': blocksize = 512 # bytes units='si' # si|machine-readable dev = None # /dev/sda - exact_blocksize = False + single_blocksize = False pattern='random' # random|sequential if len(sys.argv) < 2: @@ -247,7 +247,7 @@ if __name__ == '__main__': units = 'machine-readable' elif arg in ['-b', '--block-size']: blocksize = int(sys.argv.pop(0)) - exact_blocksize = True + single_blocksize = True elif arg in ['-p', '--pattern']: pattern = sys.argv.pop(0) if not pattern in ['random', 'sequential']: @@ -284,7 +284,7 @@ if __name__ == '__main__': print " %sB blocks: %6.1f IO/s, %sB/s (%sbit/s)" % (greek(blocksize, 0, units), _iops, greek(bandwidth, 1, units), greek(8*bandwidth, 1, units)) - if exact_blocksize: + if single_blocksize: break blocksize *= 2 From 279965b63b0382b5e472ccf7b4ba774b099efd8d Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Wed, 6 Jul 2016 10:13:22 -0400 Subject: [PATCH 3/7] Cleanup intro documentation line for sequential. The original text only mentioned random reads. --- README.md | 4 ++-- iops | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c7d0ffe..57e74a0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ iops ===== -iops is an IO benachmark tool that performs random reads on block devices. +iops is an IO benachmark tool that reads block devices randomly (default). If a single block size is not specified using -b, the the size starts with the physical sector size (defaulting to 4k) and doubles every iteration of -the loop. You can switch the read pattern using -p toggle. +the loop. You can switch the read pattern to sequential using -p toggle. Usage ----- diff --git a/iops b/iops index c75fe28..efdc059 100755 --- a/iops +++ b/iops @@ -38,10 +38,10 @@ USAGE = """Copyright (c) 2008-2016 Benjamin Schweizer and others. -iops is an IO benchmark tool that performs random reads on block devices. +iops is an IO benachmark tool that reads block devices randomly (default). If a single block size is not specified using -b, the the size starts with the physical sector size (defaulting to 4k) and doubles every iteration of -the loop. You can switch the read pattern using -p toggle. +the loop. You can switch the read pattern to sequential using -p toggle. usage: From 57c0f81bd9754d3b68bcc5ad1b9d537885c98000 Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Mon, 18 Jul 2016 08:35:45 -0400 Subject: [PATCH 4/7] Fix sequential read wraparound seek to file start os.lseek needs the file descriptor number of the file handle --- iops | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iops b/iops index efdc059..fa63b5a 100755 --- a/iops +++ b/iops @@ -214,7 +214,7 @@ def iops(dev, blocksize=512, pattern='random', t=2): blockdata = fh.read(blocksize) # check wraparound if len(blockdata) == 0 and pattern=='sequential': - os.lseek(fd, 0, os.SEEK_SET) + os.lseek(fh.fileno(), 0, os.SEEK_SET) end = time.time() t = end - start From ab73eb343cc23dd22538d40d8d43b8a98f10c2e5 Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Mon, 18 Jul 2016 08:46:30 -0400 Subject: [PATCH 5/7] Don't increment count until after a successful read. Cleanup some obsolete function text too. This is just good general paranoia to keep results on short runs from being inflated by failed reads. There's no path out of the code yet that might count a read without doing one. --- iops | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iops b/iops index fa63b5a..b2d7489 100755 --- a/iops +++ b/iops @@ -197,8 +197,8 @@ def greek(value, precision=0, prefix=None): def iops(dev, blocksize=512, pattern='random', t=2): """measure input/output operations per second - Perform random 512b aligned reads of blocksize bytes on fh for t seconds - and print a stats line + Perform random or sequential aligned reads of blocksize bytes + on fh for t seconds and return statistics Returns: IOs/s """ @@ -206,12 +206,12 @@ def iops(dev, blocksize=512, pattern='random', t=2): count = 0 start = time.time() while time.time() < start+t: - count += 1 if pattern=='random': pos = random.randint(0, mediasize - blocksize) # need at least one block left pos &= ~(sectorsize-1) # sector alignment at blocksize fh.seek(pos) blockdata = fh.read(blocksize) + count += 1 # check wraparound if len(blockdata) == 0 and pattern=='sequential': os.lseek(fh.fileno(), 0, os.SEEK_SET) From c766b5b94837cf0ae3108b7bfdabdc921e602bae Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Mon, 18 Jul 2016 08:53:27 -0400 Subject: [PATCH 6/7] Reject tiny block size values. A few code assumptions didn't hold for blocksize < 512. This avoids the potential exploits of negative sizes too. --- iops | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iops b/iops index b2d7489..e1b53e6 100755 --- a/iops +++ b/iops @@ -248,6 +248,8 @@ if __name__ == '__main__': elif arg in ['-b', '--block-size']: blocksize = int(sys.argv.pop(0)) single_blocksize = True + if blocksize < 512: + raise SystemExit("block size too small: %d" % blocksize) elif arg in ['-p', '--pattern']: pattern = sys.argv.pop(0) if not pattern in ['random', 'sequential']: From f9897b7508936b6df8fea862edbd6ef6a030652d Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Mon, 18 Jul 2016 10:44:56 -0400 Subject: [PATCH 7/7] Refactor sequential reads to count blocks. Relying on a read to return no bytes was a fragile way to do wrap-around. Sequential read wrap-around was depending the EOF behavior of read() to return 0 bytes. That isn't the most robust approach possible. As one example, in cases cases where files/devices were not an exact multiple of block_size, this was counting the leftover small reads at the end of the file/device as a full read. That could inflate results a good bit at the end of runs where the block size became very large. Counting blocks also allows the seq read logic to be refactored to start at any position. The way all the sequential read threads were synchronized to start at the same spot is itself a problem. This fix was needed first. New code was tested with some temporary logging and a test long enough here to wrap around to the start: dd if=/dev/zero of=test.bin bs=1M count=1000 ./iops -n 1 -b 512 -p sequential -t 20 test.bin 1468848531.4 count=2047997 seq to pos 2047997 out of 2048000 blocks 1468848531.4 count=2047998 seq to pos 2047998 out of 2048000 blocks 1468848531.4 count=2047999 seq to pos 2047999 out of 2048000 blocks 1468848531.4 count=2048000 seq to pos 0 out of 2048000 blocks 1468848531.4 count=2048001 seq to pos 1 out of 2048000 blocks 1468848531.4 count=2048002 seq to pos 2 out of 2048000 blocks Here's the temporary debug lines: if pattern=='random': pos = random.randint(0, mediasize - blocksize) # need at least one block left pos &= ~(sectorsize-1) # sector alignment at blocksize fh.seek(pos) + else: + print time.time(), "count=%d seq to pos %d out of %d blocks" % (count,seq_block,blocks) --- iops | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/iops b/iops index e1b53e6..85a5ae0 100755 --- a/iops +++ b/iops @@ -203,6 +203,8 @@ def iops(dev, blocksize=512, pattern='random', t=2): """ fh = open(dev, 'r') + blocks = int(mediasize / blocksize) + seq_block = 0 count = 0 start = time.time() while time.time() < start+t: @@ -212,9 +214,11 @@ def iops(dev, blocksize=512, pattern='random', t=2): fh.seek(pos) blockdata = fh.read(blocksize) count += 1 + seq_block += 1 # check wraparound - if len(blockdata) == 0 and pattern=='sequential': + if pattern=='sequential' and seq_block >= blocks: os.lseek(fh.fileno(), 0, os.SEEK_SET) + seq_block = 0 end = time.time() t = end - start