mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-25 19:17:09 +03:00
Merge branch 'release/v2.1'
This commit is contained in:
commit
20fbe4f5d5
@ -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
|
4
AUTHORS
4
AUTHORS
@ -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
47
NEWS
@ -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
|
||||
|
16
README.rst
16
README.rst
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 <<a class="reference external" href="mailto:nicolas@nicolargo.com">nicolas@nicolargo.com</a>></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 @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://@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://@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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </td><td>run Glances in Web server mode</td></tr>
|
||||
<tr><td> </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> </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> </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\<User>\Application Data
|
||||
<p>Since Windows Vista and newer versions:</p>
|
||||
<pre class="literal-block">
|
||||
C:\Users\<User>\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 & WARNING & ERROR &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> </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">"OK"</tt></div>
|
||||
<div class="line"><tt class="docutils literal">BLUE</tt> stat counter is <tt class="docutils literal">"CAREFUL"</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&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">
|
||||
|
@ -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
|
BIN
docs/images/processlist-top.png
Normal file
BIN
docs/images/processlist-top.png
Normal file
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 |
@ -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
|
||||
|
@ -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."""
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
83
glances/core/glances_logging.py
Normal file
83
glances/core/glances_logging.py
Normal 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
|
@ -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
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
111
glances/outputs/glances_history.py
Normal file
111
glances/outputs/glances_history.py
Normal 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
|
@ -61,6 +61,9 @@ div#newline{
|
||||
#ok {
|
||||
color: green;
|
||||
}
|
||||
#filter {
|
||||
color: cyan;
|
||||
}
|
||||
#ok_log {
|
||||
background-color: green;
|
||||
color: white;
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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']))
|
||||
|
@ -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'])))
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
9
setup.py
9
setup.py
@ -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
178
unitest-restful.py
Executable 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
206
unitest-xmlrpc.py
Executable 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()
|
Loading…
Reference in New Issue
Block a user