mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-10 20:38:23 +03:00
Plugins are classes
This commit is contained in:
parent
a47cfafdaf
commit
83170d7da5
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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...
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -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):
|
@ -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()))
|
||||
|
Loading…
Reference in New Issue
Block a user