First draft for the monitoring list

This commit is contained in:
Nicolas Hennion 2014-03-01 16:52:18 +01:00
parent 43c9c805be
commit 8d03722ea6
13 changed files with 593 additions and 23 deletions

View File

@ -30,9 +30,6 @@ import os
import gettext
import locale
# Import Glances libs
from ..core.glances_logs import glancesLogs
# Import PsUtil
try:
from psutil import __version__ as __psutil_version__
@ -78,5 +75,15 @@ else:
locale_dir = None
gettext.install(gettext_domain, locale_dir)
# Logs instance share between all scripts
# Import Glances libs
from ..core.glances_config import Config as glancesConfig
from ..core.glances_logs import glancesLogs
from ..core.glances_monitor_list import monitorList as glancesMonitorList
# Instances shared between all scripts
# The global instance for the configuration file
glances_config = glancesConfig()
# The global instance for the logs
glances_logs = glancesLogs()
# The global instance for the monitored list
glances_monitors = glancesMonitorList(glances_config)

View File

@ -173,9 +173,9 @@ class glancesLogs:
"""
# Create a new clean list
clean_logs_list = []
while self.len() > 0:
while (self.len() > 0):
item = self.logs_list.pop()
if item[1] < 0 or (not critical and item[2] == "CRITICAL"):
if ((item[1] < 0) or (not critical and (item[2].startswith("CRITICAL")))):
clean_logs_list.insert(0, item)
# The list is now the clean one
self.logs_list = clean_logs_list

View File

@ -100,10 +100,18 @@ class monitorList:
else:
return None
def getAll(self):
def get(self):
return self.__monitor_list
def getAll(self):
# To be deprecated
return self.__monitor_list
def set(self, newlist):
self.__monitor_list = newlist
def setAll(self, newlist):
# To be deprecated
self.__monitor_list = newlist
def description(self, item):

View File

@ -169,12 +169,10 @@ class GlancesInstance():
def getAllLimits(self):
# Return all the limits
# !!! Not implemented
return json.dumps(self.limits.getAll())
def getAllMonitored(self):
# Return the processes monitored list
# !!! Not implemented
return json.dumps(self.monitors.getAll())
def __getattr__(self, item):

View File

@ -40,8 +40,6 @@ class GlancesStandalone():
args=None,
refresh_time=3,
use_bold=True):
# Init the limits
self.limits = glancesLimits(config)
# Init the monitoring list
self.monitors = monitorList(config)
@ -77,7 +75,7 @@ class GlancesStandalone():
self.stats.update()
# Update the screen
self.screen.update(self.stats)
self.screen.update(self.stats, monitors=self.monitors)
# Update the HTML output
# !!! TODO

View File

@ -0,0 +1,460 @@
#!/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/>.
# Import system lib
import sys
try:
import curses
import curses.panel
except ImportError:
print('Curses module not found. Glances cannot start in standalone mode.')
sys.exit(1)
# Import Glances lib
from ..core.glances_timer import Timer
from ..core.glances_globals import glances_logs
class glancesCurses:
"""
This class manage the curses display (and key pressed)
"""
def __init__(self, args=None):
# Init args
self.args = args
# Init windows positions
self.term_w = 80
self.term_h = 24
# Space between stats
self.space_between_column = 3
self.space_between_line = 2
# Init the curses screen
self.screen = curses.initscr()
if not self.screen:
print(_("Error: Cannot init the curses library.\n"))
# Set curses options
if hasattr(curses, 'start_color'):
curses.start_color()
if hasattr(curses, 'use_default_colors'):
curses.use_default_colors()
if hasattr(curses, 'noecho'):
curses.noecho()
if hasattr(curses, 'cbreak'):
curses.cbreak()
if hasattr(curses, 'curs_set'):
try:
curses.curs_set(0)
except Exception:
pass
# 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)
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)
curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
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)
else:
self.hascolors = False
if (args.no_bold):
A_BOLD = curses.A_BOLD
else:
A_BOLD = 0
self.title_color = A_BOLD
self.title_underline_color = A_BOLD | curses.A_UNDERLINE
self.help_color = A_BOLD
if self.hascolors:
# Colors text styles
self.no_color = curses.color_pair(1)
self.default_color = curses.color_pair(3) | A_BOLD
self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD
self.ifWARNING_color = curses.color_pair(5) | A_BOLD
self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD
self.default_color2 = curses.color_pair(7) | A_BOLD
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
else:
# B&W text styles
self.no_color = curses.A_NORMAL
self.default_color = curses.A_NORMAL
self.ifCAREFUL_color = curses.A_UNDERLINE
self.ifWARNING_color = A_BOLD
self.ifCRITICAL_color = curses.A_REVERSE
self.default_color2 = curses.A_NORMAL
self.ifCAREFUL_color2 = curses.A_UNDERLINE
self.ifWARNING_color2 = A_BOLD
self.ifCRITICAL_color2 = curses.A_REVERSE
# Define the colors list (hash table) for stats
self.__colors_list = {
'DEFAULT': self.no_color,
'UNDERLINE': curses.A_UNDERLINE,
'BOLD': A_BOLD,
'OK': self.default_color2,
'TITLE': self.title_color,
'CAREFUL': self.ifCAREFUL_color2,
'WARNING': self.ifWARNING_color2,
'CRITICAL': self.ifCRITICAL_color2,
'OK_LOG': self.default_color,
'CAREFUL_LOG': self.ifCAREFUL_color,
'WARNING_LOG': self.ifWARNING_color,
'CRITICAL_LOG': self.ifCRITICAL_color
}
# Init main window
self.term_window = self.screen.subwin(0, 0)
# Init refresh time
self.__refresh_time = args.time
# Init process sort method
self.args.process_sorted_by = 'auto'
# Catch key pressed with non blocking mode
self.term_window.keypad(1)
self.term_window.nodelay(1)
self.pressedkey = -1
def __getkey(self, window):
"""
A getKey function to catch ESC key AND Numlock key (issue #163)
"""
keycode = [0, 0]
keycode[0] = window.getch()
keycode[1] = window.getch()
if keycode[0] == 27 and keycode[1] != -1:
# Do not escape on specials keys
return -1
else:
return keycode[0]
def __catchKey(self):
# Get key
#~ self.pressedkey = self.term_window.getch()
self.pressedkey = self.__getkey(self.term_window)
# Actions...
if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
# 'ESC'|'q' > Quit
self.end()
sys.exit(0)
elif self.pressedkey == ord('1'):
# '1' > Switch between CPU and PerCPU information
self.args.percpu = not self.args.percpu
elif self.pressedkey == ord('a'):
# 'a' > Sort processes automatically
self.args.process_sorted_by = 'auto'
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
self.args.byte = not self.args.byte
elif self.pressedkey == ord('c'):
# 'c' > Sort processes by CPU usage
self.args.process_sorted_by = 'cpu_percent'
elif self.pressedkey == ord('d') and diskio_tag:
# 'd' > Show/hide disk I/O stats
self.diskio_tag = not self.diskio_tag
elif self.pressedkey == ord('f') and fs_tag:
# 'f' > Show/hide fs stats
self.fs_tag = not self.fs_tag
elif self.pressedkey == ord('h'):
# 'h' > Show/hide help
self.help_tag = not self.help_tag
elif self.pressedkey == ord('i') and psutil_get_io_counter_tag:
# 'i' > Sort processes by IO rate (not available on OS X)
self.args.process_sorted_by = 'io_counter'
elif self.pressedkey == ord('l'):
# 'l' > Show/hide log messages
self.log_tag = not self.log_tag
elif self.pressedkey == ord('m'):
# 'm' > Sort processes by MEM usage
self.args.process_sorted_by = 'memory_percent'
elif self.pressedkey == ord('n') and network_tag:
# 'n' > Show/hide network stats
self.network_tag = not self.network_tag
elif self.pressedkey == ord('p'):
# 'p' > Sort processes by name
self.args.process_sorted_by = 'name'
elif self.pressedkey == ord('s'):
# 's' > Show/hide sensors stats (Linux-only)
self.sensors_tag = not self.sensors_tag
elif self.pressedkey == ord('t'):
# 't' > View network traffic as combination
self.network_stats_combined = not self.network_stats_combined
elif self.pressedkey == ord('u'):
# 'u' > View cumulative network IO
self.network_stats_cumulative = not self.network_stats_cumulative
elif self.pressedkey == ord('w'):
# 'w' > Delete finished warning logs
glances_logs.clean()
elif self.pressedkey == ord('x'):
# 'x' > Delete finished warning and critical logs
glances_logs.clean(critical=True)
elif self.pressedkey == ord('y'):
# 'y' > Show/hide hddtemp stats
self.hddtemp_tag = not self.hddtemp_tag
# Return the key code
return self.pressedkey
def end(self):
# Shutdown the curses window
curses.echo()
curses.nocbreak()
curses.curs_set(1)
curses.endwin()
def display(self, stats, monitors=None, cs_status="None"):
"""
Display stats on the screen
stats: Stats database to display
monitors: The monitored list
cs_status:
"None": standalone or server mode
"Connected": Client is connected to the server
"Disconnected": Client is disconnected from the server
"""
# Init the internal line/column dict for Glances Curses
self.line_to_y = {}
self.column_to_x = {}
# Get the screen size
screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0]
# Display first line (system+uptime)
stats_system = stats.get_plugin('system').get_curse(args=self.args)
stats_uptime = stats.get_plugin('uptime').get_curse()
l = self.get_curse_width(stats_system) + self.get_curse_width(stats_uptime) + self.space_between_column
self.display_plugin(stats_system, display_optional=(screen_x >= l))
self.display_plugin(stats_uptime)
# Display second line (CPU|PERCPU+LOAD+MEM+SWAP+<SUMMARY>)
# CPU|PERCPU
if (self.args.percpu):
stats_percpu = stats.get_plugin('percpu').get_curse()
l = self.get_curse_width(stats_percpu)
else:
stats_cpu = stats.get_plugin('cpu').get_curse()
l = self.get_curse_width(stats_cpu)
stats_load = stats.get_plugin('load').get_curse()
stats_mem = stats.get_plugin('mem').get_curse()
stats_memswap = stats.get_plugin('memswap').get_curse()
l += self.get_curse_width(stats_load) + self.get_curse_width(stats_mem) + self.get_curse_width(stats_memswap)
# Space between column
if (screen_x > (3 * self.space_between_column + l)):
self.space_between_column = int((screen_x - l) / 3)
# Display
if (self.args.percpu):
self.display_plugin(stats_percpu)
else:
self.display_plugin(stats_cpu, display_optional=(screen_x >= 76))
self.display_plugin(stats_load)
self.display_plugin(stats_mem, display_optional=(screen_x >= (3 * self.space_between_column + l)))
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_curse(args=self.args))
self.display_plugin(stats.get_plugin('diskio').get_curse(args=self.args))
self.display_plugin(stats.get_plugin('fs').get_curse(args=self.args))
self.display_plugin(stats.get_plugin('sensors').get_curse(args=self.args))
# Display last line (currenttime)
self.display_plugin(stats.get_plugin('now').get_curse())
# Display right sidebar (PROCESS_COUNT)
if (screen_x > 52):
stats_processcount = stats.get_plugin('processcount').get_curse(args=self.args)
stats_processlist = stats.get_plugin('processlist').get_curse(args=self.args)
stats_alert = stats.get_plugin('alert').get_curse(args=self.args)
stats_monitor = stats.get_plugin('monitor').get_curse(args=self.args)
self.display_plugin(stats_processcount)
self.display_plugin(stats_monitor)
self.display_plugin(stats_processlist, max_y=(screen_y - self.get_curse_height(stats_alert) - 3))
self.display_plugin(stats_alert)
def display_plugin(self, plugin_stats,
display_optional=True,
max_y=65535):
"""
Display the plugin_stats on the screen
If display_optional=True display the optional stats
max_y do not display line > max_y
"""
# Exit if the display tag = False
if (not plugin_stats['display']):
return 0
# Get the screen size
screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0]
# Set the upper/left position of the message
if (plugin_stats['column'] < 0):
# Right align (last column)
display_x = screen_x - self.get_curse_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):
# Bottom (last line)
display_y = screen_y - self.get_curse_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
x = display_x
y = display_y
for m in plugin_stats['msgdict']:
# New line
if (m['msg'].startswith('\n')):
# Go to the next line
y = y + 1
# Return to the first column
x = display_x
continue
# Do not display outside the screen
if ((x < 0) or (x + len(m['msg']) > screen_x)):
continue
if ((y < 0) or (y + 1 > screen_y) or (y > max_y)):
break
# If display_optional = False do not display optional stats
if ((not display_optional) and m['optional']):
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']])
except:
pass
else:
# New column
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
def erase(self):
# Erase the content of the screen
self.term_window.erase()
def flush(self, stats, monitors=None, cs_status="None"):
"""
Clear and update screen
stats: Stats database to display
monitors: The monitored list
cs_status:
"None": standalone or server mode
"Connected": Client is connected to the server
"Disconnected": Client is disconnected from the server
"""
self.erase()
self.display(stats, monitors=monitors, cs_status=cs_status)
def update(self, stats, monitors=None, cs_status="None"):
"""
Update the screen and wait __refresh_time sec / catch key every 100 ms
stats: Stats database to display
monitors: The monitored list
cs_status:
"None": standalone or server mode
"Connected": Client is connected to the server
"Disconnected": Client is disconnected from the server
"""
# Flush display
self.flush(stats, monitors=monitors, cs_status=cs_status)
# Wait
countdown = Timer(self.__refresh_time)
while not countdown.finished():
# Getkey
if self.__catchKey() > -1:
# flush display
self.flush(stats, monitors=monitors, cs_status=cs_status)
# Wait 100ms...
curses.napms(100)
def get_curse_width(self, curse_msg, without_option=False):
# Return the width of the formated curses message
# The height is defined by the maximum line
try:
if (without_option):
# Size without options
c = len(max(''.join([ (i['msg'] if not i['optional'] else "")
for i in curse_msg['msgdict'] ]).split('\n'), key=len))
else:
# Size with all options
c = len(max(''.join([ i['msg']
for i in curse_msg['msgdict'] ]).split('\n'), key=len))
except:
return 0
else:
return c
def get_curse_height(self, curse_msg):
# Return the height of the formated curses message
# The height is defined by the number of '\n'
try:
c = [ i['msg'] for i in curse_msg['msgdict']].count('\n')
except:
return 0
else:
return c + 1

View File

@ -45,6 +45,9 @@ class GlancesGrabProcesses:
# key = pid
# value = [ read_bytes_old, write_bytes_old ]
self.io_old = {}
# Init
self.processlist = []
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
def __get_process_stats(self, proc):

View File

@ -43,7 +43,7 @@ class Plugin(GlancesPlugin):
# Enter -1 to right align
self.column_curse = 1
# Enter -1 to diplay bottom
self.line_curse = 4
self.line_curse = 5
def update(self):
@ -101,7 +101,7 @@ class Plugin(GlancesPlugin):
msg = "{0}".format(alert[3])
ret.append(self.curse_add_line(msg, decoration=alert[2]))
# Min / Mean / Max
msg = " ({0:2}/{1:2}/{2:2})".format(alert[6], alert[5], alert[4])
msg = " ({0:.1f}/{1:.1f}/{2:.1f})".format(alert[6], alert[5], alert[4])
ret.append(self.curse_add_line(msg))
# else:

View File

@ -142,8 +142,9 @@ class Plugin(GlancesPlugin):
msg = "{0}".format(format((100 - self.stats['idle']) / 100, '>6.1%'))
ret.append(self.curse_add_line(msg))
# Steal CPU usage
ret.append(self.curse_add_line(" ", optional=True))
if ('steal' in self.stats):
msg = " {0:8}".format(_("steal:"))
msg = "{0:8}".format(_("steal:"))
ret.append(self.curse_add_line(msg, optional=True))
msg = "{0}".format(format(self.stats['steal'] / 100, '>6.1%'))
ret.append(self.curse_add_line(msg,
@ -158,9 +159,10 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_add_line(msg,
self.get_alert_log(self.stats['user'], header="user")))
# IOWait CPU
ret.append(self.curse_add_line(" ", optional=True))
if ('iowait' in self.stats):
msg = " {0:8}".format(_("iowait:"))
ret.append(self.curse_add_line(msg))
msg = "{0:8}".format(_("iowait:"))
ret.append(self.curse_add_line(msg, optional=True))
msg = "{0}".format(format(self.stats['iowait'] / 100, '>6.1%'))
ret.append(self.curse_add_line(msg,
self.get_alert_log(self.stats['iowait'], header="iowait"), optional=True))
@ -174,8 +176,9 @@ class Plugin(GlancesPlugin):
ret.append(self.curse_add_line(msg,
self.get_alert_log(self.stats['system'], header="system")))
# IRQ CPU
ret.append(self.curse_add_line(" ", optional=True))
if ('irq' in self.stats):
msg = " {0:7} {1}".format(
msg = "{0:7} {1}".format(
_("irq:"),
format(self.stats['irq'] / 100, '>6.1%'))
ret.append(self.curse_add_line(msg, optional=True))
@ -188,8 +191,9 @@ class Plugin(GlancesPlugin):
format(self.stats['nice'] / 100, '>6.1%'))
ret.append(self.curse_add_line(msg, optional=True))
# Idles CPU
ret.append(self.curse_add_line(" ", optional=True))
if ('idle' in self.stats):
msg = " {0:7} {1}".format(
msg = "{0:7} {1}".format(
_("idle:"),
format(self.stats['idle'] / 100, '>6.1%'))
ret.append(self.curse_add_line(msg))

View File

@ -0,0 +1,82 @@
#!/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/>.
# Import Glances lib
from glances_plugin import GlancesPlugin
from glances.core.glances_globals import glances_monitors
class Plugin(GlancesPlugin):
"""
Glances's monitor Plugin
Only for display
"""
def __init__(self):
GlancesPlugin.__init__(self)
# 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
def load_limits(self, config):
"""
No limit...
"""
pass
def update(self):
"""
Nothing to do here
Just return the global glances_log
"""
# !!! It is not just a get
# !!! An update should be done on the server side before
# !!! New in v2: the monitor list is executed on the server side !
self.stats = glances_monitors.get()
def msg_curse(self, args=None):
"""
Return the dict to display in the curse interface
"""
# Init the return message
ret = []
# Build the string message
if (self.stats != []):
msg = "{0}".format(_("Monitored list"))
ret.append(self.curse_add_line(msg, "TITLE"))
for m in self.stats:
ret.append(self.curse_new_line())
msg = "{0}".format(str(m))
ret.append(self.curse_add_line(msg))
return ret

View File

@ -28,6 +28,7 @@ from glances.core.glances_globals import glances_logs
# Global list to manage the elapsed time
last_update_times = {}
def getTimeSinceLastUpdate(IOType):
global last_update_times
# assert(IOType in ['net', 'disk', 'process_disk'])
@ -62,7 +63,8 @@ class GlancesPlugin(object):
Load the limits from the configuration file
"""
if (config.has_section(self.plugin_name)):
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

View File

@ -53,7 +53,7 @@ class Plugin(GlancesPlugin):
# Enter -1 to right align
self.column_curse = 1
# Enter -1 to diplay bottom
self.line_curse = 3
self.line_curse = 4
def msg_curse(self, args=None):

View File

@ -81,7 +81,15 @@ class Plugin(GlancesPlugin):
ret = []
# Build the string message
# Hostanme is mandatory
if (args.client):
# Client mode
if cs_status.lower() == "connected":
msg = _("Connected to ") + format(server_ip)
elif cs_status.lower() == "disconnected":
msg = _("Disconnected from ") + format(server_ip)
ret.append(self.curse_add_line(msg))
# Hostname is mandatory
msg = _("{0}").format(self.stats['hostname'])
ret.append(self.curse_add_line(msg, "TITLE"))
# System info