diff options
author | nicolargo <nicolashennion@gmail.com> | 2016-03-26 17:46:57 +0100 |
---|---|---|
committer | nicolargo <nicolashennion@gmail.com> | 2016-03-26 17:46:57 +0100 |
commit | 0bb34a8d7f8bb48b77a7071deb89d59edcbe8f07 (patch) | |
tree | 440583644ed9970d67596b447f34fb42868b87ce | |
parent | ff30e5c6cfb49ddd244ceab37d009186e170ee8c (diff) | |
parent | 11659ac002308f6fe5774b3ea1289156fc27afb2 (diff) |
Merge branch 'develop'v2.6.1
35 files changed, 993 insertions, 698 deletions
@@ -48,3 +48,6 @@ Frederic Aoustin (https://github.com/fraoustin) and Nicolas Bourges (installer) Aljaž Srebrnič for the MacPorts package http://www.macports.org/ports.php?by=name&substr=glances + +John Kirkham for the conda package (at conda-forge) +https://github.com/conda-forge/glances-feedstock @@ -2,6 +2,19 @@ Glances Version 2 ============================================================================== +Version 2.6.1 +============= + +Enhancements and new features: + + * Add a connector to Riemann (issue #822 by Greogo Nagy) + +Bugs corrected: + + * Browsing for servers which are in the [serverlist] is broken (issue #819) + * [WebUI] Glances will not get past loading screen (issue #815) opened 9 days ago + * Python error after upgrading from 2.5.1 to 2.6 bug (issue #813) + Version 2.6 =========== @@ -34,6 +47,7 @@ Enhancements and new features: * InfluxDB > 0.9.3 needs float and not int for numerical value (issue#749 and issue#750 by nicolargo) Bugs corrected: + * Can't read sensors on a Thinkpad (issue #711) * InfluxDB/OpenTSDB: tag parsing broken (issue #713) * Grafana Dashboard outdated for InfluxDB 0.9.x (issue #648) @@ -50,6 +50,7 @@ Optional dependencies: - ``docker-py`` (for the Docker monitoring support) [Linux-only] - ``matplotlib`` (for graphical/chart support) - ``pika`` (for the RabbitMQ/ActiveMQ export module) +- ``bernhard`` (for the Riemann export module) - ``py-cpuinfo`` (for the Quicklook CPU info module) - ``scandir`` (for the Folders plugin) [Only for Python < 3.5] @@ -223,6 +224,26 @@ Puppet You can install Glances using ``Puppet``: https://github.com/rverchere/puppet-glances +Known issue on RHEL/CentOS/Fedora installation +============================================== + +For Python 2.6 RedHat-based distros there might be an issue with starting Glances: + + Traceback (most recent call last): + File "/usr/bin/glances", line 5, in <module> + from pkg_resources import load_entry_point + File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 2655, in <module> + workingset.require(_requires) + File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 648, in require + needed = self.resolve(parse_requirements(requirements)) + File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 546, in resolve + raise DistributionNotFound(req) + pkg_resources.DistributionNotFound: argparse + +Try upgrading setuptools, has been proven to solve the problem: + + sudo pip install -U setuptools + Usage ===== @@ -274,7 +295,7 @@ Gateway to other services ========================= Glances can export stats to: ``CSV`` file, ``InfluxDB``, ``OpenTSDB``, -``StatsD`` and ``RabbitMQ`` server. +``StatsD``, ``RabbitMQ`` and ``Riemann`` server. How to contribute ? =================== diff --git a/conf/glances.conf b/conf/glances.conf index c486614f..8c8cfe0e 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -198,7 +198,7 @@ mem_critical=90 #default=defaultpassword [influxdb] -# Configuration file for the --export-influxdb option +# Configuration for the --export-influxdb option # https://influxdb.com/ host=localhost port=8086 @@ -209,7 +209,7 @@ prefix=localhost #tags=foo:bar,spam:eggs [opentsdb] -# Configuration file for the --export-opentsdb option +# Configuration for the --export-opentsdb option # http://opentsdb.net/ host=localhost port=4242 @@ -217,23 +217,30 @@ port=4242 #tags=foo:bar,spam:eggs [statsd] -# Configuration file for the --export-statsd option +# Configuration file the --export-statsd option # https://github.com/etsy/statsd host=localhost port=8125 #prefix=glances [elasticsearch] -# Configuration file for the --export-elasticsearch option +# Configuration file the --export-elasticsearch option # Data are available via the ES Restful API. ex: URL/<index>/cpu/system # https://www.elastic.co host=localhost port=9200 index=glances +[riemann] +# Configuration file for the --export-riemann option +# http://riemann.io +host=localhost +port=5555 + [rabbitmq] host=localhost port=5672 user=guest password=guest queue=glances_queue + diff --git a/docs/cmds.rst b/docs/cmds.rst index b473f066..cfd51115 100644 --- a/docs/cmds.rst +++ b/docs/cmds.rst @@ -139,6 +139,10 @@ Command-Line Options export stats to RabbitMQ broker (pika lib needed) +.. option:: --export-riemann + + export stats to Riemann server (bernhard lib needed) + .. option:: --export-elasticsearch export stats to an Elasticsearch server (elasticsearch lib needed) diff --git a/docs/gw/riemann.rst b/docs/gw/riemann.rst new file mode 100644 index 00000000..cff4f551 --- /dev/null +++ b/docs/gw/riemann.rst @@ -0,0 +1,20 @@ +.. _riemann: + +Riemann +======== + +You can export statistics to an ``Riemann`` server (using TCP protocol). The +connection should be defined in the Glances configuration file as +following: + +.. code-block:: ini + + [riemann] + host=localhost + port=5555 + +and run Glances with: + +.. code-block:: console + + $ glances --export-riemann diff --git a/docs/man/glances.1 b/docs/man/glances.1 index 0018d8a1..e1adc7bd 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GLANCES" "1" "March 16, 2016" "2.6" "Glances" +.TH "GLANCES" "1" "March 26, 2016" "2.6.1" "Glances" .SH NAME glances \- An eye on your system . @@ -214,6 +214,11 @@ export stats to RabbitMQ broker (pika lib needed) .UNINDENT .INDENT 0.0 .TP +.B \-\-export\-riemann +export stats to Riemann server (bernhard lib needed) +.UNINDENT +.INDENT 0.0 +.TP .B \-\-export\-elasticsearch export stats to an Elasticsearch server (elasticsearch lib needed) .UNINDENT diff --git a/glances/__init__.py b/glances/__init__.py index c937e820..e381f305 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -27,13 +27,13 @@ import sys # Global name __appname__ = 'glances' -__version__ = '2.6' +__version__ = '2.6.1' __author__ = 'Nicolas Hennion <nicolas@nicolargo.com>' __license__ = 'LGPL' # Import psutil try: - from psutil import __version__ as __psutil_version + from psutil import __version__ as psutil_version except ImportError: print('PSutil library not found. Glances cannot start.') sys.exit(1) @@ -62,8 +62,8 @@ if sys.version_info[:2] == (2, 6): # Check PSutil version psutil_min_version = (2, 0, 0) -psutil_version = tuple([int(num) for num in __psutil_version.split('.')]) -if psutil_version < psutil_min_version: +psutil_version_info = tuple([int(num) for num in psutil_version.split('.')]) +if psutil_version_info < psutil_min_version: print('PSutil 2.0 or higher is needed. Glances cannot start.') sys.exit(1) @@ -107,7 +107,7 @@ def main(): logger.info('{0} {1} and PSutil {2} detected'.format( platform.python_implementation(), platform.python_version(), - __psutil_version)) + psutil_version)) # Share global var global core, standalone, client, server, webserver diff --git a/glances/autodiscover.py b/glances/autodiscover.py index fa1414d4..b2f2fcb9 100644 --- a/glances/autodiscover.py +++ b/glances/autodiscover.py @@ -22,7 +22,8 @@ import socket import sys -from glances.globals import appname, BSD +from glances import __appname__ +from glances.globals import BSD from glances.logger import logger try: @@ -46,7 +47,7 @@ if zeroconf_tag: sys.exit(1) # Global var -zeroconf_type = "_%s._tcp." % appname +zeroconf_type = "_%s._tcp." % __appname__ class AutoDiscovered(object): diff --git a/glances/client.py b/glances/client.py index 2dc33bed..a1e68e0b 100644 --- a/glances/client.py +++ b/glances/client.py @@ -23,10 +23,10 @@ import json import socket import sys +from glances import __version__ from glances.compat import Fault, ProtocolError, ServerProxy, Transport -from glances.globals import version from glances.logger import logger -from glances.stats import GlancesStatsClient +from glances.stats_client import GlancesStatsClient from glances.outputs.glances_curses import GlancesCursesClient @@ -122,11 +122,11 @@ class GlancesClient(object): if self.client_mode == 'glances': # Check that both client and server are in the same major version - if version.split('.')[0] == client_version.split('.')[0]: + if __version__.split('.')[0] == client_version.split('.')[0]: # Init stats self.stats = GlancesStatsClient(config=self.config, args=self.args) self.stats.set_plugins(json.loads(self.client.getAllPlugins())) - logger.debug("Client version: {0} / Server version: {1}".format(version, client_version)) + 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)) @@ -139,7 +139,7 @@ class GlancesClient(object): if self.client_mode == 'snmp': logger.info("Trying to grab stats by SNMP...") - from glances.stats import GlancesStatsClientSNMP + from glances.stats_client_snmp import GlancesStatsClientSNMP # Init stats self.stats = GlancesStatsClientSNMP(config=self.config, args=self.args) diff --git a/glances/client_browser.py b/glances/client_browser.py index cfcf01a2..ef1348d0 100644 --- a/glances/client_browser.py +++ b/glances/client_browser.py @@ -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 @@ -28,7 +28,7 @@ from glances.client import GlancesClient, GlancesClientTransport from glances.logger import logger from glances.password_list import GlancesPasswordList as GlancesPassword from glances.static_list import GlancesStaticServer -from glances.outputs.glances_curses import GlancesCursesBrowser +from glances.outputs.glances_curses_browser import GlancesCursesBrowser class GlancesClientBrowser(object): @@ -160,7 +160,7 @@ class GlancesClientBrowser(object): "Server list dictionnary change inside the loop (wait next update)") # Update the screen (list or Glances client) - if not self.screen.active_server: + if self.screen.active_server is None: # Display the Glances browser self.screen.update(self.get_servers_list()) else: diff --git a/glances/config.py b/glances/config.py index 3e8570ea..8c53a9ca 100644 --- a/glances/config.py +++ b/glances/config.py @@ -23,8 +23,9 @@ import os import sys from io import open +from glances import __appname__ from glances.compat import ConfigParser, NoOptionError -from glances.globals import appname, BSD, LINUX, OSX, WINDOWS, sys_prefix +from glances.globals import BSD, LINUX, OSX, WINDOWS, sys_prefix from glances.logger import logger @@ -69,22 +70,22 @@ class Config(object): paths.append( os.path.join(os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), - appname, self.config_filename)) + __appname__, self.config_filename)) if BSD: paths.append( - os.path.join(sys.prefix, 'etc', appname, self.config_filename)) + os.path.join(sys.prefix, 'etc', __appname__, self.config_filename)) else: paths.append( - os.path.join('/etc', appname, self.config_filename)) + os.path.join('/etc', __appname__, self.config_filename)) elif OSX: paths.append( os.path.join(os.path.expanduser('~/Library/Application Support/'), - appname, self.config_filename)) + __appname__, self.config_filename)) paths.append( - os.path.join(sys_prefix, 'etc', appname, self.config_filename)) + os.path.join(sys_prefix, 'etc', __appname__, self.config_filename)) elif WINDOWS: paths.append( - os.path.join(os.environ.get('APPDATA'), appname, self.config_filename)) + os.path.join(os.environ.get('APPDATA'), __appname__, self.config_filename)) return paths diff --git a/glances/exports/glances_riemann.py b/glances/exports/glances_riemann.py new file mode 100644 index 00000000..a6ec1465 --- /dev/null +++ b/glances/exports/glances_riemann.py @@ -0,0 +1,92 @@ +# -*- 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/>. + +"""Riemann interface class.""" + +import socket +import sys +from numbers import Number + +from glances.compat import NoOptionError, NoSectionError, range +from glances.logger import logger +from glances.exports.glances_export import GlancesExport + +# Import pika for Riemann +import bernhard + + +class Export(GlancesExport): + + """This class manages the Riemann export module.""" + + def __init__(self, config=None, args=None): + """Init the Riemann export IF.""" + super(Export, self).__init__(config=config, args=args) + + # Load the rabbitMQ configuration file + self.riemann_host = None + self.riemann_port = None + self.hostname = socket.gethostname() + self.export_enable = self.load_conf() + if not self.export_enable: + sys.exit(2) + + # Init the rabbitmq client + self.client = self.init() + + def load_conf(self, section="riemann"): + """Load the Riemann configuration in the Glances configuration file.""" + if self.config is None: + return False + try: + self.riemann_host = self.config.get_value(section, 'host') + self.riemann_port = int(self.config.get_value(section, 'port')) + except NoSectionError: + logger.critical("No riemann configuration found") + return False + except NoOptionError as e: + logger.critical("Error in the Riemann configuration (%s)" % e) + return False + else: + logger.debug("Load Riemann from the Glances configuration file") + return True + + def init(self): + """Init the connection to the Riemann server.""" + if not self.export_enable: + return None + try: + client = bernhard.Client(host=self.riemann_host, port=self.riemann_port) + return client + except Exception as e: + logger.critical("Connection to Riemann failed : %s " % e) + return None + + def export(self, name, columns, points): + """Write the points in Riemann.""" + for i in range(len(columns)): + if not isinstance(points[i], Number): + continue + else: + data = {'host': self.hostname, 'service': name + " " + columns[i], 'metric': points[i]} + logger.debug(data) + try: + self.client.send(data) + except Exception as e: + logger.error("Can not export stats to Riemann (%s)" % e) diff --git a/glances/globals.py b/glances/globals.py index 8eba5090..6b952a76 100644 --- a/glances/globals.py +++ b/glances/globals.py @@ -22,11 +22,6 @@ import os import sys -# Global information -appname = 'glances' -version = __import__('glances').__version__ -psutil_version = __import__('glances').__psutil_version - # Operating system flag # Note: Somes libs depends of OS BSD = sys.platform.find('bsd') != -1 diff --git a/glances/main.py b/glances/main.py index a8234295..9be00fcb 100644 --- a/glances/main.py +++ b/glances/main.py @@ -24,9 +24,10 @@ import os import sys import tempfile +from glances import __appname__, __version__, psutil_version from glances.compat import input from glances.config import Config -from glances.globals import appname, LINUX, WINDOWS, psutil_version, version +from glances.globals import LINUX, WINDOWS from glances.logger import logger @@ -86,14 +87,14 @@ Start the client browser (browser mode):\n\ def init_args(self): """Init all the command line arguments.""" - _version = "Glances v" + version + " with psutil v" + psutil_version + version = "Glances v" + __version__ + " with psutil v" + psutil_version parser = argparse.ArgumentParser( - prog=appname, + prog=__appname__, conflict_handler='resolve', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=self.example_of_use) parser.add_argument( - '-V', '--version', action='version', version=_version) + '-V', '--version', action='version', version=version) parser.add_argument('-d', '--debug', action='store_true', default=False, dest='debug', help='enable debug mode') parser.add_argument('-C', '--config', dest='conf_file', @@ -162,6 +163,8 @@ Start the client browser (browser mode):\n\ dest='export_elasticsearch', help='export stats to an ElasticSearch server (elasticsearch lib needed)') parser.add_argument('--export-rabbitmq', action='store_true', default=False, dest='export_rabbitmq', help='export stats to rabbitmq broker (pika lib needed)') + parser.add_argument('--export-riemann', action='store_true', default=False, + dest='export_riemann', help='export stats to riemann broker (bernhard lib needed)') # Client/Server option parser.add_argument('-c', '--client', dest='client', help='connect to a Glances server by IPv4/IPv6 address or hostname') diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index 053a1175..5ea21873 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -333,15 +333,8 @@ class GlancesBottle(object): abort(404, "Cannot get views for plugin %s (%s)" % (plugin, str(e))) return ret - def _api_item(self, plugin, item): - """Glances API RESTFul implementation. - - Return the JSON represenation of the couple plugin/item - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - - """ + def _api_itemvalue(self, plugin, item, value=None): + """ Father method for _api_item and _api_value""" response.content_type = 'application/json' if plugin not in self.plugins_list: @@ -350,35 +343,39 @@ class GlancesBottle(object): # Update the stat self.stats.update() - plist = self.stats.get_plugin(plugin).get_stats_item(item) + if value is None: + ret = self.stats.get_plugin(plugin).get_stats_item(item) - if plist is None: - abort(404, "Cannot get item %s in plugin %s" % (item, plugin)) + if ret is None: + abort(404, "Cannot get item %s in plugin %s" % (item, plugin)) else: - return plist + ret = self.stats.get_plugin(plugin).get_stats_value(item, value) - def _api_value(self, plugin, item, value): + if ret is None: + abort(404, "Cannot get item(%s)=value(%s) in plugin %s" % (item, value, plugin)) + + return ret + + def _api_item(self, plugin, item): """Glances API RESTFul implementation. - Return the process stats (dict) for the given item=value + Return the JSON represenation of the couple plugin/item HTTP/200 if OK HTTP/400 if plugin is not found HTTP/404 if others error - """ - response.content_type = 'application/json' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - # Update the stat - self.stats.update() + """ + return self._api_itemvalue(plugin, item) - pdict = self.stats.get_plugin(plugin).get_stats_value(item, value) + def _api_value(self, plugin, item, value): + """Glances API RESTFul implementation. - if pdict is None: - abort(404, "Cannot get item(%s)=value(%s) in plugin %s" % (item, value, plugin)) - else: - return pdict + Return the process stats (dict) for the given item=value + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + return self._api_itemvalue(plugin, item, value) def _api_args(self): """Glances API RESTFul implementation. diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 4d5934c8..f86f1175 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -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 @@ -846,7 +846,7 @@ class _GlancesCurses(object): else: # Display the popup popup.refresh() - curses.napms(duration * 1000) + self.wait(duration * 1000) return True def display_plugin(self, plugin_stats, @@ -986,10 +986,14 @@ class _GlancesCurses(object): # Redraw display self.flush(stats, cs_status=cs_status) # Wait 100ms... - curses.napms(100) + self.wait() return exitkey + def wait(self, delay=100): + """Wait delay in ms""" + curses.napms(100) + def get_stats_display_width(self, curse_msg, without_option=False): """Return the width of the formatted curses message. @@ -1036,248 +1040,6 @@ class GlancesCursesClient(_GlancesCurses): pass -class GlancesCursesBrowser(_GlancesCurses): - - """Class for the Glances curse client browser.""" - - def __init__(self, args=None): - # Init the father class - super(GlancesCursesBrowser, self).__init__(args=args) - - _colors_list = { - 'UNKNOWN': self.no_color, - 'SNMP': self.d |