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},
|
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 = {
|
TESTS = {
|
||||||
'Prime95': {
|
'Prime95': {
|
||||||
'Enabled': False,
|
'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):
|
def get_read_rate(s):
|
||||||
"""Get read rate in bytes/s from dd progress output."""
|
"""Get read rate in bytes/s from dd progress output."""
|
||||||
real_rate = None
|
real_rate = None
|
||||||
|
|
@ -254,28 +317,113 @@ def run_iobenchmark():
|
||||||
TESTS['iobenchmark']['Status'][name] = 'Working'
|
TESTS['iobenchmark']['Status'][name] = 'Working'
|
||||||
update_progress()
|
update_progress()
|
||||||
print_standard(' /dev/{:11} '.format(name+'...'), end='', flush=True)
|
print_standard(' /dev/{:11} '.format(name+'...'), end='', flush=True)
|
||||||
run_program('tmux split-window -dl 5 {} {} {}'.format(
|
|
||||||
'hw-diags-iobenchmark',
|
# Get dev size
|
||||||
'/dev/{}'.format(name),
|
cmd = 'sudo lsblk -bdno size /dev/{}'.format(name)
|
||||||
progress_file).split())
|
try:
|
||||||
wait_for_process('dd')
|
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)
|
print_standard('Done', timestamp=False)
|
||||||
|
|
||||||
# Check results
|
# Close bottom pane
|
||||||
with open(progress_file, 'r') as f:
|
run_program(['tmux', 'kill-pane', '-t', bottom_pane])
|
||||||
text = f.read()
|
|
||||||
io_stats = text.replace('\r', '\n').split('\n')
|
# Build report
|
||||||
try:
|
h_graph_rates = []
|
||||||
io_stats = [get_read_rate(s) for s in io_stats]
|
pos = 0
|
||||||
io_stats = [float(s/1048576) for s in io_stats if s]
|
width = int(test_chunks / IO_VARS['Graph Horizontal Width'])
|
||||||
TESTS['iobenchmark']['Results'][name] = 'Read speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format(
|
for i in range(IO_VARS['Graph Horizontal Width']):
|
||||||
sum(io_stats) / len(io_stats),
|
# Append average rate for WIDTH number of rates to new array
|
||||||
min(io_stats),
|
h_graph_rates.append(sum(read_rates[pos:pos+width])/width)
|
||||||
max(io_stats))
|
pos += width
|
||||||
TESTS['iobenchmark']['Status'][name] = 'CS'
|
report = generate_horizontal_graph(h_graph_rates)
|
||||||
except:
|
report += '\nRead speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format(
|
||||||
# Requires manual testing
|
sum(read_rates)/len(read_rates)/1024**2,
|
||||||
TESTS['iobenchmark']['Status'][name] = 'NS'
|
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
|
# Move temp file
|
||||||
shutil.move(progress_file, '{}/iobenchmark-{}.log'.format(
|
shutil.move(progress_file, '{}/iobenchmark-{}.log'.format(
|
||||||
|
|
@ -759,13 +907,38 @@ def show_results():
|
||||||
and io_status not in ['Denied', 'OVERRIDE', 'Skipped']):
|
and io_status not in ['Denied', 'OVERRIDE', 'Skipped']):
|
||||||
print_info('Benchmark:')
|
print_info('Benchmark:')
|
||||||
result = TESTS['iobenchmark']['Results'].get(name, '')
|
result = TESTS['iobenchmark']['Results'].get(name, '')
|
||||||
print_standard(' {}'.format(result))
|
for line in result.split('\n'):
|
||||||
|
print_standard(' {}'.format(line))
|
||||||
print_standard(' ')
|
print_standard(' ')
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
pause('Press Enter to return to main menu... ')
|
pause('Press Enter to return to main menu... ')
|
||||||
run_program('tmux kill-pane -a'.split())
|
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():
|
def update_progress():
|
||||||
"""Update progress file."""
|
"""Update progress file."""
|
||||||
if 'Progress Out' not in TESTS:
|
if 'Progress Out' not in TESTS:
|
||||||
|
|
@ -821,3 +994,4 @@ def update_progress():
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("This file is not meant to be called directly.")
|
print("This file is not meant to be called directly.")
|
||||||
|
|
||||||
|
# vim: sts=4 sw=4 ts=4
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue