From 2752e0d66a9ba0b57ac86913fd302ada23c280c0 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 1 Dec 2021 16:47:09 -0800 Subject: [PATCH] add iostat streaming parser --- jc/cli.py | 1 + jc/parsers/iostat_s.py | 165 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 jc/parsers/iostat_s.py diff --git a/jc/cli.py b/jc/cli.py index 7727a0f8..a81a1c32 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -77,6 +77,7 @@ parsers = [ 'ifconfig', 'ini', 'iostat', + 'iostat-s', 'iptables', 'iw-scan', 'jobs', diff --git a/jc/parsers/iostat_s.py b/jc/parsers/iostat_s.py new file mode 100644 index 00000000..7780c894 --- /dev/null +++ b/jc/parsers/iostat_s.py @@ -0,0 +1,165 @@ +"""jc - JSON CLI output utility `iostat` command output streaming parser + +> This streaming parser outputs JSON Lines + +<> + +Usage (cli): + + $ iostat | jc --iostat-s + +Usage (module): + + import jc.parsers.iostat_s + result = jc.parsers.iostat_s.parse(iostat_command_output.splitlines()) # result is an iterable object + for item in result: + # do something + +Schema: + + { + "iostat": string, + "_jc_meta": # This object only exists if using -qq or ignore_exceptions=True + { + "success": booean, # true if successfully parsed, false if error + "error": string, # exists if "success" is false + "line": string # exists if "success" is false + } + } + +Examples: + + $ iostat | jc --iostat-s + {example output} + ... + + $ iostat | jc --iostat-s -r + {example output} + ... +""" +import jc.utils +from jc.utils import stream_success, stream_error +from jc.exceptions import ParseError +import jc.parsers.universal + + +class info(): + """Provides parser metadata (version, author, etc.)""" + version = '1.0' + description = '`iostat` command streaming parser' + author = 'Kelly Brazil' + author_email = 'kellyjonbrazil@gmail.com' + compatible = ['linux'] + streaming = True + + +__version__ = info.version + + +def _process(proc_data): + """ + Final processing to conform to the schema. + + Parameters: + + proc_data: (Dictionary) raw structured data to process + + Returns: + + Dictionary. Structured data to conform to the schema. + """ + float_list = [ + 'percent_user', 'percent_nice', 'percent_system', 'percent_iowait', + 'percent_steal', 'percent_idle', 'tps', 'kb_read_s', 'mb_read_s', 'kb_wrtn_s', + 'mb_wrtn_s', 'rrqm_s', 'wrqm_s', 'r_s', 'w_s', 'rmb_s', 'rkb_s', 'wmb_s', + 'wkb_s', 'avgrq_sz', 'avgqu_sz', 'await', 'r_await', 'w_await', 'svctm', + 'percent_util', 'percent_rrqm', 'percent_wrqm', 'aqu_sz', 'rareq_sz', 'wareq_sz' + ] + int_list = ['kb_read', 'mb_read', 'kb_wrtn', 'mb_wrtn'] + for key in proc_data: + if key in int_list: + proc_data[key] = jc.utils.convert_to_int(proc_data[key]) + + if key in float_list: + proc_data[key] = jc.utils.convert_to_float(proc_data[key]) + + return proc_data + +def _normalize_headers(line): + return line.replace('%', 'percent_').replace('/', '_').replace('-', '_').lower() + +def _create_obj_list(section_list, section_name): + output_list = jc.parsers.universal.simple_table_parse(section_list) + for item in output_list: + item['type'] = section_name + return output_list + +def parse(data, raw=False, quiet=False, ignore_exceptions=False): + """ + Main text parsing generator function. Returns an iterator object. + + Parameters: + + data: (iterable) line-based text data to parse (e.g. sys.stdin or str.splitlines()) + raw: (boolean) output preprocessed JSON if True + quiet: (boolean) suppress warning messages if True + ignore_exceptions: (boolean) ignore parsing exceptions if True + + Yields: + + Dictionary. Raw or processed structured data. + + Returns: + + Iterator object + """ + jc.utils.compatibility(__name__, info.compatible, quiet) + jc.utils.streaming_input_type_check(data) + + section = '' # either 'cpu' or 'device' + headers = '' + cpu_list = [] + device_list = [] + + for line in data: + output_line = {} + try: + jc.utils.streaming_line_input_type_check(line) + + # ignore blank lines and header line + if line == '\n' or line.startswith('Linux'): + continue + + if line.startswith('avg-cpu:'): + section = 'cpu' + headers = _normalize_headers(line) + headers = headers.strip().split(':', maxsplit=1)[1:] + headers = ' '.join(headers) + continue + + if line.startswith('Device'): + section = 'device' + headers = _normalize_headers(line) + headers = headers.replace(':', ' ') + continue + + if section == 'cpu': + cpu_list.append(headers) + cpu_list.append(line) + output_line = _create_obj_list(cpu_list, 'cpu')[0] + cpu_list = [] + + + if section == 'device': + device_list.append(headers) + device_list.append(line) + output_line = _create_obj_list(device_list, 'device')[0] + device_list = [] + + if output_line: + yield stream_success(output_line, ignore_exceptions) if raw else stream_success(_process(output_line), ignore_exceptions) + else: + raise ParseError('Not iostat data') + + except Exception as e: + yield stream_error(e, ignore_exceptions, line)