Merge branch 'release/v2.1'

This commit is contained in:
Nicolargo 2014-09-13 15:33:04 +02:00
commit 20fbe4f5d5
55 changed files with 2828 additions and 810 deletions

View File

@ -1,9 +1,14 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "pypy"
install:
- pip install -r requirements.txt
script: python setup.py install
- pip install coveralls
script:
- python setup.py install
- coverage run --source=glances unitest.py
after_success:
- coveralls

View File

@ -20,8 +20,8 @@ https://github.com/jrenner
Packagers
=========
Geoffroy Youri Berret for the Debian package
http://packages.debian.org/fr/sid/glances
Rémi Verchère for the Debian package
https://github.com/rverchere/debian-glances
gasol.wu@gmail.com for the FreeBSD port

47
NEWS
View File

@ -2,6 +2,49 @@
Glances Version 2.x
==============================================================================
Version 2.1
===========
* Add user process filter feature
User can define a process filter pattern (as a regular expression).
The pattern could be defined from the command line (-f <pattern>)
or by pressing the ENTER key in the curse interface.
For the moment, process filter feature is only available in standalone mode.
* Add extended processes informations for top process
Top process stats availables: CPU affinity, extended memory information (shared, text, lib, datat, dirty, swap), openned threads/files and TCP/UDP network sessions, IO nice level
For the moment, extended processes stats are only available in standalone mode.
* Add --process-short-name tag and '/' key to switch between short/command line
* Create a max_processes key in the configuration file
The goal is to reduce the number of displayed processes in the curses UI and
so limit the CPU footprint of the Glances standalone mode.
The API always return all the processes, the key is only active in the curses UI.
If the key is not define, all the processes will be displayed.
The default value is 20 (processes displayed).
For the moment, this feature is only available in standalone mode.
* Alias for network interfaces, disks and sensors
Users can configure alias from the Glances configuration file.
* Add Glances log message (in the /tmp/glances.log file)
The default log level is INFO, you can switch to the DEBUG mode using the -d option on the command line.
* Add RESTFul API to the Web server mode
RestFul API doc: https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API
* Improve SNMP fallback mode for Cisco IOS, VMware ESXi
* Add --theme-white feature to optimize display for white background
* Experimental history feature (--enable-history option on the command line)
This feature allows users to generate graphs within the curse interface.
Graphs are available for CPU, LOAD and MEM.
To generate graph, click on the 'g' key.
To reset the history, press the 'r' key.
Note: This feature uses the matplotlib library.
* CI: Improve Travis coverage
Bugs corrected:
* Quitting glances leaves a column layout to the current terminal (issue #392)
* Glances crashes with malformed UTF-8 sequences in process command lines (issue #391)
* SNMP fallback mode is not Python 3 compliant (issue #386)
* Trouble using batinfo, hddtemp, pysensors w/ Python (issue #324)
Version 2.0.1
=============
@ -325,13 +368,13 @@ Version 1.3
===========
* Add file system stats (total and used space)
* Adapt unit dynamicaly (K, M, G)
* Adapt unit dynamically (K, M, G)
* Add man page (Thanks to Edouard Bourguignon)
Version 1.2
===========
* Resize the terminal and the windows are adapted dynamicaly
* Resize the terminal and the windows are adapted dynamically
* Refresh screen instantanetly when a key is pressed
Version 1.1.3

View File

@ -43,6 +43,18 @@ Optional dependencies:
Installation
============
Glances Auto Install script
---------------------------
Just enter the following command line:
.. code-block:: console
curl -L http://bit.ly/glances | /bin/bash
*Note*: Only supported on some GNU/Linux distributions.
If you want to support others distribs, please contribute to `glancesautoinstall`_.
PyPI: The simple way
--------------------
@ -188,6 +200,8 @@ Documentation
For complete documentation see `glances-doc`_.
If you have any question (after RTFM !), please post it on the official Q&A `forum`_.
Author
======
@ -199,6 +213,7 @@ License
LGPL. See ``COPYING`` for more details.
.. _psutil: https://code.google.com/p/psutil/
.. _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
@ -209,3 +224,4 @@ LGPL. See ``COPYING`` for more details.
.. _colorconsole: https://pypi.python.org/pypi/colorconsole
.. _Puppet: https://puppetlabs.com/puppet/what-is-puppet/
.. _glances-doc: https://github.com/nicolargo/glances/blob/master/docs/glances-doc.rst
.. _forum: https://groups.google.com/forum/?hl=en#!forum/glances-users

View File

@ -51,7 +51,9 @@ critical=90
[network]
# Define the list of hidden network interfaces (comma separeted)
hide=lo
# Default limits (in bits per second aka bps) for interface bitrate
# WLAN0 alias name
wlan0_alias=Wireless
# WLAN0 Default limits (in bits per second aka bps) for interface bitrate
wlan0_rx_careful=4000000
wlan0_rx_warning=5000000
wlan0_rx_critical=6000000
@ -62,6 +64,8 @@ wlan0_tx_critical=1000000
[diskio]
# Define the list of hidden disks (comma separeted)
hide=sda2,sda5
# Alias for sda1
#sda1_alias=IntDisk
[fs]
# Default limits for free filesytem space in %
@ -85,8 +89,17 @@ temperature_hdd_critical=60
battery_careful=80
battery_warning=90
battery_critical=95
# Sensors alias
temp1_alias=Motherboard 0
temp2_alias=Motherboard 1
core 0_alias=CPU Core 0
core 1_alias=CPU Core 1
[processlist]
# Maximum number of processes to show in the UI
# Note: Only limit number of showed processes (not the one returned by the API)
# Default is 20 processes (Top 20)
max_processes=20
# Limit values for CPU/MEM per process in %
# Default values if not defined: 50/70/90
cpu_careful=50

View File

@ -51,7 +51,9 @@ critical=90
#[network]
# Define the list of hidden network interfaces (comma separeted)
#hide=lo
# Default limits (in bits per second aka bps) for interface bitrate
# WLAN 0 alias
#wlan0_alias=Wireless IF
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
#wlan0_rx_careful=4000000
#wlan0_rx_warning=5000000
#wlan0_rx_critical=6000000
@ -62,6 +64,8 @@ critical=90
#[diskio]
# Define the list of hidden disks (comma separeted)
#hide=sda2,sda5
# Alias for sda1
#sda1_alias=IntDisk
[fs]
# Default limits for free filesytem space in %
@ -85,8 +89,17 @@ temperature_hdd_critical=60
battery_careful=80
battery_warning=90
battery_critical=95
# Sensors alias
#temp1_alias=Motherboard 0
#temp2_alias=Motherboard 1
#core 0_alias=CPU Core 0
#core 1_alias=CPU Core 1
[processlist]
# Maximum number of processes to show in the UI
# Note: Only limit number of showed processes (not the one returned by the API)
# Default is 20 processes (Top 20)
max_processes=20
# Limit values for CPU/MEM per process in %
# Default values if not defined: 50/70/90
cpu_careful=50

View File

@ -123,9 +123,9 @@ td.option-group {
<div class="document" id="glances">
<h1 class="title">Glances</h1>
<p>This manual describes <em>Glances</em> version 2.0.1.</p>
<p>This manual describes <em>Glances</em> version 2.1.</p>
<p>Copyright © 2012-2014 Nicolas Hennion &lt;<a class="reference external" href="mailto:nicolas&#64;nicolargo.com">nicolas&#64;nicolargo.com</a>&gt;</p>
<p>June 2014</p>
<p>September 2014</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title first">Table of Contents</p>
<ul class="simple">
@ -142,24 +142,25 @@ td.option-group {
</ul>
</li>
<li><a class="reference internal" href="#configuration" id="id13">Configuration</a></li>
<li><a class="reference internal" href="#anatomy-of-the-application" id="id14">Anatomy Of The Application</a><ul>
<li><a class="reference internal" href="#legend" id="id15">Legend</a></li>
<li><a class="reference internal" href="#header" id="id16">Header</a></li>
<li><a class="reference internal" href="#cpu" id="id17">CPU</a></li>
<li><a class="reference internal" href="#load" id="id18">Load</a></li>
<li><a class="reference internal" href="#memory" id="id19">Memory</a></li>
<li><a class="reference internal" href="#network" id="id20">Network</a></li>
<li><a class="reference internal" href="#disk-i-o" id="id21">Disk I/O</a></li>
<li><a class="reference internal" href="#file-system" id="id22">File System</a></li>
<li><a class="reference internal" href="#sensors" id="id23">Sensors</a></li>
<li><a class="reference internal" href="#processes-list" id="id24">Processes List</a></li>
<li><a class="reference internal" href="#monitored-processes-list" id="id25">Monitored Processes List</a></li>
<li><a class="reference internal" href="#logs" id="id26">Logs</a></li>
<li><a class="reference internal" href="#logs-and-debug-mode" id="id14">Logs and debug mode</a></li>
<li><a class="reference internal" href="#anatomy-of-the-application" id="id15">Anatomy Of The Application</a><ul>
<li><a class="reference internal" href="#legend" id="id16">Legend</a></li>
<li><a class="reference internal" href="#header" id="id17">Header</a></li>
<li><a class="reference internal" href="#cpu" id="id18">CPU</a></li>
<li><a class="reference internal" href="#load" id="id19">Load</a></li>
<li><a class="reference internal" href="#memory" id="id20">Memory</a></li>
<li><a class="reference internal" href="#network" id="id21">Network</a></li>
<li><a class="reference internal" href="#disk-i-o" id="id22">Disk I/O</a></li>
<li><a class="reference internal" href="#file-system" id="id23">File System</a></li>
<li><a class="reference internal" href="#sensors" id="id24">Sensors</a></li>
<li><a class="reference internal" href="#processes-list" id="id25">Processes List</a></li>
<li><a class="reference internal" href="#monitored-processes-list" id="id26">Monitored Processes List</a></li>
<li><a class="reference internal" href="#logs" id="id27">Logs</a></li>
</ul>
</li>
<li><a class="reference internal" href="#other-outputs" id="id27">Other Outputs</a></li>
<li><a class="reference internal" href="#api-documentation" id="id28">API Documentation</a></li>
<li><a class="reference internal" href="#support" id="id29">Support</a></li>
<li><a class="reference internal" href="#other-outputs" id="id28">Other Outputs</a></li>
<li><a class="reference internal" href="#apis-documentations" id="id29">APIs Documentations</a></li>
<li><a class="reference internal" href="#support" id="id30">Support</a></li>
</ul>
</div>
<div class="section" id="introduction">
@ -206,7 +207,7 @@ TCP port <tt class="docutils literal"><span class="pre">-p</span> PORT</tt>.</p>
<p>In client mode, you can set the TCP port of the server <tt class="docutils literal"><span class="pre">-p</span> PORT</tt>.</p>
<p>You can also set a password to access to the server <tt class="docutils literal"><span class="pre">--password</span></tt>.</p>
<p>Default binding address is <tt class="docutils literal">0.0.0.0</tt> (Glances will listen on all the
network interfaces) and TCP port is <tt class="docutils literal">61209</tt>.</p>
available network interfaces) and TCP port is <tt class="docutils literal">61209</tt>.</p>
<p>In client/server mode, limits are set by the server side.</p>
<p>Glances is <tt class="docutils literal">IPv6</tt> compatible. Just use the <tt class="docutils literal"><span class="pre">-B</span> ::</tt> option to bind to
all IPv6 addresses.</p>
@ -215,13 +216,12 @@ client, the latter will try to grab stats using the <tt class="docutils literal"
<pre class="code console literal-block">
<span class="generic output">client$ glances -c &#64;snmpserver</span>
</pre>
<p>Known issues: grab using SNMP is only validated for GNU/Linux with SNMP
v2/2c server.</p>
<p>Note: Stats grabbed by SNMP request are limited.</p>
</div>
<div class="section" id="web-server-mode">
<h2><a class="toc-backref" href="#id9">Web Server Mode</a></h2>
<p>If you want to remotely monitor a machine, called <tt class="docutils literal">server</tt>, from any
device with a web browser, called <tt class="docutils literal">client</tt>, just run on the server:</p>
device with a web browser, just run on the server:</p>
<pre class="code console literal-block">
<span class="generic output">server$ glances -w</span>
</pre>
@ -239,6 +239,7 @@ http://&#64;server:61208
<h1><a class="toc-backref" href="#id10">Command Reference</a></h1>
<div class="section" id="command-line-options">
<h2><a class="toc-backref" href="#id11">Command-Line Options</a></h2>
<blockquote>
<table class="docutils option-list" frame="void" rules="none">
<col class="option" />
<col class="description" />
@ -250,21 +251,16 @@ http://&#64;server:61208
<kbd><span class="option">-V</span>, <span class="option">--version</span></kbd></td>
<td>show program's version number and exit</td></tr>
<tr><td class="option-group">
<kbd><span class="option">-b</span>, <span class="option">--byte</span></kbd></td>
<td>display network rate in byte per second</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-B <var>BIND_ADDRESS</var></span>, <span class="option">--bind <var>BIND_ADDRESS</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>bind server to the given IPv4/IPv6 address or hostname</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-c <var>CLIENT</var></span>, <span class="option">--client <var>CLIENT</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>connect to a Glances server by IPv4/IPv6 address or
hostname</td></tr>
<kbd><span class="option">-d</span>, <span class="option">--debug</span></kbd></td>
<td>Enable debug mode</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-C <var>CONF_FILE</var></span>, <span class="option">--config <var>CONF_FILE</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>path to the configuration file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--enable-history</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>enable the history mode</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--disable-bold</span></kbd></td>
<td>disable bold mode in the terminal</td></tr>
@ -274,7 +270,7 @@ hostname</td></tr>
<tr><td>&nbsp;</td><td>disable disk I/O module</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--disable-fs</span></kbd></td>
<td>disable file system module</td></tr>
<td>disable filesystem module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-network</span></kbd></td>
</tr>
@ -287,6 +283,10 @@ hostname</td></tr>
<kbd><span class="option">--disable-process</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable process module</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--disable-process-extended</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>disable extended stats on top process</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--disable-log</span></kbd></td>
<td>disable log module</td></tr>
@ -295,16 +295,29 @@ hostname</td></tr>
</tr>
<tr><td>&nbsp;</td><td>export stats to a CSV file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-c <var>CLIENT</var></span>, <span class="option">--client <var>CLIENT</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>connect to a Glances server by IPv4/IPv6 address or
hostname</td></tr>
<tr><td class="option-group">
<kbd><span class="option">-s</span>, <span class="option">--server</span></kbd></td>
<td>run Glances in server mode</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-p <var>PORT</var></span>, <span class="option">--port <var>PORT</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>define the client/server TCP port [default: 61209]</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-B <var>BIND_ADDRESS</var></span>, <span class="option">--bind <var>BIND_ADDRESS</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>bind server to the given IPv4/IPv6 address or hostname</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--password-badidea <var>PASSWORD_ARG</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>define password from the command line</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--password</span></kbd></td>
<td>define a client/server password from the prompt or
file</td></tr>
<tr><td class="option-group">
<kbd><span class="option">-s</span>, <span class="option">--server</span></kbd></td>
<td>run Glances in server mode</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--snmp-community <var>SNMP_COMMUNITY</var></span></kbd></td>
</tr>
@ -325,6 +338,9 @@ file</td></tr>
<kbd><span class="option">--snmp-auth <var>SNMP_AUTH</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>SNMP authentication key (only for SNMPv3)</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--snmp-force</span></kbd></td>
<td>force SNMP mode</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-t <var>TIME</var></span>, <span class="option">--time <var>TIME</var></span></kbd></td>
</tr>
@ -332,17 +348,40 @@ file</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-w</span>, <span class="option">--webserver</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>run Glances in Web server mode</td></tr>
<tr><td>&nbsp;</td><td>run Glances in web server mode</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">-f <var>PROCESS_FILTER</var></span>, <span class="option">--process-filter <var>PROCESS_FILTER</var></span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>set the process filter patern (regular expression)</td></tr>
<tr><td class="option-group" colspan="2">
<kbd><span class="option">--process-short-name</span></kbd></td>
</tr>
<tr><td>&nbsp;</td><td>force short name for processes name</td></tr>
<tr><td class="option-group">
<kbd><span class="option">-b</span>, <span class="option">--byte</span></kbd></td>
<td>display network rate in byte per second</td></tr>
<tr><td class="option-group">
<kbd><span class="option">-1</span>, <span class="option">--percpu</span></kbd></td>
<td>start Glances in per CPU mode</td></tr>
<tr><td class="option-group">
<kbd><span class="option">--theme-white</span></kbd></td>
<td>optimize display for white background</td></tr>
</tbody>
</table>
</blockquote>
</div>
<div class="section" id="interactive-commands">
<h2><a class="toc-backref" href="#id12">Interactive Commands</a></h2>
<p>The following commands (key pressed) are supported while in Glances:</p>
<dl class="docutils">
<dt><tt class="docutils literal">ENTER</tt></dt>
<dd><p class="first">Set the process filter
Filter is a regular expression pattern:</p>
<ul class="last simple">
<li>gnome: all processes starting with the gnome string</li>
<li>.*gnome.*: all processes containing the gnome string</li>
</ul>
</dd>
<dt><tt class="docutils literal">a</tt></dt>
<dd><p class="first">Sort process list automatically</p>
<ul class="last simple">
@ -357,8 +396,12 @@ file</td></tr>
<dd>Sort processes by CPU usage</dd>
<dt><tt class="docutils literal">d</tt></dt>
<dd>Show/hide disk I/O stats</dd>
<dt><tt class="docutils literal">e</tt></dt>
<dd>Enable/disable top extended stats</dd>
<dt><tt class="docutils literal">f</tt></dt>
<dd>Show/hide file system stats</dd>
<dt><tt class="docutils literal">g</tt></dt>
<dd>Generate hraphs for current history</dd>
<dt><tt class="docutils literal">h</tt></dt>
<dd>Show/hide the help screen</dd>
<dt><tt class="docutils literal">i</tt></dt>
@ -373,6 +416,8 @@ file</td></tr>
<dd>Sort processes by name</dd>
<dt><tt class="docutils literal">q</tt></dt>
<dd>Quit</dd>
<dt><tt class="docutils literal">r</tt></dt>
<dd>Reset history</dd>
<dt><tt class="docutils literal">s</tt></dt>
<dd>Show/hide sensors stats</dd>
<dt><tt class="docutils literal">t</tt></dt>
@ -387,16 +432,17 @@ file</td></tr>
<dd>Show/hide processes stats</dd>
<dt><tt class="docutils literal">1</tt></dt>
<dd>Switch between global CPU and per-CPU stats</dd>
<dt><tt class="docutils literal">/</tt></dt>
<dd>Switch between short name / command line (processes name)</dd>
</dl>
</div>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id13">Configuration</a></h1>
<p><strong>Caution! Glances version 1.x configuration files are not compatible
with the version 2.x.</strong></p>
<p>No configuration file is mandatory to use Glances.</p>
<p>Furthermore a configuration file is needed to set up limits, disks or
network interfaces to hide and/or monitored processes list.</p>
network interfaces to hide and/or monitored processes list or to define
alias.</p>
<p>By default, the configuration file is under:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
@ -417,6 +463,8 @@ C:\Documents and Settings\&lt;User&gt;\Application Data
<p>Since Windows Vista and newer versions:</p>
<pre class="literal-block">
C:\Users\&lt;User&gt;\AppData\Roaming
or
%userprofile%\AppData\Roaming
</pre>
<p>You can override the default configuration, located in one of the above
directories on your system, except for Windows.</p>
@ -429,10 +477,28 @@ cp /usr/share/doc/glances/glances.conf $XDG_CONFIG_HOME/glances/</span>
<p>On OS X, you should copy the configuration file to
<tt class="docutils literal">~/Library/Application Support/glances/</tt>.</p>
</div>
<div class="section" id="logs-and-debug-mode">
<h1><a class="toc-backref" href="#id14">Logs and debug mode</a></h1>
<p>Glances logs all its internal messages to a log file. By default, only
INFO &amp; WARNING &amp; ERROR &amp;CRITICAL levels are logged, but DEBUG messages
can ben logged using the -d option on the command line.</p>
<p>By default, the configuration file is under:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name" colspan="2">Linux, *BSD and OS X:</th></tr>
<tr class="field"><td>&nbsp;</td><td class="field-body"><tt class="docutils literal">/tmp/glances.conf</tt></td>
</tr>
<tr class="field"><th class="field-name">Windows:</th><td class="field-body"><tt class="docutils literal"><span class="pre">%APPDATA%\Local\temp\glances.conf</span></tt></td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="anatomy-of-the-application">
<h1><a class="toc-backref" href="#id14">Anatomy Of The Application</a></h1>
<h1><a class="toc-backref" href="#id15">Anatomy Of The Application</a></h1>
<div class="section" id="legend">
<h2><a class="toc-backref" href="#id15">Legend</a></h2>
<h2><a class="toc-backref" href="#id16">Legend</a></h2>
<div class="line-block">
<div class="line"><tt class="docutils literal">GREEN</tt> stat counter is <tt class="docutils literal">&quot;OK&quot;</tt></div>
<div class="line"><tt class="docutils literal">BLUE</tt> stat counter is <tt class="docutils literal">&quot;CAREFUL&quot;</tt></div>
@ -443,7 +509,7 @@ cp /usr/share/doc/glances/glances.conf $XDG_CONFIG_HOME/glances/</span>
view.</p>
</div>
<div class="section" id="header">
<h2><a class="toc-backref" href="#id16">Header</a></h2>
<h2><a class="toc-backref" href="#id17">Header</a></h2>
<img alt="images/header.png" src="images/header.png" />
<p>The header shows the hostname, OS name, release version, platform
architecture and system uptime (on the upper right corner).
@ -455,7 +521,7 @@ Additionally, on GNU/Linux, it also shows the kernel version.</p>
<img alt="images/disconnected.png" src="images/disconnected.png" />
</div>
<div class="section" id="cpu">
<h2><a class="toc-backref" href="#id17">CPU</a></h2>
<h2><a class="toc-backref" href="#id18">CPU</a></h2>
<p>Short view:</p>
<img alt="images/cpu.png" src="images/cpu.png" />
<p>If enough horizontal space is available, extended CPU information are
@ -476,7 +542,7 @@ time. The total CPU usage is displayed on the first line.</p>
the <tt class="docutils literal">[cpu]</tt> and/or <tt class="docutils literal">[percpu]</tt> sections.</p>
</div>
<div class="section" id="load">
<h2><a class="toc-backref" href="#id18">Load</a></h2>
<h2><a class="toc-backref" href="#id19">Load</a></h2>
<img alt="images/load.png" src="images/load.png" />
<p>On the <em>No Sheep</em> blog, <em>Zachary Tirrell</em> defines the load average <a class="footnote-reference" href="#id3" id="id1">[1]</a>:</p>
<blockquote>
@ -496,7 +562,7 @@ The first line also displays the number of CPU core.</p>
the <tt class="docutils literal">[load]</tt> section.</p>
</div>
<div class="section" id="memory">
<h2><a class="toc-backref" href="#id19">Memory</a></h2>
<h2><a class="toc-backref" href="#id20">Memory</a></h2>
<p>Glances uses two columns: one for the <tt class="docutils literal">RAM</tt> and one for the <tt class="docutils literal">SWAP</tt>.</p>
<img alt="images/mem.png" src="images/mem.png" />
<p>If enough space is available, Glances displays extended information for
@ -513,7 +579,7 @@ the <tt class="docutils literal">RAM</tt>:</p>
the <tt class="docutils literal">[memory]</tt> and/or <tt class="docutils literal">[memswap]</tt> sections.</p>
</div>
<div class="section" id="network">
<h2><a class="toc-backref" href="#id20">Network</a></h2>
<h2><a class="toc-backref" href="#id21">Network</a></h2>
<img alt="images/network.png" src="images/network.png" />
<p>Glances displays the network interface bit rate. The unit is adapted
dynamically (bits per second, kbits per second, Mbits per second, etc).</p>
@ -521,18 +587,18 @@ dynamically (bits per second, kbits per second, Mbits per second, etc).</p>
(see sample in the configuration file).</p>
<p><em>Note</em>: it is possibile to define a list of network interfaces to hide
and per-interface limit values in the <tt class="docutils literal">[network]</tt> section of the
configuration file.</p>
configuration file and aliases for interface name.</p>
</div>
<div class="section" id="disk-i-o">
<h2><a class="toc-backref" href="#id21">Disk I/O</a></h2>
<h2><a class="toc-backref" href="#id22">Disk I/O</a></h2>
<img alt="images/diskio.png" src="images/diskio.png" />
<p>Glances displays the disk I/O throughput. The unit is adapted dynamically.</p>
<p>There is no alert on this information.</p>
<p><em>Note</em>: it is possible to define a list of disks to hide under the
<tt class="docutils literal">[diskio]</tt> section in the configuration file.</p>
<tt class="docutils literal">[diskio]</tt> section in the configuration file and aliases for disk name.</p>
</div>
<div class="section" id="file-system">
<h2><a class="toc-backref" href="#id22">File System</a></h2>
<h2><a class="toc-backref" href="#id23">File System</a></h2>
<img alt="images/fs.png" src="images/fs.png" />
<p>Glances displays the used and total file system disk space. The unit is
adapted dynamically.</p>
@ -547,7 +613,7 @@ adapted dynamically.</p>
the <tt class="docutils literal">[filesystem]</tt> section.</p>
</div>
<div class="section" id="sensors">
<h2><a class="toc-backref" href="#id23">Sensors</a></h2>
<h2><a class="toc-backref" href="#id24">Sensors</a></h2>
<p>Glances can displays the sensors information using <cite>lm-sensors</cite>,
<cite>hddtemp</cite> and <cite>batinfo</cite> <a class="footnote-reference" href="#id4" id="id2">[2]</a>.</p>
<p>All of the above libraries are available only on Linux.</p>
@ -555,11 +621,11 @@ the <tt class="docutils literal">[filesystem]</tt> section.</p>
temperature only.</p>
<img alt="images/sensors.png" src="images/sensors.png" />
<p>There is no alert on this information.</p>
<p><em>Note</em>: limit values can be overwritten in the configuration file under
the <tt class="docutils literal">[sensors]</tt> section.</p>
<p><em>Note</em>: limit values and sensors alias names can be defined in the configuration
file under the <tt class="docutils literal">[sensors]</tt> section.</p>
</div>
<div class="section" id="processes-list">
<h2><a class="toc-backref" href="#id24">Processes List</a></h2>
<h2><a class="toc-backref" href="#id25">Processes List</a></h2>
<p>Compact view:</p>
<img alt="images/processlist.png" src="images/processlist.png" />
<p>Full view:</p>
@ -612,7 +678,8 @@ automatically sorted by:</p>
<dt><tt class="docutils literal">IOW/s</tt></dt>
<dd>Per process I/O write rate (in Byte/s)</dd>
<dt><tt class="docutils literal">COMMAND</tt></dt>
<dd>Process command line (process name is highlighted)</dd>
<dd>Process command line
User cans switch to the process name by pressing on the <tt class="docutils literal">/</tt> key</dd>
</dl>
<p>Process status legend:</p>
<dl class="docutils">
@ -627,11 +694,20 @@ automatically sorted by:</p>
<dt><tt class="docutils literal">Z</tt></dt>
<dd>Zombie</dd>
</dl>
<p>In standalone mode, additionals informations are provided for the top process:</p>
<img alt="images/processlist-top.png" src="images/processlist-top.png" />
<ul class="simple">
<li>CPU affinity (number of cores used by the process)</li>
<li>Extended memory information (swap, shared, text, lib, data and dirty on Linux)</li>
<li>Openned threads, files and network sessions (TCP and UDP)</li>
<li>IO nice level</li>
</ul>
<p>The extended stats feature could be disabled using the --disable-process-extended option (command line) or the <tt class="docutils literal">e</tt> key (curses interface).</p>
<p><em>Note</em>: limit values can be overwritten in the configuration file under
the <tt class="docutils literal">[process]</tt> section.</p>
</div>
<div class="section" id="monitored-processes-list">
<h2><a class="toc-backref" href="#id25">Monitored Processes List</a></h2>
<h2><a class="toc-backref" href="#id26">Monitored Processes List</a></h2>
<p>The monitored processes list allows user, through the configuration file,
to group processes and quickly show if the number of running processes is
not good.</p>
@ -683,7 +759,7 @@ get the JSON representation of the monitored processes list.</p>
</div>
</div>
<div class="section" id="logs">
<h2><a class="toc-backref" href="#id26">Logs</a></h2>
<h2><a class="toc-backref" href="#id27">Logs</a></h2>
<img alt="images/logs.png" src="images/logs.png" />
<p>A log messages list is displayed in the bottom of the screen if (and
only if):</p>
@ -703,7 +779,7 @@ processes list alerts</li>
</div>
</div>
<div class="section" id="other-outputs">
<h1><a class="toc-backref" href="#id27">Other Outputs</a></h1>
<h1><a class="toc-backref" href="#id28">Other Outputs</a></h1>
<p>It is possible to export statistics to CSV file.</p>
<pre class="code console literal-block">
<span class="generic prompt">$</span> glances --output-csv /tmp/glances.csv
@ -714,17 +790,21 @@ processes list alerts</li>
<li>Stats (comma separated)</li>
</ul>
</div>
<div class="section" id="api-documentation">
<h1><a class="toc-backref" href="#id28">API Documentation</a></h1>
<p>Glances uses a <a class="reference external" href="http://docs.python.org/2/library/simplexmlrpcserver.html">XML-RPC server</a> and can be used by another client software.</p>
<p>API documentation is available at
<a class="reference external" href="https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to">https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to</a>.</p>
<div class="section" id="apis-documentations">
<h1><a class="toc-backref" href="#id29">APIs Documentations</a></h1>
<p>Glances includes a <a class="reference external" href="http://docs.python.org/2/library/simplexmlrpcserver.html">XML-RPC server</a> and a <a class="reference external" href="http://jsonapi.org/">RESTFULL-JSON</a> API which and can be used by another client software.</p>
<p>APIs documentations are available at:</p>
<ul class="simple">
<li>XML-RPC: <a class="reference external" href="https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to">https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to</a></li>
<li>RESTFULL-JSON: <a class="reference external" href="https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API">https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API</a></li>
</ul>
</div>
<div class="section" id="support">
<h1><a class="toc-backref" href="#id29">Support</a></h1>
<h1><a class="toc-backref" href="#id30">Support</a></h1>
<p>To post a question about Glances use case, please post it to the offical Q&amp;A <a class="reference external" href="https://groups.google.com/forum/?hl=en#!forum/glances-users">forum</a>.</p>
<p>To report a bug or a feature request use the bug tracking system at
<a class="reference external" href="https://github.com/nicolargo/glances/issues">https://github.com/nicolargo/glances/issues</a>.</p>
<p>Feel free to contribute!</p>
<p>Feel free to contribute !</p>
<table class="docutils footnote" frame="void" id="id3" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">

View File

@ -2,11 +2,11 @@
Glances
=======
This manual describes *Glances* version 2.0.1.
This manual describes *Glances* version 2.1.
Copyright © 2012-2014 Nicolas Hennion <nicolas@nicolargo.com>
June 2014
September 2014
.. contents:: Table of Contents
@ -75,7 +75,7 @@ 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``.
Default binding address is ``0.0.0.0`` (Glances will listen on all the
network interfaces) and TCP port is ``61209``.
available network interfaces) and TCP port is ``61209``.
In client/server mode, limits are set by the server side.
@ -89,14 +89,13 @@ client, the latter will try to grab stats using the ``SNMP`` protocol:
client$ glances -c @snmpserver
Known issues: grab using SNMP is only validated for GNU/Linux with SNMP
v2/2c server.
Note: Stats grabbed by SNMP request are limited.
Web Server Mode
---------------
If you want to remotely monitor a machine, called ``server``, from any
device with a web browser, called ``client``, just run on the server:
device with a web browser, just run on the server:
.. code-block:: console
@ -123,48 +122,65 @@ Command Reference
Command-Line Options
--------------------
-h, --help show this help message and exit
-V, --version show program's version number and exit
-b, --byte display network rate in byte per second
-B BIND_ADDRESS, --bind BIND_ADDRESS
bind server to the given IPv4/IPv6 address or hostname
-c CLIENT, --client CLIENT
connect to a Glances server by IPv4/IPv6 address or
hostname
-C CONF_FILE, --config CONF_FILE
path to the configuration file
--disable-bold disable bold mode in the terminal
--disable-diskio disable disk I/O module
--disable-fs disable file system module
--disable-network disable network module
--disable-sensors disable sensors module
--disable-process disable process module
--disable-log disable log module
--output-csv OUTPUT_CSV
export stats to a CSV file
-p PORT, --port PORT define the client/server TCP port [default: 61209]
--password define a client/server password from the prompt or
file
-s, --server run Glances in server mode
--snmp-community SNMP_COMMUNITY
SNMP community
--snmp-port SNMP_PORT
SNMP port
--snmp-version SNMP_VERSION
SNMP version (1, 2c or 3)
--snmp-user SNMP_USER
SNMP username (only for SNMPv3)
--snmp-auth SNMP_AUTH
SNMP authentication key (only for SNMPv3)
-t TIME, --time TIME set refresh time in seconds [default: 3 sec]
-w, --webserver run Glances in Web server mode
-1, --percpu start Glances in per CPU mode
-h, --help show this help message and exit
-V, --version show program's version number and exit
-d, --debug Enable debug mode
-C CONF_FILE, --config CONF_FILE
path to the configuration file
--enable-history enable the history mode
--disable-bold disable bold mode in the terminal
--disable-diskio disable disk I/O module
--disable-fs disable filesystem module
--disable-network disable network module
--disable-sensors disable sensors module
--disable-process disable process module
--disable-process-extended
disable extended stats on top process
--disable-log disable log module
--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
-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
--password-badidea PASSWORD_ARG
define password from the command line
--password define a client/server password from the prompt or
file
--snmp-community SNMP_COMMUNITY
SNMP community
--snmp-port SNMP_PORT
SNMP port
--snmp-version SNMP_VERSION
SNMP version (1, 2c or 3)
--snmp-user SNMP_USER
SNMP username (only for SNMPv3)
--snmp-auth SNMP_AUTH
SNMP authentication key (only for SNMPv3)
--snmp-force force SNMP mode
-t TIME, --time TIME set refresh time in seconds [default: 3 sec]
-w, --webserver run Glances in web server mode
-f PROCESS_FILTER, --process-filter PROCESS_FILTER
set the process filter patern (regular expression)
--process-short-name force short name for processes name
-b, --byte display network rate in byte per second
-1, --percpu start Glances in per CPU mode
--theme-white optimize display for white background
Interactive Commands
--------------------
The following commands (key pressed) are supported while in Glances:
``ENTER``
Set the process filter
Filter is a regular expression pattern:
- gnome: all processes starting with the gnome string
- .*gnome.*: all processes containing the gnome string
``a``
Sort process list automatically
@ -177,8 +193,12 @@ The following commands (key pressed) are supported while in Glances:
Sort processes by CPU usage
``d``
Show/hide disk I/O stats
``e``
Enable/disable top extended stats
``f``
Show/hide file system stats
``g``
Generate hraphs for current history
``h``
Show/hide the help screen
``i``
@ -193,6 +213,8 @@ The following commands (key pressed) are supported while in Glances:
Sort processes by name
``q``
Quit
``r``
Reset history
``s``
Show/hide sensors stats
``t``
@ -207,17 +229,17 @@ The following commands (key pressed) are supported while in Glances:
Show/hide processes stats
``1``
Switch between global CPU and per-CPU stats
``/``
Switch between short name / command line (processes name)
Configuration
=============
**Caution! Glances version 1.x configuration files are not compatible
with the version 2.x.**
No configuration file is mandatory to use Glances.
Furthermore a configuration file is needed to set up limits, disks or
network interfaces to hide and/or monitored processes list.
network interfaces to hide and/or monitored processes list or to define
alias.
By default, the configuration file is under:
@ -236,6 +258,8 @@ Since Windows Vista and newer versions:
::
C:\Users\<User>\AppData\Roaming
or
%userprofile%\AppData\Roaming
You can override the default configuration, located in one of the above
directories on your system, except for Windows.
@ -251,6 +275,18 @@ e.g., on Linux:
On OS X, you should copy the configuration file to
``~/Library/Application Support/glances/``.
Logs and debug mode
===================
Glances logs all its internal messages to a log file. By default, only
INFO & WARNING & ERROR &CRITICAL levels are logged, but DEBUG messages
can ben logged using the -d option on the command line.
By default, the configuration file is under:
:Linux, \*BSD and OS X: ``/tmp/glances.conf``
:Windows: ``%APPDATA%\Local\temp\glances.conf``
Anatomy Of The Application
==========================
@ -371,7 +407,7 @@ Alerts are only set if the maximum speed per network interface is available
*Note*: it is possibile to define a list of network interfaces to hide
and per-interface limit values in the ``[network]`` section of the
configuration file.
configuration file and aliases for interface name.
Disk I/O
--------
@ -383,7 +419,7 @@ Glances displays the disk I/O throughput. The unit is adapted dynamically.
There is no alert on this information.
*Note*: it is possible to define a list of disks to hide under the
``[diskio]`` section in the configuration file.
``[diskio]`` section in the configuration file and aliases for disk name.
File System
-----------
@ -418,8 +454,8 @@ temperature only.
There is no alert on this information.
*Note*: limit values can be overwritten in the configuration file under
the ``[sensors]`` section.
*Note*: limit values and sensors alias names can be defined in the configuration
file under the ``[sensors]`` section.
Processes List
--------------
@ -480,7 +516,8 @@ The number of processes in the list is adapted to the screen size.
``IOW/s``
Per process I/O write rate (in Byte/s)
``COMMAND``
Process command line (process name is highlighted)
Process command line
User cans switch to the process name by pressing on the ``/`` key
Process status legend:
@ -495,6 +532,17 @@ Process status legend:
``Z``
Zombie
In standalone mode, additionals informations are provided for the top process:
.. image:: images/processlist-top.png
* CPU affinity (number of cores used by the process)
* Extended memory information (swap, shared, text, lib, data and dirty on Linux)
* Openned threads, files and network sessions (TCP and UDP)
* IO nice level
The extended stats feature could be disabled using the --disable-process-extended option (command line) or the ``e`` key (curses interface).
*Note*: limit values can be overwritten in the configuration file under
the ``[process]`` section.
@ -592,21 +640,25 @@ CSV files have two lines per stats:
- Stats description
- Stats (comma separated)
API Documentation
=================
APIs Documentations
===================
Glances uses a `XML-RPC server`_ and can be used by another client software.
Glances includes a `XML-RPC server`_ and a `RESTFULL-JSON`_ API which and can be used by another client software.
API documentation is available at
https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to.
APIs documentations are available at:
- XML-RPC: https://github.com/nicolargo/glances/wiki/The-Glances-2.x-API-How-to
- RESTFULL-JSON: https://github.com/nicolargo/glances/wiki/The-Glances-RESTFULL-JSON-API
Support
=======
To post a question about Glances use case, please post it to the offical Q&A `forum`_.
To report a bug or a feature request use the bug tracking system at
https://github.com/nicolargo/glances/issues.
Feel free to contribute!
Feel free to contribute !
.. [1] http://nosheep.net/story/defining-unix-load-average/
@ -614,3 +666,5 @@ Feel free to contribute!
.. _psutil: https://code.google.com/p/psutil/
.. _XML-RPC server: http://docs.python.org/2/library/simplexmlrpcserver.html
.. _RESTFULL-JSON: http://jsonapi.org/
.. _forum: https://groups.google.com/forum/?hl=en#!forum/glances-users

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 KiB

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

@ -20,7 +20,7 @@
"""Init the Glances software."""
__appname__ = 'glances'
__version__ = '2.0.1'
__version__ = '2.1'
__author__ = 'Nicolas Hennion <nicolas@nicolargo.com>'
__license__ = 'LGPL'
@ -29,6 +29,7 @@ import gettext
import locale
import signal
import sys
import platform
# Import psutil
try:
@ -37,19 +38,26 @@ except ImportError:
print('psutil library not found. Glances cannot start.')
sys.exit(1)
# Check psutil version
psutil_min_version = (2, 0, 0)
psutil_version = tuple([int(num) for num in __psutil_version.split('.')])
if psutil_version < psutil_min_version:
print('psutil version {0} detected.').format(__psutil_version)
print('psutil 2.0 or higher is needed. Glances cannot start.')
sys.exit(1)
# Import Glances libs
# Note: others Glances libs will be imported optionally
from glances.core.glances_globals import gettext_domain, locale_dir
from glances.core.glances_globals import gettext_domain, locale_dir, logger
from glances.core.glances_main import GlancesMain
# Get PSutil version
psutil_min_version = (2, 0, 0)
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))
# Check PSutil version
if psutil_version < psutil_min_version:
logger.critical('PSutil 2.0 or higher is needed. Glances cannot start.')
sys.exit(1)
def __signal_handler(signal, frame):
"""Callback for CTRL-C."""
@ -61,12 +69,19 @@ def end():
if core.is_standalone():
# Stop the standalone (CLI)
standalone.end()
logger.info("Stop Glances (with CTRL-C)")
elif core.is_client():
# Stop the client
client.end()
logger.info("Stop Glances client (with CTRL-C)")
elif core.is_server():
# Stop the server
server.end()
logger.info("Stop Glances server (with CTRL-C)")
elif core.is_webserver():
# Stop the Web server
webserver.end()
logger.info("Stop Glances web server(with CTRL-C)")
# The end...
sys.exit(0)
@ -83,7 +98,7 @@ def main():
gettext.install(gettext_domain, locale_dir)
# Share global var
global core, standalone, client, server
global core, standalone, client, server, webserver
# Create the Glances main instance
core = GlancesMain()
@ -93,6 +108,7 @@ def main():
# Glances can be ran in standalone, client or server mode
if core.is_standalone():
logger.info("Start standalone mode")
# Import the Glances standalone module
from glances.core.glances_standalone import GlancesStandalone
@ -105,6 +121,7 @@ def main():
standalone.serve_forever()
elif core.is_client():
logger.info("Start client mode")
# Import the Glances client module
from glances.core.glances_client import GlancesClient
@ -115,7 +132,7 @@ def main():
# Test if client and server are in the same major version
if not client.login():
print(_("Error: The server version is not compatible with the client"))
logger.critical(_("The server version is not compatible with the client"))
sys.exit(2)
# Start the client loop
@ -125,6 +142,7 @@ def main():
client.close()
elif core.is_server():
logger.info("Start server mode")
# Import the Glances server module
from glances.core.glances_server import GlancesServer
@ -147,6 +165,7 @@ def main():
server.server_close()
elif core.is_webserver():
logger.info("Start web server mode")
# Import the Glances web server module
from glances.core.glances_webserver import GlancesWebServer

View File

@ -24,18 +24,28 @@ import json
import socket
import sys
try:
from xmlrpc.client import ServerProxy, ProtocolError, Fault
from xmlrpc.client import Transport, ServerProxy, ProtocolError, Fault
except ImportError: # Python 2
from xmlrpclib import ServerProxy, ProtocolError, Fault
from xmlrpclib import Transport, ServerProxy, ProtocolError, Fault
import httplib
# Import Glances libs
from glances.core.glances_globals import version
from glances.core.glances_globals import version, logger
from glances.core.glances_stats import GlancesStatsClient
from glances.outputs.glances_curses import GlancesCurses
class GlancesClient(object):
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):
@ -54,10 +64,13 @@ class GlancesClient(object):
uri = 'http://{0}:{1}'.format(args.client, args.port)
# Try to connect to the URI
transport = GlancesClientTransport()
# Configure the server timeout to 7 seconds
transport.set_timeout(7)
try:
self.client = ServerProxy(uri)
self.client = ServerProxy(uri, transport = transport)
except Exception as e:
print(_("Error: Couldn't create socket {0}: {1}").format(uri, e))
logger.error(_("Couldn't create socket {0}: {1}").format(uri, e))
sys.exit(2)
def set_mode(self, mode='glances'):
@ -81,39 +94,47 @@ class GlancesClient(object):
"""Logon to the server."""
ret = True
# First of all, trying to connect to a Glances server
try:
client_version = self.client.init()
if not self.args.snmp_force:
# First of all, trying to connect to a Glances server
self.set_mode('glances')
except socket.error as err:
# print(_("Error: Connection to {0} server failed").format(self.get_mode()))
# Fallback to SNMP
self.set_mode('snmp')
except ProtocolError as err:
# Others errors
if str(err).find(" 401 ") > 0:
print(_("Error: Connection to server failed: Bad password"))
else:
print(_("Error: Connection to server failed: {0}").format(err))
sys.exit(2)
client_version = None
try:
client_version = self.client.init()
except socket.error as err:
# Fallback to SNMP
logger.error(_("Connection to Glances server failed"))
self.set_mode('snmp')
fallbackmsg = _("Trying fallback to SNMP...")
print(fallbackmsg)
except ProtocolError as err:
# Others errors
if str(err).find(" 401 ") > 0:
logger.error(_("Connection to server failed (Bad password)"))
else:
logger.error(_("Connection to server failed ({0})").format(err))
sys.exit(2)
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()))
elif self.get_mode() == 'snmp':
print (_("Info: Connection to Glances server failed. Trying fallback to SNMP..."))
# Then fallback to SNMP if needed
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()))
else:
logger.error("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..."))
# Fallback to SNMP if needed
from glances.core.glances_stats import GlancesStatsClientSNMP
# Init stats
self.stats = GlancesStatsClientSNMP(args=self.args)
if not self.stats.check_snmp():
print(_("Error: Connection to SNMP server failed"))
logger.error(_("Connection to SNMP server failed"))
sys.exit(2)
else:
ret = False
if ret:
# Load limits from the configuration file
@ -133,7 +154,8 @@ class GlancesClient(object):
elif self.get_mode() == 'snmp':
return self.update_snmp()
else:
print(_("Error: Unknown server mode: {0}").format(self.get_mode()))
self.end()
logger.critical(_("Unknown server mode: {0}").format(self.get_mode()))
sys.exit(2)
def update_glances(self):
@ -183,8 +205,6 @@ class GlancesClient(object):
# Update the screen
self.screen.update(self.stats, cs_status=cs_status)
# print self.stats
# print self.stats.getAll()
def end(self):
"""End of the client session."""

View File

@ -38,7 +38,8 @@ from glances.core.glances_globals import (
is_py3,
is_windows,
sys_prefix,
work_path
work_path,
logger
)
@ -52,9 +53,12 @@ class Config(object):
def __init__(self, location=None):
self.location = location
self.config_filename = 'glances.conf'
self.parser = RawConfigParser()
self._loaded_config_file = None
self.load()
def load(self):
@ -66,12 +70,18 @@ class Config(object):
self.parser.read(config_file, encoding='utf-8')
else:
self.parser.read(config_file)
# print(_("DEBUG: Read configuration file %s") % config_file)
logger.info(_("Read configuration file %s") % config_file)
except UnicodeDecodeError as e:
print(_("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
break
def get_loaded_config_file(self):
"""Return the loaded configuration file"""
return self._loaded_config_file
def get_config_paths(self):
r"""Get a list of config file paths.

View File

@ -22,6 +22,8 @@
import os
import sys
from glances.core.glances_logging import glancesLogger
# Global information
appname = 'glances'
version = __import__('glances').__version__
@ -58,10 +60,13 @@ elif os.path.exists(sys_i18n_path):
else:
locale_dir = None
# Create and init the logging instance
logger = glancesLogger()
# Instances shared between all Glances scripts
# ============================================
# glances_processes for processcount and processlist plugins
# Glances_processes for processcount and processlist plugins
from glances.core.glances_processes import GlancesProcesses
glances_processes = GlancesProcesses()

View File

@ -0,0 +1,83 @@
# -*- 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/>.
"""Custom logging class"""
import logging
import logging.config
import tempfile
import os
# Define the logging configuration
LOGGING_CFG = {
'version': 1,
'disable_existing_loggers': False,
'root': {
'level': 'INFO',
'handlers': ['file', 'console']
},
'formatters': {
'standard': {
'format': '%(asctime)s -- %(levelname)s -- %(message)s'
},
'short': {
'format': '%(levelname)s: %(message)s'
}
},
'handlers': {
'file': {
'level':'DEBUG',
'class':'logging.handlers.RotatingFileHandler',
'formatter': 'standard',
# http://stackoverflow.com/questions/847850/cross-platform-way-of-getting-temp-directory-in-python
'filename': os.path.join(tempfile.gettempdir(), 'glances.log')
},
'console':{
'level':'CRITICAL',
'class':'logging.StreamHandler',
'formatter': 'short'
}
},
'loggers': {
'debug': {
'handlers':['file', 'console'],
'level':'DEBUG',
},
'verbose': {
'handlers': ['file', 'console'],
'level': 'INFO'
},
'standard': {
'handlers': ['file'],
'level': 'INFO'
}
}
}
def glancesLogger():
_logger = logging.getLogger()
try:
logging.config.dictConfig(LOGGING_CFG)
except AttributeError:
# dictConfig is only available for Python 2.7 or higher
# Minimal configuration for Python 2.6
logging.basicConfig(filename=os.path.join(tempfile.gettempdir(), 'glances.log'),
level=logging.DEBUG,
format='%(asctime)s -- %(levelname)s -- %(message)s')
return _logger

View File

@ -87,8 +87,7 @@ class GlancesLogs(object):
else:
# Default sort is...
process_auto_by = 'cpu_percent'
glances_processes.setsortkey(process_auto_by)
glances_processes.setautosortkey(process_auto_by)
return process_auto_by
@ -96,8 +95,8 @@ class GlancesLogs(object):
"""Reset the process_auto_by variable."""
# Default sort is...
process_auto_by = 'cpu_percent'
glances_processes.setsortkey(process_auto_by)
glances_processes.setautosortkey(process_auto_by)
glances_processes.setmanualsortkey(None)
return process_auto_by

View File

@ -21,10 +21,11 @@
# Import system libs
import argparse
import sys
# Import Glances libs
from glances.core.glances_config import Config
from glances.core.glances_globals import appname, psutil_version, version
from glances.core.glances_globals import appname, psutil_version, version, logger
class GlancesMain(object):
@ -55,15 +56,13 @@ class GlancesMain(object):
_version = "Glances v" + version + " with psutil v" + psutil_version
parser = argparse.ArgumentParser(prog=appname, conflict_handler='resolve')
parser.add_argument('-V', '--version', action='version', version=_version)
parser.add_argument('-b', '--byte', action='store_true', default=False,
dest='byte', help=_('display network rate in byte per second'))
parser.add_argument('-B', '--bind', default='0.0.0.0', dest='bind_address',
help=_('bind server to the given IPv4/IPv6 address or hostname'))
parser.add_argument('-c', '--client', dest='client',
help=_('connect to a Glances server by IPv4/IPv6 address or hostname'))
parser.add_argument('-d', '--debug', action='store_true', default=False,
dest='debug', help=_('Enable debug mode'))
parser.add_argument('-C', '--config', dest='conf_file',
help=_('path to the configuration file'))
# Enable or disable option on startup
parser.add_argument('--enable-history', action='store_true', default=False,
dest='enable_history', help=_('enable the history mode'))
parser.add_argument('--disable-bold', action='store_false', default=True,
dest='disable_bold', help=_('disable bold mode in the terminal'))
parser.add_argument('--disable-diskio', action='store_true', default=False,
@ -76,20 +75,26 @@ class GlancesMain(object):
dest='disable_sensors', help=_('disable sensors module'))
parser.add_argument('--disable-process', action='store_true', default=False,
dest='disable_process', help=_('disable process module'))
parser.add_argument('--disable-process-extended', action='store_true', default=False,
dest='disable_process_extended', help=_('disable extended stats on top process'))
parser.add_argument('--disable-log', action='store_true', default=False,
dest='disable_log', help=_('disable log module'))
# CSV output feature
parser.add_argument('--output-csv', default=None,
dest='output_csv', help=_('export stats to a CSV file'))
# Server option
parser.add_argument('-p', '--port', default=self.server_port, type=int, dest='port',
# Client/Server option
parser.add_argument('-c', '--client', dest='client',
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('-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',
help=_('bind server to the given IPv4/IPv6 address or hostname'))
parser.add_argument('--password-badidea', dest='password_arg',
help=_('define password from the command line'))
parser.add_argument('--password', action='store_true', default=False, dest='password_prompt',
help=_('define a client/server password from the prompt or file'))
parser.add_argument('-s', '--server', action='store_true', default=False,
dest='server', help=_('run Glances in server mode'))
parser.add_argument('--snmp-community', default='public', dest='snmp_community',
help=_('SNMP community'))
parser.add_argument('--snmp-port', default=161, type=int,
@ -100,13 +105,23 @@ class GlancesMain(object):
help=_('SNMP username (only for SNMPv3)'))
parser.add_argument('--snmp-auth', default='password', dest='snmp_auth',
help=_('SNMP authentication key (only for SNMPv3)'))
parser.add_argument('-t', '--time', default=self.refresh_time, type=int,
parser.add_argument('--snmp-force', action='store_true', default=False,
dest='snmp_force', help=_('force SNMP mode'))
parser.add_argument('-t', '--time', default=self.refresh_time, type=float,
dest='time', help=_('set refresh time in seconds [default: {0} sec]').format(self.refresh_time))
parser.add_argument('-w', '--webserver', action='store_true', default=False,
dest='webserver', help=_('run Glances in web server mode'))
# Other options
# Display options
parser.add_argument('-f', '--process-filter', default=None, type=str,
dest='process_filter', help=_('set the process filter patern (regular expression)'))
parser.add_argument('--process-short-name', action='store_true', default=False,
dest='process_short_name', help=_('force short name for processes name'))
parser.add_argument('-b', '--byte', action='store_true', default=False,
dest='byte', help=_('display network rate in byte per second'))
parser.add_argument('-1', '--percpu', action='store_true', default=False,
dest='percpu', help=_('start Glances in per CPU mode'))
parser.add_argument('--theme-white', action='store_true', default=False,
dest='theme_white', help=_('optimize display for white background'))
return parser
@ -114,15 +129,24 @@ class GlancesMain(object):
"""Parse command line arguments."""
args = self.init_args().parse_args()
# Load the configuration file, it it exists
# Load the configuration file, if it exists
self.config = Config(args.conf_file)
# In web server mode, default:
# - refresh time: 5 sec
# - host port: 61208
# Debug mode
if args.debug:
from logging import DEBUG
logger.setLevel(DEBUG)
# Client/server Port
if args.port is None:
if args.webserver:
args.port = self.web_server_port
else:
args.port = self.server_port
# In web server mode, defaul refresh time: 5 sec
if args.webserver:
args.time = 5
args.port = self.web_server_port
# Server or client login/password
args.username = self.username
@ -146,15 +170,6 @@ class GlancesMain(object):
# Default is no password
args.password = self.password
# !!! Change global variables regarding to user args
# !!! To be refactor to use directly the args list in the code
self.server_tag = args.server
self.webserver_tag = args.webserver
if args.client is not None:
self.client_tag = True
self.server_ip = args.client
# /!!!
# By default help is hidden
args.help_tag = False
@ -162,6 +177,14 @@ class GlancesMain(object):
args.network_sum = False
args.network_cumul = False
# Control parameter and exit if it is not OK
self.args = args
# Filter is only available in standalone mode
if args.process_filter is not None and not self.is_standalone():
logger.critical(_("Process filter is only available in standalone mode"))
sys.exit(2)
return args
def __hash_password(self, plain_password):
@ -186,19 +209,19 @@ class GlancesMain(object):
def is_standalone(self):
"""Return True if Glances is running in standalone mode."""
return not self.client_tag and not self.server_tag and not self.webserver_tag
return not self.args.client 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.client_tag and not self.server_tag
return self.args.client and not self.args.server
def is_server(self):
"""Return True if Glances is running in server mode."""
return not self.client_tag and self.server_tag
return not self.args.client and self.args.server
def is_webserver(self):
"""Return True if Glances is running in Web server mode."""
return not self.client_tag and self.webserver_tag
return not self.args.client and self.args.webserver
def get_config(self):
"""Return configuration file object."""

View File

@ -24,7 +24,7 @@ import re
import subprocess
# Import Glances lib
from glances.core.glances_globals import glances_processes
from glances.core.glances_globals import glances_processes, logger
class MonitorList(object):
@ -72,7 +72,7 @@ class MonitorList(object):
countmin = self.config.get_raw_option(section, key + "countmin")
countmax = self.config.get_raw_option(section, key + "countmax")
except Exception as e:
print(_("Error: Cannot read monitored list: {0}").format(e))
logger.error(_("Cannot read monitored list: {0}").format(e))
pass
else:
if description is not None and regex is not None:

View File

@ -32,7 +32,8 @@ from glances.core.glances_globals import (
is_bsd,
is_linux,
is_mac,
is_windows
is_windows,
logger
)
# Trick: bind raw_input to input in Python 2
@ -106,7 +107,7 @@ class GlancesPassword(object):
"""
if os.path.exists(self.password_filepath) and not clear:
# If the password file exist then use it
print(_("Info: Read password from file: {0}").format(self.password_filepath))
logger.info(_("Read password from file: {0}").format(self.password_filepath))
password = self.load_password()
else:
# Else enter the password from the command line
@ -122,7 +123,7 @@ class GlancesPassword(object):
password_confirm = hashlib.sha256(getpass.getpass(_("Password (confirm): ")).encode('utf-8')).hexdigest()
if not self.check_password(password_hashed, password_confirm):
print(_("Error: Sorry, but passwords did not match..."))
logger.critical(_("Sorry, but passwords did not match..."))
sys.exit(1)
# Return the plain or hashed password
@ -147,7 +148,7 @@ class GlancesPassword(object):
try:
os.makedirs(self.password_path)
except OSError as e:
print(_("Warning: Cannot create Glances directory: {0}").format(e))
logger.error(_("Cannot create Glances directory: {0}").format(e))
return
# Create/overwrite the password file

View File

@ -17,10 +17,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from glances.core.glances_globals import is_bsd, is_mac, is_windows
# Import Glances lib
from glances.core.glances_globals import is_linux, is_bsd, is_mac, is_windows, logger
from glances.core.glances_timer import Timer, getTimeSinceLastUpdate
# Import Python lib
import psutil
import re
class GlancesProcesses(object):
@ -44,7 +47,7 @@ class GlancesProcesses(object):
self.io_old = {}
# Init stats
self.processsort = 'cpu_percent'
self.resetsort()
self.processlist = []
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
@ -52,6 +55,20 @@ class GlancesProcesses(object):
# Default is to enable the processes stats
self.disable_tag = False
# Extended stats for top process is enable by default
self.disable_extended_tag = False
# Maximum number of processes showed in the UI interface
# None if no limit
self.max_processes = None
# Process filter is a regular expression
self.process_filter = None
self.process_filter_re = None
# !!! ONLY FOR TEST
# self.set_process_filter('.*python.*')
def enable(self):
"""Enable process stats."""
self.disable_tag = False
@ -61,99 +78,231 @@ class GlancesProcesses(object):
"""Disable process stats."""
self.disable_tag = True
def __get_process_stats(self, proc):
"""Get process stats."""
procstat = {}
def enable_extended(self):
"""Enable extended process stats."""
self.disable_extended_tag = False
self.update()
# Process ID
procstat['pid'] = proc.pid
def disable_extended(self):
"""Disable extended process stats."""
self.disable_extended_tag = True
# Process name (cached by PSUtil)
try:
procstat['name'] = proc.name()
except psutil.AccessDenied:
procstat['name'] = ""
def set_max_processes(self, value):
"""Set the maximum number of processes showed in the UI interfaces"""
self.max_processes = value
return self.max_processes
# Process username (cached with internal cache)
try:
self.username_cache[procstat['pid']]
except:
def get_max_processes(self):
"""Get the maximum number of processes showed in the UI interfaces"""
return self.max_processes
def set_process_filter(self, value):
"""Set the process filter"""
logger.info(_("Set process filter to %s") % value)
self.process_filter = value
if value is not None:
try:
self.username_cache[procstat['pid']] = proc.username()
except (KeyError, psutil.AccessDenied):
self.process_filter_re = re.compile(value)
logger.debug(_("Process filter regular expression compilation OK: %s") % self.get_process_filter())
except:
logger.error(_("Can not compile process filter regular expression: %s") % value)
self.process_filter_re = None
else:
self.process_filter_re = None
return self.process_filter
def get_process_filter(self):
"""Get the process filter"""
return self.process_filter
def get_process_filter_re(self):
"""Get the process regular expression compiled"""
return self.process_filter_re
def is_filtered(self, value):
"""Return True if the value should be filtered"""
if self.get_process_filter() is None:
# No filter => Not filtered
return False
else:
# logger.debug(self.get_process_filter() + " <> " + value + " => " + str(self.get_process_filter_re().match(value) is None))
return self.get_process_filter_re().match(value) is None
def __get_process_stats(self, proc,
mandatory_stats=True,
standard_stats=True,
extended_stats=False):
"""
Get process stats of the proc processes (proc is returned psutil.process_iter())
mandatory_stats: need for the sorting/filter step
=> cpu_percent, memory_percent, io_counters, name, cmdline
standard_stats: for all the displayed processes
=> username, status, memory_info, cpu_times
extended_stats: only for top processes (see issue #403)
=> connections (UDP/TCP), memory_swap...
"""
# Process ID (always)
procstat = proc.as_dict(attrs=['pid'])
if mandatory_stats:
procstat['mandatory_stats'] = True
# Process CPU, MEM percent and name
procstat.update(proc.as_dict(attrs=['cpu_percent', 'memory_percent', 'name'], ad_value=''))
# Process command line (cached with internal cache)
try:
self.cmdline_cache[procstat['pid']]
except KeyError:
# Patch for issue #391
try:
self.username_cache[procstat['pid']] = proc.uids().real
except (KeyError, AttributeError, psutil.AccessDenied):
self.username_cache[procstat['pid']] = "?"
procstat['username'] = self.username_cache[procstat['pid']]
self.cmdline_cache[procstat['pid']] = ' '.join(proc.cmdline())
except (AttributeError, psutil.AccessDenied, UnicodeDecodeError):
self.cmdline_cache[procstat['pid']] = ""
procstat['cmdline'] = self.cmdline_cache[procstat['pid']]
# Process command line (cached with internal cache)
try:
self.cmdline_cache[procstat['pid']]
except:
self.cmdline_cache[procstat['pid']] = ' '.join(proc.cmdline())
procstat['cmdline'] = self.cmdline_cache[procstat['pid']]
# Process IO
# procstat['io_counters'] is a list:
# [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag]
# If io_tag = 0 > Access denied (display "?")
# If io_tag = 1 > No access denied (display the IO rate)
# Note Disk IO stat not available on Mac OS
if not is_mac:
try:
# Get the process IO counters
proc_io = proc.io_counters()
io_new = [proc_io.read_bytes, proc_io.write_bytes]
except (psutil.AccessDenied, psutil.NoSuchProcess):
# Access denied to process IO (no root account)
# NoSuchProcess (process die between first and second grab)
# Put 0 in all values (for sort) and io_tag = 0 (for display)
procstat['io_counters'] = [0, 0] + [0, 0]
io_tag = 0
else:
# For IO rate computation
# Append saved IO r/w bytes
try:
procstat['io_counters'] = io_new + self.io_old[procstat['pid']]
except KeyError:
procstat['io_counters'] = io_new + [0, 0]
# then save the IO r/w bytes
self.io_old[procstat['pid']] = io_new
io_tag = 1
# Process status
procstat['status'] = str(proc.status())[:1].upper()
# Append the IO tag (for display)
procstat['io_counters'] += [io_tag]
# Process nice
try:
procstat['nice'] = proc.nice()
except psutil.AccessDenied:
procstat['nice'] = None
if standard_stats:
procstat['standard_stats'] = True
# Process memory
procstat['memory_info'] = proc.memory_info()
procstat['memory_percent'] = proc.memory_percent()
# Process CPU
procstat['cpu_times'] = proc.cpu_times()
procstat['cpu_percent'] = proc.cpu_percent(interval=0)
# Process network connections (TCP and UDP) (Experimental)
# !!! High CPU consumption
# try:
# procstat['tcp'] = len(proc.connections(kind="tcp"))
# procstat['udp'] = len(proc.connections(kind="udp"))
# except:
# procstat['tcp'] = 0
# procstat['udp'] = 0
# Process IO
# procstat['io_counters'] is a list:
# [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag]
# If io_tag = 0 > Access denied (display "?")
# If io_tag = 1 > No access denied (display the IO rate)
# Note Disk IO stat not available on Mac OS
if not is_mac:
# Process username (cached with internal cache)
try:
# Get the process IO counters
proc_io = proc.io_counters()
io_new = [proc_io.read_bytes, proc_io.write_bytes]
except psutil.AccessDenied:
# Access denied to process IO (no root account)
# Put 0 in all values (for sort) and io_tag = 0 (for display)
procstat['io_counters'] = [0, 0] + [0, 0]
io_tag = 0
self.username_cache[procstat['pid']]
except KeyError:
try:
self.username_cache[procstat['pid']] = proc.username()
except psutil.NoSuchProcess:
self.username_cache[procstat['pid']] = "?"
except (KeyError, psutil.AccessDenied):
try:
self.username_cache[procstat['pid']] = proc.uids().real
except (KeyError, AttributeError, psutil.AccessDenied):
self.username_cache[procstat['pid']] = "?"
procstat['username'] = self.username_cache[procstat['pid']]
# Process status, nice, memory_info and cpu_times
try:
procstat.update(proc.as_dict(attrs=['status', 'nice', 'memory_info', 'cpu_times']))
except psutil.NoSuchProcess:
pass
else:
# For IO rate computation
# Append saved IO r/w bytes
try:
procstat['io_counters'] = io_new + self.io_old[procstat['pid']]
except KeyError:
procstat['io_counters'] = io_new + [0, 0]
# then save the IO r/w bytes
self.io_old[procstat['pid']] = io_new
io_tag = 1
procstat['status'] = str(procstat['status'])[:1].upper()
# Append the IO tag (for display)
procstat['io_counters'] += [io_tag]
if extended_stats and not self.disable_extended_tag:
procstat['extended_stats'] = True
# CPU affinity (Windows and Linux only)
try:
procstat.update(proc.as_dict(attrs=['cpu_affinity']))
except psutil.NoSuchProcess:
pass
except AttributeError:
procstat['cpu_affinity'] = None
# Memory extended
try:
procstat.update(proc.as_dict(attrs=['memory_info_ex']))
except psutil.NoSuchProcess:
pass
except AttributeError:
procstat['memory_info_ex'] = None
# Number of context switch
try:
procstat.update(proc.as_dict(attrs=['num_ctx_switches']))
except psutil.NoSuchProcess:
pass
except AttributeError:
procstat['num_ctx_switches'] = None
# Number of file descriptors (Unix only)
try:
procstat.update(proc.as_dict(attrs=['num_fds']))
except psutil.NoSuchProcess:
pass
except AttributeError:
procstat['num_fds'] = None
# Threads number
try:
procstat.update(proc.as_dict(attrs=['num_threads']))
except psutil.NoSuchProcess:
pass
except AttributeError:
procstat['num_threads'] = None
# Number of handles (Windows only)
if is_windows:
try:
procstat.update(proc.as_dict(attrs=['num_handles']))
except psutil.NoSuchProcess:
pass
else:
procstat['num_handles'] = None
# SWAP memory (Only on Linux based OS)
# http://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
if is_linux:
try:
procstat['memory_swap'] = sum([v.swap for v in proc.memory_maps()])
except psutil.NoSuchProcess:
pass
except psutil.AccessDenied:
procstat['memory_swap'] = None
# Process network connections (TCP and UDP)
try:
procstat['tcp'] = len(proc.connections(kind="tcp"))
procstat['udp'] = len(proc.connections(kind="udp"))
except:
procstat['tcp'] = None
procstat['udp'] = None
# IO Nice
# http://pythonhosted.org/psutil/#psutil.Process.ionice
if is_linux or is_windows:
try:
procstat.update(proc.as_dict(attrs=['ionice']))
except psutil.NoSuchProcess:
pass
else:
procstat['ionice'] = None
#logger.debug(procstat)
return procstat
def update(self):
"""Update the processes stats."""
"""
Update the processes stats
"""
# Reset the stats
self.processlist = []
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
@ -165,37 +314,71 @@ class GlancesProcesses(object):
# Get the time since last update
time_since_update = getTimeSinceLastUpdate('process_disk')
# For each existing process...
# Build an internal dict with only mandatories stats (sort keys)
processdict = {}
for proc in psutil.process_iter():
# If self.get_max_processes() is None: Only retreive mandatory stats
# Else: retreive mandatoryadn standard stast
s = self.__get_process_stats(proc,
mandatory_stats=True,
standard_stats=self.get_max_processes() is None)
# Continue to the next process if it has to be filtered
if s is None or (self.is_filtered(s['cmdline']) and self.is_filtered(s['name'])):
continue
# Ok add the process to the list
processdict[proc] = s
# ignore the 'idle' process on Windows and *BSD
# ignore the 'kernel_task' process on OS X
# waiting for upstream patch from psutil
if (is_bsd and processdict[proc]['name'] == 'idle' or
is_windows and processdict[proc]['name'] == 'System Idle Process' or
is_mac and processdict[proc]['name'] == 'kernel_task'):
continue
# Update processcount (global statistics)
try:
# Get stats using the PSUtil
procstat = self.__get_process_stats(proc)
self.processcount[str(proc.status())] += 1
except KeyError:
# Key did not exist, create it
self.processcount[str(proc.status())] = 1
else:
self.processcount['total'] += 1
# Update thread number (global statistics)
try:
self.processcount['thread'] += proc.num_threads()
except:
pass
if self.get_max_processes() is not None:
# Sort the internal dict and cut the top N (Return a list of tuple)
# tuple=key (proc), dict (returned by __get_process_stats)
processiter = sorted(processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True)
first = True
for i in processiter[0:self.get_max_processes()]:
# Already existing mandatory stats
procstat = i[1]
# Update with standard stats
# and extended stats but only for TOP (first) process
s = self.__get_process_stats(i[0],
mandatory_stats=False,
standard_stats=True,
extended_stats=first)
if s is None:
continue
procstat.update(s)
# Add a specific time_since_update stats for bitrate
procstat['time_since_update'] = time_since_update
# ignore the 'idle' process on Windows and *BSD
# ignore the 'kernel_task' process on OS X
# waiting for upstream patch from psutil
if (is_bsd and procstat['name'] == 'idle' or
is_windows and procstat['name'] == 'System Idle Process' or
is_mac and procstat['name'] == 'kernel_task'):
continue
# Update processcount (global statistics)
try:
self.processcount[str(proc.status())] += 1
except KeyError:
# Key did not exist, create it
self.processcount[str(proc.status())] = 1
else:
self.processcount['total'] += 1
# Update thread number (global statistics)
try:
self.processcount['thread'] += proc.num_threads()
except:
pass
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
else:
# Update processlist
# Update process list
self.processlist.append(procstat)
# Next...
first = False
else:
# Get all the processes
for i in processdict.items():
# Already existing mandatory and standard stats
procstat = i[1]
# Add a specific time_since_update stats for bitrate
procstat['time_since_update'] = time_since_update
# Update process list
self.processlist.append(procstat)
# Clean internals caches if timeout is reached
@ -214,13 +397,34 @@ class GlancesProcesses(object):
return self.processlist
def getsortkey(self):
"""Get the current sort key for automatic sort."""
return self.processsort
"""Get the current sort key"""
if self.getmanualsortkey() is not None:
return self.getmanualsortkey()
else:
return self.getautosortkey()
def setsortkey(self, sortedby):
def getmanualsortkey(self):
"""Get the current sort key for manual sort."""
return self.processmanualsort
def getautosortkey(self):
"""Get the current sort key for automatic sort."""
return self.processautosort
def setmanualsortkey(self, sortedby):
"""Set the current sort key for manual sort."""
self.processmanualsort = sortedby
return self.processmanualsort
def setautosortkey(self, sortedby):
"""Set the current sort key for automatic sort."""
self.processsort = sortedby
return self.processsort
self.processautosort = sortedby
return self.processautosort
def resetsort(self):
"""Set the default sort: Auto"""
self.setmanualsortkey(None)
self.setautosortkey('cpu_percent')
def getsortlist(self, sortedby=None):
"""Get the sorted processlist."""

View File

@ -32,7 +32,7 @@ except ImportError: # Python 2
from SimpleXMLRPCServer import SimpleXMLRPCServer
# Import Glances libs
from glances.core.glances_globals import version
from glances.core.glances_globals import version, logger
from glances.core.glances_stats import GlancesStatsServer
from glances.core.glances_timer import Timer
@ -113,7 +113,7 @@ class GlancesXMLRPCServer(SimpleXMLRPCServer):
try:
self.address_family = socket.getaddrinfo(bind_address, bind_port)[0][0]
except socket.error as e:
print(_("Error: Couldn't open socket: {0}").format(e))
logger.error(_("Couldn't open socket: {0}").format(e))
sys.exit(1)
SimpleXMLRPCServer.__init__(self, (bind_address, bind_port),
@ -170,7 +170,6 @@ class GlancesInstance(object):
The goal is to dynamically generate the API get'Stats'() methods.
"""
# print "DEBUG: Call method: %s" % item
header = 'get'
# Check if the attribute starts with 'get'
if item.startswith(header):
@ -200,7 +199,7 @@ class GlancesServer(object):
try:
self.server = GlancesXMLRPCServer(args.bind_address, args.port, requestHandler)
except Exception as e:
print(_("Error: Cannot start Glances server: {0}").format(e))
logger.error(_("Cannot start Glances server: {0}").format(e))
sys.exit(2)
# The users dict

View File

@ -19,11 +19,15 @@
import sys
# Import Glances libs
from glances.core.glances_globals import logger
# Import mandatory PySNMP lib
try:
from pysnmp.entity.rfc3413.oneliner import cmdgen
except ImportError:
print("PySNMP library not found.")
print("Install using pip: # pip install pysnmp")
logger.critical(_("PySNMP library not found."))
print(_("Install it using pip: # pip install pysnmp"))
sys.exit(2)
@ -55,6 +59,9 @@ class GlancesSNMPClient(object):
ret[name.prettyPrint()] = ''
else:
ret[name.prettyPrint()] = val.prettyPrint()
# In Python 3, prettyPrint() return 'b'linux'' instead of 'linux'
if ret[name.prettyPrint()].startswith('b\''):
ret[name.prettyPrint()] = ret[name.prettyPrint()][2:-1]
return ret
def get_by_oid(self, *oid):
@ -89,6 +96,9 @@ class GlancesSNMPClient(object):
item[name.prettyPrint()] = ''
else:
item[name.prettyPrint()] = val.prettyPrint()
# In Python 3, prettyPrint() return 'b'linux'' instead of 'linux'
if item[name.prettyPrint()].startswith('b\''):
item[name.prettyPrint()] = item[name.prettyPrint()][2:-1]
ret.append(item)
return ret

View File

@ -20,8 +20,10 @@
"""Manage the Glances standalone session."""
# 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.core.glances_globals import glances_processes
class GlancesStandalone(object):
@ -30,7 +32,28 @@ class GlancesStandalone(object):
def __init__(self, config=None, args=None):
# Init stats
self.stats = GlancesStats(config)
self.stats = GlancesStats(config=config, args=args)
# If configured, set the maximum processes number to display
try:
max_processes = int(self.stats.get_plugin('processlist').get_conf_value('max_processes'))
logger.debug(_("Limit maximum displayed processes to %s") % max_processes)
except:
max_processes = None
logger.warning(_("Maximum displayed processes is not configured (high CPU consumption)"))
glances_processes.set_max_processes(max_processes)
# If process extended stats is disabled by user
if args.disable_process_extended:
logger.info(_("Extended stats for top process is disabled"))
glances_processes.disable_extended()
else:
logger.debug(_("Extended stats for top process is enabled (default behavor)"))
glances_processes.enable_extended()
# Manage optionnal process filter
if args.process_filter is not None:
glances_processes.set_process_filter(args.process_filter)
# Initial system informations update
self.stats.update()

View File

@ -22,20 +22,35 @@
import collections
import os
import sys
import re
from glances.core.glances_globals import plugins_path, sys_path
from glances.core.glances_globals import plugins_path, sys_path, logger
# SNMP OID regexp pattern to short system name dict
oid_to_short_system_name = {'.*Linux.*': 'linux',
'.*Darwin.*': 'mac',
'.*BSD.*': 'bsd',
'.*Windows.*': 'windows',
'.*Cisco.*': 'cisco',
'.*VMware ESXi.*': 'esxi'}
class GlancesStats(object):
"""This class stores, updates and gives stats."""
def __init__(self, config=None):
def __init__(self, config=None, args=None):
# Init the plugin list dict
self._plugins = collections.defaultdict(dict)
# Set the argument instance
self.args = args
# Set the config instance
self.config = config
# Load the plugins
self.load_plugins()
self.load_plugins(args=args)
# Load the limits
self.load_limits(config)
@ -76,9 +91,14 @@ class GlancesStats(object):
# for example, the file glances_xxx.py
# generate self._plugins_list["xxx"] = ...
plugin_name = os.path.basename(item)[len(header):-3].lower()
self._plugins[plugin_name] = plugin.Plugin(args=args)
if plugin_name == 'help':
self._plugins[plugin_name] = plugin.Plugin(args=args, config=self.config)
else:
self._plugins[plugin_name] = plugin.Plugin(args=args)
# Restoring system path
sys.path = sys_path
# Log plugins list
logger.debug(_("Available plugins list: %s"), self.getAllPlugins())
def getAllPlugins(self):
"""Return the plugins list."""
@ -88,7 +108,7 @@ class GlancesStats(object):
"""Load the stats limits."""
# For each plugins, call the init_limits method
for p in self._plugins:
# print "DEBUG: Load limits for %s" % p
# logger.debug(_("Load limits for %s") % p)
self._plugins[p].load_limits(config)
def __update__(self, input_stats):
@ -96,8 +116,8 @@ class GlancesStats(object):
if input_stats == {}:
# For standalone and server modes
# For each plugins, call the update method
for p in self._plugins:
# print "DEBUG: Update %s stats" % p
for p in self._plugins:
# logger.debug(_("Update %s stats") % p)
self._plugins[p].update()
else:
# For Glances client mode
@ -110,9 +130,18 @@ class GlancesStats(object):
self.__update__(input_stats)
def getAll(self):
"""Return all the stats."""
"""Return all the stats (list)"""
return [self._plugins[p].get_raw() for p in self._plugins]
def getAllAsDict(self):
"""Return all the stats (dict)"""
# Python > 2.6
# {p: self._plugins[p].get_raw() for p in self._plugins}
ret = {}
for p in self._plugins:
ret[p] = self._plugins[p].get_raw()
return ret
def get_plugin_list(self):
"""Return the plugin list."""
self._plugins
@ -146,9 +175,18 @@ class GlancesStatsServer(GlancesStats):
self.all_stats[p] = self._plugins[p].get_raw()
def getAll(self):
"""Return the stats as a dict."""
"""Return the stats as a list"""
return self.all_stats
def getAllAsDict(self):
"""Return the stats as a dict"""
# Python > 2.6
# return {p: self.all_stats[p] for p in self._plugins}
ret = {}
for p in self._plugins:
ret[p] = self.all_stats[p]
return ret
def getAllPlugins(self):
"""Return the plugins list."""
return [p for p in self._plugins]
@ -176,8 +214,8 @@ class GlancesStatsClient(GlancesStats):
# Add the plugin to the dictionary
# The key is the plugin name
# for example, the file glances_xxx.py
# generate self._plugins_list["xxx"] = ...
# print "DEBUG: Init %s plugin" % item
# generate self._plugins_list["xxx"] = ...
logger.debug(_("Init %s plugin") % item)
self._plugins[item] = plugin.Plugin()
# Restoring system path
sys.path = sys_path
@ -197,6 +235,9 @@ class GlancesStatsClientSNMP(GlancesStats):
# Init the arguments
self.args = args
# OS name is used because OID is differents between system
self.os_name = None
# Load plugins
self.load_plugins(args=self.args)
@ -213,17 +254,48 @@ class GlancesStatsClientSNMP(GlancesStats):
user=self.args.snmp_user,
auth=self.args.snmp_auth)
return clientsnmp.get_by_oid("1.3.6.1.2.1.1.5.0") != {}
# If we can not grab the hostname, then exit...
ret = clientsnmp.get_by_oid("1.3.6.1.2.1.1.5.0") != {}
if ret:
# Get the OS name (need to grab the good OID...)
oid_os_name = clientsnmp.get_by_oid("1.3.6.1.2.1.1.1.0")
try:
self.system_name = self.get_system_name(oid_os_name['1.3.6.1.2.1.1.1.0'])
logger.info(_('SNMP system name detected: {0}').format(self.system_name))
except KeyError:
self.system_name = None
logger.warning(_('Can not detect SNMP system name'))
return ret
def get_system_name(self, oid_system_name):
"""Get the short os name from the OS name OID string"""
short_system_name = None
if oid_system_name == '':
return short_system_name
# Find the short name in the oid_to_short_os_name dict
try:
iteritems = oid_to_short_system_name.iteritems()
except AttributeError:
# Correct issue #386
iteritems = oid_to_short_system_name.items()
for r,v in iteritems:
if re.search(r, oid_system_name):
short_system_name = v
break
return short_system_name
def update(self):
"""Update the stats using SNMP."""
# For each plugins, call the update method
for p in self._plugins:
# Set the input method to SNMP
self._plugins[p].set_input('snmp')
# print "DEBUG: Update %s stats using SNMP request" % p
self._plugins[p].set_input('snmp', self.system_name)
try:
self._plugins[p].update()
except Exception as e:
print(_("Error: Update {0} failed: {1}").format(p, e))
# pass
logger.error(_("Error: Update {0} failed: {1}").format(p, e))

View File

@ -22,11 +22,17 @@
import os
import sys
# Import Glances libs
from glances.core.glances_globals import logger
# Import mandatory Bottle lib
try:
from bottle import Bottle, template, static_file, TEMPLATE_PATH
from bottle import Bottle, template, static_file, TEMPLATE_PATH, abort, response
except ImportError:
print('Bottle module not found. Glances cannot start in web server mode.')
sys.exit(1)
logger.critical('Bottle module not found. Glances cannot start in web server mode.')
print(_("Install it using pip: # pip install bottle"))
sys.exit(2)
import json
class GlancesBottle(object):
@ -58,6 +64,7 @@ class GlancesBottle(object):
'BOLD': 'bold',
'SORT': 'sort',
'OK': 'ok',
'FILTER': 'filter',
'TITLE': 'title',
'CAREFUL': 'careful',
'WARNING': 'warning',
@ -77,13 +84,27 @@ class GlancesBottle(object):
self._app.route('/<refresh_time:int>', method=["GET", "POST"], callback=self._index)
self._app.route('/<filename:re:.*\.css>', method="GET", callback=self._css)
self._app.route('/<filename:re:.*\.js>', method="GET", callback=self._js)
# REST API
self._app.route('/api/2/pluginslist', method="GET", callback=self._api_plugins)
self._app.route('/api/2/all', method="GET", callback=self._api_all)
self._app.route('/api/2/:plugin', method="GET", callback=self._api)
self._app.route('/api/2/:plugin/limits', method="GET", callback=self._api_limits)
self._app.route('/api/2/:plugin/:item', method="GET", callback=self._api_item)
self._app.route('/api/2/:plugin/:item/:value', method="GET", callback=self._api_value)
def start(self, stats):
"""Start the bottle."""
# Init stats
self.stats = stats
self._app.run(host=self.args.bind_address, port=self.args.port)
# Init plugin list
self.plugins_list = self.stats.getAllPlugins()
# Bind the Bottle TCP address/port
bindmsg = _("Glances web server started on http://{0}:{1}/").format(self.args.bind_address, self.args.port)
logger.info(bindmsg)
print(bindmsg)
self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug)
def end(self):
"""End the bottle."""
@ -91,6 +112,7 @@ class GlancesBottle(object):
def _index(self, refresh_time=None):
"""Bottle callback for index.html (/) file."""
response.content_type = 'text/html'
# Manage parameter
if refresh_time is None:
refresh_time = self.args.time
@ -103,14 +125,146 @@ class GlancesBottle(object):
def _css(self, filename):
"""Bottle callback for *.css files."""
response.content_type = 'text/html'
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'css'))
def _js(self, filename):
"""Bottle callback for *.js files."""
response.content_type = 'text/html'
# Return the static file
return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js'))
def _api_plugins(self):
"""
Glances API RESTFul implementation
Return the plugin list
or 404 error
"""
response.content_type = 'application/json'
# Update the stat
self.stats.update()
try:
plist = json.dumps(self.plugins_list)
except Exception as e:
abort(404, "Can not get plugin list (%s)" % str(e))
return plist
def _api_all(self):
"""
Glances API RESTFul implementation
Return the JSON representation of all the plugins
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
# Update the stat
self.stats.update()
try:
# Get the JSON value of the stat ID
statval = json.dumps(self.stats.getAllAsDict())
except Exception as e:
abort(404, "Can not get stats (%s)" % str(e))
return statval
def _api(self, plugin):
"""
Glances API RESTFul implementation
Return the JSON representation of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.stats.update()
try:
# Get the JSON value of the stat ID
statval = self.stats.get_plugin(plugin).get_stats()
except Exception as e:
abort(404, "Can not get plugin %s (%s)" % (plugin, str(e)))
return statval
def _api_limits(self, plugin):
"""
Glances API RESTFul implementation
Return the JSON limits of a given plugin
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
# self.stats.update()
try:
# Get the JSON value of the stat ID
limits = self.stats.get_plugin(plugin).get_limits()
except Exception as e:
abort(404, "Can not get limits for plugin %s (%s)" % (plugin, str(e)))
return limits
def _api_item(self, plugin, item):
"""
Glances API RESTFul implementation
Return the JSON represenation of the couple plugin/item
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.stats.update()
plist = self.stats.get_plugin(plugin).get_stats_item(item)
if plist is None:
abort(404, "Can not get item %s in plugin %s" % (item, plugin))
else:
return plist
def _api_value(self, plugin, item, value):
"""
Glances API RESTFul implementation
Return the process stats (dict) for the given item=value
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
response.content_type = 'application/json'
if plugin not in self.plugins_list:
abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
# Update the stat
self.stats.update()
pdict = self.stats.get_plugin(plugin).get_stats_value(item, value)
if pdict is None:
abort(404, "Can not get item(%s)=value(%s) in plugin %s" % (item, value, plugin))
else:
return pdict
def display(self, stats, refresh_time=None):
"""Display stats on the web page.

View File

@ -21,12 +21,14 @@ import sys
import threading
import time
from glances.core.glances_globals import logger
import msvcrt
try:
import colorconsole
import colorconsole.terminal
except ImportError:
print('Colorconsole module not found. Glances cannot start in standalone mode.')
logger.critical(_('Colorconsole module not found. Glances cannot start in standalone mode.'))
sys.exit(1)
try:

View File

@ -20,10 +20,11 @@
"""CSV interface class."""
# Import sys libs
import csv
import sys
import csv
# Import Glances libs
# Import Glances lib
from glances.core.glances_globals import logger
from glances.core.glances_globals import is_py3
# List of stats enabled in the CSV output
@ -46,10 +47,10 @@ class GlancesCSV(object):
self.csv_file = open(self.csv_filename, 'wb')
self.writer = csv.writer(self.csv_file)
except IOError as e:
print(_("Error: Cannot create the CSV file: {0}").format(e))
logger.critical(_("Error: Cannot create the CSV file: {0}").format(e))
sys.exit(2)
print(_("Stats dumped to CSV file: {0}").format(self.csv_filename))
logger.info(_("Stats dumped to CSV file: {0}").format(self.csv_filename))
def exit(self):
"""Close the CSV file."""

View File

@ -23,7 +23,7 @@
import sys
# Import Glances lib
from glances.core.glances_globals import glances_logs, glances_processes, is_windows
from glances.core.glances_globals import glances_logs, glances_processes, is_linux, is_bsd, is_mac, is_windows, logger
from glances.core.glances_timer import Timer
# Import curses lib for "normal" operating system and consolelog for Windows
@ -31,8 +31,9 @@ if not is_windows:
try:
import curses
import curses.panel
from curses.textpad import Textbox, rectangle
except ImportError:
print('Curses module not found. Glances cannot start in standalone mode.')
logger.critical('Curses module not found. Glances cannot start in standalone mode.')
sys.exit(1)
else:
from glances.outputs.glances_colorconsole import WCurseLight
@ -58,7 +59,7 @@ class GlancesCurses(object):
# Init the curses screen
self.screen = curses.initscr()
if not self.screen:
print(_("Error: Cannot init the curses library.\n"))
logger.critical(_("Error: Cannot init the curses library.\n"))
sys.exit(1)
# Set curses options
@ -70,18 +71,17 @@ class GlancesCurses(object):
curses.noecho()
if hasattr(curses, 'cbreak'):
curses.cbreak()
if hasattr(curses, 'curs_set'):
try:
curses.curs_set(0)
except Exception:
pass
self.set_cursor(0)
# Init colors
self.hascolors = False
if curses.has_colors() and curses.COLOR_PAIRS > 8:
self.hascolors = True
# FG color, BG color
curses.init_pair(1, curses.COLOR_WHITE, -1)
if args.theme_white:
curses.init_pair(1, curses.COLOR_BLACK, -1)
else:
curses.init_pair(1, curses.COLOR_WHITE, -1)
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED)
curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_GREEN)
curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLUE)
@ -89,7 +89,21 @@ class GlancesCurses(object):
curses.init_pair(6, curses.COLOR_RED, -1)
curses.init_pair(7, curses.COLOR_GREEN, -1)
curses.init_pair(8, curses.COLOR_BLUE, -1)
curses.init_pair(9, curses.COLOR_MAGENTA, -1)
try:
curses.init_pair(9, curses.COLOR_MAGENTA, -1)
except:
if args.theme_white:
curses.init_pair(9, curses.COLOR_BLACK, -1)
else:
curses.init_pair(9, curses.COLOR_WHITE, -1)
try:
curses.init_pair(10, curses.COLOR_CYAN, -1)
except:
if args.theme_white:
curses.init_pair(10, curses.COLOR_BLACK, -1)
else:
curses.init_pair(10, curses.COLOR_WHITE, -1)
else:
self.hascolors = False
@ -113,6 +127,7 @@ class GlancesCurses(object):
self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD
self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD
self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
self.filter_color = curses.color_pair(10) | A_BOLD
else:
# B&W text styles
self.no_color = curses.A_NORMAL
@ -125,6 +140,7 @@ class GlancesCurses(object):
self.ifCAREFUL_color2 = curses.A_UNDERLINE
self.ifWARNING_color2 = A_BOLD
self.ifCRITICAL_color2 = curses.A_REVERSE
self.filter_color = A_BOLD
# Define the colors list (hash table) for stats
self.__colors_list = {
@ -133,6 +149,7 @@ class GlancesCurses(object):
'BOLD': A_BOLD,
'SORT': A_BOLD,
'OK': self.default_color2,
'FILTER': self.filter_color,
'TITLE': self.title_color,
'PROCESS': self.default_color2,
'STATUS': self.default_color2,
@ -155,11 +172,37 @@ class GlancesCurses(object):
# Init process sort method
self.args.process_sorted_by = 'auto'
# Init edit filter tag
self.edit_filter = False
# Catch key pressed with non blocking mode
self.term_window.keypad(1)
self.term_window.nodelay(1)
self.pressedkey = -1
# History tag
self.reset_history_tag = False
self.history_tag = False
if args.enable_history:
logger.info('Stats history enabled')
from glances.outputs.glances_history import GlancesHistory
self.glances_history = GlancesHistory()
if not self.glances_history.graph_enabled():
args.enable_history = False
logger.error('Stats history disabled because graph lib is not available')
def set_cursor(self, value):
"""Configure the cursor
0: invisible
1: visible
2: very visible
"""
if hasattr(curses, 'curs_set'):
try:
curses.curs_set(value)
except Exception:
pass
def __get_key(self, window):
# Catch ESC key AND numlock key (issue #163)
keycode = [0, 0]
@ -181,13 +224,21 @@ class GlancesCurses(object):
if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
# 'ESC'|'q' > Quit
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
elif self.pressedkey == ord('1'):
# '1' > Switch between CPU and PerCPU information
self.args.percpu = not self.args.percpu
elif self.pressedkey == ord('/'):
# '/' > Switch between short/long name for processes
self.args.process_short_name = not self.args.process_short_name
elif self.pressedkey == ord('a'):
# 'a' > Sort processes automatically
self.args.process_sorted_by = 'auto'
glances_processes.resetsort()
elif self.pressedkey == ord('b'):
# 'b' > Switch between bit/s and Byte/s for network IO
# self.net_byteps_tag = not self.net_byteps_tag
@ -195,30 +246,47 @@ class GlancesCurses(object):
elif self.pressedkey == ord('c'):
# 'c' > Sort processes by CPU usage
self.args.process_sorted_by = 'cpu_percent'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
elif self.pressedkey == ord('d'):
# 'd' > Show/hide disk I/O stats
self.args.disable_diskio = not self.args.disable_diskio
elif self.pressedkey == ord('e'):
# 'e' > Enable/Disable extended stats for top process
self.args.disable_process_extended = not self.args.disable_process_extended
if self.args.disable_process_extended:
glances_processes.disable_extended()
else:
glances_processes.enable_extended()
elif self.pressedkey == ord('f'):
# 'f' > Show/hide fs stats
self.args.disable_fs = not self.args.disable_fs
elif self.pressedkey == ord('g'):
# 'g' > History
self.history_tag = not self.history_tag
elif self.pressedkey == ord('h'):
# 'h' > Show/hide help
self.args.help_tag = not self.args.help_tag
elif self.pressedkey == ord('i'):
# 'i' > Sort processes by IO rate (not available on OS X)
self.args.process_sorted_by = 'io_counters'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
elif self.pressedkey == ord('l'):
# 'l' > Show/hide log messages
self.args.disable_log = not self.args.disable_log
elif self.pressedkey == ord('m'):
# 'm' > Sort processes by MEM usage
self.args.process_sorted_by = 'memory_percent'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
elif self.pressedkey == ord('n'):
# 'n' > Show/hide network stats
self.args.disable_network = not self.args.disable_network
elif self.pressedkey == ord('p'):
# 'p' > Sort processes by name
self.args.process_sorted_by = 'name'
glances_processes.setmanualsortkey(self.args.process_sorted_by)
elif self.pressedkey == ord('r'):
# 'r' > Reset history
self.reset_history_tag = not self.reset_history_tag
elif self.pressedkey == ord('s'):
# 's' > Show/hide sensors stats (Linux-only)
self.args.disable_sensors = not self.args.disable_sensors
@ -247,7 +315,7 @@ class GlancesCurses(object):
return self.pressedkey
def end(self):
"""Shutdown the curses window."""
"""Shutdown the curses window."""
if hasattr(curses, 'echo'):
curses.echo()
if hasattr(curses, 'nocbreak'):
@ -258,6 +326,31 @@ class GlancesCurses(object):
except Exception:
pass
curses.endwin()
def init_line_column(self):
"""Init the line and column position for the curses inteface"""
self.line = 0
self.column = 0
self.next_line = 0
self.next_column = 0
def init_line(self):
"""Init the line position for the curses inteface"""
self.line = 0
self.next_line = 0
def init_column(self):
"""Init the column position for the curses inteface"""
self.column = 0
self.next_column = 0
def new_line(self):
"""New line in the curses interface"""
self.line = self.next_line
def new_column(self):
"""New column in the curses interface"""
self.column = self.next_column
def display(self, stats, cs_status="None"):
"""Display stats on the screen.
@ -273,96 +366,241 @@ class GlancesCurses(object):
True if the stats have been displayed
False if the help have been displayed
"""
# Init the internal line/column dict for Glances Curses
self.line_to_y = {}
self.column_to_x = {}
# Init the internal line/column for Glances Curses
self.init_line_column()
# Get the screen size
screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0]
# No processes list in SNMP mode
if cs_status == 'SNMP':
# so... more space for others plugins
plugin_max_width = 43
else:
plugin_max_width = None
# Update the stats messages
###########################
# Update the client server status
self.args.cs_status = cs_status
stats_system = stats.get_plugin('system').get_stats_display(args=self.args)
stats_uptime = stats.get_plugin('uptime').get_stats_display()
if self.args.percpu:
stats_percpu = stats.get_plugin('percpu').get_stats_display()
else:
stats_cpu = stats.get_plugin('cpu').get_stats_display()
stats_load = stats.get_plugin('load').get_stats_display()
stats_mem = stats.get_plugin('mem').get_stats_display()
stats_memswap = stats.get_plugin('memswap').get_stats_display()
stats_network = stats.get_plugin('network').get_stats_display(args=self.args, max_width=plugin_max_width)
stats_diskio = stats.get_plugin('diskio').get_stats_display(args=self.args)
stats_fs = stats.get_plugin('fs').get_stats_display(args=self.args, max_width=plugin_max_width)
stats_sensors = stats.get_plugin('sensors').get_stats_display(args=self.args)
stats_now = stats.get_plugin('now').get_stats_display()
stats_processcount = stats.get_plugin('processcount').get_stats_display(args=self.args)
stats_processlist = stats.get_plugin('processlist').get_stats_display(args=self.args)
stats_monitor = stats.get_plugin('monitor').get_stats_display(args=self.args)
stats_alert = stats.get_plugin('alert').get_stats_display(args=self.args)
# Display the stats on the curses interface
###########################################
# Help screen (on top of the other stats)
if self.args.help_tag:
# Display the stats...
self.display_plugin(stats.get_plugin('help').get_stats_display(args=self.args))
# ... and exit
return False
# Update the client server status
self.args.cs_status = cs_status
# Display first line (system+uptime)
stats_system = stats.get_plugin('system').get_stats_display(args=self.args)
stats_uptime = stats.get_plugin('uptime').get_stats_display()
self.new_line()
l = self.get_stats_display_width(stats_system) + self.get_stats_display_width(stats_uptime) + self.space_between_column
self.display_plugin(stats_system, display_optional=(screen_x >= l))
self.new_column()
self.display_plugin(stats_uptime)
# Display second line (CPU|PERCPU+LOAD+MEM+SWAP+<SUMMARY>)
# CPU|PERCPU
self.init_column()
self.new_line()
if self.args.percpu:
stats_percpu = stats.get_plugin('percpu').get_stats_display()
l = self.get_stats_display_width(stats_percpu)
else:
stats_cpu = stats.get_plugin('cpu').get_stats_display()
l = self.get_stats_display_width(stats_cpu)
stats_load = stats.get_plugin('load').get_stats_display()
stats_mem = stats.get_plugin('mem').get_stats_display()
stats_memswap = stats.get_plugin('memswap').get_stats_display()
l += self.get_stats_display_width(stats_load) + self.get_stats_display_width(stats_mem) + self.get_stats_display_width(stats_memswap)
# Space between column
if screen_x > (3 * self.space_between_column + l):
self.space_between_column = int((screen_x - l) / 3)
space_number = int(stats_load['msgdict'] != []) + int(stats_mem['msgdict'] != []) + int(stats_memswap['msgdict'] != [])
if space_number == 0:
space_number = 1
if screen_x > (space_number * self.space_between_column + l):
self.space_between_column = int((screen_x - l) / space_number)
# Display
if self.args.percpu:
self.display_plugin(stats_percpu)
else:
self.display_plugin(stats_cpu, display_optional=(screen_x >= 80))
self.new_column()
self.display_plugin(stats_load)
self.display_plugin(stats_mem, display_optional=(screen_x >= (3 * self.space_between_column + l)))
self.new_column()
self.display_plugin(stats_mem, display_optional=(screen_x >= (space_number * self.space_between_column + l)))
self.new_column()
self.display_plugin(stats_memswap)
# Space between column
self.space_between_column = 3
# Display left sidebar (NETWORK+DISKIO+FS+SENSORS)
self.display_plugin(stats.get_plugin('network').get_stats_display(args=self.args))
self.display_plugin(stats.get_plugin('diskio').get_stats_display(args=self.args))
self.display_plugin(stats.get_plugin('fs').get_stats_display(args=self.args))
self.display_plugin(stats.get_plugin('sensors').get_stats_display(args=self.args))
# Display last line (currenttime)
self.display_plugin(stats.get_plugin('now').get_stats_display())
# Backup line position
self.saved_line = self.next_line
# Display right sidebar (PROCESS_COUNT+MONITORED+PROCESS_LIST+ALERT)
# Display left sidebar (NETWORK+DISKIO+FS+SENSORS+Current time)
self.init_column()
self.new_line()
self.display_plugin(stats_network)
self.new_line()
self.display_plugin(stats_diskio)
self.new_line()
self.display_plugin(stats_fs)
self.new_line()
self.display_plugin(stats_sensors)
self.new_line()
self.display_plugin(stats_now)
# If space available...
if screen_x > 52:
stats_processcount = stats.get_plugin('processcount').get_stats_display(args=self.args)
stats_processlist = stats.get_plugin('processlist').get_stats_display(args=self.args)
stats_alert = stats.get_plugin('alert').get_stats_display(args=self.args)
stats_monitor = stats.get_plugin('monitor').get_stats_display(args=self.args)
# Display
# Restore line position
self.next_line = self.saved_line
# Display right sidebar (PROCESS_COUNT+MONITORED+PROCESS_LIST+ALERT)
self.new_column()
self.new_line()
self.display_plugin(stats_processcount)
self.display_plugin(stats_monitor)
if glances_processes.get_process_filter() == None and cs_status == 'None':
# Do not display stats monitor list if a filter exist
self.new_line()
self.display_plugin(stats_monitor)
self.new_line()
self.display_plugin(stats_processlist,
display_optional=(screen_x > 102),
display_additional=(is_mac == False),
max_y=(screen_y - self.get_stats_display_height(stats_alert) - 2))
self.new_line()
self.display_plugin(stats_alert)
# History option
# Generate history graph
if self.history_tag and self.args.enable_history:
self.display_popup(_("Graphs history generated in %s") % self.glances_history.get_output_folder())
self.glances_history.generate_graph(stats)
elif self.reset_history_tag and self.args.enable_history:
self.display_popup(_("Reset history"))
self.glances_history.reset(stats)
elif (self.history_tag or self.reset_history_tag) and not self.args.enable_history:
self.display_popup(_("History disabled\nEnable it using --enable-history"))
self.history_tag = False
self.reset_history_tag = False
# Display edit filter popup
# Only in standalone mode (cs_status == 'None')
if self.edit_filter and cs_status == 'None':
new_filter = self.display_popup(_("Process filter pattern: "),
is_input=True,
input_value=glances_processes.get_process_filter())
glances_processes.set_process_filter(new_filter)
elif self.edit_filter and cs_status != 'None':
self.display_popup(_("Process filter only available in standalone mode"))
self.edit_filter = False
return True
def display_plugin(self, plugin_stats, display_optional=True, max_y=65535):
def display_popup(self, message,
size_x=None, size_y=None,
duration=3,
is_input=False,
input_size=30,
input_value=None):
"""
If is_input is False:
Display a centered popup with the given message during duration seconds
If size_x and size_y: set the popup size
else set it automatically
Return True if the popup could be displayed
If is_input is True:
Display a centered popup with the given message and a input field
If size_x and size_y: set the popup size
else set it automatically
Return the input string or None if the field is empty
"""
# Center the popup
if size_x is None:
size_x = len(message) + 4
# Add space for the input field
if is_input:
size_x += input_size
if size_y is None:
size_y = message.count('\n') + 1 + 4
screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0]
if size_x > screen_x or size_y > screen_y:
# No size to display the popup => abord
return False
pos_x = int((screen_x - size_x) / 2)
pos_y = int((screen_y - size_y) / 2)
# Create the popup
popup = curses.newwin(size_y, size_x, pos_y, pos_x)
# Fill the popup
popup.border()
# Add the message
y = 0
for m in message.split('\n'):
popup.addnstr(2 + y, 2, m, len(m))
y += 1
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'])
# Init the field with the current value
if input_value is not None:
subpop.addnstr(0, 0, input_value, len(input_value))
# Display the popup
popup.refresh()
subpop.refresh()
# Create the textbox inside the subwindows
self.set_cursor(2)
textbox = glances_textbox(subpop, insert_mode=False)
textbox.edit()
self.set_cursor(0)
if textbox.gather() != '':
logger.debug(_("User enters the following process filter patern: %s") % textbox.gather())
return textbox.gather()[:-1]
else:
logger.debug(_("User clears the process filter patern"))
return None
else:
# Display the popup
popup.refresh()
curses.napms(duration * 1000)
return True
def display_plugin(self, plugin_stats,
display_optional=True,
display_additional=True,
max_y=65535):
"""Display the plugin_stats on the screen.
If display_optional=True display the optional stats.
If display_optional=True display the optional stats
If display_additional=True display additionnal stats
max_y do not display line > max_y
"""
# Exit if:
# - the plugin_stats message is empty
# - the display tag = False
if plugin_stats['msgdict'] == [] or not plugin_stats['display']:
# Display the next plugin at the current plugin position
try:
self.column_to_x[plugin_stats['column'] + 1] = self.column_to_x[plugin_stats['column']]
self.line_to_y[plugin_stats['line'] + 1] = self.line_to_y[plugin_stats['line']]
except Exception:
pass
# Exit
return 0
@ -371,21 +609,17 @@ class GlancesCurses(object):
screen_y = self.screen.getmaxyx()[0]
# Set the upper/left position of the message
if plugin_stats['column'] < 0:
if plugin_stats['align'] == 'right':
# Right align (last column)
display_x = screen_x - self.get_stats_display_width(plugin_stats)
else:
if plugin_stats['column'] not in self.column_to_x:
self.column_to_x[plugin_stats['column']] = plugin_stats['column']
display_x = self.column_to_x[plugin_stats['column']]
if plugin_stats['line'] < 0:
display_x = self.column
if plugin_stats['align'] == 'bottom':
# Bottom (last line)
display_y = screen_y - self.get_stats_display_height(plugin_stats)
else:
if plugin_stats['line'] not in self.line_to_y:
self.line_to_y[plugin_stats['line']] = plugin_stats['line']
display_y = self.line_to_y[plugin_stats['line']]
display_y = self.line
# Display
x = display_x
y = display_y
@ -407,13 +641,16 @@ class GlancesCurses(object):
# If display_optional = False do not display optional stats
if not display_optional and m['optional']:
continue
# If display_additional = False do not display additional stats
if not display_additional and m['additional']:
continue
# Is it possible to display the stat with the current screen size
# !!! Crach if not try/except... Why ???
try:
self.term_window.addnstr(y, x,
m['msg'],
screen_x - x, # Do not disply outside the screen
self.__colors_list[m['decoration']])
self.__colors_list[m['decoration']])
except:
pass
else:
@ -421,10 +658,8 @@ class GlancesCurses(object):
x = x + len(m['msg'])
# Compute the next Glances column/line position
if plugin_stats['column'] > -1:
self.column_to_x[plugin_stats['column'] + 1] = x + self.space_between_column
if plugin_stats['line'] > -1:
self.line_to_y[plugin_stats['line'] + 1] = y + self.space_between_line
self.next_column = max(self.next_column, x + self.space_between_column)
self.next_line = max(self.next_line, y + self.space_between_line)
def erase(self):
"""Erase the content of the screen."""
@ -461,8 +696,8 @@ class GlancesCurses(object):
while not countdown.finished():
# Getkey
if self.__catch_key() > -1:
# flush display
self.flush(stats, cs_status=cs_status)
# Redraw display
self.flush(stats, cs_status=cs_status)
# Wait 100ms...
curses.napms(100)
@ -496,3 +731,17 @@ class GlancesCurses(object):
return 0
else:
return c + 1
if not is_windows:
class glances_textbox(Textbox):
"""
"""
def __init__(*args, **kwargs):
Textbox.__init__(*args, **kwargs)
def do_command(self, ch):
if ch == 10: # Enter
return 0
if ch == 127: # Enter
return 8
return Textbox.do_command(self, ch)

View File

@ -0,0 +1,111 @@
# -*- 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/>.
"""History class."""
# Import system lib
import os
import tempfile
# Import Glances lib
from glances.core.glances_globals import logger
# Import specific lib
try:
from matplotlib import __version__ as matplotlib_version
import matplotlib.pyplot as plt
import matplotlib.dates as dates
except:
matplotlib_check = False
logger.warning('Can not load Matplotlib library. Please install it using "pip install matplotlib"')
else:
matplotlib_check = True
logger.info('Load Matplotlib version %s' % matplotlib_version)
class GlancesHistory(object):
"""This class define the object to manage stats history"""
def __init__(self, output_folder=tempfile.gettempdir()):
# !!! MINUS: matplotlib footprint (mem/cpu) => Fork process ?
# !!! MINUS: Mem used to store history
# !!! TODO: sampling before graph => Usefull ?
# !!! TODO: do not display first two point (glances is running)
# !!! TODO: replace /tmp by a cross platform way to get /tmp folder
self.output_folder = output_folder
def get_output_folder(self):
"""Return the output folder where the graph are generated"""
return self.output_folder
def graph_enabled(self):
"""Return True if Glances can generaate history graphs"""
return matplotlib_check
def reset(self, stats):
"""
Reset all the history
"""
if not self.graph_enabled():
return False
for p in stats.getAllPlugins():
h = stats.get_plugin(p).get_stats_history()
if h is not None:
stats.get_plugin(p).reset_stats_history()
return True
def generate_graph(self, stats):
"""
Generate graphs from plugins history
"""
if not self.graph_enabled():
return False
for p in stats.getAllPlugins():
h = stats.get_plugin(p).get_stats_history()
if h is not None:
# Build the graph
# fig = plt.figure(dpi=72)
ax = plt.subplot(1, 1, 1)
# Label
plt.title("%s stats" % p)
handles = []
for i in stats.get_plugin(p).get_items_history_list():
handles.append(plt.Rectangle((0, 0), 1, 1, fc=i['color'], ec=i['color'], linewidth=1))
labels = [i['name'] for i in stats.get_plugin(p).get_items_history_list()]
plt.legend(handles, labels, loc=1, prop={'size':9})
formatter = dates.DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(formatter)
# ax.set_ylabel('%')
# Draw the stats
for i in stats.get_plugin(p).get_items_history_list():
ax.plot_date(h['date'], h[i['name']],
i['color'],
label='%s' % i['name'],
xdate=True, ydate=False)
# Save and display
plt.savefig(os.path.join(self.output_folder, 'glances_%s.png' % p), dpi=72)
# plt.show()
return True

View File

@ -61,6 +61,9 @@ div#newline{
#ok {
color: green;
}
#filter {
color: cyan;
}
#ok_log {
background-color: green;
color: white;

View File

@ -20,6 +20,7 @@
"""Alert plugin."""
# Import system lib
import types
from datetime import datetime
# Import Glances libs
@ -40,12 +41,9 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 1
# Enter -1 to diplay bottom
self.line_curse = -1
self.set_align('bottom')
# Init the stats
self.reset()
@ -107,7 +105,7 @@ class Plugin(GlancesPlugin):
msg = str(alert[3])
ret.append(self.curse_add_line(msg, decoration=alert[2]))
# Min / Mean / Max
if alert[6] == alert[4]:
if self.approx_equal(alert[6], alert[4], tolerance=0.1):
msg = ' ({0:.1f})'.format(alert[5])
else:
msg = _(" (Min:{0:.1f} Mean:{1:.1f} Max:{2:.1f})").format(alert[6], alert[5], alert[4])
@ -122,3 +120,12 @@ class Plugin(GlancesPlugin):
# ret.append(self.curse_add_line(msg))
return ret
def approx_equal(self, a, b, tolerance=0.0):
"""
Compare a with b using the tolerance (if numerical)
"""
if str(int(a)).isdigit() and str(int(b)).isdigit():
return abs(a-b) <= max(abs(a), abs(b)) * tolerance
else:
return a == b

View File

@ -19,14 +19,15 @@
"""Battery plugin."""
# Import Glances libs
from glances.core.glances_globals import logger
from glances.plugins.glances_plugin import GlancesPlugin
# Batinfo library (optional; Linux-only)
try:
import batinfo
except ImportError:
pass
# Import Glances libs
from glances.plugins.glances_plugin import GlancesPlugin
logger.debug(_("Cannot grab battery sensor. Missing BatInfo library."))
class Plugin(GlancesPlugin):
@ -84,18 +85,12 @@ class GlancesGrabBat(object):
self.bat_list = []
self.update()
except Exception:
# print(_("Warning: Cannot grab battery sensor. Missing BatInfo library."))
self.initok = False
def update(self):
"""Update the stats."""
if self.initok:
reply = self.bat.update()
if reply is not None:
self.bat_list = []
new_item = {'label': _("Battery (%)"),
'value': self.getcapacitypercent()}
self.bat_list.append(new_item)
self.bat_list = [{'label': _("Battery (%)"), 'value': self.getcapacitypercent()}]
else:
self.bat_list = []
@ -108,15 +103,14 @@ class GlancesGrabBat(object):
if not self.initok or self.bat.stat == []:
return []
# Init the bsum (sum of percent) and bcpt (number of batteries)
# Init the bsum (sum of percent)
# and Loop over batteries (yes a computer could have more than 1 battery)
bsum = 0
for bcpt in range(len(self.bat.stat)):
for b in self.bat.stat:
try:
bsum = bsum + int(self.bat.stat[bcpt].capacity)
bsum = bsum + int(b.capacity)
except ValueError:
return []
bcpt = bcpt + 1
# Return the global percent
return int(bsum / bcpt)
return int(bsum / len(self.bat.stat))

View File

@ -27,13 +27,20 @@ import psutil
# percentage of user CPU time: .1.3.6.1.4.1.2021.11.9.0
# percentages of system CPU time: .1.3.6.1.4.1.2021.11.10.0
# percentages of idle CPU time: .1.3.6.1.4.1.2021.11.11.0
snmp_oid = {'user': '1.3.6.1.4.1.2021.11.9.0',
'system': '1.3.6.1.4.1.2021.11.10.0',
'idle': '1.3.6.1.4.1.2021.11.11.0'}
snmp_oid = {'default': {'user': '1.3.6.1.4.1.2021.11.9.0',
'system': '1.3.6.1.4.1.2021.11.10.0',
'idle': '1.3.6.1.4.1.2021.11.11.0'},
'windows': {'percent': '1.3.6.1.2.1.25.3.3.1.2'},
'esxi': {'percent': '1.3.6.1.2.1.25.3.3.1.2'}}
# Define the history items list
# 'color' define the graph color in #RGB format
# All items in this list will be historised if the --enable-history tag is set
items_history_list = [{'name': 'user', 'color': '#00FF00'},
{'name': 'system', 'color': '#FF0000'}]
class Plugin(GlancesPlugin):
"""
Glances' CPU plugin.
@ -42,16 +49,10 @@ class Plugin(GlancesPlugin):
def __init__(self, args=None):
"""Init the CPU plugin."""
GlancesPlugin.__init__(self, args=args)
GlancesPlugin.__init__(self, args=args, items_history_list=items_history_list)
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 1
# Init stats
self.first_call = True
@ -91,14 +92,45 @@ class Plugin(GlancesPlugin):
self.stats[cpu] = getattr(cputimespercent, cpu)
elif self.get_input() == 'snmp':
# Update stats using SNMP
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid)
if self.stats['user'] == '':
self.reset()
return self.stats
if self.get_short_system_name() in ('windows', 'esxi'):
# Windows or VMWare ESXi
# You can find the CPU utilization of windows system by querying the oid
# Give also the number of core (number of element in the table)
try:
cpu_stats = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
bulk=True)
except KeyError:
self.reset()
for key in self.stats.iterkeys():
self.stats[key] = float(self.stats[key])
# Iter through CPU and compute the idle CPU stats
self.stats['nb_log_core'] = 0
self.stats['idle'] = 0
for c in cpu_stats:
if c.startswith('percent'):
self.stats['idle'] += float(cpu_stats['percent.3'])
self.stats['nb_log_core'] += 1
if self.stats['nb_log_core'] > 0:
self.stats['idle'] = self.stats['idle'] / self.stats['nb_log_core']
self.stats['idle'] = 100 - self.stats['idle']
else:
# Default behavor
try:
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()])
except KeyError:
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default'])
if self.stats['idle'] == '':
self.reset()
return self.stats
# Convert SNMP stats to float
for key in list(self.stats.keys()):
self.stats[key] = float(self.stats[key])
# Update the history list
self.update_stats_history()
return self.stats
@ -112,12 +144,17 @@ class Plugin(GlancesPlugin):
return ret
# Build the string message
# If user stat is not here, display only idle / total CPU usage (for exemple on Windows OS)
idle_tag = 'user' not in self.stats
# Header
msg = '{0:8}'.format(_("CPU"))
ret.append(self.curse_add_line(msg, "TITLE"))
# Total CPU usage
msg = '{0:>6.1%}'.format((100 - self.stats['idle']) / 100)
ret.append(self.curse_add_line(msg))
if idle_tag:
ret.append(self.curse_add_line(msg, self.get_alert_log((100 - self.stats['idle']) / 100, header="system")))
else:
ret.append(self.curse_add_line(msg))
# Nice CPU
if 'nice' in self.stats:
msg = ' {0:8}'.format(_("nice:"))
@ -131,7 +168,12 @@ class Plugin(GlancesPlugin):
msg = '{0:8}'.format(_("user:"))
ret.append(self.curse_add_line(msg))
msg = '{0:>6.1%}'.format(self.stats['user'] / 100)
ret.append(self.curse_add_line(msg, self.get_alert_log(self.stats['user'], header="user")))
ret.append(self.curse_add_line(msg, self.get_alert_log(self.stats['user'], header="user")))
elif 'idle' in self.stats:
msg = '{0:8}'.format(_("idle:"))
ret.append(self.curse_add_line(msg))
msg = '{0:>6.1%}'.format(self.stats['idle'] / 100)
ret.append(self.curse_add_line(msg))
# IRQ CPU
if 'irq' in self.stats:
msg = ' {0:8}'.format(_("irq:"))
@ -141,11 +183,16 @@ class Plugin(GlancesPlugin):
# New line
ret.append(self.curse_new_line())
# System CPU
if 'system' in self.stats:
if 'system' in self.stats and not idle_tag:
msg = '{0:8}'.format(_("system:"))
ret.append(self.curse_add_line(msg))
msg = '{0:>6.1%}'.format(self.stats['system'] / 100)
ret.append(self.curse_add_line(msg, self.get_alert_log(self.stats['system'], header="system")))
else:
msg = '{0:8}'.format(_("core:"))
ret.append(self.curse_add_line(msg))
msg = '{0:>6}'.format(self.stats['nb_log_core'])
ret.append(self.curse_add_line(msg))
# IOWait CPU
if 'iowait' in self.stats:
msg = ' {0:8}'.format(_("iowait:"))
@ -155,7 +202,7 @@ class Plugin(GlancesPlugin):
# New line
ret.append(self.curse_new_line())
# Idle CPU
if 'idle' in self.stats:
if 'idle' in self.stats and not idle_tag:
msg = '{0:8}'.format(_("idle:"))
ret.append(self.curse_add_line(msg))
msg = '{0:>6.1%}'.format(self.stats['idle'] / 100)

View File

@ -39,12 +39,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 3
# Init the stats
self.reset()
@ -134,13 +128,15 @@ class Plugin(GlancesPlugin):
# Do not display hidden interfaces
if self.is_hide(i['disk_name']):
continue
# Is there an alias for the disk name ?
disk_name = self.has_alias(i['disk_name'])
if disk_name is None:
disk_name = i['disk_name']
# New line
ret.append(self.curse_new_line())
if len(i['disk_name']) > 9:
if len(disk_name) > 9:
# Cut disk name if it is too long
disk_name = '_' + i['disk_name'][-8:]
else:
disk_name = i['disk_name']
disk_name = '_' + disk_name[-8:]
msg = '{0:9}'.format(disk_name)
ret.append(self.curse_add_line(msg))
txps = self.auto_unit(int(i['read_bytes'] // i['time_since_update']))

View File

@ -19,8 +19,14 @@
"""File system plugin."""
# System libs
import base64
# Glances libs
from glances.core.glances_globals import version, logger
from glances.plugins.glances_plugin import GlancesPlugin
# PSutil lib for local grab
import psutil
# SNMP OID
@ -37,11 +43,16 @@ import psutil
# Used space on the disk: .1.3.6.1.4.1.2021.9.1.8.1
# Percentage of space used on disk: .1.3.6.1.4.1.2021.9.1.9.1
# Percentage of inodes used on disk: .1.3.6.1.4.1.2021.9.1.10.1
snmp_oid = {'mnt_point': '1.3.6.1.4.1.2021.9.1.2',
'device_name': '1.3.6.1.4.1.2021.9.1.3',
'size': '1.3.6.1.4.1.2021.9.1.6',
'used': '1.3.6.1.4.1.2021.9.1.8',
'percent': '1.3.6.1.4.1.2021.9.1.9'}
snmp_oid = {'default': {'mnt_point': '1.3.6.1.4.1.2021.9.1.2',
'device_name': '1.3.6.1.4.1.2021.9.1.3',
'size': '1.3.6.1.4.1.2021.9.1.6',
'used': '1.3.6.1.4.1.2021.9.1.8',
'percent': '1.3.6.1.4.1.2021.9.1.9'},
'windows': {'mnt_point': '1.3.6.1.2.1.25.2.3.1.3',
'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4',
'size': '1.3.6.1.2.1.25.2.3.1.5',
'used': '1.3.6.1.2.1.25.2.3.1.6'}}
snmp_oid['esxi'] = snmp_oid['windows']
class Plugin(GlancesPlugin):
@ -57,12 +68,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 4
# Init the stats
self.reset()
@ -109,22 +114,42 @@ class Plugin(GlancesPlugin):
elif self.get_input() == 'snmp':
# Update stats using SNMP
# SNMP bulk command to get all file system in one shot
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid, bulk=True)
# SNMP bulk command to get all file system in one shot
try:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
bulk=True)
except KeyError:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid['default'],
bulk=True)
# Loop over fs
for fs in fs_stat:
fs_current = {}
fs_current['device_name'] = fs_stat[fs]['device_name']
fs_current['mnt_point'] = fs
fs_current['size'] = int(fs_stat[fs]['size']) * 1024
fs_current['used'] = int(fs_stat[fs]['used']) * 1024
fs_current['percent'] = float(fs_stat[fs]['percent'])
self.stats.append(fs_current)
if self.get_short_system_name() in ('windows', 'esxi'):
# Windows or ESXi tips
for fs in fs_stat:
# Memory stats are grabed in the same OID table (ignore it)
if fs == 'Virtual Memory' or fs == 'Physical Memory' or fs == 'Real Memory':
continue
fs_current = {}
fs_current['device_name'] = ''
fs_current['mnt_point'] = fs.partition(' ')[0]
fs_current['size'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
fs_current['used'] = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
fs_current['percent'] = float(fs_current['used'] * 100 / fs_current['size'])
self.stats.append(fs_current)
else:
# Default behavor
for fs in fs_stat:
fs_current = {}
fs_current['device_name'] = fs_stat[fs]['device_name']
fs_current['mnt_point'] = fs
fs_current['size'] = int(fs_stat[fs]['size']) * 1024
fs_current['used'] = int(fs_stat[fs]['used']) * 1024
fs_current['percent'] = float(fs_stat[fs]['percent'])
self.stats.append(fs_current)
return self.stats
def msg_curse(self, args=None):
def msg_curse(self, args=None, max_width=None):
"""Return the dict to display in the curse interface."""
# Init the return message
ret = []
@ -133,9 +158,16 @@ class Plugin(GlancesPlugin):
if self.stats == [] or args.disable_fs:
return ret
# Max size for the fsname name
if max_width is not None and max_width >= 23:
# Interface size name = max_width - space for interfaces bitrate
fsname_max_width = max_width - 14
else:
fsname_max_width = 9
# Build the string message
# Header
msg = '{0:9}'.format(_("FILE SYS"))
msg = '{0:{width}}'.format(_("FILE SYS"), width=fsname_max_width)
ret.append(self.curse_add_line(msg, "TITLE"))
msg = '{0:>7}'.format(_("Used"))
ret.append(self.curse_add_line(msg))
@ -146,15 +178,17 @@ class Plugin(GlancesPlugin):
for i in sorted(self.stats, key=lambda fs: fs['mnt_point']):
# New line
ret.append(self.curse_new_line())
if len(i['mnt_point']) + len(i['device_name'].split('/')[-1]) <= 6:
if i['device_name'] == '' or i['device_name'] == 'none':
mnt_point = i['mnt_point'][-fsname_max_width+1:]
elif len(i['mnt_point']) + len(i['device_name'].split('/')[-1]) <= fsname_max_width - 3:
# If possible concatenate mode info... Glances touch inside :)
mnt_point = i['mnt_point'] + ' (' + i['device_name'].split('/')[-1] + ')'
elif len(i['mnt_point']) > 9:
elif len(i['mnt_point']) > fsname_max_width:
# Cut mount point name if it is too long
mnt_point = '_' + i['mnt_point'][-8:]
mnt_point = '_' + i['mnt_point'][-fsname_max_width+1:]
else:
mnt_point = i['mnt_point']
msg = '{0:9}'.format(mnt_point)
msg = '{0:{width}}'.format(mnt_point, width=fsname_max_width)
ret.append(self.curse_add_line(msg))
msg = '{0:>7}'.format(self.auto_unit(i['used']))
ret.append(self.curse_add_line(msg, self.get_alert(i['used'], max=i['size'])))

View File

@ -32,18 +32,15 @@ class Plugin(GlancesPlugin):
"""Glances' help plugin."""
def __init__(self, args=None):
def __init__(self, args=None, config=None):
"""Init the plugin."""
GlancesPlugin.__init__(self, args=args)
# Set the config instance
self.config = config
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 0
def update(self):
"""No stats. It is just a plugin to display the help."""
@ -58,62 +55,88 @@ class Plugin(GlancesPlugin):
# Header
msg = '{0} {1}'.format(appname.title(), version)
ret.append(self.curse_add_line(msg, "TITLE"))
msg = _(" with psutil {0}").format(psutil_version)
msg = _(" with PSutil {0}").format(psutil_version)
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
# Configuration file path
try:
msg = '{0}: {1}'.format(_("Configuration file"), self.config.get_loaded_config_file())
except AttributeError as e:
pass
else:
ret.append(self.curse_new_line())
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
# Keys
msg_col = ' {0:1} {1:35}'
msg_col2 = ' {0:1} {1:35}'
ret.append(self.curse_new_line())
msg = msg_col.format(_("a"), _("Sort processes automatically"))
msg = msg_col.format("a", _("Sort processes automatically"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("b"), _("Bytes or bits for network I/O"))
msg = msg_col2.format("b", _("Bytes or bits for network I/O"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("c"), _("Sort processes by CPU%"))
msg = msg_col.format("c", _("Sort processes by CPU%"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("l"), _("Show/hide logs (alerts)"))
msg = msg_col2.format("l", _("Show/hide logs (alerts)"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("m"), _("Sort processes by MEM%"))
msg = msg_col.format("m", _("Sort processes by MEM%"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("w"), _("Delete warning alerts"))
msg = msg_col2.format("w", _("Delete warning alerts"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("p"), _("Sort processes by name"))
msg = msg_col.format("p", _("Sort processes by name"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("x"), _("Delete warning and critical alerts"))
msg = msg_col2.format("x", _("Delete warning and critical alerts"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("i"), _("Sort processes by I/O rate"))
msg = msg_col.format("i", _("Sort processes by I/O rate"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("1"), _("Global CPU or per-CPU stats"))
msg = msg_col2.format("1", _("Global CPU or per-CPU stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("d"), _("Show/hide disk I/O stats"))
msg = msg_col.format("d", _("Show/hide disk I/O stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("h"), _("Show/hide this help screen"))
msg = msg_col2.format("h", _("Show/hide this help screen"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("f"), _("Show/hide file system stats"))
msg = msg_col.format("f", _("Show/hide file system stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("t"), _("View network I/O as combination"))
msg = msg_col2.format("t", _("View network I/O as combination"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("n"), _("Show/hide network stats"))
msg = msg_col.format("n", _("Show/hide network stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("u"), _("View cumulative network I/O"))
msg = msg_col2.format("u", _("View cumulative network I/O"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("s"), _("Show/hide sensors stats"))
msg = msg_col.format("s", _("Show/hide sensors stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format(_("z"), _("Enable/disable processes stats"))
msg = msg_col2.format("g", _("Generate graphs for current history"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format(_("q"), _("Quit (Esc and Ctrl-C also work)"))
msg = msg_col.format("z", _("Enable/disable processes stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("r", _("Reset history"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("e", _("Enable/disable top extended stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("q", _("Quit (Esc and Ctrl-C also work)"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("/", _("Enable/disable short processes name"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())
msg = '{0}: {1}'.format("ENTER", _("Edit the process filter patern"))
ret.append(self.curse_add_line(msg))
# Return the message with decoration
return ret

View File

@ -23,6 +23,7 @@
import os
# Import Glances libs
from glances.core.glances_globals import logger
from glances.plugins.glances_core import Plugin as CorePlugin
from glances.plugins.glances_plugin import GlancesPlugin
@ -34,6 +35,13 @@ snmp_oid = {'min1': '1.3.6.1.4.1.2021.10.1.3.1',
'min5': '1.3.6.1.4.1.2021.10.1.3.2',
'min15': '1.3.6.1.4.1.2021.10.1.3.3'}
# Define the history items list
# All items in this list will be historised if the --enable-history tag is set
# 'color' define the graph color in #RGB format
items_history_list = [{'name': 'min1', 'color': '#0000FF'},
{'name': 'min5', 'color': '#0000AA'},
{'name': 'min15', 'color': '#000044'}]
class Plugin(GlancesPlugin):
@ -44,20 +52,20 @@ class Plugin(GlancesPlugin):
def __init__(self, args=None):
"""Init the plugin."""
GlancesPlugin.__init__(self, args=args)
GlancesPlugin.__init__(self, args=args, items_history_list=items_history_list)
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 1
# Enter -1 to diplay bottom
self.line_curse = 1
# Init stats
self.reset()
# Call CorePlugin in order to display the core number
try:
self.nb_log_core = CorePlugin(args=self.args).update()["log"]
except Exception:
self.nb_log_core = 0
def reset(self):
"""Reset/init the stats."""
self.stats = {}
@ -67,12 +75,6 @@ class Plugin(GlancesPlugin):
# Reset stats
self.reset()
# Call CorePlugin in order to display the core number
try:
nb_log_core = CorePlugin().update()["log"]
except Exception:
nb_log_core = 0
if self.get_input() == 'local':
# Update stats using the standard system lib
@ -85,19 +87,28 @@ class Plugin(GlancesPlugin):
self.stats = {'min1': load[0],
'min5': load[1],
'min15': load[2],
'cpucore': nb_log_core}
'cpucore': self.nb_log_core}
elif self.get_input() == 'snmp':
# Update stats using SNMP
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid)
self.stats['cpucore'] = nb_log_core
if self.stats['min1'] == '':
self.reset()
return self.stats
for key in self.stats.iterkeys():
self.stats[key] = float(self.stats[key])
# Python 3 return a dict like:
# {'min1': "b'0.08'", 'min5': "b'0.12'", 'min15': "b'0.15'"}
try:
iteritems = self.stats.iteritems()
except AttributeError:
iteritems = self.stats.items()
for k, v in iteritems:
self.stats[k] = float(v)
self.stats['cpucore'] = self.nb_log_core
# Update the history list
self.update_stats_history()
return self.stats
@ -116,7 +127,7 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_add_line(msg, "TITLE"))
# Core number
if self.stats['cpucore'] > 0:
msg = _("{0}-core").format(self.stats['cpucore'], '>1')
msg = _("{0:d}-core").format(int(self.stats['cpucore']), '>1')
ret.append(self.curse_add_line(msg))
# New line
ret.append(self.curse_new_line())

View File

@ -30,12 +30,25 @@ import psutil
# Total RAM Shared: .1.3.6.1.4.1.2021.4.13.0
# Total RAM Buffered: .1.3.6.1.4.1.2021.4.14.0
# Total Cached Memory: .1.3.6.1.4.1.2021.4.15.0
snmp_oid = {'total': '1.3.6.1.4.1.2021.4.5.0',
# 'used': '1.3.6.1.4.1.2021.4.6.0',
'free': '1.3.6.1.4.1.2021.4.11.0',
'shared': '1.3.6.1.4.1.2021.4.13.0',
'buffers': '1.3.6.1.4.1.2021.4.14.0',
'cached': '1.3.6.1.4.1.2021.4.15.0'}
# Note: For Windows, stats are in the FS table
snmp_oid = {'default': {'total': '1.3.6.1.4.1.2021.4.5.0',
'free': '1.3.6.1.4.1.2021.4.11.0',
'shared': '1.3.6.1.4.1.2021.4.13.0',
'buffers': '1.3.6.1.4.1.2021.4.14.0',
'cached': '1.3.6.1.4.1.2021.4.15.0'},
'windows': {'mnt_point': '1.3.6.1.2.1.25.2.3.1.3',
'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4',
'size': '1.3.6.1.2.1.25.2.3.1.5',
'used': '1.3.6.1.2.1.25.2.3.1.6'},
'esxi': {'mnt_point': '1.3.6.1.2.1.25.2.3.1.3',
'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4',
'size': '1.3.6.1.2.1.25.2.3.1.5',
'used': '1.3.6.1.2.1.25.2.3.1.6'}}
# Define the history items list
# All items in this list will be historised if the --enable-history tag is set
# 'color' define the graph color in #RGB format
items_history_list = [{'name': 'percent', 'color': '#00FF00'}]
class Plugin(GlancesPlugin):
@ -47,16 +60,10 @@ class Plugin(GlancesPlugin):
def __init__(self, args=None):
"""Init the plugin."""
GlancesPlugin.__init__(self, args=args)
GlancesPlugin.__init__(self, args=args, items_history_list=items_history_list)
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 2
# Enter -1 to diplay bottom
self.line_curse = 1
# Init the stats
self.reset()
@ -88,7 +95,7 @@ class Plugin(GlancesPlugin):
# cached: (Linux, BSD): cache for various things.
# wired: (BSD, OSX): memory that is marked to always stay in RAM. It is never moved to disk.
# shared: (BSD): memory that may be simultaneously accessed by multiple processes.
self.stats = {}
self.reset()
for mem in ['total', 'available', 'percent', 'used', 'free',
'active', 'inactive', 'buffers', 'cached',
'wired', 'shared']:
@ -106,24 +113,46 @@ class Plugin(GlancesPlugin):
self.stats['used'] = self.stats['total'] - self.stats['free']
elif self.get_input() == 'snmp':
# Update stats using SNMP
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid)
if self.get_short_system_name() in ('windows', 'esxi'):
# Mem stats for Windows|Vmware Esxi are stored in the FS table
try:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
bulk=True)
except KeyError:
self.reset()
else:
for fs in fs_stat:
# The Physical Memory (Windows) or Real Memory (VmWare)
# gives statistics on RAM usage and availability.
if fs in ('Physical Memory', 'Real Memory'):
self.stats['total'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
self.stats['used'] = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
self.stats['percent'] = float(self.stats['used'] * 100 / self.stats['total'])
self.stats['free'] = self.stats['total'] - self.stats['used']
break
else:
# Default behavor for others OS
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default'])
if self.stats['total'] == '':
self.reset()
return self.stats
if self.stats['total'] == '':
self.reset()
return self.stats
for key in self.stats.iterkeys():
if self.stats[key] != '':
self.stats[key] = float(self.stats[key]) * 1024
for key in list(self.stats.keys()):
if self.stats[key] != '':
self.stats[key] = float(self.stats[key]) * 1024
# Use the 'free'/htop calculation
self.stats['free'] = self.stats['free'] - self.stats['total'] + (self.stats['buffers'] + self.stats['cached'])
# Use the 'free'/htop calculation
self.stats['free'] = self.stats['free'] - self.stats['total'] + (self.stats['buffers'] + self.stats['cached'])
# used=total-free
self.stats['used'] = self.stats['total'] - self.stats['free']
# used=total-free
self.stats['used'] = self.stats['total'] - self.stats['free']
# percent: the percentage usage calculated as (total - available) / total * 100.
self.stats['percent'] = float((self.stats['total'] - self.stats['free']) / self.stats['total'] * 100)
# percent: the percentage usage calculated as (total - available) / total * 100.
self.stats['percent'] = float((self.stats['total'] - self.stats['free']) / self.stats['total'] * 100)
# Update the history list
self.update_stats_history()
return self.stats

View File

@ -26,8 +26,12 @@ import psutil
# SNMP OID
# Total Swap Size: .1.3.6.1.4.1.2021.4.3.0
# Available Swap Space: .1.3.6.1.4.1.2021.4.4.0
snmp_oid = {'total': '1.3.6.1.4.1.2021.4.3.0',
'free': '1.3.6.1.4.1.2021.4.4.0'}
snmp_oid = {'default': {'total': '1.3.6.1.4.1.2021.4.3.0',
'free': '1.3.6.1.4.1.2021.4.4.0'},
'windows': {'mnt_point': '1.3.6.1.2.1.25.2.3.1.3',
'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4',
'size': '1.3.6.1.2.1.25.2.3.1.5',
'used': '1.3.6.1.2.1.25.2.3.1.6'}}
class Plugin(GlancesPlugin):
@ -43,12 +47,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 3
# Enter -1 to diplay bottom
self.line_curse = 1
# Init the stats
self.reset()
@ -80,21 +78,39 @@ class Plugin(GlancesPlugin):
self.stats[swap] = getattr(sm_stats, swap)
elif self.get_input() == 'snmp':
# Update stats using SNMP
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid)
if self.get_short_system_name() == 'windows':
# Mem stats for Windows OS are stored in the FS table
try:
fs_stat = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
bulk=True)
except KeyError:
self.reset()
else:
for fs in fs_stat:
# The virtual memory concept is used by the operating system to extend (virtually) the physical
# memory and thus to run more programs by swapping unused memory zone (page) to a disk file.
if fs == 'Virtual Memory':
self.stats['total'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
self.stats['used'] = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
self.stats['percent'] = float(self.stats['used'] * 100 / self.stats['total'])
self.stats['free'] = self.stats['total'] - self.stats['used']
break
else:
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default'])
if self.stats['total'] == '':
self.reset()
return self.stats
if self.stats['total'] == '':
self.reset()
return self.stats
for key in self.stats.iterkeys():
if self.stats[key] != '':
self.stats[key] = float(self.stats[key]) * 1024
for key in list(self.stats.keys()):
if self.stats[key] != '':
self.stats[key] = float(self.stats[key]) * 1024
# used=total-free
self.stats['used'] = self.stats['total'] - self.stats['free']
# used=total-free
self.stats['used'] = self.stats['total'] - self.stats['free']
# percent: the percentage usage calculated as (total - available) / total * 100.
self.stats['percent'] = float((self.stats['total'] - self.stats['free']) / self.stats['total'] * 100)
# percent: the percentage usage calculated as (total - available) / total * 100.
self.stats['percent'] = float((self.stats['total'] - self.stats['free']) / self.stats['total'] * 100)
return self.stats

View File

@ -20,6 +20,7 @@
"""Monitor plugin."""
# Import Glances lib
from glances.core.glances_globals import logger
from glances.core.glances_monitor_list import MonitorList as glancesMonitorList
from glances.plugins.glances_plugin import GlancesPlugin
@ -34,12 +35,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 1
# Enter -1 to diplay bottom
self.line_curse = 3
# Init stats
self.glances_monitors = None
@ -47,7 +42,7 @@ class Plugin(GlancesPlugin):
def load_limits(self, config):
"""Load the monitored list from the conf file."""
# print "DEBUG: Monitor plugin load config file %s" % config
logger.debug(_("Monitor plugin configuration detected in the configuration file"))
self.glances_monitors = glancesMonitorList(config)
def update(self):
@ -93,7 +88,7 @@ class Plugin(GlancesPlugin):
ret = []
# Only process if stats exist and display plugin enable...
if self.stats == [] or args.disable_process:
if self.stats == [] or args.disable_process:
return ret
# Build the string message

View File

@ -19,6 +19,8 @@
"""Network plugin."""
import base64
from glances.core.glances_timer import getTimeSinceLastUpdate
from glances.plugins.glances_plugin import GlancesPlugin
@ -27,9 +29,9 @@ import psutil
# SNMP OID
# http://www.net-snmp.org/docs/mibs/interfaces.html
# Dict key = interface_name
snmp_oid = {'interface_name': '1.3.6.1.2.1.2.2.1.2',
'cumulative_rx': '1.3.6.1.2.1.2.2.1.10',
'cumulative_tx': '1.3.6.1.2.1.2.2.1.16'}
snmp_oid = {'default': {'interface_name': '1.3.6.1.2.1.2.2.1.2',
'cumulative_rx': '1.3.6.1.2.1.2.2.1.10',
'cumulative_tx': '1.3.6.1.2.1.2.2.1.16'}}
class Plugin(GlancesPlugin):
@ -45,12 +47,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 2
# Init the stats
self.reset()
@ -94,7 +90,7 @@ class Plugin(GlancesPlugin):
for net in network_new:
try:
# Try necessary to manage dynamic network interface
netstat = {}
netstat = {}
netstat['interface_name'] = net
netstat['time_since_update'] = time_since_update
netstat['cumulative_rx'] = network_new[net].bytes_recv
@ -118,7 +114,12 @@ class Plugin(GlancesPlugin):
# Update stats using SNMP
# SNMP bulk command to get all network interface in one shot
netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid, bulk=True)
try:
netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()],
bulk=True)
except KeyError:
netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid['default'],
bulk=True)
# Previous network interface stats are stored in the network_old variable
if not hasattr(self, 'network_old'):
@ -138,7 +139,15 @@ class Plugin(GlancesPlugin):
try:
# Try necessary to manage dynamic network interface
netstat = {}
netstat['interface_name'] = net
# Windows: a tips is needed to convert HEX to TXT
# http://blogs.technet.com/b/networking/archive/2009/12/18/how-to-query-the-list-of-network-interfaces-using-snmp-via-the-ifdescr-counter.aspx
if self.get_short_system_name() == 'windows':
try:
netstat['interface_name'] = str(base64.b16decode(net[2:-2].upper()))
except TypeError:
netstat['interface_name'] = net
else:
netstat['interface_name'] = net
netstat['time_since_update'] = time_since_update
netstat['cumulative_rx'] = float(network_new[net]['cumulative_rx'])
netstat['rx'] = (float(network_new[net]['cumulative_rx']) -
@ -159,9 +168,8 @@ class Plugin(GlancesPlugin):
return self.stats
def msg_curse(self, args=None):
def msg_curse(self, args=None, max_width=None):
"""Return the dict to display in the curse interface."""
# !!! TODO: Add alert on network interface bitrate
# Init the return message
ret = []
@ -170,9 +178,16 @@ class Plugin(GlancesPlugin):
if self.stats == [] or args.disable_network:
return ret
# Max size for the interface name
if max_width is not None and max_width >= 23:
# Interface size name = max_width - space for interfaces bitrate
ifname_max_width = max_width - 14
else:
ifname_max_width = 9
# Build the string message
# Header
msg = '{0:9}'.format(_("NETWORK"))
msg = '{0:{width}}'.format(_("NETWORK"), width=ifname_max_width)
ret.append(self.curse_add_line(msg, "TITLE"))
if args.network_cumul:
# Cumulative stats
@ -203,10 +218,13 @@ class Plugin(GlancesPlugin):
if self.is_hide(i['interface_name']):
continue
# Format stats
ifname = i['interface_name'].split(':')[0]
if len(ifname) > 9:
# Is there an alias for the interface name ?
ifname = self.has_alias(i['interface_name'])
if ifname is None:
ifname = i['interface_name'].split(':')[0]
if len(ifname) > ifname_max_width:
# Cut interface name if it is too long
ifname = '_' + ifname[-8:]
ifname = '_' + ifname[-ifname_max_width+1:]
if args.byte:
# Bytes per second (for dummy)
if args.network_cumul:
@ -233,7 +251,7 @@ class Plugin(GlancesPlugin):
int(i['tx'] // i['time_since_update'] * 8)) + "b"
# New line
ret.append(self.curse_new_line())
msg = '{0:9}'.format(ifname)
msg = '{0:{width}}'.format(ifname, width=ifname_max_width)
ret.append(self.curse_add_line(msg))
if args.network_sum:
msg = '{0:>14}'.format(sx)

View File

@ -37,12 +37,9 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = -1
self.set_align('bottom')
def update(self):
"""Update current date/time."""

View File

@ -39,12 +39,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 1
# Init stats
self.reset()

View File

@ -24,30 +24,41 @@ I am your father...
"""
# Import system libs
from datetime import datetime
import json
from operator import itemgetter
# Import Glances lib
from glances.core.glances_globals import glances_logs
from glances.core.glances_globals import glances_logs, logger
class GlancesPlugin(object):
"""Main class for Glances' plugin."""
def __init__(self, args=None):
def __init__(self, args=None, items_history_list=None):
"""Init the plugin of plugins class."""
# Plugin name (= module name without glances_)
self.plugin_name = self.__class__.__module__[len('glances_'):]
# logger.debug(_("Init plugin %s") % self.plugin_name)
# Init the args
self.args = args
# Init the default alignement (for curses)
self.set_align('left')
# Init the input method
self.input_method = 'local'
self.short_system_name = None
# Init the stats list
self.stats = None
# Init the history list
self.items_history_list = items_history_list
self.stats_history = self.init_stats_history()
# Init the limits dictionnary
self.limits = dict()
@ -59,20 +70,70 @@ class GlancesPlugin(object):
"""Return the human-readable stats."""
return str(self.stats)
def set_input(self, input_method):
def init_stats_history(self):
"""Init the stats history (dict of list)"""
ret = None
if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None:
iList = [i['name'] for i in self.get_items_history_list()]
logger.debug(_("Stats history activated for plugin %s (items: %s)") % (self.plugin_name, iList))
ret = {}
# First column for the date
ret['date'] = []
for i in self.get_items_history_list():
# One column per item
ret[i['name']] = []
return ret
def reset_stats_history(self):
"""Reset the stats history (dict of list)"""
if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None:
iList = [i['name'] for i in self.get_items_history_list()]
logger.debug(_("Reset history for plugin %s (items: %s)") % (self.plugin_name, iList))
self.stats_history = {}
# First column for the date
self.stats_history['date'] = []
for i in self.get_items_history_list():
# One column per item
self.stats_history[i['name']] = []
return self.stats_history
def update_stats_history(self):
"""Update stats history"""
if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None:
self.stats_history['date'].append(datetime.now())
for i in self.get_items_history_list():
self.stats_history[i['name']].append(self.stats[i['name']])
return self.stats_history
def get_stats_history(self):
"""Return the stats history"""
return self.stats_history
def get_items_history_list(self):
"""Return the items history list"""
return self.items_history_list
def set_input(self, input_method, short_system_name=None):
"""Set the input method.
* local: system local grab (psutil or direct access)
* snmp: Client server mode via SNMP
* glances: Client server mode via Glances API
For SNMP, short_system_name is detected short OS name
"""
self.input_method = input_method
self.short_system_name = short_system_name
return self.input_method
def get_input(self):
"""Get the input method."""
return self.input_method
def get_short_system_name(self):
"""Get the short detected OS name"""
return self.short_system_name
def set_stats(self, input_stats):
"""Set the stats to input_stats."""
self.stats = input_stats
@ -97,28 +158,35 @@ class GlancesPlugin(object):
# Bulk request
snmpresult = clientsnmp.getbulk_by_oid(0, 10, *snmp_oid.values())
# Build the internal dict with the SNMP result
# key is the first item in the snmp_oid
index = 1
for item in snmpresult:
item_stats = {}
item_key = None
for key in snmp_oid.iterkeys():
oid = snmp_oid[key] + '.' + str(index)
if oid in item:
if item_key is None:
item_key = item[oid]
else:
item_stats[key] = item[oid]
if item_stats != {}:
ret[item_key] = item_stats
index += 1
if len(snmp_oid) == 1:
# Bulk command for only one OID
# Note: key is the item indexed but the OID result
for item in snmpresult:
if item.keys()[0].startswith(snmp_oid.values()[0]):
ret[snmp_oid.keys()[0] + item.keys()[0].split(snmp_oid.values()[0])[1]] = item.values()[0]
else:
# Build the internal dict with the SNMP result
# Note: key is the first item in the snmp_oid
index = 1
for item in snmpresult:
item_stats = {}
item_key = None
for key in list(snmp_oid.keys()):
oid = snmp_oid[key] + '.' + str(index)
if oid in item:
if item_key is None:
item_key = item[oid]
else:
item_stats[key] = item[oid]
if item_stats != {}:
ret[item_key] = item_stats
index += 1
else:
# Simple get request
snmpresult = clientsnmp.get_by_oid(*snmp_oid.values())
# Build the internal dict with the SNMP result
for key in snmp_oid.iterkeys():
for key in list(snmp_oid.keys()):
ret[key] = snmpresult[snmp_oid[key]]
return ret
@ -128,17 +196,52 @@ class GlancesPlugin(object):
return self.stats
def get_stats(self):
"""Return the stats object in JSON format for the XML-RPC API."""
"""Return the stats object in JSON format"""
return json.dumps(self.stats)
def get_stats_item(self, item):
"""
Return the stats object for a specific item (in JSON format)
Stats should be a list of dict (processlist, network...)
"""
if type(self.stats) is not list:
if type(self.stats) is dict:
try:
return json.dumps({ item: self.stats[item] })
except KeyError as e:
logger.error(_("Can not get item %s (%s)") % (item, e))
else:
return None
else:
try:
# Source: http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list
return json.dumps({ item: map(itemgetter(item), self.stats) })
except (KeyError, ValueError) as e:
logger.error(_("Can not get item %s (%s)") % (item, e))
return None
def get_stats_value(self, item, value):
"""
Return the stats object for a specific item=value (in JSON format)
Stats should be a list of dict (processlist, network...)
"""
if type(self.stats) is not list:
return None
else:
if value.isdigit():
value = int(value)
try:
return json.dumps({ value: [i for i in self.stats if i[item] == value] })
except (KeyError, ValueError) as e:
logger.error(_("Can not get item(%s)=value(%s) (%s)") % (item, value,e))
return None
def load_limits(self, config):
"""Load the limits from the configuration file."""
if (hasattr(config, 'has_section') and
config.has_section(self.plugin_name)):
# print "Load limits for %s" % self.plugin_name
for s, v in config.items(self.plugin_name):
# Read limits
# print "\t%s = %s" % (self.plugin_name + '_' + s, v)
try:
self.limits[self.plugin_name + '_' + s] = config.get_option(self.plugin_name, s)
except ValueError:
@ -229,55 +332,67 @@ class GlancesPlugin(object):
else:
return self.limits[self.plugin_name + '_' + header + '_' + 'careful']
def get_hide(self, header=""):
"""Return the hide configuration list key for the current plugin."""
def get_conf_value(self, value, header="", plugin_name=None):
"""Return the configuration (header_)value for the current plugin (or the one given by the plugin_name var)"""
if plugin_name is None:
plugin_name = self.plugin_name
if header == "":
try:
return self.limits[self.plugin_name + '_' + 'hide']
return self.limits[plugin_name + '_' + value]
except KeyError:
return []
else:
try:
return self.limits[self.plugin_name + '_' + header + '_' + 'hide']
return self.limits[plugin_name + '_' + header + '_' + value]
except KeyError:
return []
def is_hide(self, value, header=""):
"""Return True if the value is in the hide configuration list."""
return value in self.get_hide(header=header)
return value in self.get_conf_value('hide', header=header)
def msg_curse(self, args):
def has_alias(self, header):
"""Return the alias name for the relative header or None if nonexist"""
try:
return self.limits[self.plugin_name + '_' + header + '_' + 'alias'][0]
except (KeyError, IndexError):
return None
def msg_curse(self, args=None, max_width=None):
"""Return default string to display in the curse interface."""
return [self.curse_add_line(str(self.stats))]
def get_stats_display(self, args=None):
def get_stats_display(self, args=None, max_width=None):
"""Return a dict with all the information needed to display the stat.
key | description
----------------------------
display | Display the stat (True or False)
msgdict | Message to display (list of dict [{ 'msg': msg, 'decoration': decoration } ... ])
column | column number
line | Line number
align | Message position
"""
display_curse = False
column_curse = -1
line_curse = -1
if hasattr(self, 'display_curse'):
display_curse = self.display_curse
if hasattr(self, 'column_curse'):
column_curse = self.column_curse
if hasattr(self, 'line_curse'):
line_curse = self.line_curse
if hasattr(self, 'align'):
align_curse = self.align
return {'display': display_curse,
'msgdict': self.msg_curse(args),
'column': column_curse,
'line': line_curse}
if max_width is not None:
ret = {'display': display_curse,
'msgdict': self.msg_curse(args, max_width=max_width),
'align': align_curse}
else:
ret = {'display': display_curse,
'msgdict': self.msg_curse(args),
'align': align_curse}
def curse_add_line(self, msg, decoration="DEFAULT", optional=False, splittable=False):
"""Return a dict with: { 'msg': msg, 'decoration': decoration, 'optional': False }.
return ret
def curse_add_line(self, msg, decoration="DEFAULT",
optional=False, additional=False,
splittable=False):
"""Return a dict with
Where:
msg: string
@ -298,14 +413,26 @@ class GlancesPlugin(object):
CRITICAL: Value is CRITICAL and non logged
CRITICAL_LOG: Value is CRITICAL and logged
optional: True if the stat is optional (display only if space is available)
additional: True if the stat is additional (display only if space is available after optional)
spittable: Line can be splitted to fit on the screen (default is not)
"""
return {'msg': msg, 'decoration': decoration, 'optional': optional, 'splittable': splittable}
return {'msg': msg, 'decoration': decoration, 'optional': optional, 'additional': additional, 'splittable': splittable}
def curse_new_line(self):
"""Go to a new line."""
return self.curse_add_line('\n')
def set_align(self, align='left'):
"""Set the Curse align"""
if align in ('left', 'right', 'bottom'):
self.align = align
else:
self.align = 'left'
def get_align(self):
"""Get the Curse align"""
return self.align
def auto_unit(self, number, low_precision=False):
"""Make a nice human-readable string out of number.

View File

@ -37,12 +37,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 1
# Enter -1 to diplay bottom
self.line_curse = 2
# Note: 'glances_processes' is already init in the glances_processes.py script
@ -75,9 +69,6 @@ class Plugin(GlancesPlugin):
ret = []
# Only process if stats exist and display plugin enable...
# if self.stats == {} or args.disable_process:
# return ret
if args.disable_process:
msg = _("PROCESSES DISABLED (press 'z' to display)")
ret.append(self.curse_add_line(msg))
@ -86,6 +77,16 @@ class Plugin(GlancesPlugin):
if self.stats == {}:
return ret
# Display the filter (if it exists)
if glances_processes.get_process_filter() is not None:
msg = _("Processes filter:")
ret.append(self.curse_add_line(msg, "TITLE"))
msg = _(" {0} ").format(glances_processes.get_process_filter())
ret.append(self.curse_add_line(msg, "FILTER"))
msg = _("(press ENTER to edit)")
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
# Build the string message
# Header
msg = _("TASKS ")
@ -113,17 +114,13 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_add_line(msg))
# Display sort information
try:
args.process_sorted_by
except AttributeError:
args.process_sorted_by = glances_processes.getsortkey()
if args.process_sorted_by == 'auto':
if glances_processes.getmanualsortkey() is None:
msg = _("sorted automatically")
ret.append(self.curse_add_line(msg))
msg = _(" by {0}").format(glances_processes.getsortkey())
msg = _(" by {0}").format(glances_processes.getautosortkey())
ret.append(self.curse_add_line(msg))
else:
msg = _("sorted by {0}").format(args.process_sorted_by)
msg = _("sorted by {0}").format(glances_processes.getmanualsortkey())
ret.append(self.curse_add_line(msg))
# Return the message with decoration

View File

@ -24,7 +24,7 @@ import os
from datetime import timedelta
# Import Glances libs
from glances.core.glances_globals import glances_processes, is_windows
from glances.core.glances_globals import glances_processes, is_linux, is_bsd, is_mac, is_windows, logger
from glances.plugins.glances_plugin import GlancesPlugin
@ -41,12 +41,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 1
# Enter -1 to diplay bottom
self.line_curse = 4
# Note: 'glances_processes' is already init in the glances_processes.py script
@ -63,10 +57,9 @@ class Plugin(GlancesPlugin):
# Update stats using the standard system lib
# Note: Update is done in the processcount plugin
# Just return the processes list
self.stats = glances_processes.getlist()
self.stats = glances_processes.getlist()
elif self.get_input() == 'snmp':
# Update stats using SNMP
# !!! TODO
# No SNMP grab for processes
pass
return self.stats
@ -81,14 +74,10 @@ class Plugin(GlancesPlugin):
return ret
# Compute the sort key
try:
args.process_sorted_by
except AttributeError:
args.process_sorted_by = glances_processes.getsortkey()
if args.process_sorted_by == 'auto':
process_sort_key = glances_processes.getsortkey()
if glances_processes.getmanualsortkey() is None:
process_sort_key = glances_processes.getautosortkey()
else:
process_sort_key = args.process_sorted_by
process_sort_key = glances_processes.getmanualsortkey()
sort_style = 'SORT'
# Header
@ -111,9 +100,9 @@ class Plugin(GlancesPlugin):
msg = '{0:>9}'.format(_("TIME+"))
ret.append(self.curse_add_line(msg, optional=True))
msg = '{0:>6}'.format(_("IOR/s"))
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True))
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
msg = '{0:>6}'.format(_("IOW/s"))
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True))
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
msg = ' {0:8}'.format(_("Command"))
ret.append(self.curse_add_line(msg))
@ -121,45 +110,72 @@ class Plugin(GlancesPlugin):
tag_proc_time = True
# Loop over processes (sorted by the sort key previously compute)
first = True
for p in self.sortlist(process_sort_key):
ret.append(self.curse_new_line())
# CPU
msg = '{0:>6.1f}'.format(p['cpu_percent'])
ret.append(self.curse_add_line(msg,
self.get_alert(p['cpu_percent'], header="cpu")))
if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '':
msg = '{0:>6.1f}'.format(p['cpu_percent'])
ret.append(self.curse_add_line(msg,
self.get_alert(p['cpu_percent'], header="cpu")))
else:
msg = '{0:>6}'.format('?')
ret.append(self.curse_add_line(msg))
# MEM
msg = '{0:>6.1f}'.format(p['memory_percent'])
ret.append(self.curse_add_line(msg,
self.get_alert(p['memory_percent'], header="mem")))
# VMS
msg = '{0:>6}'.format(self.auto_unit(p['memory_info'][1], low_precision=False))
ret.append(self.curse_add_line(msg, optional=True))
# RSS
msg = '{0:>6}'.format(self.auto_unit(p['memory_info'][0], low_precision=False))
ret.append(self.curse_add_line(msg, optional=True))
if 'memory_percent' in p and p['memory_percent'] is not None and p['memory_percent'] != '':
msg = '{0:>6.1f}'.format(p['memory_percent'])
ret.append(self.curse_add_line(msg,
self.get_alert(p['memory_percent'], header="mem")))
else:
msg = '{0:>6}'.format('?')
ret.append(self.curse_add_line(msg))
# VMS/RSS
if 'memory_info' in p and p['memory_info'] is not None and p['memory_info'] != '':
# VMS
msg = '{0:>6}'.format(self.auto_unit(p['memory_info'][1], low_precision=False))
ret.append(self.curse_add_line(msg, optional=True))
# RSS
msg = '{0:>6}'.format(self.auto_unit(p['memory_info'][0], low_precision=False))
ret.append(self.curse_add_line(msg, optional=True))
else:
msg = '{0:>6}'.format('?')
ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(msg))
# PID
msg = '{0:>6}'.format(p['pid'])
ret.append(self.curse_add_line(msg))
# USER
# docker internal users are displayed as ints only, therefore str()
msg = ' {0:9}'.format(str(p['username'])[:9])
ret.append(self.curse_add_line(msg))
# NICE
nice = p['nice']
if nice is None:
nice = '?'
msg = '{0:>5}'.format(nice)
if isinstance(nice, int) and ((is_windows and nice != 32) or
(not is_windows and nice != 0)):
ret.append(self.curse_add_line(msg, decoration='NICE'))
if 'username' in p:
# docker internal users are displayed as ints only, therefore str()
msg = ' {0:9}'.format(str(p['username'])[:9])
ret.append(self.curse_add_line(msg))
else:
msg = ' {0:9}'.format('?')
ret.append(self.curse_add_line(msg))
# NICE
if 'nice' in p:
nice = p['nice']
if nice is None:
nice = '?'
msg = '{0:>5}'.format(nice)
if isinstance(nice, int) and ((is_windows and nice != 32) or
(not is_windows and nice != 0)):
ret.append(self.curse_add_line(msg, decoration='NICE'))
else:
ret.append(self.curse_add_line(msg))
else:
msg = '{0:>5}'.format('?')
ret.append(self.curse_add_line(msg))
# STATUS
status = p['status']
msg = '{0:>2}'.format(status)
if status == 'R':
ret.append(self.curse_add_line(msg, decoration='STATUS'))
if 'status' in p:
status = p['status']
msg = '{0:>2}'.format(status)
if status == 'R':
ret.append(self.curse_add_line(msg, decoration='STATUS'))
else:
ret.append(self.curse_add_line(msg))
else:
msg = '{0:>2}'.format('?')
ret.append(self.curse_add_line(msg))
# TIME+
if tag_proc_time:
@ -185,29 +201,30 @@ class Plugin(GlancesPlugin):
msg = '{0:>6}'.format("0")
else:
msg = '{0:>6}'.format(self.auto_unit(io_rs, low_precision=False))
ret.append(self.curse_add_line(msg, optional=True))
ret.append(self.curse_add_line(msg, optional=True, additional=True))
# IO write
io_ws = (p['io_counters'][1] - p['io_counters'][3]) / p['time_since_update']
if io_ws == 0:
msg = '{0:>6}'.format("0")
else:
msg = '{0:>6}'.format(self.auto_unit(io_ws, low_precision=False))
ret.append(self.curse_add_line(msg, optional=True))
ret.append(self.curse_add_line(msg, optional=True, additional=True))
else:
msg = '{0:>6}'.format("?")
ret.append(self.curse_add_line(msg, optional=True))
ret.append(self.curse_add_line(msg, optional=True))
ret.append(self.curse_add_line(msg, optional=True, additional=True))
ret.append(self.curse_add_line(msg, optional=True, additional=True))
# Command line
# If no command line for the process is available, fallback to
# the bare process name instead
cmdline = p['cmdline']
if cmdline == "":
if cmdline == "" or args.process_short_name:
msg = ' {0}'.format(p['name'])
ret.append(self.curse_add_line(msg, splittable=True))
else:
try:
cmd = cmdline.split()[0]
args = ' '.join(cmdline.split()[1:])
argument = ' '.join(cmdline.split()[1:])
path, basename = os.path.split(cmd)
if os.path.isdir(path):
msg = ' {0}'.format(path) + os.sep
@ -216,11 +233,83 @@ class Plugin(GlancesPlugin):
else:
msg = ' {0}'.format(basename)
ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True))
msg = " {0}".format(args)
msg = " {0}".format(argument)
ret.append(self.curse_add_line(msg, splittable=True))
except UnicodeEncodeError:
ret.append(self.curse_add_line("", splittable=True))
# Add extended stats but only for the top processes
# !!! CPU consumption ???
# TODO: extended stats into the web interface
if first and 'extended_stats' in p:
# Left padding
xpad = ' ' * 13
# First line is CPU affinity
if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
ret.append(self.curse_new_line())
msg = xpad + _('CPU affinity: ') + str(len(p['cpu_affinity'])) + _(' cores')
ret.append(self.curse_add_line(msg, splittable=True))
# Second line is memory info
if 'memory_info_ex' in p and p['memory_info_ex'] is not None:
ret.append(self.curse_new_line())
msg = xpad + _('Memory info: ')
for k, v in p['memory_info_ex']._asdict().items():
# Ignore rss and vms (already displayed)
if k not in ['rss', 'vms'] and v is not None:
msg += k + ' ' + self.auto_unit(v, low_precision=False) + ' '
if 'memory_swap' in p and p['memory_swap'] is not None:
msg += _('swap ') + self.auto_unit(p['memory_swap'], low_precision=False)
ret.append(self.curse_add_line(msg, splittable=True))
# Third line is for openned files/network sessions
ret.append(self.curse_new_line())
msg = xpad + _('Openned: ')
if 'num_threads' in p and p['num_threads'] is not None:
msg += _('threads ') + str(p['num_threads']) + ' '
if 'num_fds' in p and p['num_fds'] is not None:
msg += _('files ') + str(p['num_fds']) + ' '
if 'num_handles' in p and p['num_handles'] is not None:
msg += _('handles ') + str(p['num_handles']) + ' '
if 'tcp' in p and p['tcp'] is not None:
msg += _('TCP ') + str(p['tcp']) + ' '
if 'udp' in p and p['udp'] is not None:
msg += _('UDP ') + str(p['udp']) + ' '
ret.append(self.curse_add_line(msg, splittable=True))
# Fouth line is IO nice level (only Linux and Windows OS)
if 'ionice' in p and p['ionice'] is not None:
ret.append(self.curse_new_line())
msg = xpad + _('IO nice: ')
k = _('Class is ')
v = p['ionice'].ioclass
# Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
# Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
if is_windows:
if v == 0:
msg += k + 'Very Low'
elif v == 1:
msg += k + 'Low'
elif v == 2:
msg += _('No specific I/O priority')
else:
msg += k + str(v)
else:
if v == 0:
msg += _('No specific I/O priority')
elif v == 1:
msg += k + 'Real Time'
elif v == 2:
msg += k + 'Best Effort'
elif v == 3:
msg += k + 'IDLE'
else:
msg += k + str(v)
# value is a number which goes from 0 to 7.
# The higher the value, the lower the I/O priority of the process.
if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
msg += _(' (value %s/7)') % str(p['ionice'].value)
ret.append(self.curse_add_line(msg, splittable=True))
# End of extended stats
first = False
# Return the message with decoration
return ret
@ -250,9 +339,14 @@ class Plugin(GlancesPlugin):
reverse=sortedreverse)
else:
# Others sorts
listsorted = sorted(self.stats,
key=lambda process: process[sortedby],
reverse=sortedreverse)
try:
listsorted = sorted(self.stats,
key=lambda process: process[sortedby],
reverse=sortedreverse)
except KeyError:
listsorted = sorted(self.stats,
key=lambda process: process['name'],
reverse=False)
self.stats = listsorted

View File

@ -27,7 +27,7 @@ except ImportError:
pass
# Import Glances lib
from glances.core.glances_globals import is_py3
from glances.core.glances_globals import is_py3, logger
from glances.plugins.glances_batpercent import Plugin as BatPercentPlugin
from glances.plugins.glances_hddtemp import Plugin as HddTempPlugin
from glances.plugins.glances_plugin import GlancesPlugin
@ -50,19 +50,13 @@ class Plugin(GlancesPlugin):
self.glancesgrabsensors = GlancesGrabSensors()
# Instance for the HDDTemp Plugin in order to display the hard disks temperatures
self.hddtemp_plugin = HddTempPlugin()
self.hddtemp_plugin = HddTempPlugin(args=args)
# Instance for the BatPercent in order to display the batteries capacities
self.batpercent_plugin = BatPercentPlugin()
self.batpercent_plugin = BatPercentPlugin(args=args)
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 5
# Init the stats
self.reset()
@ -140,18 +134,24 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_add_line(msg))
for item in self.stats:
# New line
ret.append(self.curse_new_line())
msg = '{0:18}'.format(item['label'][:18])
ret.append(self.curse_add_line(msg))
msg = '{0:>5}'.format(item['value'])
if item['type'] == 'battery':
try:
ret.append(self.curse_add_line(msg, self.get_alert(100 - item['value'], header=item['type'])))
except TypeError:
pass
else:
ret.append(self.curse_add_line(msg, self.get_alert(item['value'], header=item['type'])))
if item['value'] is not None and item['value'] != []:
# New line
ret.append(self.curse_new_line())
# Alias for the lable name ?
label = self.has_alias(item['label'].lower())
if label is None:
label = item['label']
label = label[:18]
msg = '{0:18}'.format(label)
ret.append(self.curse_add_line(msg))
msg = '{0:>5}'.format(item['value'])
if item['type'] == 'battery':
try:
ret.append(self.curse_add_line(msg, self.get_alert(100 - item['value'], header=item['type'])))
except TypeError:
pass
else:
ret.append(self.curse_add_line(msg, self.get_alert(item['value'], header=item['type'])))
return ret

View File

@ -22,13 +22,25 @@
# Import system libs
import os
import platform
import re
# Import Glances libs
from glances.plugins.glances_plugin import GlancesPlugin
# SNMP OID
snmp_oid = {'hostname': '1.3.6.1.2.1.1.5.0',
'os_name': '1.3.6.1.2.1.1.1.0'}
snmp_oid = {'default': {'hostname': '1.3.6.1.2.1.1.5.0',
'system_name': '1.3.6.1.2.1.1.1.0'}}
# SNMP to human read
# Dict (key: OS short name) of dict (reg exp OID to human)
# Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx
snmp_to_human = {'windows': {'Windows Version 6.3': 'Windows 8.1 or Server 2012R2',
'Windows Version 6.2': 'Windows 8 or Server 2012',
'Windows Version 6.1': 'Windows 7 or Server 2008R2',
'Windows Version 6.0': 'Windows Vista or Server 2008',
'Windows Version 5.2': 'Windows XP 64bits or 2003 server',
'Windows Version 5.1': 'Windows XP',
'Windows Version 5.0': 'Windows 2000'}}
class Plugin(GlancesPlugin):
@ -44,12 +56,6 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = 0
# Enter -1 to diplay bottom
self.line_curse = 0
# Init the stats
self.reset()
@ -90,7 +96,18 @@ class Plugin(GlancesPlugin):
self.stats['os_version'] = ""
elif self.get_input() == 'snmp':
# Update stats using SNMP
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid)
try:
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid[self.get_short_system_name()])
except KeyError:
self.stats = self.set_stats_snmp(snmp_oid=snmp_oid['default'])
# Default behavor: display all the information
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():
if re.search(r, self.stats['system_name']):
self.stats['os_name'] = v
break
return self.stats

View File

@ -45,12 +45,10 @@ class Plugin(GlancesPlugin):
# We want to display the stat in the curse interface
self.display_curse = True
# Set the message position
# It is NOT the curse position but the Glances column/line
# Enter -1 to right align
self.column_curse = -1
# Enter -1 to diplay bottom
self.line_curse = 0
self.set_align('right')
# Init the stats
self.reset()

View File

@ -34,13 +34,13 @@ function gen_pot() {
xgettext --language=Python --keyword=_ --output=${ROOT}i18n/glances.pot `find ${ROOT}glances/ -name "*.py"`
}
OPERATION="$1"
shift
if [ -z "$1" ]; then
if [ $# != 2 ]; then
usage
fi
OPERATION="$1"
shift
case "$OPERATION" in
init)
# If there is already a language file for specified language there is no need to generate a new one

View File

@ -1,4 +1,4 @@
.TH glances 1 "June, 2014" "version 2.0.1" "USER COMMANDS"
.TH glances 1 "September, 2014" "version 2.1" "USER COMMANDS"
.SH NAME
glances \- A cross-platform curses-based system monitoring tool
.SH SYNOPSIS
@ -25,18 +25,18 @@ display the help and exit
.B \-V, \-\-version
show program's version number and exit
.TP
.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 \-B BIND_ADDRESS, \-\-bind BIND_ADDRESS
bind server to the given IPv4/IPv6 address or hostname
.TP
.B \-c CLIENT, \-\-client CLIENT
connect to a Glances server by IPv4/IPv6 address or hostname
.TP
.B \-C CONF_FILE, \-\-config CONF_FILE
path to the configuration file
.TP
.B \-\-enable-history
enable the history mode
.TP
.B \-\-disable-bold
disable bold mode in the terminal
.TP
@ -58,20 +58,29 @@ disable sensors module
.B \-\-disable-process
disable process module
.TP
.B \-\-disable-process-extended
disable extended stats on top process
.TP
.B \-\-disable-log
disable log module
.TP
.B \-\-output-csv OUTPUT_CSV
export stats to a CSV file
.TP
.B \-s, \-\-server
run Glances in server mode
.TP
.B \-c CLIENT, \-\-client CLIENT
connect to a Glances server by IPv4/IPv6 address or hostname
.TP
.B \-p PORT, \-\-port PORT
define the client/server TCP port [default: 61209]
.TP
.B \-\-password
define a client/server password from the prompt or file
.TP
.B \-s, \-\-server
run Glances in server mode
.B \-B BIND_ADDRESS, \-\-bind BIND_ADDRESS
bind server to the given IPv4/IPv6 address or hostname
.TP
.B \-\-snmp-community SNMP_COMMUNITY
SNMP community
@ -88,6 +97,9 @@ SNMP username (only for SNMPv3)
.B \-\-snmp-auth SNMP_AUTH
SNMP authentication key (only for SNMPv3)
.TP
.B \-\-snmp-force
Force the SNMP mode (do not try Glances server)
.TP
.B \-t TIME, \-\-time TIME
set refresh time in seconds [default: 3 sec]
.TP
@ -96,9 +108,18 @@ run Glances in Web server mode
.TP
.B \-1, \-\-percpu
start Glances in per CPU mode
.TP
.B \-1, \-\-process-short-name
Force short name for processes name
.TP
.B \-1, \-\-theme-white
Optimize display for white background
.SH INTERACTIVE COMMANDS
You can use the following keys while in Glances:
.TP
.B ENTER
Set the process filter patern (as a regular expression)
.TP
.B a
Sort process list automatically
.TP
@ -111,8 +132,13 @@ Sort processes by CPU usage
.B d
Show/hide disk I/O stats
.TP
.B e
Enable/disable top extended stats
.TP
.B f
Show/hide file system stats
.B g
Generate graphs for current history
.TP
.B h
Show/hide the help screen
@ -135,6 +161,9 @@ Sort processes by name
.B q
Quit
.TP
.B r
Reset history
.TP
.B s
Show/hide sensors stats
.TP

View File

@ -6,7 +6,6 @@ import sys
from setuptools import setup
def get_data_files():
data_files = [
('share/doc/glances', ['AUTHORS', 'COPYING', 'NEWS', 'README.rst',
@ -30,7 +29,6 @@ def get_data_files():
return data_files
def get_requires():
requires = ['psutil>=2.0.0']
if sys.platform.startswith('win'):
@ -42,13 +40,13 @@ def get_requires():
setup(
name='Glances',
version='2.0.1',
version='2.1',
description="A cross-platform curses-based monitoring tool",
long_description=open('README.rst').read(),
author='Nicolas Hennion',
author_email='nicolas@nicolargo.com',
url='https://github.com/nicolargo/glances',
# download_url='https://s3.amazonaws.com/glances/glances-2.0.1.tar.gz',
# download_url='https://s3.amazonaws.com/glances/glances-2.1.tar.gz',
license="LGPL",
keywords="cli curses monitoring system",
install_requires=get_requires(),
@ -56,7 +54,8 @@ setup(
'WEB': ['bottle'],
'SENSORS': ['py3sensors'],
'BATINFO': ['batinfo'],
'SNMP': ['pysnmp']
'SNMP': ['pysnmp'],
'CHART': ['matplotlib']
},
packages=['glances'],
include_package_data=True,

178
unitest-restful.py Executable file
View File

@ -0,0 +1,178 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Glances - An eye on your system
#
# 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/>.
"""Glances unitary tests suite for the RESTFul API."""
import gettext
import locale
import sys
import time
import unittest
import shlex
import subprocess
import requests
import json
import types
from glances.core.glances_globals import (
appname,
is_linux,
version
)
SERVER_PORT = 61234
URL = "http://localhost:%s/api/2" % SERVER_PORT
pid = None
# Global variables
# =================
# Unitary test is only available from a GNU/Linus machine
if not is_linux:
print('ERROR: RESTFul API unitaries tests should be ran on GNU/Linux operating system')
sys.exit(2)
else:
print('Unitary tests for {0} {1}'.format(appname, version))
# Import local settings
from glances.core.glances_globals import gettext_domain, locale_dir
locale.setlocale(locale.LC_ALL, '')
gettext.install(gettext_domain, locale_dir)
# Init Glances core
from glances.core.glances_main import GlancesMain
core = GlancesMain()
if not core.is_standalone():
print('ERROR: Glances core should be ran in standalone mode')
sys.exit(1)
# Init Glances stats
from glances.core.glances_stats import GlancesStats
stats = GlancesStats()
# Unitest class
# ==============
class TestGlances(unittest.TestCase):
"""Test Glances class."""
def setUp(self):
"""The function is called *every time* before test_*."""
print('\n' + '=' * 78)
def test_000_start_server(self):
"""Start the Glances Web Server"""
print('INFO: [TEST_000] Start the Glances Web Server')
global pid
cmdline = "/usr/bin/python -m glances -w -p %s" % SERVER_PORT
print("Run the Glances Web Server on port %s" % SERVER_PORT)
args = shlex.split(cmdline)
pid = subprocess.Popen(args)
print("Please wait...")
time.sleep(1)
self.assertTrue(pid is not None)
def test_001_all(self):
"""All"""
method = "all"
print('INFO: [TEST_001] Connection test')
print("HTTP RESTFul request: %s/%s" % (URL, method))
req = requests.get("%s/%s" % (URL, method))
self.assertTrue(req.ok)
def test_002_pluginslist(self):
"""Plugins list"""
method = "pluginslist"
print('INFO: [TEST_002] Plugins list')
print("HTTP RESTFul request: %s/%s" % (URL, method))
req = requests.get("%s/%s" % (URL, method))
self.assertTrue(req.ok)
self.assertIsInstance(req.json(), types.ListType)
self.assertIn('cpu', req.json())
def test_003_plugins(self):
"""Plugins"""
method = "pluginslist"
print('INFO: [TEST_003] Plugins')
plist = requests.get("%s/%s" % (URL, method))
print plist.json()
for p in plist.json():
print("HTTP RESTFul request: %s/%s" % (URL, p))
req = requests.get("%s/%s" % (URL, p))
self.assertTrue(req.ok)
if p in ('uptime', 'now'):
self.assertIsInstance(req.json(), types.UnicodeType)
elif p in ('fs', 'monitor', 'percpu', 'sensors', 'alert', 'processlist', 'diskio', 'hddtemp', 'batpercent', 'network'):
self.assertIsInstance(req.json(), types.ListType)
elif p in ('psutilversion', 'help'):
pass
else:
self.assertIsInstance(req.json(), types.DictType)
def test_004_items(self):
"""Items"""
method = "cpu"
print('INFO: [TEST_004] Items for the CPU method')
ilist = requests.get("%s/%s" % (URL, method))
for i in ilist.json():
print("HTTP RESTFul request: %s/%s/%s" % (URL, method,i))
req = requests.get("%s/%s/%s" % (URL, method, i))
self.assertTrue(req.ok)
self.assertIsInstance(req.json(), types.DictType)
self.assertIsInstance(req.json()[i], types.FloatType)
def test_005_values(self):
"""Valuess"""
method = "processlist"
print('INFO: [TEST_005] Item=Value for the PROCESSLIST method')
print("%s/%s/pid/0" % (URL, method))
req = requests.get("%s/%s/pid/0" % (URL, method))
self.assertTrue(req.ok)
self.assertIsInstance(req.json(), types.DictType)
def test_999_stop_server(self):
"""Stop the Glances Web Server"""
print('INFO: [TEST_999] Stop the Glances Web Server')
print("Stop the Glances Web Server")
pid.terminate()
print("Please wait...")
time.sleep(1)
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()

206
unitest-xmlrpc.py Executable file
View File

@ -0,0 +1,206 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Glances - An eye on your system
#
# 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/>.
"""Glances unitary tests suite for the XML/RPC API."""
import gettext
import locale
import sys
import time
import unittest
import shlex
import subprocess
import xmlrpclib
import json
import types
from glances.core.glances_globals import (
appname,
is_linux,
version
)
SERVER_PORT = 61234
URL = "http://localhost:%s" % SERVER_PORT
pid = None
# Global variables
# =================
# Unitary test is only available from a GNU/Linus machine
if not is_linux:
print('ERROR: XML/RPC API unitaries tests should be ran on GNU/Linux operating system')
sys.exit(2)
else:
print('Unitary tests for {0} {1}'.format(appname, version))
# Import local settings
from glances.core.glances_globals import gettext_domain, locale_dir
locale.setlocale(locale.LC_ALL, '')
gettext.install(gettext_domain, locale_dir)
# Init Glances core
from glances.core.glances_main import GlancesMain
core = GlancesMain()
if not core.is_standalone():
print('ERROR: Glances core should be ran in standalone mode')
sys.exit(1)
# Init Glances stats
from glances.core.glances_stats import GlancesStats
stats = GlancesStats()
# Init the XML/RCP client
client = xmlrpclib.ServerProxy(URL)
# Unitest class
# ==============
class TestGlances(unittest.TestCase):
"""Test Glances class."""
def setUp(self):
"""The function is called *every time* before test_*."""
print('\n' + '=' * 78)
def test_000_start_server(self):
"""Start the Glances Web Server"""
print('INFO: [TEST_000] Start the Glances Web Server')
global pid
cmdline = "/usr/bin/python -m glances -s -p %s" % SERVER_PORT
print("Run the Glances Server on port %s" % SERVER_PORT)
args = shlex.split(cmdline)
pid = subprocess.Popen(args)
print("Please wait...")
time.sleep(1)
self.assertTrue(pid is not None)
def test_001_all(self):
"""All"""
method = "getAll()"
print('INFO: [TEST_001] Connection test')
print("XML/RPC request: %s" % method)
req = json.loads(client.getAll())
self.assertIsInstance(req, types.DictType)
def test_002_pluginslist(self):
"""Plugins list"""
method = "getAllPlugins()"
print('INFO: [TEST_002] Get plugins list')
print("XML/RPC request: %s" % method)
req = json.loads(client.getAllPlugins())
self.assertIsInstance(req, types.ListType)
def test_003_system(self):
"""System"""
method = "getSystem()"
print('INFO: [TEST_003] Method: %s' % method)
req = json.loads(client.getSystem())
self.assertIsInstance(req, types.DictType)
def test_004_cpu(self):
"""CPU"""
method = "getCpu(), getPerCpu(), getLoad() and getCore()"
print('INFO: [TEST_004] Method: %s' % method)
req = json.loads(client.getCpu())
self.assertIsInstance(req, types.DictType)
req = json.loads(client.getPerCpu())
self.assertIsInstance(req, types.ListType)
req = json.loads(client.getLoad())
self.assertIsInstance(req, types.DictType)
req = json.loads(client.getCore())
self.assertIsInstance(req, types.DictType)
def test_005_mem(self):
"""MEM"""
method = "getMem() and getMemSwap()"
print('INFO: [TEST_005] Method: %s' % method)
req = json.loads(client.getMem())
self.assertIsInstance(req, types.DictType)
req = json.loads(client.getMemSwap())
self.assertIsInstance(req, types.DictType)
def test_006_net(self):
"""NETWORK"""
method = "getNetwork()"
print('INFO: [TEST_006] Method: %s' % method)
req = json.loads(client.getNetwork())
self.assertIsInstance(req, types.ListType)
def test_007_disk(self):
"""DISK"""
method = "getFs() and getDiskIO()"
print('INFO: [TEST_007] Method: %s' % method)
req = json.loads(client.getFs())
self.assertIsInstance(req, types.ListType)
req = json.loads(client.getDiskIO())
self.assertIsInstance(req, types.ListType)
def test_008_sensors(self):
"""SENSORS"""
method = "getSensors()"
print('INFO: [TEST_008] Method: %s' % method)
req = json.loads(client.getSensors())
self.assertIsInstance(req, types.ListType)
def test_009_process(self):
"""PROCESS"""
method = "getProcessCount() and getProcessList()"
print('INFO: [TEST_009] Method: %s' % method)
req = json.loads(client.getProcessCount())
self.assertIsInstance(req, types.DictType)
req = json.loads(client.getProcessList())
self.assertIsInstance(req, types.ListType)
def test_999_stop_server(self):
"""Stop the Glances Web Server"""
print('INFO: [TEST_999] Stop the Glances Server')
print("Stop the Glances Server")
pid.terminate()
print("Please wait...")
time.sleep(1)
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()