summaryrefslogtreecommitdiffstats
path: root/glances/plugins/raid/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'glances/plugins/raid/__init__.py')
-rw-r--r--glances/plugins/raid/__init__.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/glances/plugins/raid/__init__.py b/glances/plugins/raid/__init__.py
index e69de29b..530d2053 100644
--- a/glances/plugins/raid/__init__.py
+++ b/glances/plugins/raid/__init__.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Glances.
+#
+# SPDX-FileCopyrightText: 2022 Nicolas Hennion <nicolas@nicolargo.com>
+#
+# SPDX-License-Identifier: LGPL-3.0-only
+#
+
+"""RAID plugin."""
+
+from glances.globals import iterkeys
+from glances.logger import logger
+from glances.plugins.plugin.model import GlancesPluginModel
+
+# Import plugin specific dependency
+try:
+ from pymdstat import MdStat
+except ImportError as e:
+ import_error_tag = True
+ logger.warning("Missing Python Lib ({}), Raid plugin is disabled".format(e))
+else:
+ import_error_tag = False
+
+
+class PluginModel(GlancesPluginModel):
+ """Glances RAID plugin.
+
+ stats is a dict (see pymdstat documentation)
+ """
+
+ def __init__(self, args=None, config=None):
+ """Init the plugin."""
+ super(PluginModel, self).__init__(args=args, config=config)
+
+ # We want to display the stat in the curse interface
+ self.display_curse = True
+
+ @GlancesPluginModel._check_decorator
+ @GlancesPluginModel._log_result_decorator
+ def update(self):
+ """Update RAID stats using the input method."""
+ # Init new stats
+ stats = self.get_init_value()
+
+ if import_error_tag:
+ return self.stats
+
+ if self.input_method == 'local':
+ # Update stats using the PyMDstat lib (https://github.com/nicolargo/pymdstat)
+ try:
+ # Just for test
+ # mds = MdStat(path='/home/nicolargo/dev/pymdstat/tests/mdstat.10')
+ mds = MdStat()
+ stats = mds.get_stats()['arrays']
+ except Exception as e:
+ logger.debug("Can not grab RAID stats (%s)" % e)
+ return self.stats
+
+ elif self.input_method == 'snmp':
+ # Update stats using SNMP
+ # No standard way for the moment...
+ pass
+
+ # Update the stats
+ self.stats = stats
+
+ return self.stats
+
+ def msg_curse(self, args=None, max_width=None):
+ """Return the dict to display in the curse interface."""
+ # Init the return message
+ ret = []
+
+ # Only process if stats exist...
+ if not self.stats or self.is_disabled():
+ return ret
+
+ # Max size for the interface name
+ name_max_width = max_width - 12
+
+ # Header
+ msg = '{:{width}}'.format('RAID disks', width=name_max_width)
+ ret.append(self.curse_add_line(msg, "TITLE"))
+ msg = '{:>7}'.format('Used')
+ ret.append(self.curse_add_line(msg))
+ msg = '{:>7}'.format('Avail')
+ ret.append(self.curse_add_line(msg))
+ # Data
+ arrays = sorted(iterkeys(self.stats))
+ for array in arrays:
+ # New line
+ ret.append(self.curse_new_line())
+ # Display the current status
+ if not isinstance(self.stats[array], dict):
+ continue
+ status = self.raid_alert(
+ self.stats[array]['status'],
+ self.stats[array]['used'],
+ self.stats[array]['available'],
+ self.stats[array]['type'],
+ )
+ # Data: RAID type name | disk used | disk available
+ array_type = self.stats[array]['type'].upper() if self.stats[array]['type'] is not None else 'UNKNOWN'
+ # Build the full name = array type + array name
+ full_name = '{} {}'.format(array_type, array)
+ msg = '{:{width}}'.format(full_name, width=name_max_width)
+ ret.append(self.curse_add_line(msg))
+ if self.stats[array]['type'] == 'raid0' and self.stats[array]['status'] == 'active':
+ msg = '{:>7}'.format(len(self.stats[array]['components']))
+ ret.append(self.curse_add_line(msg, status))
+ msg = '{:>7}'.format('-')
+ ret.append(self.curse_add_line(msg, status))
+ elif self.stats[array]['status'] == 'active':
+ msg = '{:>7}'.format(self.stats[array]['used'])
+ ret.append(self.curse_add_line(msg, status))
+ msg = '{:>7}'.format(self.stats[array]['available'])
+ ret.append(self.curse_add_line(msg, status))
+ elif self.stats[array]['status'] == 'inactive':
+ ret.append(self.curse_new_line())
+ msg = '└─ Status {}'.format(self.stats[array]['status'])
+ ret.append(self.curse_add_line(msg, status))
+ components = sorted(iterkeys(self.stats[array]['components']))
+ for i, component in enumerate(components):
+ if i == len(components) - 1:
+ tree_char = '└─'
+ else:
+ tree_char = '├─'
+ ret.append(self.curse_new_line())
+ msg = ' {} disk {}: '.format(tree_char, self.stats[array]['components'][component])
+ ret.append(self.curse_add_line(msg))
+ msg = '{}'.format(component)
+ ret.append(self.curse_add_line(msg))
+ if self.stats[array]['type'] != 'raid0' and (self.stats[array]['used'] < self.stats[array]['available']):
+ # Display current array configuration
+ ret.append(self.curse_new_line())
+ msg = '└─ Degraded mode'
+ ret.append(self.curse_add_line(msg, status))
+ if len(self.stats[array]['config']) < 17:
+ ret.append(self.curse_new_line())
+ msg = ' └─ {}'.format(self.stats[array]['config'].replace('_', 'A'))
+ ret.append(self.curse_add_line(msg))
+
+ return ret
+
+ def raid_alert(self, status, used, available, type):
+ """RAID alert messages.
+
+ [available/used] means that ideally the array may have _available_
+ devices however, _used_ devices are in use.
+ Obviously when used >= available then things are good.
+ """
+ if type == 'raid0':
+ return 'OK'
+ if status == 'inactive':
+ return 'CRITICAL'
+ if used is None or available is None:
+ return 'DEFAULT'
+ elif used < available:
+ return 'WARNING'
+ return 'OK'