mirror of https://github.com/nicolargo/glances.git
Merge branch 'issue780' into develop
This commit is contained in:
commit
aafb5060bc
6
NEWS
6
NEWS
|
|
@ -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
|
||||
=============
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
|
|
@ -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.
|
||||
|
|
@ -32,6 +32,7 @@ Legend:
|
|||
sensors
|
||||
ps
|
||||
monitor
|
||||
amps
|
||||
logs
|
||||
docker
|
||||
actions
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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,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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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 doesn’t 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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ body {
|
|||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#monitor .process-result {
|
||||
#amps .process-result {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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_"
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue