summaryrefslogtreecommitdiffstats
path: root/glances/plugins/fs/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'glances/plugins/fs/__init__.py')
-rw-r--r--glances/plugins/fs/__init__.py268
1 files changed, 268 insertions, 0 deletions
diff --git a/glances/plugins/fs/__init__.py b/glances/plugins/fs/__init__.py
index e69de29b..acace159 100644
--- a/glances/plugins/fs/__init__.py
+++ b/glances/plugins/fs/__init__.py
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Glances.
+#
+# SPDX-FileCopyrightText: 2022 Nicolas Hennion <nicolas@nicolargo.com>
+#
+# SPDX-License-Identifier: LGPL-3.0-only
+#
+
+"""File system plugin."""
+from __future__ import unicode_literals
+
+import operator
+
+from glances.globals import u, nativestr, PermissionError
+from glances.logger import logger
+from glances.plugins.plugin.model import GlancesPluginModel
+
+import psutil
+
+# SNMP OID
+# The snmpd.conf needs to be edited.
+# Add the following to enable it on all disk
+# ...
+# includeAllDisks 10%
+# ...
+# The OIDs are as follows (for the first disk)
+# Path where the disk is mounted: .1.3.6.1.4.1.2021.9.1.2.1
+# Path of the device for the partition: .1.3.6.1.4.1.2021.9.1.3.1
+# Total size of the disk/partition (kBytes): .1.3.6.1.4.1.2021.9.1.6.1
+# Available space on the disk: .1.3.6.1.4.1.2021.9.1.7.1
+# Used space on the disk: .1.3.6.1.4.1.2021.9.1.8.1
+# Percentage of space used on disk: .1.3.6.1.4.1.2021.9.1.9.1
+# Percentage of inodes used on disk: .1.3.6.1.4.1.2021.9.1.10.1
+snmp_oid = {
+ 'default': {
+ 'mnt_point': '1.3.6.1.4.1.2021.9.1.2',
+ 'device_name': '1.3.6.1.4.1.2021.9.1.3',
+ 'size': '1.3.6.1.4.1.2021.9.1.6',
+ 'used': '1.3.6.1.4.1.2021.9.1.8',
+ 'percent': '1.3.6.1.4.1.2021.9.1.9',
+ },
+ 'windows': {
+ 'mnt_point': '1.3.6.1.2.1.25.2.3.1.3',
+ 'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4',
+ 'size': '1.3.6.1.2.1.25.2.3.1.5',
+ 'used': '1.3.6.1.2.1.25.2.3.1.6',
+ },
+ 'netapp': {
+ 'mnt_point': '1.3.6.1.4.1.789.1.5.4.1.2',
+ 'device_name': '1.3.6.1.4.1.789.1.5.4.1.10',
+ 'size': '1.3.6.1.4.1.789.1.5.4.1.3',
+ 'used': '1.3.6.1.4.1.789.1.5.4.1.4',
+ 'percent': '1.3.6.1.4.1.789.1.5.4.1.6',
+ },
+}
+snmp_oid['esxi'] = snmp_oid['windows']
+
+# Define the history items list
+# All items in this list will be historised if the --enable-history tag is set
+items_history_list = [{'name': 'percent', 'description': 'File system usage in percent', 'y_unit': '%'}]
+
+
+class PluginModel(GlancesPluginModel):
+ """Glances file system plugin.
+
+ stats is a list
+ """
+
+ def __init__(self, args=None, config=None):
+ """Init the plugin."""
+ super(PluginModel, self).__init__(
+ args=args, config=config, items_history_list=items_history_list, stats_init_value=[]
+ )
+
+ # We want to display the stat in the curse interface
+ self.display_curse = True
+
+ def get_key(self):
+ """Return the key of the list."""
+ return 'mnt_point'
+
+ @GlancesPluginModel._check_decorator
+ @GlancesPluginModel._log_result_decorator
+ def update(self):
+ """Update the FS stats using the input method."""
+ # Init new stats
+ stats = self.get_init_value()
+
+ if self.input_method == 'local':
+ # Update stats using the standard system lib
+
+ # Grab the stats using the psutil disk_partitions
+ # If 'all'=False return physical devices only (e.g. hard disks, cd-rom drives, USB keys)
+ # and ignore all others (e.g. memory partitions such as /dev/shm)
+ try:
+ fs_stat = psutil.disk_partitions(all=False)
+ except (UnicodeDecodeError, PermissionError):
+ logger.debug("Plugin - fs: PsUtil fetch failed")
+ return self.stats
+
+ # Optional hack to allow logical mounts points (issue #448)
+ allowed_fs_types = self.get_conf_value('allow')
+ if allowed_fs_types:
+ # Avoid Psutil call unless mounts need to be allowed
+ try:
+ all_mounted_fs = psutil.disk_partitions(all=True)
+ except (UnicodeDecodeError, PermissionError):
+ logger.debug("Plugin - fs: PsUtil extended fetch failed")
+ else:
+ # Discard duplicates (#2299) and add entries matching allowed fs types
+ tracked_mnt_points = set(f.mountpoint for f in fs_stat)
+ for f in all_mounted_fs:
+ if (
+ any(f.fstype.find(fs_type) >= 0 for fs_type in allowed_fs_types)
+ and f.mountpoint not in tracked_mnt_points
+ ):
+ fs_stat.append(f)
+
+ # Loop over fs
+ for fs in fs_stat:
+ # Hide the stats if the mount point is in the exclude list
+ if not self.is_display(fs.mountpoint):
+ continue
+
+ # Grab the disk usage
+ try:
+ fs_usage = psutil.disk_usage(fs.mountpoint)
+ except OSError:
+ # Correct issue #346
+ # Disk is ejected during the command
+ continue
+ fs_current = {
+ 'device_name': fs.device,
+ 'fs_type': fs.fstype,
+ # Manage non breaking space (see issue #1065)
+ 'mnt_point': u(fs.mountpoint).replace(u'\u00A0', ' '),
+ 'size': fs_usage.total,
+ 'used': fs_usage.used,
+ 'free': fs_usage.free,
+ 'percent': fs_usage.percent,
+ 'key': self.get_key(),
+ }
+
+ # Hide the stats if the device name is in the exclude list
+ # Correct issue: glances.conf FS hide not applying #1666
+ if not self.is_display(fs_current['device_name']):
+ continue
+
+ # Add alias if exist (define in the configuration file)
+ if self.has_alias(fs_current['mnt_point']) is not None:
+ fs_current['alias'] = self.has_alias(fs_current['mnt_point'])
+
+ stats.append(fs_current)
+
+ elif self.input_method == 'snmp':
+ # Update stats using SNMP
+
+ # SNMP bulk command to get all file system in one shot
+ try:
+ fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True)
+ except KeyError:
+ fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True)
+
+ # Loop over fs
+ if self.short_system_name in ('windows', 'esxi'):
+ # Windows or ESXi tips
+ for fs in fs_stat:
+ # Memory stats are grabbed in the same OID table (ignore it)
+ if fs == 'Virtual Memory' or fs == 'Physical Memory' or fs == 'Real Memory':
+ continue
+ size = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
+ used = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
+ percent = float(used * 100 / size)
+ fs_current = {
+ 'device_name': '',
+ 'mnt_point': fs.partition(' ')[0],
+ 'size': size,
+ 'used': used,
+ 'percent': percent,
+ 'key': self.get_key(),
+ }
+ # Do not take hidden file system into account
+ if self.is_hide(fs_current['mnt_point']):
+ continue
+ else:
+ stats.append(fs_current)
+ else:
+ # Default behavior
+ for fs in fs_stat:
+ fs_current = {
+ 'device_name': fs_stat[fs]['device_name'],
+ 'mnt_point': fs,
+ 'size': int(fs_stat[fs]['size']) * 1024,
+ 'used': int(fs_stat[fs]['used']) * 1024,
+ 'percent': float(fs_stat[fs]['percent']),
+ 'key': self.get_key(),
+ }
+ # Do not take hidden file system into account
+ if self.is_hide(fs_current['mnt_point']) or self.is_hide(fs_current['device_name']):
+ continue
+ else:
+ stats.append(fs_current)
+
+ # Update the stats
+ self.stats = stats
+
+ return self.stats
+
+ def update_views(self):
+ """Update stats views."""
+ # Call the father's method
+ super(PluginModel, self).update_views()
+
+ # Add specifics information
+ # Alert
+ for i in self.stats:
+ self.views[i[self.get_key()]]['used']['decoration'] = self.get_alert(
+ current=i['size'] - i['free'], maximum=i['size'], header=i['mnt_point']
+ )
+
+ 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 and display plugin enable...
+ if not self.stats or self.is_disabled():
+ return ret
+
+ # Max size for the interface name
+ name_max_width = max_width - 13
+
+ # Build the string message
+ # Header
+ msg = '{:{width}}'.format('FILE SYS', width=name_max_width)
+ ret.append(self.curse_add_line(msg, "TITLE"))
+ if args.fs_free_space:
+ msg = '{:>8}'.format('Free')
+ else:
+ msg = '{:>8}'.format('Used')
+ ret.append(self.curse_add_line(msg))
+ msg = '{:>7}'.format('Total')
+ ret.append(self.curse_add_line(msg))
+
+ # Filesystem list (sorted by name)
+ for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
+ # New line
+ ret.append(self.curse_new_line())
+ mnt_point = i['alias'] if 'alias' in i else i['mnt_point']
+ if len(mnt_point) + len(i['device_name'].split('/')[-1]) <= name_max_width - 3:
+ # If possible concatenate mode info... Glances touch inside :)
+ mnt_point = i['mnt_point'] + ' (' + i['device_name'].split('/')[-1] + ')'
+ elif len(mnt_point) > name_max_width:
+ mnt_point = mnt_point[:name_max_width] + '_'
+ msg = '{:{width}}'.format(nativestr(mnt_point), width=name_max_width + 1)
+ ret.append(self.curse_add_line(msg))
+ if args.fs_free_space:
+ msg = '{:>7}'.format(self.auto_unit(i['free']))
+ else:
+ msg = '{:>7}'.format(self.auto_unit(i['used']))
+ ret.append(
+ self.curse_add_line(msg, self.get_views(item=i[self.get_key()], key='used', option='decoration'))
+ )
+ msg = '{:>7}'.format(self.auto_unit(i['size']))
+ ret.append(self.curse_add_line(msg))
+
+ return ret