From a549b08107f600a73e67589afc80bad3c0f01a0d Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 19 May 2023 17:32:41 +0200 Subject: First version available on the (simple) mem plugin --- glances/plugins/mem/model.py | 106 ++++++++++++++++++++++------------------ glances/plugins/plugin/model.py | 73 +++++++++++++++++++++------ 2 files changed, 116 insertions(+), 63 deletions(-) diff --git a/glances/plugins/mem/model.py b/glances/plugins/mem/model.py index 52ceed0c..c3d6b5c5 100644 --- a/glances/plugins/mem/model.py +++ b/glances/plugins/mem/model.py @@ -12,12 +12,16 @@ from glances.globals import iterkeys from glances.plugins.plugin.model import GlancesPluginModel -import psutil - # Fields description fields_description = { - 'total': {'description': 'Total physical memory available.', 'unit': 'bytes', 'min_symbol': 'K'}, + 'total': { + 'getter': 'psutil.virtual_memory', + 'description': 'Total physical memory available.', + 'unit': 'bytes', + 'min_symbol': 'K' + }, 'available': { + 'getter': 'psutil.virtual_memory', 'description': 'The actual amount of available memory that can be given instantly \ to processes that request more memory in bytes; this is calculated by summing \ different memory values depending on the platform (e.g. free + buffers + cached on Linux) \ @@ -26,49 +30,62 @@ and it is supposed to be used to monitor actual memory usage in a cross platform 'min_symbol': 'K', }, 'percent': { + 'getter': 'psutil.virtual_memory', 'description': 'The percentage usage calculated as (total - available) / total * 100.', 'unit': 'percent', }, - 'used': { - 'description': 'Memory used, calculated differently depending on the platform and \ -designed for informational purposes only.', - 'unit': 'bytes', - 'min_symbol': 'K', - }, - 'free': { - 'description': 'Memory not being used at all (zeroed) that is readily available; \ -note that this doesn\'t reflect the actual memory available (use \'available\' instead).', - 'unit': 'bytes', - 'min_symbol': 'K', - }, 'active': { + 'getter': 'psutil.virtual_memory', 'description': '*(UNIX)*: memory currently in use or very recently used, and so it is in RAM.', 'unit': 'bytes', 'min_symbol': 'K', }, 'inactive': { + 'getter': 'psutil.virtual_memory', 'description': '*(UNIX)*: memory that is marked as not used.', 'unit': 'bytes', 'min_symbol': 'K', 'short_name': 'inacti', }, 'buffers': { + 'getter': 'psutil.virtual_memory', 'description': '*(Linux, BSD)*: cache for things like file system metadata.', 'unit': 'bytes', 'min_symbol': 'K', 'short_name': 'buffer', }, - 'cached': {'description': '*(Linux, BSD)*: cache for various things.', 'unit': 'bytes', 'min_symbol': 'K'}, + 'cached': { + 'getter': 'psutil.virtual_memory', + 'description': '*(Linux, BSD)*: cache for various things.', + 'unit': 'bytes', + 'min_symbol': 'K' + }, 'wired': { + 'getter': 'psutil.virtual_memory', 'description': '*(BSD, macOS)*: memory that is marked to always stay in RAM. It is never moved to disk.', 'unit': 'bytes', 'min_symbol': 'K', }, 'shared': { + 'getter': 'psutil.virtual_memory', 'description': '*(BSD)*: memory that may be simultaneously accessed by multiple processes.', 'unit': 'bytes', 'min_symbol': 'K', }, + 'free': { + 'getter': 'compute', + 'description': 'Memory not being used at all (zeroed) that is readily available; \ +note that this doesn\'t reflect the actual memory available (use \'available\' instead).', + 'unit': 'bytes', + 'min_symbol': 'K', + }, + 'used': { + 'getter': 'compute', + 'description': 'Memory used, calculated differently depending on the platform and \ +designed for informational purposes only.', + 'unit': 'bytes', + 'min_symbol': 'K', + }, } # SNMP OID @@ -115,25 +132,42 @@ class PluginModel(GlancesPluginModel): 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 + args=args, + config=config, + items_history_list=items_history_list, + fields_description=fields_description ) # We want to display the stat in the curse interface self.display_curse = True + def free(self, stats): + """Return the free memory compute parameter""" + # Use the 'free'/htop calculation + # free=available+buffer+cached + ret = stats['available'] + if hasattr(stats, 'buffers'): + ret += stats['buffers'] + if hasattr(stats, 'cached'): + ret += stats['cached'] + return ret + + def used(self, stats): + """Return the used memory compute parameter""" + return stats['total'] - stats['free'] + @GlancesPluginModel._check_decorator @GlancesPluginModel._log_result_decorator def update(self): """Update RAM memory stats using the input method.""" + # Init new stats stats = self.get_init_value() if self.input_method == 'local': - # Update stats using the standard system lib - # Grab MEM using the psutil virtual_memory method - vm_stats = psutil.virtual_memory() - - # Get all the memory stats (copy/paste of the psutil documentation) + # + # Under the wood, Glances use psutil.virtual_memory() to get the memory stats + # # total: total physical memory available. # available: the actual amount of available memory that can be given instantly # to processes that request more memory in bytes; this is calculated by summing @@ -151,32 +185,8 @@ class PluginModel(GlancesPluginModel): # cached: (Linux, BSD): cache for various things. # wired: (BSD, macOS): memory that is marked to always stay in RAM. It is never moved to disk. # shared: (BSD): memory that may be simultaneously accessed by multiple processes. - self.reset() - for mem in [ - 'total', - 'available', - 'percent', - 'used', - 'free', - 'active', - 'inactive', - 'buffers', - 'cached', - 'wired', - 'shared', - ]: - if hasattr(vm_stats, mem): - stats[mem] = getattr(vm_stats, mem) - - # Use the 'free'/htop calculation - # free=available+buffer+cached - stats['free'] = stats['available'] - if hasattr(stats, 'buffers'): - stats['free'] += stats['buffers'] - if hasattr(stats, 'cached'): - stats['free'] += stats['cached'] - # used=total-free - stats['used'] = stats['total'] - stats['free'] + stats = self.update_local(stats) + elif self.input_method == 'snmp': # Update stats using SNMP if self.short_system_name in ('windows', 'esxi'): diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index 3c2eb4b4..af6993b5 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -16,6 +16,9 @@ I am your father... import re import copy +# PsUtil is call "dynamically", so the import should not be removed +import psutil + from glances.globals import iterkeys, itervalues, listkeys, mean, nativestr, json_dumps, json_dumps_dictlist from glances.actions import GlancesActions from glances.history import GlancesHistory @@ -111,6 +114,21 @@ class GlancesPluginModel(object): # Init stats description self.fields_description = fields_description + if fields_description: + # Return a list of getter (external functions to run to get the stats) + self.getters = list(set([fields_description[i]['getter'] for i in fields_description + if (i in fields_description and + 'getter' in fields_description[i] and + fields_description[i]['getter'] not in ('compute'))])) + # Return a list of internal functions to run to compute the stats + # Computation is done after the getters + self.computes = [i for i in fields_description + if (i in fields_description and + 'getter' in fields_description[i] and + fields_description[i]['getter'] in ('compute'))] + else: + self.getters = [] + self.computes = [] # Init the stats self.stats_init_value = stats_init_value @@ -175,6 +193,31 @@ class GlancesPluginModel(object): logger.debug("Reset history for plugin {} (items: {})".format(self.plugin_name, reset_list)) self.stats_history.reset() + def update_getters(self, stats): + """Return the stats updated from the getters functions.""" + for g in self.getters: + # For each "getter", the Python function is called + g_call = getattr(globals()[g.split('.')[0]], g.split('.')[1])() + # The result is stored in the stats dict + # (only if the field is in the self.fields_description) + for name in [f for f in g_call._fields if f in self.fields_description]: + stats[name] = getattr(g_call, name) + return stats + + def update_computes(self, stats): + """Return the stats updated from the computes functions.""" + for c in self.computes: + stats[c] = getattr(self, c)(stats) + return stats + + def update_local(self, stats): + """Return the stats updated by getters and computes.""" + # Update the stats from the getters + stats = self.update_getters(stats) + # Update the stats from the computes + stats = self.update_computes(stats) + return stats + def update_stats_history(self): """Update stats history.""" # Build the history @@ -483,9 +526,9 @@ class GlancesPluginModel(object): 'splittable': False, 'hidden': False, '_zero': self.views[i[self.get_key()]][key]['_zero'] - if i[self.get_key()] in self.views - and key in self.views[i[self.get_key()]] - and 'zero' in self.views[i[self.get_key()]][key] + if i[self.get_key()] in self.views and + key in self.views[i[self.get_key()]] and + 'zero' in self.views[i[self.get_key()]][key] else True, } ret[i[self.get_key()]][key] = value @@ -962,9 +1005,9 @@ class GlancesPluginModel(object): # Check if unit is defined and get the short unit char in the unit_sort dict if ( - key in self.fields_description - and 'unit' in self.fields_description[key] - and self.fields_description[key]['unit'] in fields_unit_short + key in self.fields_description and + 'unit' in self.fields_description[key] and + self.fields_description[key]['unit'] in fields_unit_short ): # Get the shortname unit_short = fields_unit_short[self.fields_description[key]['unit']] @@ -973,9 +1016,9 @@ class GlancesPluginModel(object): # Check if unit is defined and get the unit type unit_type dict if ( - key in self.fields_description - and 'unit' in self.fields_description[key] - and self.fields_description[key]['unit'] in fields_unit_type + key in self.fields_description and + 'unit' in self.fields_description[key] and + self.fields_description[key]['unit'] in fields_unit_type ): # Get the shortname unit_type = fields_unit_type[self.fields_description[key]['unit']] @@ -984,9 +1027,9 @@ class GlancesPluginModel(object): # Is it a rate ? Yes, compute it thanks to the time_since_update key if ( - key in self.fields_description - and 'rate' in self.fields_description[key] - and self.fields_description[key]['rate'] is True + key in self.fields_description and + 'rate' in self.fields_description[key] and + self.fields_description[key]['rate'] is True ): value = self.stats[key] // self.stats['time_since_update'] else: @@ -1010,8 +1053,8 @@ class GlancesPluginModel(object): msg_value = ( msg_template.format( self.auto_unit(int(value), min_symbol=self.fields_description[key]['min_symbol']), unit_short - ) - + trailer + ) + + trailer ) else: msg_value = msg_template.format(int(value), unit_short) + trailer @@ -1055,7 +1098,7 @@ class GlancesPluginModel(object): """ symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') if min_symbol in symbols: - symbols = symbols[symbols.index(min_symbol) :] + symbols = symbols[symbols.index(min_symbol):] prefix = { 'Y': 1208925819614629174706176, 'Z': 1180591620717411303424, -- cgit v1.2.3