Merge branch 'feature/issue418' into develop

This commit is contained in:
Nicolargo 2014-11-29 10:18:28 +01:00
commit 1bc15ad7c0
20 changed files with 1010 additions and 103 deletions

15
NEWS
View File

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

View File

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

View File

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

View File

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

View File

@ -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 != "":

View 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()

View File

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

View 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()

View File

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

View File

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

View 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()

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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