Plugins are classes

This commit is contained in:
nicolargo 2021-08-24 10:04:02 +02:00
parent a47cfafdaf
commit 83170d7da5
37 changed files with 144 additions and 186 deletions

View File

@ -3,6 +3,9 @@
Wi-Fi
=====
* WARNING: The Wifi Python lib (https://pypi.python.org/pypi/wifi) is note
compatible with Python 3. So the plugin is disable in Glances 4.0 or higher*
*Availability: Linux*
.. image:: ../_static/wifi.png

View File

@ -19,7 +19,9 @@
# flake8: noqa
# pylint: skip-file
"""Python 2/3 compatibility shims."""
# TODO: merge this file with the globals.py
# Not needed anymore because only Python 3 is supported.
from __future__ import print_function, unicode_literals
@ -32,191 +34,96 @@ import os
from glances.logger import logger
PY3 = sys.version_info[0] == 3
import queue
from configparser import ConfigParser, NoOptionError, NoSectionError
from statistics import mean
from xmlrpc.client import Fault, ProtocolError, ServerProxy, Transport, Server
from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from urllib.request import urlopen
from urllib.error import HTTPError, URLError
from urllib.parse import urlparse
if PY3:
import queue
from configparser import ConfigParser, NoOptionError, NoSectionError
from statistics import mean
from xmlrpc.client import Fault, ProtocolError, ServerProxy, Transport, Server
from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from urllib.request import urlopen
from urllib.error import HTTPError, URLError
from urllib.parse import urlparse
# Correct issue #1025 by monkey path the xmlrpc lib
from defusedxml.xmlrpc import monkey_patch
monkey_patch()
# Correct issue #1025 by monkey path the xmlrpc lib
from defusedxml.xmlrpc import monkey_patch
monkey_patch()
input = input
range = range
map = map
input = input
range = range
map = map
text_type = str
binary_type = bytes
bool_type = bool
long = int
text_type = str
binary_type = bytes
bool_type = bool
long = int
PermissionError = OSError
PermissionError = OSError
viewkeys = operator.methodcaller('keys')
viewvalues = operator.methodcaller('values')
viewitems = operator.methodcaller('items')
viewkeys = operator.methodcaller('keys')
viewvalues = operator.methodcaller('values')
viewitems = operator.methodcaller('items')
def printandflush(string):
"""Print and flush (used by stdout* outputs modules)"""
print(string, flush=True)
def printandflush(string):
"""Print and flush (used by stdout* outputs modules)"""
print(string, flush=True)
def to_ascii(s):
"""Convert the bytes string to a ASCII string
Usefull to remove accent (diacritics)"""
if isinstance(s, binary_type):
return s.decode()
return s.encode('ascii', 'ignore').decode()
def to_ascii(s):
"""Convert the bytes string to a ASCII string
Usefull to remove accent (diacritics)"""
if isinstance(s, binary_type):
return s.decode()
return s.encode('ascii', 'ignore').decode()
def listitems(d):
return list(d.items())
def listitems(d):
return list(d.items())
def listkeys(d):
return list(d.keys())
def listkeys(d):
return list(d.keys())
def listvalues(d):
return list(d.values())
def listvalues(d):
return list(d.values())
def iteritems(d):
return iter(d.items())
def iteritems(d):
return iter(d.items())
def iterkeys(d):
return iter(d.keys())
def iterkeys(d):
return iter(d.keys())
def itervalues(d):
return iter(d.values())
def itervalues(d):
return iter(d.values())
def u(s, errors='replace'):
if isinstance(s, text_type):
return s
return s.decode('utf-8', errors=errors)
def b(s, errors='replace'):
if isinstance(s, binary_type):
return s
return s.encode('utf-8', errors=errors)
def n(s):
'''Only in Python 2...
from future.utils import bytes_to_native_str as n
'''
def u(s, errors='replace'):
if isinstance(s, text_type):
return s
return s.decode('utf-8', errors=errors)
def nativestr(s, errors='replace'):
if isinstance(s, text_type):
return s
elif isinstance(s, (int, float)):
return s.__str__()
else:
return s.decode('utf-8', errors=errors)
def b(s, errors='replace'):
if isinstance(s, binary_type):
return s
return s.encode('utf-8', errors=errors)
def system_exec(command):
"""Execute a system command and return the result as a str"""
try:
res = subprocess.run(command.split(' '),
stdout=subprocess.PIPE).stdout.decode('utf-8')
except Exception as e:
logger.debug('Can not evaluate command {} ({})'.format(command, e))
res = ''
return res.rstrip()
else:
def n(s):
'''Only in Python 2...
from future.utils import bytes_to_native_str as n
import Queue as queue
from itertools import imap as map
from ConfigParser import SafeConfigParser as ConfigParser, NoOptionError, NoSectionError
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from xmlrpclib import Fault, ProtocolError, ServerProxy, Transport, Server
from urllib2 import urlopen, HTTPError, URLError
from urlparse import urlparse
'''
return s
# Correct issue #1025 by monkey path the xmlrpc lib
from defusedxml.xmlrpc import monkey_patch
monkey_patch()
input = raw_input
range = xrange
ConfigParser.read_file = ConfigParser.readfp
text_type = unicode
binary_type = str
bool_type = types.BooleanType
long = long
PermissionError = OSError
viewkeys = operator.methodcaller('viewkeys')
viewvalues = operator.methodcaller('viewvalues')
viewitems = operator.methodcaller('viewitems')
def printandflush(string):
"""Print and flush (used by stdout* outputs modules)"""
print(string)
sys.stdout.flush()
def mean(numbers):
return float(sum(numbers)) / max(len(numbers), 1)
def to_ascii(s):
"""Convert the unicode 's' to a ASCII string
Usefull to remove accent (diacritics)"""
if isinstance(s, binary_type):
return s
return unicodedata.normalize('NFKD', s).encode('ascii', 'ignore')
def listitems(d):
return d.items()
def listkeys(d):
return d.keys()
def listvalues(d):
return d.values()
def iteritems(d):
return d.iteritems()
def iterkeys(d):
return d.iterkeys()
def itervalues(d):
return d.itervalues()
def u(s, errors='replace'):
if isinstance(s, text_type):
return s.encode('utf-8', errors=errors)
def nativestr(s, errors='replace'):
if isinstance(s, text_type):
return s
elif isinstance(s, (int, float)):
return s.__str__()
else:
return s.decode('utf-8', errors=errors)
def b(s, errors='replace'):
if isinstance(s, binary_type):
return s
return s.encode('utf-8', errors=errors)
def nativestr(s, errors='replace'):
if isinstance(s, binary_type):
return s
elif isinstance(s, (int, float)):
return s.__str__()
else:
return s.encode('utf-8', errors=errors)
def system_exec(command):
"""Execute a system command and return the resul as a str"""
try:
res = subprocess.check_output(command.split(' '))
except Exception as e:
logger.debug('Can not execute command {} ({})'.format(command, e))
res = ''
return res.rstrip()
# Globals functions for both Python 2 and 3
def system_exec(command):
"""Execute a system command and return the result as a str"""
try:
res = subprocess.run(command.split(' '),
stdout=subprocess.PIPE).stdout.decode('utf-8')
except Exception as e:
logger.debug('Can not evaluate command {} ({})'.format(command, e))
res = ''
return res.rstrip()
def subsample(data, sampling):

View File

@ -80,7 +80,7 @@ class CpuPercent(object):
def __get_cpu_name(self):
# Get the CPU name once from the /proc/cpuinfo file
# @TODO: Multisystem...
# TODO: Multisystem...
try:
self.cpu_info['cpu_name'] = open('/proc/cpuinfo', 'r').readlines()[4].split(':')[1].strip()
except:

View File

@ -34,8 +34,8 @@ class GlancesExport(object):
"""Main class for Glances export IF."""
# For the moment, only thoses plugins can be exported
# @TODO: remove this part and make all plugins exportable (see issue #1556)
# @TODO: also make this list configurable by the user (see issue #1443)
# TODO: remove this part and make all plugins exportable (see issue #1556)
# TODO: also make this list configurable by the user (see issue #1443)
exportable_plugins = ['cpu',
'percpu',
'load',

View File

@ -336,7 +336,7 @@ class _GlancesCurses(object):
pass
def get_key(self, window):
# @TODO: Check issue #163
# TODO: Check issue #163
ret = window.getch()
return ret

View File

@ -64,7 +64,7 @@ tree = [{'msg': 'No warning or critical alert detected',
'thresholds_min': 2},
]
# @TODO: change the algo to use the following decision tree
# 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

View File

@ -24,7 +24,7 @@ from glances.timer import getTimeSinceLastUpdate
from glances.compat import iterkeys
from glances.cpu_percent import cpu_percent
from glances.globals import LINUX
from glances.plugins.glances_core import Plugin as CorePlugin
from glances.plugins.core import Plugin as CorePlugin
from glances.plugins.glances_plugin import GlancesPlugin
import psutil
@ -174,7 +174,7 @@ class Plugin(GlancesPlugin):
if not hasattr(self, 'cpu_stats_old'):
# Init the stats (needed to have the key name for export)
for stat in cpu_stats._fields:
# @TODO: better to set it to None but should refactor views and UI...
# TODO: better to set it to None but should refactor views and UI...
stats[stat] = 0
else:
# Others calls...

View File

@ -69,9 +69,14 @@ class GlancesPlugin(object):
:stats_init_value: Default value for a stats item
"""
# Plugin name (= module name without glances_)
pos = self.__class__.__module__.find('glances_') + len('glances') + 1
self.plugin_name = self.__class__.__module__[pos:]
# logger.debug("Init plugin %s" % self.plugin_name)
# pos = self.__class__.__module__.find('glances_') + len('glances') + 1
# self.plugin_name = self.__class__.__module__[pos:]
# TODO: For Glances 4 => 3 next line to be removed when all plugins are migrated
if self.__class__.__module__.startswith('glances_'):
self.plugin_name = self.__class__.__module__.split('glances_')[1]
else:
self.plugin_name = self.__class__.__module__
logger.debug("Init {} plugin".format(self.plugin_name))
# Init the args
self.args = args
@ -573,7 +578,7 @@ class GlancesPlugin(object):
return False
# Read the global section
# @TODO: not optimized because this section is loaded for each plugin...
# TODO: not optimized because this section is loaded for each plugin...
if config.has_section('global'):
self._limits['history_size'] = config.get_float_value('global', 'history_size', default=28800)
logger.debug("Load configuration key: {} = {}".format('history_size', self._limits['history_size']))
@ -865,7 +870,7 @@ class GlancesPlugin(object):
Example for diskio:
show=sda.*
"""
# @TODO: possible optimisation: create a re.compile list
# TODO: possible optimisation: create a re.compile list
if self.get_conf_value('show', header=header) == []:
return True
else:
@ -879,7 +884,7 @@ class GlancesPlugin(object):
Example for diskio:
hide=sda2,sda5,loop.*
"""
# @TODO: possible optimisation: create a re.compile list
# TODO: possible optimisation: create a re.compile list
return any(j for j in [re.match(i, value) for i in self.get_conf_value('hide', header=header)])
def has_alias(self, header):

View File

@ -23,7 +23,7 @@ import os
import psutil
from glances.compat import iteritems
from glances.plugins.glances_core import Plugin as CorePlugin
from glances.plugins.core import Plugin as CorePlugin
from glances.plugins.glances_plugin import GlancesPlugin
from glances.logger import logger

View File

@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
# Copyright (C) 2019 Nicolargo <nicolas@nicolargo.com>
# 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

View File

@ -26,7 +26,7 @@ from glances.logger import logger
from glances.globals import WINDOWS
from glances.compat import key_exist_value_not_none_not_v
from glances.processes import glances_processes, sort_stats
from glances.plugins.glances_core import Plugin as CorePlugin
from glances.plugins.core import Plugin as CorePlugin
from glances.plugins.glances_plugin import GlancesPlugin

View File

@ -21,7 +21,7 @@
import operator
from glances.compat import nativestr, PY3
from glances.compat import nativestr
from glances.logger import logger
from glances.plugins.glances_plugin import GlancesPlugin
@ -38,9 +38,8 @@ else:
import_error_tag = False
# Python 3 is not supported (see issue #1377)
if PY3:
import_error_tag = True
logger.warning("Wifi lib is not compliant with Python 3, Wifi plugin is disabled")
import_error_tag = True
logger.warning("Wifi lib is not compliant with Python 3, Wifi plugin is disabled")
class Plugin(GlancesPlugin):

View File

@ -24,6 +24,7 @@ import os
import sys
import threading
import traceback
from importlib import import_module
from glances.logger import logger
from glances.globals import exports_path, plugins_path, sys_path
@ -102,6 +103,7 @@ class GlancesStats(object):
# Restoring system path
sys.path = sys_path
# TODO: To be removed and replace by the _load_plugin_v4
def _load_plugin(self, plugin_script, args=None, config=None):
"""Load the plugin (script), init it and add to the _plugin dict."""
# The key is the plugin name
@ -112,7 +114,38 @@ class GlancesStats(object):
# Loaf the plugin class
try:
# Import the plugin
plugin = __import__(plugin_script[:-3])
plugin = import_module(plugin_script[:-3])
# Init and add the plugin to the dictionary
self._plugins[name] = plugin.Plugin(args=args, config=config)
except Exception as e:
# If a plugin can not be loaded, display a critical message
# on the console but do not crash
logger.critical("Error while initializing the {} plugin ({})".format(name, e))
logger.error(traceback.format_exc())
# Disable the plugin
if args is not None:
setattr(args,
'disable_' + name,
False)
else:
# Set the disable_<name> to False by default
if args is not None:
setattr(args,
'disable_' + name,
getattr(args, 'disable_' + name, False))
# TODO: To be rename to _load_plugin
def _load_plugin_v4(self, plugin_path, args=None, config=None):
"""Load the plugin, init it and add to the _plugin dict."""
# The key is the plugin name = plugin_path
# for example, the path ./glances/plugins/xxx
# generate self._plugins_list["xxx"] = ...
name = plugin_path.lower()
# Load the plugin class
try:
# Import the plugin
plugin = import_module(name)
# Init and add the plugin to the dictionary
self._plugins[name] = plugin.Plugin(args=args, config=config)
except Exception as e:
@ -145,6 +178,17 @@ class GlancesStats(object):
args=args, config=self.config)
logger.debug("Plugin {} started in {} seconds".format(item,
start_duration.get()))
for item in os.listdir(plugins_path):
if os.path.isdir(os.path.join(plugins_path, item)) and \
not item.startswith('__') and \
not item.startswith('sensors') and \
item != "plugin":
# Load the plugin
start_duration.reset()
self._load_plugin_v4(os.path.basename(item),
args=args, config=self.config)
logger.debug("Plugin {} started in {} seconds".format(item,
start_duration.get()))
# Log plugins list
logger.debug("Active plugins list: {}".format(self.getPluginsList()))