From dcd28b778d50074df885449a43b3439d002ec041 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 28 Sep 2019 17:27:06 +0200 Subject: [PATCH] Add a new TCP connections status plugin #1526 RC1 --- conf/glances.conf | 7 +- docs/_static/connections.png | Bin 0 -> 7476 bytes docs/aoa/connections.rst | 25 ++++ glances/outputs/glances_curses.py | 2 +- glances/plugins/glances_connections.py | 156 +++++++++++++++++++++++++ 5 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 docs/_static/connections.png create mode 100644 docs/aoa/connections.rst create mode 100644 glances/plugins/glances_connections.py diff --git a/conf/glances.conf b/conf/glances.conf index 07c6f0c7..bd9ca819 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -6,7 +6,7 @@ # Does Glances should check if a newer version is available on PyPI ? check_update=true # History size (maximum number of values) -# Default is 28800: 1 day with 1 point every 3 seconds (default refresh time) +# Default is 28800: 1 day with 1 point every 3 seconds history_size=28800 ############################################################################## @@ -139,6 +139,11 @@ tx_critical=90 #wlan0_tx_critical=1000000 #wlan0_tx_log=True +[connections] +# Display additional information about TCP connections +# This plugin will be disable by default +disable=True + [wifi] # Define the list of hidden wireless network interfaces (comma-separated regexp) hide=lo,docker.* diff --git a/docs/_static/connections.png b/docs/_static/connections.png new file mode 100644 index 0000000000000000000000000000000000000000..c46db065bbc14e8f52fc2a2e32fd84db57e531bf GIT binary patch literal 7476 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 k#_EqMv#<9 +# +# Glances is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Glances is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""Connections plugin.""" +from __future__ import unicode_literals + +from glances.timer import getTimeSinceLastUpdate +from glances.plugins.glances_plugin import GlancesPlugin +from glances.compat import n, u, b, nativestr + +import psutil + +# 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 Plugin(GlancesPlugin): + """Glances connections plugin. + + stats is a dict + """ + + status_list = [psutil.CONN_LISTEN, + psutil.CONN_ESTABLISHED] + initiated_states = [psutil.CONN_SYN_SENT, + psutil.CONN_SYN_RECV] + terminated_states = [psutil.CONN_FIN_WAIT1, + psutil.CONN_FIN_WAIT2, + psutil.CONN_TIME_WAIT, + psutil.CONN_CLOSE, + psutil.CONN_CLOSE_WAIT, + psutil.CONN_LAST_ACK] + + def __init__(self, args=None, config=None): + """Init the plugin.""" + super(Plugin, 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 + + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator + def update(self): + """Update connections stats using the input method. + + Stats is a dict + """ + # Init new stats + stats = self.get_init_value() + + if self.input_method == 'local': + # Update stats using the PSUtils lib + + # Grab network interface stat using the psutil net_connections method + try: + net_connections = psutil.net_connections(kind="tcp") + except Exception as e: + logger.debug('Can not get network connections stats ({})'.format(e)) + return self.stats + + for s in self.status_list: + stats[s] = len([c for c in net_connections if c.status == s]) + initiated = 0 + for s in self.initiated_states: + stats[s] = len([c for c in net_connections if c.status == s]) + initiated += stats[s] + stats['initiated'] = initiated + terminated = 0 + for s in self.initiated_states: + stats[s] = len([c for c in net_connections if c.status == s]) + terminated += stats[s] + stats['terminated'] = terminated + + elif self.input_method == 'snmp': + # Update stats using SNMP + pass + + # Update the stats + self.stats = stats + + return self.stats + + def update_views(self): + """Update stats views.""" + # Call the father's method + super(Plugin, self).update_views() + + # Add specifics informations + # Alert + # for i in self.stats: + # ifrealname = 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 thresolds + # alert_rx = self.get_alert(bps_rx, header=ifrealname + '_rx') + # alert_tx = self.get_alert(bps_tx, header=ifrealname + '_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_disable(): + return ret + + # Header + msg = '{}'.format('TCP CONNECTIONS') + ret.append(self.curse_add_line(msg, "TITLE")) + # Connections status + for s in [psutil.CONN_LISTEN, 'initiated', psutil.CONN_ESTABLISHED, 'terminated']: + ret.append(self.curse_new_line()) + msg = '{:{width}}'.format(nativestr(s).capitalize(), width=len(s)) + ret.append(self.curse_add_line(msg)) + msg = '{:>{width}}'.format(self.stats[s], width=max_width - len(s) + 2) + ret.append(self.curse_add_line(msg)) + + return ret -- GitLab