diff --git a/conf/glances.conf b/conf/glances.conf index 07c6f0c78d69d9b90526891a9a333ea38f56c008..028def5a6d89c0fbaad80c8560a9048eb39d2285 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 ############################################################################## @@ -26,7 +26,7 @@ max_processes_display=30 [quicklook] # Set to true to disable a plugin # Note: you can also disable it from the command line (see --disable-plugin) -disable=false +disable=False # Graphical percentage char used in the terminal user interface (default is |) percentage_char=| # Define CPU, MEM and SWAP thresholds in % @@ -139,6 +139,15 @@ 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 +# nf_conntrack thresholds in % +nf_conntrack_percent_careful=70 +nf_conntrack_percent_warning=80 +nf_conntrack_percent_critical=90 + [wifi] # Define the list of hidden wireless network interfaces (comma-separated regexp) hide=lo,docker.* diff --git a/docs/aoa/connections.rst b/docs/aoa/connections.rst new file mode 100644 index 0000000000000000000000000000000000000000..320dcb2972734c3283cab717440202d6446d7ed3 --- /dev/null +++ b/docs/aoa/connections.rst @@ -0,0 +1,29 @@ +.. _connections: + +Connections +=========== + +.. image:: ../_static/connections.png + +This plugin display extended information about network connections. + +The states are the following: +- Listen: all ports created by server and waiting for a client to connect +- Initialized: All states when a connection is initialized (sum of SYN_SENT and SYN_RECEIVED) +- Established: All established connections between a client and a server +- Terminated: All states when a connection is terminated (FIN_WAIT1, CLOSE_WAIT, LAST_ACK, FIN_WAIT2, TIME_WAIT and CLOSE) +- Tracked: Current number and maximum Netfilter tracker connection (nf_conntrack_count/nf_conntrack_max) + +The configuration should be done in the ``[connections]`` section of the +Glances configuration file. + +By default the plugin is **disabled**. Please change your configuration file as following to enable it + +.. code-block:: ini + + [connections] + disable=False + # nf_conntrack thresholds in % + nf_conntrack_percent_careful=70 + nf_conntrack_percent_warning=80 + nf_conntrack_percent_critical=90 diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 4a4b7632e9b7e445d2ac79f9389e1d06c7179205..dac51e071cebf3d27746780f47e5101c50bf27bd 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -96,7 +96,7 @@ class _GlancesCurses(object): _quicklook_max_width = 68 # Define left sidebar - _left_sidebar = ['network', 'wifi', 'ports', 'diskio', 'fs', + _left_sidebar = ['network', 'connections', 'wifi', 'ports', 'diskio', 'fs', 'irq', 'folders', 'raid', 'sensors', 'now'] _left_sidebar_min_width = 23 _left_sidebar_max_width = 34 diff --git a/glances/plugins/glances_connections.py b/glances/plugins/glances_connections.py new file mode 100644 index 0000000000000000000000000000000000000000..cda7a1bab9136f8372d3869c0c70e88c52e09832 --- /dev/null +++ b/glances/plugins/glances_connections.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2019 Nicolargo +# +# 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.logger import logger +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] + conntrack = {'nf_conntrack_count': '/proc/sys/net/netfilter/nf_conntrack_count', + 'nf_conntrack_max': '/proc/sys/net/netfilter/nf_conntrack_max'} + + 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 + + # @TODO the plugin should be enable only for Linux OS + + @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 + + # Grab connections track directly from the /proc file + for i in self.conntrack: + with open(self.conntrack[i], 'r') as f: + stats[i] = float(f.readline().rstrip("\n")) + stats['nf_conntrack_percent'] = stats['nf_conntrack_count'] * 100 / stats['nf_conntrack_max'] + + 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 + try: + # Alert and log + self.views['nf_conntrack_percent']['decoration'] = self.get_alert(header='nf_conntrack_percent') + except KeyError: + # try/except mandatory for Windows compatibility (no conntrack stats) + pass + + def msg_curse(self, args=None, max_width=None): + """Return the dict to display in the curse interface.""" + # Init the return message + ret = [] + + logger.info(self.is_disable()) + + # 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)) + # Connections track + s = 'Tracked' + 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('{:0.0f}/{:0.0f}'.format(self.stats['nf_conntrack_count'], + self.stats['nf_conntrack_max']), + width=max_width - len(s) + 2) + ret.append(self.curse_add_line(msg, + self.get_views(key='nf_conntrack_percent', + option='decoration'))) + + return ret diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 84c5cc2f89ca6414a26ecab4e37839c4750bfb7c..0856cac3130696a1adde91adbb232c3a1fc70f2e 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -90,6 +90,9 @@ class GlancesPlugin(object): if not self.load_limits(config=config): logger.debug('Can not load section {} in {}'.format(self.plugin_name, config)) + else: + logger.debug('Load section {} in {}'.format(self.plugin_name, + config)) # Init the actions self.actions = GlancesActions(args=args)