|
|
|
@ -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) |
|
|
|
|