diff options
Diffstat (limited to 'glances')
-rw-r--r-- | glances/__init__.py | 2 | ||||
-rw-r--r-- | glances/main.py | 2 | ||||
-rw-r--r-- | glances/outputs/glances_stdout_apidoc.py | 231 | ||||
-rw-r--r-- | glances/plugins/glances_core.py | 15 | ||||
-rw-r--r-- | glances/plugins/glances_cpu.py | 49 | ||||
-rw-r--r-- | glances/plugins/glances_load.py | 21 | ||||
-rw-r--r-- | glances/plugins/glances_mem.py | 34 | ||||
-rw-r--r-- | glances/plugins/glances_memswap.py | 30 | ||||
-rw-r--r-- | glances/plugins/glances_network.py | 31 | ||||
-rw-r--r-- | glances/plugins/glances_plugin.py | 8 | ||||
-rw-r--r-- | glances/processes.py | 3 | ||||
-rw-r--r-- | glances/standalone.py | 5 |
12 files changed, 420 insertions, 11 deletions
diff --git a/glances/__init__.py b/glances/__init__.py index 1f270d50..dfe1591c 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -110,7 +110,7 @@ def start(config, args): # Start the main loop logger.debug("Glances started in {} seconds".format(start_duration.get())) - if args.stdout_issue: + if args.stdout_issue or args.stdout_apidoc: # Serve once for issue/test mode mode.serve_issue() else: diff --git a/glances/main.py b/glances/main.py index 88b27038..ae23dbf0 100644 --- a/glances/main.py +++ b/glances/main.py @@ -230,6 +230,8 @@ Examples of use: dest='stdout_csv', help='display stats to stdout, csv format (comma separated list of plugins/plugins.attribute)') parser.add_argument('--issue', default=None, action='store_true', dest='stdout_issue', help='test all plugins and exit (please copy/paste the output if you open an issue)') + parser.add_argument('--api-doc', default=None, action='store_true', + dest='stdout_apidoc', help='display fields descriptions') if not WINDOWS: parser.add_argument('--hide-kernel-threads', action='store_true', default=False, dest='no_kernel_threads', help='hide kernel threads in process list (not available on Windows)') diff --git a/glances/outputs/glances_stdout_apidoc.py b/glances/outputs/glances_stdout_apidoc.py new file mode 100644 index 00000000..eb1e4acc --- /dev/null +++ b/glances/outputs/glances_stdout_apidoc.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2021 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/>. + +"""Fields description interface class.""" + +from pprint import pformat +import json +import time + +from glances.logger import logger +from glances.compat import iteritems + +API_URL = "http://localhost:61208/api/3" + +APIDOC_HEADER = """\ +.. _api: + +API (Restfull/JSON) documentation +================================= + +The Glances Restfull/API server could be ran using the following command line: + +.. code-block:: bash + + # glances -w --disable-webui + +Note: Change request URL api/3 by api/2 if you use Glances 2.x. +""" + + +def indent_stat(stat, indent=' '): + # Indent stats to pretty print it + if isinstance(stat, list) and len(stat) > 1 and isinstance(stat[0], dict): + # Only display two first items + return indent + pformat(stat[0:2]).replace('\n', '\n' + indent) + else: + return indent + pformat(stat).replace('\n', '\n' + indent) + + +def print_plugins_list(stat): + sub_title = 'GET plugins list' + print(sub_title) + print('-' * len(sub_title)) + print('') + print('Get the plugins list::') + print('') + print(' # curl {}/pluginslist'.format(API_URL)) + print(indent_stat(stat)) + print('') + + +def print_plugin_export(plugin, stat_export): + sub_title = 'GET {}'.format(plugin) + print(sub_title) + print('-' * len(sub_title)) + print('') + + print('Get plugin stats::') + print('') + print(' # curl {}/{}'.format(API_URL, plugin)) + print(indent_stat(stat_export)) + print('') + + +def print_plugin_description(plugin, stat): + if stat.fields_description: + # For each plugins with a description + print('Fields descriptions:') + print('') + for field, description in iteritems(stat.fields_description): + print('* **{}**: {} (unit is *{}*)'.format(field, + description['description'][:-1] if description['description'].endswith('.') else description['description'], + description['unit'])) + print('') + else: + logger.error('No fields_description variable defined for plugin {}'.format(plugin)) + + +def print_plugin_item_value(plugin, stat, stat_export): + item = None + value = None + if isinstance(stat_export, dict): + item = list(stat_export.keys())[0] + value = None + elif isinstance(stat_export, list) and len(stat_export) > 0 and isinstance(stat_export[0], dict): + if 'key' in stat_export[0]: + item = stat_export[0]['key'] + else: + item = list(stat_export[0].keys())[0] + if item and stat.get_stats_item(item): + stat_item = json.loads(stat.get_stats_item(item)) + if isinstance(stat_item[item], list): + value = stat_item[item][0] + else: + value = stat_item[item] + print('Get a specific field::') + print('') + print(' # curl {}/{}/{}'.format(API_URL, plugin, item)) + print(indent_stat(stat_item)) + print('') + if item and value and stat.get_stats_value(item, value): + print('Get a specific item when field matchs the given value::') + print('') + print(' # curl {}/{}/{}/{}'.format(API_URL, plugin, item, value)) + print(indent_stat(json.loads(stat.get_stats_value(item, value)))) + print('') + + +def print_all(): + sub_title = 'GET all stats' + print(sub_title) + print('-' * len(sub_title)) + print('') + print('Get all Glances stats::') + print('') + print(' # curl {}/all'.format(API_URL)) + print(' Return a very big dictionnary (avoid using this request, performances will be poor)...') + print('') + + +def print_history(stats): + time.sleep(1) + stats.update() + time.sleep(1) + stats.update() + sub_title = 'GET stats history' + print(sub_title) + print('-' * len(sub_title)) + print('') + print('History of a plugin::') + print('') + print(' # curl {}/cpu/history'.format(API_URL)) + print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=3)))) + print('') + print('Limit history to last 2 values::') + print('') + print(' # curl {}/cpu/history/2'.format(API_URL)) + print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=2)))) + print('') + print('History for a specific field::') + print('') + print(' # curl {}/cpu/system/history'.format(API_URL)) + print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system')))) + print('') + print('Limit history for a specific field to last 2 values::') + print('') + print(' # curl {}/cpu/system/history'.format(API_URL)) + print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system', nb=2)))) + print('') + + +def print_limits(stats): + sub_title = 'GET limits (used for thresholds)' + print(sub_title) + print('-' * len(sub_title)) + print('') + print('All limits/thresholds::') + print('') + print(' # curl {}/all/limits'.format(API_URL)) + print(indent_stat(stats.getAllLimitsAsDict())) + print('') + print('Limits/thresholds for the cpu plugin::') + print('') + print(' # curl {}/cpu/limits'.format(API_URL)) + print(indent_stat(stats.get_plugin('cpu').limits)) + print('') + + +class GlancesStdoutApiDoc(object): + + """ + This class manages the fields description display. + """ + + def __init__(self, config=None, args=None): + # Init + self.config = config + self.args = args + + def end(self): + pass + + def update(self, + stats, + duration=1): + """Display issue + """ + + # Display header + print(APIDOC_HEADER) + + # Display plugins list + print_plugins_list(sorted(stats._plugins)) + + # Loop over plugins + for plugin in sorted(stats._plugins): + stat = stats.get_plugin(plugin) + stat_export = stat.get_export() + if stat_export is None or stat_export == [] or stat_export == {}: + continue + print_plugin_export(plugin, stat_export) + print_plugin_description(plugin, stat) + print_plugin_item_value(plugin, stat, stat_export) + + # Get all stats + print_all() + + # History + print_history(stats) + + # Limits + print_limits(stats) + + # Return True to exit directly (no refresh) + return True diff --git a/glances/plugins/glances_core.py b/glances/plugins/glances_core.py index f731593c..5a56ac75 100644 --- a/glances/plugins/glances_core.py +++ b/glances/plugins/glances_core.py @@ -23,6 +23,17 @@ from glances.plugins.glances_plugin import GlancesPlugin import psutil +# Fields description + + # - phys: physical cores only (hyper thread CPUs are excluded) + # - log: logical CPUs in the system +fields_description = { + 'phys': {'description': 'Number of physical cores (hyper thread CPUs are excluded).', + 'unit': 'number'}, + 'log': {'description': 'Number of logical CPUs. A logical CPU is the number of \ +physical cores multiplied by the number of threads that can run on each core.', + 'unit': 'number'}, +} class Plugin(GlancesPlugin): """Glances CPU core plugin. @@ -34,7 +45,9 @@ class Plugin(GlancesPlugin): def __init__(self, args=None, config=None): """Init the plugin.""" - super(Plugin, self).__init__(args=args, config=config) + super(Plugin, self).__init__(args=args, + config=config, + fields_description=fields_description) # We dot not want to display the stat in the curse interface # The core number is displayed by the load plugin diff --git a/glances/plugins/glances_cpu.py b/glances/plugins/glances_cpu.py index 1e582dc3..acf0d6d2 100644 --- a/glances/plugins/glances_cpu.py +++ b/glances/plugins/glances_cpu.py @@ -29,6 +29,50 @@ from glances.plugins.glances_plugin import GlancesPlugin import psutil +# Fields description +fields_description = { + 'total': {'description': 'Sum of all CPU percentages (except idle).', + 'unit': 'percent'}, + 'system': {'description': 'percent time spent in kernel space. System CPU time is the \ +time spent running code in the Operating System kernel.', + 'unit': 'percent'}, + 'user': {'description': 'CPU percent time spent in user space. \ +User CPU time is the time spent on the processor running your program\'s code (or code in libraries).', + 'unit': 'percent'}, + 'iowait': {'description': '*(Linux)*: percent time spent by the CPU waiting for I/O \ +operations to complete.', + 'unit': 'percent'}, + 'idle': {'description': 'percent of CPU used by any program. Every program or task \ +that runs on a computer system occupies a certain amount of processing \ +time on the CPU. If the CPU has completed all tasks it is idle.', + 'unit': 'percent'}, + 'irq': {'description': '*(Linux and BSD)*: percent time spent servicing/handling \ +hardware/software interrupts. Time servicing interrupts (hardware + \ +software).', + 'unit': 'percent'}, + 'nice': {'description': '*(Unix)*: percent time occupied by user level processes with \ +a positive nice value. The time the CPU has spent running users\' \ +processes that have been *niced*.', + 'unit': 'percent'}, + 'steal': {'description': '*(Linux)*: percentage of time a virtual CPU waits for a real \ +CPU while the hypervisor is servicing another virtual processor.', + 'unit': 'percent'}, + 'ctx_switches': {'description': 'number of context switches (voluntary + involuntary) per \ +second. A context switch is a procedure that a computer\'s CPU (central \ +processing unit) follows to change from one task (or process) to \ +another while ensuring that the tasks do not conflict.', + 'unit': 'percent'}, + 'interrupts': {'description': 'number of interrupts per second.', + 'unit': 'percent'}, + 'soft_interrupts': {'description': 'number of software interrupts per second. Always set to \ +0 on Windows and SunOS.', + 'unit': 'percent'}, + 'cpucore': {'description': 'Total number of CPU core.', + 'unit': 'number'}, + 'time_since_update': {'description': 'Number of seconds since last update.', + 'unit': 'seconds'}, +} + # SNMP OID # percentage of user CPU time: .1.3.6.1.4.1.2021.11.9.0 # percentages of system CPU time: .1.3.6.1.4.1.2021.11.10.0 @@ -40,7 +84,7 @@ snmp_oid = {'default': {'user': '1.3.6.1.4.1.2021.11.9.0', 'esxi': {'percent': '1.3.6.1.2.1.25.3.3.1.2'}, 'netapp': {'system': '1.3.6.1.4.1.789.1.2.1.3.0', 'idle': '1.3.6.1.4.1.789.1.2.1.5.0', - 'nb_log_core': '1.3.6.1.4.1.789.1.2.1.6.0'}} + 'cpucore': '1.3.6.1.4.1.789.1.2.1.6.0'}} # Define the history items list # - 'name' define the stat identifier @@ -64,7 +108,8 @@ class Plugin(GlancesPlugin): """Init the CPU plugin.""" super(Plugin, self).__init__(args=args, config=config, - items_history_list=items_history_list) + items_history_list=items_history_list, + fields_description=fields_description) # We want to display the stat in the curse interface self.display_curse = True diff --git a/glances/plugins/glances_load.py b/glances/plugins/glances_load.py index ab96ff0d..29a50cdf 100644 --- a/glances/plugins/glances_load.py +++ b/glances/plugins/glances_load.py @@ -27,6 +27,24 @@ from glances.plugins.glances_core import Plugin as CorePlugin from glances.plugins.glances_plugin import GlancesPlugin from glances.logger import logger +# Fields description +fields_description = { + 'min1': {'description': 'Average sum of the number of processes \ +waiting in the run-queue plus the number currently executing \ +over 1 minute.', + 'unit': 'number'}, + 'min5': {'description': 'Average sum of the number of processes \ +waiting in the run-queue plus the number currently executing \ +over 5 minutes.', + 'unit': 'number'}, + 'min15': {'description': 'Average sum of the number of processes \ +waiting in the run-queue plus the number currently executing \ +over 15 minutes.', + 'unit': 'number'}, + 'cpucore': {'description': 'Total number of CPU core.', + 'unit': 'number'}, +} + # SNMP OID # 1 minute Load: .1.3.6.1.4.1.2021.10.1.3.1 # 5 minute Load: .1.3.6.1.4.1.2021.10.1.3.2 @@ -55,7 +73,8 @@ class Plugin(GlancesPlugin): """Init the plugin.""" super(Plugin, self).__init__(args=args, config=config, - items_history_list=items_history_list) + items_history_list=items_history_list, + fields_description=fields_description) # We want to display the stat in the curse interface self.display_curse = True diff --git a/glances/plugins/glances_mem.py b/glances/plugins/glances_mem.py index 417483a3..79c13c9c 100644 --- a/glances/plugins/glances_mem.py +++ b/glances/plugins/glances_mem.py @@ -25,6 +25,37 @@ from glances.plugins.glances_plugin import GlancesPlugin import psutil +# Fields description +fields_description = { + 'total': {'description': 'Total physical memory available.', + 'unit': 'bytes'}, + 'available': {'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) \ +and it is supposed to be used to monitor actual memory usage in a cross platform fashion.', + 'unit': 'bytes'}, + 'percent': {'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'}, + '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'}, + 'active': {'description': '*(UNIX)*: memory currently in use or very recently used, and so it is in RAM.', + 'unit': 'bytes'}, + 'inactive': {'description': '*(UNIX)*: memory that is marked as not used.', + 'unit': 'bytes'}, + 'buffers': {'description': '*(Linux, BSD)*: cache for things like file system metadata.', + 'unit': 'bytes'}, + 'cached': {'description': '*(Linux, BSD)*: cache for various things.', + 'unit': 'bytes'}, + 'wired': {'description': '*(BSD, macOS)*: memory that is marked to always stay in RAM. It is never moved to disk.', + 'unit': 'bytes'}, + 'shared': {'description': '*(BSD)*: memory that may be simultaneously accessed by multiple processes.', + 'unit': 'bytes'}, +} + # SNMP OID # Total RAM in machine: .1.3.6.1.4.1.2021.4.5.0 # Total RAM used: .1.3.6.1.4.1.2021.4.6.0 @@ -64,7 +95,8 @@ class Plugin(GlancesPlugin): """Init the plugin.""" super(Plugin, self).__init__(args=args, config=config, - items_history_list=items_history_list) + items_history_list=items_history_list, + fields_description=fields_description) # We want to display the stat in the curse interface self.display_curse = True diff --git a/glances/plugins/glances_memswap.py b/glances/plugins/glances_memswap.py index 06924def..d305d0c3 100644 --- a/glances/plugins/glances_memswap.py +++ b/glances/plugins/glances_memswap.py @@ -20,10 +20,29 @@ """Swap memory plugin.""" from glances.compat import iterkeys +from glances.timer import getTimeSinceLastUpdate from glances.plugins.glances_plugin import GlancesPlugin import psutil +# Fields description +fields_description = { + 'total': {'description': 'Total swap memory.', + 'unit': 'bytes'}, + 'used': {'description': 'Used swap memory.', + 'unit': 'bytes'}, + 'free': {'description': 'Free swap memory.', + 'unit': 'bytes'}, + 'percent': {'description': 'Used swap memory in percentage.', + 'unit': 'percent'}, + 'sin': {'description': 'The number of bytes the system has swapped in from disk (cumulative).', + 'unit': 'bytes'}, + 'sout': {'description': 'The number of bytes the system has swapped out from disk (cumulative).', + 'unit': 'bytes'}, + 'time_since_update': {'description': 'Number of seconds since last update.', + 'unit': 'seconds'}, +} + # SNMP OID # Total Swap Size: .1.3.6.1.4.1.2021.4.3.0 # Available Swap Space: .1.3.6.1.4.1.2021.4.4.0 @@ -51,7 +70,8 @@ class Plugin(GlancesPlugin): """Init the plugin.""" super(Plugin, self).__init__(args=args, config=config, - items_history_list=items_history_list) + items_history_list=items_history_list, + fields_description=fields_description) # We want to display the stat in the curse interface self.display_curse = True @@ -78,12 +98,16 @@ class Plugin(GlancesPlugin): # free: free swap memory in bytes # percent: the percentage usage # sin: the number of bytes the system has swapped in from disk (cumulative) - # sout: the number of bytes the system has swapped out from disk - # (cumulative) + # sout: the number of bytes the system has swapped out from disk (cumulative) for swap in ['total', 'used', 'free', 'percent', 'sin', 'sout']: if hasattr(sm_stats, swap): stats[swap] = getattr(sm_stats, swap) + + # By storing time data we enable sin/s and sout/s calculations in the + # XML/RPC API, which would otherwise be overly difficult work + # for users of the API + stats['time_since_update'] = getTimeSinceLastUpdate('memswap') elif self.input_method == 'snmp': # Update stats using SNMP if self.short_system_name == 'windows': diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index cde30510..46d19261 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -29,6 +29,36 @@ from glances.logger import logger import psutil +# {'interface_name': 'mpqemubr0-dummy', +# 'alias': None, +# 'time_since_update': 2.081636428833008, +# 'cumulative_rx': 0, +# 'rx': 0, 'cumulative_tx': 0, 'tx': 0, 'cumulative_cx': 0, 'cx': 0, +# 'is_up': False, +# 'speed': 0, +# 'key': 'interface_name'} +# Fields description +fields_description = { + 'interface_name': {'description': 'Interface name.', + 'unit': 'string'}, + 'alias': {'description': 'Interface alias name (optional).', + 'unit': 'string'}, + 'rx': {'description': 'The received/input rate (in bit per second).', + 'unit': 'bps'}, + 'tx': {'description': 'The sent/output rate (in bit per second).', + 'unit': 'bps'}, + 'cumulative_rx': {'description': 'The number of bytes received through the interface (cumulative).', + 'unit': 'bytes'}, + 'cumulative_tx': {'description': 'The number of bytes sent through the interface (cumulative).', + 'unit': 'bytes'}, + 'speed': {'description': 'Maximum interface speed (in bit per second). Can return 0 on some operating-system.', + 'unit': 'bps'}, + 'is_up': {'description': 'Is the interface up ?', + 'unit': 'bool'}, + 'time_since_update': {'description': 'Number of seconds since last update.', + 'unit': 'seconds'}, +} + # SNMP OID # http://www.net-snmp.org/docs/mibs/interfaces.html # Dict key = interface_name @@ -56,6 +86,7 @@ class Plugin(GlancesPlugin): super(Plugin, self).__init__(args=args, config=config, items_history_list=items_history_list, + fields_description=fields_description, stats_init_value=[]) # We want to display the stat in the curse interface diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 73cc7fd9..6a617c8c 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -44,7 +44,8 @@ class GlancesPlugin(object): args=None, config=None, items_history_list=None, - stats_init_value={}): + stats_init_value={}, + fields_description=None): """Init the plugin of plugins class. All Glances' plugins should inherit from this class. Most of the @@ -107,6 +108,9 @@ class GlancesPlugin(object): # Set the initial refresh time to display stats the first time self.refresh_timer = Timer(0) + # Init stats description + self.fields_description = fields_description + # Init the stats self.stats_init_value = stats_init_value self.stats = None @@ -430,7 +434,7 @@ class GlancesPlugin(object): if not isinstance(self.stats, list): return None else: - if value.isdigit(): + if not isinstance(value, int) and value.isdigit(): value = int(value) try: return self._json_dumps({value: [i for i in self.stats if i[item] == value]}) diff --git a/glances/processes.py b/glances/processes.py index 84650588..8fcda569 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -363,6 +363,9 @@ class GlancesProcesses(object): first = False # /End of extended stats + # PID is the key + proc['key'] = 'pid' + # Time since last update (for disk_io rate computation) proc['time_since_update'] = time_since_update diff --git a/glances/standalone.py b/glances/standalone.py index d6a07f2f..d042873f 100644 --- a/glances/standalone.py +++ b/glances/standalone.py @@ -30,6 +30,7 @@ from glances.outputs.glances_curses import GlancesCursesStandalone from glances.outputs.glances_stdout import GlancesStdout from glances.outputs.glances_stdout_csv import GlancesStdoutCsv from glances.outputs.glances_stdout_issue import GlancesStdoutIssue +from glances.outputs.glances_stdout_apidoc import GlancesStdoutApiDoc from glances.outdated import Outdated from glances.timer import Counter @@ -87,6 +88,10 @@ class GlancesStandalone(object): logger.info("Issue mode is ON") # Init screen self.screen = GlancesStdoutIssue(config=config, args=args) + elif args.stdout_apidoc: + logger.info("Fields descriptions mode is ON") + # Init screen + self.screen = GlancesStdoutApiDoc(config=config, args=args) elif args.stdout: logger.info("Stdout mode is ON, following stats will be displayed: {}".format(args.stdout)) # Init screen |