Rewrote I/O benchmark sections

* Displays graph during test and in summary
* Reduce test area to speedup the benchmark
* Addresses issues #48 & #49
This commit is contained in:
2Shirt 2018-09-09 19:41:46 -06:00
parent 5ef7c9b16e
commit 793581ac22
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C

View file

@ -27,6 +27,30 @@ ATTRIBUTES = {
201: {'Warning': 1},
},
}
IO_VARS = {
'Block Size': 512*1024,
'Chunk Size': 16*1024**2,
'Minimum Dev Size': 8*1024**3,
'Minimum Test Size': 10*1024**3,
'Alt Test Size Factor': 0.01,
'Progress Refresh Rate': 5,
'Scale 16': [2**(0.6*x)+(16*x) for x in range(1,17)],
'Scale 32': [2**(0.6*x/2)+(16*x/2) for x in range(1,33)],
'Threshold Fail': 65*1024**2,
'Threshold Warn': 135*1024**2,
'Threshold Great': 750*1024**2,
'Graph Horizontal': ('', '', '', '', '', '', '', ''),
'Graph Horizontal Width': 40,
'Graph Vertical': (
'', '', '', '',
'', '', '', '',
'█▏', '█▎', '█▍', '█▌',
'█▋', '█▊', '█▉', '██',
'██▏', '██▎', '██▍', '██▌',
'██▋', '██▊', '██▉', '███',
'███▏', '███▎', '███▍', '███▌',
'███▋', '███▊', '███▉', '████'),
}
TESTS = {
'Prime95': {
'Enabled': False,
@ -49,6 +73,45 @@ TESTS = {
},
}
def generate_horizontal_graph(rates):
"""Generate two-line horizontal graph from rates, returns str."""
line_top = ''
line_bottom = ''
for r in rates:
step = get_graph_step(r, scale=16)
# Set color
r_color = COLORS['CLEAR']
if r < IO_VARS['Threshold Fail']:
r_color = COLORS['RED']
elif r < IO_VARS['Threshold Warn']:
r_color = COLORS['YELLOW']
elif r > IO_VARS['Threshold Great']:
r_color = COLORS['GREEN']
# Build graph
if step < 8:
line_top += ' '
line_bottom += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step])
else:
line_top += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step-8])
line_bottom += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][-1])
line_top += COLORS['CLEAR']
line_bottom += COLORS['CLEAR']
return '{}\n{}'.format(line_top, line_bottom)
def get_graph_step(rate, scale=16):
"""Get graph step based on rate and scale, returns int."""
m_rate = rate / (1024**2)
step = 0
scale_name = 'Scale {}'.format(scale)
for x in range(scale-1, -1, -1):
# Iterate over scale backwards
if m_rate >= IO_VARS[scale_name][x]:
step = x
break
return step
def get_read_rate(s):
"""Get read rate in bytes/s from dd progress output."""
real_rate = None
@ -254,28 +317,113 @@ def run_iobenchmark():
TESTS['iobenchmark']['Status'][name] = 'Working'
update_progress()
print_standard(' /dev/{:11} '.format(name+'...'), end='', flush=True)
run_program('tmux split-window -dl 5 {} {} {}'.format(
'hw-diags-iobenchmark',
'/dev/{}'.format(name),
progress_file).split())
wait_for_process('dd')
# Get dev size
cmd = 'sudo lsblk -bdno size /dev/{}'.format(name)
try:
result = run_program(cmd.split())
dev_size = result.stdout.decode().strip()
dev_size = int(dev_size)
except:
# Failed to get dev size, requires manual testing instead
TESTS['iobenchmark']['Status'][name] = 'Unknown'
continue
if dev_size < IO_VARS['Minimum Dev Size']:
TESTS['iobenchmark']['Status'][name] = 'Unknown'
continue
# Calculate dd values
## test_size is the area to be read in bytes
## If the dev is < 10Gb then it's the whole dev
## Otherwise it's the smaller of 10Gb and 1% of the dev
##
## test_chunks is the number of groups of "Chunk Size" in test_size
## This number is reduced to a multiple of the graph width in
## order to allow for the data to be condensed cleanly
##
## skip_blocks is the number of "Block Size" groups not tested
## skip_count is the number of blocks to skip per test_chunk
## skip_extra is how often to add an additional skip block
## This is needed to ensure an even testing across the dev
## This is calculated by using the fractional amount left off
## of the skip_count variable
test_size = min(IO_VARS['Minimum Test Size'], dev_size)
test_size = max(
test_size, dev_size*IO_VARS['Alt Test Size Factor'])
test_chunks = int(test_size // IO_VARS['Chunk Size'])
test_chunks -= test_chunks % IO_VARS['Graph Horizontal Width']
test_size = test_chunks * IO_VARS['Chunk Size']
skip_blocks = int((dev_size - test_size) // IO_VARS['Block Size'])
skip_count = int((skip_blocks / test_chunks) // 1)
skip_extra = 0
try:
skip_extra = 1 + int(1 / ((skip_blocks / test_chunks) % 1))
except ZeroDivisionError:
# skip_extra == 0 is fine
pass
# Open dd progress pane after initializing file
with open(progress_file, 'w') as f:
f.write('')
sleep(1)
cmd = 'tmux split-window -dp 75 -PF #D tail -f {}'.format(
progress_file)
result = run_program(cmd.split())
bottom_pane = result.stdout.decode().strip()
# Run dd read tests
offset = 0
read_rates = []
for i in range(test_chunks):
i += 1
s = skip_count
c = int(IO_VARS['Chunk Size'] / IO_VARS['Block Size'])
if skip_extra and i % skip_extra == 0:
s += 1
cmd = 'sudo dd bs={b} skip={s} count={c} if=/dev/{n} of={o}'.format(
b=IO_VARS['Block Size'],
s=offset+s,
c=c,
n=name,
o='/dev/null')
result = run_program(cmd.split())
result_str = result.stderr.decode().replace('\n', '')
read_rates.append(get_read_rate(result_str))
if i % IO_VARS['Progress Refresh Rate'] == 0:
# Update vertical graph
update_io_progress(
percent=i/test_chunks*100,
rate=read_rates[-1],
progress_file=progress_file)
# Update offset
offset += s + c
print_standard('Done', timestamp=False)
# Check results
with open(progress_file, 'r') as f:
text = f.read()
io_stats = text.replace('\r', '\n').split('\n')
try:
io_stats = [get_read_rate(s) for s in io_stats]
io_stats = [float(s/1048576) for s in io_stats if s]
TESTS['iobenchmark']['Results'][name] = 'Read speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format(
sum(io_stats) / len(io_stats),
min(io_stats),
max(io_stats))
TESTS['iobenchmark']['Status'][name] = 'CS'
except:
# Requires manual testing
TESTS['iobenchmark']['Status'][name] = 'NS'
# Close bottom pane
run_program(['tmux', 'kill-pane', '-t', bottom_pane])
# Build report
h_graph_rates = []
pos = 0
width = int(test_chunks / IO_VARS['Graph Horizontal Width'])
for i in range(IO_VARS['Graph Horizontal Width']):
# Append average rate for WIDTH number of rates to new array
h_graph_rates.append(sum(read_rates[pos:pos+width])/width)
pos += width
report = generate_horizontal_graph(h_graph_rates)
report += '\nRead speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format(
sum(read_rates)/len(read_rates)/1024**2,
min(read_rates)/1024**2,
max(read_rates)/1024**2)
TESTS['iobenchmark']['Results'][name] = report
# Set CS/NS
if min(read_rates) <= IO_VARS['Threshold Fail']:
TESTS['iobenchmark']['Status'][name] = 'NS'
elif min(read_rates) <= IO_VARS['Threshold Warn']:
TESTS['iobenchmark']['Status'][name] = 'Unknown'
else:
TESTS['iobenchmark']['Status'][name] = 'CS'
# Move temp file
shutil.move(progress_file, '{}/iobenchmark-{}.log'.format(
@ -759,13 +907,38 @@ def show_results():
and io_status not in ['Denied', 'OVERRIDE', 'Skipped']):
print_info('Benchmark:')
result = TESTS['iobenchmark']['Results'].get(name, '')
print_standard(' {}'.format(result))
for line in result.split('\n'):
print_standard(' {}'.format(line))
print_standard(' ')
# Done
pause('Press Enter to return to main menu... ')
run_program('tmux kill-pane -a'.split())
def update_io_progress(percent, rate, progress_file):
"""Update I/O progress file."""
bar_color = COLORS['CLEAR']
rate_color = COLORS['CLEAR']
step = get_graph_step(rate, scale=32)
if rate < IO_VARS['Threshold Fail']:
bar_color = COLORS['RED']
rate_color = COLORS['YELLOW']
elif rate < IO_VARS['Threshold Warn']:
bar_color = COLORS['YELLOW']
rate_color = COLORS['YELLOW']
elif rate > IO_VARS['Threshold Great']:
bar_color = COLORS['GREEN']
rate_color = COLORS['GREEN']
line = ' {p:5.1f}% {b_color}{b:<4} {r_color}{r:6.1f} Mb/s{c}\n'.format(
p=percent,
b_color=bar_color,
b=IO_VARS['Graph Vertical'][step],
r_color=rate_color,
r=rate/(1024**2),
c=COLORS['CLEAR'])
with open(progress_file, 'a') as f:
f.write(line)
def update_progress():
"""Update progress file."""
if 'Progress Out' not in TESTS:
@ -821,3 +994,4 @@ def update_progress():
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=4 sw=4 ts=4