From 3765c0ee1654874e59223436ef6c3ad6e1084ad4 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 15 Oct 2023 09:57:05 +0200 Subject: Correct estimated executed duration when using --stop-after command line --- glances/__init__.py | 2 +- glances/standalone.py | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/glances/__init__.py b/glances/__init__.py index 9ae141aa..da642a3b 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -108,7 +108,7 @@ def start(config, args): # Start the main loop logger.debug("Glances started in {} seconds".format(start_duration.get())) if args.stop_after: - logger.info('Glances will be stopped in ~{} seconds'.format(args.stop_after * args.time * args.memory_leak * 2)) + logger.info('Glances will be stopped in ~{} seconds'.format(args.stop_after * args.time)) if args.memory_leak: print( diff --git a/glances/standalone.py b/glances/standalone.py index 8ab60e8c..8ea5400a 100644 --- a/glances/standalone.py +++ b/glances/standalone.py @@ -161,24 +161,22 @@ class GlancesStandalone(object): return ret + def serve_n(self, n=1): + """Serve n time.""" + for _ in range(n): + if not self.__serve_once(): + break + # self.end() + def serve_forever(self): """Wrapper to the serve_forever function.""" if self.args.stop_after: - for _ in range(self.args.stop_after): - if not self.__serve_once(): - break + self.serve_n(self.args.stop_after) else: while self.__serve_once(): pass # self.end() - def serve_n(self, n=1): - """Serve n time.""" - for _ in range(n): - if not self.__serve_once(): - break - # self.end() - def end(self): """End of the standalone CLI.""" if not self.quiet: -- cgit v1.2.3 From 30cc9f3e0e3b7d427e1d7c9a8a58a536288f7210 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 15 Oct 2023 10:42:19 +0200 Subject: Chage the color of the separator line --- glances/outputs/glances_curses.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 3f53ea77..e078e9b1 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -272,7 +272,9 @@ class _GlancesCurses(object): self.selected_color = A_BOLD if curses.COLOR_PAIRS > 8: - colors_list = [curses.COLOR_MAGENTA, curses.COLOR_CYAN, curses.COLOR_YELLOW] + colors_list = [curses.COLOR_MAGENTA, + curses.COLOR_CYAN, + curses.COLOR_YELLOW] for i in range(0, 3): try: curses.init_pair(i + 9, colors_list[i], -1) @@ -286,6 +288,10 @@ class _GlancesCurses(object): self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD self.filter_color = curses.color_pair(10) | A_BOLD self.selected_color = curses.color_pair(11) | A_BOLD + # Define separator line style + curses.init_color(12, 500, 500, 500) + curses.init_pair(12, curses.COLOR_BLACK, -1) + self.separator = curses.color_pair(12) else: # The screen is NOT compatible with a colored design @@ -304,6 +310,7 @@ class _GlancesCurses(object): self.ifINFO_color = A_BOLD self.filter_color = A_BOLD self.selected_color = A_BOLD + self.separator = curses.COLOR_BLACK # Define the colors list (hash table) for stats self.colors_list = { @@ -331,6 +338,7 @@ class _GlancesCurses(object): 'SELECTED': self.selected_color, 'INFO': self.ifINFO_color, 'ERROR': self.selected_color, + 'SEPARATOR': self.separator, } def set_cursor(self, value): @@ -578,7 +586,7 @@ class _GlancesCurses(object): """New column in the curses interface.""" self.column = self.next_column - def separator_line(self, color='TITLE'): + def separator_line(self, color='SEPARATOR'): """New separator line in the curses interface.""" if not self.args.enable_separator: return -- cgit v1.2.3 From 48251c8271e0d3c6492bee80507fead201088645 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 15 Oct 2023 18:05:18 +0200 Subject: Refactor plugin folder in the same way than exports modules --- glances/plugins/README.rst | 21 +- glances/plugins/alert/__init__.py | 251 +++++++++ glances/plugins/alert/model.py | 251 --------- glances/plugins/amps/__init__.py | 123 +++++ glances/plugins/amps/model.py | 123 ----- glances/plugins/cloud/__init__.py | 220 ++++++++ glances/plugins/cloud/model.py | 220 -------- glances/plugins/connections/__init__.py | 176 +++++++ glances/plugins/connections/model.py | 176 ------- glances/plugins/containers/__init__.py | 430 +++++++++++++++ glances/plugins/containers/model.py | 430 --------------- glances/plugins/core/__init__.py | 76 +++ glances/plugins/core/model.py | 76 --- glances/plugins/cpu/__init__.py | 392 ++++++++++++++ glances/plugins/cpu/model.py | 392 -------------- glances/plugins/diskio/__init__.py | 230 +++++++++ glances/plugins/diskio/model.py | 230 --------- glances/plugins/folders/__init__.py | 122 +++++ glances/plugins/folders/model.py | 122 ----- glances/plugins/fs/__init__.py | 268 ++++++++++ glances/plugins/fs/model.py | 268 ---------- glances/plugins/gpu/__init__.py | 347 +++++++++++++ glances/plugins/gpu/model.py | 347 ------------- glances/plugins/help/__init__.py | 183 +++++++ glances/plugins/help/model.py | 183 ------- glances/plugins/ip/__init__.py | 300 +++++++++++ glances/plugins/ip/model.py | 300 ----------- glances/plugins/irq/__init__.py | 198 +++++++ glances/plugins/irq/model.py | 198 ------- glances/plugins/load/__init__.py | 183 +++++++ glances/plugins/load/model.py | 183 ------- glances/plugins/mem/__init__.py | 285 ++++++++++ glances/plugins/mem/model.py | 285 ---------- glances/plugins/memswap/__init__.py | 191 +++++++ glances/plugins/memswap/model.py | 191 ------- glances/plugins/network/__init__.py | 389 ++++++++++++++ glances/plugins/network/model.py | 389 -------------- glances/plugins/now/__init__.py | 70 +++ glances/plugins/now/model.py | 70 --- glances/plugins/percpu/__init__.py | 102 ++++ glances/plugins/percpu/model.py | 102 ---- glances/plugins/ports/__init__.py | 352 +++++++++++++ glances/plugins/ports/model.py | 352 ------------- glances/plugins/processcount/__init__.py | 138 +++++ glances/plugins/processcount/model.py | 138 ----- glances/plugins/processlist/__init__.py | 833 ++++++++++++++++++++++++++++++ glances/plugins/processlist/model.py | 833 ------------------------------ glances/plugins/psutilversion/__init__.py | 49 ++ glances/plugins/psutilversion/model.py | 49 -- glances/plugins/quicklook/__init__.py | 176 +++++++ glances/plugins/quicklook/model.py | 176 ------- glances/plugins/raid/__init__.py | 161 ++++++ glances/plugins/raid/model.py | 161 ------ glances/plugins/sensors/__init__.py | 367 +++++++++++++ glances/plugins/sensors/model.py | 367 ------------- glances/plugins/smart/__init__.py | 191 +++++++ glances/plugins/smart/model.py | 191 ------- glances/plugins/system/__init__.py | 200 +++++++ glances/plugins/system/model.py | 200 ------- glances/plugins/uptime/__init__.py | 88 ++++ glances/plugins/uptime/model.py | 88 ---- glances/plugins/wifi/__init__.py | 268 ++++++++++ glances/plugins/wifi/model.py | 268 ---------- glances/stats.py | 25 +- 64 files changed, 7374 insertions(+), 7390 deletions(-) delete mode 100644 glances/plugins/alert/model.py delete mode 100644 glances/plugins/amps/model.py delete mode 100644 glances/plugins/cloud/model.py delete mode 100644 glances/plugins/connections/model.py delete mode 100644 glances/plugins/containers/model.py delete mode 100644 glances/plugins/core/model.py delete mode 100644 glances/plugins/cpu/model.py delete mode 100644 glances/plugins/diskio/model.py delete mode 100644 glances/plugins/folders/model.py delete mode 100644 glances/plugins/fs/model.py delete mode 100644 glances/plugins/gpu/model.py delete mode 100644 glances/plugins/help/model.py delete mode 100644 glances/plugins/ip/model.py delete mode 100644 glances/plugins/irq/model.py delete mode 100644 glances/plugins/load/model.py delete mode 100644 glances/plugins/mem/model.py delete mode 100644 glances/plugins/memswap/model.py delete mode 100644 glances/plugins/network/model.py delete mode 100644 glances/plugins/now/model.py delete mode 100644 glances/plugins/percpu/model.py delete mode 100644 glances/plugins/ports/model.py delete mode 100644 glances/plugins/processcount/model.py delete mode 100644 glances/plugins/processlist/model.py delete mode 100644 glances/plugins/psutilversion/model.py delete mode 100644 glances/plugins/quicklook/model.py delete mode 100644 glances/plugins/raid/model.py delete mode 100644 glances/plugins/sensors/model.py delete mode 100644 glances/plugins/smart/model.py delete mode 100644 glances/plugins/system/model.py delete mode 100644 glances/plugins/uptime/model.py delete mode 100644 glances/plugins/wifi/model.py diff --git a/glances/plugins/README.rst b/glances/plugins/README.rst index 11fe8b3a..32da654e 100644 --- a/glances/plugins/README.rst +++ b/glances/plugins/README.rst @@ -6,23 +6,16 @@ This is the Glances plugins folder. A Glances plugin is a Python module hosted in a folder. -It should be based on the MVC model. -- model: data model (where the stats will be updated) -- view: input for UI (where the stats are displayed) -- controler: output from UI (where the stats are controled) +It should implement a Class named PluginModel (inherited from GlancesPluginModel). -//// -TODO -//// +This class should be based on the MVC model. +- model: where the stats are updated (update method) +- view: where the stats are prepare to be displayed (update_views) +- controler: where the stats are displayed (msg_curse method) A plugin should define the following global variables: - fields_description: a dict twith the field description/option -- items_history_list: define items history +- items_history_list (optional): define items history -A plugin should implement the following methods: - -- update(): update the self.stats variable (most of the time a dict or a list of dict) -- msg_curse(): return a list of messages to display in UI - -Have a look of all Glances plugin's methods in the plugin.py file. +Have a look of all Glances plugin's methods in the plugin folder (where the GlancesPluginModel is defined). diff --git a/glances/plugins/alert/__init__.py b/glances/plugins/alert/__init__.py index e69de29b..950acec5 100644 --- a/glances/plugins/alert/__init__.py +++ b/glances/plugins/alert/__init__.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2023 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + +"""Alert plugin.""" + +from datetime import datetime + +from glances.logger import logger +from glances.events import glances_events +from glances.thresholds import glances_thresholds + +# from glances.logger import logger +from glances.plugins.plugin.model import GlancesPluginModel + +# Static decision tree for the global alert message +# - msg: Message to be displayed (result of the decision tree) +# - thresholds: a list of stats to take into account +# - thresholds_min: minimal value of the thresholds sum +# - 0: OK +# - 1: CAREFUL +# - 2: WARNING +# - 3: CRITICAL +tree = [ + {'msg': 'No warning or critical alert detected', 'thresholds': [], 'thresholds_min': 0}, + {'msg': 'High CPU user mode', 'thresholds': ['cpu_user'], 'thresholds_min': 2}, + {'msg': 'High CPU kernel usage', 'thresholds': ['cpu_system'], 'thresholds_min': 2}, + {'msg': 'High CPU I/O waiting', 'thresholds': ['cpu_iowait'], 'thresholds_min': 2}, + { + 'msg': 'Large CPU stolen time. System running the hypervisor is too busy.', + 'thresholds': ['cpu_steal'], + 'thresholds_min': 2, + }, + {'msg': 'High CPU niced value', 'thresholds': ['cpu_niced'], 'thresholds_min': 2}, + {'msg': 'System overloaded in the last 5 minutes', 'thresholds': ['load'], 'thresholds_min': 2}, + {'msg': 'High swap (paging) usage', 'thresholds': ['memswap'], 'thresholds_min': 2}, + {'msg': 'High memory consumption', 'thresholds': ['mem'], 'thresholds_min': 2}, +] + +# TODO: change the algo to use the following decision tree +# Source: Inspire by https://scoutapm.com/blog/slow_server_flow_chart +# _yes means threshold >= 2 +# _no means threshold < 2 +# With threshold: +# - 0: OK +# - 1: CAREFUL +# - 2: WARNING +# - 3: CRITICAL +tree_new = { + 'cpu_iowait': { + '_yes': { + 'memswap': { + '_yes': { + 'mem': { + '_yes': { + # Once you've identified the offenders, the resolution will again + # depend on whether their memory usage seems business-as-usual or not. + # For example, a memory leak can be satisfactorily addressed by a one-time + # or periodic restart of the process. + # - if memory usage seems anomalous: kill the offending processes. + # - if memory usage seems business-as-usual: add RAM to the server, + # or split high-memory using services to other servers. + '_msg': "Memory issue" + }, + '_no': { + # ??? + '_msg': "Swap issue" + }, + } + }, + '_no': { + # Low swap means you have a "real" IO wait problem. The next step is to see what's hogging your IO. + # iotop is an awesome tool for identifying io offenders. Two things to note: + # unless you've already installed iotop, it's probably not already on your system. + # Recommendation: install it before you need it - - it's no fun trying to install a troubleshooting + # tool on an overloaded machine (iotop requires a Linux of 2.62 or above) + '_msg': "I/O issue" + }, + } + }, + '_no': { + 'cpu_total': { + '_yes': { + 'cpu_user': { + '_yes': { + # We expect the user-time percentage to be high. + # There's most likely a program or service you've configured on you server that's + # hogging CPU. + # Checking the % user time just confirms this. When you see that the % user-time is high, + # it's time to see what executable is monopolizing the CPU + # Once you've confirmed that the % usertime is high, check the process list(also provided + # by top). + # Be default, top sorts the process list by % CPU, so you can just look at the top process + # or processes. + # If there's a single process hogging the CPU in a way that seems abnormal, it's an + # anomalous situation + # that a service restart can fix. If there are are multiple processes taking up CPU + # resources, or it + # there's one process that takes lots of resources while otherwise functioning normally, + # than your setup + # may just be underpowered. You'll need to upgrade your server(add more cores), + # or split services out onto + # other boxes. In either case, you have a resolution: + # - if situation seems anomalous: kill the offending processes. + # - if situation seems typical given history: upgrade server or add more servers. + '_msg': "CPU issue with user process(es)" + }, + '_no': { + 'cpu_steal': { + '_yes': { + '_msg': "CPU issue with stolen time. System running the hypervisor may be too busy." + }, + '_no': {'_msg': "CPU issue with system process(es)"}, + } + }, + } + }, + '_no': { + '_yes': { + # ??? + '_msg': "Memory issue" + }, + '_no': { + # Your slowness isn't due to CPU or IO problems, so it's likely an application-specific issue. + # It's also possible that the slowness is being caused by another server in your cluster, or + # by an external service you rely on. + # start by checking important applications for uncharacteristic slowness(the DB is a good place + # to start), think through which parts of your infrastructure could be slowed down externally. + # For example, do you use an externally hosted email service that could slow down critical + # parts of your application ? + # If you suspect another server in your cluster, strace and lsof can provide information on + # what the process is doing or waiting on. Strace will show you which file descriptors are + # being read or written to (or being attempted to be read from) and lsof can give you a + # mapping of those file descriptors to network connections. + '_msg': "External issue" + }, + }, + } + }, + } +} + + +def global_message(): + """Parse the decision tree and return the message. + + Note: message corresponding to the current thresholds values + """ + # Compute the weight for each item in the tree + current_thresholds = glances_thresholds.get() + for i in tree: + i['weight'] = sum([current_thresholds[t].value() for t in i['thresholds'] if t in current_thresholds]) + themax = max(tree, key=lambda d: d['weight']) + if themax['weight'] >= themax['thresholds_min']: + # Check if the weight is > to the minimal threshold value + return themax['msg'] + else: + return tree[0]['msg'] + + +class PluginModel(GlancesPluginModel): + """Glances alert plugin. + + Only for display. + """ + + def __init__(self, args=None, config=None): + """Init the plugin.""" + super(PluginModel, self).__init__(args=args, + config=config, + stats_init_value=[]) + + # We want to display the stat in the curse interface + self.display_curse = True + + # Set the message position + self.align = 'bottom' + + # Set the maximum number of events to display + if config is not None and (config.has_section('alert') or config.has_section('alerts')): + glances_events.set_max_events(config.get_int_value('alert', 'max_events')) + + def update(self): + """Nothing to do here. Just return the global glances_log.""" + # Set the stats to the glances_events + self.stats = glances_events.get() + # Define the global message thanks to the current thresholds + # and the decision tree + # !!! Call directly in the msg_curse function + # global_message() + + 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 display plugin enable... + if not self.stats or self.is_disabled(): + return ret + + # Build the string message + # Header + ret.append(self.curse_add_line(global_message(), "TITLE")) + # Loop over alerts + for alert in self.stats: + # New line + ret.append(self.curse_new_line()) + # Start + msg = str(datetime.fromtimestamp(alert[0])) + ret.append(self.curse_add_line(msg)) + # Duration + if alert[1] > 0: + # If finished display duration + msg = ' ({})'.format(datetime.fromtimestamp(alert[1]) - datetime.fromtimestamp(alert[0])) + else: + msg = ' (ongoing)' + ret.append(self.curse_add_line(msg)) + ret.append(self.curse_add_line(" - ")) + # Infos + if alert[1] > 0: + # If finished do not display status + msg = '{} on {}'.format(alert[2], alert[3]) + ret.append(self.curse_add_line(msg)) + else: + msg = str(alert[3]) + ret.append(self.curse_add_line(msg, decoration=alert[2])) + # Min / Mean / Max + if self.approx_equal(alert[6], alert[4], tolerance=0.1): + msg = ' ({:.1f})'.format(alert[5]) + else: + msg = ' (Min:{:.1f} Mean:{:.1f} Max:{:.1f})'.format(alert[6], alert[5], alert[4]) + ret.append(self.curse_add_line(msg)) + # Top processes + top_process = ', '.join([p['name'] for p in alert[9]]) + if top_process != '': + msg = ': {}'.format(top_process) + ret.append(self.curse_add_line(msg)) + + return ret + + def approx_equal(self, a, b, tolerance=0.0): + """Compare a with b using the tolerance (if numerical).""" + if str(int(a)).isdigit() and str(int(b)).isdigit(): + return abs(a - b) <= max(abs(a), abs(b)) * tolerance + else: + return a == b diff --git a/glances/plugins/alert/model.py b/glances/plugins/alert/model.py deleted file mode 100644 index 950acec5..00000000 --- a/glances/plugins/alert/model.py +++ /dev/null @@ -1,251 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# SPDX-FileCopyrightText: 2023 Nicolas Hennion -# -# SPDX-License-Identifier: LGPL-3.0-only -# - -"""Alert plugin.""" - -from datetime import datetime - -from glances.logger import logger -from glances.events import glances_events -from glances.thresholds import glances_thresholds - -# from glances.logger import logger -from glances.plugins.plugin.model import GlancesPluginModel - -# Static decision tree for the global alert message -# - msg: Message to be displayed (result of the decision tree) -# - thresholds: a list of stats to take into account -# - thresholds_min: minimal value of the thresholds sum -# - 0: OK -# - 1: CAREFUL -# - 2: WARNING -# - 3: CRITICAL -tree = [ - {'msg': 'No warning or critical alert detected', 'thresholds': [], 'thresholds_min': 0}, - {'msg': 'High CPU user mode', 'thresholds': ['cpu_user'], 'thresholds_min': 2}, - {'msg': 'High CPU kernel usage', 'thresholds': ['cpu_system'], 'thresholds_min': 2}, - {'msg': 'High CPU I/O waiting', 'thresholds': ['cpu_iowait'], 'thresholds_min': 2}, - { - 'msg': 'Large CPU stolen time. System running the hypervisor is too busy.', - 'thresholds': ['cpu_steal'], - 'thresholds_min': 2, - }, - {'msg': 'High CPU niced value', 'thresholds': ['cpu_niced'], 'thresholds_min': 2}, - {'msg': 'System overloaded in the last 5 minutes', 'thresholds': ['load'], 'thresholds_min': 2}, - {'msg': 'High swap (paging) usage', 'thresholds': ['memswap'], 'thresholds_min': 2}, - {'msg': 'High memory consumption', 'thresholds': ['mem'], 'thresholds_min': 2}, -] - -# TODO: change the algo to use the following decision tree -# Source: Inspire by https://scoutapm.com/blog/slow_server_flow_chart -# _yes means threshold >= 2 -# _no means threshold < 2 -# With threshold: -# - 0: OK -# - 1: CAREFUL -# - 2: WARNING -# - 3: CRITICAL -tree_new = { - 'cpu_iowait': { - '_yes': { - 'memswap': { - '_yes': { - 'mem': { - '_yes': { - # Once you've identified the offenders, the resolution will again - # depend on whether their memory usage seems business-as-usual or not. - # For example, a memory leak can be satisfactorily addressed by a one-time - # or periodic restart of the process. - # - if memory usage seems anomalous: kill the offending processes. - # - if memory usage seems business-as-usual: add RAM to the server, - # or split high-memory using services to other servers. - '_msg': "Memory issue" - }, - '_no': { - # ??? - '_msg': "Swap issue" - }, - } - }, - '_no': { - # Low swap means you have a "real" IO wait problem. The next step is to see what's hogging your IO. - # iotop is an awesome tool for identifying io offenders. Two things to note: - # unless you've already installed iotop, it's probably not already on your system. - # Recommendation: install it before you need it - - it's no fun trying to install a troubleshooting - # tool on an overloaded machine (iotop requires a Linux of 2.62 or above) - '_msg': "I/O issue" - }, - } - }, - '_no': { - 'cpu_total': { - '_yes': { - 'cpu_user': { - '_yes': { - # We expect the user-time percentage to be high. - # There's most likely a program or service you've configured on you server that's - # hogging CPU. - # Checking the % user time just confirms this. When you see that the % user-time is high, - # it's time to see what executable is monopolizing the CPU - # Once you've confirmed that the % usertime is high, check the process list(also provided - # by top). - # Be default, top sorts the process list by % CPU, so you can just look at the top process - # or processes. - # If there's a single process hogging the CPU in a way that seems abnormal, it's an - # anomalous situation - # that a service restart can fix. If there are are multiple processes taking up CPU - # resources, or it - # there's one process that takes lots of resources while otherwise functioning normally, - # than your setup - # may just be underpowered. You'll need to upgrade your server(add more cores), - # or split services out onto - # other boxes. In either case, you have a resolution: - # - if situation seems anomalous: kill the offending processes. - # - if situation seems typical given history: upgrade server or add more servers. - '_msg': "CPU issue with user process(es)" - }, - '_no': { - 'cpu_steal': { - '_yes': { - '_msg': "CPU issue with stolen time. System running the hypervisor may be too busy." - }, - '_no': {'_msg': "CPU issue with system process(es)"}, - } - }, - } - }, - '_no': { - '_yes': { - # ??? - '_msg': "Memory issue" - }, - '_no': { - # Your slowness isn't due to CPU or IO problems, so it's likely an application-specific issue. - # It's also possible that the slowness is being caused by another server in your cluster, or - # by an external service you rely on. - # start by checking important applications for uncharacteristic slowness(the DB is a good place - # to start), think through which parts of your infrastructure could be slowed down externally. - # For example, do you use an externally hosted email service that could slow down critical - # parts of your application ? - # If you suspect another server in your cluster, strace and lsof can provide information on - # what the process is doing or waiting on. Strace will show you which file descriptors are - # being read or written to (or being attempted to be read from) and lsof can give you a - # mapping of those file descriptors to network connections. - '_msg': "External issue" - }, - }, - } - }, - } -} - - -def global_message(): - """Parse the decision tree and return the message. - - Note: message corresponding to the current thresholds values - """ - # Compute the weight for each item in the tree - current_thresholds = glances_thresholds.get() - for i in tree: - i['weight'] = sum([current_thresholds[t].value() for t in i['thresholds'] if t in current_thresholds]) - themax = max(tree, key=lambda d: d['weight']) - if themax['weight'] >= themax['thresholds_min']: - # Check if the weight is > to the minimal threshold value - return themax['msg'] - else: - return tree[0]['msg'] - - -class PluginModel(GlancesPluginModel): - """Glances alert plugin. - - Only for display. - """ - - def __init__(self, args=None, config=None): - """Init the plugin.""" - super(PluginModel, self).__init__(args=args, - config=config, - stats_init_value=[]) - - # We want to display the stat in the curse interface - self.display_curse = True - - # Set the message position - self.align = 'bottom' - - # Set the maximum number of events to display - if config is not None and (config.has_section('alert') or config.has_section('alerts')): - glances_events.set_max_events(config.get_int_value('alert', 'max_events')) - - def update(self): - """Nothing to do here. Just return the global glances_log.""" - # Set the stats to the glances_events - self.stats = glances_events.get() - # Define the global message thanks to the current thresholds - # and the decision tree - # !!! Call directly in the msg_curse function - # global_message() - - 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 display plugin enable... - if not self.stats or self.is_disabled(): - return ret - - # Build the string message - # Header - ret.append(self.curse_add_line(global_message(), "TITLE")) - # Loop over alerts - for alert in self.stats: - # New line - ret.append(self.curse_new_line()) - # Start - msg = str(datetime.fromtimestamp(alert[0])) - ret.append(self.curse_add_line(msg)) - # Duration - if alert[1] > 0: - # If finished display duration - msg = ' ({})'.format(datetime.fromtimestamp(alert[1]) - datetime.fromtimestamp(alert[0])) - else: - msg = ' (ongoing)' - ret.append(self.curse_add_line(msg)) - ret.append(self.curse_add_line(" - ")) - # Infos - if alert[1] > 0: - # If finished do not display status - msg = '{} on {}'.format(alert[2], alert[3]) - ret.append(self.curse_add_line(msg)) - else: - msg = str(alert[3]) - ret.append(self.curse_add_line(msg, decoration=alert[2])) - # Min / Mean / Max - if self.approx_equal(alert[6], alert[4], tolerance=0.1): - msg = ' ({:.1f})'.format(alert[5]) - else: - msg = ' (Min:{:.1f} Mean:{:.1f} Max:{:.1f})'.format(alert[6], alert[5], alert[4]) - ret.append(self.curse_add_line(msg)) - # Top processes - top_process = ', '.join([p['name'] for p in alert[9]]) - if top_process != '': - msg = ': {}'.format(top_process) - ret.append(self.curse_add_line(msg)) - - return ret - - def approx_equal(self, a, b, tolerance=0.0): - """Compare a with b using the tolerance (if numerical).""" - if str(int(a)).isdigit() and str(int(b)).isdigit(): - return abs(a - b) <= max(abs(a), abs(b)) * tolerance - else: - return a == b diff --git a/glances/plugins/amps/__init__.py b/glances/plugins/amps/__init__.py index e69de29b..347f1c31 100644 --- a/glances/plugins/amps/__init__.py +++ b/glances/plugins/amps/__init__.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2023 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + +"""Monitor plugin.""" + +from glances.globals import iteritems +from glances.amps_list import AmpsList as glancesAmpsList +from glances.plugins.plugin.model import GlancesPluginModel + + +class PluginModel(GlancesPluginModel): + """Glances AMPs plugin.""" + + def __init__(self, args=None, config=None): + """Init the plugin.""" + super(PluginModel, self).__init__(args=args, config=config, stats_init_value=[]) + self.args = args + self.config = config + + # We want to display the stat in the curse interface + self.display_curse = True + + # Init the list of AMP (classes define in the glances/amps_list.py script) + self.glances_amps = glancesAmpsList(self.args, self.config) + + def get_key(self): + """Return the key of the list.""" + return 'name' + + @GlancesPluginModel._check_decorator + @GlancesPluginModel._log_result_decorator + def update(self): + """Update the AMP list.""" + # Init new stats + stats = self.get_init_value() + + if self.input_method == 'local': + for k, v in iteritems(self.glances_amps.update()): + stats.append( + { + 'key': self.get_key(), + 'name': v.NAME, + 'result': v.result(), + 'refresh': v.refresh(), + 'timer': v.time_until_refresh(), + 'count': v.count(), + 'countmin': v.count_min(), + 'countmax': v.count_max(), + 'regex': v.regex() is not None, + }, + ) + else: + # Not available in SNMP mode + pass + + # Update the stats + self.stats = stats + + return self.stats + + def get_alert(self, nbprocess=0, countmin=None, countmax=None, header="", log=False): + """Return the alert status relative to the process number.""" + if nbprocess is None: + return 'OK' + if countmin is None: + countmin = nbprocess + if countmax is None: + countmax = nbprocess + if nbprocess > 0: + if int(countmin) <= int(nbprocess) <= int(countmax): + return 'OK' + else: + return 'WARNING' + else: + if int(countmin) == 0: + return 'OK' + else: + return 'CRITICAL' + + def msg_curse(self, args=None, max_width=None): + """Return the dict to display in the curse interface.""" + # Init the return message + # Only process if stats exist and display plugin enable... + ret = [] + + if not self.stats or args.disable_process or self.is_disabled(): + return ret + + # Build the string message + for m in self.stats: + # Only display AMP if a result exist + if m['result'] is None: + continue + # Display AMP + first_column = '{}'.format(m['name']) + first_column_style = self.get_alert(m['count'], m['countmin'], m['countmax']) + second_column = '{}'.format(m['count'] if m['regex'] else '') + for line in m['result'].split('\n'): + # Display first column with the process name... + msg = '{:<16} '.format(first_column) + ret.append(self.curse_add_line(msg, first_column_style)) + # ... and second column with the number of matching processes... + msg = '{:<4} '.format(second_column) + ret.append(self.curse_add_line(msg)) + # ... only on the first line + first_column = second_column = '' + # Display AMP result in the third column + ret.append(self.curse_add_line(line, splittable=True)) + ret.append(self.curse_new_line()) + + # Delete the last empty line + try: + ret.pop() + except IndexError: + pass + + return ret diff --git a/glances/plugins/amps/model.py b/glances/plugins/amps/model.py deleted file mode 100644 index 347f1c31..00000000 --- a/glances/plugins/amps/model.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# SPDX-FileCopyrightText: 2023 Nicolas Hennion -# -# SPDX-License-Identifier: LGPL-3.0-only -# - -"""Monitor plugin.""" - -from glances.globals import iteritems -from glances.amps_list import AmpsList as glancesAmpsList -from glances.plugins.plugin.model import GlancesPluginModel - - -class PluginModel(GlancesPluginModel): - """Glances AMPs plugin.""" - - def __init__(self, args=None, config=None): - """Init the plugin.""" - super(PluginModel, self).__init__(args=args, config=config, stats_init_value=[]) - self.args = args - self.config = config - - # We want to display the stat in the curse interface - self.display_curse = True - - # Init the list of AMP (classes define in the glances/amps_list.py script) - self.glances_amps = glancesAmpsList(self.args, self.config) - - def get_key(self): - """Return the key of the list.""" - return 'name' - - @GlancesPluginModel._check_decorator - @GlancesPluginModel._log_result_decorator - def update(self): - """Update the AMP list.""" - # Init new stats - stats = self.get_init_value() - - if self.input_method == 'local': - for k, v in iteritems(self.glances_amps.update()): - stats.append( - { - 'key': self.get_key(), - 'name': v.NAME, - 'result': v.result(), - 'refresh': v.refresh(), - 'timer': v.time_until_refresh(), - 'count': v.count(), - 'countmin': v.count_min(), - 'countmax': v.count_max(), - 'regex': v.regex() is not None, - }, - ) - else: - # Not available in SNMP mode - pass - - # Update the stats - self.stats = stats - - return self.stats - - def get_alert(self, nbprocess=0, countmin=None, countmax=None, header="", log=False): - """Return the alert status relative to the process number.""" - if nbprocess is None: - return 'OK' - if countmin is None: - countmin = nbprocess - if countmax is None: - countmax = nbprocess - if nbprocess > 0: - if int(countmin) <= int(nbprocess) <= int(countmax): - return 'OK' - else: - return 'WARNING' - else: - if int(countmin) == 0: - return 'OK' - else: - return 'CRITICAL' - - def msg_curse(self, args=None, max_width=None): - """Return the dict to display in the curse interface.""" - # Init the return message - # Only process if stats exist and display plugin enable... - ret = [] - - if not self.stats or args.disable_process or self.is_disabled(): - return ret - - # Build the string message - for m in self.stats: - # Only display AMP if a result exist - if m['result'] is None: - continue - # Display AMP - first_column = '{}'.format(m['name']) - first_column_style = self.get_alert(m['count'], m['countmin'], m['countmax']) - second_column = '{}'.format(m['count'] if m['regex'] else '') - for line in m['result'].split('\n'): - # Display first column with the process name... - msg = '{:<16} '.format(first_column) - ret.append(self.curse_add_line(msg, first_column_style)) - # ... and second column with the number of matching processes... - msg = '{:<4} '.format(second_column) - ret.append(self.curse_add_line(msg)) - # ... only on the first line - first_column = second_column = '' - # Display AMP result in the third column - ret.append(self.curse_add_line(line, splittable=True)) - ret.append(self.curse_new_line()) - - # Delete the last empty line - try: - ret.pop() - except IndexError: - pass - - return ret diff --git a/glances/plugins/cloud/__init__.py b/glances/plugins/cloud/__init__.py index e69de29b..72b8dde1 100644 --- a/glances/plugins/cloud/__init__.py +++ b/glances/plugins/cloud/__init__.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + +"""Cloud plugin. + +Supported Cloud API: +- OpenStack meta data (class ThreadOpenStack) - Vanilla OpenStack +- OpenStackEC2 meta data (class ThreadOpenStackEC2) - Amazon EC2 compatible +""" + +import threading + +from glances.globals import iteritems, to_ascii +from glances.plugins.plugin.model import GlancesPluginModel +from glances.logger import logger + +# Import plugin specific dependency +try: + import requests +except ImportError as e: + import_error_tag = True + # Display debug message if import error + logger.warning("Missing Python Lib ({}), Cloud plugin is disabled".format(e)) +else: + import_error_tag = False + + +class PluginModel(GlancesPluginModel): + """Glances' cloud plugin. + + The goal of this plugin is to retrieve additional information + concerning the datacenter where the host is connected. + + See https://github.com/nicolargo/glances/issues/1029 + + stats is a dict + """ + + def __init__(self, args=None, config=None): + """Init the plugin.""" + super(PluginModel, self).__init__(args=args, config=config) + + # We want to display the stat in the curse interface + self.display_curse = True + + # Init the stats + self.reset() + + # Init thread to grab OpenStack stats asynchronously + self.OPENSTACK = ThreadOpenStack() + self.OPENSTACKEC2 = ThreadOpenStackEC2() + + # Run the thread + self.OPENSTACK.start() + self.OPENSTACKEC2.start() + + def exit(self): + """Overwrite the exit method to close threads.""" + self.OPENSTACK.stop() + self.OPENSTACKEC2.stop() + # Call the father class + super(PluginModel, self).exit() + + @GlancesPluginModel._check_decorator + @GlancesPluginModel._log_result_decorator + def update(self): + """Update the cloud stats. + + Return the stats (dict) + """ + # Init new stats + stats = self.get_init_value() + + # Requests lib is needed to get stats from the Cloud API + if import_error_tag: + return stats + + # Update the stats + if self.input_method == 'local': + stats = self.OPENSTACK.stats + if not stats: + stats = self.OPENSTACKEC2.stats + # Example: + # Uncomment to test on physical computer (only for test purpose) + # stats = {'id': 'ami-id', + # 'name': 'My VM', + # 'type': 'Gold', + # 'region': 'France', + # 'platform': 'OpenStack'} + + # Update the stats + self.stats = stats + + return self.stats + + def msg_curse(self, args=None, max_width=None): + """Return the string to display in the curse interface.""" + # Init the return message + ret = [] + + if not self.stats or self.stats == {} or self.is_disabled(): + return ret + + # Generate the output + msg = self.stats.get('platform', 'Unknown') + ret.append(self.curse_add_line(msg, "TITLE")) + msg = ' {} instance {} ({})'.format( + self.stats.get('type', 'Unknown'), self.stats.get('name', 'Unknown'), self.stats.get('region', 'Unknown') + ) + ret.append(self.curse_add_line(msg)) + + # Return the message with decoration + # logger.info(ret) + return ret + + +class ThreadOpenStack(threading.Thread): + """ + Specific thread to grab OpenStack stats. + + stats is a dict + """ + + # The metadata service provides a way for instances to retrieve + # instance-specific data via a REST API. Instances access this + # service at 169.254.169.254 or at fe80::a9fe:a9fe. + # All types of metadata, be it user-, nova- or vendor-provided, + # can be accessed via this service. + # https://docs.openstack.org/nova/latest/user/metadata-service.html + OPENSTACK_PLATFORM = "OpenStack" + OPENSTACK_API_URL = 'http://169.254.169.254/openstack/latest/meta-data' + OPENSTACK_API_METADATA = { + 'id': 'project_id', + 'name': 'name', + 'type': 'meta/role', + 'region': 'availability_zone', + } + + def __init__(self): + """Init the class.""" + logger.debug("cloud plugin - Create thread for OpenStack metadata") + super(ThreadOpenStack, self).__init__() + # Event needed to stop properly the thread + self._stopper = threading.Event() + # The class return the stats as a dict + self._stats = {} + + def run(self): + """Grab plugin's stats. + + Infinite loop, should be stopped by calling the stop() method + """ + if import_error_tag: + self.stop() + return False + + for k, v in iteritems(self.OPENSTACK_API_METADATA): + r_url = '{}/{}'.format(self.OPENSTACK_API_URL, v) + try: + # Local request, a timeout of 3 seconds is OK + r = requests.get(r_url, timeout=3) + except Exception as e: + logger.debug('cloud plugin - Cannot connect to the OpenStack metadata API {}: {}'.format(r_url, e)) + break + else: + if r.ok: + self._stats[k] = to_ascii(r.content) + else: + # No break during the loop, so we can set the platform + self._stats['platform'] = self.OPENSTACK_PLATFORM + + return True + + @property + def stats(self): + """Stats getter.""" + return self._stats + + @stats.setter + def stats(self, value): + """Stats setter.""" + self._stats = value + + def stop(self, timeout=None): + """Stop the thread.""" + logger.debug("cloud plugin - Close thread for OpenStack metadata") + self._stopper.set() + + def stopped(self): + """Return True is the thread is stopped.""" + return self._stopper.is_set() + + +class ThreadOpenStackEC2(ThreadOpenStack): + """ + Specific thread to grab OpenStack EC2 (Amazon cloud) stats. + + stats is a dict + """ + + # The metadata service provides a way for instances to retrieve + # instance-specific data via a REST API. Instances access this + # service at 169.254.169.254 or at fe80::a9fe:a9fe. + # All types of metadata, be it user-, nova- or vendor-provided, + # can be accessed via this service. + # https://docs.openstack.org/nova/latest/user/metadata-service.html + OPENSTACK_PLATFORM = "Amazon EC2" + OPENSTACK_API_URL = 'http://169.254.169.254/latest/meta-data' + OPENSTACK_API_METADATA = { + 'id': 'ami-id', + 'name': 'instance-id', + 'type': 'instance-type', + 'region': 'placement/availability-zone', + } diff --git a/glances/plugins/cloud/model.py b/glances/plugins/cloud/model.py deleted file mode 100644 index 72b8dde1..00000000 --- a/glances/plugins/cloud/model.py +++ /dev/null @@ -1,220 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# SPDX-FileCopyrightText: 2022 Nicolas Hennion -# -# SPDX-License-Identifier: LGPL-3.0-only -# - -"""Cloud plugin. - -Supported Cloud API: -- OpenStack meta data (class ThreadOpenStack) - Vanilla OpenStack -- OpenStackEC2 meta data (class ThreadOpenStackEC2) - Amazon EC2 compatible -""" - -import threading - -from glances.globals import iteritems, to_ascii -from glances.plugins.plugin.model import GlancesPluginModel -from glances.logger import logger - -# Import plugin specific dependency -try: - import requests -except ImportError as e: - import_error_tag = True - # Display debug message if import error - logger.warning("Missing Python Lib ({}), Cloud plugin is disabled".format(e)) -else: - import_error_tag = False - - -class PluginModel(GlancesPluginModel): - """Glances' cloud plugin. - - The goal of this plugin is to retrieve additional information - concerning the datacenter where the host is connected. - - See https://github.com/nicolargo/glances/issues/1029 - - stats is a dict - """ - - def __init__(self, args=None, config=None): - """Init the plugin.""" - super(PluginModel, self).__init__(args=args, config=config) - - # We want to display the stat in the curse interface - self.display_curse = True - - # Init the stats - self.reset() - - # Init thread to grab OpenStack stats asynchronously - self.OPENSTACK = ThreadOpenStack() - self.OPENSTACKEC2 = ThreadOpenStackEC2() - - # Run the thread - self.OPENSTACK.start() - self.OPENSTACKEC2.start() - - def exit(self): - """Overwrite the exit method to close threads.""" - self.OPENSTACK.stop() - self.OPENSTACKEC2.stop() - # Call the father class - super(PluginModel, self).exit() - - @GlancesPluginModel._check_decorator - @GlancesPluginModel._log_result_decorator - def update(self): - """Update the cloud stats. - - Return the stats (dict) - """ - # Init new stats - stats = self.get_init_value() - - # Requests lib is needed to get stats from the Cloud API - if import_error_tag: - return stats - - # Update the stats - if self.input_method == 'local': - stats = self.OPENSTACK.stats - if not stats: - stats = self.OPENSTACKEC2.stats - # Example: - # Uncomment to test on physical computer (only for test purpose) - # stats = {'id': 'ami-id', - # 'name': 'My VM', - # 'type': 'Gold', - # 'region': 'France', - # 'platform': 'OpenStack'} - - # Update the stats - self.stats = stats - - return self.stats - - def msg_curse(self, args=None, max_width=None): - """Return the string to display in the curse interface.""" - # Init the return message - ret = [] - - if not self.stats or self.stats == {} or self.is_disabled(): - return ret - - # Generate the output - msg = self.stats.get('platform', 'Unknown') - ret.append(self.curse_add_line(msg, "TITLE")) - msg = ' {} instance {} ({})'.format( - self.stats.get('type', 'Unknown'), self.stats.get('name', 'Unknown'), self.stats.get('region', 'Unknown') - ) - ret.append(self.curse_add_line(msg)) - - # Return the message with decoration - # logger.info(ret) - return ret - - -class ThreadOpenStack(threading.Thread): - """ - Specific thread to grab OpenStack stats. - - stats is a dict - """ - - # The metadata service provides a way for instances to retrieve - # instance-specific data via a REST API. Instances access this - # service at 169.254.169.254 or at fe80::a9fe:a9fe. - # All types of metadata, be it user-, nova- or vendor-provided, - # can be accessed via this service. - # https://docs.openstack.org/nova/latest/user/metadata-service.html - OPENSTACK_PLATFORM = "OpenStack" - OPENSTACK_API_URL = 'http://169.254.169.254/openstack/latest/meta-data' - OPENSTACK_API_METADATA = { - 'id': 'project_id', - 'name': 'name', - 'type': 'meta/role', - 'region': 'availability_zone', - } - - def __init__(self): - """Init the class.""" - logger.debug("cloud plugin - Create thread for OpenStack metadata") - super(ThreadOpenStack, self).__init__() - # Event needed to stop properly the thread - self._stopper = threading.Event() - # The class return the stats as a dict - self._stats = {} - - def run(self): - """Grab plugin's stats. - - Infinite loop, should be stopped by calling the stop() method - """ - if import_error_tag: - self.stop() - return False - - for k, v in iteritems(self.OPENSTACK_API_METADATA): - r_url = '{}/{}'.format(self.OPENSTACK_API_URL, v) - try: - # Local request, a timeout of 3 seconds is OK - r = requests.get(r_url, timeout=3) - except Exception as e: - logger.debug('cloud plugin - Cannot connect to the OpenStack metadata API {}: {}'.format(r_url, e)) - break - else: - if r.ok: - self._stats[k] = to_ascii(r.content) - else: - # No break during the loop, so we can set the platform - self._stats['platform'] = self.OPENSTACK_PLATFORM - - return True - - @property - def stats(self): - """Stats getter.""" - return self._stats - - @stats.setter - def stats(self, value): - """Stats setter.""" - self._stats = value - - def stop(self, timeout=None): - """Stop the thread.""" - logger.debug("cloud plugin - Close thread for OpenStack metadata") - self._stopper.set() - - def stopped(self): - """Return True is the thread is stopped.""" - return self._stopper.is_set() - - -class ThreadOpenStackEC2(ThreadOpenStack): - """ - Specific thread to grab OpenStack EC2 (Amazon cloud) stats. - - stats is a dict - """ - - # The metadata service provides a way for instances to retrieve - # instance-specific data via a REST API. Instances access this - # service at 169.254.169.254 or at fe80::a9fe:a9fe. - # All types of metadata, be it user-, nova- or vendor-provided, - # can be accessed via this service. - # https://docs.openstack.org/nova/latest/user/metadata-service.html - OPENSTACK_PLATFORM = "Amazon EC2" - OPENSTACK_API_URL = 'http://169.254.169.254/latest/meta-data' - OPENSTACK_API_METADATA = { - 'id': 'ami-id', - 'name': 'instance-id', - 'type': 'instance-type', - 'region': 'placement/availability-zone', - } diff --git a/glances/plugins/connections/__init__.py b/glances/plugins/connections/__init__.py index e69de29b..b3858d1e 100644 --- a/glances/plugins/connections/__init__.py +++ b/glances/plugins/connections/__init__.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# +# SPDX-License-Identifier: LGPL-3.0-only +# + +"""Connections plugin.""" +from __future__ import unicode_literals + +from glances.logger import logger +from glances.plugins.plugin.model import GlancesPluginModel +from glances.globals import 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 PluginModel(GlancesPluginModel): + """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(PluginModel, self).__init__( + args=args, + config=config, + # items_history_list=items_history_list, + stats_init_value={'net_connections_enabled': True, 'nf_conntrack_enabled': True}, + ) + + # We want to display the stat in the curse interface + self.display_curse = True + + @GlancesPluginModel._check_decorator + @GlancesPluginModel._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 + if stats['net_connections_enabled']: + try: + net_connections = psutil.net_connections(kind="tcp") + except Exception as e: + logger.warning('Can not get network connections stats ({})'.format(e)) + logger.info('Disable connections stats') + stats['net_connections_enabled'] = False + self.stats = stats + 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 + + if stats['nf_conntrack_enabled']: + # Grab connections track directly from the /proc file + for i in self.conntrack: + try: + with open(self.conntrack[i], 'r') as f: + stats[i] = float(f.readline().rstrip("\n")) + except (IOError, FileNotFoundError) as e: + logger.warning('Can not get network connections track ({})'.format(e)) + logger.info('Disable connections track') + stats['nf_conntrack_enabled'] = False + self.stats = stats + return self.stats + if 'nf_conntrack_max' in stats and 'nf_conntrack_count' in stats: + stats['nf_conntrack_percent'] = stats['nf_conntrack_count'] * 100 / stats['nf_conntrack_max'] + else: + stats['nf_conntrack_enabled'] = False + self.stats = stats + return self.stats + + 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(PluginModel, self).update_views() + + # Add specific information + try: + # Alert and log + if self.stats['nf_conntrack_enabled']: + 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 = [] + + # Only process if stats exist and display plugin enable... + if not self.stats or self.is_disabled(): + return ret + + # Header + if self.stats['net_connections_enabled'] or self.stats['nf_conntrack_enabled']: + msg = '{}'.format('TCP CONNECTIONS') + ret.append(self.curse_add_line(msg, "TITLE")) + # Connections status + if self.stats['net_connections_enabled']: + 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 = '{:>