First run for the Nginx AMP process

pull/863/head
nicolargo 2016-04-14 19:40:36 +02:00
parent b5e59d3e3a
commit 8efb5e2ac8
12 changed files with 407 additions and 23 deletions

View File

@ -155,7 +155,7 @@ mem_careful=50
mem_warning=70
mem_critical=90
#[monitor]
[monitor]
# Define the list of processes to monitor
# *** This section is optional ***
# The list is composed of items (list_#nb <= 10)
@ -181,7 +181,7 @@ mem_critical=90
#list_3_regex=.*xeyes.*
#list_3_countmin=1
#[serverlist]
[serverlist]
# Define the static servers list
#server_1_name=localhost
#server_1_alias=My local PC
@ -194,7 +194,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
@ -250,3 +250,13 @@ user=guest
password=guest
queue=glances_queue
######
# AMPS
######
[nginx]
# Nginx status page should be enable (https://easyengine.io/tutorials/nginx/status-page/)
enable=true
regex=\/usr\/sbin\/nginx
refresh=60
status_url=http://localhost/nginx_status

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

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

@ -0,0 +1,116 @@
# -*- 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).
"""
from glances.compat import u
from glances.logger import logger
class GlancesAmp(object):
"""Main class for Glances AMP."""
def __init__(self, args=None):
"""Init AMP classe."""
# AMP name (= module name without glances_)
self.amp_name = self.__class__.__module__[len('glances_'):]
# Init the args
self.args = args
# Init the configs
self.configs = {}
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:
#
# option1=opt1
#
if (hasattr(config, 'has_section') and
config.has_section(self.amp_name)):
logger.debug("AMP: Load {0} configuration".format(self.amp_name))
for param, _ in config.items(self.amp_name):
try:
self.configs[param] = config.get_float_value(self.amp_name, param)
except ValueError:
self.configs[param] = config.get_value(self.amp_name, param).split(',')
if len(self.configs[param]) == 1:
self.configs[param] = self.configs[param][0]
logger.debug("AMP: Load {0} parameter: {1} = {2}".format(self.amp_name, param, self.configs[param]))
else:
logger.warning("AMP: Can not find section {0} in the configuration file".format(self.amp_name))
# enable, regex and refresh are mandatories
# if not configured then AMP is disabled
for k in ['enable', 'regex', 'refresh']:
if k not in self.configs:
logger.warning("AMP: Can not find configuration key {0} in section {1}".format(k, self.amp_name))
self.configs['enable'] = 'false'
if not self.enable():
logger.warning("AMP: {0} is disabled".format(self.amp_name))
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)."""
return self.get('enable').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 should_update(self):
"""Return True is the AMP should be updated:
- AMP is enable
- only update every 'refresh' seconds
"""
return True
def set_result(self, result):
"""Store the result (string) into the result key of the AMP"""
self.configs['result'] = str(result)
def result(self):
""" Return the result of the AMP (as a string)"""
return u(self.get('result'))

View File

@ -0,0 +1,48 @@
# -*- 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."""
import requests
from glances.logger import logger
from glances.amps.glances_amp import GlancesAmp
class Amp(GlancesAmp):
"""Glances' Nginx AMP."""
def __init__(self, args=None):
"""Init the AMP."""
super(Amp, self).__init__(args=args)
def update(self):
"""Update the AMP"""
if self.should_update():
logger.debug('AMPS: Update {0} using status URL {1}'.format(self.amp_name, self.get('status_url')))
req = requests.get(self.get('status_url'))
if req.ok:
# u'Active connections: 1 \nserver accepts handled requests\n 1 1 1 \nReading: 0 Writing: 1 Waiting: 0 \n'
self.set_result(req.text)
else:
logger.debug('AMPS: Can not grab status URL {0} ({1})'.format(self.get('status_url'), req.reason))
return self.result()

117
glances/amps_list.py Normal file
View File

@ -0,0 +1,117 @@
# -*- 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 subprocess
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 process monitoring 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
# Create the AMPS list
self.load_amps()
self.load_configs()
def load_amps(self):
"""Load all amps in the 'amps' folder."""
header = "glances_"
for item in os.listdir(amps_path):
if (item.startswith(header) and
item.endswith(".py") and
item != (header + "amp.py")):
# Import the amp
amp = __import__(os.path.basename(item)[:-3])
# Add the AMP to the dictionary
# The key is the AMP name
# for example, the file glances_xxx.py
# generate self._amps_list["xxx"] = ...
amp_name = os.path.basename(item)[len(header):-3].lower()
self.__amps_dict[amp_name] = amp.Amp(self.args)
# Log AMPs list
logger.debug("Available AMPs list: {0}".format(self.getList()))
def load_configs(self):
"""Load the AMP configuration files."""
# For each AMPs, call the load_config method
for a in self.get():
self.get()[a].load_config(self.config)
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 = [p for p in glances_processes.getalllist()]
# Iter upon the AMPs dict
for k, v in iteritems(self.get()):
amps_list = [p for p in processlist for c in p['cmdline'] if re.search(v.regex(), c) is not None]
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
v.update()
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

@ -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

@ -535,6 +535,8 @@ class _GlancesCurses(object):
'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,12 +719,14 @@ class _GlancesCurses(object):
self.next_line = self.saved_line
# Display right sidebar
# ((DOCKER)+PROCESS_COUNT+(MONITORED)+PROCESS_LIST+ALERT)
# ((DOCKER)+PROCESS_COUNT+(MONITORED)+(AMPS)+PROCESS_LIST+ALERT)
self.new_column()
self.new_line()
self.display_plugin(stats_docker)
self.new_line()
self.display_plugin(stats_processcount)
self.new_line()
self.display_plugin(stats_amps)
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()

View File

@ -0,0 +1,89 @@
# -*- 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.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':
# TODO
for k, v in iteritems(self.glances_amps.update()):
self.stats.append({k: v.result()})
else:
pass
return self.stats
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:
return ret
# Build the string message
for m in self.stats:
for k, v in iteritems(m):
msg = '{0:<16} '.format(k)
ret.append(self.curse_add_line(msg))
msg = '{0}'.format(v.replace('\n', ''))
ret.append(self.curse_add_line(msg, 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

@ -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
# 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

@ -40,8 +40,8 @@ class GlancesStatsClient(GlancesStats):
# Init the arguments
self.args = args
# Load plugins and exports
self.load_plugins_and_exports(self.args)
# Load AMPs, plugins and exports modules
self.load_modules(self.args)
def set_plugins(self, input_plugins):
"""Set the plugin list according to the Glances server."""

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."""