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:
parent
5ef7c9b16e
commit
793581ac22
1 changed files with 195 additions and 21 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue