Merge branch 'develop'

This commit is contained in:
nicolargo 2016-03-26 17:46:57 +01:00
commit 0bb34a8d7f
35 changed files with 994 additions and 699 deletions

View File

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

14
NEWS
View File

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

View File

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

View File

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

View File

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

20
docs/gw/riemann.rst Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
# Copyright (C) 2016 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
@ -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:

View File

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

View File

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

View File

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

View File

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

View File

@ -333,6 +333,29 @@ class GlancesBottle(object):
abort(404, "Cannot get views for plugin %s (%s)" % (plugin, str(e)))
return ret
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:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.stats.update()
if value is None:
ret = self.stats.get_plugin(plugin).get_stats_item(item)
if ret is None:
abort(404, "Cannot get item %s in plugin %s" % (item, plugin))
else:
ret = self.stats.get_plugin(plugin).get_stats_value(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.
@ -342,20 +365,7 @@ class GlancesBottle(object):
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()
plist = self.stats.get_plugin(plugin).get_stats_item(item)
if plist is None:
abort(404, "Cannot get item %s in plugin %s" % (item, plugin))
else:
return plist
return self._api_itemvalue(plugin, item)
def _api_value(self, plugin, item, value):
"""Glances API RESTFul implementation.
@ -365,20 +375,7 @@ class GlancesBottle(object):
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()
pdict = self.stats.get_plugin(plugin).get_stats_value(item, value)
if pdict is None:
abort(404, "Cannot get item(%s)=value(%s) in plugin %s" % (item, value, plugin))
else:
return pdict
return self._api_itemvalue(plugin, item, value)
def _api_args(self):
"""Glances API RESTFul implementation.

View File

@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
# Copyright (C) 2016 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
@ -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.default_color2,
'ONLINE': self.default_color2,
'OFFLINE': self.ifCRITICAL_color2,
'PROTECTED': self.ifWARNING_color2,
}
self.colors_list.update(_colors_list)
# First time scan tag
# Used to display a specific message when the browser is started
self.first_scan = True
# Init refresh time
self.__refresh_time = args.time
# Init the cursor position for the client browser
self.cursor_position = 0
# Active Glances server number
self._active_server = None
@property
def active_server(self):
"""Return the active server or None if it's the browser list."""
return self._active_server
@active_server.setter
def active_server(self, index):
"""Set the active server or None if no server selected."""
self._active_server = index
@property
def cursor(self):
"""Get the cursor position."""
return self.cursor_position
@cursor.setter
def cursor(self, position):
"""Set the cursor position."""
self.cursor_position = position
def cursor_up(self, servers_list):
"""Set the cursor to position N-1 in the list."""
if self.cursor_position > 0:
self.cursor_position -= 1
else:
self.cursor_position = len(servers_list) - 1
def cursor_down(self, servers_list):
"""Set the cursor to position N-1 in the list."""
if self.cursor_position < len(servers_list) - 1:
self.cursor_position += 1
else:
self.cursor_position = 0
def __catch_key(self, servers_list):
# Catch the browser pressed key
self.pressedkey = self.get_key(self.term_window)
# Actions...
if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
# 'ESC'|'q' > Quit
self.end()
logger.info("Stop Glances client browser")
sys.exit(0)
elif self.pressedkey == 10:
# 'ENTER' > Run Glances on the selected server
logger.debug("Server number {0} selected".format(self.cursor + 1))
self.active_server = self.cursor
elif self.pressedkey == 259:
# 'UP' > Up in the server list
self.cursor_up(servers_list)
elif self.pressedkey == 258:
# 'DOWN' > Down in the server list
self.cursor_down(servers_list)
# Return the key code
return self.pressedkey
def update(self, servers_list):
"""Update the servers' list screen.
Wait for __refresh_time sec / catch key every 100 ms.
servers_list: Dict of dict with servers stats
"""
# Flush display
logger.debug('Servers list: {0}'.format(servers_list))
self.flush(servers_list)
# Wait
exitkey = False
countdown = Timer(self.__refresh_time)
while not countdown.finished() and not exitkey:
# Getkey
pressedkey = self.__catch_key(servers_list)
# Is it an exit or select server key ?
exitkey = (
pressedkey == ord('\x1b') or pressedkey == ord('q') or pressedkey == 10)
if not exitkey and pressedkey > -1:
# Redraw display
self.flush(servers_list)
# Wait 100ms...
curses.napms(100)
return self.active_server
def flush(self, servers_list):
"""Update the servers' list screen.
servers_list: List of dict with servers stats
"""
self.erase()
self.display(servers_list)
def display(self, servers_list):
"""Display the servers list.
Return:
True if the stats have been displayed
False if the stats have not been displayed (no server available)
"""
# Init the internal line/column for Glances Curses
self.init_line_column()
# Get the current screen size
screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0]
# Init position
x = 0
y = 0
# Display top header
if len(servers_list) == 0:
if self.first_scan and not self.args.disable_autodiscover:
msg = 'Glances is scanning your network. Please wait...'
self.first_scan = False
else:
msg = 'No Glances server available'
elif len(servers_list) == 1:
msg = 'One Glances server available'
else:
msg = '{0} Glances servers available'.format(len(servers_list))
if self.args.disable_autodiscover:
msg += ' ' + '(auto discover is disabled)'
self.term_window.addnstr(y, x,
msg,
screen_x - x,
self.colors_list['TITLE'])
if len(servers_list) == 0:
return False
# Display the Glances server list
# ================================
# Table of table
# Item description: [stats_id, column name, column size]
column_def = [
['name', 'Name', 16],
['alias', None, None],
['load_min5', 'LOAD', 6],
['cpu_percent', 'CPU%', 5],
['mem_percent', 'MEM%', 5],
['status', 'STATUS', 9],
['ip', 'IP', 15],
# ['port', 'PORT', 5],
['hr_name', 'OS', 16],
]
y = 2
# Display table header
xc = x + 2
for cpt, c in enumerate(column_def):
if xc < screen_x and y < screen_y and c[1] is not None:
self.term_window.addnstr(y, xc,
c[1],
screen_x - x,
self.colors_list['BOLD'])
xc += c[2] + self.space_between_column
y += 1
# If a servers has been deleted from the list...
# ... and if the cursor is in the latest position
if self.cursor > len(servers_list) - 1:
# Set the cursor position to the latest item
self.cursor = len(servers_list) - 1
# Display table
line = 0
for v in servers_list:
# Get server stats
server_stat = {}
for c in column_def:
try:
server_stat[c[0]] = v[c[0]]
except KeyError as e:
logger.debug(
"Cannot grab stats {0} from server (KeyError: {1})".format(c[0], e))
server_stat[c[0]] = '?'
# Display alias instead of name
try:
if c[0] == 'alias' and v[c[0]] is not None:
server_stat['name'] = v[c[0]]
except KeyError:
pass
# Display line for server stats
cpt = 0
xc = x
# Is the line selected ?
if line == self.cursor:
# Display cursor
self.term_window.addnstr(
y, xc, ">", screen_x - xc, self.colors_list['BOLD'])
# Display the line
xc += 2
for c in column_def:
if xc < screen_x and y < screen_y and c[1] is not None:
# Display server stats
self.term_window.addnstr(
y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
xc += c[2] + self.space_between_column
cpt += 1
# Next line, next server...
y += 1
line += 1
return True
if not WINDOWS:
class GlancesTextbox(Textbox, object):

View File

@ -0,0 +1,273 @@
# -*- 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/>.
"""Curses browser interface class ."""
import sys
from glances.outputs.glances_curses import _GlancesCurses
from glances.logger import logger
from glances.timer import Timer
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.default_color2,
'ONLINE': self.default_color2,
'OFFLINE': self.ifCRITICAL_color2,
'PROTECTED': self.ifWARNING_color2,
}
self.colors_list.update(_colors_list)
# First time scan tag
# Used to display a specific message when the browser is started
self.first_scan = True
# Init refresh time
self.__refresh_time = args.time
# Init the cursor position for the client browser
self.cursor_position = 0
# Active Glances server number
self._active_server = None
@property
def active_server(self):
"""Return the active server or None if it's the browser list."""
return self._active_server
@active_server.setter
def active_server(self, index):
"""Set the active server or None if no server selected."""
self._active_server = index
@property
def cursor(self):
"""Get the cursor position."""
return self.cursor_position
@cursor.setter
def cursor(self, position):
"""Set the cursor position."""
self.cursor_position = position
def cursor_up(self, servers_list):
"""Set the cursor to position N-1 in the list."""
if self.cursor_position > 0:
self.cursor_position -= 1
else:
self.cursor_position = len(servers_list) - 1
def cursor_down(self, servers_list):
"""Set the cursor to position N-1 in the list."""
if self.cursor_position < len(servers_list) - 1:
self.cursor_position += 1
else:
self.cursor_position = 0
def __catch_key(self, servers_list):
# Catch the browser pressed key
self.pressedkey = self.get_key(self.term_window)
if self.pressedkey != -1:
logger.debug("Key pressed. Code=%s" % self.pressedkey)
# Actions...
if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
# 'ESC'|'q' > Quit
self.end()
logger.info("Stop Glances client browser")
sys.exit(0)
elif self.pressedkey == 10:
# 'ENTER' > Run Glances on the selected server
logger.debug("Server number {0} selected".format(self.cursor + 1))
self.active_server = self.cursor
elif self.pressedkey == 65:
# 'UP' > Up in the server list
self.cursor_up(servers_list)
elif self.pressedkey == 66:
# 'DOWN' > Down in the server list
self.cursor_down(servers_list)
# Return the key code
return self.pressedkey
def update(self, servers_list):
"""Update the servers' list screen.
Wait for __refresh_time sec / catch key every 100 ms.
servers_list: Dict of dict with servers stats
"""
# Flush display
logger.debug('Servers list: {0}'.format(servers_list))
self.flush(servers_list)
# Wait
exitkey = False
countdown = Timer(self.__refresh_time)
while not countdown.finished() and not exitkey:
# Getkey
pressedkey = self.__catch_key(servers_list)
# Is it an exit or select server key ?
exitkey = (
pressedkey == ord('\x1b') or pressedkey == ord('q') or pressedkey == 10)
if not exitkey and pressedkey > -1:
# Redraw display
self.flush(servers_list)
# Wait 100ms...
self.wait()
return self.active_server
def flush(self, servers_list):
"""Update the servers' list screen.
servers_list: List of dict with servers stats
"""
self.erase()
self.display(servers_list)
def display(self, servers_list):
"""Display the servers list.
Return:
True if the stats have been displayed
False if the stats have not been displayed (no server available)
"""
# Init the internal line/column for Glances Curses
self.init_line_column()
# Get the current screen size
screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0]
# Init position
x = 0
y = 0
# Display top header
if len(servers_list) == 0:
if self.first_scan and not self.args.disable_autodiscover:
msg = 'Glances is scanning your network. Please wait...'
self.first_scan = False
else:
msg = 'No Glances server available'
elif len(servers_list) == 1:
msg = 'One Glances server available'
else:
msg = '{0} Glances servers available'.format(len(servers_list))
if self.args.disable_autodiscover:
msg += ' ' + '(auto discover is disabled)'
self.term_window.addnstr(y, x,
msg,
screen_x - x,
self.colors_list['TITLE'])
if len(servers_list) == 0:
return False
# Display the Glances server list
# ================================
# Table of table
# Item description: [stats_id, column name, column size]
column_def = [
['name', 'Name', 16],
['alias', None, None],
['load_min5', 'LOAD', 6],
['cpu_percent', 'CPU%', 5],
['mem_percent', 'MEM%', 5],
['status', 'STATUS', 9],
['ip', 'IP', 15],
# ['port', 'PORT', 5],
['hr_name', 'OS', 16],
]
y = 2
# Display table header
xc = x + 2
for cpt, c in enumerate(column_def):
if xc < screen_x and y < screen_y and c[1] is not None:
self.term_window.addnstr(y, xc,
c[1],
screen_x - x,
self.colors_list['BOLD'])
xc += c[2] + self.space_between_column
y += 1
# If a servers has been deleted from the list...
# ... and if the cursor is in the latest position
if self.cursor > len(servers_list) - 1:
# Set the cursor position to the latest item
self.cursor = len(servers_list) - 1
# Display table
line = 0
for v in servers_list:
# Get server stats
server_stat = {}
for c in column_def:
try:
server_stat[c[0]] = v[c[0]]
except KeyError as e:
logger.debug(
"Cannot grab stats {0} from server (KeyError: {1})".format(c[0], e))
server_stat[c[0]] = '?'
# Display alias instead of name
try:
if c[0] == 'alias' and v[c[0]] is not None:
server_stat['name'] = v[c[0]]
except KeyError:
pass
# Display line for server stats
cpt = 0
xc = x
# Is the line selected ?
if line == self.cursor:
# Display cursor
self.term_window.addnstr(
y, xc, ">", screen_x - xc, self.colors_list['BOLD'])
# Display the line
xc += 2
for c in column_def:
if xc < screen_x and y < screen_y and c[1] is not None:
# Display server stats
self.term_window.addnstr(
y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
xc += c[2] + self.space_between_column
cpt += 1
# Next line, next server...
y += 1
line += 1
return True

View File

@ -26,8 +26,9 @@ import sys
import uuid
from io import open
from glances import __appname__
from glances.compat import b, input
from glances.globals import appname, BSD, LINUX, OSX, WINDOWS
from glances.globals import BSD, LINUX, OSX, WINDOWS
from glances.logger import logger
@ -58,7 +59,7 @@ class GlancesPassword(object):
app_path = '.'
# Append the Glances folder
app_path = os.path.join(app_path, appname)
app_path = os.path.join(app_path, __appname__)
return app_path

View File

@ -23,7 +23,7 @@ Help plugin.
Just a stupid plugin to display the help screen.
"""
from glances.globals import appname, psutil_version, version
from glances import __appname__, __version__, psutil_version
from glances.plugins.glances_plugin import GlancesPlugin
@ -50,7 +50,7 @@ class Plugin(GlancesPlugin):
pass
def generate_view_data(self):
self.view_data['version'] = '{0} {1}'.format(appname.title(), version)
self.view_data['version'] = '{0} {1}'.format(__appname__.title(), __version__)
self.view_data['psutil_version'] = ' with PSutil {0}'.format(psutil_version)
try:

View File

@ -17,13 +17,11 @@
# 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/>.
from glances import psutil_version_info
from glances.plugins.glances_plugin import GlancesPlugin
from psutil import __version__ as __psutil_version
class Plugin(GlancesPlugin):
"""Get the psutil version for client/server purposes.
stats is a tuple
@ -48,7 +46,7 @@ class Plugin(GlancesPlugin):
if self.input_method == 'local':
# PsUtil version only available in local
try:
self.stats = tuple([int(num) for num in __psutil_version.split('.')])
self.stats = psutil_version_info
except NameError:
pass
else:

View File

@ -208,7 +208,7 @@ class Plugin(GlancesPlugin):
msg, self.get_views(item=i[self.get_key()],
key='value',
option='decoration')))
except ValueError:
except (TypeError, ValueError):
pass
return ret

View File

@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
# Copyright (C) 2016 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
@ -17,7 +17,6 @@
# 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/>.
import collections
import os
import re
@ -25,6 +24,7 @@ from glances.compat import iteritems, itervalues, listitems
from glances.globals import BSD, LINUX, OSX, WINDOWS
from glances.logger import logger
from glances.timer import Timer, getTimeSinceLastUpdate
from glances.processes_tree import ProcessTreeNode
import psutil
@ -39,199 +39,6 @@ def is_kernel_thread(proc):
return False
class ProcessTreeNode(object):
"""Represent a process tree.
We avoid recursive algorithm to manipulate the tree because function
calls are expensive with CPython.
"""
def __init__(self, process=None, stats=None, sort_key=None, sort_reverse=True, root=False):
self.process = process
self.stats = stats
self.children = []
self.children_sorted = False
self.sort_key = sort_key
self.sort_reverse = sort_reverse
self.is_root = root
def __str__(self):
"""Return the tree as a string for debugging."""
lines = []
nodes_to_print = collections.deque([collections.deque([("#", self)])])
while nodes_to_print:
indent_str, current_node = nodes_to_print[-1].pop()
if not nodes_to_print[-1]:
nodes_to_print.pop()
if current_node.is_root:
lines.append(indent_str)
else:
lines.append("%s[%s]" %
(indent_str, current_node.process.name()))
indent_str = " " * (len(lines[-1]) - 1)
children_nodes_to_print = collections.deque()
for child in current_node.children:
if child is current_node.children[-1]:
tree_char = "└─"
else:
tree_char = "├─"
children_nodes_to_print.appendleft(
(indent_str + tree_char, child))
if children_nodes_to_print:
nodes_to_print.append(children_nodes_to_print)
return "\n".join(lines)
def set_sorting(self, key, reverse):
"""Set sorting key or func for use with __iter__.
This affects the whole tree from this node.
"""
if self.sort_key != key or self.sort_reverse != reverse:
nodes_to_flag_unsorted = collections.deque([self])
while nodes_to_flag_unsorted:
current_node = nodes_to_flag_unsorted.pop()
current_node.children_sorted = False
current_node.sort_key = key
current_node.reverse_sorting = reverse
nodes_to_flag_unsorted.extend(current_node.children)
def get_weight(self):
"""Return 'weight' of a process and all its children for sorting."""
if self.sort_key == 'name' or self.sort_key == 'username':
return self.stats[self.sort_key]
# sum ressource usage for self and children
total = 0
nodes_to_sum = collections.deque([self])
while nodes_to_sum:
current_node = nodes_to_sum.pop()
if isinstance(self.sort_key, collections.Callable):
total += self.sort_key(current_node.stats)
elif self.sort_key == "io_counters":
stats = current_node.stats[self.sort_key]
total += stats[0] - stats[2] + stats[1] - stats[3]
elif self.sort_key == "cpu_times":
total += sum(current_node.stats[self.sort_key])
else:
total += current_node.stats[self.sort_key]
nodes_to_sum.extend(current_node.children)
return total
def __len__(self):
"""Return the number of nodes in the tree."""
total = 0
nodes_to_sum = collections.deque([self])
while nodes_to_sum:
current_node = nodes_to_sum.pop()
if not current_node.is_root:
total += 1
nodes_to_sum.extend(current_node.children)
return total
def __iter__(self):
"""Iterator returning ProcessTreeNode in sorted order, recursively."""
if not self.is_root:
yield self
if not self.children_sorted:
# optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
# and once before displaying)
self.children.sort(
key=self.__class__.get_weight, reverse=self.sort_reverse)
self.children_sorted = True
for child in self.children:
for n in iter(child):
yield n
def iter_children(self, exclude_incomplete_stats=True):
"""Iterator returning ProcessTreeNode in sorted order.
Return only children of this node, non recursive.
If exclude_incomplete_stats is True, exclude processes not
having full statistics. It can happen after a resort (change of
sort key) because process stats are not grabbed immediately, but
only at next full update.
"""
if not self.children_sorted:
# optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
# and once before displaying)
self.children.sort(
key=self.__class__.get_weight, reverse=self.sort_reverse)
self.children_sorted = True
for child in self.children:
if not exclude_incomplete_stats or "time_since_update" in child.stats:
yield child
def find_process(self, process):
"""Search in tree for the ProcessTreeNode owning process.
Return it or None if not found.
"""
nodes_to_search = collections.deque([self])
while nodes_to_search:
current_node = nodes_to_search.pop()
if not current_node.is_root and current_node.process.pid == process.pid:
return current_node
nodes_to_search.extend(current_node.children)
@staticmethod
def build_tree(process_dict, sort_key, sort_reverse, hide_kernel_threads, excluded_processes):
"""Build a process tree using using parent/child relationships.
Return the tree root node.
"""
tree_root = ProcessTreeNode(root=True)
nodes_to_add_last = collections.deque()
# first pass: add nodes whose parent are in the tree
for process, stats in iteritems(process_dict):
new_node = ProcessTreeNode(process, stats, sort_key, sort_reverse)
try:
parent_process = process.parent()
except psutil.NoSuchProcess:
# parent is dead, consider no parent
parent_process = None
if (parent_process is None) or (parent_process in excluded_processes):
# no parent, or excluded parent, add this node at the top level
tree_root.children.append(new_node)
else:
parent_node = tree_root.find_process(parent_process)
if parent_node is not None:
# parent is already in the tree, add a new child
parent_node.children.append(new_node)
else:
# parent is not in tree, add this node later
nodes_to_add_last.append(new_node)
# next pass(es): add nodes to their parents if it could not be done in
# previous pass
while nodes_to_add_last:
# pop from left and append to right to avoid infinite loop
node_to_add = nodes_to_add_last.popleft()
try:
parent_process = node_to_add.process.parent()
except psutil.NoSuchProcess:
# parent is dead, consider no parent, add this node at the top
# level
tree_root.children.append(node_to_add)
else:
if (parent_process is None) or (parent_process in excluded_processes):
# no parent, or excluded parent, add this node at the top level
tree_root.children.append(node_to_add)
else:
parent_node = tree_root.find_process(parent_process)
if parent_node is not None:
# parent is already in the tree, add a new child
parent_node.children.append(node_to_add)
else:
# parent is not in tree, add this node later
nodes_to_add_last.append(node_to_add)
return tree_root
class GlancesProcesses(object):
"""Get processed stats using the psutil library."""

217
glances/processes_tree.py Normal file
View File

@ -0,0 +1,217 @@
# -*- 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/>.
import collections
from glances.compat import iteritems
import psutil
class ProcessTreeNode(object):
"""Represent a process tree.
We avoid recursive algorithm to manipulate the tree because function
calls are expensive with CPython.
"""
def __init__(self, process=None, stats=None, sort_key=None, sort_reverse=True, root=False):
self.process = process
self.stats = stats
self.children = []
self.children_sorted = False
self.sort_key = sort_key
self.sort_reverse = sort_reverse
self.is_root = root
def __str__(self):
"""Return the tree as a string for debugging."""
lines = []
nodes_to_print = collections.deque([collections.deque([("#", self)])])
while nodes_to_print:
indent_str, current_node = nodes_to_print[-1].pop()
if not nodes_to_print[-1]:
nodes_to_print.pop()
if current_node.is_root:
lines.append(indent_str)
else:
lines.append("%s[%s]" %
(indent_str, current_node.process.name()))
indent_str = " " * (len(lines[-1]) - 1)
children_nodes_to_print = collections.deque()
for child in current_node.children:
if child is current_node.children[-1]:
tree_char = "└─"
else:
tree_char = "├─"
children_nodes_to_print.appendleft(
(indent_str + tree_char, child))
if children_nodes_to_print:
nodes_to_print.append(children_nodes_to_print)
return "\n".join(lines)
def set_sorting(self, key, reverse):
"""Set sorting key or func for use with __iter__.
This affects the whole tree from this node.
"""
if self.sort_key != key or self.sort_reverse != reverse:
nodes_to_flag_unsorted = collections.deque([self])
while nodes_to_flag_unsorted:
current_node = nodes_to_flag_unsorted.pop()
current_node.children_sorted = False
current_node.sort_key = key
current_node.reverse_sorting = reverse
nodes_to_flag_unsorted.extend(current_node.children)
def get_weight(self):
"""Return 'weight' of a process and all its children for sorting."""
if self.sort_key == 'name' or self.sort_key == 'username':
return self.stats[self.sort_key]
# sum ressource usage for self and children
total = 0
nodes_to_sum = collections.deque([self])
while nodes_to_sum:
current_node = nodes_to_sum.pop()
if isinstance(self.sort_key, collections.Callable):
total += self.sort_key(current_node.stats)
elif self.sort_key == "io_counters":
stats = current_node.stats[self.sort_key]
total += stats[0] - stats[2] + stats[1] - stats[3]
elif self.sort_key == "cpu_times":
total += sum(current_node.stats[self.sort_key])
else:
total += current_node.stats[self.sort_key]
nodes_to_sum.extend(current_node.children)
return total
def __len__(self):
"""Return the number of nodes in the tree."""
total = 0
nodes_to_sum = collections.deque([self])
while nodes_to_sum:
current_node = nodes_to_sum.pop()
if not current_node.is_root:
total += 1
nodes_to_sum.extend(current_node.children)
return total
def __iter__(self):
"""Iterator returning ProcessTreeNode in sorted order, recursively."""
if not self.is_root:
yield self
if not self.children_sorted:
# optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
# and once before displaying)
self.children.sort(
key=self.__class__.get_weight, reverse=self.sort_reverse)
self.children_sorted = True
for child in self.children:
for n in iter(child):
yield n
def iter_children(self, exclude_incomplete_stats=True):
"""Iterator returning ProcessTreeNode in sorted order.
Return only children of this node, non recursive.
If exclude_incomplete_stats is True, exclude processes not
having full statistics. It can happen after a resort (change of
sort key) because process stats are not grabbed immediately, but
only at next full update.
"""
if not self.children_sorted:
# optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
# and once before displaying)
self.children.sort(
key=self.__class__.get_weight, reverse=self.sort_reverse)
self.children_sorted = True
for child in self.children:
if not exclude_incomplete_stats or "time_since_update" in child.stats:
yield child
def find_process(self, process):
"""Search in tree for the ProcessTreeNode owning process.
Return it or None if not found.
"""
nodes_to_search = collections.deque([self])
while nodes_to_search:
current_node = nodes_to_search.pop()
if not current_node.is_root and current_node.process.pid == process.pid:
return current_node
nodes_to_search.extend(current_node.children)
@staticmethod
def build_tree(process_dict, sort_key, sort_reverse, hide_kernel_threads, excluded_processes):
"""Build a process tree using using parent/child relationships.
Return the tree root node.
"""
tree_root = ProcessTreeNode(root=True)
nodes_to_add_last = collections.deque()
# first pass: add nodes whose parent are in the tree
for process, stats in iteritems(process_dict):
new_node = ProcessTreeNode(process, stats, sort_key, sort_reverse)
try:
parent_process = process.parent()
except psutil.NoSuchProcess:
# parent is dead, consider no parent
parent_process = None
if (parent_process is None) or (parent_process in excluded_processes):
# no parent, or excluded parent, add this node at the top level
tree_root.children.append(new_node)
else:
parent_node = tree_root.find_process(parent_process)
if parent_node is not None:
# parent is already in the tree, add a new child
parent_node.children.append(new_node)
else:
# parent is not in tree, add this node later
nodes_to_add_last.append(new_node)
# next pass(es): add nodes to their parents if it could not be done in
# previous pass
while nodes_to_add_last:
# pop from left and append to right to avoid infinite loop
node_to_add = nodes_to_add_last.popleft()
try:
parent_process = node_to_add.process.parent()
except psutil.NoSuchProcess:
# parent is dead, consider no parent, add this node at the top
# level
tree_root.children.append(node_to_add)
else:
if (parent_process is None) or (parent_process in excluded_processes):
# no parent, or excluded parent, add this node at the top level
tree_root.children.append(node_to_add)
else:
parent_node = tree_root.find_process(parent_process)
if parent_node is not None:
# parent is already in the tree, add a new child
parent_node.children.append(node_to_add)
else:
# parent is not in tree, add this node later
nodes_to_add_last.append(node_to_add)
return tree_root

View File

@ -24,11 +24,11 @@ import socket
import sys
from base64 import b64decode
from glances import __version__
from glances.compat import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from glances.autodiscover import GlancesAutoDiscoverClient
from glances.globals import version
from glances.logger import logger
from glances.stats import GlancesStatsServer
from glances.stats_server import GlancesStatsServer
from glances.timer import Timer
@ -137,7 +137,7 @@ class GlancesInstance(object):
def init(self):
# Return the Glances version
return version
return __version__
def getAll(self):
# Update and return all the stats

View File

@ -56,7 +56,7 @@ class GlancesStaticServer(object):
if new_server['name'] is not None:
# Manage optionnal information
if new_server['port'] is None:
new_server['port'] = 61209
new_server['port'] = '61209'
new_server['username'] = 'glances'
# By default, try empty (aka no) password
new_server['password'] = ''

View File

@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
# Copyright (C) 2016 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
@ -21,23 +21,12 @@
import collections
import os
import re
import sys
import threading
from glances.compat import iteritems
from glances.globals import exports_path, plugins_path, sys_path
from glances.logger import logger
# SNMP OID regexp pattern to short system name dict
oid_to_short_system_name = {'.*Linux.*': 'linux',
'.*Darwin.*': 'mac',
'.*BSD.*': 'bsd',
'.*Windows.*': 'windows',
'.*Cisco.*': 'cisco',
'.*VMware ESXi.*': 'esxi',
'.*NetApp.*': 'netapp'}
class GlancesStats(object):
@ -240,163 +229,3 @@ class GlancesStats(object):
# Close plugins
for p in self._plugins:
self._plugins[p].exit()
class GlancesStatsServer(GlancesStats):
"""This class stores, updates and gives stats for the server."""
def __init__(self, config=None):
# Init the stats
super(GlancesStatsServer, self).__init__(config)
# Init the all_stats dict used by the server
# all_stats is a dict of dicts filled by the server
self.all_stats = collections.defaultdict(dict)
def update(self, input_stats=None):
"""Update the stats."""
input_stats = input_stats or {}
# Force update of all the stats
super(GlancesStatsServer, self).update()
# Build all_stats variable (concatenation of all the stats)
self.all_stats = self._set_stats(input_stats)
def _set_stats(self, input_stats):
"""Set the stats to the input_stats one."""
# Build the all_stats with the get_raw() method of the plugins
ret = collections.defaultdict(dict)
for p in self._plugins:
ret[p] = self._plugins[p].get_raw()
return ret
def getAll(self):
"""Return the stats as a list."""
return self.all_stats
def getAllAsDict(self):
"""Return the stats as a dict."""
# Python > 2.6
# return {p: self.all_stats[p] for p in self._plugins}
ret = {}
for p in self._plugins:
ret[p] = self.all_stats[p]
return ret
class GlancesStatsClient(GlancesStats):
"""This class stores, updates and gives stats for the client."""
def __init__(self, config=None, args=None):
"""Init the GlancesStatsClient class."""
super(GlancesStatsClient, self).__init__()
# Init the configuration
self.config = config
# 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_"
for item in input_plugins:
# Import the plugin
plugin = __import__(header + item)
# Add the plugin to the dictionary
# The key is the plugin name
# for example, the file glances_xxx.py
# generate self._plugins_list["xxx"] = ...
logger.debug("Server uses {0} plugin".format(item))
self._plugins[item] = plugin.Plugin()
# Restoring system path
sys.path = sys_path
def update(self, input_stats):
"""Update all the stats."""
# For Glances client mode
for p in input_stats:
# Update plugin stats with items sent by the server
self._plugins[p].set_stats(input_stats[p])
# Update the views for the updated stats
self._plugins[p].update_views()
class GlancesStatsClientSNMP(GlancesStats):
"""This class stores, updates and gives stats for the SNMP client."""
def __init__(self, config=None, args=None):
super(GlancesStatsClientSNMP, self).__init__()
# Init the configuration
self.config = config
# Init the arguments
self.args = args
# 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)
def check_snmp(self):
"""Chek if SNMP is available on the server."""
# Import the SNMP client class
from glances.snmp import GlancesSNMPClient
# Create an instance of the SNMP client
clientsnmp = GlancesSNMPClient(host=self.args.client,
port=self.args.snmp_port,
version=self.args.snmp_version,
community=self.args.snmp_community,
user=self.args.snmp_user,
auth=self.args.snmp_auth)
# If we cannot grab the hostname, then exit...
ret = clientsnmp.get_by_oid("1.3.6.1.2.1.1.5.0") != {}
if ret:
# Get the OS name (need to grab the good OID...)
oid_os_name = clientsnmp.get_by_oid("1.3.6.1.2.1.1.1.0")
try:
self.system_name = self.get_system_name(oid_os_name['1.3.6.1.2.1.1.1.0'])
logger.info("SNMP system name detected: {0}".format(self.system_name))
except KeyError:
self.system_name = None
logger.warning("Cannot detect SNMP system name")
return ret
def get_system_name(self, oid_system_name):
"""Get the short os name from the OS name OID string."""
short_system_name = None
if oid_system_name == '':
return short_system_name
# Find the short name in the oid_to_short_os_name dict
for r, v in iteritems(oid_to_short_system_name):
if re.search(r, oid_system_name):
short_system_name = v
break
return short_system_name
def update(self):
"""Update the stats using SNMP."""
# For each plugins, call the update method
for p in self._plugins:
# Set the input method to SNMP
self._plugins[p].input_method = 'snmp'
self._plugins[p].short_system_name = self.system_name
try:
self._plugins[p].update()
except Exception as e:
logger.error("Update {0} failed: {1}".format(p, e))

68
glances/stats_client.py Normal file
View File

@ -0,0 +1,68 @@
# -*- 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/>.
"""The stats server manager."""
import sys
from glances.stats import GlancesStats
from glances.globals import sys_path
from glances.logger import logger
class GlancesStatsClient(GlancesStats):
"""This class stores, updates and gives stats for the client."""
def __init__(self, config=None, args=None):
"""Init the GlancesStatsClient class."""
super(GlancesStatsClient, self).__init__()
# Init the configuration
self.config = config
# 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_"
for item in input_plugins:
# Import the plugin
plugin = __import__(header + item)
# Add the plugin to the dictionary
# The key is the plugin name
# for example, the file glances_xxx.py
# generate self._plugins_list["xxx"] = ...
logger.debug("Server uses {0} plugin".format(item))
self._plugins[item] = plugin.Plugin()
# Restoring system path
sys.path = sys_path
def update(self, input_stats):
"""Update all the stats."""
# For Glances client mode
for p in input_stats:
# Update plugin stats with items sent by the server
self._plugins[p].set_stats(input_stats[p])
# Update the views for the updated stats
self._plugins[p].update_views()

View File

@ -0,0 +1,109 @@
# -*- 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/>.
"""The stats manager."""
import re
from glances.stats import GlancesStats
from glances.compat import iteritems
from glances.logger import logger
# SNMP OID regexp pattern to short system name dict
oid_to_short_system_name = {'.*Linux.*': 'linux',
'.*Darwin.*': 'mac',
'.*BSD.*': 'bsd',
'.*Windows.*': 'windows',
'.*Cisco.*': 'cisco',
'.*VMware ESXi.*': 'esxi',
'.*NetApp.*': 'netapp'}
class GlancesStatsClientSNMP(GlancesStats):
"""This class stores, updates and gives stats for the SNMP client."""
def __init__(self, config=None, args=None):
super(GlancesStatsClientSNMP, self).__init__()
# Init the configuration
self.config = config
# Init the arguments
self.args = args
# 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)
def check_snmp(self):
"""Chek if SNMP is available on the server."""
# Import the SNMP client class
from glances.snmp import GlancesSNMPClient
# Create an instance of the SNMP client
clientsnmp = GlancesSNMPClient(host=self.args.client,
port=self.args.snmp_port,
version=self.args.snmp_version,
community=self.args.snmp_community,
user=self.args.snmp_user,
auth=self.args.snmp_auth)
# If we cannot grab the hostname, then exit...
ret = clientsnmp.get_by_oid("1.3.6.1.2.1.1.5.0") != {}
if ret:
# Get the OS name (need to grab the good OID...)
oid_os_name = clientsnmp.get_by_oid("1.3.6.1.2.1.1.1.0")
try:
self.system_name = self.get_system_name(oid_os_name['1.3.6.1.2.1.1.1.0'])
logger.info("SNMP system name detected: {0}".format(self.system_name))
except KeyError:
self.system_name = None
logger.warning("Cannot detect SNMP system name")
return ret
def get_system_name(self, oid_system_name):
"""Get the short os name from the OS name OID string."""
short_system_name = None
if oid_system_name == '':
return short_system_name
# Find the short name in the oid_to_short_os_name dict
for r, v in iteritems(oid_to_short_system_name):
if re.search(r, oid_system_name):
short_system_name = v
break
return short_system_name
def update(self):
"""Update the stats using SNMP."""
# For each plugins, call the update method
for p in self._plugins:
# Set the input method to SNMP
self._plugins[p].input_method = 'snmp'
self._plugins[p].short_system_name = self.system_name
try:
self._plugins[p].update()
except Exception as e:
logger.error("Update {0} failed: {1}".format(p, e))

68
glances/stats_server.py Normal file
View File

@ -0,0 +1,68 @@
# -*- 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/>.
"""The stats server manager."""
import collections
from glances.stats import GlancesStats
class GlancesStatsServer(GlancesStats):
"""This class stores, updates and gives stats for the server."""
def __init__(self, config=None):
# Init the stats
super(GlancesStatsServer, self).__init__(config)
# Init the all_stats dict used by the server
# all_stats is a dict of dicts filled by the server
self.all_stats = collections.defaultdict(dict)
def update(self, input_stats=None):
"""Update the stats."""
input_stats = input_stats or {}
# Force update of all the stats
super(GlancesStatsServer, self).update()
# Build all_stats variable (concatenation of all the stats)
self.all_stats = self._set_stats(input_stats)
def _set_stats(self, input_stats):
"""Set the stats to the input_stats one."""
# Build the all_stats with the get_raw() method of the plugins
ret = collections.defaultdict(dict)
for p in self._plugins:
ret[p] = self._plugins[p].get_raw()
return ret
def getAll(self):
"""Return the stats as a list."""
return self.all_stats
def getAllAsDict(self):
"""Return the stats as a dict."""
# Python > 2.6
# return {p: self.all_stats[p] for p in self._plugins}
ret = {}
for p in self._plugins:
ret[p] = self.all_stats[p]
return ret

View File

@ -50,13 +50,12 @@ def get_requires():
setup(
name='Glances',
version='2.6',
version='2.6.1',
description="A cross-platform curses-based monitoring tool",
long_description=open('README.rst').read(),
author='Nicolas Hennion',
author_email='nicolas@nicolargo.com',
url='https://github.com/nicolargo/glances',
# download_url='https://s3.amazonaws.com/glances/glances-2.6.tar.gz',
license="LGPL",
keywords="cli curses monitoring system",
install_requires=get_requires(),

View File

@ -1,7 +1,7 @@
# Required metadata
sonar.projectKey=glances
sonar.projectName=Glances
sonar.projectVersion=2.6
sonar.projectVersion=2.6.1
# Path to the parent source code directory.
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.

View File

@ -25,8 +25,8 @@ import subprocess
import time
import unittest
from glances import __version__
from glances.compat import text_type
from glances.globals import version
import requests
@ -36,7 +36,7 @@ pid = None
# Unitest class
# ==============
print('RESTful API unitary tests for Glances %s' % version)
print('RESTful API unitary tests for Glances %s' % __version__)
class TestGlances(unittest.TestCase):

View File

@ -26,8 +26,8 @@ import subprocess
import time
import unittest
from glances import __version__
from glances.compat import ServerProxy
from glances.globals import version
SERVER_PORT = 61234
URL = "http://localhost:%s" % SERVER_PORT
@ -38,7 +38,7 @@ client = ServerProxy(URL)
# Unitest class
# ==============
print('XML-RPC API unitary tests for Glances %s' % version)
print('XML-RPC API unitary tests for Glances %s' % __version__)
class TestGlances(unittest.TestCase):

View File

@ -38,12 +38,13 @@ if not core.is_standalone():
from glances.stats import GlancesStats
stats = GlancesStats()
from glances.globals import WINDOWS, version
from glances import __version__
from glances.globals import WINDOWS
from glances.outputs.glances_bars import Bar
# Unitest class
# ==============
print('Unitary tests for Glances %s' % version)
print('Unitary tests for Glances %s' % __version__)
class TestGlances(unittest.TestCase):