summaryrefslogtreecommitdiffstats
path: root/glances/plugins/network/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'glances/plugins/network/__init__.py')
-rw-r--r--glances/plugins/network/__init__.py389
1 files changed, 389 insertions, 0 deletions
diff --git a/glances/plugins/network/__init__.py b/glances/plugins/network/__init__.py
index e69de29b..cccd440c 100644
--- a/glances/plugins/network/__init__.py
+++ b/glances/plugins/network/__init__.py
@@ -0,0 +1,389 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Glances.
+#
+# SPDX-FileCopyrightText: 2022 Nicolas Hennion <nicolas@nicolargo.com>
+#
+# SPDX-License-Identifier: LGPL-3.0-only
+#
+
+"""Network plugin."""
+from __future__ import unicode_literals
+
+import base64
+
+from glances.timer import getTimeSinceLastUpdate
+from glances.plugins.plugin.model import GlancesPluginModel
+from glances.logger import logger
+
+import psutil
+
+# {'interface_name': 'mpqemubr0-dummy',
+# 'alias': None,
+# 'time_since_update': 2.081636428833008,
+# 'cumulative_rx': 0,
+# 'rx': 0, 'cumulative_tx': 0, 'tx': 0, 'cumulative_cx': 0, 'cx': 0,
+# 'is_up': False,
+# 'speed': 0,
+# 'key': 'interface_name'}
+# Fields description
+fields_description = {
+ 'interface_name': {'description': 'Interface name.', 'unit': 'string'},
+ 'alias': {'description': 'Interface alias name (optional).', 'unit': 'string'},
+ 'rx': {'description': 'The received/input rate (in bit per second).', 'unit': 'bps'},
+ 'tx': {'description': 'The sent/output rate (in bit per second).', 'unit': 'bps'},
+ 'cx': {'description': 'The cumulative received+sent rate (in bit per second).', 'unit': 'bps'},
+ 'cumulative_rx': {
+ 'description': 'The number of bytes received through the interface (cumulative).',
+ 'unit': 'bytes',
+ },
+ 'cumulative_tx': {'description': 'The number of bytes sent through the interface (cumulative).', 'unit': 'bytes'},
+ 'cumulative_cx': {
+ 'description': 'The cumulative number of bytes reveived and sent through the interface (cumulative).',
+ 'unit': 'bytes',
+ },
+ 'speed': {
+ 'description': 'Maximum interface speed (in bit per second). Can return 0 on some operating-system.',
+ 'unit': 'bps',
+ },
+ 'is_up': {'description': 'Is the interface up ?', 'unit': 'bool'},
+ 'time_since_update': {'description': 'Number of seconds since last update.', 'unit': 'seconds'},
+}
+
+# SNMP OID
+# http://www.net-snmp.org/docs/mibs/interfaces.html
+# Dict key = interface_name
+snmp_oid = {
+ 'default': {
+ 'interface_name': '1.3.6.1.2.1.2.2.1.2',
+ 'cumulative_rx': '1.3.6.1.2.1.2.2.1.10',
+ 'cumulative_tx': '1.3.6.1.2.1.2.2.1.16',
+ }
+}
+
+# Define the history items list
+items_history_list = [
+ {'name': 'rx', 'description': 'Download rate per second', 'y_unit': 'bit/s'},
+ {'name': 'tx', 'description': 'Upload rate per second', 'y_unit': 'bit/s'},
+]
+
+
+class PluginModel(GlancesPluginModel):
+ """Glances network 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,
+ fields_description=fields_description,
+ stats_init_value=[],
+ )
+
+ # We want to display the stat in the curse interface
+ self.display_curse = True
+
+ # Hide stats if it has never been != 0
+ if config is not None:
+ self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False)
+ else:
+ self.hide_zero = False
+ self.hide_zero_fields = ['rx', 'tx']
+
+ # Force a first update because we need two update to have the first stat
+ self.update()
+ self.refresh_timer.set(0)
+
+ def get_key(self):
+ """Return the key of the list."""
+ return 'interface_name'
+
+ # @GlancesPluginModel._check_decorator
+ @GlancesPluginModel._log_result_decorator
+ def update(self):
+ """Update network stats using the input method.
+
+ :return: list of stats dict (one dict per interface)
+ """
+ # Init new stats
+ stats = self.get_init_value()
+
+ if self.input_method == 'local':
+ # Update stats using the standard system lib
+
+ # Grab network interface stat using the psutil net_io_counter method
+ try:
+ net_io_counters = psutil.net_io_counters(pernic=True)
+ except UnicodeDecodeError as e:
+ logger.debug('Can not get network interface counters ({})'.format(e))
+ return self.stats
+
+ # Grab interface's status (issue #765)
+ # Grab interface's speed (issue #718)
+ net_status = {}
+ try:
+ net_status = psutil.net_if_stats()
+ except OSError as e:
+ # see psutil #797/glances #1106
+ logger.debug('Can not get network interface status ({})'.format(e))
+
+ # Previous network interface stats are stored in the network_old variable
+ if not hasattr(self, 'network_old'):
+ # First call, we init the network_old var
+ try:
+ self.network_old = net_io_counters
+ except (IOError, UnboundLocalError):
+ pass
+ return self.stats
+
+ # By storing time data we enable Rx/s and Tx/s calculations in the
+ # XML/RPC API, which would otherwise be overly difficult work
+ # for users of the API
+ time_since_update = getTimeSinceLastUpdate('net')
+
+ # Loop over interfaces
+ network_new = net_io_counters
+ for net in network_new:
+ # Do not take hidden interface into account
+ # or KeyError: 'eth0' when interface is not connected #1348
+ if not self.is_display(net) or net not in net_status:
+ continue
+ try:
+ cumulative_rx = network_new[net].bytes_recv
+ cumulative_tx = network_new[net].bytes_sent
+ cumulative_cx = cumulative_rx + cumulative_tx
+ rx = cumulative_rx - self.network_old[net].bytes_recv
+ tx = cumulative_tx - self.network_old[net].bytes_sent
+ cx = rx + tx
+ netstat = {
+ 'interface_name': net,
+ 'alias': self.has_alias(net),
+ 'time_since_update': time_since_update,
+ 'cumulative_rx': cumulative_rx,
+ 'rx': rx,
+ 'cumulative_tx': cumulative_tx,
+ 'tx': tx,
+ 'cumulative_cx': cumulative_cx,
+ 'cx': cx,
+ # Interface status
+ 'is_up': net_status[net].isup,
+ # Interface speed in Mbps, convert it to bps
+ # Can be always 0 on some OSes
+ 'speed': net_status[net].speed * 1048576,
+ # Set the key for the dict
+ 'key': self.get_key(),
+ }
+ except KeyError:
+ continue
+ else:
+ # Append the interface stats to the list
+ stats.append(netstat)
+
+ # Save stats to compute next bitrate
+ self.network_old = network_new
+
+ elif self.input_method == 'snmp':
+ # Update stats using SNMP
+
+ # SNMP bulk command to get all network interface in one shot
+ try:
+ net_io_counters = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True)
+ except KeyError:
+ net_io_counters = self.get_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True)
+
+ # Previous network interface stats are stored in the network_old variable
+ if not hasattr(self, 'network_old'):
+ # First call, we init the network_old var
+ try:
+ self.network_old = net_io_counters
+ except (IOError, UnboundLocalError):
+ pass
+ else:
+ # See description in the 'local' block
+ time_since_update = getTimeSinceLastUpdate('net')
+
+ # Loop over interfaces
+ network_new = net_io_counters
+
+ for net in network_new:
+ # Do not take hidden interface into account
+ if not self.is_display(net):
+ continue
+
+ try:
+ # Windows: a tips is needed to convert HEX to TXT
+ # http://blogs.technet.com/b/networking/archive/2009/12/18/how-to-query-the-list-of-network-interfaces-using-snmp-via-the-ifdescr-counter.aspx
+ if self.short_system_name == 'windows':
+ try:
+ interface_name = str(base64.b16decode(net[2:-2].upper()))
+ except TypeError:
+ interface_name = net
+ else:
+ interface_name = net
+
+ cumulative_rx = float(network_new[net]['cumulative_rx'])
+ cumulative_tx = float(network_new[net]['cumulative_tx'])
+ cumulative_cx = cumulative_rx + cumulative_tx
+ rx = cumulative_rx - float(self.network_old[net]['cumulative_rx'])
+ tx = cumulative_tx - float(self.network_old[net]['cumulative_tx'])
+ cx = rx + tx
+ netstat = {
+ 'interface_name': interface_name,
+ 'alias': self.has_alias(interface_name),
+ 'time_since_update': time_since_update,
+ 'cumulative_rx': cumulative_rx,
+ 'rx': rx,
+ 'cumulative_tx': cumulative_tx,
+ 'tx': tx,
+ 'cumulative_cx': cumulative_cx,
+ 'cx': cx,
+ }
+ except KeyError:
+ continue
+ else:
+ netstat['key'] = self.get_key()
+ stats.append(netstat)
+
+ # Save stats to compute next bitrate
+ self.network_old = network_new
+
+ # 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()
+
+ # Check if the stats should be hidden
+ self.update_views_hidden()
+
+ # Add specifics information
+ # Alert
+ for i in self.get_raw():
+ if i['time_since_update'] == 0:
+ # Skip alert if no timespan to measure
+ continue
+
+ if_real_name = i['interface_name'].split(':')[0]
+ # Convert rate in bps (to be able to compare to interface speed)
+ bps_rx = int(i['rx'] // i['time_since_update'] * 8)
+ bps_tx = int(i['tx'] // i['time_since_update'] * 8)
+
+ # Decorate the bitrate with the configuration file thresholds
+ alert_rx = self.get_alert(bps_rx, header=if_real_name + '_rx')
+ alert_tx = self.get_alert(bps_tx, header=if_real_name + '_tx')
+ # If nothing is define in the configuration file...
+ # ... then use the interface speed (not available on all systems)
+ if alert_rx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
+ alert_rx = self.get_alert(current=bps_rx, maximum=i['speed'], header='rx')
+ if alert_tx == 'DEFAULT' and 'speed' in i and i['speed'] != 0:
+ alert_tx = self.get_alert(current=bps_tx, maximum=i['speed'], header='tx')
+ # then decorates
+ self.views[i[self.get_key()]]['rx']['decoration'] = alert_rx
+ self.views[i[self.get_key()]]['tx']['decoration'] = alert_tx
+
+ 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 - 12
+
+ # Header
+ msg = '{:{width}}'.format('NETWORK', width=name_max_width)
+ ret.append(self.curse_add_line(msg, "TITLE"))
+ if args.network_cumul:
+ # Cumulative stats
+ if args.network_sum:
+ # Sum stats
+ msg = '{:>14}'.format('Rx+Tx')
+ ret.append(self.curse_add_line(msg))
+ else:
+ # Rx/Tx stats
+ msg = '{:>7}'.format('Rx')
+ ret.append(self.curse_add_line(msg))
+ msg = '{:>7}'.format('Tx')
+ ret.append(self.curse_add_line(msg))
+ else:
+ # Bitrate stats
+ if args.network_sum:
+ # Sum stats
+ msg = '{:>14}'.format('Rx+Tx/s')
+ ret.append(self.curse_add_line(msg))
+ else:
+ msg = '{:>7}'.format('Rx/s')
+ ret.append(self.curse_add_line(msg))
+ msg = '{:>7}'.format('Tx/s')
+ ret.append(self.curse_add_line(msg))
+ # Interface list (sorted by name)
+ for i in self.sorted_stats():
+ # Do not display interface in down state (issue #765)
+ if ('is_up' in i) and (i['is_up'] is False):
+ continue
+ # Hide stats if never be different from 0 (issue #1787)
+ if all([self.get_views(item=i[self.get_key()], key=f, option='hidden') for f in self.hide_zero_fields]):
+ continue
+ # Format stats
+ # Is there an alias for the interface name ?
+ if i['alias'] is None:
+ if_name = i['interface_name'].split(':')[0]
+ else:
+ if_name = i['alias']
+ if len(if_name) > name_max_width:
+ # Cut interface name if it is too long
+ if_name = '_' + if_name[-name_max_width + 1 :]
+
+ if args.byte:
+ # Bytes per second (for dummy)
+ to_bit = 1
+ unit = ''
+ else:
+ # Bits per second (for real network administrator | Default)
+ to_bit = 8
+ unit = 'b'
+
+ if args.network_cumul:
+ rx = self.auto_unit(int(i['cumulative_rx'] * to_bit)) + unit
+ tx = self.auto_unit(int(i['cumulative_tx'] * to_bit)) + unit
+ sx = self.auto_unit(int(i['cumulative_rx'] * to_bit) + int(i['cumulative_tx'] * to_bit)) + unit
+ else:
+ rx = self.auto_unit(int(i['rx'] // i['time_since_update'] * to_bit)) + unit
+ tx = self.auto_unit(int(i['tx'] // i['time_since_update'] * to_bit)) + unit
+ sx = (
+ self.auto_unit(
+ int(i['rx'] // i['time_since_update'] * to_bit)
+ + int(i['tx'] // i['time_since_update'] * to_bit)
+ )
+ + unit
+ )
+
+ # New line
+ ret.append(self.curse_new_line())
+ msg = '{:{width}}'.format(if_name, width=name_max_width)
+ ret.append(self.curse_add_line(msg))
+ if args.network_sum:
+ msg = '{:>14}'.format(sx)
+ ret.append(self.curse_add_line(msg))
+ else:
+ msg = '{:>7}'.format(rx)
+ ret.append(
+ self.curse_add_line(msg, self.get_views(item=i[self.get_key()], key='rx', option='decoration'))
+ )
+ msg = '{:>7}'.format(tx)
+ ret.append(
+ self.curse_add_line(msg, self.get_views(item=i[self.get_key()], key='tx', option='decoration'))
+ )
+
+ return ret