From 8da175d5fa27fbb17d29ac1865aa352907760e23 Mon Sep 17 00:00:00 2001 From: Nicolas Hennion Date: Sat, 18 Jan 2014 23:01:57 +0100 Subject: [PATCH] Init plugins structure --- glances/README.txt | 14 +- glances/__init__.py | 40 +++- glances/core/glances_config.py | 143 +++++++++++++ glances/core/glances_core.py | 128 ++++++++---- glances/core/glances_globals.py | 51 +++++ glances/core/glances_limits.py | 270 +++++++++++++++++++++++++ glances/core/glances_monitor_list.py | 135 +++++++++++++ glances/core/glances_server.py | 288 +++++++++++++++++++++++++++ glances/core/glances_timer.py | 44 ++++ glances/plugins/glances_cpu.py | 5 +- glances/plugins/glances_host.py | 43 ++++ 11 files changed, 1114 insertions(+), 47 deletions(-) create mode 100644 glances/core/glances_config.py create mode 100644 glances/core/glances_globals.py create mode 100644 glances/core/glances_limits.py create mode 100644 glances/core/glances_monitor_list.py create mode 100644 glances/core/glances_server.py create mode 100644 glances/core/glances_timer.py create mode 100644 glances/plugins/glances_host.py diff --git a/glances/README.txt b/glances/README.txt index c94e9ad5..c7e36ad3 100644 --- a/glances/README.txt +++ b/glances/README.txt @@ -9,17 +9,19 @@ __init__.py Global module init __main__.py Entry point for module core/ glances_core.py Main script to rule them up... + glances_globals.py Share variables uppon modules + glances_config.py Manage configuration file + glances_timer.py Manage timer + glances_stats.py Inteface to grab stats glances_client.py Glances client - glances_config.py Script to manage configuration file glances_server.py Glances_server plugins/ - glances_plugins.py Main class for others plugins - glances_cpu.py Grab CPU stats - glances_load.py Grab LOAD stats - glances_mem.py Grab MEM (both RAM and SWAP) stats + glances_plugins.py "Father class" for others plugins + glances_cpu.py Manage CPU stats + glances_load.py Manage LOAD stats + glances_mem.py Manage MEM (both RAM and SWAP) stats ... outputs/ - glances_api.py The API interface glances_curse.py The Curse (console) interface glances_csv.py The CSV interface glances_html.py The HTML interface diff --git a/glances/__init__.py b/glances/__init__.py index 70d2d3e2..b8fc3c9e 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -21,5 +21,41 @@ from .core.glances_core import GlancesCore def main(argv=None): - glances_instance = GlancesCore() - glances_instance.start() + # Create the Glances core instance + core = GlancesCore() + + # Glances can be ran in standalone, client or server mode + if (core.is_standalone()): + # !!! + print "Standalone mode" + elif (core.is_client()): + # !!! + print "Client mode" + elif (core.is_server()): + # Import the Glances server module + from .core.glances_server import GlancesServer + + # Init the server + server = GlancesServer(bind_address=core.bind_ip, + bind_port=int(core.server_port), + cached_time=core.cached_time) + print(_("Glances server is running on") + " %s:%s" % (core.bind_ip, core.server_port)) + + # Set the server login/password (if -P/--password tag) + if (core.password != ""): + server.add_user(core.username, core.password) + + # Init stats + # !!! Uncomment + # stats = GlancesStatsServer() + # stats.update({}) + + # Shutdown the server + # !!! How to close the server with CTRL-C + # !!! Call core.end() with parameters ? + server.server_close() + + + + + diff --git a/glances/core/glances_config.py b/glances/core/glances_config.py new file mode 100644 index 00000000..e7f90385 --- /dev/null +++ b/glances/core/glances_config.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Glances - An eye on your system +# +# Copyright (C) 2014 Nicolargo +# +# 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 . + +__appname__ = 'glances' + +from ..core.glances_globals import * + +# Import system libs +import os +try: + # Python 2 + from ConfigParser import RawConfigParser + from ConfigParser import NoOptionError +except ImportError: + # Python 3 + from configparser import RawConfigParser + from configparser import NoOptionError + + +class Config: + """ + This class is used to access/read config file, if it exists + + :param location: the custom path to search for config file + :type location: str or None + """ + + def __init__(self, location=None): + self.location = location + self.filename = 'glances.conf' + + self.parser = RawConfigParser() + self.load() + + def load(self): + """ + Load a config file from the list of paths, if it exists + """ + for path in self.get_paths_list(): + if (os.path.isfile(path) and os.path.getsize(path) > 0): + try: + if (sys.version_info >= (3, 2)): + self.parser.read(path, encoding='utf-8') + else: + self.parser.read(path) + except UnicodeDecodeError as e: + print(_("Error decoding config file '%s': %s") % (path, e)) + sys.exit(1) + break + + def get_paths_list(self): + """ + Get a list of config file paths, taking into account of the OS, + priority and location. + + * running from source: /path/to/glances/conf + * Linux: ~/.config/glances, /etc/glances + * BSD: ~/.config/glances, /usr/local/etc/glances + * Mac: ~/Library/Application Support/glances, /usr/local/etc/glances + * Windows: %APPDATA%\glances + + The config file will be searched in the following order of priority: + * /path/to/file (via -C flag) + * /path/to/glances/conf + * user's home directory (per-user settings) + * {/usr/local,}/etc directory (system-wide settings) + """ + paths = [] + conf_path = os.path.realpath(os.path.join(work_path, '..', 'conf')) + + if self.location is not None: + paths.append(self.location) + + if os.path.exists(conf_path): + paths.append(os.path.join(conf_path, self.filename)) + + if is_Linux or is_BSD: + paths.append(os.path.join( + os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), + __appname__, self.filename)) + elif is_Mac: + paths.append(os.path.join( + os.path.expanduser('~/Library/Application Support/'), + __appname__, self.filename)) + elif is_Windows: + paths.append(os.path.join( + os.environ.get('APPDATA'), __appname__, self.filename)) + + if is_Linux: + paths.append(os.path.join('/etc', __appname__, self.filename)) + elif is_BSD: + paths.append(os.path.join( + sys.prefix, 'etc', __appname__, self.filename)) + elif is_Mac: + paths.append(os.path.join( + sys_prefix, 'etc', __appname__, self.filename)) + + return paths + + def has_section(self, section): + """ + Return info about the existence of a section + """ + return self.parser.has_section(section) + + def get_option(self, section, option): + """ + Get the float value of an option, if it exists + """ + try: + value = self.parser.getfloat(section, option) + except NoOptionError: + return + else: + return value + + def get_raw_option(self, section, option): + """ + Get the raw value of an option, if it exists + """ + try: + value = self.parser.get(section, option) + except NoOptionError: + return + else: + return value diff --git a/glances/core/glances_core.py b/glances/core/glances_core.py index d8e80750..ca7bd102 100644 --- a/glances/core/glances_core.py +++ b/glances/core/glances_core.py @@ -18,42 +18,26 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . +# Glances informations __appname__ = 'glances' __version__ = "2.0_Alpha" __author__ = "Nicolas Hennion " __license__ = "LGPL" +# Import system libs import sys import os -import gettext -import locale +import signal import argparse -# path definitions -work_path = os.path.realpath(os.path.dirname(__file__)) -appname_path = os.path.split(sys.argv[0])[0] -sys_prefix = os.path.realpath(os.path.dirname(appname_path)) - -# i18n -locale.setlocale(locale.LC_ALL, '') -gettext_domain = __appname__ -# get locale directory -i18n_path = os.path.realpath(os.path.join(work_path, '..', 'i18n')) -sys_i18n_path = os.path.join(sys_prefix, 'share', 'locale') -if os.path.exists(i18n_path): - locale_dir = i18n_path -elif os.path.exists(sys_i18n_path): - locale_dir = sys_i18n_path -else: - locale_dir = None -gettext.install(gettext_domain, locale_dir) - -# Operating system flag -# Note: Somes libs depends of OS -is_BSD = sys.platform.find('bsd') != -1 -is_Linux = sys.platform.startswith('linux') -is_Mac = sys.platform.startswith('darwin') -is_Windows = sys.platform.startswith('win') +# Import Glances libs +# !!! Todo: rename class +# GlancesExemple +from ..core.glances_globals import * +from ..core.glances_config import Config +from ..core.glances_limits import glancesLimits +from ..core.glances_monitor_list import monitorList +from ..core.glances_stats import GlancesStats, GlancesStatsServer # Import PSUtil # !!! Is it mandatory for client ? @@ -125,6 +109,7 @@ class GlancesCore(object): # Default stats' refresh time is 3 seconds refresh_time = 3 # Set the default cache lifetime to 1 second (only for server) + # !!! Todo: configuration from the command line cached_time = 1 # Default network bitrate is display in bit per second network_bytepersec_tag = False @@ -160,16 +145,74 @@ class GlancesCore(object): def __init__(self): - self.parser = argparse.ArgumentParser( - prog=__appname__, - description='Glances, an eye on your system.') + # Init and manage command line arguments self.init_arg() + self.parse_arg() + + # Read the configuration file + if (self.conf_file_tag): + self.config = Config(self.conf_file) + else: + self.config = Config() + + # Init the limits + self.limits = glancesLimits(self.config) + + # Init the monitoring list + self.monitors = monitorList(self.config) + + # Init stats + stats = GlancesStatsServer() + + # Catch the CTRL-C signal + signal.signal(signal.SIGINT, self.__signal_handler) + + + def __signal_handler(self, signal, frame): + self.end() + + + def end(self): + """ + Stop the Glances core and exit + """ + + if (self.is_standalone()): + # Stop the classical CLI loop + # !!! Uncomment + # screen.end() + pass + elif (self.is_client()): + # Stop the client loop + #~ client.client_quit() + pass + elif (self.is_server()): + # Stop the server loop + # !!! Uncomment + # server.server_close() + pass + + # !!! Uncomment + # if (self.csv_tag): + # csvoutput.exit() + + # !! Todo for htmloutput too + # The exit should generate a new HTML page + # to inform the user that data are not refreshed anymore + + # The end... + sys.exit(0) + def init_arg(self): """ Init all the command line arguments """ + self.parser = argparse.ArgumentParser( + prog=__appname__, + description='Glances, an eye on your system.') + # Version self.parser.add_argument('-v', '--version', action='version', @@ -250,12 +293,11 @@ class GlancesCore(object): choices=self.output_list) # Define output type flag to False (default is no output) for o in self.output_list: - setattr(self, o+"_tag", False) + setattr(self, o + "_tag", False) # Output file/folder self.parser.add_argument('-f', '--file', help=_('set the html output folder or csv file')) - def parse_arg(self): """ Parse command line argument @@ -264,6 +306,7 @@ class GlancesCore(object): args = self.parser.parse_args() # Change global variables regarding to user args + # !!! To be refactor to use directly the args list in the code if (args.time is not None): self.refresh_time = args.time self.network_bytepersec_tag = args.byte self.diskio_tag = args.disable_diskio @@ -300,7 +343,7 @@ class GlancesCore(object): output_folder = args.file # !!! Debug - print args + # print args def get_password(self, description='', confirm=False): """ @@ -323,10 +366,21 @@ class GlancesCore(object): sys.stdout.write(_("[Warning] Passwords did not match, please try again...\n")) return self.get_password(description=description, confirm=confirm) - def start(self): + def is_standalone(self): """ - Start the instance - It is the 'real' main function for Glances + Return True if Glances is running in standalone mode """ + return not self.client_tag and not self.server_tag + + def is_client(self): + """ + Return True if Glances is running in client mode + """ + return self.client_tag and not self.server_tag + + def is_server(self): + """ + Return True if Glances is running in sserver mode + """ + return not self.client_tag and self.server_tag - self.parse_arg() diff --git a/glances/core/glances_globals.py b/glances/core/glances_globals.py new file mode 100644 index 00000000..a8382d3f --- /dev/null +++ b/glances/core/glances_globals.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Glances - An eye on your system +# +# Copyright (C) 2014 Nicolargo +# +# 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 . + +# Import system libs +import sys +import os +import gettext +import locale + +# Path definitions +work_path = os.path.realpath(os.path.dirname(__file__)) +appname_path = os.path.split(sys.argv[0])[0] +sys_prefix = os.path.realpath(os.path.dirname(appname_path)) + +# Operating system flag +# Note: Somes libs depends of OS +is_BSD = sys.platform.find('bsd') != -1 +is_Linux = sys.platform.startswith('linux') +is_Mac = sys.platform.startswith('darwin') +is_Windows = sys.platform.startswith('win') + +# i18n +locale.setlocale(locale.LC_ALL, '') +gettext_domain = 'glances' +# get locale directory +i18n_path = os.path.realpath(os.path.join(work_path, '..', 'i18n')) +sys_i18n_path = os.path.join(sys_prefix, 'share', 'locale') +if os.path.exists(i18n_path): + locale_dir = i18n_path +elif os.path.exists(sys_i18n_path): + locale_dir = sys_i18n_path +else: + locale_dir = None +gettext.install(gettext_domain, locale_dir) diff --git a/glances/core/glances_limits.py b/glances/core/glances_limits.py new file mode 100644 index 00000000..a6be900f --- /dev/null +++ b/glances/core/glances_limits.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Glances - An eye on your system +# +# Copyright (C) 2014 Nicolargo +# +# 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 . + +class glancesLimits: + """ + Manage limits for each stats. A limit can be: + * a set of careful, warning and critical values + * a filter (for example: hide some network interfaces) + + The limit list is stored in an hash table: + __limits_list[STAT] = [CAREFUL, WARNING, CRITICAL] + + STD is for defaults limits (CPU/MEM/SWAP/FS) + CPU_IOWAIT limits (iowait in %) + CPU_STEAL limits (steal in %) + LOAD is for LOAD limits (5 min/15 min) + TEMP is for sensors limits (temperature in °C) + HDDTEMP is for hddtemp limits (temperature in °C) + FS is for partitions space limits + IODISK_HIDE is a list of disk (name) to hide + NETWORK_HIDE is a list of network interface (name) to hide + """ + __limits_list = {'STD': [50, 70, 90], + 'CPU_USER': [50, 70, 90], + 'CPU_SYSTEM': [50, 70, 90], + 'CPU_IOWAIT': [40, 60, 80], + 'CPU_STEAL': [10, 15, 20], + 'LOAD': [0.7, 1.0, 5.0], + 'MEM': [50, 70, 90], + 'SWAP': [50, 70, 90], + 'TEMP': [60, 70, 80], + 'HDDTEMP': [45, 52, 60], + 'FS': [50, 70, 90], + 'PROCESS_CPU': [50, 70, 90], + 'PROCESS_MEM': [50, 70, 90], + 'IODISK_HIDE': [], + 'NETWORK_HIDE': []} + + def __init__(self, config): + """ + Init the limits with: default values and configuration file + """ + + self.config = config + + # Test if the configuration file has a limits section + if config.has_section('global'): + # Read STD limits + self.__setLimits('STD', 'global', 'careful') + self.__setLimits('STD', 'global', 'warning') + self.__setLimits('STD', 'global', 'critical') + if config.has_section('cpu'): + # Read CPU limits + self.__setLimits('CPU_USER', 'cpu', 'user_careful') + self.__setLimits('CPU_USER', 'cpu', 'user_warning') + self.__setLimits('CPU_USER', 'cpu', 'user_critical') + self.__setLimits('CPU_SYSTEM', 'cpu', 'system_careful') + self.__setLimits('CPU_SYSTEM', 'cpu', 'system_warning') + self.__setLimits('CPU_SYSTEM', 'cpu', 'system_critical') + self.__setLimits('CPU_IOWAIT', 'cpu', 'iowait_careful') + self.__setLimits('CPU_IOWAIT', 'cpu', 'iowait_warning') + self.__setLimits('CPU_IOWAIT', 'cpu', 'iowait_critical') + self.__setLimits('CPU_STEAL', 'cpu', 'steal_careful') + self.__setLimits('CPU_STEAL', 'cpu', 'steal_warning') + self.__setLimits('CPU_STEAL', 'cpu', 'steal_critical') + if config.has_section('load'): + # Read LOAD limits + self.__setLimits('LOAD', 'load', 'careful') + self.__setLimits('LOAD', 'load', 'warning') + self.__setLimits('LOAD', 'load', 'critical') + if config.has_section('memory'): + # Read MEM limits + self.__setLimits('MEM', 'memory', 'careful') + self.__setLimits('MEM', 'memory', 'warning') + self.__setLimits('MEM', 'memory', 'critical') + if config.has_section('swap'): + # Read MEM limits + self.__setLimits('SWAP', 'swap', 'careful') + self.__setLimits('SWAP', 'swap', 'warning') + self.__setLimits('SWAP', 'swap', 'critical') + if config.has_section('temperature'): + # Read TEMP limits + self.__setLimits('TEMP', 'temperature', 'careful') + self.__setLimits('TEMP', 'temperature', 'warning') + self.__setLimits('TEMP', 'temperature', 'critical') + if config.has_section('hddtemperature'): + # Read HDDTEMP limits + self.__setLimits('HDDTEMP', 'hddtemperature', 'careful') + self.__setLimits('HDDTEMP', 'hddtemperature', 'warning') + self.__setLimits('HDDTEMP', 'hddtemperature', 'critical') + if config.has_section('filesystem'): + # Read FS limits + self.__setLimits('FS', 'filesystem', 'careful') + self.__setLimits('FS', 'filesystem', 'warning') + self.__setLimits('FS', 'filesystem', 'critical') + if config.has_section('process'): + # Process limits + self.__setLimits('PROCESS_CPU', 'process', 'cpu_careful') + self.__setLimits('PROCESS_CPU', 'process', 'cpu_warning') + self.__setLimits('PROCESS_CPU', 'process', 'cpu_critical') + self.__setLimits('PROCESS_MEM', 'process', 'mem_careful') + self.__setLimits('PROCESS_MEM', 'process', 'mem_warning') + self.__setLimits('PROCESS_MEM', 'process', 'mem_critical') + if config.has_section('iodisk'): + # Hidden disks' list + self.__setHidden('IODISK_HIDE', 'iodisk', 'hide') + if config.has_section('network'): + # Network interfaces' list + self.__setHidden('NETWORK_HIDE', 'network', 'hide') + + def __setHidden(self, stat, section, alert='hide'): + """ + stat: 'IODISK', 'NETWORK' + section: 'iodisk', 'network' + alert: 'hide' + """ + value = self.config.get_raw_option(section, alert) + + # print("%s / %s = %s -> %s" % (section, alert, value, stat)) + if (value is not None): + self.__limits_list[stat] = value.split(",") + + def __setLimits(self, stat, section, alert): + """ + stat: 'CPU', 'LOAD', 'MEM', 'SWAP', 'TEMP', etc. + section: 'cpu', 'load', 'memory', 'swap', 'temperature', etc. + alert: 'careful', 'warning', 'critical' + """ + value = self.config.get_option(section, alert) + + # print("%s / %s = %s -> %s" % (section, alert, value, stat)) + if alert.endswith('careful'): + self.__limits_list[stat][0] = value + elif alert.endswith('warning'): + self.__limits_list[stat][1] = value + elif alert.endswith('critical'): + self.__limits_list[stat][2] = value + + def setAll(self, newlimits): + self.__limits_list = newlimits + return True + + def getAll(self): + return self.__limits_list + + def getHide(self, stat): + try: + self.__limits_list[stat] + except KeyError: + return [] + else: + return self.__limits_list[stat] + + def getCareful(self, stat): + return self.__limits_list[stat][0] + + def getWarning(self, stat): + return self.__limits_list[stat][1] + + def getCritical(self, stat): + return self.__limits_list[stat][2] + + # TO BE DELETED AFTER THE HTML output refactoring + def getSTDCareful(self): + return self.getCareful('STD') + + def getSTDWarning(self): + return self.getWarning('STD') + + def getSTDCritical(self): + return self.getCritical('STD') + # /TO BE DELETED AFTER THE HTML output refactoring + + def getCPUCareful(self, stat): + return self.getCareful('CPU_' + stat.upper()) + + def getCPUWarning(self, stat): + return self.getWarning('CPU_' + stat.upper()) + + def getCPUCritical(self, stat): + return self.getCritical('CPU_' + stat.upper()) + + def getLOADCareful(self, core=1): + return self.getCareful('LOAD') * core + + def getLOADWarning(self, core=1): + return self.getWarning('LOAD') * core + + def getLOADCritical(self, core=1): + return self.getCritical('LOAD') * core + + def getMEMCareful(self): + return self.getCareful('MEM') + + def getMEMWarning(self): + return self.getWarning('MEM') + + def getMEMCritical(self): + return self.getCritical('MEM') + + def getSWAPCareful(self): + return self.getCareful('SWAP') + + def getSWAPWarning(self): + return self.getWarning('SWAP') + + def getSWAPCritical(self): + return self.getCritical('SWAP') + + def getTEMPCareful(self): + return self.getCareful('TEMP') + + def getTEMPWarning(self): + return self.getWarning('TEMP') + + def getTEMPCritical(self): + return self.getCritical('TEMP') + + def getHDDTEMPCareful(self): + return self.getCareful('HDDTEMP') + + def getHDDTEMPWarning(self): + return self.getWarning('HDDTEMP') + + def getHDDTEMPCritical(self): + return self.getCritical('HDDTEMP') + + def getFSCareful(self): + return self.getCareful('FS') + + def getFSWarning(self): + return self.getWarning('FS') + + def getFSCritical(self): + return self.getCritical('FS') + + def getProcessCareful(self, stat='', core=1): + if stat.upper() != 'CPU': + # Use core only for CPU + core = 1 + return self.getCareful('PROCESS_' + stat.upper()) * core + + def getProcessWarning(self, stat='', core=1): + if stat.upper() != 'CPU': + # Use core only for CPU + core = 1 + return self.getWarning('PROCESS_' + stat.upper()) * core + + def getProcessCritical(self, stat='', core=1): + if stat.upper() != 'CPU': + # Use core only for CPU + core = 1 + return self.getCritical('PROCESS_' + stat.upper()) * core diff --git a/glances/core/glances_monitor_list.py b/glances/core/glances_monitor_list.py new file mode 100644 index 00000000..7be64811 --- /dev/null +++ b/glances/core/glances_monitor_list.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Glances - An eye on your system +# +# Copyright (C) 2014 Nicolargo +# +# 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 . + +class monitorList: + """ + This class describes the optionnal monitored processes list + A list of 'important' processes to monitor. + + The list (Python list) is composed of items (Python dict) + An item is defined (Dict keys'): + * description: Description of the processes (max 16 chars) + * regex: regular expression of the processes to monitor + * command: (optional) shell command for extended stat + * countmin: (optional) minimal number of processes + * countmax: (optional) maximum number of processes + """ + # Maximum number of items in the list + __monitor_list_max_size = 10 + # The list + __monitor_list = [] + + def __init__(self, config): + """ + Init the monitoring list from the configuration file + """ + + if config.has_section('monitor'): + # Process monitoring list + self.__setMonitorList('monitor', 'list') + + def __setMonitorList(self, section, key): + """ + Init the monitored processes list + The list is defined in the Glances configuration file + """ + for l in range(1, self.__monitor_list_max_size + 1): + value = {} + key = "list_" + str(l) + "_" + try: + description = config.get_raw_option(section, key + "description") + regex = config.get_raw_option(section, key + "regex") + command = config.get_raw_option(section, key + "command") + countmin = config.get_raw_option(section, key + "countmin") + countmax = config.get_raw_option(section, key + "countmax") + except Exception: + pass + else: + if description is not None and regex is not None: + # Build the new item + value["description"] = description + value["regex"] = regex + value["command"] = command + value["countmin"] = countmin + value["countmax"] = countmax + # Add the item to the list + self.__monitor_list.append(value) + + def __str__(self): + return str(self.__monitor_list) + + def __repr__(self): + return self.__monitor_list + + def __getitem__(self, item): + return self.__monitor_list[item] + + def __len__(self): + return len(self.__monitor_list) + + def __get__(self, item, key): + """ + Meta function to return key value of item + None if not defined or item > len(list) + """ + if item < len(self.__monitor_list): + try: + return self.__monitor_list[item][key] + except Exception: + return None + else: + return None + + def getAll(self): + return self.__monitor_list + + def setAll(self, newlist): + self.__monitor_list = newlist + + def description(self, item): + """ + Return the description of the item number (item) + """ + return self.__get__(item, "description") + + def regex(self, item): + """ + Return the regular expression of the item number (item) + """ + return self.__get__(item, "regex") + + def command(self, item): + """ + Return the stats command of the item number (item) + """ + return self.__get__(item, "command") + + def countmin(self, item): + """ + Return the minimum number of processes of the item number (item) + """ + return self.__get__(item, "countmin") + + def countmax(self, item): + """ + Return the maximum number of processes of the item number (item) + """ + return self.__get__(item, "countmax") + diff --git a/glances/core/glances_server.py b/glances/core/glances_server.py new file mode 100644 index 00000000..1b21b79f --- /dev/null +++ b/glances/core/glances_server.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Glances - An eye on your system +# +# Copyright (C) 2014 Nicolargo +# +# 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 . + +# Import system libs +import sys +import socket + +from ..core.glances_timer import Timer + +try: + # Python 2 + from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler + from SimpleXMLRPCServer import SimpleXMLRPCServer +except ImportError: + # Python 3 + from xmlrpc.server import SimpleXMLRPCRequestHandler + from xmlrpc.server import SimpleXMLRPCServer + +try: + # Python 2 + from xmlrpclib import ServerProxy, ProtocolError +except ImportError: + # Python 3 + from xmlrpc.client import ServerProxy, ProtocolError + + +class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler): + """ + Main XMLRPC handler + """ + rpc_paths = ('/RPC2', ) + + def end_headers(self): + # Hack to add a specific header + # Thk to: https://gist.github.com/rca/4063325 + self.send_my_headers() + SimpleXMLRPCRequestHandler.end_headers(self) + + def send_my_headers(self): + # Specific header is here (solved the issue #227) + self.send_header("Access-Control-Allow-Origin", "*") + + def authenticate(self, headers): + # auth = headers.get('Authorization') + try: + (basic, _, encoded) = headers.get('Authorization').partition(' ') + except Exception: + # Client did not ask for authentidaction + # If server need it then exit + return not self.server.isAuth + else: + # Client authentication + (basic, _, encoded) = headers.get('Authorization').partition(' ') + assert basic == 'Basic', 'Only basic authentication supported' + # Encoded portion of the header is a string + # Need to convert to bytestring + encodedByteString = encoded.encode() + # Decode Base64 byte String to a decoded Byte String + decodedBytes = b64decode(encodedByteString) + # Convert from byte string to a regular String + decodedString = decodedBytes.decode() + # Get the username and password from the string + (username, _, password) = decodedString.partition(':') + # Check that username and password match internal global dictionary + return self.check_user(username, password) + + def check_user(self, username, password): + # Check username and password in the dictionnary + if username in self.server.user_dict: + if self.server.user_dict[username] == md5(password).hexdigest(): + return True + return False + + def parse_request(self): + if SimpleXMLRPCRequestHandler.parse_request(self): + # Next we authenticate + if self.authenticate(self.headers): + return True + else: + # if authentication fails, tell the client + self.send_error(401, 'Authentication failed') + return False + + def log_message(self, format, *args): + # No message displayed on the server side + pass + + +class GlancesXMLRPCServer(SimpleXMLRPCServer): + """ + Init a SimpleXMLRPCServer instance (IPv6-ready) + """ + + def __init__(self, bind_address, bind_port=61209, + requestHandler=GlancesXMLRPCHandler): + + try: + self.address_family = socket.getaddrinfo(bind_address, bind_port)[0][0] + except socket.error as e: + print(_("Couldn't open socket: %s") % e) + sys.exit(1) + + SimpleXMLRPCServer.__init__(self, (bind_address, bind_port), + requestHandler) + + +class GlancesInstance(): + """ + All the methods of this class are published as XML RPC methods + """ + + def __init__(self, cached_time=1): + # cached_time is the minimum time interval between stats updates + # i.e. XML/RPC calls will not retrieve updated info until the time + # since last update is passed (will retrieve old cached info instead) + self.timer = Timer(0) + self.cached_time = cached_time + + def __update__(self): + # Never update more than 1 time per cached_time + if self.timer.finished(): + stats.update() + self.timer = Timer(self.cached_time) + + def init(self): + # Return the Glances version + return __version__ + + def getAll(self): + # Update and return all the stats + self.__update__() + return json.dumps(stats.getAll()) + + def getAllLimits(self): + # Return all the limits + return json.dumps(limits.getAll()) + + def getAllMonitored(self): + # Return the processes monitored list + return json.dumps(monitors.getAll()) + + def getSystem(self): + # Return operating system info + # No need to update... + #~ self.__update__() + return json.dumps(stats.getSystem()) + + def getCore(self): + # Update and return number of Core + self.__update__() + return json.dumps(stats.getCore()) + + def getCpu(self): + # Update and return CPU stats + self.__update__() + return json.dumps(stats.getCpu()) + + def getLoad(self): + # Update and return LOAD stats + self.__update__() + return json.dumps(stats.getLoad()) + + def getMem(self): + # Update and return MEM stats + self.__update__() + return json.dumps(stats.getMem()) + + def getMemSwap(self): + # Update and return MEMSWAP stats + self.__update__() + return json.dumps(stats.getMemSwap()) + + def getSensors(self): + # Update and return SENSORS stats + self.__update__() + return json.dumps(stats.getSensors()) + + def getHDDTemp(self): + # Update and return HDDTEMP stats + self.__update__() + return json.dumps(stats.getHDDTemp()) + + def getNetwork(self): + # Update and return NET stats + self.__update__() + return json.dumps(stats.getNetwork()) + + def getDiskIO(self): + # Update and return DISK IO stats + self.__update__() + return json.dumps(stats.getDiskIO()) + + def getFs(self): + # Update and return FS stats + self.__update__() + return json.dumps(stats.getFs()) + + def getProcessCount(self): + # Update and return ProcessCount stats + self.__update__() + return json.dumps(stats.getProcessCount()) + + def getProcessList(self): + # Update and return ProcessList stats + self.__update__() + return json.dumps(stats.getProcessList()) + + def getBatPercent(self): + # Update and return total batteries percent stats + self.__update__() + return json.dumps(stats.getBatPercent()) + + def getNow(self): + # Update and return current date/hour + self.__update__() + return json.dumps(stats.getNow().strftime(_("%Y-%m-%d %H:%M:%S"))) + + def getUptime(self): + # Update and return system uptime + self.__update__() + return json.dumps(stats.getUptime().strftime(_("%Y-%m-%d %H:%M:%S"))) + + def __getTimeSinceLastUpdate(self, IOType): + assert(IOType in ['net', 'disk', 'process_disk']) + return getTimeSinceLastUpdate(IOType) + + def getNetTimeSinceLastUpdate(self): + return getTimeSinceLastUpdate('net') + + def getDiskTimeSinceLastUpdate(self): + return getTimeSinceLastUpdate('net') + + def getProcessDiskTimeSinceLastUpdate(self): + return getTimeSinceLastUpdate('process_disk') + + +class GlancesServer(): + """ + This class creates and manages the TCP client + """ + + def __init__(self, bind_address="0.0.0.0", bind_port=61209, + requestHandler=GlancesXMLRPCHandler, cached_time=1): + # Init the XML RPC server + try: + self.server = GlancesXMLRPCServer(bind_address, bind_port, requestHandler) + except Exception, err: + print(_("Error: Can not start Glances server (%s)") % err) + sys.exit(2) + + # The users dict + # username / MD5 password couple + # By default, no auth is needed + self.server.user_dict = {} + self.server.isAuth = False + # Register functions + self.server.register_introspection_functions() + self.server.register_instance(GlancesInstance(cached_time)) + + def add_user(self, username, password): + """ + Add an user to the dictionnary + """ + self.server.user_dict[username] = md5(password).hexdigest() + self.server.isAuth = True + + def serve_forever(self): + self.server.serve_forever() + + def server_close(self): + self.server.server_close() diff --git a/glances/core/glances_timer.py b/glances/core/glances_timer.py new file mode 100644 index 00000000..7e4f2441 --- /dev/null +++ b/glances/core/glances_timer.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Glances - An eye on your system +# +# Copyright (C) 2014 Nicolargo +# +# 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 . + +import time + +class Timer: + """ + The timer class + A simple chrono + """ + + def __init__(self, duration): + self.duration = duration + self.start() + + def start(self): + self.target = time.time() + self.duration + + def reset(self): + self.start() + + def set(self, duration): + self.duration = duration + + def finished(self): + return time.time() > self.target + diff --git a/glances/plugins/glances_cpu.py b/glances/plugins/glances_cpu.py index de3f7d67..901cfa13 100644 --- a/glances/plugins/glances_cpu.py +++ b/glances/plugins/glances_cpu.py @@ -18,9 +18,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -from ..plugins.glances_plugin import GlancesPlugin +# from ..plugins.glances_plugin import GlancesPlugin +from glances_plugin import GlancesPlugin -class CpuPlugin(GlancesPlugin): +class Plugin(GlancesPlugin): """ Glances' Cpu Plugin diff --git a/glances/plugins/glances_host.py b/glances/plugins/glances_host.py new file mode 100644 index 00000000..f703aed7 --- /dev/null +++ b/glances/plugins/glances_host.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Glances - An eye on your system +# +# Copyright (C) 2014 Nicolargo +# +# 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 . + +# from ..plugins.glances_plugin import GlancesPlugin +from glances_plugin import GlancesPlugin + +class Plugin(GlancesPlugin): + """ + Glances' Host Plugin + + stats is a ? + """ + + def __init__(self): + GlancesPlugin.__init__(self) + self.update() + + def update(self): + # !!! Example + self.stats = { 'os': 'linux' } + + def __str__(self): + ret = "Host\n" + for k in self.stats: + ret += "{0} {1}\n".format(k, self.stats[k]) + return ret