Added I/O Benchmark PNG graph sections
This commit is contained in:
parent
8643ec2c7c
commit
701d647a91
3 changed files with 136 additions and 3 deletions
|
|
@ -28,6 +28,8 @@ CRASH_SERVER = {
|
||||||
'Pass': '',
|
'Pass': '',
|
||||||
'Headers': {'X-Requested-With': 'XMLHttpRequest'},
|
'Headers': {'X-Requested-With': 'XMLHttpRequest'},
|
||||||
}
|
}
|
||||||
|
# Misc
|
||||||
|
IMGUR_CLIENT_ID='3d1ee1d38707b85'
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,24 @@
|
||||||
# pylint: disable=bad-whitespace
|
# pylint: disable=bad-whitespace
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
|
import pathlib
|
||||||
|
import time
|
||||||
|
|
||||||
|
import Gnuplot
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from wk.cfg.net import BENCHMARK_SERVER, IMGUR_CLIENT_ID
|
||||||
from wk.std import color_string
|
from wk.std import color_string
|
||||||
|
|
||||||
|
|
||||||
|
# Hack to hide X11 error when running in CLI mode
|
||||||
|
Gnuplot.gp.GnuplotOpts.default_term = 'xterm'
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
GRAPH_HORIZONTAL = ('▁', '▂', '▃', '▄', '▅', '▆', '▇', '█')
|
GRAPH_HORIZONTAL = ('▁', '▂', '▃', '▄', '▅', '▆', '▇', '█')
|
||||||
|
|
@ -34,6 +47,48 @@ THRESH_GREAT = 750 * 1024**2
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
|
def export_io_graph(disk, log_dir):
|
||||||
|
"""Exports PNG graph using gnuplot, returns pathlib.Path obj."""
|
||||||
|
read_rates = disk.tests['Disk I/O Benchmark'].read_rates
|
||||||
|
|
||||||
|
# Safety check
|
||||||
|
if not read_rates:
|
||||||
|
raise RuntimeError(f'No read rates for {disk.path}')
|
||||||
|
|
||||||
|
# Prep
|
||||||
|
max_rate = max(read_rates) / (1024**2)
|
||||||
|
max_rate = max(800, max_rate)
|
||||||
|
out_path = pathlib.Path(f'{log_dir}/{disk.path.name}_iobenchmark.png')
|
||||||
|
plot_data = out_path.with_suffix('.data')
|
||||||
|
|
||||||
|
# Adjust Y-axis range to either 1000 or roughly max rate + 200
|
||||||
|
## Round up to the nearest 100 and then add 200
|
||||||
|
y_range = (math.ceil(max_rate/100)*100) + 200
|
||||||
|
|
||||||
|
# Save plot data to file for Gnuplot
|
||||||
|
with open(plot_data, 'w') as _f:
|
||||||
|
for i, rate in enumerate(read_rates):
|
||||||
|
percent = (i+1) / len(read_rates) * 100
|
||||||
|
rate = int(rate / (1024**2))
|
||||||
|
_f.write(f'{percent:0.1f} {rate}\n')
|
||||||
|
|
||||||
|
# Run gnuplot commands
|
||||||
|
_g = Gnuplot.Gnuplot()
|
||||||
|
_g('reset')
|
||||||
|
_g(f'set output "{out_path}"')
|
||||||
|
_g('set terminal png large size 660,300 truecolor font "Noto Sans,11"')
|
||||||
|
_g('set title "I/O Benchmark"')
|
||||||
|
_g(f'set yrange [0:{y_range}]')
|
||||||
|
_g('set style data lines')
|
||||||
|
_g(f'plot "{plot_data}" title "{disk.description.replace("_", " ")}"')
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
_g.close()
|
||||||
|
del _g
|
||||||
|
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
def generate_horizontal_graph(rate_list, graph_width=40, oneline=False):
|
def generate_horizontal_graph(rate_list, graph_width=40, oneline=False):
|
||||||
"""Generate horizontal graph from rate_list, returns list."""
|
"""Generate horizontal graph from rate_list, returns list."""
|
||||||
graph = ['', '', '', '']
|
graph = ['', '', '', '']
|
||||||
|
|
@ -111,6 +166,68 @@ def merge_rates(rates, graph_width=40):
|
||||||
return merged_rates
|
return merged_rates
|
||||||
|
|
||||||
|
|
||||||
|
def upload_to_imgur(image_path):
|
||||||
|
"""Upload image to Imgur and return image url as str."""
|
||||||
|
image_data = None
|
||||||
|
image_link = None
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not image_path:
|
||||||
|
raise RuntimeError(f'Invalid image path: {image_path}')
|
||||||
|
|
||||||
|
# Read image file and convert to base64 then convert to str
|
||||||
|
with open(image_path, 'rb') as _f:
|
||||||
|
image_data = base64.b64encode(_f.read()).decode()
|
||||||
|
|
||||||
|
# POST image
|
||||||
|
url = 'https://api.imgur.com/3/image'
|
||||||
|
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||||
|
payload = (
|
||||||
|
f'--{boundary}\r\nContent-Disposition: form-data; '
|
||||||
|
f'name="image"\r\n\r\n{image_data}\r\n--{boundary}--'
|
||||||
|
)
|
||||||
|
headers = {
|
||||||
|
'content-type': f'multipart/form-data; boundary={boundary}',
|
||||||
|
'Authorization': f'Client-ID {IMGUR_CLIENT_ID}',
|
||||||
|
}
|
||||||
|
response = requests.request('POST', url, data=payload, headers=headers)
|
||||||
|
|
||||||
|
# Return image link
|
||||||
|
if response.ok:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
image_link = json_data['data']['link']
|
||||||
|
return image_link
|
||||||
|
|
||||||
|
|
||||||
|
def upload_to_nextcloud(image_path, ticket_number, dev_name):
|
||||||
|
"""Upload image to Nextcloud server and return folder url as str."""
|
||||||
|
image_data = None
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not image_path:
|
||||||
|
raise RuntimeError(f'Invalid image path: {image_path}')
|
||||||
|
|
||||||
|
# Read image file and convert to base64
|
||||||
|
with open(image_path, 'rb') as _f:
|
||||||
|
image_data = _f.read()
|
||||||
|
|
||||||
|
# PUT image
|
||||||
|
url = (
|
||||||
|
f'{BENCHMARK_SERVER["Url"]}/'
|
||||||
|
f'{ticket_number}_iobenchmark'
|
||||||
|
f'_{dev_name}_{time.strftime("%Y-%m-%d_%H%M_%z")}.png'
|
||||||
|
)
|
||||||
|
requests.put(
|
||||||
|
url,
|
||||||
|
data=image_data,
|
||||||
|
headers = {'X-Requested-With': 'XMLHttpRequest'},
|
||||||
|
auth = (BENCHMARK_SERVER['User'], BENCHMARK_SERVER['Pass']),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return folder link
|
||||||
|
return BENCHMARK_SERVER['Short Url']
|
||||||
|
|
||||||
|
|
||||||
def vertical_graph_line(percent, rate, scale=32):
|
def vertical_graph_line(percent, rate, scale=32):
|
||||||
"""Build colored graph string using thresholds, returns str."""
|
"""Build colored graph string using thresholds, returns str."""
|
||||||
color_bar = None
|
color_bar = None
|
||||||
|
|
|
||||||
|
|
@ -748,7 +748,7 @@ def cpu_mprime_test(state, test_objects):
|
||||||
std.print_info('Posting results to osTicket...')
|
std.print_info('Posting results to osTicket...')
|
||||||
test_cooling_obj.cpu_max_temp = sensors.cpu_max_temp()
|
test_cooling_obj.cpu_max_temp = sensors.cpu_max_temp()
|
||||||
state.ost.post_response(
|
state.ost.post_response(
|
||||||
ost_build_report(state.cpu, 'CPU'),
|
ost_build_report(state.cpu, 'CPU', state),
|
||||||
color='Diags FAIL' if state.cpu.any_test_failed() else 'Diags',
|
color='Diags FAIL' if state.cpu.any_test_failed() else 'Diags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -860,6 +860,9 @@ def disk_io_benchmark(state, test_objects, skip_usb=True):
|
||||||
# Check results
|
# Check results
|
||||||
check_io_benchmark_results(test_obj, read_rates, IO_GRAPH_WIDTH)
|
check_io_benchmark_results(test_obj, read_rates, IO_GRAPH_WIDTH)
|
||||||
|
|
||||||
|
# osTicket
|
||||||
|
test_obj.read_rates = read_rates
|
||||||
|
|
||||||
# Run benchmarks
|
# Run benchmarks
|
||||||
state.update_top_pane(
|
state.update_top_pane(
|
||||||
f'Disk I/O Benchmark{"s" if len(test_objects) > 1 else ""}',
|
f'Disk I/O Benchmark{"s" if len(test_objects) > 1 else ""}',
|
||||||
|
|
@ -1228,7 +1231,8 @@ def network_test():
|
||||||
std.pause('Press Enter to return to main menu...')
|
std.pause('Press Enter to return to main menu...')
|
||||||
|
|
||||||
|
|
||||||
def ost_build_report(dev, dev_type):
|
def ost_build_report(dev, dev_type, state):
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
"""Build report for posting to osTicket, returns str."""
|
"""Build report for posting to osTicket, returns str."""
|
||||||
report = []
|
report = []
|
||||||
|
|
||||||
|
|
@ -1279,6 +1283,16 @@ def ost_build_report(dev, dev_type):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
report.extend(ost_convert_report(test.report, start_index=1))
|
report.extend(ost_convert_report(test.report, start_index=1))
|
||||||
|
if name == 'Disk I/O Benchmark':
|
||||||
|
try:
|
||||||
|
image_path = graph.export_io_graph(dev, state.log_dir)
|
||||||
|
imgur_url = graph.upload_to_imgur(image_path)
|
||||||
|
nextcloud_url = graph.upload_to_nextcloud(
|
||||||
|
image_path, state.ost.ticket_id, dev.path.name)
|
||||||
|
report.append(f'Imgur: {imgur_url}')
|
||||||
|
report.append(f'Nextcloud: {nextcloud_url}')
|
||||||
|
except (AttributeError, RuntimeError):
|
||||||
|
report.append('Error(s) exporting graph')
|
||||||
|
|
||||||
# Spacer
|
# Spacer
|
||||||
report.append('')
|
report.append('')
|
||||||
|
|
@ -1391,7 +1405,7 @@ def ost_post_disk_results(state):
|
||||||
std.print_info('Posting results to osTicket...')
|
std.print_info('Posting results to osTicket...')
|
||||||
for disk in state.disks:
|
for disk in state.disks:
|
||||||
state.ost.post_response(
|
state.ost.post_response(
|
||||||
ost_build_report(disk, 'Disk'),
|
ost_build_report(disk, 'Disk', state),
|
||||||
color='Diags FAIL' if disk.any_test_failed() else 'Diags',
|
color='Diags FAIL' if disk.any_test_failed() else 'Diags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue