Merge branch 'issue780' into develop

This commit is contained in:
nicolargo 2016-05-02 10:33:08 +02:00
commit aafb5060bc
46 changed files with 1177 additions and 538 deletions

6
NEWS
View File

@ -7,6 +7,7 @@ Version 2.7
Enhancements and new features:
* Add Application Monitoring Process plugin (issue #780)
* Improve IP plugin to display public IP address (issue #646)
* CPU additionnal stats monitoring: Context switch, Interrupts... (issue #810)
* [Folders] Differentiate permission issue and non-existence of a directory (issue #828)
@ -18,6 +19,11 @@ Bugs corrected:
* [Web UI] Fix folders plugin never displayed (issue #829)
* Correct issue IP plugin: VPN with no internet access (issue #842)
Deprecated:
* Python 2.6 is no longer supported
* Monitoring process list module is replaced by AMP
Version 2.6.1
=============

View File

@ -1,3 +1,7 @@
##############################################################################
# plugins
##############################################################################
[quicklook]
# Define CPU, MEM and SWAP thresholds in %
cpu_careful=50
@ -11,16 +15,12 @@ swap_warning=70
swap_critical=90
[cpu]
# Define CPU thresholds in %
# Default values if not defined: 50/70/90
# Default values if not defined: 50/70/90 (except for iowait)
user_careful=50
user_warning=70
user_critical=90
#user_log=False
#user_critical_action=echo `date` - {{user}} > /tmp/cpu.alert
iowait_careful=50
iowait_warning=70
iowait_critical=90
#user_critical_action=echo {{user}} {{value}} {{max}} > /tmp/cpu.alert
system_careful=50
system_warning=70
system_critical=90
@ -28,8 +28,13 @@ steal_careful=50
steal_warning=70
steal_critical=90
#steal_log=True
# Context switch limit per core / second
# For example, if you have 2 Core, critical limit will be 28000/sec
# I/O wait percentage should be lower than 1/# (of CPU cores)
# Let commented for default config (1/#-20% / 1/#-10% / 1/#)
#iowait_careful=30
#iowait_warning=40
#iowait_critical=50
# Context switch limit (core / second)
# Let commented for default config (critical is 56000/# (of CPU core))
ctx_switches_careful=10000
ctx_switches_warning=12000
ctx_switches_critical=14000
@ -155,33 +160,11 @@ mem_careful=50
mem_warning=70
mem_critical=90
#[monitor]
# Define the list of processes to monitor
# *** This section is optional ***
# The list is composed of items (list_#nb <= 10)
# An item is defined:
# * description: Description of the processes (max 16 chars)
# * regex: regular expression of the processes to monitor
# * command: (optional) full path to shell command/script for extended stat
# Use with caution. Should return a single line string.
# Only execute when at least one process is running
# By default display CPU and MEM %
# Limitation: Do not use in client / server mode
# * countmin: (optional) minimal number of processes
# A warning will be displayed if number of process < count
# * countmax: (optional) maximum number of processes
# A warning will be displayed if number of process > count
#list_1_description=Dropbox
#list_1_regex=.*dropbox.*
#list_1_countmin=1
#list_1_command=dropbox status | head -1
#list_2_description=Python programs
#list_2_regex=.*python.*
#list_3_description=Famous Xeyes
#list_3_regex=.*xeyes.*
#list_3_countmin=1
##############################################################################
# Client/server
##############################################################################
#[serverlist]
[serverlist]
# Define the static servers list
#server_1_name=localhost
#server_1_alias=My local PC
@ -194,7 +177,7 @@ mem_critical=90
#server_4_name=pasbon
#server_4_port=61237
#[passwords]
[passwords]
# Define the passwords list
# Syntax: host=password
# Where: host is the hostname
@ -203,6 +186,10 @@ mem_critical=90
#xps=abc
#default=defaultpassword
##############################################################################
# Exports
##############################################################################
[influxdb]
# Configuration for the --export-influxdb option
# https://influxdb.com/
@ -250,3 +237,56 @@ user=guest
password=guest
queue=glances_queue
##############################################################################
# AMPS
# * enable: Enable (true) or disable (false) the AMP
# * regex: Regular expression to filter the process(es)
# * refresh: The AMP is executed every refresh seconds
# * one_line: (optional) Force (if true) the AMP to be displayed in one line
* * command: (optional) command to execute when the process is detected (thk to the regex)
# * countmin: (optional) minimal number of processes
# A warning will be displayed if number of process < count
# * countmax: (optional) maximum number of processes
# A warning will be displayed if number of process > count
# * <foo>: Others variables can be defined and used in the AMP script
##############################################################################
[amp_dropbox]
# Use the default AMP (no dedicated AMP Python script)
enable=false
regex=.*dropbox.*
refresh=3
one_line=false
command=dropbox status
countmin=1
[amp_python]
# Monitor all Python scripts
enable=false
regex=.*python.*
refresh=3
countmax=2
[amp_nginx]
# Nginx status page should be enable (https://easyengine.io/tutorials/nginx/status-page/)
enable=true
regex=\/usr\/sbin\/nginx
refresh=60
one_line=false
status_url=http://localhost/nginx_status
[amp_systemd]
# Systemd
enable=true
regex=\/usr\/lib\/systemd\/systemd
refresh=30
one_line=true
systemctl_cmd=/usr/bin/systemctl --plain
[amp_systemv]
# Systemv
enable=true
regex=\/sbin\/init
refresh=30
one_line=true
service_cmd=/usr/bin/service --status-all

BIN
docs/_static/amp-python-warning.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
docs/_static/amp-python.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
docs/_static/amps-dropbox.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/_static/amps.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

101
docs/aoa/amps.rst Normal file
View File

@ -0,0 +1,101 @@
.. _amps:
Applications Monitoring Process
===============================
Thanks to Glances and it AMP module, you can add specific monitoring
to running process. AMP are defined in the Glances configuration file.
You can disable AMP using the --disable-amps option or pressing the `A` shortkey.
Simple AMP
----------
For example, a simple AMP which monitor the CPU/MEM of all Python processes
can be define using:
.. code-block:: ini
[amp_python]
enable=true
regex=.*python.*
refresh=3
Every 3 seconds (*refresh*) and if the *enable* key is true, Glances will
filter the running processes list thanks to the .*python.* regular
expression (*regex*). The default behavor for an AMP is to display:
the number of matching processes, the CPU and MEM:
.. image:: ../_static/amp-python.png
You can also define the minimum (*countmin*) and/or maximum (*countmax*) process
number. For example:
.. code-block:: ini
[amp_python]
enable=true
regex=.*python.*
refresh=3
countmin=1
countmax=2
With this configuration, if the number of running Python script is higher than 2
then the AMP is display with a purple color (red if < countmin):
.. image:: ../_static/amp-python-warning.png
User define AMP
---------------
If you need to execute a specific command line, you can use the *command* option.
For example, if you want to display the Dropbox process status, you can define the
following section in the Glances configuration file:
.. code-block:: ini
[amp_dropbox]
# Use the default AMP (no dedicated AMP Python script)
enable=true
regex=.*dropbox.*
refresh=3
one_line=false
command=dropbox status
countmin=1
The *dropbox status* command line will be executed and displayed in the Glances UI:
.. image:: ../_static/amp-python-dropbox.png
You can force Glances to display the result in one line setting the *one_line* to true.
Embeded AMP
-----------
Glances provides some specifics AMP scripts (replacing the *command* line) hosted
in the glances/amps folder. You can write your own AMP script to fill yours needs.
AMP scripts are located in the glances/amps folder and should be names glances_*.py.
An AMP script define an Amp class (GlancesAmp) with a mandatory update method.
The update method call the set_result method to set the AMP return string.
The return string is a string with one or more line (\n between lines).
You can write your owns AMP and enable its from the configuration file.
The configuration file section should be named [amp_*].
For example, if you want to enable the Nginx AMP, the following definition
should do the job (NGinx AMP is provided by the Glances team as an example):
.. code-block:: ini
[amp_nginx]
enable=true
regex=\/usr\/sbin\/nginx
refresh=60
one_line=false
status_url=http://localhost/nginx_status
Here is the result:
.. image:: ../_static/amps.png
In client/server mode, the AMP list is defined on the server side.

View File

@ -32,6 +32,7 @@ Legend:
sensors
ps
monitor
amps
logs
docker
actions

View File

@ -3,63 +3,6 @@
Monitored Processes List
========================
The monitored processes list allows user, through the configuration
file, to group processes and quickly show if the number of running
processes is not good.
The monitored processes list is deprecated.
.. image:: ../_static/monitored.png
Each item is defined by:
- ``description``: description of the processes (max 16 chars).
- ``regex``: regular expression of the processes to monitor.
- ``command``: (optional) full path to shell command/script for extended
- stat. Should return a single line string. Use with caution.
- ``countmin``: (optional) minimal number of processes. A warning will
- be displayed if number of processes < count.
- ``countmax``: (optional) maximum number of processes. A warning will
be displayed if number of processes > count.
Up to ``10`` items can be defined.
For example, if you want to monitor the Nginx processes on a web server,
the following definition should do the job:
.. code-block:: ini
[monitor]
list_1_description=Nginx server
list_1_regex=.*nginx.*
list_1_command=nginx -v
list_1_countmin=1
list_1_countmax=4
If you also want to monitor the PHP-FPM daemon processes, you should add
another item:
.. code-block:: ini
[monitor]
list_1_description=Nginx server
list_1_regex=.*nginx.*
list_1_command=nginx -v
list_1_countmin=1
list_1_countmax=4
list_2_description=PHP-FPM
list_2_regex=.*php-fpm.*
list_2_countmin=1
list_2_countmax=20
In client/server mode, the list is defined on the server side.
A new method, called `getAllMonitored`, is available in the APIs and
get the JSON representation of the monitored processes list.
Alerts are set as following:
================= ============
# of process Status
================= ============
``0`` ``CRITICAL``
``min < p < max`` ``OK``
``p > max`` ``WARNING``
================= ============
Please use the Application Monitoring Process (AMP).

View File

@ -95,6 +95,10 @@ Command-Line Options
disable process module
.. option:: --disable-amps
disable application monitoring process module
.. option:: --disable-log
disable log module
@ -291,6 +295,9 @@ The following commands (key pressed) are supported while in Glances:
- If CPU iowait ``>60%``, sort processes by I/O read and write
``A``
Enable/disable Application Monitoring Process
``b``
Switch between bit/s or Byte/s for network I/O

0
glances/amps/__init__.py Normal file
View File

203
glances/amps/glances_amp.py Normal file
View File

@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 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/>.
"""
I am your father...
...for all Glances Application Monitoring Processes (AMP).
AMP (Application Monitoring Process)
A Glances AMP is a Python script called (every *refresh* seconds) if:
- the AMP is *enabled* in the Glances configuration file
- a process is running (match the *regex* define in the configuration file)
The script should define a Amp (GlancesAmp) class with, at least, an update method.
The update method should call the set_result method to set the AMP return string.
The return string is a string with one or more line (\n between lines).
If the *one_line* var is true then the AMP will be displayed in one line.
"""
from glances.compat import u
from glances.timer import Timer
from glances.logger import logger
class GlancesAmp(object):
"""Main class for Glances AMP."""
NAME = '?'
VERSION = '?'
DESCRIPTION = '?'
AUTHOR = '?'
EMAIL = '?'
def __init__(self, name=None, args=None):
"""Init AMP classe."""
logger.debug("Init {0} version {1}".format(self.NAME, self.VERSION))
# AMP name (= module name without glances_)
if name is None:
self.amp_name = self.__class__.__module__[len('glances_'):]
else:
self.amp_name = name
# Init the args
self.args = args
# Init the configs
self.configs = {}
# A timer is needed to only update every refresh seconds
# Init to 0 in order to update the AMP on startup
self.timer = Timer(0)
def load_config(self, config):
"""Load AMP parameters from the configuration file."""
# Read AMP confifuration.
# For ex, the AMP foo should have the following section:
#
# [foo]
# enable=true
# regex=\/usr\/bin\/nginx
# refresh=60
#
# and optionnaly:
#
# one_line=false
# option1=opt1
# ...
#
amp_section = 'amp_' + self.amp_name
if (hasattr(config, 'has_section') and
config.has_section(amp_section)):
logger.debug("{0}: Load configuration".format(self.NAME))
for param, _ in config.items(amp_section):
try:
self.configs[param] = config.get_float_value(amp_section, param)
except ValueError:
self.configs[param] = config.get_value(amp_section, param).split(',')
if len(self.configs[param]) == 1:
self.configs[param] = self.configs[param][0]
logger.debug("{0}: Load parameter: {1} = {2}".format(self.NAME, param, self.configs[param]))
else:
logger.debug("{0}: Can not find section {1} in the configuration file".format(self.NAME, self.amp_name))
return False
# enable, regex and refresh are mandatories
# if not configured then AMP is disabled
if self.enable():
for k in ['regex', 'refresh']:
if k not in self.configs:
logger.warning("{0}: Can not find configuration key {1} in section {2}".format(self.NAME, k, self.amp_name))
self.configs['enable'] = 'false'
else:
logger.warning("{0} is disabled".format(self.NAME))
# Init the count to 0
self.configs['count'] = 0
return self.enable()
def get(self, key):
"""Generic method to get the item in the AMP configuration"""
if key in self.configs:
return self.configs[key]
else:
return None
def enable(self):
"""Return True|False if the AMP is enabled in the configuration file (enable=true|false)."""
ret = self.get('enable')
if ret is None:
return False
else:
return ret.lower().startswith('true')
def regex(self):
"""Return regular expression used to identified the current application."""
return self.get('regex')
def refresh(self):
"""Return refresh time in seconds for the current application monitoring process."""
return self.get('refresh')
def one_line(self):
"""Return True|False if the AMP shoukd be displayed in oneline (one_lineline=true|false)."""
ret = self.get('one_line')
if ret is None:
return False
else:
return ret.lower().startswith('true')
def time_until_refresh(self):
"""Return time in seconds until refresh."""
return self.timer.get()
def should_update(self):
"""Return True is the AMP should be updated:
- AMP is enable
- only update every 'refresh' seconds
"""
if self.timer.finished():
self.timer.set(self.refresh())
self.timer.reset()
return self.enable()
return False
def set_count(self, count):
"""Set the number of processes matching the regex"""
self.configs['count'] = count
def count(self):
"""Get the number of processes matching the regex"""
return self.get('count')
def count_min(self):
"""Get the minimum number of processes"""
return self.get('countmin')
def count_max(self):
"""Get the maximum number of processes"""
return self.get('countmax')
def set_result(self, result, separator=''):
"""Store the result (string) into the result key of the AMP
if one_line is true then replace \n by separator
"""
if self.one_line():
self.configs['result'] = str(result).replace('\n', separator)
else:
self.configs['result'] = str(result)
def result(self):
""" Return the result of the AMP (as a string)"""
ret = self.get('result')
if ret is not None:
ret = u(ret)
return ret
def update_wrapper(self, process_list):
"""Wrapper for the children update"""
# Set the number of running process
self.set_count(len(process_list))
# Call the children update method
if self.should_update():
return self.update(process_list)
else:
return self.result()

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 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/>.
"""
Default AMP
=========
Monitor a process by executing a command line. This is the default AMP's behavor
if no AMP script is found.
Configuration file example
--------------------------
[amp_foo]
enable=true
regex=\/usr\/bin\/foo
refresh=10
one_line=false
command=foo status
"""
from subprocess import check_output, STDOUT
from glances.compat import u, to_ascii
from glances.logger import logger
from glances.amps.glances_amp import GlancesAmp
class Amp(GlancesAmp):
"""Glances' Default AMP."""
NAME = ''
VERSION = '1.0'
DESCRIPTION = ''
AUTHOR = 'Nicolargo'
EMAIL = 'contact@nicolargo.com'
def __init__(self, name=None, args=None):
"""Init the AMP."""
self.NAME = name.capitalize()
super(Amp, self).__init__(name=name, args=args)
def update(self, process_list):
"""Update the AMP"""
# Get the systemctl status
logger.debug('{0}: Update stats using service {1}'.format(self.NAME, self.get('service_cmd')))
try:
res = self.get('command')
except OSError as e:
logger.debug('{0}: Error while executing service ({1})'.format(self.NAME, e))
else:
if res is not None:
msg = u(check_output(res.split(), stderr=STDOUT))
self.set_result(to_ascii(msg.rstrip()))
else:
# Set the default message if command return None
# Default sum of CPU and MEM for the matching regex
self.set_result('CPU: {0:.1f}% | MEM: {1:.1f}%'.format(
sum([p['cpu_percent'] for p in process_list]),
sum([p['memory_percent'] for p in process_list])))
return self.result()

View File

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 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/>.
"""
Nginx AMP
=========
Monitor the Nginx process using the status page.
How to read the stats
---------------------
Active connections Number of all open connections. This doesnt mean number of users.
A single user, for a single pageview can open many concurrent connections to your server.
Server accepts handled requests This shows three values.
First is total accepted connections.
Second is total handled connections. Usually first 2 values are same.
Third value is number of and handles requests. This is usually greater than second value.
Dividing third-value by second-one will give you number of requests per connection handled
by Nginx. In above example, 10993/7368, 1.49 requests per connections.
Reading nginx reads request header
Writing nginx reads request body, processes request, or writes response to a client
Waiting keep-alive connections, actually it is active (reading + writing).
This value depends on keepalive-timeout. Do not confuse non-zero waiting value for poor
performance. It can be ignored.
Source reference: https://easyengine.io/tutorials/nginx/status-page/
Configuration file example
--------------------------
[amp_nginx]
# Nginx status page should be enable (https://easyengine.io/tutorials/nginx/status-page/)
enable=true
regex=\/usr\/sbin\/nginx
refresh=60
one_line=false
status_url=http://localhost/nginx_status
"""
import requests
from glances.logger import logger
from glances.amps.glances_amp import GlancesAmp
class Amp(GlancesAmp):
"""Glances' Nginx AMP."""
NAME = 'Nginx'
VERSION = '1.0'
DESCRIPTION = 'Get Nginx stats from status-page'
AUTHOR = 'Nicolargo'
EMAIL = 'contact@nicolargo.com'
# def __init__(self, args=None):
# """Init the AMP."""
# super(Amp, self).__init__(args=args)
def update(self, process_list):
"""Update the AMP"""
# Get the Nginx status
logger.debug('{0}: Update stats using status URL {1}'.format(self.NAME, self.get('status_url')))
res = requests.get(self.get('status_url'))
if res.ok:
# u'Active connections: 1 \nserver accepts handled requests\n 1 1 1 \nReading: 0 Writing: 1 Waiting: 0 \n'
self.set_result(res.text.rstrip())
else:
logger.debug('{0}: Can not grab status URL {1} ({2})'.format(self.NAME, self.get('status_url'), res.reason))
return self.result()

View File

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 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/>.
"""
Systemd AMP
===========
Monitor the state of the systemd system and service (unit) manager.
How to read the stats
---------------------
active: Number of active units. This is usually a fairly basic way to tell if the
unit has started successfully or not.
loaded: Number of loaded units (unit's configuration has been parsed by systemd).
failed: Number of units with an active failed status.
Source reference: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units
Configuration file example
--------------------------
[amp_systemd]
# Systemd
enable=true
regex=\/usr\/lib\/systemd\/systemd
refresh=60
one_line=true
systemctl_cmd=/usr/bin/systemctl --plain
"""
from subprocess import check_output
from glances.logger import logger
from glances.compat import iteritems
from glances.amps.glances_amp import GlancesAmp
class Amp(GlancesAmp):
"""Glances' Systemd AMP."""
NAME = 'Systemd'
VERSION = '1.0'
DESCRIPTION = 'Get services list from systemctl (systemd)'
AUTHOR = 'Nicolargo'
EMAIL = 'contact@nicolargo.com'
# def __init__(self, args=None):
# """Init the AMP."""
# super(Amp, self).__init__(args=args)
def update(self, process_list):
"""Update the AMP"""
# Get the systemctl status
logger.debug('{0}: Update stats using systemctl {1}'.format(self.NAME, self.get('systemctl_cmd')))
try:
res = check_output(self.get('systemctl_cmd').split())
except OSError as e:
logger.debug('{0}: Error while executing systemctl ({1})'.format(self.NAME, e))
else:
status = {}
# For each line
for r in res.split('\n')[1:-8]:
# Split per space .*
l = r.split()
if len(l) > 3:
# load column
for c in range(1, 3):
try:
status[l[c]] += 1
except KeyError:
status[l[c]] = 1
# Build the output (string) message
output = 'Services\n'
for k, v in iteritems(status):
output += '{0}: {1}\n'.format(k, v)
self.set_result(output, separator=' ')
return self.result()

View File

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 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/>.
"""
SystemV AMP
===========
Monitor the state of the Syste V init system and service.
How to read the stats
---------------------
Running: Number of running services.
Stopped: Number of stopped services.
Upstart: Number of service managed by Upstart.
Source reference: http://askubuntu.com/questions/407075/how-to-read-service-status-all-results
Configuration file example
--------------------------
[amp_systemv]
# Systemv
enable=true
regex=\/sbin\/init
refresh=60
one_line=true
service_cmd=/usr/bin/service --status-all
"""
from subprocess import check_output, STDOUT
from glances.logger import logger
from glances.compat import iteritems
from glances.amps.glances_amp import GlancesAmp
class Amp(GlancesAmp):
"""Glances' Systemd AMP."""
NAME = 'SystemV'
VERSION = '1.0'
DESCRIPTION = 'Get services list from service (initd)'
AUTHOR = 'Nicolargo'
EMAIL = 'contact@nicolargo.com'
# def __init__(self, args=None):
# """Init the AMP."""
# super(Amp, self).__init__(args=args)
def update(self, process_list):
"""Update the AMP"""
# Get the systemctl status
logger.debug('{0}: Update stats using service {1}'.format(self.NAME, self.get('service_cmd')))
try:
res = check_output(self.get('service_cmd').split(), stderr=STDOUT)
except OSError as e:
logger.debug('{0}: Error while executing service ({1})'.format(self.NAME, e))
else:
status = {'running': 0, 'stopped': 0, 'upstart': 0}
# For each line
for r in res.split('\n'):
# Split per space .*
l = r.split()
if len(l) < 4:
continue
if l[1] == '+':
status['running'] += 1
elif l[1] == '-':
status['stopped'] += 1
elif l[1] == '?':
status['upstart'] += 1
# Build the output (string) message
output = 'Services\n'
for k, v in iteritems(status):
output += '{0}: {1}\n'.format(k, v)
self.set_result(output, separator=' ')
return self.result()

137
glances/amps_list.py Normal file
View File

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 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/>.
"""Manage the AMPs list."""
import os
import re
import threading
from glances.compat import listkeys, iteritems
from glances.logger import logger
from glances.globals import amps_path
from glances.processes import glances_processes
class AmpsList(object):
"""This class describes the optional application monitoring process list.
The AMP list is a list of processes with a specific monitoring action.
The list (Python list) is composed of items (Python dict).
An item is defined (dict keys):
*...
"""
# The dict
__amps_dict = {}
def __init__(self, args, config):
"""Init the AMPs list."""
self.args = args
self.config = config
# Load the AMP configurations / scripts
self.load_configs()
def load_configs(self):
"""Load the AMP configuration files."""
if self.config is None:
return False
header = "glances_"
# For each AMP scrip, call the load_config method
for s in self.config.sections():
if s.startswith("amp_"):
# An AMP section exists in the configuration file
# If an AMP script exist in the glances/amps folder, use it
amp_conf_name = s[4:]
amp_script = os.path.join(amps_path, header + s[4:] + ".py")
if not os.path.exists(amp_script):
# If not, use the default script
amp_script = os.path.join(amps_path, "glances_default.py")
try:
amp = __import__(os.path.basename(amp_script)[:-3])
except ImportError as e:
logger.warning("Can not load {0}, you need to install an external Python package ({1})".format(os.path.basename(amp_script), e))
except Exception as e:
logger.warning("Can not load {0} ({1})".format(os.path.basename(amp_script), e))
else:
# Add the AMP to the dictionary
# The key is the AMP name
# for example, the file glances_xxx.py
# generate self._amps_list["xxx"] = ...
self.__amps_dict[amp_conf_name] = amp.Amp(name=amp_conf_name, args=self.args)
# Load the AMP configuration
self.__amps_dict[amp_conf_name].load_config(self.config)
# Log AMPs list
logger.debug("AMPs' list: {0}".format(self.getList()))
return True
def __str__(self):
return str(self.__amps_dict)
def __repr__(self):
return self.__amps_dict
def __getitem__(self, item):
return self.__amps_dict[item]
def __len__(self):
return len(self.__amps_dict)
def update(self):
"""Update the command result attributed."""
# Search application monitored processes by a regular expression
processlist = glances_processes.getalllist()
# Iter upon the AMPs dict
for k, v in iteritems(self.get()):
try:
amps_list = [p for p in processlist for c in p['cmdline'] if re.search(v.regex(), c) is not None]
except TypeError:
continue
if len(amps_list) > 0:
# At least one process is matching the regex
logger.debug("AMPS: {} process detected (PID={})".format(k, amps_list[0]['pid']))
# Call the AMP update method
thread = threading.Thread(target=v.update_wrapper, args=[amps_list])
thread.start()
else:
# Set the process number to 0
v.set_count(0)
if v.count_min() > 0:
# Only display the "No running process message" is countmin is defined
v.set_result("No running process")
return self.__amps_dict
def getList(self):
"""Return the AMPs list."""
return listkeys(self.__amps_dict)
def get(self):
"""Return the AMPs dict."""
return self.__amps_dict
def set(self, new_dict):
"""Set the AMPs dict."""
self.__amps_dict = new_dict

View File

@ -204,12 +204,16 @@ class GlancesAutoDiscoverClient(object):
# Issue #528 (no network interface available)
pass
print("Announce the Glances server on the LAN (using {0} IP address)".format(zeroconf_bind_address))
self.info = ServiceInfo(
zeroconf_type, '{0}:{1}.{2}'.format(hostname, args.port, zeroconf_type),
address=socket.inet_aton(zeroconf_bind_address), port=args.port,
weight=0, priority=0, properties={}, server=hostname)
self.zeroconf.register_service(self.info)
try:
self.zeroconf.register_service(self.info)
except socket.error as e:
logger.error("Error while announcing Glances server: {0}".format(e))
else:
print("Announce the Glances server on the LAN (using {0} IP address)".format(zeroconf_bind_address))
else:
logger.error("Cannot announce Glances server on the network: zeroconf library not found.")

View File

@ -129,7 +129,7 @@ class GlancesClient(object):
logger.debug("Client version: {0} / Server version: {1}".format(__version__, client_version))
else:
self.log_and_exit("Client and server not compatible: \
Client version: {0} / Server version: {1}".format(version, client_version))
Client version: {0} / Server version: {1}".format(__version__, client_version))
return False
else:
@ -151,6 +151,7 @@ class GlancesClient(object):
if ret:
# Load limits from the configuration file
# Each client can choose its owns limits
logger.debug("Load limits from the client configuration file")
self.stats.load_limits(self.config)
# Init screen
@ -180,7 +181,6 @@ class GlancesClient(object):
# Update the stats
try:
server_stats = json.loads(self.client.getAll())
server_stats['monitor'] = json.loads(self.client.getAllMonitored())
except socket.error:
# Client cannot get server stats
return "Disconnected"

View File

@ -23,9 +23,15 @@
import operator
import sys
import unicodedata
PY3 = sys.version_info[0] == 3
def to_ascii(s):
"""Convert the unicode 's' to a ASCII string
Usefull to remove accent (diacritics)"""
return unicodedata.normalize('NFKD', s).encode('ASCII', 'ignore')
if PY3:
import queue
from configparser import ConfigParser, NoOptionError, NoSectionError

View File

@ -21,6 +21,7 @@
import os
import sys
import multiprocessing
from io import open
from glances import __appname__
@ -108,101 +109,108 @@ class Config(object):
# Quicklook
if not self.parser.has_section('quicklook'):
self.parser.add_section('quicklook')
self.parser.set('quicklook', 'cpu_careful', '50')
self.parser.set('quicklook', 'cpu_warning', '70')
self.parser.set('quicklook', 'cpu_critical', '90')
self.parser.set('quicklook', 'mem_careful', '50')
self.parser.set('quicklook', 'mem_warning', '70')
self.parser.set('quicklook', 'mem_critical', '90')
self.parser.set('quicklook', 'swap_careful', '50')
self.parser.set('quicklook', 'swap_warning', '70')
self.parser.set('quicklook', 'swap_critical', '90')
self.set_default('quicklook', 'cpu_careful', '50')
self.set_default('quicklook', 'cpu_warning', '70')
self.set_default('quicklook', 'cpu_critical', '90')
self.set_default('quicklook', 'mem_careful', '50')
self.set_default('quicklook', 'mem_warning', '70')
self.set_default('quicklook', 'mem_critical', '90')
self.set_default('quicklook', 'swap_careful', '50')
self.set_default('quicklook', 'swap_warning', '70')
self.set_default('quicklook', 'swap_critical', '90')
# CPU
if not self.parser.has_section('cpu'):
self.parser.add_section('cpu')
self.parser.set('cpu', 'user_careful', '50')
self.parser.set('cpu', 'user_warning', '70')
self.parser.set('cpu', 'user_critical', '90')
self.parser.set('cpu', 'iowait_careful', '50')
self.parser.set('cpu', 'iowait_warning', '70')
self.parser.set('cpu', 'iowait_critical', '90')
self.parser.set('cpu', 'system_careful', '50')
self.parser.set('cpu', 'system_warning', '70')
self.parser.set('cpu', 'system_critical', '90')
self.parser.set('cpu', 'steal_careful', '50')
self.parser.set('cpu', 'steal_warning', '70')
self.parser.set('cpu', 'steal_critical', '90')
self.set_default('cpu', 'user_careful', '50')
self.set_default('cpu', 'user_warning', '70')
self.set_default('cpu', 'user_critical', '90')
self.set_default('cpu', 'system_careful', '50')
self.set_default('cpu', 'system_warning', '70')
self.set_default('cpu', 'system_critical', '90')
self.set_default('cpu', 'steal_careful', '50')
self.set_default('cpu', 'steal_warning', '70')
self.set_default('cpu', 'steal_critical', '90')
# By default I/O wait should be lower than 1/number of CPU cores
iowait_bottleneck = (1.0 / multiprocessing.cpu_count()) * 100.0
self.set_default('cpu', 'iowait_careful', str(iowait_bottleneck - (iowait_bottleneck * 0.20)))
self.set_default('cpu', 'iowait_warning', str(iowait_bottleneck - (iowait_bottleneck * 0.10)))
self.set_default('cpu', 'iowait_critical', str(iowait_bottleneck))
ctx_switches_bottleneck = 56000 / multiprocessing.cpu_count()
self.set_default('cpu', 'ctx_switches_careful', str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.20)))
self.set_default('cpu', 'ctx_switches_warning', str(ctx_switches_bottleneck - (ctx_switches_bottleneck * 0.10)))
self.set_default('cpu', 'ctx_switches_critical', str(ctx_switches_bottleneck))
# Per-CPU
if not self.parser.has_section('percpu'):
self.parser.add_section('percpu')
self.parser.set('percpu', 'user_careful', '50')
self.parser.set('percpu', 'user_warning', '70')
self.parser.set('percpu', 'user_critical', '90')
self.parser.set('percpu', 'iowait_careful', '50')
self.parser.set('percpu', 'iowait_warning', '70')
self.parser.set('percpu', 'iowait_critical', '90')
self.parser.set('percpu', 'system_careful', '50')
self.parser.set('percpu', 'system_warning', '70')
self.parser.set('percpu', 'system_critical', '90')
self.set_default('percpu', 'user_careful', '50')
self.set_default('percpu', 'user_warning', '70')
self.set_default('percpu', 'user_critical', '90')
self.set_default('percpu', 'system_careful', '50')
self.set_default('percpu', 'system_warning', '70')
self.set_default('percpu', 'system_critical', '90')
# Load
if not self.parser.has_section('load'):
self.parser.add_section('load')
self.parser.set('load', 'careful', '0.7')
self.parser.set('load', 'warning', '1.0')
self.parser.set('load', 'critical', '5.0')
self.set_default('load', 'careful', '0.7')
self.set_default('load', 'warning', '1.0')
self.set_default('load', 'critical', '5.0')
# Mem
if not self.parser.has_section('mem'):
self.parser.add_section('mem')
self.parser.set('mem', 'careful', '50')
self.parser.set('mem', 'warning', '70')
self.parser.set('mem', 'critical', '90')
self.set_default('mem', 'careful', '50')
self.set_default('mem', 'warning', '70')
self.set_default('mem', 'critical', '90')
# Swap
if not self.parser.has_section('memswap'):
self.parser.add_section('memswap')
self.parser.set('memswap', 'careful', '50')
self.parser.set('memswap', 'warning', '70')
self.parser.set('memswap', 'critical', '90')
self.set_default('memswap', 'careful', '50')
self.set_default('memswap', 'warning', '70')
self.set_default('memswap', 'critical', '90')
# FS
if not self.parser.has_section('fs'):
self.parser.add_section('fs')
self.parser.set('fs', 'careful', '50')
self.parser.set('fs', 'warning', '70')
self.parser.set('fs', 'critical', '90')
self.set_default('fs', 'careful', '50')
self.set_default('fs', 'warning', '70')
self.set_default('fs', 'critical', '90')
# Sensors
if not self.parser.has_section('sensors'):
self.parser.add_section('sensors')
self.parser.set('sensors', 'temperature_core_careful', '60')
self.parser.set('sensors', 'temperature_core_warning', '70')
self.parser.set('sensors', 'temperature_core_critical', '80')
self.parser.set('sensors', 'temperature_hdd_careful', '45')
self.parser.set('sensors', 'temperature_hdd_warning', '52')
self.parser.set('sensors', 'temperature_hdd_critical', '60')
self.parser.set('sensors', 'battery_careful', '80')
self.parser.set('sensors', 'battery_warning', '90')
self.parser.set('sensors', 'battery_critical', '95')
self.set_default('sensors', 'temperature_core_careful', '60')
self.set_default('sensors', 'temperature_core_warning', '70')
self.set_default('sensors', 'temperature_core_critical', '80')
self.set_default('sensors', 'temperature_hdd_careful', '45')
self.set_default('sensors', 'temperature_hdd_warning', '52')
self.set_default('sensors', 'temperature_hdd_critical', '60')
self.set_default('sensors', 'battery_careful', '80')
self.set_default('sensors', 'battery_warning', '90')
self.set_default('sensors', 'battery_critical', '95')
# Process list
if not self.parser.has_section('processlist'):
self.parser.add_section('processlist')
self.parser.set('processlist', 'cpu_careful', '50')
self.parser.set('processlist', 'cpu_warning', '70')
self.parser.set('processlist', 'cpu_critical', '90')
self.parser.set('processlist', 'mem_careful', '50')
self.parser.set('processlist', 'mem_warning', '70')
self.parser.set('processlist', 'mem_critical', '90')
self.set_default('processlist', 'cpu_careful', '50')
self.set_default('processlist', 'cpu_warning', '70')
self.set_default('processlist', 'cpu_critical', '90')
self.set_default('processlist', 'mem_careful', '50')
self.set_default('processlist', 'mem_warning', '70')
self.set_default('processlist', 'mem_critical', '90')
@property
def loaded_config_file(self):
"""Return the loaded configuration file."""
return self._loaded_config_file
def sections(self):
"""Return a list of all sections."""
return self.parser.sections()
def items(self, section):
"""Return the items list of a section."""
return self.parser.items(section)
@ -211,6 +219,11 @@ class Config(object):
"""Return info about the existence of a section."""
return self.parser.has_section(section)
def set_default(self, section, option, default):
"""If the option did not exist, create a default value."""
if not self.parser.has_option(section, option):
self.parser.set(section, option, default)
def get_value(self, section, option, default=None):
"""Get the value of an option, if it exists."""
try:

View File

@ -34,9 +34,11 @@ 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))
# Set the plugins and export modules path
# Set the AMPs, plugins and export modules path
amps_path = os.path.realpath(os.path.join(work_path, 'amps'))
plugins_path = os.path.realpath(os.path.join(work_path, 'plugins'))
exports_path = os.path.realpath(os.path.join(work_path, 'exports'))
sys_path = sys.path[:]
sys.path.insert(1, exports_path)
sys.path.insert(1, plugins_path)
sys.path.insert(1, amps_path)

View File

@ -138,6 +138,8 @@ Start the client browser (browser mode):\n\
help='disable network, disk I/O, FS and sensors modules')
parser.add_argument('--disable-process', action='store_true', default=False,
dest='disable_process', help='disable process module')
parser.add_argument('--disable-amps', action='store_true', default=False,
dest='disable_amps', help='disable applications monitoring process (AMP) module')
parser.add_argument('--disable-log', action='store_true', default=False,
dest='disable_log', help='disable log module')
parser.add_argument('--disable-bold', action='store_true', default=False,

View File

@ -1,200 +0,0 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2015 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/>.
"""Manage the monitor list."""
import re
import subprocess
from glances.compat import range, u
from glances.logger import logger
from glances.processes import glances_processes
class MonitorList(object):
"""This class describes the optional monitored processes list.
The monitored list is 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 it exists."""
self.config = config
if self.config is not None and self.config.has_section('monitor'):
# Process monitoring list
logger.debug("Monitor list configuration detected")
self.__set_monitor_list('monitor', 'list')
else:
self.__monitor_list = []
def __set_monitor_list(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 = self.config.get_value(section, key + 'description')
regex = self.config.get_value(section, key + 'regex')
command = self.config.get_value(section, key + 'command')
countmin = self.config.get_value(section, key + 'countmin')
countmax = self.config.get_value(section, key + 'countmax')
except Exception as e:
logger.error("Cannot read monitored list: {0}".format(e))
else:
if description is not None and regex is not None:
# Build the new item
value["description"] = description
try:
re.compile(regex)
except Exception:
continue
else:
value["regex"] = regex
value["command"] = command
value["countmin"] = countmin
value["countmax"] = countmax
value["count"] = None
value["result"] = None
# 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.
Return 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 update(self):
"""Update the command result attributed."""
# Only continue if monitor list is not empty
if len(self.__monitor_list) == 0:
return self.__monitor_list
# Search monitored processes by a regular expression
processlist = [p for p in glances_processes.getalllist()]
# Iter upon the monitored list
for i in range(len(self.get())):
monitoredlist = [p for p in processlist for c in p['cmdline'] if re.search(self.regex(i), c) is not None]
self.__monitor_list[i]['count'] = len(monitoredlist)
# Always get processes CPU and MEM
self.__monitor_list[i]['default_result'] = 'CPU: {0:.1f}% | MEM: {1:.1f}%'.format(
sum([p['cpu_percent'] for p in monitoredlist]),
sum([p['memory_percent'] for p in monitoredlist]))
if self.command(i) is not None:
# Execute the user command line
try:
self.__monitor_list[i]['result'] = subprocess.check_output(self.command(i),
shell=True)
except subprocess.CalledProcessError:
self.__monitor_list[i]['result'] = 'Error: ' + self.command(i)
except Exception:
self.__monitor_list[i]['result'] = 'Cannot execute command'
# Only save the first line
try:
self.__monitor_list[i]['result'] = u(self.__monitor_list[i]['result']).split('\n')[0]
except:
self.__monitor_list[i]['result'] = ''
if self.command(i) is None or self.__monitor_list[i]['result'] == '':
# If there is no command specified in the conf file
# then display CPU and MEM %
self.__monitor_list[i]['result'] = self.__monitor_list[i]['default_result']
return self.__monitor_list
def get(self):
"""Return the monitored list (list of dict)."""
return self.__monitor_list
def set(self, newlist):
"""Set the monitored list (list of dict)."""
self.__monitor_list = newlist
def getAll(self):
# Deprecated: use get()
return self.get()
def setAll(self, newlist):
# Deprecated: use set()
self.set(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 stat command of the item number (item)."""
return self.__get__(item, "command")
def result(self, item):
"""Return the reult command of the item number (item)."""
return self.__get__(item, "result")
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")

View File

@ -328,6 +328,9 @@ class _GlancesCurses(object):
# 'a' > Sort processes automatically and reset to 'cpu_percent'
glances_processes.auto_sort = True
glances_processes.sort_key = 'cpu_percent'
elif self.pressedkey == ord('A'):
# 'A' > enable/disable AMP module
self.args.disable_amps = not self.args.disable_amps
elif self.pressedkey == ord('b'):
# 'b' > Switch between bit/s and Byte/s for network IO
self.args.byte = not self.args.byte
@ -422,7 +425,7 @@ class _GlancesCurses(object):
# 'x' > Delete finished warning and critical logs
glances_logs.clean(critical=True)
elif self.pressedkey == ord('z'):
# 'z' > Enable/Disable processes stats (count + list + monitor)
# 'z' > Enable/Disable processes stats (count + list + AMPs)
# Enable/Disable display
self.args.disable_process = not self.args.disable_process
# Enable/Disable update
@ -533,8 +536,8 @@ class _GlancesCurses(object):
args=self.args)
stats_processcount = stats.get_plugin(
'processcount').get_stats_display(args=self.args)
stats_monitor = stats.get_plugin(
'monitor').get_stats_display(args=self.args)
stats_amps = stats.get_plugin(
'amps').get_stats_display(args=self.args)
stats_alert = stats.get_plugin(
'alert').get_stats_display(args=self.args)
@ -717,16 +720,14 @@ class _GlancesCurses(object):
self.next_line = self.saved_line
# Display right sidebar
# ((DOCKER)+PROCESS_COUNT+(MONITORED)+PROCESS_LIST+ALERT)
# DOCKER+PROCESS_COUNT+AMPS+PROCESS_LIST+ALERT
self.new_column()
self.new_line()
self.display_plugin(stats_docker)
self.new_line()
self.display_plugin(stats_processcount)
if glances_processes.process_filter is None and cs_status is None:
# Do not display stats monitor list if a filter exist
self.new_line()
self.display_plugin(stats_monitor)
self.new_line()
self.display_plugin(stats_amps)
self.new_line()
self.display_plugin(stats_processlist,
display_optional=(screen_x > 102),

View File

@ -133,7 +133,7 @@ body {
width: 100%;
text-overflow: ellipsis;
}
#monitor .process-result {
#amps .process-result {
max-width: 300px;
overflow: hidden;
white-space: nowrap;

View File

@ -31,7 +31,7 @@
<script type="text/javascript" src="services/plugins/glances_load.js"></script>
<script type="text/javascript" src="services/plugins/glances_mem.js"></script>
<script type="text/javascript" src="services/plugins/glances_memswap.js"></script>
<script type="text/javascript" src="services/plugins/glances_monitor.js"></script>
<script type="text/javascript" src="services/plugins/glances_amps.js"></script>
<script type="text/javascript" src="services/plugins/glances_network.js"></script>
<script type="text/javascript" src="services/plugins/glances_percpu.js"></script>
<script type="text/javascript" src="services/plugins/glances_processcount.js"></script>

View File

@ -0,0 +1,7 @@
<div class="table">
<div class="table-row" ng-repeat="process in statsAmps.processes">
<div class="table-cell" ng-class="statsAmps.getDescriptionDecoration(process)">{{ process.name }}</div>
<div class="table-cell">{{ process.count }}</div>
<div class="table-cell process-result">{{ process.result }}</div>
</div>
</div>

View File

@ -1,8 +0,0 @@
<div class="table">
<div class="table-row" ng-repeat="process in statsMonitor.processes">
<div class="table-cell" ng-class="statsMonitor.getDescriptionDecoration(process)">{{ process.description }}</div>
<div class="table-cell">{{ process.count > 1 ? process.count : '' }}</div>
<div class="table-cell">{{ process.count > 0 ? 'RUNNING' : 'NOT RUNNING' }}</div>
<div class="table-cell process-result">{{ process.result }}</div>
</div>
</div>

View File

@ -59,9 +59,9 @@
<section id="alert" class="plugin" ng-show="!arguments.disable_log" ng-include src="'plugins/alert.html'"></section>
<div ng-show="!arguments.disable_process">
<section id="processcount" class="plugin" ng-include src="'plugins/processcount.html'"></section>
<div class="row">
<div class="row" ng-if="!arguments.disable_amps">
<div class="col-lg-18">
<section id="monitor" class="plugin" ng-include src="'plugins/monitor.html'"></section>
<section id="amps" class="plugin" ng-include src="'plugins/amps.html'"></section>
</div>
</div>
<section id="processlist" class="plugin" ng-include src="'plugins/processlist.html'"></section>

View File

@ -12,7 +12,7 @@ glancesApp.service('GlancesStats', function($http, $injector, $q, GlancesPlugin)
'load': 'GlancesPluginLoad',
'mem': 'GlancesPluginMem',
'memswap': 'GlancesPluginMemSwap',
'monitor': 'GlancesPluginMonitor',
'amps': 'GlancesPluginAmps',
'network': 'GlancesPluginNetwork',
'percpu': 'GlancesPluginPerCpu',
'processcount': 'GlancesPluginProcessCount',

View File

@ -1,5 +1,5 @@
glancesApp.service('GlancesPluginMonitor', function() {
var _pluginName = "monitor";
glancesApp.service('GlancesPluginAmps', function() {
var _pluginName = "amps";
this.processes = [];
this.setData = function(data, views) {

View File

@ -31,7 +31,7 @@ glancesApp.controller('statsController', function ($scope, $rootScope, $interval
$scope.statsLoad = GlancesStats.getPlugin('load');
$scope.statsMem = GlancesStats.getPlugin('mem');
$scope.statsMemSwap = GlancesStats.getPlugin('memswap');
$scope.statsMonitor = GlancesStats.getPlugin('monitor');
$scope.statsAmps = GlancesStats.getPlugin('amps');
$scope.statsNetwork = GlancesStats.getPlugin('network');
$scope.statsPerCpu = GlancesStats.getPlugin('percpu');
$scope.statsProcessCount = GlancesStats.getPlugin('processcount');
@ -64,6 +64,10 @@ glancesApp.controller('statsController', function ($scope, $rootScope, $interval
$scope.sorter.column = "cpu_percent";
$scope.sorter.auto = true;
break;
case $event.shiftKey && $event.keyCode == keycodes.A:
// D => Enable/disable AMPs
$scope.arguments.disable_amps = !$scope.arguments.disable_amps;
break;
case !$event.shiftKey && $event.keyCode == keycodes.c:
// c => Sort processes by CPU%
$scope.sorter.column = "cpu_percent";

View File

@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 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/>.
"""Monitor plugin."""
from glances.compat import iteritems
from glances.amps_list import AmpsList as glancesAmpsList
from glances.logger import logger
from glances.plugins.glances_plugin import GlancesPlugin
class Plugin(GlancesPlugin):
"""Glances AMPs plugin."""
def __init__(self, args=None, config=None):
"""Init the plugin."""
super(Plugin, self).__init__(args=args)
self.args = args
self.config = config
# We want to display the stat in the curse interface
self.display_curse = True
# Init the list of AMP (classe define in the glances/amps_list.py script)
self.glances_amps = glancesAmpsList(self.args, self.config)
# Init stats
self.reset()
def reset(self):
"""Reset/init the stats."""
self.stats = []
@GlancesPlugin._log_result_decorator
def update(self):
"""Update the AMP list."""
# Reset stats
self.reset()
if self.input_method == 'local':
for k, v in iteritems(self.glances_amps.update()):
# self.stats.append({k: v.result()})
self.stats.append({'key': k,
'name': v.NAME,
'result': v.result(),
'refresh': v.refresh(),
'timer': v.time_until_refresh(),
'count': v.count(),
'countmin': v.count_min(),
'countmax': v.count_max(),
})
else:
# Not available in SNMP mode
pass
return self.stats
def get_alert(self, nbprocess=0, countmin=None, countmax=None, header="", log=False):
"""Return the alert status relative to the process number."""
if nbprocess is None:
return 'OK'
if countmin is None:
countmin = nbprocess
if countmax is None:
countmax = nbprocess
if nbprocess > 0:
if int(countmin) <= int(nbprocess) <= int(countmax):
return 'OK'
else:
return 'WARNING'
else:
if int(countmin) == 0:
return 'OK'
else:
return 'CRITICAL'
def msg_curse(self, args=None):
"""Return the dict to display in the curse interface."""
# Init the return message
# Only process if stats exist and display plugin enable...
ret = []
if not self.stats or args.disable_process or args.disable_amps:
return ret
# Build the string message
for m in self.stats:
# Only display AMP if a result exist
if m['result'] is None:
continue
# Display AMP
first_column = '{0}'.format(m['name'])
first_column_style = self.get_alert(m['count'], m['countmin'], m['countmax'])
second_column = '{0}'.format(m['count'])
for l in m['result'].split('\n'):
# Display first column with the process name...
msg = '{0:<16} '.format(first_column)
ret.append(self.curse_add_line(msg, first_column_style))
# ... and second column with the number of matching processes...
msg = '{0:<4} '.format(second_column)
ret.append(self.curse_add_line(msg))
# ... only on the first line
first_column = second_column = ''
# Display AMP result in the third column
ret.append(self.curse_add_line(l, splittable=True))
ret.append(self.curse_new_line())
# Delete the last empty line
try:
ret.pop()
except IndexError:
pass
return ret

View File

@ -143,7 +143,7 @@ class GlancesGrabHDDTemp(object):
sck.connect((self.host, self.port))
data = sck.recv(4096)
except socket.error as e:
logger.warning("Can not connect to an HDDtemp server ({0}:{1} => {2})".format(self.host, self.port, e))
logger.debug("Can not connect to an HDDtemp server ({0}:{1} => {2})".format(self.host, self.port, e))
logger.debug("Disable the HDDtemp module. Use the --disable-hddtemp to hide the previous message.")
self.args.disable_hddtemp = True
data = ""

View File

@ -138,11 +138,15 @@ class Plugin(GlancesPlugin):
# VPN with no internet access (issue #842)
msg = '/{0}'.format(self.stats['mask_cidr'])
ret.append(self.curse_add_line(msg))
if self.stats['public_address'] is not None:
msg = ' Pub '
ret.append(self.curse_add_line(msg, 'TITLE'))
msg = '{0:}'.format(self.stats['public_address'])
ret.append(self.curse_add_line(msg))
try:
msg_pub = '{0:}'.format(self.stats['public_address'])
except UnicodeEncodeError:
pass
else:
if self.stats['public_address'] is not None:
msg = ' Pub '
ret.append(self.curse_add_line(msg, 'TITLE'))
ret.append(self.curse_add_line(msg_pub))
return ret
@ -186,7 +190,10 @@ class PublicIpAddress(object):
queue_target.put(None)
else:
# Request depend on service
if not json:
queue_target.put(response)
else:
queue_target.put(loads(response)[key])
try:
if not json:
queue_target.put(response)
else:
queue_target.put(loads(response)[key])
except ValueError:
queue_target.put(None)

View File

@ -1,116 +0,0 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2015 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/>.
"""Monitor plugin."""
from glances.compat import u
from glances.monitor_list import MonitorList as glancesMonitorList
from glances.plugins.glances_plugin import GlancesPlugin
class Plugin(GlancesPlugin):
"""Glances monitor plugin."""
def __init__(self, args=None):
"""Init the plugin."""
super(Plugin, self).__init__(args=args)
# We want to display the stat in the curse interface
self.display_curse = True
# Init stats
self.glances_monitors = None
self.stats = []
def load_limits(self, config):
"""Load the monitored list from the config file, if it exists."""
self.glances_monitors = glancesMonitorList(config)
def update(self):
"""Update the monitored list."""
if self.input_method == 'local':
# Monitor list only available in a full Glances environment
# Check if the glances_monitor instance is init
if self.glances_monitors is None:
return self.stats
# Update the monitored list (result of command)
self.glances_monitors.update()
# Put it on the stats var
self.stats = self.glances_monitors.get()
else:
pass
return self.stats
def get_alert(self, nbprocess=0, countmin=None, countmax=None, header="", log=False):
"""Return the alert status relative to the process number."""
if nbprocess is None:
return 'OK'
if countmin is None:
countmin = nbprocess
if countmax is None:
countmax = nbprocess
if nbprocess > 0:
if int(countmin) <= int(nbprocess) <= int(countmax):
return 'OK'
else:
return 'WARNING'
else:
if int(countmin) == 0:
return 'OK'
else:
return 'CRITICAL'
def msg_curse(self, args=None):
"""Return the dict to display in the curse interface."""
# Init the return message
ret = []
# Only process if stats exist and display plugin enable...
if not self.stats or args.disable_process:
return ret
# Build the string message
for m in self.stats:
msg = '{0:<16} '.format(m['description'])
ret.append(self.curse_add_line(
msg, self.get_alert(m['count'], m['countmin'], m['countmax'])))
msg = '{0:<3} '.format(m['count'] if m['count'] > 1 else '')
ret.append(self.curse_add_line(msg))
msg = '{0:13} '.format('RUNNING' if m['count'] >= 1 else 'NOT RUNNING')
ret.append(self.curse_add_line(msg))
# Decode to UTF-8 (for Python 2)
try:
msg = u(m['result']) if m['count'] >= 1 else ''
except UnicodeEncodeError:
# Hack if return message contains non UTF-8 compliant char
msg = u(m['default_result']) if m['count'] >= 1 else ''
ret.append(self.curse_add_line(msg, optional=True, splittable=True))
ret.append(self.curse_new_line())
# Delete the last empty line
try:
ret.pop()
except IndexError:
pass
return ret

View File

@ -403,8 +403,6 @@ class GlancesPlugin(object):
except KeyError:
return 'DEFAULT'
logger.debug("{0} => ret = {1}".format(stat_name, ret))
# Manage log
log_str = ""
if self.__get_limit_log(stat_name=stat_name, default_action=log):

View File

@ -510,8 +510,8 @@ class GlancesProcesses(object):
# Next...
first = False
# Build the all processes list used by the monitored list
self.allprocesslist = itervalues(processdict)
# Build the all processes list used by the AMPs
self.allprocesslist = [p for p in itervalues(processdict)]
# Clean internals caches if timeout is reached
if self.cache_timer.finished():

View File

@ -156,11 +156,6 @@ class GlancesInstance(object):
# Return all the plugins views
return json.dumps(self.stats.getAllViewsAsDict())
def getAllMonitored(self):
# Return the processes monitored list
# return json.dumps(self.monitors.getAll())
return json.dumps(self.stats.getAll()['monitor'])
def __getattr__(self, item):
"""Overwrite the getattr method in case of attribute is not found.

View File

@ -33,16 +33,16 @@ class GlancesStats(object):
"""This class stores, updates and gives stats."""
def __init__(self, config=None, args=None):
# Set the argument instance
self.args = args
# Set the config instance
self.config = config
# Load plugins and export modules
self.load_plugins_and_exports(self.args)
# Set the argument instance
self.args = args
# Load the limits
# Load plugins and exports modules
self.load_modules(self.args)
# Load the limits (for plugins)
self.load_limits(config)
def __getattr__(self, item):
@ -67,8 +67,9 @@ class GlancesStats(object):
# Default behavior
raise AttributeError(item)
def load_plugins_and_exports(self, args):
"""Wrapper to load both plugins and export modules."""
def load_modules(self, args):
"""Wrapper to load: plugins and export modules."""
# Init the plugins dict
self._plugins = collections.defaultdict(dict)
# Load the plugins
@ -96,7 +97,7 @@ class GlancesStats(object):
# for example, the file glances_xxx.py
# generate self._plugins_list["xxx"] = ...
plugin_name = os.path.basename(item)[len(header):-3].lower()
if plugin_name == 'help':
if plugin_name in ('help', 'amps'):
self._plugins[plugin_name] = plugin.Plugin(args=args, config=self.config)
else:
self._plugins[plugin_name] = plugin.Plugin(args=args)
@ -136,13 +137,12 @@ class GlancesStats(object):
def getExportList(self):
"""Return the exports modules list."""
return [p for p in self._exports]
return [e for e in self._exports]
def load_limits(self, config=None):
"""Load the stats limits."""
# For each plugins, call the init_limits method
"""Load the stats limits (except the one in the exclude list)."""
# For each plugins, call the load_limits method
for p in self._plugins:
# logger.debug("Load limits for %s" % p)
self._plugins[p].load_limits(config)
def update(self):

View File

@ -32,7 +32,7 @@ class GlancesStatsClient(GlancesStats):
def __init__(self, config=None, args=None):
"""Init the GlancesStatsClient class."""
super(GlancesStatsClient, self).__init__()
super(GlancesStatsClient, self).__init__(config=config, args=args)
# Init the configuration
self.config = config
@ -40,9 +40,6 @@ class GlancesStatsClient(GlancesStats):
# Init the arguments
self.args = args
# Load plugins and exports
self.load_plugins_and_exports(self.args)
def set_plugins(self, input_plugins):
"""Set the plugin list according to the Glances server."""
header = "glances_"

View File

@ -51,8 +51,8 @@ class GlancesStatsClientSNMP(GlancesStats):
# OS name is used because OID is differents between system
self.os_name = None
# Load plugins and export modules
self.load_plugins_and_exports(self.args)
# Load AMPs, plugins and exports modules
self.load_modules(self.args)
def check_snmp(self):
"""Chek if SNMP is available on the server."""

View File

@ -30,7 +30,7 @@ class GlancesStatsServer(GlancesStats):
def __init__(self, config=None):
# Init the stats
super(GlancesStatsServer, self).__init__(config)
super(GlancesStatsServer, self).__init__(config=config)
# Init the all_stats dict used by the server
# all_stats is a dict of dicts filled by the server

View File

@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
# Copyright (C) 2016 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
@ -53,6 +53,9 @@ class Timer(object):
def reset(self):
self.start()
def get(self):
return self.duration - (self.target - time())
def set(self, duration):
self.duration = duration

View File

@ -93,8 +93,8 @@ class TestGlances(unittest.TestCase):
self.assertTrue(req.ok)
if p in ('uptime', 'now'):
self.assertIsInstance(req.json(), text_type)
elif p in ('fs', 'monitor', 'percpu', 'sensors', 'alert', 'processlist',
'diskio', 'hddtemp', 'batpercent', 'network', 'folders'):
elif p in ('fs', 'percpu', 'sensors', 'alert', 'processlist', 'diskio',
'hddtemp', 'batpercent', 'network', 'folders', 'amps'):
self.assertIsInstance(req.json(), list)
elif p in ('psutilversion', 'help'):
pass