StorCli text collector: fix pylint issues and handle StorCli not installed (#758)

* StorCli text collector: fix pylint issues and handle StorCli not installed

* StorCli text collector: Add HELP and TYPE strings.
This commit is contained in:
Mario Trangoni 2017-12-12 18:48:06 +01:00 committed by Ben Kochie
parent af4cf20b46
commit a40f7e78da

View file

@ -1,31 +1,37 @@
#!/usr/bin/env python #!/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 Tested against StorCLI 'Ver 1.14.12 Nov 25, 2014'.
# 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.
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 argparse
import json import json
import os
import subprocess import subprocess
DESCRIPTION = """Parses StorCLI's JSON output and exposes MegaRAID health as DESCRIPTION = """Parses StorCLI's JSON output and exposes MegaRAID health as
Prometheus metrics.""" Prometheus metrics."""
VERSION = '0.0.1' VERSION = '0.0.1'
METRIC_PREFIX = 'megaraid_'
METRIC_CONTROLLER_LABELS = '{{controller="{}", model="{}"}}'
def main(args): def main(args):
""" main """
# exporter variables
metric_prefix = 'megaraid_'
metric_controller_labels = '{{controller="{}", model="{}"}}'
data = json.loads(get_storcli_json(args.storcli_path)) data = json.loads(get_storcli_json(args.storcli_path))
# It appears that the data we need will always be present in the first # 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] status = data['Controllers'][0]
metrics = { metrics = {
'status_code': status['Command Status']['Status Code'], 'status_code': status['Command Status']['Status Code'],
'controllers': status['Response Data']['Number of Controllers'], 'controllers': status['Response Data']['Number of Controllers'],
} }
for name, value in metrics.iteritems(): 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_info = []
controller_metrics = {} controller_metrics = {}
@ -52,50 +60,69 @@ def main(args):
for controller in overview: for controller in overview:
controller_index = controller['Ctl'] controller_index = controller['Ctl']
model = controller['Model'] 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 = { controller_metrics = {
# FIXME: Parse dimmer switch options # FIXME: Parse dimmer switch options
# 'dimmer_switch': controller['DS'], # 'dimmer_switch': controller['DS'],
'battery_backup_healthy': int(controller['BBU'] == 'Opt'), 'battery_backup_healthy': int(controller['BBU'] == 'Opt'),
'degraded': int(controller['Hlth'] == 'Dgd'), 'degraded': int(controller['Hlth'] == 'Dgd'),
'drive_groups': controller['DGs'], 'drive_groups': controller['DGs'],
'emergency_hot_spare': int(controller['EHS'] == 'Y'), 'emergency_hot_spare': int(controller['EHS'] == 'Y'),
'failed': int(controller['Hlth'] == 'Fld'), 'failed': int(controller['Hlth'] == 'Fld'),
'healthy': int(controller['Hlth'] == 'Opt'), 'healthy': int(controller['Hlth'] == 'Opt'),
'physical_drives': controller['PDs'], 'physical_drives': controller['PDs'],
'ports': controller['Ports'], 'ports': controller['Ports'],
'scheduled_patrol_read': int(controller['sPR'] == 'On'), 'scheduled_patrol_read': int(controller['sPR'] == 'On'),
'virtual_drives': controller['VDs'], 'virtual_drives': controller['VDs'],
# Reverse StorCLI's logic to make metrics consistent # Reverse StorCLI's logic to make metrics consistent
'drive_groups_optimal': int(controller['DNOpt'] == 0), 'drive_groups_optimal': int(controller['DNOpt'] == 0),
'virtual_drives_optimal': int(controller['VNOpt'] == 0), 'virtual_drives_optimal': int(controller['VNOpt'] == 0),
} }
for name, value in controller_metrics.iteritems(): 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: 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): def get_storcli_json(storcli_path):
storcli_cmd = [storcli_path, 'show', 'all', 'J'] """Get storcli output in JSON format."""
proc = subprocess.Popen(storcli_cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) # Check if storcli is installed
return proc.communicate()[0] 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__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description=DESCRIPTION, PARSER = argparse.ArgumentParser(description=DESCRIPTION,
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--storcli_path', PARSER.add_argument('--storcli_path',
default='/opt/MegaRAID/storcli/storcli64', default='/opt/MegaRAID/storcli/storcli64',
help='path to StorCLi binary') help='path to StorCLi binary')
parser.add_argument('--version', PARSER.add_argument('--version',
action='version', action='version',
version='%(prog)s {}'.format(VERSION)) version='%(prog)s {}'.format(VERSION))
args = parser.parse_args() ARGS = PARSER.parse_args()
main(args) main(ARGS)