mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-29 15:32:25 +03:00
Merge branch 'feature/issue418' into develop
This commit is contained in:
commit
1bc15ad7c0
15
NEWS
15
NEWS
@ -5,9 +5,19 @@ Glances Version 2.x
|
||||
Version 2.2
|
||||
===========
|
||||
|
||||
* Improve graph history feature (issue #428)
|
||||
Enhancements and news features:
|
||||
|
||||
* Add centralized curse interface with a Glances servers list to monitor (issue #418)
|
||||
* Add processes tree view (--tree) (issue #444)
|
||||
* Improve graph history feature (issue #69)
|
||||
* Extended stats is disable by default (use --enable-process-extended to enable it - issue #430)
|
||||
* Add a key ('F') and a command line option (--fs-free-space) to display FS free space instead of used space (issue #411)
|
||||
* Add a short key ('F') and a command line option (--fs-free-space) to display FS free space instead of used space (issue #411)
|
||||
* Add a short key ('2') and a command line option (--disable-left-sidebar) to disable/enable the side bar (issue #429)
|
||||
|
||||
Bugs corrected:
|
||||
|
||||
* Correct issue on battery stat update (issue #433)
|
||||
* Correct issue on process no longer exist (issues #414 and #432)
|
||||
|
||||
Version 2.1.2
|
||||
=============
|
||||
@ -22,6 +32,7 @@ Version 2.1.1
|
||||
=============
|
||||
|
||||
Enhancement:
|
||||
|
||||
* Automaticaly compute top processes number for the current screen (issue #408)
|
||||
* CPU and Memory footprint optimization (issue #401)
|
||||
|
||||
|
13
README.rst
13
README.rst
@ -1,7 +1,5 @@
|
||||
Follow Glances on Twitter: `@nicolargo`_ or `@glances_system`_
|
||||
|
||||
Give Glances some Bitcoin: `18Nbs6kg9UCqtX4RPDM3qMkeKwjDxBFYrW`_
|
||||
|
||||
===============================
|
||||
Glances - An eye on your system
|
||||
===============================
|
||||
@ -39,6 +37,7 @@ Optional dependencies:
|
||||
- ``hddtemp`` (for HDD temperature monitoring support) [Linux-only]
|
||||
- ``batinfo`` (for battery monitoring support) [Linux-only]
|
||||
- ``pysnmp`` (for SNMP support)
|
||||
- ``zeroconf`` and ``netifaces`` (for the auto discoverer mode)
|
||||
|
||||
Installation
|
||||
============
|
||||
@ -46,7 +45,7 @@ Installation
|
||||
Glances Auto Install script
|
||||
---------------------------
|
||||
|
||||
Just enter the following command line:
|
||||
To install both dependacies and latest Glances version, just enter the following command line:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@ -193,6 +192,13 @@ on the server side and run:
|
||||
|
||||
on the client one.
|
||||
|
||||
You can also detect and display all Glances servers available on your network or defined in the configuration file:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ glances --browser
|
||||
|
||||
|
||||
And RTFM, always.
|
||||
|
||||
Documentation
|
||||
@ -216,7 +222,6 @@ LGPL. See ``COPYING`` for more details.
|
||||
.. _glancesautoinstall: https://github.com/nicolargo/glancesautoinstall
|
||||
.. _@nicolargo: https://twitter.com/nicolargo
|
||||
.. _@glances_system: https://twitter.com/glances_system
|
||||
.. _18Nbs6kg9UCqtX4RPDM3qMkeKwjDxBFYrW: bitcoin:18Nbs6kg9UCqtX4RPDM3qMkeKwjDxBFYrW?amount=1X8&label=Glances
|
||||
.. _PyPI: https://pypi.python.org/pypi
|
||||
.. _pip: http://www.pip-installer.org/
|
||||
.. _Homebrew: http://brew.sh/
|
||||
|
@ -130,3 +130,14 @@ list_1_regex=.*python.*
|
||||
list_2_description=Famous Xeyes
|
||||
list_2_regex=.*xeyes.*
|
||||
list_2_countmin=1
|
||||
|
||||
[serverlist]
|
||||
# Define the static server list
|
||||
server_1_name=localhost
|
||||
server_1_port=61234
|
||||
server_2_name=localhost
|
||||
server_2_port=61235
|
||||
server_3_name=localhost
|
||||
server_3_port=61236
|
||||
server_4_name=pasbon
|
||||
server_4_port=61237
|
||||
|
@ -67,12 +67,20 @@ and on the client:
|
||||
|
||||
where ``@server`` is the IP address or hostname of the server.
|
||||
|
||||
Glances can centralize available Glances servers using the ``--browser`` option. The server list can be staticaly defined in the Glances configuration file (section [serverlist]). Glances can also detect and display all Glances servers available on you network (auto discover mode is based on the the Zeroconf protocol):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
client$ glances --browser
|
||||
|
||||
It is possible to disable the auto discover mode ``--disable-autodiscover``.
|
||||
|
||||
In server mode, you can set the bind address ``-B ADDRESS`` and listening
|
||||
TCP port ``-p PORT``.
|
||||
|
||||
In client mode, you can set the TCP port of the server ``-p PORT``.
|
||||
|
||||
You can also set a password to access to the server ``--password``.
|
||||
You can set a password to access to the server ``--password``.
|
||||
|
||||
Default binding address is ``0.0.0.0`` (Glances will listen on all the
|
||||
available network interfaces) and TCP port is ``61209``.
|
||||
@ -89,13 +97,13 @@ client, the latter will try to grab stats using the ``SNMP`` protocol:
|
||||
|
||||
client$ glances -c @snmpserver
|
||||
|
||||
Note: Stats grabbed by SNMP request are limited.
|
||||
Note: Stats grabbed by SNMP request are limited (operating system dependent).
|
||||
|
||||
Web Server Mode
|
||||
---------------
|
||||
|
||||
If you want to remotely monitor a machine, called ``server``, from any
|
||||
device with a web browser, just run on the server:
|
||||
device with a web browser, just run the server with the ``-w`` option:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@ -139,12 +147,18 @@ Command-Line Options
|
||||
--disable-log disable log module
|
||||
--enable-process-extended
|
||||
enable extended stats on top process
|
||||
--enable-history enable the history mode
|
||||
--path-history PATH_HISTORY
|
||||
Set the export path for graph history
|
||||
--output-csv OUTPUT_CSV
|
||||
export stats to a CSV file
|
||||
-c CLIENT, --client CLIENT
|
||||
connect to a Glances server by IPv4/IPv6 address or
|
||||
hostname
|
||||
-s, --server run Glances in server mode
|
||||
--browser run the Glances client browser (list of Glances server)
|
||||
--disable-autodiscover
|
||||
disable autodiscover feature
|
||||
-p PORT, --port PORT define the client/server TCP port [default: 61209]
|
||||
-B BIND_ADDRESS, --bind BIND_ADDRESS
|
||||
bind server to the given IPv4/IPv6 address or hostname
|
||||
@ -152,6 +166,8 @@ Command-Line Options
|
||||
define password from the command line
|
||||
--password define a client/server password from the prompt or
|
||||
file
|
||||
--disable-autodiscover
|
||||
Hide Glances server from the auto discover feature
|
||||
--snmp-community SNMP_COMMUNITY
|
||||
SNMP community
|
||||
--snmp-port SNMP_PORT
|
||||
@ -168,6 +184,9 @@ Command-Line Options
|
||||
-f PROCESS_FILTER, --process-filter PROCESS_FILTER
|
||||
set the process filter patern (regular expression)
|
||||
--process-short-name force short name for processes name
|
||||
--hide-kernel-threads
|
||||
hide kernel threads in process list
|
||||
--tree display processes as a tree
|
||||
-b, --byte display network rate in byte per second
|
||||
-1, --percpu start Glances in per CPU mode
|
||||
--fs-free-space display FS free space instead of used
|
||||
@ -216,8 +235,8 @@ The following commands (key pressed) are supported while in Glances:
|
||||
Show/hide network stats
|
||||
``p``
|
||||
Sort processes by name
|
||||
``q``
|
||||
Quit
|
||||
``q`` or ``ESC``
|
||||
Quit the current Glances session
|
||||
``r``
|
||||
Reset history
|
||||
``s``
|
||||
@ -239,6 +258,17 @@ The following commands (key pressed) are supported while in Glances:
|
||||
``/``
|
||||
Switch between short name / command line (processes name)
|
||||
|
||||
In the Glances client browser (accessible through the --browser command line argument):
|
||||
|
||||
``ENTER``
|
||||
Run Glances client to the selected server
|
||||
``UP``
|
||||
Up in the servers list
|
||||
``DOWN``
|
||||
Down in the servers list
|
||||
``q`` or ``ESC``
|
||||
Quit Glances
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
|
@ -50,11 +50,11 @@ psutil_version = tuple([int(num) for num in __psutil_version.split('.')])
|
||||
# First log with Glances and PSUtil version
|
||||
logger.info('Start Glances {0}'.format(__version__))
|
||||
logger.info('{0} {1} and PSutil {2} detected'.format(platform.python_implementation(),
|
||||
platform.python_version(),
|
||||
__psutil_version))
|
||||
platform.python_version(),
|
||||
__psutil_version))
|
||||
|
||||
# Check PSutil version
|
||||
if psutil_version < psutil_min_version:
|
||||
if psutil_version < psutil_min_version:
|
||||
logger.critical('PSutil 2.0 or higher is needed. Glances cannot start.')
|
||||
sys.exit(1)
|
||||
|
||||
@ -121,25 +121,37 @@ def main():
|
||||
standalone.serve_forever()
|
||||
|
||||
elif core.is_client():
|
||||
logger.info("Start client mode")
|
||||
if core.is_client_browser():
|
||||
logger.info("Start client mode (browser)")
|
||||
|
||||
# Import the Glances client module
|
||||
from glances.core.glances_client import GlancesClient
|
||||
# Import the Glances client browser module
|
||||
from glances.core.glances_client_browser import GlancesClientBrowser
|
||||
|
||||
# Init the client
|
||||
client = GlancesClient(config=core.get_config(),
|
||||
args=core.get_args())
|
||||
# Init the client
|
||||
client = GlancesClientBrowser(config=core.get_config(),
|
||||
args=core.get_args())
|
||||
|
||||
# Test if client and server are in the same major version
|
||||
if not client.login():
|
||||
logger.critical(_("The server version is not compatible with the client"))
|
||||
sys.exit(2)
|
||||
else:
|
||||
logger.info("Start client mode")
|
||||
|
||||
# Import the Glances client module
|
||||
from glances.core.glances_client import GlancesClient
|
||||
|
||||
# Init the client
|
||||
client = GlancesClient(config=core.get_config(),
|
||||
args=core.get_args())
|
||||
|
||||
# Test if client and server are in the same major version
|
||||
if not client.login():
|
||||
logger.critical(
|
||||
_("The server version is not compatible with the client"))
|
||||
sys.exit(2)
|
||||
|
||||
# Start the client loop
|
||||
client.serve_forever()
|
||||
|
||||
# Shutdown the client
|
||||
client.close()
|
||||
client.end()
|
||||
|
||||
elif core.is_server():
|
||||
logger.info("Start server mode")
|
||||
@ -152,7 +164,8 @@ def main():
|
||||
server = GlancesServer(cached_time=core.cached_time,
|
||||
config=core.get_config(),
|
||||
args=args)
|
||||
print(_("Glances server is running on {0}:{1}").format(args.bind_address, args.port))
|
||||
print(_("Glances server is running on {0}:{1}").format(
|
||||
args.bind_address, args.port))
|
||||
|
||||
# Set the server login/password (if -P/--password tag)
|
||||
if args.password != "":
|
||||
|
216
glances/core/glances_autodiscover.py
Normal file
216
glances/core/glances_autodiscover.py
Normal file
@ -0,0 +1,216 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# Copyright (C) 2014 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 autodiscover Glances server (thk to the ZeroConf protocol)."""
|
||||
|
||||
# Import system libs
|
||||
import sys
|
||||
import socket
|
||||
try:
|
||||
import netifaces
|
||||
netifaces_tag = True
|
||||
except ImportError:
|
||||
netifaces_tag = True
|
||||
try:
|
||||
from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf
|
||||
zeroconf_tag = True
|
||||
except ImportError:
|
||||
zeroconf_tag = False
|
||||
|
||||
# Import Glances libs
|
||||
from glances.core.glances_globals import appname, logger
|
||||
|
||||
# Global var
|
||||
zeroconf_type = "_%s._tcp." % appname
|
||||
|
||||
|
||||
class AutoDiscovered(object):
|
||||
|
||||
"""Class to manage the auto discovered servers dict"""
|
||||
|
||||
def __init__(self):
|
||||
# server_dict is a list of dict (JSON compliant)
|
||||
# [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'cpu': 3, 'mem': 34 ...} ... ]
|
||||
self._server_list = []
|
||||
|
||||
def get_servers_list(self):
|
||||
"""Return the current server list (list of dict)"""
|
||||
return self._server_list
|
||||
|
||||
def set_server(self, server_pos, key, value):
|
||||
"""Set the key to the value for the server_pos (position in the list)"""
|
||||
self._server_list[server_pos][key] = value
|
||||
|
||||
def add_server(self, name, ip, port):
|
||||
"""Add a new server to the list"""
|
||||
new_server = {'key': name, # Zeroconf name with both hostname and port
|
||||
'name': name.split(':')[0], # Short name
|
||||
'ip': ip, # IP address seen by the client
|
||||
'port': port, # TCP port
|
||||
'username': 'glances', # Default username
|
||||
'password': '', # Default password
|
||||
'status': 'UNKNOWN', # Server status: 'UNKNOWN', 'OFFLINE', 'ONLINE', 'PROTECTED'
|
||||
}
|
||||
self._server_list.append(new_server)
|
||||
logger.debug("Updated servers list (%s servers): %s" %
|
||||
(len(self._server_list), self._server_list))
|
||||
|
||||
def remove_server(self, name):
|
||||
"""Remove a server from the dict"""
|
||||
for i in self._server_list:
|
||||
if i['key'] == name:
|
||||
try:
|
||||
self._server_list.remove(i)
|
||||
logger.debug("Remove server %s from the list" % name)
|
||||
logger.debug("Updated servers list (%s servers): %s" % (
|
||||
len(self._server_list), self._server_list))
|
||||
except ValueError:
|
||||
logger.error(
|
||||
"Can not remove server %s from the list" % name)
|
||||
|
||||
|
||||
class GlancesAutoDiscoverListener(object):
|
||||
|
||||
"""Zeroconf listener for Glances server"""
|
||||
|
||||
def __init__(self):
|
||||
# Create an instance of the servers list
|
||||
self.servers = AutoDiscovered()
|
||||
|
||||
def get_servers_list(self):
|
||||
"""Return the current server list (list of dict)"""
|
||||
return self.servers.get_servers_list()
|
||||
|
||||
def set_server(self, server_pos, key, value):
|
||||
"""Set the key to the value for the server_pos (position in the list)"""
|
||||
self.servers.set_server(server_pos, key, value)
|
||||
|
||||
def addService(self, zeroconf, srv_type, srv_name):
|
||||
"""Method called when a new Zeroconf client is detected
|
||||
Return True if the zeroconf client is a Glances server
|
||||
Note: the return code will never be used
|
||||
"""
|
||||
if srv_type != zeroconf_type:
|
||||
return False
|
||||
logger.debug("Check new Zeroconf server: %s / %s" %
|
||||
(srv_type, srv_name))
|
||||
info = zeroconf.getServiceInfo(srv_type, srv_name)
|
||||
if info:
|
||||
new_server_ip = socket.inet_ntoa(info.getAddress())
|
||||
new_server_port = info.getPort()
|
||||
|
||||
# Add server to the global dict
|
||||
self.servers.add_server(srv_name, new_server_ip, new_server_port)
|
||||
logger.info("New Glances server detected (%s from %s:%s)" %
|
||||
(srv_name, new_server_ip, new_server_port))
|
||||
else:
|
||||
logger.warning(
|
||||
"New Glances server detected, but Zeroconf info failed to be grabbed")
|
||||
return True
|
||||
|
||||
def removeService(self, zeroconf, srv_type, srv_name):
|
||||
# Remove the server from the list
|
||||
self.servers.remove_server(srv_name)
|
||||
logger.info(
|
||||
"Glances server %s removed from the autodetect list" % srv_name)
|
||||
|
||||
|
||||
class GlancesAutoDiscoverServer(object):
|
||||
|
||||
"""Implementation of the Zeroconf protocol (server side for the Glances client)"""
|
||||
|
||||
def __init__(self, args=None):
|
||||
if zeroconf_tag:
|
||||
logger.info("Init autodiscover mode (Zeroconf protocol)")
|
||||
try:
|
||||
self.zeroconf = Zeroconf()
|
||||
except socket.error as e:
|
||||
logger.error("Can not start Zeroconf (%s)" % e)
|
||||
self.zeroconf_enable_tag = False
|
||||
else:
|
||||
self.listener = GlancesAutoDiscoverListener()
|
||||
self.browser = ServiceBrowser(
|
||||
self.zeroconf, zeroconf_type, self.listener)
|
||||
self.zeroconf_enable_tag = True
|
||||
else:
|
||||
logger.error(
|
||||
"Can not start autodiscover mode (Zeroconf lib is not installed)")
|
||||
self.zeroconf_enable_tag = False
|
||||
|
||||
def get_servers_list(self):
|
||||
"""Return the current server list (dict of dict)"""
|
||||
if zeroconf_tag and self.zeroconf_enable_tag:
|
||||
return self.listener.get_servers_list()
|
||||
else:
|
||||
return []
|
||||
|
||||
def set_server(self, server_pos, key, value):
|
||||
"""Set the key to the value for the server_pos (position in the list)"""
|
||||
if zeroconf_tag and self.zeroconf_enable_tag:
|
||||
self.listener.set_server(server_pos, key, value)
|
||||
|
||||
def close(self):
|
||||
if zeroconf_tag and self.zeroconf_enable_tag:
|
||||
self.zeroconf.close()
|
||||
|
||||
|
||||
class GlancesAutoDiscoverClient(object):
|
||||
|
||||
"""Implementation of the Zeroconf protocol (client side for the Glances server)"""
|
||||
|
||||
def __init__(self, hostname, args=None):
|
||||
if netifaces_tag:
|
||||
try:
|
||||
zeroconf_bind_address = netifaces.ifaddresses(
|
||||
netifaces.interfaces()[1])[netifaces.AF_INET][0]['addr']
|
||||
except:
|
||||
zeroconf_bind_address = args.bind_address
|
||||
print("Announce the Glances server on the local area network (using %s IP address)" %
|
||||
zeroconf_bind_address)
|
||||
|
||||
if zeroconf_tag:
|
||||
logger.info(
|
||||
"Announce the Glances server on the local area network (using %s IP address)" % zeroconf_bind_address)
|
||||
try:
|
||||
self.zeroconf = Zeroconf()
|
||||
except socket.error as e:
|
||||
logger.error("Can not start Zeroconf (%s)" % e)
|
||||
else:
|
||||
self.info = ServiceInfo(zeroconf_type,
|
||||
hostname + ':' +
|
||||
str(args.port) + '.' + zeroconf_type,
|
||||
address=socket.inet_aton(
|
||||
zeroconf_bind_address),
|
||||
port=args.port,
|
||||
weight=0,
|
||||
priority=0,
|
||||
properties={},
|
||||
server=hostname)
|
||||
self.zeroconf.registerService(self.info)
|
||||
else:
|
||||
logger.error(
|
||||
"Can not announce Glances server on the network (Zeroconf lib is not installed)")
|
||||
else:
|
||||
logger.error(
|
||||
"Can not announce Glances server on the network (Netifaces lib is not installed)")
|
||||
|
||||
def close(self):
|
||||
if zeroconf_tag:
|
||||
self.zeroconf.unregisterService(self.info)
|
||||
self.zeroconf.close()
|
@ -25,35 +25,35 @@ import socket
|
||||
import sys
|
||||
try:
|
||||
from xmlrpc.client import Transport, ServerProxy, ProtocolError, Fault
|
||||
except ImportError:
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from xmlrpclib import Transport, ServerProxy, ProtocolError, Fault
|
||||
try:
|
||||
import http.client as httplib
|
||||
except:
|
||||
except:
|
||||
# Python 2
|
||||
import httplib
|
||||
|
||||
# Import Glances libs
|
||||
from glances.core.glances_globals import version, logger
|
||||
from glances.core.glances_stats import GlancesStatsClient
|
||||
from glances.outputs.glances_curses import GlancesCurses
|
||||
from glances.outputs.glances_curses import GlancesCursesClient
|
||||
from glances.core.glances_autodiscover import GlancesAutoDiscoverServer
|
||||
|
||||
|
||||
class GlancesClientTransport(Transport):
|
||||
|
||||
"""This class overwrite the default XML-RPC transport and manage timeout"""
|
||||
|
||||
|
||||
def set_timeout(self, timeout):
|
||||
self.timeout = timeout
|
||||
|
||||
def make_connection(self, host):
|
||||
return httplib.HTTPConnection(host, timeout=self.timeout)
|
||||
|
||||
|
||||
class GlancesClient(object):
|
||||
|
||||
"""This class creates and manages the TCP client."""
|
||||
|
||||
def __init__(self, config=None, args=None):
|
||||
def __init__(self, config=None, args=None, timeout=7, return_to_browser=False):
|
||||
# Store the arg/config
|
||||
self.args = args
|
||||
self.config = config
|
||||
@ -67,16 +67,21 @@ class GlancesClient(object):
|
||||
args.client, args.port)
|
||||
else:
|
||||
uri = 'http://{0}:{1}'.format(args.client, args.port)
|
||||
logger.debug("Try to connect to {0}".format(uri))
|
||||
|
||||
# Try to connect to the URI
|
||||
transport = GlancesClientTransport()
|
||||
# Configure the server timeout to 7 seconds
|
||||
transport.set_timeout(7)
|
||||
# Configure the server timeout
|
||||
transport.set_timeout(timeout)
|
||||
try:
|
||||
self.client = ServerProxy(uri, transport = transport)
|
||||
self.client = ServerProxy(uri, transport=transport)
|
||||
except Exception as e:
|
||||
logger.error(_("Couldn't create socket {0}: {1}").format(uri, e))
|
||||
sys.exit(2)
|
||||
msg = _("Client couldn't create socket {0}: {1}").format(uri, e)
|
||||
if not return_to_browser:
|
||||
logger.critical(msg)
|
||||
sys.exit(2)
|
||||
else:
|
||||
logger.error(msg)
|
||||
|
||||
def set_mode(self, mode='glances'):
|
||||
"""Set the client mode.
|
||||
@ -95,7 +100,7 @@ class GlancesClient(object):
|
||||
"""
|
||||
return self.mode
|
||||
|
||||
def login(self):
|
||||
def login(self, return_to_browser=False):
|
||||
"""Logon to the server."""
|
||||
ret = True
|
||||
|
||||
@ -107,30 +112,41 @@ class GlancesClient(object):
|
||||
client_version = self.client.init()
|
||||
except socket.error as err:
|
||||
# Fallback to SNMP
|
||||
logger.error(_("Connection to Glances server failed"))
|
||||
logger.error("Connection to Glances server failed (%s)" % err)
|
||||
self.set_mode('snmp')
|
||||
fallbackmsg = _("Trying fallback to SNMP...")
|
||||
print(fallbackmsg)
|
||||
if not return_to_browser:
|
||||
print(fallbackmsg)
|
||||
else:
|
||||
logger.info(fallbackmsg)
|
||||
except ProtocolError as err:
|
||||
# Others errors
|
||||
if str(err).find(" 401 ") > 0:
|
||||
logger.error(_("Connection to server failed (Bad password)"))
|
||||
msg = _("Connection to server failed (Bad password)")
|
||||
else:
|
||||
logger.error(_("Connection to server failed ({0})").format(err))
|
||||
sys.exit(2)
|
||||
msg = _("Connection to server failed ({0})").format(err)
|
||||
if not return_to_browser:
|
||||
logger.critical(msg)
|
||||
sys.exit(2)
|
||||
else:
|
||||
logger.error(msg)
|
||||
return False
|
||||
|
||||
if self.get_mode() == 'glances' and version[:3] == client_version[:3]:
|
||||
# Init stats
|
||||
self.stats = GlancesStatsClient()
|
||||
self.stats.set_plugins(json.loads(self.client.getAllPlugins()))
|
||||
logger.debug(
|
||||
"Client version: %s / Server version: %s" % (version, client_version))
|
||||
else:
|
||||
logger.error("Client version: %s / Server version: %s" % (version, client_version))
|
||||
logger.error(
|
||||
"Client and server not compatible: Client version: %s / Server version: %s" % (version, client_version))
|
||||
|
||||
else:
|
||||
self.set_mode('snmp')
|
||||
|
||||
if self.get_mode() == 'snmp':
|
||||
logger.info(_("Trying to grab stats by SNMP..."))
|
||||
if self.get_mode() == 'snmp':
|
||||
logger.info("Trying to grab stats by SNMP...")
|
||||
# Fallback to SNMP if needed
|
||||
from glances.core.glances_stats import GlancesStatsClientSNMP
|
||||
|
||||
@ -138,8 +154,11 @@ class GlancesClient(object):
|
||||
self.stats = GlancesStatsClientSNMP(args=self.args)
|
||||
|
||||
if not self.stats.check_snmp():
|
||||
logger.error(_("Connection to SNMP server failed"))
|
||||
sys.exit(2)
|
||||
logger.error("Connection to SNMP server failed")
|
||||
if not return_to_browser:
|
||||
sys.exit(2)
|
||||
else:
|
||||
return False
|
||||
|
||||
if ret:
|
||||
# Load limits from the configuration file
|
||||
@ -147,7 +166,7 @@ class GlancesClient(object):
|
||||
self.stats.load_limits(self.config)
|
||||
|
||||
# Init screen
|
||||
self.screen = GlancesCurses(args=self.args)
|
||||
self.screen = GlancesCursesClient(args=self.args)
|
||||
|
||||
# Return result
|
||||
return ret
|
||||
@ -202,14 +221,19 @@ class GlancesClient(object):
|
||||
# Grab success
|
||||
return "SNMP"
|
||||
|
||||
def serve_forever(self):
|
||||
def serve_forever(self, return_to_browser=False):
|
||||
"""Main client loop."""
|
||||
while True:
|
||||
|
||||
exitkey = False
|
||||
|
||||
while True and not exitkey:
|
||||
# Update the stats
|
||||
cs_status = self.update()
|
||||
|
||||
# Update the screen
|
||||
self.screen.update(self.stats, cs_status=cs_status)
|
||||
exitkey = self.screen.update(self.stats,
|
||||
cs_status=cs_status,
|
||||
return_to_browser=return_to_browser)
|
||||
|
||||
def end(self):
|
||||
"""End of the client session."""
|
||||
|
185
glances/core/glances_client_browser.py
Normal file
185
glances/core/glances_client_browser.py
Normal file
@ -0,0 +1,185 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# Copyright (C) 2014 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 Glances client browser (list of Glances server)."""
|
||||
|
||||
# Import system libs
|
||||
import json
|
||||
import socket
|
||||
try:
|
||||
from xmlrpc.client import ServerProxy, Fault, ProtocolError
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from xmlrpclib import ServerProxy, Fault, ProtocolError
|
||||
|
||||
# Import Glances libs
|
||||
from glances.core.glances_globals import logger
|
||||
from glances.outputs.glances_curses import GlancesCursesBrowser
|
||||
from glances.core.glances_client import GlancesClientTransport, GlancesClient
|
||||
from glances.core.glances_autodiscover import GlancesAutoDiscoverServer
|
||||
from glances.core.glances_staticlist import GlancesStaticServer
|
||||
|
||||
|
||||
class GlancesClientBrowser(object):
|
||||
|
||||
"""This class creates and manages the TCP client browser (servers' list)."""
|
||||
|
||||
def __init__(self, config=None, args=None):
|
||||
# Store the arg/config
|
||||
self.args = args
|
||||
self.config = config
|
||||
|
||||
# Init the static server list (if defined)
|
||||
self.static_server = GlancesStaticServer(config=self.config)
|
||||
|
||||
# Start the autodiscover mode (Zeroconf listener)
|
||||
if not self.args.disable_autodiscover:
|
||||
self.autodiscover_server = GlancesAutoDiscoverServer()
|
||||
else:
|
||||
self.autodiscover_server = None
|
||||
|
||||
# Init screen
|
||||
self.screen = GlancesCursesBrowser(args=self.args)
|
||||
|
||||
def get_servers_list(self):
|
||||
"""
|
||||
Return the current server list (list of dict)
|
||||
Merge of static + autodiscover servers list
|
||||
"""
|
||||
ret = []
|
||||
|
||||
if self.args.browser:
|
||||
ret = self.static_server.get_servers_list()
|
||||
if self.autodiscover_server is not None:
|
||||
ret = self.static_server.get_servers_list() + self.autodiscover_server.get_servers_list()
|
||||
|
||||
return ret
|
||||
|
||||
def serve_forever(self):
|
||||
"""Main client loop."""
|
||||
while True:
|
||||
# No need to update the server list
|
||||
# It's done by the GlancesAutoDiscoverListener class (glances_autodiscover.py)
|
||||
# For each server in the list, grab elementary stats (CPU, LOAD, MEM, OS...)
|
||||
# logger.debug(self.get_servers_list())
|
||||
try:
|
||||
for v in self.get_servers_list():
|
||||
# !!! Perhaps, it will be better to store the ServerProxy instance in the get_servers_list
|
||||
if v['password'] != "":
|
||||
uri = 'http://{0}:{1}@{2}:{3}'.format(v['username'], v['password'],
|
||||
v['ip'], v['port'])
|
||||
else:
|
||||
uri = 'http://{0}:{1}'.format(v['ip'], v['port'])
|
||||
# Try to connect to the URI
|
||||
t = GlancesClientTransport()
|
||||
t.set_timeout(3)
|
||||
# Get common stats
|
||||
try:
|
||||
s = ServerProxy(uri, transport=t)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Client browser couldn't create socket {0}: {1}".format(uri, e))
|
||||
else:
|
||||
try:
|
||||
# LOAD
|
||||
v['load_min5'] = json.loads(s.getLoad())['min5']
|
||||
# CPU%
|
||||
v['cpu_percent'] = 100 - \
|
||||
json.loads(s.getCpu())['idle']
|
||||
# MEM%
|
||||
v['mem_percent'] = json.loads(
|
||||
s.getMem())['percent']
|
||||
# OS (Human Readable name)
|
||||
v['hr_name'] = json.loads(s.getSystem())['hr_name']
|
||||
# Status
|
||||
v['status'] = 'ONLINE'
|
||||
except (socket.error, Fault, KeyError) as e:
|
||||
logger.debug(
|
||||
"Error while grabbing stats form {0}: {1}".format(uri, e))
|
||||
v['status'] = 'OFFLINE'
|
||||
except ProtocolError as e:
|
||||
if str(e).find(" 401 ") > 0:
|
||||
# Error 401 (Authentication failed)
|
||||
# Password is not the good one...
|
||||
v['password'] = None
|
||||
v['status'] = 'PROTECTED'
|
||||
else:
|
||||
v['status'] = 'OFFLINE'
|
||||
logger.debug(
|
||||
"Can not grab stats from {0}: {1}".format(uri, e))
|
||||
# List can change size during iteration...
|
||||
except RuntimeError:
|
||||
logger.debug(
|
||||
"Server list dictionnary change inside the loop (wait next update)")
|
||||
|
||||
# Update the screen (list or Glances client)
|
||||
if self.screen.get_active() is None:
|
||||
# Display the Glances browser
|
||||
self.screen.update(self.get_servers_list())
|
||||
else:
|
||||
# Display the Glances client for the selected server
|
||||
logger.debug("Selected server: %s" % self.get_servers_list()[self.screen.get_active()])
|
||||
# A password is needed to access to the server's stats
|
||||
if self.get_servers_list()[self.screen.get_active()]['password'] is None:
|
||||
from hashlib import sha256
|
||||
# Display a popup to enter password
|
||||
clear_password = self.screen.display_popup(_("Password needed for %s: " % v['name']), is_input=True)
|
||||
# Hash with SHA256
|
||||
encoded_password = sha256(clear_password).hexdigest()
|
||||
# Static list then dynamic one
|
||||
if self.screen.get_active() >= len(self.static_server.get_servers_list()):
|
||||
self.autodiscover_server.set_server(self.screen.get_active() - len(self.static_server.get_servers_list()),
|
||||
'password',
|
||||
encoded_password)
|
||||
else:
|
||||
self.static_server.set_server(self.screen.get_active(),
|
||||
'password',
|
||||
encoded_password)
|
||||
|
||||
# Display the Glance client on the selected server
|
||||
logger.info("Connect Glances client to the %s server" %
|
||||
self.get_servers_list()[self.screen.get_active()]['key'])
|
||||
|
||||
# Init the client
|
||||
args_server = self.args
|
||||
|
||||
# Overwrite connection setting
|
||||
args_server.client = self.get_servers_list()[self.screen.get_active()]['ip']
|
||||
args_server.port = self.get_servers_list()[self.screen.get_active()]['port']
|
||||
args_server.username = self.get_servers_list()[self.screen.get_active()]['username']
|
||||
args_server.password = self.get_servers_list()[self.screen.get_active()]['password']
|
||||
client = GlancesClient(config=self.config,
|
||||
args=args_server)
|
||||
|
||||
# Test if client and server are in the same major version
|
||||
if not client.login(return_to_browser=True):
|
||||
self.screen.display_popup(_("Sorry, can not connect to %s (see log file for additional information)" % v['name']))
|
||||
else:
|
||||
# Start the client loop
|
||||
client.serve_forever(return_to_browser=True)
|
||||
|
||||
logger.debug("Disconnect Glances client from the %s server" %
|
||||
self.get_servers_list()[self.screen.get_active()]['key'])
|
||||
|
||||
# Return to the browser (no server selected)
|
||||
self.screen.set_active(None)
|
||||
|
||||
def end(self):
|
||||
"""End of the client browser session."""
|
||||
self.screen.end()
|
@ -53,7 +53,7 @@ class Config(object):
|
||||
|
||||
def __init__(self, location=None):
|
||||
self.location = location
|
||||
|
||||
|
||||
self.config_filename = 'glances.conf'
|
||||
|
||||
self.parser = RawConfigParser()
|
||||
@ -72,7 +72,8 @@ class Config(object):
|
||||
self.parser.read(config_file)
|
||||
logger.info(_("Read configuration file %s") % config_file)
|
||||
except UnicodeDecodeError as e:
|
||||
logger.error(_("Cannot decode configuration file '{0}': {1}").format(config_file, e))
|
||||
logger.error(
|
||||
_("Cannot decode configuration file '{0}': {1}").format(config_file, e))
|
||||
sys.exit(1)
|
||||
# Save the loaded configuration file path (issue #374)
|
||||
self._loaded_config_file = config_file
|
||||
@ -100,7 +101,8 @@ class Config(object):
|
||||
* {/usr/local,}/etc directory (system-wide settings)
|
||||
"""
|
||||
paths = []
|
||||
conf_path = os.path.realpath(os.path.join(work_path, '..', '..', 'conf'))
|
||||
conf_path = os.path.realpath(
|
||||
os.path.join(work_path, '..', '..', 'conf'))
|
||||
|
||||
if self.location is not None:
|
||||
paths.append(self.location)
|
||||
@ -110,12 +112,15 @@ class Config(object):
|
||||
|
||||
if is_linux or is_bsd:
|
||||
paths.append(os.path.join(
|
||||
os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'),
|
||||
os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser(
|
||||
'~/.config'),
|
||||
appname, self.config_filename))
|
||||
if hasattr(sys, 'real_prefix') or is_bsd:
|
||||
paths.append(os.path.join(sys.prefix, 'etc', appname, self.config_filename))
|
||||
paths.append(
|
||||
os.path.join(sys.prefix, 'etc', appname, self.config_filename))
|
||||
else:
|
||||
paths.append(os.path.join('/etc', appname, self.config_filename))
|
||||
paths.append(
|
||||
os.path.join('/etc', appname, self.config_filename))
|
||||
elif is_mac:
|
||||
paths.append(os.path.join(
|
||||
os.path.expanduser('~/Library/Application Support/'),
|
||||
|
@ -82,7 +82,7 @@ class GlancesMain(object):
|
||||
dest='disable_log', help=_('disable log module'))
|
||||
parser.add_argument('--disable-bold', action='store_false', default=True,
|
||||
dest='disable_bold', help=_('disable bold mode in the terminal'))
|
||||
parser.add_argument('--enable-process-extended', action='store_false', default=False,
|
||||
parser.add_argument('--enable-process-extended', action='store_true', default=False,
|
||||
dest='enable_process_extended', help=_('enable extended stats on top process'))
|
||||
parser.add_argument('--enable-history', action='store_true', default=False,
|
||||
dest='enable_history', help=_('enable the history mode'))
|
||||
@ -96,6 +96,10 @@ class GlancesMain(object):
|
||||
help=_('connect to a Glances server by IPv4/IPv6 address or hostname'))
|
||||
parser.add_argument('-s', '--server', action='store_true', default=False,
|
||||
dest='server', help=_('run Glances in server mode'))
|
||||
parser.add_argument('--browser', action='store_true', default=False,
|
||||
dest='browser', help=_('start the client browser (list of servers)'))
|
||||
parser.add_argument('--disable-autodiscover', action='store_true', default=False,
|
||||
dest='disable_autodiscover', help=_('disable autodiscover feature'))
|
||||
parser.add_argument('-p', '--port', default=None, type=int, dest='port',
|
||||
help=_('define the client/server TCP port [default: {0}]').format(self.server_port))
|
||||
parser.add_argument('-B', '--bind', default='0.0.0.0', dest='bind_address',
|
||||
@ -122,7 +126,7 @@ class GlancesMain(object):
|
||||
dest='webserver', help=_('run Glances in web server mode'))
|
||||
# Display options
|
||||
parser.add_argument('-f', '--process-filter', default=None, type=str,
|
||||
dest='process_filter', help=_('set the process filter patern (regular expression)'))
|
||||
dest='process_filter', help=_('set the process filter pattern (regular expression)'))
|
||||
parser.add_argument('--process-short-name', action='store_true', default=False,
|
||||
dest='process_short_name', help=_('force short name for processes name'))
|
||||
if not is_windows:
|
||||
@ -160,6 +164,10 @@ class GlancesMain(object):
|
||||
else:
|
||||
args.port = self.server_port
|
||||
|
||||
# Autodiscover
|
||||
if args.disable_autodiscover:
|
||||
logger.info("Auto discover mode is disabled")
|
||||
|
||||
# In web server mode, defaul refresh time: 5 sec
|
||||
if args.webserver:
|
||||
args.time = 5
|
||||
@ -236,11 +244,15 @@ class GlancesMain(object):
|
||||
|
||||
def is_standalone(self):
|
||||
"""Return True if Glances is running in standalone mode."""
|
||||
return not self.args.client and not self.args.server and not self.args.webserver
|
||||
return not self.args.client and not self.args.browser and not self.args.server and not self.args.webserver
|
||||
|
||||
def is_client(self):
|
||||
"""Return True if Glances is running in client mode."""
|
||||
return self.args.client and not self.args.server
|
||||
return (self.args.client or self.args.browser) and not self.args.server
|
||||
|
||||
def is_client_browser(self):
|
||||
"""Return True if Glances is running in client browser mode."""
|
||||
return self.args.browser and not self.args.server
|
||||
|
||||
def is_server(self):
|
||||
"""Return True if Glances is running in server mode."""
|
||||
|
0
glances/core/glances_processes.py
Executable file → Normal file
0
glances/core/glances_processes.py
Executable file → Normal file
@ -35,6 +35,7 @@ except ImportError: # Python 2
|
||||
from glances.core.glances_globals import version, logger
|
||||
from glances.core.glances_stats import GlancesStatsServer
|
||||
from glances.core.glances_timer import Timer
|
||||
from glances.core.glances_autodiscover import GlancesAutoDiscoverClient
|
||||
|
||||
|
||||
class GlancesXMLRPCHandler(SimpleXMLRPCRequestHandler):
|
||||
@ -175,8 +176,7 @@ class GlancesInstance(object):
|
||||
if item.startswith(header):
|
||||
try:
|
||||
# Update the stat
|
||||
# !!! All the stat are updated before one grab (not optimized)
|
||||
self.stats.update()
|
||||
self.__update__()
|
||||
# Return the attribute
|
||||
return getattr(self.stats, item)
|
||||
except Exception:
|
||||
@ -195,11 +195,14 @@ class GlancesServer(object):
|
||||
cached_time=1,
|
||||
config=None,
|
||||
args=None):
|
||||
# Args
|
||||
self.args = args
|
||||
|
||||
# Init the XML RPC server
|
||||
try:
|
||||
self.server = GlancesXMLRPCServer(args.bind_address, args.port, requestHandler)
|
||||
except Exception as e:
|
||||
logger.error(_("Cannot start Glances server: {0}").format(e))
|
||||
logger.critical(_("Cannot start Glances server: {0}").format(e))
|
||||
sys.exit(2)
|
||||
|
||||
# The users dict
|
||||
@ -212,6 +215,12 @@ class GlancesServer(object):
|
||||
self.server.register_introspection_functions()
|
||||
self.server.register_instance(GlancesInstance(cached_time, config))
|
||||
|
||||
if not self.args.disable_autodiscover:
|
||||
# Note: The Zeroconf service name will be based on the hostname
|
||||
self.autodiscover_client = GlancesAutoDiscoverClient(socket.gethostname(), args)
|
||||
else:
|
||||
logger.info("Glances autodiscover announce is disabled")
|
||||
|
||||
def add_user(self, username, password):
|
||||
"""Add an user to the dictionary."""
|
||||
self.server.user_dict[username] = password
|
||||
@ -227,4 +236,6 @@ class GlancesServer(object):
|
||||
|
||||
def end(self):
|
||||
"""End of the Glances server session."""
|
||||
if not self.args.disable_autodiscover:
|
||||
self.autodiscover_client.close()
|
||||
self.server_close()
|
||||
|
@ -22,7 +22,7 @@
|
||||
# Import Glances libs
|
||||
from glances.core.glances_globals import logger
|
||||
from glances.core.glances_stats import GlancesStats
|
||||
from glances.outputs.glances_curses import GlancesCurses
|
||||
from glances.outputs.glances_curses import GlancesCursesStandalone
|
||||
from glances.core.glances_globals import glances_processes, is_windows
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ class GlancesStandalone(object):
|
||||
self.csv_tag = False
|
||||
|
||||
# Init screen
|
||||
self.screen = GlancesCurses(args=args)
|
||||
self.screen = GlancesCursesStandalone(args=args)
|
||||
|
||||
def serve_forever(self):
|
||||
"""Main loop for the CLI."""
|
||||
|
88
glances/core/glances_staticlist.py
Normal file
88
glances/core/glances_staticlist.py
Normal file
@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# Copyright (C) 2014 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 Glances server static list """
|
||||
|
||||
# System lib
|
||||
from socket import gethostbyname, gaierror
|
||||
|
||||
# Import Glances libs
|
||||
from glances.core.glances_globals import logger
|
||||
|
||||
|
||||
class GlancesStaticServer(object):
|
||||
|
||||
"""Manage the static servers list for the client browser"""
|
||||
|
||||
_section = "serverlist"
|
||||
|
||||
def __init__(self, config=None, args=None):
|
||||
# server_dict is a list of dict (JSON compliant)
|
||||
# [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'cpu': 3, 'mem': 34 ...} ... ]
|
||||
# Load the configuration file
|
||||
self._server_list = self.load(config)
|
||||
|
||||
def load(self, config):
|
||||
"""Load the server list from the configuration file"""
|
||||
|
||||
server_list = []
|
||||
|
||||
if config is None:
|
||||
logger.warning("No configuration file available. Can not load server list.")
|
||||
elif not config.has_section(self._section):
|
||||
logger.warning("No [%s] section in the configuration file. Can not load server list." % self._section)
|
||||
else:
|
||||
logger.info("Start reading the [%s] section in the configuration file" % self._section)
|
||||
for i in range(1, 256):
|
||||
new_server = {}
|
||||
postfix = 'server_%s_' % str(i)
|
||||
# Read the server name (mandatory)
|
||||
for s in ['name', 'port']:
|
||||
new_server[s] = config.get_raw_option(self._section, '%s%s' % (postfix, s))
|
||||
if new_server['name'] is not None:
|
||||
# Manage optionnal information
|
||||
if new_server['port'] is None:
|
||||
new_server['port'] = 61209
|
||||
new_server['username'] = 'glances'
|
||||
new_server['password'] = ''
|
||||
try:
|
||||
new_server['ip'] = gethostbyname(new_server['name'])
|
||||
except gaierror as e:
|
||||
logger.error("Can not get IP address for server %s (%s)" % (new_server['name'], e))
|
||||
continue
|
||||
new_server['key'] = new_server['name'] + ':' + new_server['port']
|
||||
new_server['status'] = 'UNKNOWN'
|
||||
|
||||
# Add the server to the list
|
||||
logger.debug("Add server %s to the static list" % new_server['name'])
|
||||
server_list.append(new_server)
|
||||
|
||||
# Server list loaded
|
||||
logger.info("%s server(s) loaded from the configuration file" % len(server_list))
|
||||
logger.debug("Static server list: %s" % server_list)
|
||||
|
||||
return server_list
|
||||
|
||||
def get_servers_list(self):
|
||||
"""Return the current server list (dict of dict)"""
|
||||
return self._server_list
|
||||
|
||||
def set_server(self, server_pos, key, value):
|
||||
"""Set the key to the value for the server_pos (position in the list)"""
|
||||
self._server_list[server_pos][key] = value
|
@ -41,9 +41,12 @@ else:
|
||||
curses = WCurseLight()
|
||||
|
||||
|
||||
class GlancesCurses(object):
|
||||
class _GlancesCurses(object):
|
||||
|
||||
"""This class manages the curses display (and key pressed)."""
|
||||
"""
|
||||
This class manages the curses display (and key pressed).
|
||||
Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser
|
||||
"""
|
||||
|
||||
def __init__(self, args=None):
|
||||
# Init args
|
||||
@ -144,7 +147,7 @@ class GlancesCurses(object):
|
||||
self.filter_color = A_BOLD
|
||||
|
||||
# Define the colors list (hash table) for stats
|
||||
self.__colors_list = {
|
||||
self.colors_list = {
|
||||
'DEFAULT': self.no_color,
|
||||
'UNDERLINE': curses.A_UNDERLINE,
|
||||
'BOLD': A_BOLD,
|
||||
@ -195,7 +198,7 @@ class GlancesCurses(object):
|
||||
'Stats history disabled because MatPlotLib is not installed')
|
||||
|
||||
def set_cursor(self, value):
|
||||
"""Configure the cursor
|
||||
"""Configure the curse cursor apparence
|
||||
0: invisible
|
||||
1: visible
|
||||
2: very visible
|
||||
@ -206,29 +209,34 @@ class GlancesCurses(object):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def __get_key(self, window):
|
||||
def get_key(self, window):
|
||||
# Catch ESC key AND numlock key (issue #163)
|
||||
keycode = [0, 0]
|
||||
keycode[0] = window.getch()
|
||||
keycode[1] = window.getch()
|
||||
|
||||
if keycode != [-1, -1]:
|
||||
logger.debug("Keypressed (code: %s)" % keycode)
|
||||
|
||||
if keycode[0] == 27 and keycode[1] != -1:
|
||||
# Do not escape on specials keys
|
||||
return -1
|
||||
else:
|
||||
return keycode[0]
|
||||
|
||||
def __catch_key(self):
|
||||
def __catch_key(self, return_to_browser=False):
|
||||
# Catch the pressed key
|
||||
# ~ self.pressedkey = self.term_window.getch()
|
||||
self.pressedkey = self.__get_key(self.term_window)
|
||||
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")
|
||||
sys.exit(0)
|
||||
if return_to_browser:
|
||||
logger.info("Stop Glances client and return to the browser")
|
||||
else:
|
||||
self.end()
|
||||
logger.info("Stop Glances")
|
||||
sys.exit(0)
|
||||
elif self.pressedkey == 10:
|
||||
# 'ENTER' > Edit the process filter
|
||||
self.edit_filter = not self.edit_filter
|
||||
@ -429,8 +437,8 @@ class GlancesCurses(object):
|
||||
max_processes_displayed = 0
|
||||
if glances_processes.get_max_processes() is None or \
|
||||
glances_processes.get_max_processes() != max_processes_displayed:
|
||||
logger.debug(
|
||||
_("Set number of displayed processes to %s") % max_processes_displayed)
|
||||
logger.debug("Set number of displayed processes to %s" %
|
||||
max_processes_displayed)
|
||||
glances_processes.set_max_processes(max_processes_displayed)
|
||||
|
||||
stats_processlist = stats.get_plugin(
|
||||
@ -615,7 +623,7 @@ class GlancesCurses(object):
|
||||
if is_input and not is_windows:
|
||||
# Create a subwindow for the text field
|
||||
subpop = popup.derwin(1, input_size, 2, 2 + len(m))
|
||||
subpop.attron(self.__colors_list['FILTER'])
|
||||
subpop.attron(self.colors_list['FILTER'])
|
||||
# Init the field with the current value
|
||||
if input_value is not None:
|
||||
subpop.addnstr(0, 0, input_value, len(input_value))
|
||||
@ -629,10 +637,10 @@ class GlancesCurses(object):
|
||||
self.set_cursor(0)
|
||||
if textbox.gather() != '':
|
||||
logger.debug(
|
||||
_("User enters the following process filter patern: %s") % textbox.gather())
|
||||
"User enters the following process filter patern: %s" % textbox.gather())
|
||||
return textbox.gather()[:-1]
|
||||
else:
|
||||
logger.debug(_("User clears the process filter patern"))
|
||||
logger.debug("User clears the process filter patern")
|
||||
return None
|
||||
else:
|
||||
# Display the popup
|
||||
@ -704,7 +712,7 @@ class GlancesCurses(object):
|
||||
m['msg'],
|
||||
# Do not disply outside the screen
|
||||
screen_x - x,
|
||||
self.__colors_list[m['decoration']])
|
||||
self.colors_list[m['decoration']])
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
@ -714,7 +722,8 @@ class GlancesCurses(object):
|
||||
# occupy several bytes
|
||||
offset = len(m['msg'].decode("utf-8"))
|
||||
except AttributeError:
|
||||
# Python 3: strings are strings and bytes are bytes, all is good
|
||||
# Python 3: strings are strings and bytes are bytes, all is
|
||||
# good
|
||||
offset = len(m['msg'])
|
||||
x = x + offset
|
||||
|
||||
@ -738,30 +747,44 @@ class GlancesCurses(object):
|
||||
self.erase()
|
||||
self.display(stats, cs_status=cs_status)
|
||||
|
||||
def update(self, stats, cs_status="None"):
|
||||
def update(self, stats, cs_status="None", return_to_browser=False):
|
||||
"""Update the screen.
|
||||
|
||||
Wait for __refresh_time sec / catch key every 100 ms.
|
||||
|
||||
INPUT
|
||||
stats: Stats database to display
|
||||
cs_status:
|
||||
"None": standalone or server mode
|
||||
"Connected": Client is connected to the server
|
||||
"Disconnected": Client is disconnected from the server
|
||||
return_to_browser:
|
||||
True: Do not exist, return to the browser list
|
||||
False: Exit and return to the shell
|
||||
|
||||
OUPUT
|
||||
True: Exit key has been pressed
|
||||
False: Others cases...
|
||||
"""
|
||||
# Flush display
|
||||
self.flush(stats, cs_status=cs_status)
|
||||
|
||||
# Wait
|
||||
exitkey = False
|
||||
countdown = Timer(self.__refresh_time)
|
||||
while not countdown.finished():
|
||||
while not countdown.finished() and not exitkey:
|
||||
# Getkey
|
||||
if self.__catch_key() > -1:
|
||||
pressedkey = self.__catch_key(return_to_browser=return_to_browser)
|
||||
# Is it an exit key ?
|
||||
exitkey = (pressedkey == ord('\x1b') or pressedkey == ord('q'))
|
||||
if not exitkey and pressedkey > -1:
|
||||
# Redraw display
|
||||
self.flush(stats, cs_status=cs_status)
|
||||
# Wait 100ms...
|
||||
curses.napms(100)
|
||||
|
||||
return exitkey
|
||||
|
||||
def get_stats_display_width(self, curse_msg, without_option=False):
|
||||
"""Return the width of the formatted curses message.
|
||||
|
||||
@ -793,6 +816,260 @@ class GlancesCurses(object):
|
||||
else:
|
||||
return c + 1
|
||||
|
||||
|
||||
class GlancesCursesStandalone(_GlancesCurses):
|
||||
|
||||
"""Class for the Glances' curse standalone"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GlancesCursesClient(_GlancesCurses):
|
||||
|
||||
"""Class for the Glances' curse client"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GlancesCursesBrowser(_GlancesCurses):
|
||||
|
||||
"""Class for the Glances' curse client browser"""
|
||||
|
||||
def __init__(self, args=None):
|
||||
# Init the father class
|
||||
_GlancesCurses.__init__(self, args=args)
|
||||
|
||||
_colors_list = {
|
||||
'UNKNOWN': self.no_color,
|
||||
'ONLINE': self.default_color2,
|
||||
'OFFLINE': self.ifCRITICAL_color2,
|
||||
'PROTECTED': self.ifWARNING_color2,
|
||||
}
|
||||
self.colors_list = dict(self.colors_list.items() + _colors_list.items())
|
||||
|
||||
# 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_init()
|
||||
|
||||
# Active Glances server number
|
||||
self.set_active()
|
||||
|
||||
def set_active(self, index=None):
|
||||
"""Set the active server or None if no server selected"""
|
||||
self.active_server = index
|
||||
return self.active_server
|
||||
|
||||
def get_active(self):
|
||||
"""Return the active server (the one display in front) or None if it is the browser list"""
|
||||
return self.active_server
|
||||
|
||||
def cursor_init(self):
|
||||
"""Init the cursor position to the top of the list"""
|
||||
return self.cursor_set(0)
|
||||
|
||||
def cursor_set(self, pos):
|
||||
"""Set the cursor position and return it"""
|
||||
self.cursor_position = pos
|
||||
return self.cursor_position
|
||||
|
||||
def cursor_get(self):
|
||||
"""Return the cursor position"""
|
||||
return self.cursor_position
|
||||
|
||||
def cursor_up(self):
|
||||
"""Set the cursor to position N-1 in the list"""
|
||||
if self.cursor_position > 0:
|
||||
self.cursor_position -= 1
|
||||
return self.cursor_position
|
||||
|
||||
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
|
||||
return self.cursor_position
|
||||
|
||||
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 %s selected" % (self.cursor_get() + 1))
|
||||
self.set_active(self.cursor_get())
|
||||
elif self.pressedkey == 259:
|
||||
# 'UP' > Up in the server list
|
||||
logger
|
||||
self.cursor_up()
|
||||
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
|
||||
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.get_active()
|
||||
|
||||
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 servers available")
|
||||
elif len(servers_list) == 1:
|
||||
msg = _("One Glances server available")
|
||||
else:
|
||||
msg = _("%d Glances servers available" %
|
||||
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],
|
||||
['load_min5', _('LOAD'), 6],
|
||||
['cpu_percent', _('CPU%'), 5],
|
||||
['mem_percent', _('MEM%'), 5],
|
||||
['status', _('STATUS'), 8],
|
||||
['ip', _('IP'), 15],
|
||||
# ['port', _('PORT'), 5],
|
||||
['hr_name', _('OS'), 16],
|
||||
]
|
||||
y = 2
|
||||
|
||||
# Display table header
|
||||
cpt = 0
|
||||
xc = x + 2
|
||||
for c in column_def:
|
||||
if xc < screen_x and y < screen_y:
|
||||
self.term_window.addnstr(y, xc,
|
||||
c[1],
|
||||
screen_x - x,
|
||||
self.colors_list['BOLD'])
|
||||
xc += c[2] + self.space_between_column
|
||||
cpt += 1
|
||||
y += 1
|
||||
|
||||
# If a servers has been deleted from the list...
|
||||
# ... and if the cursor is in the latest position
|
||||
if self.cursor_get() > len(servers_list) - 1:
|
||||
# Set the cursor position to the latest item
|
||||
self.cursor_set(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(
|
||||
"Can not grab stats {0} from server (KeyError: {1})".format(c[0], e))
|
||||
server_stat[c[0]] = '?'
|
||||
|
||||
# Display line for server stats
|
||||
cpt = 0
|
||||
xc = x
|
||||
|
||||
# Is the line selected ?
|
||||
if line == self.cursor_get():
|
||||
# Display cursor
|
||||
self.term_window.addnstr(y, xc,
|
||||
">",
|
||||
screen_x - xc,
|
||||
self.colors_list['BOLD'])
|
||||
|
||||
xc += 2
|
||||
for c in column_def:
|
||||
if xc < screen_x and y < screen_y:
|
||||
# Display server stats
|
||||
self.term_window.addnstr(y, xc,
|
||||
"%s" % server_stat[c[0]],
|
||||
screen_x - xc,
|
||||
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 is_windows:
|
||||
class glances_textbox(Textbox):
|
||||
|
||||
|
@ -139,7 +139,7 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
ret.append(self.curse_new_line())
|
||||
ret.append(self.curse_new_line())
|
||||
msg = '{0}: {1}'.format("ENTER", _("Edit the process filter patern"))
|
||||
msg = '{0}: {1}'.format("ENTER", _("Edit the process filter pattern"))
|
||||
ret.append(self.curse_add_line(msg))
|
||||
|
||||
|
||||
|
@ -37,7 +37,7 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
# We want to display the stat in the curse interface
|
||||
self.display_curse = True
|
||||
|
||||
|
||||
# Set the message position
|
||||
self.set_align('bottom')
|
||||
|
||||
|
@ -94,6 +94,13 @@ class Plugin(GlancesPlugin):
|
||||
self.stats['os_version'] = ' '.join(os_version[::2])
|
||||
else:
|
||||
self.stats['os_version'] = ""
|
||||
# Add human readable name
|
||||
self.stats['hr_name'] = self.stats['os_name']
|
||||
self.stats['hr_name'] += ' ' + self.stats['os_version']
|
||||
if self.stats['os_name'] == "Linux":
|
||||
self.stats['hr_name'] = self.stats['linux_distro']
|
||||
self.stats['hr_name'] += ' (' + self.stats['platform'] + ')'
|
||||
|
||||
elif self.get_input() == 'snmp':
|
||||
# Update stats using SNMP
|
||||
try:
|
||||
@ -104,10 +111,12 @@ class Plugin(GlancesPlugin):
|
||||
self.stats['os_name'] = self.stats['system_name']
|
||||
# Windows OS tips
|
||||
if self.get_short_system_name() == 'windows':
|
||||
for r,v in snmp_to_human['windows'].iteritems():
|
||||
for r, v in snmp_to_human['windows'].iteritems():
|
||||
if re.search(r, self.stats['system_name']):
|
||||
self.stats['os_name'] = v
|
||||
self.stats['os_name'] = v
|
||||
break
|
||||
# Add human readable name
|
||||
self.stats['hr_name'] = self.stats['os_name']
|
||||
|
||||
return self.stats
|
||||
|
||||
|
@ -48,7 +48,7 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
# Set the message position
|
||||
self.set_align('right')
|
||||
|
||||
|
||||
# Init the stats
|
||||
self.reset()
|
||||
|
||||
@ -63,7 +63,8 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
if self.get_input() == 'local':
|
||||
# Update stats using the standard system lib
|
||||
uptime = datetime.now() - datetime.fromtimestamp(psutil.boot_time())
|
||||
uptime = datetime.now() - \
|
||||
datetime.fromtimestamp(psutil.boot_time())
|
||||
|
||||
# Convert uptime to string (because datetime is not JSONifi)
|
||||
self.stats = str(uptime).split('.')[0]
|
||||
|
@ -20,7 +20,7 @@ systems, processes and so on.
|
||||
The command-line options are the following:
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
display the help and exit
|
||||
show this help message and exit
|
||||
.TP
|
||||
.B \-V, \-\-version
|
||||
show program's version number and exit
|
||||
@ -28,14 +28,11 @@ show program's version number and exit
|
||||
.B \-d, \-\-debug
|
||||
Enable debug mode (log file is /tmp/glances.log)
|
||||
.TP
|
||||
.B \-b, \-\-byte
|
||||
display network rate in byte per second [default: bit per second]
|
||||
.TP
|
||||
.B \-C CONF_FILE, \-\-config CONF_FILE
|
||||
path to the configuration file
|
||||
.TP
|
||||
.B \-\-enable-history
|
||||
enable the history mode
|
||||
.B \-b, \-\-byte
|
||||
display network rate in byte per second [default: bit per second]
|
||||
.TP
|
||||
.B \-\-disable-bold
|
||||
disable bold mode in the terminal
|
||||
@ -64,12 +61,24 @@ disable log module
|
||||
.B \-\-enable-process-extended
|
||||
enable extended stats on top process
|
||||
.TP
|
||||
.B \-\-enable-history
|
||||
enable the history mode
|
||||
.TP
|
||||
B \-\-path-history PATH_HISTORY
|
||||
set the export path for graph history
|
||||
.TP
|
||||
.B \-\-output-csv OUTPUT_CSV
|
||||
export stats to a CSV file
|
||||
.TP
|
||||
.B \-s, \-\-server
|
||||
run Glances in server mode
|
||||
.TP
|
||||
.B \-\-browser
|
||||
start the client browser (display list of servers)
|
||||
.TP
|
||||
.B \-\-disable-autodiscover
|
||||
disable autodiscover feature
|
||||
.TP
|
||||
.B \-c CLIENT, \-\-client CLIENT
|
||||
connect to a Glances server by IPv4/IPv6 address or hostname
|
||||
.TP
|
||||
@ -121,7 +130,7 @@ Optimize display for white background
|
||||
You can use the following keys while in Glances:
|
||||
.TP
|
||||
.B ENTER
|
||||
Set the process filter patern (as a regular expression)
|
||||
Set the process filter pattern (as a regular expression)
|
||||
.TP
|
||||
.B a
|
||||
Sort process list automatically
|
||||
|
Loading…
Reference in New Issue
Block a user