summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornicolargo <nicolas@nicolargo.com>2019-09-28 17:27:06 +0200
committernicolargo <nicolas@nicolargo.com>2019-09-28 17:27:06 +0200
commitdcd28b778d50074df885449a43b3439d002ec041 (patch)
treeb20975060c09d520d112b839799f10287c13d363
parent66c9fff716434edb533f28b963f12aac173c5c4e (diff)
Add a new TCP connections status plugin #1526 RC1
-rw-r--r--conf/glances.conf7
-rw-r--r--docs/_static/connections.pngbin0 -> 7476 bytes
-rw-r--r--docs/aoa/connections.rst25
-rw-r--r--glances/outputs/glances_curses.py2
-rw-r--r--glances/plugins/glances_connections.py156
5 files changed, 188 insertions, 2 deletions
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 00000000..c46db065
--- /dev/null
+++ b/docs/_static/connections.png
Binary files differ
diff --git a/docs/aoa/connections.rst b/docs/aoa/connections.rst
new file mode 100644
index 00000000..b07733a9
--- /dev/null
+++ b/docs/aoa/connections.rst
@@ -0,0 +1,25 @@
+.. _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)
+
+The configuration should be done in the ``[connections]`` section of the
+Glances configuration file.
+
+By default the plugin is disabled.
+
+.. code-block:: ini
+
+ [connections]
+ disable=False
diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py
index 4a4b7632..dac51e07 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 00000000..920255e0
--- /dev/null
+++ b/glances/plugins/glances_connections.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Glances.
+#
+# Copyright (C) 2019 Nicolargo <nicolas@nicolargo.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""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