diff --git a/text_collector_examples/storcli.py b/text_collector_examples/storcli.py index f1a8a602..42b1db35 100755 --- a/text_collector_examples/storcli.py +++ b/text_collector_examples/storcli.py @@ -1,31 +1,37 @@ #!/usr/bin/env python +""" +Script to parse StorCLI's JSON output and expose +MegaRAID health as Prometheus metrics. -# Script to parse StorCLI's JSON output and expose -# MegaRAID health as Prometheus metrics. -# -# Tested against StorCLI 'Ver 1.14.12 Nov 25, 2014'. -# -# StorCLI reference manual: -# http://docs.avagotech.com/docs/12352476 -# -# Advanced Software Options (ASO) not exposed as metrics currently. -# -# JSON key abbreviations used by StorCLI are documented in the standard command -# output, i.e. when you omit the trailing 'J' from the command. +Tested against StorCLI 'Ver 1.14.12 Nov 25, 2014'. +StorCLI reference manual: +http://docs.avagotech.com/docs/12352476 + +Advanced Software Options (ASO) not exposed as metrics currently. + +JSON key abbreviations used by StorCLI are documented in the standard command +output, i.e. when you omit the trailing 'J' from the command. +""" + +from __future__ import print_function import argparse import json +import os import subprocess DESCRIPTION = """Parses StorCLI's JSON output and exposes MegaRAID health as Prometheus metrics.""" VERSION = '0.0.1' -METRIC_PREFIX = 'megaraid_' -METRIC_CONTROLLER_LABELS = '{{controller="{}", model="{}"}}' - def main(args): + """ main """ + + # exporter variables + metric_prefix = 'megaraid_' + metric_controller_labels = '{{controller="{}", model="{}"}}' + data = json.loads(get_storcli_json(args.storcli_path)) # It appears that the data we need will always be present in the first @@ -33,12 +39,14 @@ def main(args): status = data['Controllers'][0] metrics = { - 'status_code': status['Command Status']['Status Code'], - 'controllers': status['Response Data']['Number of Controllers'], - } + 'status_code': status['Command Status']['Status Code'], + 'controllers': status['Response Data']['Number of Controllers'], + } for name, value in metrics.iteritems(): - print("{}{} {}".format(METRIC_PREFIX, name, value)) + print('# HELP {}{} MegaRAID {}'.format(metric_prefix, name, name.replace('_', ' '))) + print('# TYPE {}{} gauge'.format(metric_prefix, name)) + print("{}{} {}".format(metric_prefix, name, value)) controller_info = [] controller_metrics = {} @@ -52,50 +60,69 @@ def main(args): for controller in overview: controller_index = controller['Ctl'] model = controller['Model'] - controller_info.append(METRIC_CONTROLLER_LABELS.format(controller_index, model)) + controller_info.append(metric_controller_labels.format(controller_index, model)) controller_metrics = { - # FIXME: Parse dimmer switch options - # 'dimmer_switch': controller['DS'], - - 'battery_backup_healthy': int(controller['BBU'] == 'Opt'), - 'degraded': int(controller['Hlth'] == 'Dgd'), - 'drive_groups': controller['DGs'], - 'emergency_hot_spare': int(controller['EHS'] == 'Y'), - 'failed': int(controller['Hlth'] == 'Fld'), - 'healthy': int(controller['Hlth'] == 'Opt'), - 'physical_drives': controller['PDs'], - 'ports': controller['Ports'], - 'scheduled_patrol_read': int(controller['sPR'] == 'On'), - 'virtual_drives': controller['VDs'], - - # Reverse StorCLI's logic to make metrics consistent - 'drive_groups_optimal': int(controller['DNOpt'] == 0), - 'virtual_drives_optimal': int(controller['VNOpt'] == 0), - } + # FIXME: Parse dimmer switch options + # 'dimmer_switch': controller['DS'], + + 'battery_backup_healthy': int(controller['BBU'] == 'Opt'), + 'degraded': int(controller['Hlth'] == 'Dgd'), + 'drive_groups': controller['DGs'], + 'emergency_hot_spare': int(controller['EHS'] == 'Y'), + 'failed': int(controller['Hlth'] == 'Fld'), + 'healthy': int(controller['Hlth'] == 'Opt'), + 'physical_drives': controller['PDs'], + 'ports': controller['Ports'], + 'scheduled_patrol_read': int(controller['sPR'] == 'On'), + 'virtual_drives': controller['VDs'], + + # Reverse StorCLI's logic to make metrics consistent + 'drive_groups_optimal': int(controller['DNOpt'] == 0), + 'virtual_drives_optimal': int(controller['VNOpt'] == 0), + } for name, value in controller_metrics.iteritems(): - print('{}{}{{controller="{}"}} {}'.format(METRIC_PREFIX, name, controller_index, value)) - + print('# HELP {}{} MegaRAID {}'.format(metric_prefix, name, name.replace('_', ' '))) + print('# TYPE {}{} gauge'.format(metric_prefix, name)) + print('{}{}{{controller="{}"}} {}'.format(metric_prefix, name, + controller_index, value)) + + if controller_info: + print('# HELP {}{} MegaRAID controller info'.format(metric_prefix, 'controller_info')) + print('# TYPE {}{} gauge'.format(metric_prefix, name)) for labels in controller_info: - print('{}{}{} {}'.format(METRIC_PREFIX, 'controller_info', labels, 1)) + print('{}{}{} {}'.format(metric_prefix, 'controller_info', labels, 1)) def get_storcli_json(storcli_path): - storcli_cmd = [storcli_path, 'show', 'all', 'J'] - proc = subprocess.Popen(storcli_cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - return proc.communicate()[0] + """Get storcli output in JSON format.""" + + # Check if storcli is installed + if os.path.isfile(storcli_path) and os.access(storcli_path, os.X_OK): + storcli_cmd = [storcli_path, 'show', 'all', 'J'] + proc = subprocess.Popen(storcli_cmd, shell=False, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output_json = proc.communicate()[0] + else: + # Create an empty dummy-JSON where storcli not installed. + dummy_json = {"Controllers":[{ + "Command Status": {"Status Code": 0, "Status": "Success", + "Description": "None"}, + "Response Data": {"Number of Controllers": 0}}]} + output_json = json.dumps(dummy_json) + + return output_json if __name__ == "__main__": - parser = argparse.ArgumentParser(description=DESCRIPTION, - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('--storcli_path', + PARSER = argparse.ArgumentParser(description=DESCRIPTION, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + PARSER.add_argument('--storcli_path', default='/opt/MegaRAID/storcli/storcli64', help='path to StorCLi binary') - parser.add_argument('--version', + PARSER.add_argument('--version', action='version', version='%(prog)s {}'.format(VERSION)) - args = parser.parse_args() + ARGS = PARSER.parse_args() - main(args) + main(ARGS)