From c6e07e1670f181e95335f1f497ead640e51eaaaa Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 6 Feb 2012 18:14:24 +0100 Subject: [PATCH 01/57] First version of the experimental BRANCH PSUtil replace StatGrab for CPU, LOAD and MEM --- README.md | 249 ++++++++++++++++++++++++++++++++++++++++++++++++- src/glances.py | 72 ++++++++++++-- 2 files changed, 311 insertions(+), 10 deletions(-) mode change 120000 => 100644 README.md diff --git a/README.md b/README.md deleted file mode 120000 index 100b9382..00000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -README \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..3ccb7acc --- /dev/null +++ b/README.md @@ -0,0 +1,248 @@ +[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) + +============================= +Glances -- Eye on your system +============================= + +## Description + +Glances is a CLI curses based monitoring tool for GNU/Linux or BSD OS. + +Glances uses the libstatgrab library to get information from your system. +It is developed in Python and uses the python-statgrab lib. + +![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) + +## Installation + +### From package manager + +Packages exist for Arch, Fedora, Redhat ... + +### From source + +Get the latest version: + + $ wget https://github.com/downloads/nicolargo/glances/glances-1.3.7.tar.gz + +Glances use a standard GNU style installer: + + $ tar zxvf glances-1.3.7.tar.gz + $ cd glances-1.3.7 + $ ./configure + $ make + $ sudo make install + +Pre-requisites: + +* Python 2.6+ (not tested with Python 3+) +* python-statgrab 0.5+ (did NOT work with python-statgrab 0.4) + +Notes: For Debian. +The Debian Squeeze repos only include the python-statgrab 0.4. +You had to install the version 0.5 using the following commands: + + $ sudo apt-get install libstatgrab-dev pkg-config python-dev make + $ wget http://ftp.uk.i-scream.org/sites/ftp.i-scream.org/pub/i-scream/pystatgrab/pystatgrab-0.5.tar.gz + $ tar zxvf pystatgrab-0.5.tar.gz + $ cd pystatgrab-0.5/ + $ ./setup.py build + $ sudo ./setup.py install + +Notes: For Ubuntu 10.04 and 10.10. +The instruction to install the version 0.5 are here: +https://github.com/nicolargo/glances/issues/5#issuecomment-3033194 + +## Running + +Easy: + + $ glances.py + +## User guide + +By default, stats are refreshed every second, to change this setting, you can +use the -t option. For exemple to set the refrech rate to 5 seconds: + + $ glances.py -t 5 + +Importants stats are colored: + +* GREEN: stat counter is "OK" +* BLUE: stat counter is "CAREFUL" +* MAGENTA: stat counter is "WARNING" +* RED: stat counter is "CRITICAL" + +When Glances is running, you can press: + +* 'h' to display an help message whith the keys you can press +* 'a' to set the automatic mode. The processes are sorted automatically + + If CPU > 70%, sort by process "CPU consumption" + + If MEM > 70%, sort by process "memory size" + +* 'c' to sort the processes list by CPU consumption +* 'd' Disable or enable the disk IO stats +* 'f' Disable or enable the file system stats +* 'l' Disable or enable the logs +* 'm' to sort the processes list by process size +* 'n' Disable or enable the network interfaces stats +* 'q' Exit + +### Header + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) + +The header shows the Glances version, the host name and the operating +system name, version and architecture. + +### CPU + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) + +The CPU states are shown as a percentage and for the configured refresh +time. + +If user|kernel|nice CPU is < 50%, then status is set to "OK". + +If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". + +If user|kernel|nice CPU is > 70%, then status is set to "WARNING". + +If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". + +### Load + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) + +On the Nosheep blog, Zach defines the average load: "In short it is the +average sum of the number of processes waiting in the run-queue plus the +number currently executing over 1, 5, and 15 minute time periods." + +Glances gets the number of CPU cores to adapt the alerts. With Glances, +alerts on average load are only set on 5 and 15 mins. + +If average load is < O.7*Core, then status is set to "OK". + +If average load is > O.7*Core, then status is set to "CAREFUL". + +If average load is > 1*Core, then status is set to "WARNING". + +If average load is > 5*Core, then status is set to "CRITICAL". + +### Memory + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) + +Glances uses tree columns: memory (RAM), swap and "real". + +Real used memory is: used - cache. + +Real free memory is: free + cache. + +With Glances, alerts are only set for on used swap and real memory. + +If memory is < 50%, then status is set to "OK". + +If memory is > 50%, then status is set to "CAREFUL". + +If memory is > 70%, then status is set to "WARNING". + +If memory is > 90%, then status is set to "CRITICAL". + +### Network bit rate + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) + +Glances display the network interface bit rate. The unit is adapted +dynamicaly (bits per second, Kbits per second, Mbits per second...). + +Alerts are set only if the network interface maximum speed is available. + +If bitrate is < 50%, then status is set to "OK". + +If bitrate is > 50%, then status is set to "CAREFUL". + +If bitrate is > 70%, then status is set to "WARNING". + +If bitrate is > 90%, then status is set to "CRITICAL". + +For exemple, on a 100 Mbps Ethernet interface, the warning status is set +if the bit rate is higher than 70 Mbps. + +### Disk I/O + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) + +Glances display the disk I/O throughput. The unit is adapted dynamicaly +(bytes per second, Kbytes per second, Mbytes per second...). + +There is no alert on this information. + +### Filesystem + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) + +Glances display the total and used filesytem disk space. The unit is +adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per +second...). + +Alerts are set for used disk space: + +If disk used is < 50%, then status is set to "OK". + +If disk used is > 50%, then status is set to "CAREFUL". + +If disk used is > 70%, then status is set to "WARNING". + +If disk used is > 90%, then status is set to "CRITICAL". + +### Processes + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) + +Glances displays a summary and a list of processes. + +By default (or if you hit the 'a' key) the process list is automaticaly +sorted by CPU of memory consumption. + +The number of processes in the list is adapted to the screen size. + +### Logs + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) + +A logs list is displayed in the bottom of the screen if (an only if): + +* at least one WARNING or CRITICAL alert was occured. +* space is available in the bottom of the console/terminal + +There is one line per alert with the following information: + +* start date +* end date +* alert name +* (min/avg/max) values + +### Footer + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) + +Glances displays a caption and the current time/date. + +## Localisation + +To generate french locale execute as root or sudo : +i18n_francais_generate.sh + +To generate spanish locale execute as root or sudo : +i18n_espanol_generate.sh + +## Todo + +You are welcome to contribute to this software. + +* Packaging for Debian, Ubuntu, BSD... +* Check the needed Python library in the configure.ac +* Add file system stats when the python-statgrab is corrected diff --git a/src/glances.py b/src/glances.py index 8ff0b818..73f709ea 100755 --- a/src/glances.py +++ b/src/glances.py @@ -38,9 +38,18 @@ except KeyboardInterrupt: #===== application = 'glances' -__version__ = "1.3.7" +__version__ = "1.4.0a" gettext.install(application) +try: + import psutil +except: + print _('PsUtil library initialization failed, Glances cannot start.') + print + sys.exit(1) + +# TODO: test PsUtil psutil.__version__ + try: import statgrab except: @@ -294,28 +303,68 @@ class glancesStats(): Update the stats """ - # Get informations from libstatgrab and others... + # Get system informations + + # Host and OS informations try: self.host = statgrab.sg_get_host_info() except: self.host = {} self.system = self.host + + # CPU try: - self.cpu = statgrab.sg_get_cpu_percents() + self.cputime_old except: + self.cputime_old = psutil.cpu_times() + self.cputime_total_old = self.cputime_old.user+self.cputime_old.nice+self.cputime_old.system+self.cputime_old.idle+self.cputime_old.iowait+self.cputime_old.irq+self.cputime_old.softirq self.cpu = {} + else: + try: + self.cputime_new = psutil.cpu_times() + self.cputime_total_new = self.cputime_new.user+self.cputime_new.nice+self.cputime_new.system+self.cputime_new.idle+self.cputime_new.iowait+self.cputime_new.irq+self.cputime_new.softirq + percent = 100/(self.cputime_total_new-self.cputime_total_old) + self.cpu = { 'kernel': (self.cputime_new.system-self.cputime_old.system)*percent, + 'user': (self.cputime_new.user-self.cputime_old.user)*percent, + 'idle': (self.cputime_new.idle-self.cputime_old.idle)*percent, + 'nice': (self.cputime_new.nice-self.cputime_old.nice)*percent } + self.cputime_old = self.cputime_new + self.cputime_total_old = self.cputime_total_new + except: + self.cpu = {} + + # LOAD try: - self.load = statgrab.sg_get_load_stats() + getload = os.getloadavg() + self.load = { 'min1': getload[0], + 'min5': getload[1], + 'min15': getload[2] } except: self.load = {} + + # MEM try: - self.mem = statgrab.sg_get_mem_stats() + # Only for Linux + cachemem = psutil.cached_phymem()+psutil.phymem_buffers() + except: + cachemem = 0 + try: + phymem = psutil.phymem_usage() + self.mem = { 'cache': cachemem, + 'total': phymem.total, + 'free': phymem.free, + 'used': phymem.used } except: self.mem = {} try: - self.memswap = statgrab.sg_get_swap_stats() + virtmem = psutil.virtmem_usage() + self.memswap = { 'total': virtmem.total, + 'free': virtmem.free, + 'used': virtmem.used } except: self.memswap = {} + + # NET try: self.networkinterface = statgrab.sg_get_network_iface_stats() except: @@ -324,15 +373,21 @@ class glancesStats(): self.network = statgrab.sg_get_network_io_stats_diff() except: self.network = {} + + # DISK IO try: self.diskio = statgrab.sg_get_disk_io_stats_diff() except: self.diskio = {} + + # FILE SYSTEM try: # Replace the bugged self.fs = statgrab.sg_get_fs_stats() self.fs = self.glancesgrabfs.get() except: self.fs = {} + + # PROCESS try: self.processcount = statgrab.sg_get_process_count() except: @@ -345,9 +400,8 @@ class glancesStats(): # Get the current date/time self.now = datetime.datetime.now() - # Get the number of core (CPU) - # Used to display load alerts - self.core_number = multiprocessing.cpu_count() + # Get the number of core (CPU) (Used to display load alerts) + self.core_number = psutil.NUM_CPUS def end(self): From 89b5ebc8b4bac47bd67ef2443d64de6809fc2e79 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 6 Feb 2012 18:54:31 +0100 Subject: [PATCH 02/57] DISK IO --- src/glances.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/glances.py b/src/glances.py index 73f709ea..99efff0c 100755 --- a/src/glances.py +++ b/src/glances.py @@ -376,9 +376,23 @@ class glancesStats(): # DISK IO try: - self.diskio = statgrab.sg_get_disk_io_stats_diff() + self.diskio_old except: - self.diskio = {} + self.diskio_old = psutil.disk_io_counters(True) + self.diskio = [] + else: + try: + self.diskio_new = psutil.disk_io_counters(True) + self.diskio = [] + for disk in self.diskio_new: + diskstat = {} + diskstat['disk_name'] = disk + diskstat['read_bytes'] = self.diskio_new[disk].read_bytes - self.diskio_old[disk].read_bytes + diskstat['write_bytes'] = self.diskio_new[disk].write_bytes - self.diskio_old[disk].write_bytes + self.diskio.append(diskstat) + self.diskio_old = self.diskio_new + except: + self.diskio = [] # FILE SYSTEM try: @@ -992,7 +1006,7 @@ class glancesScreen(): # Adapt the maximum disk to the screen disk = 0 for disk in range(0, min(screen_y-self.diskio_y-3, len(diskio))): - elapsed_time = max(1, diskio[disk]['systime']) + elapsed_time = max(1, self.__refresh_time) self.term_window.addnstr(self.diskio_y+1+disk, self.diskio_x, diskio[disk]['disk_name']+':', 8) self.term_window.addnstr(self.diskio_y+1+disk, self.diskio_x+10, self.__autoUnit(diskio[disk]['write_bytes']/elapsed_time) + "B", 8) self.term_window.addnstr(self.diskio_y+1+disk, self.diskio_x+20, self.__autoUnit(diskio[disk]['read_bytes']/elapsed_time) + "B", 8) From e1e8a99276d5a0f17a274750fea662aa12c210ab Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 7 Feb 2012 11:50:05 +0100 Subject: [PATCH 03/57] Replace FsGrab by PsUtil --- src/glances.py | 131 ++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 73 deletions(-) diff --git a/src/glances.py b/src/glances.py index 99efff0c..2b89171c 100755 --- a/src/glances.py +++ b/src/glances.py @@ -218,7 +218,7 @@ class glancesLogs(): class glancesGrabFs(): """ - Get FS stats: idem as structure http://www.i-scream.org/libstatgrab/docs/sg_get_fs_stats.3.html + Get FS stats """ def __init__(self): @@ -229,45 +229,28 @@ class glancesGrabFs(): """ Update the stats """ - + + # Ignore the following fs + ignore_fsname = ('', 'none', 'gvfs-fuse-daemon', 'fusectl', 'cgroup') + ignore_fstype = ('binfmt_misc', 'devpts', 'iso9660', 'none', 'proc', 'sysfs', 'usbfs') + # Reset the list self.fs_list = [] - # Ignore the following fs - ignore_fsname = ('none', 'gvfs-fuse-daemon', 'fusectl', 'cgroup') - ignore_fstype = ('binfmt_misc', 'devpts', 'iso9660', 'none', 'proc', 'sysfs', 'usbfs') - # Open the current mounted FS - mtab = open("/etc/mtab", "r") - for line in mtab.readlines(): - if line.split()[0] in ignore_fsname: continue - if line.split()[2] in ignore_fstype: continue - # Get FS stats + fs_stat = psutil.disk_partitions(True) + for fs in range(len(fs_stat)): fs_current = {} - fs_name = self.__getmount__(line.split()[1]) - fs_stats = os.statvfs(fs_name) - # Build the list - fs_current['device_name'] = str(line.split()[0]) - fs_current['fs_type'] = str(line.split()[2]) - fs_current['mnt_point'] = str(fs_name) - fs_current['size'] = float(fs_stats.f_blocks) * long(fs_stats.f_frsize) - fs_current['used'] = float(fs_stats.f_blocks - fs_stats.f_bfree) * long(fs_stats.f_frsize) - fs_current['avail'] = float(fs_stats.f_bfree) * long(fs_stats.f_frsize) - self.fs_list.append(fs_current) - mtab.close() - - - def __getmount__(self, path): - """ - Return the real root path of a file - Exemple: /home/nicolargo can return /home or / - """ - path = os.path.realpath(os.path.abspath(path)) - while path != os.path.sep: - if os.path.ismount(path): - return path - path = os.path.abspath(os.path.join(path, os.pardir)) - return path + fs_current['device_name'] = fs_stat[fs].device + if fs_current['device_name'] in ignore_fsname: continue + fs_current['fs_type'] = fs_stat[fs].fstype + if fs_current['fs_type'] in ignore_fstype: continue + fs_current['mnt_point'] = fs_stat[fs].mountpoint + fs_usage = psutil.disk_usage(fs_current['mnt_point']) + fs_current['size'] = fs_usage.total + fs_current['used'] = fs_usage.used + fs_current['avail'] = fs_usage.free + self.fs_list.append(fs_current) def get(self): @@ -366,13 +349,23 @@ class glancesStats(): # NET try: - self.networkinterface = statgrab.sg_get_network_iface_stats() + self.network_old except: - self.networkinterface = {} - try: - self.network = statgrab.sg_get_network_io_stats_diff() - except: - self.network = {} + self.network_old = psutil.network_io_counters(True) + self.network = [] + else: + try: + self.network_new = psutil.network_io_counters(True) + self.network = [] + for net in self.network_new: + netstat = {} + netstat['interface_name'] = net + netstat['rx'] = self.network_new[net].bytes_recv - self.network_old[net].bytes_recv + netstat['tx'] = self.network_new[net].bytes_sent - self.network_old[net].bytes_sent + self.network.append(netstat) + self.network_old = self.network_new + except: + self.network = [] # DISK IO try: @@ -454,11 +447,7 @@ class glancesStats(): def getMemSwap(self): return self.memswap - - - def getNetworkInterface(self): - return self.networkinterface - + def getNetwork(self): return self.network @@ -626,14 +615,23 @@ class glancesScreen(): 560745673 -> 561M ... """ - if val >= 1073741824L: - return "%.1fG" % (val / 1073741824L) - elif val >= 1048576L: - return "%.1fM" % (val / 1048576L) - elif val >= 1024: - return "%.1fK" % (val / 1024) - else: - return str(int(val)) + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = { + 'Y': 1208925819614629174706176L, + 'Z': 1180591620717411303424L, + 'E': 1152921504606846976L, + 'P': 1125899906842624L, + 'T': 1099511627776L, + 'G': 1073741824, + 'M': 1048576, + 'K': 1024 + } + for key in reversed(symbols): + if val >= prefix[key]: + value = float(val) / prefix[key] + return '%.1f%s' % (value, key) + return "%s" % val + def __getAlert(self, current = 0, max = 100): # If current < CAREFUL of max then alert = OK @@ -760,7 +758,7 @@ class glancesScreen(): self.displayCpu(stats.getCpu()) self.displayLoad(stats.getLoad(), stats.getCore()) self.displayMem(stats.getMem(), stats.getMemSwap()) - network_count = self.displayNetwork(stats.getNetwork(), stats.getNetworkInterface()) + network_count = self.displayNetwork(stats.getNetwork()) diskio_count = self.displayDiskIO(stats.getDiskIO(), self.network_y + network_count) fs_count = self.displayFs(stats.getFs(), self.network_y + network_count + diskio_count) log_count = self.displayLog(self.network_y + network_count + diskio_count + fs_count) @@ -950,27 +948,18 @@ class glancesScreen(): self.term_window.addnstr(self.mem_y+3, self.mem_x+30, str((mem['free']+mem['cache'])/1048576), 8) - def displayNetwork(self, network, networkinterface): + def displayNetwork(self, network): """ Display the network interface bitrate Return the number of interfaces """ # Network interfaces bitrate - if (not network or not networkinterface or not self.network_tag): + if (not network or not self.network_tag): return 0 screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] if ((screen_y > self.network_y+3) and (screen_x > self.network_x+28)): - # Get the speed of the network interface - # TODO: optimize... - speed = {} - for i in range(0, len(networkinterface)): - # Strange think, on Ubuntu, libstatgrab return 65525 for my ethernet card... - if networkinterface[i]['speed'] == 65535: - speed[networkinterface[i]['interface_name']] = 0 - else: - speed[networkinterface[i]['interface_name']] = networkinterface[i]['speed']*1000000 # Network interfaces bitrate self.term_window.addnstr(self.network_y, self.network_x, _("Net rate"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) self.term_window.addnstr(self.network_y, self.network_x+10, _("Rx/ps"), 8) @@ -978,14 +967,10 @@ class glancesScreen(): # Adapt the maximum interface to the screen ret = 2 for i in range(0, min(screen_y-self.network_y-3, len(network))): - try: - speed[network[i]['interface_name']] - except: - break - elapsed_time = max (1, network[i]['systime']) + elapsed_time = max (1, self.__refresh_time) self.term_window.addnstr(self.network_y+1+i, self.network_x, network[i]['interface_name']+':', 8) - self.term_window.addnstr(self.network_y+1+i, self.network_x+10, self.__autoUnit(network[i]['rx']/elapsed_time*8) + "b", 8, self.__getNetColor(network[i]['rx']/elapsed_time*8, speed[network[i]['interface_name']])) - self.term_window.addnstr(self.network_y+1+i, self.network_x+20, self.__autoUnit(network[i]['tx']/elapsed_time*8) + "b", 8, self.__getNetColor(network[i]['tx']/elapsed_time*8, speed[network[i]['interface_name']])) + self.term_window.addnstr(self.network_y+1+i, self.network_x+10, self.__autoUnit(network[i]['rx']/elapsed_time*8) + "b", 8) + self.term_window.addnstr(self.network_y+1+i, self.network_x+20, self.__autoUnit(network[i]['tx']/elapsed_time*8) + "b", 8) ret = ret + 1 return ret return 0 From 73e92da8fc3e4af2103e093f68e45e957fb28212 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 8 Feb 2012 14:32:53 +0100 Subject: [PATCH 04/57] PsUtil - Add process stat --- src/glances.py | 113 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 21 deletions(-) diff --git a/src/glances.py b/src/glances.py index 2b89171c..2f072d13 100755 --- a/src/glances.py +++ b/src/glances.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -# Glances is a simple CLI monitoring tool based on libstatgrab +# Glances is a simple textual monitoring tool # -# Pre-requisites: python-statgrab 0.5 or > +# Pre-requisites: Python 2.6+ and PsUtil 0.4.0+ # # Copyright (C) Nicolargo 2012 # @@ -23,6 +23,7 @@ from __future__ import generators try: import os + import platform import getopt import sys import signal @@ -45,10 +46,24 @@ try: import psutil except: print _('PsUtil library initialization failed, Glances cannot start.') + print _('On Debian/Ubuntu, you can try (as root):') + print _('# apt-get install python-dev python-pip') + print _('# pip install psutil') print sys.exit(1) - -# TODO: test PsUtil psutil.__version__ + +try: + # This function is available in the 0.4.0+ version of PsUtil + psutil.disk_io_counters() +except: + print _('PsUtil version 0.4.0 or higher is needed.') + print _('On Debian/Ubuntu, you can try (as root):') + print _('# apt-get install python-dev python-pip') + print _('# pip install --upgrade psutil') + print + sys.exit(1) + +# TODO: Remove after test PsUtil psutil.__version__ try: import statgrab @@ -222,7 +237,7 @@ class glancesGrabFs(): """ def __init__(self): - self.__update__() + pass def __update__(self): @@ -276,10 +291,8 @@ class glancesStats(): try: self.glancesgrabfs = glancesGrabFs() except: - self.glancesgrabfs = {} - - # Do the first update - self.__update__() + self.glancesgrabfs = {} + def __update__(self): """ @@ -289,13 +302,28 @@ class glancesStats(): # Get system informations # Host and OS informations + self.host = {} + self.host['hostname'] = platform.node() + self.host['platform'] = platform.architecture()[0] + self.host['processor'] = platform.processor() + self.host['os_name'] = platform.system() try: - self.host = statgrab.sg_get_host_info() + if (self.host['os_name'] == "Linux" or self.host['os_name'] == "FreeBSD"): + os_version = platform.linux_distribution() + self.host['os_version'] = os_version[0]+" "+os_version[1]+" ("+os_version[2]+")" + elif (self.host['os_name'] == "Windows"): + os_version = platform.win32_ver() + self.host['os_version'] = os_version[0]+" "+os_version[2] + elif (self.host['os_name'] == "Darwin"): + os_version = platform.mac_ver() + self.host['os_version'] = os_version[0] + else: + self.host['os_version'] = "" except: - self.host = {} - self.system = self.host + self.host['os_version'] = "" # CPU + percent = 0 try: self.cputime_old except: @@ -389,20 +417,61 @@ class glancesStats(): # FILE SYSTEM try: - # Replace the bugged self.fs = statgrab.sg_get_fs_stats() self.fs = self.glancesgrabfs.get() except: self.fs = {} # PROCESS + # Initialiation of the processes list + self.processcount = {'zombie': 0, 'running': 0, 'total': 0, 'stopped': 0, 'sleeping': 0, 'disk sleep': 0} try: - self.processcount = statgrab.sg_get_process_count() + self.process except: - self.processcount = {} + self.process = [] + try: - self.process = statgrab.sg_get_process_stats() + self.process_all except: - self.process = {} + self.process_all = [proc for proc in psutil.process_iter()] + for proc in self.process_all[:]: + # Global stats + try: + self.processcount[str(proc.status)] + except: + pass + else: + self.processcount[str(proc.status)] += 1 + self.processcount['total'] += 1 + # A first value is needed to compute the CPU percent + try: + proc.get_cpu_percent(interval=0) + except: + pass + else: + proc._before = proc + else: + self.process = [] + for proc in self.process_all[:]: + # Global stats + try: + self.processcount[str(proc.status)] + except: + pass + else: + self.processcount[str(proc.status)] += 1 + self.processcount['total'] += 1 + # Per process stats + try: + procstat = {} + procstat['process_name'] = proc.name + procstat['proctitle'] = " ".join(str(i) for i in proc.cmdline) + procstat['proc_size'] = proc.get_memory_info().vms + procstat['proc_resident'] = proc.get_memory_info().rss + procstat['cpu_percent'] = proc._before.get_cpu_percent(interval=0) + self.process.append(procstat) + except: + pass + del(self.process_all) # Get the current date/time self.now = datetime.datetime.now() @@ -426,7 +495,7 @@ class glancesStats(): def getSystem(self): - return self.system + return self.host def getCpu(self): @@ -1015,8 +1084,8 @@ class glancesScreen(): mounted = 0 for mounted in range(0, min(screen_y-self.fs_y-3, len(fs))): self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x, fs[mounted]['mnt_point'], 8) - self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+10, self.__autoUnit(fs[mounted]['size']), 8) - self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+20, self.__autoUnit(fs[mounted]['used']), 8, self.__getFsColor(fs[mounted]['used'], fs[mounted]['size'])) + self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+10, self.__autoUnit(fs[mounted]['size']) + "B", 8) + self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+20, self.__autoUnit(fs[mounted]['used']) + "B", 8, self.__getFsColor(fs[mounted]['used'], fs[mounted]['size'])) return mounted+3 return 0 @@ -1058,7 +1127,7 @@ class glancesScreen(): def displayProcess(self, processcount, processlist, log_count = 0): # Process - if (not processcount or not processlist): + if (not processcount): return 0 screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] @@ -1083,6 +1152,8 @@ class glancesScreen(): self.term_window.addnstr(self.process_y+1, process_x+30,str(processcount['sleeping']), 8) self.term_window.addnstr(self.process_y+1, process_x+40,str(processcount['stopped']+stats.getProcessCount()['zombie']), 8) # Display the process detail + if (not processlist): + return 3 if ((screen_y > self.process_y+6) and (screen_x > process_x+49)): # Processes detail From 2799eb8369f55391587e4d6a4567a68c58b35aad Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 11 Feb 2012 15:33:31 +0100 Subject: [PATCH 05/57] Change the help windows --- NEWS | 8 ++ README.md | 249 +------------------------------------------------ src/glances.py | 249 ++++++++++++++++++++++++++++--------------------- 3 files changed, 153 insertions(+), 353 deletions(-) mode change 100644 => 120000 README.md diff --git a/NEWS b/NEWS index 15d68d23..567e378c 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,11 @@ +Version 1.4 +=========== + + * No more StatGrab, welcome to the PsUtil library ! + * Sort by Process name ('p' key) + * Only major stats (CPU, Load and memory) use background colors + * Improve operating system name + Version 1.3.7 ============= diff --git a/README.md b/README.md deleted file mode 100644 index 3ccb7acc..00000000 --- a/README.md +++ /dev/null @@ -1,248 +0,0 @@ -[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) - -============================= -Glances -- Eye on your system -============================= - -## Description - -Glances is a CLI curses based monitoring tool for GNU/Linux or BSD OS. - -Glances uses the libstatgrab library to get information from your system. -It is developed in Python and uses the python-statgrab lib. - -![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) - -## Installation - -### From package manager - -Packages exist for Arch, Fedora, Redhat ... - -### From source - -Get the latest version: - - $ wget https://github.com/downloads/nicolargo/glances/glances-1.3.7.tar.gz - -Glances use a standard GNU style installer: - - $ tar zxvf glances-1.3.7.tar.gz - $ cd glances-1.3.7 - $ ./configure - $ make - $ sudo make install - -Pre-requisites: - -* Python 2.6+ (not tested with Python 3+) -* python-statgrab 0.5+ (did NOT work with python-statgrab 0.4) - -Notes: For Debian. -The Debian Squeeze repos only include the python-statgrab 0.4. -You had to install the version 0.5 using the following commands: - - $ sudo apt-get install libstatgrab-dev pkg-config python-dev make - $ wget http://ftp.uk.i-scream.org/sites/ftp.i-scream.org/pub/i-scream/pystatgrab/pystatgrab-0.5.tar.gz - $ tar zxvf pystatgrab-0.5.tar.gz - $ cd pystatgrab-0.5/ - $ ./setup.py build - $ sudo ./setup.py install - -Notes: For Ubuntu 10.04 and 10.10. -The instruction to install the version 0.5 are here: -https://github.com/nicolargo/glances/issues/5#issuecomment-3033194 - -## Running - -Easy: - - $ glances.py - -## User guide - -By default, stats are refreshed every second, to change this setting, you can -use the -t option. For exemple to set the refrech rate to 5 seconds: - - $ glances.py -t 5 - -Importants stats are colored: - -* GREEN: stat counter is "OK" -* BLUE: stat counter is "CAREFUL" -* MAGENTA: stat counter is "WARNING" -* RED: stat counter is "CRITICAL" - -When Glances is running, you can press: - -* 'h' to display an help message whith the keys you can press -* 'a' to set the automatic mode. The processes are sorted automatically - - If CPU > 70%, sort by process "CPU consumption" - - If MEM > 70%, sort by process "memory size" - -* 'c' to sort the processes list by CPU consumption -* 'd' Disable or enable the disk IO stats -* 'f' Disable or enable the file system stats -* 'l' Disable or enable the logs -* 'm' to sort the processes list by process size -* 'n' Disable or enable the network interfaces stats -* 'q' Exit - -### Header - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) - -The header shows the Glances version, the host name and the operating -system name, version and architecture. - -### CPU - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) - -The CPU states are shown as a percentage and for the configured refresh -time. - -If user|kernel|nice CPU is < 50%, then status is set to "OK". - -If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". - -If user|kernel|nice CPU is > 70%, then status is set to "WARNING". - -If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". - -### Load - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) - -On the Nosheep blog, Zach defines the average load: "In short it is the -average sum of the number of processes waiting in the run-queue plus the -number currently executing over 1, 5, and 15 minute time periods." - -Glances gets the number of CPU cores to adapt the alerts. With Glances, -alerts on average load are only set on 5 and 15 mins. - -If average load is < O.7*Core, then status is set to "OK". - -If average load is > O.7*Core, then status is set to "CAREFUL". - -If average load is > 1*Core, then status is set to "WARNING". - -If average load is > 5*Core, then status is set to "CRITICAL". - -### Memory - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) - -Glances uses tree columns: memory (RAM), swap and "real". - -Real used memory is: used - cache. - -Real free memory is: free + cache. - -With Glances, alerts are only set for on used swap and real memory. - -If memory is < 50%, then status is set to "OK". - -If memory is > 50%, then status is set to "CAREFUL". - -If memory is > 70%, then status is set to "WARNING". - -If memory is > 90%, then status is set to "CRITICAL". - -### Network bit rate - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) - -Glances display the network interface bit rate. The unit is adapted -dynamicaly (bits per second, Kbits per second, Mbits per second...). - -Alerts are set only if the network interface maximum speed is available. - -If bitrate is < 50%, then status is set to "OK". - -If bitrate is > 50%, then status is set to "CAREFUL". - -If bitrate is > 70%, then status is set to "WARNING". - -If bitrate is > 90%, then status is set to "CRITICAL". - -For exemple, on a 100 Mbps Ethernet interface, the warning status is set -if the bit rate is higher than 70 Mbps. - -### Disk I/O - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) - -Glances display the disk I/O throughput. The unit is adapted dynamicaly -(bytes per second, Kbytes per second, Mbytes per second...). - -There is no alert on this information. - -### Filesystem - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) - -Glances display the total and used filesytem disk space. The unit is -adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per -second...). - -Alerts are set for used disk space: - -If disk used is < 50%, then status is set to "OK". - -If disk used is > 50%, then status is set to "CAREFUL". - -If disk used is > 70%, then status is set to "WARNING". - -If disk used is > 90%, then status is set to "CRITICAL". - -### Processes - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) - -Glances displays a summary and a list of processes. - -By default (or if you hit the 'a' key) the process list is automaticaly -sorted by CPU of memory consumption. - -The number of processes in the list is adapted to the screen size. - -### Logs - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) - -A logs list is displayed in the bottom of the screen if (an only if): - -* at least one WARNING or CRITICAL alert was occured. -* space is available in the bottom of the console/terminal - -There is one line per alert with the following information: - -* start date -* end date -* alert name -* (min/avg/max) values - -### Footer - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) - -Glances displays a caption and the current time/date. - -## Localisation - -To generate french locale execute as root or sudo : -i18n_francais_generate.sh - -To generate spanish locale execute as root or sudo : -i18n_espanol_generate.sh - -## Todo - -You are welcome to contribute to this software. - -* Packaging for Debian, Ubuntu, BSD... -* Check the needed Python library in the configure.ac -* Add file system stats when the python-statgrab is corrected diff --git a/README.md b/README.md new file mode 120000 index 00000000..100b9382 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +README \ No newline at end of file diff --git a/src/glances.py b/src/glances.py index 2f072d13..19fce56f 100755 --- a/src/glances.py +++ b/src/glances.py @@ -39,7 +39,7 @@ except KeyboardInterrupt: #===== application = 'glances' -__version__ = "1.4.0a" +__version__ = "1.4a" gettext.install(application) try: @@ -442,8 +442,9 @@ class glancesStats(): else: self.processcount[str(proc.status)] += 1 self.processcount['total'] += 1 - # A first value is needed to compute the CPU percent + # Per process stats try: + # A first value is needed to compute the CPU percent proc.get_cpu_percent(interval=0) except: pass @@ -468,6 +469,8 @@ class glancesStats(): procstat['proc_size'] = proc.get_memory_info().vms procstat['proc_resident'] = proc.get_memory_info().rss procstat['cpu_percent'] = proc._before.get_cpu_percent(interval=0) + procstat['diskio_read'] = proc.get_io_counters().read_bytes - proc._before.get_io_counters().read_bytes + procstat['diskio_write'] = proc.get_io_counters().write_bytes - proc._before.get_io_counters().write_bytes self.process.append(procstat) except: pass @@ -519,15 +522,15 @@ class glancesStats(): def getNetwork(self): - return self.network + return sorted(self.network, key=lambda network: network['interface_name']) def getDiskIO(self): - return self.diskio + return sorted(self.diskio, key=lambda diskio: diskio['disk_name']) def getFs(self): - return self.fs + return sorted(self.fs, key=lambda fs: fs['mnt_point']) def getProcessCount(self): @@ -539,6 +542,7 @@ class glancesStats(): Return the sorted process list """ + sortedReverse = True if sortedby == 'auto': # If global Mem > 70% sort by process size # Else sort by cpu comsoption @@ -546,7 +550,10 @@ class glancesStats(): if ( self.mem['total'] != 0): if ( ( (self.mem['used'] - self.mem['cache']) * 100 / self.mem['total']) > limits.getSTDWarning()): sortedby = 'proc_size' - return sorted(self.process, key=lambda process: process[sortedby], reverse=True) + elif sortedby == 'process_name': + sortedReverse = False + + return sorted(self.process, key=lambda process: process[sortedby], reverse = sortedReverse) def getNow(self): @@ -579,7 +586,7 @@ class glancesScreen(): self.fs_x = 0 ; self.fs_y = -1 self.process_x = 30; self.process_y = 9 self.log_x = 0 ; self.log_y = -1 - self.help_x = 30; self.help_y = 12 + self.help_x = 0; self.help_y = 0 self.now_x = 79; self.now_y = 3 self.caption_x = 0 ; self.caption_y = 3 @@ -600,7 +607,7 @@ class glancesScreen(): # Init colors self.hascolors = False - if curses.has_colors(): + if (curses.has_colors() and curses.COLOR_PAIRS > 8): self.hascolors = True # Init FG color BG color curses.init_pair(1, curses.COLOR_WHITE, -1) @@ -608,8 +615,10 @@ class glancesScreen(): 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_WHITE, curses.COLOR_CYAN) - curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_YELLOW) + 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 @@ -622,6 +631,10 @@ class glancesScreen(): self.ifCAREFUL_color = curses.color_pair(4)|curses.A_BOLD self.ifWARNING_color = curses.color_pair(5)|curses.A_BOLD self.ifCRITICAL_color = curses.color_pair(2)|curses.A_BOLD + self.default_color2 = curses.color_pair(7)|curses.A_BOLD + self.ifCAREFUL_color2 = curses.color_pair(8)|curses.A_BOLD + self.ifWARNING_color2 = curses.color_pair(9)|curses.A_BOLD + self.ifCRITICAL_color2 = curses.color_pair(6)|curses.A_BOLD else: # B&W text styles self.no_color = curses.A_NORMAL @@ -629,8 +642,12 @@ class glancesScreen(): self.ifCAREFUL_color = curses.A_UNDERLINE self.ifWARNING_color = curses.A_BOLD self.ifCRITICAL_color = curses.A_REVERSE + self.default_color2 = curses.A_NORMAL + self.ifCAREFUL_color2 = curses.A_UNDERLINE + self.ifWARNING_color2 = curses.A_BOLD + self.ifCRITICAL_color2 = curses.A_REVERSE - # Define the colors list (hash table) + # Define the colors list (hash table) for logged stats self.__colors_list = { # CAREFUL WARNING CRITICAL 'DEFAULT': self.no_color, @@ -640,25 +657,26 @@ class glancesScreen(): 'CRITICAL': self.ifCRITICAL_color } + # Define the colors list (hash table) for non logged stats + self.__colors_list2 = { + # CAREFUL WARNING CRITICAL + 'DEFAULT': self.no_color, + 'OK': self.default_color2, + 'CAREFUL': self.ifCAREFUL_color2, + 'WARNING': self.ifWARNING_color2, + 'CRITICAL': self.ifCRITICAL_color2 + } + # By default all the stats are displayed self.network_tag = True self.diskio_tag = True self.fs_tag = True self.log_tag = True + self.help_tag = False # Init main window self.term_window = self.screen.subwin(0, 0) - # Init help panel - # TODO: pb when size of the screen < 22 lines - screen_x = self.screen.getmaxyx()[1] - screen_y = self.screen.getmaxyx()[0] - if (screen_x > (self.term_w-self.help_x) and - (screen_y > (self.term_h-self.help_y-2))): - term_help = self.screen.subwin(self.term_h-self.help_y-2, self.term_w-self.help_x, self.help_y, self.help_x) - self.panel_help = curses.panel.new_panel(term_help) - self.hideHelp() - # Init refresh time self.__refresh_time = refresh_time @@ -725,8 +743,18 @@ class glancesScreen(): def __getColor(self, current = 0, max = 100): + """ + Return colors for logged stats + """ return self.__colors_list[self.__getAlert(current, max)] + + def __getColor2(self, current = 0, max = 100): + """ + Return colors for non logged stats + """ + return self.__colors_list2[self.__getAlert(current, max)] + def __getCpuAlert(self, current = 0, max = 100): return self.__getAlert(current, max) @@ -765,11 +793,15 @@ class glancesScreen(): def __getNetColor(self, current = 0, max = 100): - return self.__getColor(current, max) + return self.__getColor2(current, max) def __getFsColor(self, current = 0, max = 100): - return self.__getColor(current, max) + return self.__getColor2(current, max) + + + def __getProcessColor(self, current = 0, max = 100): + return self.__getColor2(current, max) def __catchKey(self): @@ -796,10 +828,7 @@ class glancesScreen(): self.fs_tag = not self.fs_tag elif (self.pressedkey == 104): # 'h' > Enable/Disable help - if (self.panel_help.hidden()): - self.showHelp() - else: - self.hideHelp() + self.help_tag = not self.help_tag elif (self.pressedkey == 108): # 'l' > Enable/Disable logs list self.log_tag = not self.log_tag @@ -809,6 +838,9 @@ class glancesScreen(): elif (self.pressedkey == 110): # 'n' > Enable/Disable network stats self.network_tag = not self.network_tag + elif (self.pressedkey == 112): + # 'p' > Sort process list by Process name + self.setProcessSortedBy('process_name') # Return the key code return self.pressedkey @@ -834,10 +866,8 @@ class glancesScreen(): self.displayProcess(stats.getProcessCount(), stats.getProcessList(screen.getProcessSortedBy()), log_count) self.displayCaption() self.displayNow(stats.getNow()) + self.displayHelp() - # Display help panel - if (not self.panel_help.hidden()): - self.displayHelp() def erase(self): # Erase the content of the screen @@ -868,43 +898,6 @@ class glancesScreen(): curses.napms(100) - def displayHelp(self): - """ - Display the help panel (active| desactive with the 'h' key) - """ - screen_x = self.screen.getmaxyx()[1] - screen_y = self.screen.getmaxyx()[0] - if ((screen_y > 23) - and (screen_x > 79)): - helpWindow = self.panel_help.window() - helpWindow.resize(self.term_h-self.help_y-2, self.term_w-self.help_x) - helpWindow.clear() - msg = _("Glances help (press 'h' to hide)") - helpWindow.addnstr(1, 2, _("'a'\tto sort processes automatically"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.addnstr(2, 2, _("'c'\tto sort processes by CPU consumption"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.addnstr(3, 2, _("'d'\tto disable|enable the disk IO stats"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.addnstr(4, 2, _("'f'\tto disable|enable the file system stats"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.addnstr(5, 2, _("'l'\tto display|hide the logs messages"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.addnstr(6, 2, _("'m'\tto sort processes by process size"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.addnstr(7, 2, _("'n'\tto disable|enable the network interfaces stats"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.addnstr(8, 2, _("'q'\tto exit Glances"), self.term_w-self.help_x-4, self.help_color if self.hascolors else 0) - helpWindow.box() - - - def showHelp(self): - """ - Show the help panel - """ - self.panel_help.show() - - - def hideHelp(self): - """ - Hide the help panel - """ - self.panel_help.hide() - - def displayHost(self, host): # Host information if (not host): @@ -931,14 +924,17 @@ class glancesScreen(): def displayCpu(self, cpu): # CPU % - if (not cpu): - return 0 screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] if ((screen_y > self.cpu_y+6) and (screen_x > self.cpu_x+18)): self.term_window.addnstr(self.cpu_y, self.cpu_x, _("Cpu"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) self.term_window.addnstr(self.cpu_y, self.cpu_x+10,"%", 8) + + if (not cpu): + self.term_window.addnstr(self.cpu_y+1, self.cpu_x, _("Compute data..."), 15) + return 0 + self.term_window.addnstr(self.cpu_y+1, self.cpu_x, _("User:"), 8) self.term_window.addnstr(self.cpu_y+2, self.cpu_x, _("Kernel:"), 8) self.term_window.addnstr(self.cpu_y+3, self.cpu_x, _("Nice:"), 8) @@ -992,7 +988,7 @@ class glancesScreen(): screen_y = self.screen.getmaxyx()[0] if ((screen_y > self.mem_y+5) and (screen_x > self.mem_x+38)): - self.term_window.addnstr(self.mem_y, self.mem_x, _("Mem MB"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) + self.term_window.addnstr(self.mem_y, self.mem_x, _("Mem B"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) self.term_window.addnstr(self.mem_y, self.mem_x+10,_("Mem"), 8) self.term_window.addnstr(self.mem_y, self.mem_x+20,_("Swap"), 8) self.term_window.addnstr(self.mem_y, self.mem_x+30,_("Real"), 8) @@ -1000,21 +996,21 @@ class glancesScreen(): self.term_window.addnstr(self.mem_y+2, self.mem_x, _("Used:"), 8) self.term_window.addnstr(self.mem_y+3, self.mem_x, _("Free:"), 8) - self.term_window.addnstr(self.mem_y+1, self.mem_x+10, str(mem['total']/1048576), 8) - self.term_window.addnstr(self.mem_y+2, self.mem_x+10, str(mem['used']/1048576), 8) - self.term_window.addnstr(self.mem_y+3, self.mem_x+10, str(mem['free']/1048576), 8) + self.term_window.addnstr(self.mem_y+1, self.mem_x+10, self.__autoUnit(mem['total']), 8) + self.term_window.addnstr(self.mem_y+2, self.mem_x+10, self.__autoUnit(mem['used']), 8) + self.term_window.addnstr(self.mem_y+3, self.mem_x+10, self.__autoUnit(mem['free']), 8) alert = self.__getMemAlert(memswap['used'], memswap['total']) logs.add(alert, "MEM swap", memswap['used']/1048576) - self.term_window.addnstr(self.mem_y+1, self.mem_x+20, str(memswap['total']/1048576), 8) - self.term_window.addnstr(self.mem_y+2, self.mem_x+20, str(memswap['used']/1048576), 8, self.__colors_list[alert]) - self.term_window.addnstr(self.mem_y+3, self.mem_x+20, str(memswap['free']/1048576), 8) + self.term_window.addnstr(self.mem_y+1, self.mem_x+20, self.__autoUnit(memswap['total']), 8) + self.term_window.addnstr(self.mem_y+2, self.mem_x+20, self.__autoUnit(memswap['used']), 8, self.__colors_list[alert]) + self.term_window.addnstr(self.mem_y+3, self.mem_x+20, self.__autoUnit(memswap['free']), 8) alert = self.__getMemAlert(mem['used']-mem['cache'], mem['total']) logs.add(alert, "MEM real", (mem['used']-mem['cache'])/1048576) self.term_window.addnstr(self.mem_y+1, self.mem_x+30, "-", 8) - self.term_window.addnstr(self.mem_y+2, self.mem_x+30, str((mem['used']-mem['cache'])/1048576), 8, self.__colors_list[alert]) - self.term_window.addnstr(self.mem_y+3, self.mem_x+30, str((mem['free']+mem['cache'])/1048576), 8) + self.term_window.addnstr(self.mem_y+2, self.mem_x+30, self.__autoUnit((mem['used']-mem['cache'])), 8, self.__colors_list[alert]) + self.term_window.addnstr(self.mem_y+3, self.mem_x+30, self.__autoUnit((mem['free']+mem['cache'])), 8) def displayNetwork(self, network): @@ -1023,8 +1019,8 @@ class glancesScreen(): Return the number of interfaces """ # Network interfaces bitrate - if (not network or not self.network_tag): - return 0 + if (not self.network_tag): + return 0 screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] if ((screen_y > self.network_y+3) @@ -1033,6 +1029,12 @@ class glancesScreen(): self.term_window.addnstr(self.network_y, self.network_x, _("Net rate"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) self.term_window.addnstr(self.network_y, self.network_x+10, _("Rx/ps"), 8) self.term_window.addnstr(self.network_y, self.network_x+20, _("Tx/ps"), 8) + + # If there is no data to display... + if (not network): + self.term_window.addnstr(self.network_y+1, self.network_x, _("Compute data..."), 15) + return 3 + # Adapt the maximum interface to the screen ret = 2 for i in range(0, min(screen_y-self.network_y-3, len(network))): @@ -1047,7 +1049,7 @@ class glancesScreen(): def displayDiskIO(self, diskio, offset_y = 0): # Disk input/output rate - if (not diskio or not self.diskio_tag): + if (not self.diskio_tag): return 0 screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] @@ -1057,6 +1059,12 @@ class glancesScreen(): self.term_window.addnstr(self.diskio_y, self.diskio_x, _("Disk I/O"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) self.term_window.addnstr(self.diskio_y, self.diskio_x+10, _("In/ps"), 8) self.term_window.addnstr(self.diskio_y, self.diskio_x+20, _("Out/ps"), 8) + + # If there is no data to display... + if (not diskio): + self.term_window.addnstr(self.diskio_y+1, self.diskio_x, _("Compute data..."), 15) + return 3 + # Adapt the maximum disk to the screen disk = 0 for disk in range(0, min(screen_y-self.diskio_y-3, len(diskio))): @@ -1077,15 +1085,15 @@ class glancesScreen(): self.fs_y = offset_y if ((screen_y > self.fs_y+3) and (screen_x > self.fs_x+28)): - self.term_window.addnstr(self.fs_y, self.fs_x, _("Mount"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) + self.term_window.addnstr(self.fs_y, self.fs_x, _("Mount B"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) self.term_window.addnstr(self.fs_y, self.fs_x+10, _("Total"), 8) self.term_window.addnstr(self.fs_y, self.fs_x+20, _("Used"), 8) # Adapt the maximum disk to the screen mounted = 0 for mounted in range(0, min(screen_y-self.fs_y-3, len(fs))): self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x, fs[mounted]['mnt_point'], 8) - self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+10, self.__autoUnit(fs[mounted]['size']) + "B", 8) - self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+20, self.__autoUnit(fs[mounted]['used']) + "B", 8, self.__getFsColor(fs[mounted]['used'], fs[mounted]['size'])) + self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+10, self.__autoUnit(fs[mounted]['size']), 8) + self.term_window.addnstr(self.fs_y+1+mounted, self.fs_x+20, self.__autoUnit(fs[mounted]['used']), 8, self.__getFsColor(fs[mounted]['used'], fs[mounted]['size'])) return mounted+3 return 0 @@ -1152,27 +1160,23 @@ class glancesScreen(): self.term_window.addnstr(self.process_y+1, process_x+30,str(processcount['sleeping']), 8) self.term_window.addnstr(self.process_y+1, process_x+40,str(processcount['stopped']+stats.getProcessCount()['zombie']), 8) # Display the process detail - if (not processlist): - return 3 if ((screen_y > self.process_y+6) and (screen_x > process_x+49)): # Processes detail - if (self.getProcessSortedBy() == 'cpu_percent'): - sortchar = '^' - else: - sortchar = ' ' - self.term_window.addnstr(self.process_y+3, process_x,"Cpu %"+sortchar, 8) - if (self.getProcessSortedBy() == 'proc_size'): - sortchar = '^' - else: - sortchar = ' ' - self.term_window.addnstr(self.process_y+3, process_x+10,_("Size MB")+sortchar, 8) - self.term_window.addnstr(self.process_y+3, process_x+20,_("Res MB"), 8) - self.term_window.addnstr(self.process_y+3, process_x+30,_("Name"), 8) + self.term_window.addnstr(self.process_y+3, process_x, _("Cpu %"), 5, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'cpu_percent') else 0) + self.term_window.addnstr(self.process_y+3, process_x+7, _("Mem virt."), 9, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'proc_size') else 0) + self.term_window.addnstr(self.process_y+3, process_x+18, _("Mem resi."), 9) + self.term_window.addnstr(self.process_y+3, process_x+30, _("Process name"), 12, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'process_name') else 0) + + # If there is no data to display... + if (not processlist): + self.term_window.addnstr(self.process_y+4, self.process_x, _("Compute data..."), 15) + return 6 + for processes in range(0, min(screen_y-self.term_h+self.process_y-log_count, len(processlist))): - self.term_window.addnstr(self.process_y+4+processes, process_x, "%.1f" % processlist[processes]['cpu_percent'], 8, self.__getColor(processlist[processes]['cpu_percent'])) - self.term_window.addnstr(self.process_y+4+processes, process_x+10, str((processlist[processes]['proc_size'])/1048576), 8) - self.term_window.addnstr(self.process_y+4+processes, process_x+20, str((processlist[processes]['proc_resident'])/1048576), 8) + self.term_window.addnstr(self.process_y+4+processes, process_x, "%.1f" % processlist[processes]['cpu_percent'], 8, self.__getProcessColor(processlist[processes]['cpu_percent'])) + self.term_window.addnstr(self.process_y+4+processes, process_x+7, self.__autoUnit(processlist[processes]['proc_size']), 9) + self.term_window.addnstr(self.process_y+4+processes, process_x+18, self.__autoUnit(processlist[processes]['proc_resident']), 9) maxprocessname = screen_x-process_x-30 # If screen space is available then display long name if ((len(processlist[processes]['proctitle']) > maxprocessname) @@ -1187,13 +1191,47 @@ class glancesScreen(): # Caption screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] + if ((screen_x < 80) or (screen_y < 24)): + # Help can only be displayed on a 80x24 console + return 0 if ((screen_y > self.caption_y) and (screen_x > self.caption_x+32)): - self.term_window.addnstr(max(self.caption_y, screen_y-1), self.caption_x, _(" OK "), 8, self.default_color) - self.term_window.addnstr(max(self.caption_y, screen_y-1), self.caption_x+8, _("CAREFUL "), 8, self.ifCAREFUL_color) - self.term_window.addnstr(max(self.caption_y, screen_y-1), self.caption_x+16, _("WARNING "), 8, self.ifWARNING_color) - self.term_window.addnstr(max(self.caption_y, screen_y-1), self.caption_x+24, _("CRITICAL"), 8, self.ifCRITICAL_color) + self.term_window.addnstr(max(self.caption_y, screen_y-1), self.caption_x, _("Press 'h' for help"), self.default_color) + + def displayHelp(self): + """ + Show the help panel + """ + if (not self.help_tag): + return 0 + screen_x = self.screen.getmaxyx()[1] + screen_y = self.screen.getmaxyx()[0] + if ((screen_y > self.help_y+23) + and (screen_x > self.help_x+79)): + # Console 80x24 is mandatory to display teh help message + self.erase() + + self.term_window.addnstr(self.help_y, self.help_x, _("Glances v")+self.__version+_(" user guide"), 79, self.title_color if self.hascolors else 0) + + self.term_window.addnstr(self.help_y+2, self.help_x, _("Captions: "), 79) + self.term_window.addnstr(self.help_y+2, self.help_x+10, _(" OK "), 8, self.default_color) + self.term_window.addnstr(self.help_y+2, self.help_x+18, _("CAREFUL "), 8, self.ifCAREFUL_color) + self.term_window.addnstr(self.help_y+2, self.help_x+26, _("WARNING "), 8, self.ifWARNING_color) + self.term_window.addnstr(self.help_y+2, self.help_x+34, _("CRITICAL"), 8, self.ifCRITICAL_color) + + self.term_window.addnstr(self.help_y+4 , self.help_x, _("Key") + "\t" + _("Function"), 79, self.title_color if self.hascolors else 0) + self.term_window.addnstr(self.help_y+5 , self.help_x, _("a") + "\t" + _("Sort process list automaticaly"), 79) + self.term_window.addnstr(self.help_y+6 , self.help_x, _("c") + "\t" + _("Sort process list by CPU usage"), 79) + self.term_window.addnstr(self.help_y+7 , self.help_x, _("m") + "\t" + _("Sort process list by virtual memory usage"), 79) + self.term_window.addnstr(self.help_y+8 , self.help_x, _("p") + "\t" + _("Sort process list by name"), 79) + self.term_window.addnstr(self.help_y+9 , self.help_x, _("d") + "\t" + _("Enable/Disable disk IO stats"), 79) + self.term_window.addnstr(self.help_y+10, self.help_x, _("f") + "\t" + _("Enable/Disable file system stats"), 79) + self.term_window.addnstr(self.help_y+11, self.help_x, _("n") + "\t" + _("Enable/Disable network stats"), 79) + self.term_window.addnstr(self.help_y+12, self.help_x, _("l") + "\t" + _("Enable/Disable log list (only available if display > 24 lines)"), 79) + self.term_window.addnstr(self.help_y+13, self.help_x, _("h") + "\t" + _("Display/Hide help message"), 79) + self.term_window.addnstr(self.help_y+14, self.help_x, _("q") + "\t" + _("Exit from Glances (ESC key and CRTL-C also work...)"), 79) + def displayNow(self, now): # Display the current date and time (now...) - Center @@ -1303,6 +1341,7 @@ def end(): def signal_handler(signal, frame): end() + # Main #===== From 274516f262d8a85c00f70c71dc61449d408f4cab Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 12 Feb 2012 14:13:32 +0100 Subject: [PATCH 06/57] Ready for beta test --- NEWS | 2 +- src/glances.py | 35 +++++++---------------------------- 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/NEWS b/NEWS index 567e378c..50d8b695 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,7 @@ Version 1.4 =========== - * No more StatGrab, welcome to the PsUtil library ! + * Goodby StatGrab... Welcome to the PsUtil library ! * Sort by Process name ('p' key) * Only major stats (CPU, Load and memory) use background colors * Improve operating system name diff --git a/src/glances.py b/src/glances.py index 19fce56f..e9a520d8 100755 --- a/src/glances.py +++ b/src/glances.py @@ -34,12 +34,11 @@ try: except KeyboardInterrupt: pass - # i18n #===== application = 'glances' -__version__ = "1.4a" +__version__ = "1.4b" gettext.install(application) try: @@ -63,20 +62,12 @@ except: print sys.exit(1) -# TODO: Remove after test PsUtil psutil.__version__ - -try: - import statgrab -except: - print _('Statgrab initialization failed, Glances cannot start.') - print - sys.exit(1) - try: import curses import curses.panel except: print _('Textmode GUI initialization failed, Glances cannot start.') + print _('Use Python 2.6 or higher') print sys.exit(1) @@ -275,18 +266,14 @@ class glancesGrabFs(): class glancesStats(): """ - This class store, update and give the libstatgrab stats + This class store, update and give stats """ def __init__(self): """ - Init the libstatgrab and process to the first update + Init the stats """ - - # Init libstatgrab - if not statgrab.sg_init(): - print _("Error: Can not init the libstatgrab library.\n") - + # Init the fs stats try: self.glancesgrabfs = glancesGrabFs() @@ -298,8 +285,6 @@ class glancesStats(): """ Update the stats """ - - # Get system informations # Host and OS informations self.host = {} @@ -310,7 +295,7 @@ class glancesStats(): try: if (self.host['os_name'] == "Linux" or self.host['os_name'] == "FreeBSD"): os_version = platform.linux_distribution() - self.host['os_version'] = os_version[0]+" "+os_version[1]+" ("+os_version[2]+")" + self.host['os_version'] = os_version[0]+" "+os_version[2]+" "+os_version[1] elif (self.host['os_name'] == "Windows"): os_version = platform.win32_ver() self.host['os_version'] = os_version[0]+" "+os_version[2] @@ -481,12 +466,7 @@ class glancesStats(): # Get the number of core (CPU) (Used to display load alerts) self.core_number = psutil.NUM_CPUS - - def end(self): - # Shutdown the libstatgrab - statgrab.sg_shutdown() - def update(self): # Update the stats @@ -917,7 +897,7 @@ class glancesScreen(): screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] if ((screen_y > self.system_y) - and (screen_x > self.system_x+79)): + and (screen_x > self.system_x+79)): system_msg = system['os_name']+" "+system['platform']+" "+system['os_version'] self.term_window.addnstr(self.system_y, self.system_x+int(screen_x/2)-len(system_msg)/2, system_msg, 80) @@ -1332,7 +1312,6 @@ def main(): def end(): - stats.end() screen.end() sys.exit(0) From 1e0a303efb7c6c4aa64f6cfbabf8e80813dbf781 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 12 Feb 2012 16:41:01 +0100 Subject: [PATCH 07/57] Remove the unused get_io_counters calls --- src/glances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/glances.py b/src/glances.py index e9a520d8..8352b644 100755 --- a/src/glances.py +++ b/src/glances.py @@ -38,7 +38,7 @@ except KeyboardInterrupt: #===== application = 'glances' -__version__ = "1.4b" +__version__ = "1.4b2" gettext.install(application) try: @@ -454,8 +454,8 @@ class glancesStats(): procstat['proc_size'] = proc.get_memory_info().vms procstat['proc_resident'] = proc.get_memory_info().rss procstat['cpu_percent'] = proc._before.get_cpu_percent(interval=0) - procstat['diskio_read'] = proc.get_io_counters().read_bytes - proc._before.get_io_counters().read_bytes - procstat['diskio_write'] = proc.get_io_counters().write_bytes - proc._before.get_io_counters().write_bytes + #~ procstat['diskio_read'] = proc.get_io_counters().read_bytes - proc._before.get_io_counters().read_bytes + #~ procstat['diskio_write'] = proc.get_io_counters().write_bytes - proc._before.get_io_counters().write_bytes self.process.append(procstat) except: pass From c9f5c9b518d2399a8b19d4edd09e22ad1376ec61 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 13 Feb 2012 11:17:49 +0100 Subject: [PATCH 08/57] Change the process list algorithm --- src/glances.py | 144 +++++++++++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 52 deletions(-) diff --git a/src/glances.py b/src/glances.py index 8352b644..c1ccac2a 100755 --- a/src/glances.py +++ b/src/glances.py @@ -38,7 +38,7 @@ except KeyboardInterrupt: #===== application = 'glances' -__version__ = "1.4b2" +__version__ = "1.4b3" gettext.install(application) try: @@ -279,7 +279,7 @@ class glancesStats(): self.glancesgrabfs = glancesGrabFs() except: self.glancesgrabfs = {} - + def __update__(self): """ @@ -407,45 +407,30 @@ class glancesStats(): self.fs = {} # PROCESS - # Initialiation of the processes list - self.processcount = {'zombie': 0, 'running': 0, 'total': 0, 'stopped': 0, 'sleeping': 0, 'disk sleep': 0} - try: - self.process - except: - self.process = [] - + # Initialiation of the running processes list + process_first_grab = False try: self.process_all except: self.process_all = [proc for proc in psutil.process_iter()] - for proc in self.process_all[:]: + process_first_grab = True + self.process = [] + self.processcount = { 'total': 0 , 'running': 0, 'sleeping': 0 } + # Manage new processes + process_new = [proc.pid for proc in self.process_all] + for proc in psutil.process_iter(): + if proc.pid not in process_new: + self.process_all.append(proc) + # Grab stats from process list + for proc in self.process_all[:]: + if (proc.is_running()): # Global stats try: - self.processcount[str(proc.status)] - except: - pass - else: self.processcount[str(proc.status)] += 1 - self.processcount['total'] += 1 - # Per process stats - try: - # A first value is needed to compute the CPU percent - proc.get_cpu_percent(interval=0) except: - pass - else: - proc._before = proc - else: - self.process = [] - for proc in self.process_all[:]: - # Global stats - try: - self.processcount[str(proc.status)] - except: - pass - else: - self.processcount[str(proc.status)] += 1 - self.processcount['total'] += 1 + self.processcount[str(proc.status)] = 1 + finally: + self.processcount['total'] += 1 # Per process stats try: procstat = {} @@ -453,13 +438,65 @@ class glancesStats(): procstat['proctitle'] = " ".join(str(i) for i in proc.cmdline) procstat['proc_size'] = proc.get_memory_info().vms procstat['proc_resident'] = proc.get_memory_info().rss - procstat['cpu_percent'] = proc._before.get_cpu_percent(interval=0) - #~ procstat['diskio_read'] = proc.get_io_counters().read_bytes - proc._before.get_io_counters().read_bytes - #~ procstat['diskio_write'] = proc.get_io_counters().write_bytes - proc._before.get_io_counters().write_bytes + procstat['cpu_percent'] = proc.get_cpu_percent(interval=0) self.process.append(procstat) except: - pass - del(self.process_all) + pass + else: + self.process_all.remove(proc) + # If it is the first grab then empty process list + if (process_first_grab): + self.process = [] + + #~ self.processcount = {'zombie': 0, 'running': 0, 'total': 0, 'stopped': 0, 'sleeping': 0, 'disk sleep': 0} + #~ try: + #~ self.process + #~ except: + #~ self.process = [] + #~ + #~ try: + #~ self.process_all + #~ except: + #~ self.process_all = [proc for proc in psutil.process_iter()] + #~ for proc in self.process_all[:]: + #~ # Global stats + #~ try: + #~ self.processcount[str(proc.status)] + #~ except: + #~ pass + #~ else: + #~ self.processcount[str(proc.status)] += 1 + #~ self.processcount['total'] += 1 + #~ self.processcount == [] + #~ # Per process stats + #~ try: + #~ # A first value is needed to compute the CPU percent + #~ proc.get_cpu_percent(interval=0) + #~ except: + #~ pass + #~ else: + #~ self.process = [] + #~ for proc in self.process_all[:]: + #~ # Global stats + #~ try: + #~ self.processcount[str(proc.status)] + #~ except: + #~ pass + #~ else: + #~ self.processcount[str(proc.status)] += 1 + #~ self.processcount['total'] += 1 + #~ # Per process stats + #~ try: + #~ procstat = {} + #~ procstat['process_name'] = proc.name + #~ procstat['proctitle'] = " ".join(str(i) for i in proc.cmdline) + #~ procstat['proc_size'] = proc.get_memory_info().vms + #~ procstat['proc_resident'] = proc.get_memory_info().rss + #~ procstat['cpu_percent'] = proc.get_cpu_percent(interval=0) + #~ self.process.append(procstat) + #~ except: + #~ pass + #~ del(self.process_all) # Get the current date/time self.now = datetime.datetime.now() @@ -527,8 +564,12 @@ class glancesStats(): # If global Mem > 70% sort by process size # Else sort by cpu comsoption sortedby = 'cpu_percent' - if ( self.mem['total'] != 0): - if ( ( (self.mem['used'] - self.mem['cache']) * 100 / self.mem['total']) > limits.getSTDWarning()): + try: + memtotal = (self.mem['used'] - self.mem['cache']) * 100 / self.mem['total'] + except: + pass + else: + if (memtotal > limits.getSTDWarning()): sortedby = 'proc_size' elif sortedby == 'process_name': sortedReverse = False @@ -561,10 +602,10 @@ class glancesScreen(): self.cpu_x = 0 ; self.cpu_y = 3 self.load_x = 20; self.load_y = 3 self.mem_x = 41; self.mem_y = 3 - self.network_x = 0 ; self.network_y = 9 + self.network_x = 0 ; self.network_y = 8 self.diskio_x = 0 ; self.diskio_y = -1 self.fs_x = 0 ; self.fs_y = -1 - self.process_x = 30; self.process_y = 9 + self.process_x = 30; self.process_y = 8 self.log_x = 0 ; self.log_y = -1 self.help_x = 0; self.help_y = 0 self.now_x = 79; self.now_y = 3 @@ -906,19 +947,19 @@ class glancesScreen(): # CPU % screen_x = self.screen.getmaxyx()[1] screen_y = self.screen.getmaxyx()[0] - if ((screen_y > self.cpu_y+6) + if ((screen_y > self.cpu_y+5) and (screen_x > self.cpu_x+18)): self.term_window.addnstr(self.cpu_y, self.cpu_x, _("Cpu"), 8, self.title_color if self.hascolors else curses.A_UNDERLINE) - self.term_window.addnstr(self.cpu_y, self.cpu_x+10,"%", 8) if (not cpu): self.term_window.addnstr(self.cpu_y+1, self.cpu_x, _("Compute data..."), 15) return 0 - + + self.term_window.addnstr(self.cpu_y, self.cpu_x+10, "%.1f%%" % (100-cpu['idle']), 8) self.term_window.addnstr(self.cpu_y+1, self.cpu_x, _("User:"), 8) self.term_window.addnstr(self.cpu_y+2, self.cpu_x, _("Kernel:"), 8) self.term_window.addnstr(self.cpu_y+3, self.cpu_x, _("Nice:"), 8) - self.term_window.addnstr(self.cpu_y+4, self.cpu_x, _("Idle:"), 8) + #~ self.term_window.addnstr(self.cpu_y+4, self.cpu_x, _("Idle:"), 8) alert = self.__getCpuAlert(cpu['user']) logs.add(alert, "CPU user", cpu['user']) @@ -932,7 +973,7 @@ class glancesScreen(): logs.add(alert, "CPU nice", cpu['nice']) self.term_window.addnstr(self.cpu_y+3, self.cpu_x+10, "%.1f" % cpu['nice'], 8, self.__colors_list[alert]) - self.term_window.addnstr(self.cpu_y+4, self.cpu_x+10, "%.1f" % cpu['idle'], 8) + #~ self.term_window.addnstr(self.cpu_y+4, self.cpu_x+10, "%.1f" % cpu['idle'], 8) def displayLoad(self, load, core): @@ -1138,9 +1179,9 @@ class glancesScreen(): self.term_window.addnstr(self.process_y+1, process_x+10,str(processcount['total']), 8) self.term_window.addnstr(self.process_y+1, process_x+20,str(processcount['running']), 8) self.term_window.addnstr(self.process_y+1, process_x+30,str(processcount['sleeping']), 8) - self.term_window.addnstr(self.process_y+1, process_x+40,str(processcount['stopped']+stats.getProcessCount()['zombie']), 8) + self.term_window.addnstr(self.process_y+1, process_x+40,str(processcount['total']-stats.getProcessCount()['running']-stats.getProcessCount()['sleeping']), 8) # Display the process detail - if ((screen_y > self.process_y+6) + if ((screen_y > self.process_y+7) and (screen_x > process_x+49)): # Processes detail self.term_window.addnstr(self.process_y+3, process_x, _("Cpu %"), 5, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'cpu_percent') else 0) @@ -1153,7 +1194,7 @@ class glancesScreen(): self.term_window.addnstr(self.process_y+4, self.process_x, _("Compute data..."), 15) return 6 - for processes in range(0, min(screen_y-self.term_h+self.process_y-log_count, len(processlist))): + for processes in range(0, min(screen_y-self.term_h+self.process_y-log_count+2, len(processlist))): self.term_window.addnstr(self.process_y+4+processes, process_x, "%.1f" % processlist[processes]['cpu_percent'], 8, self.__getProcessColor(processlist[processes]['cpu_percent'])) self.term_window.addnstr(self.process_y+4+processes, process_x+7, self.__autoUnit(processlist[processes]['proc_size']), 9) self.term_window.addnstr(self.process_y+4+processes, process_x+18, self.__autoUnit(processlist[processes]['proc_resident']), 9) @@ -1297,8 +1338,7 @@ def init(): screen = glancesScreen(refresh_time) -def main(): - +def main(): # Init stuff init() From 9de0daf9902f716edd32026f8cae44e30b0c7257 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 13 Feb 2012 14:53:00 +0100 Subject: [PATCH 09/57] Verbose the help message depending of the PsUtil version --- src/glances.py | 200 ++++++++++++++++++++++++------------------------- 1 file changed, 96 insertions(+), 104 deletions(-) diff --git a/src/glances.py b/src/glances.py index c1ccac2a..f2a2f8c5 100755 --- a/src/glances.py +++ b/src/glances.py @@ -19,6 +19,9 @@ # along with this program. If not, see ."; # +# Libraries +#========== + from __future__ import generators try: @@ -34,12 +37,14 @@ try: except KeyboardInterrupt: pass -# i18n -#===== - -application = 'glances' -__version__ = "1.4b3" -gettext.install(application) +try: + import curses + import curses.panel +except: + print _('Textmode GUI initialization failed, Glances cannot start.') + print _('Use Python 2.6 or higher') + print + sys.exit(1) try: import psutil @@ -52,25 +57,53 @@ except: sys.exit(1) try: - # This function is available in the 0.4.0+ version of PsUtil - psutil.disk_io_counters() + # get_cpu_percent method only available with PsUtil 0.2.0 or + + psutil.Process(os.getpid()).get_cpu_percent(interval=0) except: - print _('PsUtil version 0.4.0 or higher is needed.') - print _('On Debian/Ubuntu, you can try (as root):') - print _('# apt-get install python-dev python-pip') - print _('# pip install --upgrade psutil') - print - sys.exit(1) + psutil_get_cpu_percent_tag = False +else: + psutil_get_cpu_percent_tag = True + +try: + # (phy|virt)mem_usage methods only available with PsUtil 0.3.0 or + + psutil.phymem_usage() + psutil.virtmem_usage() +except: + psutil_mem_usage_tag = False +else: + psutil_mem_usage_tag = True try: - import curses - import curses.panel + # disk_(partitions|usage) methods only available with PsUtil 0.3.0 or + + psutil.disk_partitions() + psutil.disk_usage('/') except: - print _('Textmode GUI initialization failed, Glances cannot start.') - print _('Use Python 2.6 or higher') - print - sys.exit(1) + psutil_fs_usage_tag = False +else: + psutil_fs_usage_tag = True +try: + # disk_io_counters method only available with PsUtil 0.4.0 or + + psutil.disk_io_counters() +except: + psutil_disk_io_tag = False +else: + psutil_disk_io_tag = True + +try: + # network_io_counters method only available with PsUtil 0.4.0 or + + psutil.network_io_counters() +except: + psutil_network_io_tag = False +else: + psutil_network_io_tag = True + +# Application informations +#========================= + +application = 'glances' +__version__ = "1.4b4" +gettext.install(application) # Classes #======== @@ -364,7 +397,8 @@ class glancesStats(): try: self.network_old except: - self.network_old = psutil.network_io_counters(True) + if (psutil_network_io_tag): + self.network_old = psutil.network_io_counters(True) self.network = [] else: try: @@ -384,7 +418,8 @@ class glancesStats(): try: self.diskio_old except: - self.diskio_old = psutil.disk_io_counters(True) + if (psutil_disk_io_tag): + self.diskio_old = psutil.disk_io_counters(True) self.diskio = [] else: try: @@ -438,7 +473,8 @@ class glancesStats(): procstat['proctitle'] = " ".join(str(i) for i in proc.cmdline) procstat['proc_size'] = proc.get_memory_info().vms procstat['proc_resident'] = proc.get_memory_info().rss - procstat['cpu_percent'] = proc.get_cpu_percent(interval=0) + if (psutil_get_cpu_percent_tag): + procstat['cpu_percent'] = proc.get_cpu_percent(interval=0) self.process.append(procstat) except: pass @@ -448,56 +484,6 @@ class glancesStats(): if (process_first_grab): self.process = [] - #~ self.processcount = {'zombie': 0, 'running': 0, 'total': 0, 'stopped': 0, 'sleeping': 0, 'disk sleep': 0} - #~ try: - #~ self.process - #~ except: - #~ self.process = [] - #~ - #~ try: - #~ self.process_all - #~ except: - #~ self.process_all = [proc for proc in psutil.process_iter()] - #~ for proc in self.process_all[:]: - #~ # Global stats - #~ try: - #~ self.processcount[str(proc.status)] - #~ except: - #~ pass - #~ else: - #~ self.processcount[str(proc.status)] += 1 - #~ self.processcount['total'] += 1 - #~ self.processcount == [] - #~ # Per process stats - #~ try: - #~ # A first value is needed to compute the CPU percent - #~ proc.get_cpu_percent(interval=0) - #~ except: - #~ pass - #~ else: - #~ self.process = [] - #~ for proc in self.process_all[:]: - #~ # Global stats - #~ try: - #~ self.processcount[str(proc.status)] - #~ except: - #~ pass - #~ else: - #~ self.processcount[str(proc.status)] += 1 - #~ self.processcount['total'] += 1 - #~ # Per process stats - #~ try: - #~ procstat = {} - #~ procstat['process_name'] = proc.name - #~ procstat['proctitle'] = " ".join(str(i) for i in proc.cmdline) - #~ procstat['proc_size'] = proc.get_memory_info().vms - #~ procstat['proc_resident'] = proc.get_memory_info().rss - #~ procstat['cpu_percent'] = proc.get_cpu_percent(interval=0) - #~ self.process.append(procstat) - #~ except: - #~ pass - #~ del(self.process_all) - # Get the current date/time self.now = datetime.datetime.now() @@ -561,9 +547,13 @@ class glancesStats(): sortedReverse = True if sortedby == 'auto': + if (psutil_get_cpu_percent_tag): + sortedby = 'cpu_percent' + else: + sortedby = 'proc_size' + # Auto selection # If global Mem > 70% sort by process size # Else sort by cpu comsoption - sortedby = 'cpu_percent' try: memtotal = (self.mem['used'] - self.mem['cache']) * 100 / self.mem['total'] except: @@ -688,10 +678,10 @@ class glancesScreen(): 'CRITICAL': self.ifCRITICAL_color2 } - # By default all the stats are displayed - self.network_tag = True - self.diskio_tag = True - self.fs_tag = True + # What are we going to display + self.network_tag = psutil_network_io_tag + self.diskio_tag = psutil_disk_io_tag + self.fs_tag = psutil_fs_usage_tag self.log_tag = True self.help_tag = False @@ -833,18 +823,16 @@ class glancesScreen(): if (self.pressedkey == 27) or (self.pressedkey == 113): # 'ESC'|'q' > Exit end() - #elif (self.pressedkey == curses.KEY_RESIZE): - # Resize event elif (self.pressedkey == 97): # 'a' > Sort process list automaticaly self.setProcessSortedBy('auto') - elif (self.pressedkey == 99): + elif ((self.pressedkey == 99) and psutil_get_cpu_percent_tag): # 'c' > Sort process list by Cpu usage self.setProcessSortedBy('cpu_percent') - elif (self.pressedkey == 100): + elif ((self.pressedkey == 100) and psutil_disk_io_tag): # 'n' > Enable/Disable diskio stats self.diskio_tag = not self.diskio_tag - elif (self.pressedkey == 102): + elif ((self.pressedkey == 102) and psutil_fs_usage_tag): # 'n' > Enable/Disable fs stats self.fs_tag = not self.fs_tag elif (self.pressedkey == 104): @@ -856,7 +844,7 @@ class glancesScreen(): elif (self.pressedkey == 109): # 'm' > Sort process list by Mem usage self.setProcessSortedBy('proc_size') - elif (self.pressedkey == 110): + elif ((self.pressedkey == 110) and psutil_network_io_tag): # 'n' > Enable/Disable network stats self.network_tag = not self.network_tag elif (self.pressedkey == 112): @@ -973,8 +961,6 @@ class glancesScreen(): logs.add(alert, "CPU nice", cpu['nice']) self.term_window.addnstr(self.cpu_y+3, self.cpu_x+10, "%.1f" % cpu['nice'], 8, self.__colors_list[alert]) - #~ self.term_window.addnstr(self.cpu_y+4, self.cpu_x+10, "%.1f" % cpu['idle'], 8) - def displayLoad(self, load, core): # Load % @@ -1184,7 +1170,7 @@ class glancesScreen(): if ((screen_y > self.process_y+7) and (screen_x > process_x+49)): # Processes detail - self.term_window.addnstr(self.process_y+3, process_x, _("Cpu %"), 5, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'cpu_percent') else 0) + self.term_window.addnstr(self.process_y+3, process_x, _("Cpu %"), 5, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'cpu_percent') else 0) self.term_window.addnstr(self.process_y+3, process_x+7, _("Mem virt."), 9, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'proc_size') else 0) self.term_window.addnstr(self.process_y+3, process_x+18, _("Mem resi."), 9) self.term_window.addnstr(self.process_y+3, process_x+30, _("Process name"), 12, curses.A_UNDERLINE if (self.getProcessSortedBy() == 'process_name') else 0) @@ -1195,7 +1181,10 @@ class glancesScreen(): return 6 for processes in range(0, min(screen_y-self.term_h+self.process_y-log_count+2, len(processlist))): - self.term_window.addnstr(self.process_y+4+processes, process_x, "%.1f" % processlist[processes]['cpu_percent'], 8, self.__getProcessColor(processlist[processes]['cpu_percent'])) + if (psutil_get_cpu_percent_tag): + self.term_window.addnstr(self.process_y+4+processes, process_x, "%.1f" % processlist[processes]['cpu_percent'], 8, self.__getProcessColor(processlist[processes]['cpu_percent'])) + else: + self.term_window.addnstr(self.process_y+4+processes, process_x, "N/A", 8) self.term_window.addnstr(self.process_y+4+processes, process_x+7, self.__autoUnit(processlist[processes]['proc_size']), 9) self.term_window.addnstr(self.process_y+4+processes, process_x+18, self.__autoUnit(processlist[processes]['proc_resident']), 9) maxprocessname = screen_x-process_x-30 @@ -1235,23 +1224,25 @@ class glancesScreen(): self.term_window.addnstr(self.help_y, self.help_x, _("Glances v")+self.__version+_(" user guide"), 79, self.title_color if self.hascolors else 0) - self.term_window.addnstr(self.help_y+2, self.help_x, _("Captions: "), 79) - self.term_window.addnstr(self.help_y+2, self.help_x+10, _(" OK "), 8, self.default_color) - self.term_window.addnstr(self.help_y+2, self.help_x+18, _("CAREFUL "), 8, self.ifCAREFUL_color) - self.term_window.addnstr(self.help_y+2, self.help_x+26, _("WARNING "), 8, self.ifWARNING_color) - self.term_window.addnstr(self.help_y+2, self.help_x+34, _("CRITICAL"), 8, self.ifCRITICAL_color) + self.term_window.addnstr(self.help_y+2, self.help_x, _("PsUtil version: ") + psutil.__version__, 79) - self.term_window.addnstr(self.help_y+4 , self.help_x, _("Key") + "\t" + _("Function"), 79, self.title_color if self.hascolors else 0) - self.term_window.addnstr(self.help_y+5 , self.help_x, _("a") + "\t" + _("Sort process list automaticaly"), 79) - self.term_window.addnstr(self.help_y+6 , self.help_x, _("c") + "\t" + _("Sort process list by CPU usage"), 79) - self.term_window.addnstr(self.help_y+7 , self.help_x, _("m") + "\t" + _("Sort process list by virtual memory usage"), 79) - self.term_window.addnstr(self.help_y+8 , self.help_x, _("p") + "\t" + _("Sort process list by name"), 79) - self.term_window.addnstr(self.help_y+9 , self.help_x, _("d") + "\t" + _("Enable/Disable disk IO stats"), 79) - self.term_window.addnstr(self.help_y+10, self.help_x, _("f") + "\t" + _("Enable/Disable file system stats"), 79) - self.term_window.addnstr(self.help_y+11, self.help_x, _("n") + "\t" + _("Enable/Disable network stats"), 79) - self.term_window.addnstr(self.help_y+12, self.help_x, _("l") + "\t" + _("Enable/Disable log list (only available if display > 24 lines)"), 79) - self.term_window.addnstr(self.help_y+13, self.help_x, _("h") + "\t" + _("Display/Hide help message"), 79) - self.term_window.addnstr(self.help_y+14, self.help_x, _("q") + "\t" + _("Exit from Glances (ESC key and CRTL-C also work...)"), 79) + self.term_window.addnstr(self.help_y+4, self.help_x, _("Captions: "), 79) + self.term_window.addnstr(self.help_y+4, self.help_x+10, _(" OK "), 8, self.default_color) + self.term_window.addnstr(self.help_y+4, self.help_x+18, _("CAREFUL "), 8, self.ifCAREFUL_color) + self.term_window.addnstr(self.help_y+4, self.help_x+26, _("WARNING "), 8, self.ifWARNING_color) + self.term_window.addnstr(self.help_y+4, self.help_x+34, _("CRITICAL"), 8, self.ifCRITICAL_color) + + self.term_window.addnstr(self.help_y+6 , self.help_x, _("Key") + "\t" + _("Function"), 79, self.title_color if self.hascolors else 0) + self.term_window.addnstr(self.help_y+7 , self.help_x, _("a") + "\t" + _("Sort process list automaticaly (need PsUtil v0.2.0 or higher)"), 79, self.ifCRITICAL_color2 if not psutil_get_cpu_percent_tag else 0) + self.term_window.addnstr(self.help_y+8 , self.help_x, _("c") + "\t" + _("Sort process list by CPU usage (need PsUtil v0.2.0 or higher)"), 79, self.ifCRITICAL_color2 if not psutil_get_cpu_percent_tag else 0) + self.term_window.addnstr(self.help_y+9 , self.help_x, _("m") + "\t" + _("Sort process list by virtual memory usage"), 79) + self.term_window.addnstr(self.help_y+10, self.help_x, _("p") + "\t" + _("Sort process list by name"), 79) + self.term_window.addnstr(self.help_y+11, self.help_x, _("d") + "\t" + _("Enable/Disable disk IO stats (need PsUtil v0.4.0 or higher)"), 79, self.ifCRITICAL_color2 if not psutil_disk_io_tag else 0) + self.term_window.addnstr(self.help_y+12, self.help_x, _("f") + "\t" + _("Enable/Disable file system stats (need PsUtil v0.3.0 or higher)"), 79, self.ifCRITICAL_color2 if not psutil_fs_usage_tag else 0) + self.term_window.addnstr(self.help_y+13, self.help_x, _("n") + "\t" + _("Enable/Disable network stats (need PsUtil v0.3.0 or higher)"), 79, self.ifCRITICAL_color2 if not psutil_network_io_tag else 0) + self.term_window.addnstr(self.help_y+14, self.help_x, _("l") + "\t" + _("Enable/Disable log list (only available if display > 24 lines)"), 79) + self.term_window.addnstr(self.help_y+15, self.help_x, _("h") + "\t" + _("Display/Hide help message"), 79) + self.term_window.addnstr(self.help_y+16, self.help_x, _("q") + "\t" + _("Exit from Glances (ESC key and CRTL-C also work...)"), 79) def displayNow(self, now): @@ -1290,6 +1281,7 @@ def printSyntax(): print _("'l' to hide or show the logs messages") print _("'m' to sort the processes list by process size") print _("'n' to disable or enable the network interfaces stats") + print _("'p' to sort the processes list by name") print _("'q' to exit") print "" From 745434e4c7d0ce89399ee7b22b5ce1e259551659 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 14 Feb 2012 11:58:04 +0100 Subject: [PATCH 10/57] Solve the _() error message --- README | 48 +++++----- README.md | 253 ++++++++++++++++++++++++++++++++++++++++++++++++- buildout.cfg | 22 ++--- configure.ac | 8 +- setup.py | 6 +- src/glances.py | 17 ++-- 6 files changed, 303 insertions(+), 51 deletions(-) mode change 120000 => 100644 README.md diff --git a/README b/README index 3ccb7acc..618cdb5d 100644 --- a/README +++ b/README @@ -6,29 +6,41 @@ Glances -- Eye on your system ## Description -Glances is a CLI curses based monitoring tool for GNU/Linux or BSD OS. +Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. -Glances uses the libstatgrab library to get information from your system. -It is developed in Python and uses the python-statgrab lib. +Glances uses the PsUtil library to get information from your system. +It is developed in Python. ![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) ## Installation -### From package manager +### From package manager (very easy way) -Packages exist for Arch, Fedora, Redhat ... +Packages exist for Arch, Fedora, Redhat, Ubuntu (with PPA), FreeBSD... + +### From PyPi (easy way) + +PyPi is an official Python package manager. + +You first need to install pypi on your system. For exemple on Debian/Ubuntu: + + $ sudo apt-get install python-pip + +Then install the latest Glances version: + + $ sudo pip install glances ### From source Get the latest version: - $ wget https://github.com/downloads/nicolargo/glances/glances-1.3.7.tar.gz + $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz Glances use a standard GNU style installer: - $ tar zxvf glances-1.3.7.tar.gz - $ cd glances-1.3.7 + $ tar zxvf glances-1.4.tar.gz + $ cd glances-1.4 $ ./configure $ make $ sudo make install @@ -36,22 +48,14 @@ Glances use a standard GNU style installer: Pre-requisites: * Python 2.6+ (not tested with Python 3+) -* python-statgrab 0.5+ (did NOT work with python-statgrab 0.4) +* psutil 0.4.1+ (did NOT work with psutil < 0.2 ) -Notes: For Debian. -The Debian Squeeze repos only include the python-statgrab 0.4. -You had to install the version 0.5 using the following commands: +Notes: For Debian and Ubuntu < 12.04 +The officials repos only include the psutil version 0.2.1. +You had to install the version 0.4.1 using the following commands: - $ sudo apt-get install libstatgrab-dev pkg-config python-dev make - $ wget http://ftp.uk.i-scream.org/sites/ftp.i-scream.org/pub/i-scream/pystatgrab/pystatgrab-0.5.tar.gz - $ tar zxvf pystatgrab-0.5.tar.gz - $ cd pystatgrab-0.5/ - $ ./setup.py build - $ sudo ./setup.py install - -Notes: For Ubuntu 10.04 and 10.10. -The instruction to install the version 0.5 are here: -https://github.com/nicolargo/glances/issues/5#issuecomment-3033194 + $ sudo apt-get install python-dev python-pip + $ sudo pip install psutil ## Running diff --git a/README.md b/README.md deleted file mode 120000 index 100b9382..00000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -README \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..618cdb5d --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) + +============================= +Glances -- Eye on your system +============================= + +## Description + +Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. + +Glances uses the PsUtil library to get information from your system. +It is developed in Python. + +![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) + +## Installation + +### From package manager (very easy way) + +Packages exist for Arch, Fedora, Redhat, Ubuntu (with PPA), FreeBSD... + +### From PyPi (easy way) + +PyPi is an official Python package manager. + +You first need to install pypi on your system. For exemple on Debian/Ubuntu: + + $ sudo apt-get install python-pip + +Then install the latest Glances version: + + $ sudo pip install glances + +### From source + +Get the latest version: + + $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz + +Glances use a standard GNU style installer: + + $ tar zxvf glances-1.4.tar.gz + $ cd glances-1.4 + $ ./configure + $ make + $ sudo make install + +Pre-requisites: + +* Python 2.6+ (not tested with Python 3+) +* psutil 0.4.1+ (did NOT work with psutil < 0.2 ) + +Notes: For Debian and Ubuntu < 12.04 +The officials repos only include the psutil version 0.2.1. +You had to install the version 0.4.1 using the following commands: + + $ sudo apt-get install python-dev python-pip + $ sudo pip install psutil + +## Running + +Easy: + + $ glances.py + +## User guide + +By default, stats are refreshed every second, to change this setting, you can +use the -t option. For exemple to set the refrech rate to 5 seconds: + + $ glances.py -t 5 + +Importants stats are colored: + +* GREEN: stat counter is "OK" +* BLUE: stat counter is "CAREFUL" +* MAGENTA: stat counter is "WARNING" +* RED: stat counter is "CRITICAL" + +When Glances is running, you can press: + +* 'h' to display an help message whith the keys you can press +* 'a' to set the automatic mode. The processes are sorted automatically + + If CPU > 70%, sort by process "CPU consumption" + + If MEM > 70%, sort by process "memory size" + +* 'c' to sort the processes list by CPU consumption +* 'd' Disable or enable the disk IO stats +* 'f' Disable or enable the file system stats +* 'l' Disable or enable the logs +* 'm' to sort the processes list by process size +* 'n' Disable or enable the network interfaces stats +* 'q' Exit + +### Header + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) + +The header shows the Glances version, the host name and the operating +system name, version and architecture. + +### CPU + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) + +The CPU states are shown as a percentage and for the configured refresh +time. + +If user|kernel|nice CPU is < 50%, then status is set to "OK". + +If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". + +If user|kernel|nice CPU is > 70%, then status is set to "WARNING". + +If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". + +### Load + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) + +On the Nosheep blog, Zach defines the average load: "In short it is the +average sum of the number of processes waiting in the run-queue plus the +number currently executing over 1, 5, and 15 minute time periods." + +Glances gets the number of CPU cores to adapt the alerts. With Glances, +alerts on average load are only set on 5 and 15 mins. + +If average load is < O.7*Core, then status is set to "OK". + +If average load is > O.7*Core, then status is set to "CAREFUL". + +If average load is > 1*Core, then status is set to "WARNING". + +If average load is > 5*Core, then status is set to "CRITICAL". + +### Memory + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) + +Glances uses tree columns: memory (RAM), swap and "real". + +Real used memory is: used - cache. + +Real free memory is: free + cache. + +With Glances, alerts are only set for on used swap and real memory. + +If memory is < 50%, then status is set to "OK". + +If memory is > 50%, then status is set to "CAREFUL". + +If memory is > 70%, then status is set to "WARNING". + +If memory is > 90%, then status is set to "CRITICAL". + +### Network bit rate + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) + +Glances display the network interface bit rate. The unit is adapted +dynamicaly (bits per second, Kbits per second, Mbits per second...). + +Alerts are set only if the network interface maximum speed is available. + +If bitrate is < 50%, then status is set to "OK". + +If bitrate is > 50%, then status is set to "CAREFUL". + +If bitrate is > 70%, then status is set to "WARNING". + +If bitrate is > 90%, then status is set to "CRITICAL". + +For exemple, on a 100 Mbps Ethernet interface, the warning status is set +if the bit rate is higher than 70 Mbps. + +### Disk I/O + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) + +Glances display the disk I/O throughput. The unit is adapted dynamicaly +(bytes per second, Kbytes per second, Mbytes per second...). + +There is no alert on this information. + +### Filesystem + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) + +Glances display the total and used filesytem disk space. The unit is +adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per +second...). + +Alerts are set for used disk space: + +If disk used is < 50%, then status is set to "OK". + +If disk used is > 50%, then status is set to "CAREFUL". + +If disk used is > 70%, then status is set to "WARNING". + +If disk used is > 90%, then status is set to "CRITICAL". + +### Processes + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) + +Glances displays a summary and a list of processes. + +By default (or if you hit the 'a' key) the process list is automaticaly +sorted by CPU of memory consumption. + +The number of processes in the list is adapted to the screen size. + +### Logs + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) + +A logs list is displayed in the bottom of the screen if (an only if): + +* at least one WARNING or CRITICAL alert was occured. +* space is available in the bottom of the console/terminal + +There is one line per alert with the following information: + +* start date +* end date +* alert name +* (min/avg/max) values + +### Footer + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) + +Glances displays a caption and the current time/date. + +## Localisation + +To generate french locale execute as root or sudo : +i18n_francais_generate.sh + +To generate spanish locale execute as root or sudo : +i18n_espanol_generate.sh + +## Todo + +You are welcome to contribute to this software. + +* Packaging for Debian, Ubuntu, BSD... +* Check the needed Python library in the configure.ac +* Add file system stats when the python-statgrab is corrected diff --git a/buildout.cfg b/buildout.cfg index dfdf6b0f..1a4ad910 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,7 +1,7 @@ # Using buildout to install glances (thx to Benoit !) # # Install system dependancies (debian example with python2.7 pinned from wheezy) -# $ sudo apt-get install build-essential libstatgrab-dev python2.7-dev +# $ sudo apt-get install build-essential python2.7-dev python-psutil # # Bootstrap buildout # $ mkdir glances @@ -18,12 +18,10 @@ # cleanner solution ?) # See http://guide.python-distribute.org/creation.html#entry-points - - [buildout] parts = - pystatgrab-src - pystatgrab-install + psutil-src + psutil-install glances include-site-packages = false @@ -31,18 +29,18 @@ allowed-eggs-from-site-packages = false [config] glances_version = 1.3.7 -pystatgrab_version = 0.5 -pystatgrab_download_url = http://ftp.uk.i-scream.org/sites/ftp.i-scream.org/pub/i-scream/pystatgrab +psutil_version = 0.4.1 +psutil_download_url = http://psutil.googlecode.com/files -[pystatgrab-src] +[psutil-src] recipe = hexagonit.recipe.download -url = ${config:pystatgrab_download_url}/pystatgrab-${config:pystatgrab_version}.tar.gz +url = ${config:psutil_download_url}/psutil-${config:psutil_version}.tar.gz -[pystatgrab-install] +[psutil-install] recipe= iw.recipe.cmd on_install = true cmds = - cd ${buildout:directory}/parts/pystatgrab-src/pystatgrab-${config:pystatgrab_version} + cd ${buildout:directory}/parts/psutil-src/psutil-${config:psutil_version} ${buildout:executable} setup.py install [glances] @@ -54,5 +52,5 @@ entry-points = glances=glances:main eggs = glances == ${config:glances_version} - pystatgrab == ${config:pystatgrab_version} + psutil == ${config:psutil_version} diff --git a/configure.ac b/configure.ac index c2a43009..f96434bf 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. dnl Created by Anjuta application wizard. -AC_INIT(Glances, 1.3.3, , glances) +AC_INIT(Glances, 1.4, , glances) AC_CONFIG_HEADERS([config.h]) @@ -13,11 +13,7 @@ AM_SILENT_RULES([yes]) AM_PATH_PYTHON([2.6]) -dnl AX_PYTHON_MODULE([statgrab],[needed]) - - - - +dnl AX_PYTHON_MODULE([psutil],[needed]) AC_OUTPUT([ Makefile diff --git a/setup.py b/setup.py index cdcc6434..c6f21ba8 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,8 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name='Glances', - version='1.3.7', - download_url='https://github.com/downloads/nicolargo/glances/glances-1.3.7.tar.gz', + version='1.4', + download_url='https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz', url='https://github.com/nicolargo/glances', description='CLI curses-based monitoring tool', author='Nicolas Hennion', @@ -21,5 +21,5 @@ setup( name='Glances', keywords = "cli curse monitoring system", long_description=read('README'), packages=['src'], - install_requires=['pystatgrab>=0.5'] + install_requires=['psutil>=0.4.1'] ) diff --git a/src/glances.py b/src/glances.py index f2a2f8c5..7164b2be 100755 --- a/src/glances.py +++ b/src/glances.py @@ -37,6 +37,16 @@ try: except KeyboardInterrupt: pass +# Application informations +#========================= + +application = 'glances' +__version__ = "1.4b5" +gettext.install(application) + +# Test methods +#============= + try: import curses import curses.panel @@ -98,13 +108,6 @@ except: else: psutil_network_io_tag = True -# Application informations -#========================= - -application = 'glances' -__version__ = "1.4b4" -gettext.install(application) - # Classes #======== From 171d6d5b6bc79231a24eed3ea76ada912ace5a46 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 14 Feb 2012 13:25:57 +0100 Subject: [PATCH 11/57] Add control/msg to lib import --- src/glances.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/glances.py b/src/glances.py index 7164b2be..c2695736 100755 --- a/src/glances.py +++ b/src/glances.py @@ -24,24 +24,27 @@ from __future__ import generators +import sys + try: import os import platform import getopt - import sys import signal import time import datetime import multiprocessing import gettext -except KeyboardInterrupt: - pass +except: + print "Error during Python libraries import:", sys.exc_info()[1] + sys.exit(1) + # Application informations #========================= application = 'glances' -__version__ = "1.4b5" +__version__ = "1.4b6" gettext.install(application) # Test methods From ac541c1b93563a24cb6efe57de51d9e79ac366aa Mon Sep 17 00:00:00 2001 From: nicolargo Date: Thu, 16 Feb 2012 11:16:00 +0100 Subject: [PATCH 12/57] Reduce process load --- TODO | 3 -- configure.ac | 2 +- man/glances.1 | 2 +- setup.py | 4 +- src/glances.py | 114 ++++++++++++++++++++++++++----------------------- 5 files changed, 64 insertions(+), 61 deletions(-) diff --git a/TODO b/TODO index 0531dad3..84804ad7 100644 --- a/TODO +++ b/TODO @@ -1,4 +1 @@ - Packaging for .deb (Debian|Ubuntu|Mint) Linux distributions (contributors needed) -- Test/coding for xBSD -- Add control to check the libstatgrab version in the script -- Add a limit class to manage the OK ,CAREFUL,WARNING,CRITICAL limits diff --git a/configure.ac b/configure.ac index f96434bf..8c2806c0 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. dnl Created by Anjuta application wizard. -AC_INIT(Glances, 1.4, , glances) +AC_INIT(Glances, 1.4b, , glances) AC_CONFIG_HEADERS([config.h]) diff --git a/man/glances.1 b/man/glances.1 index 5fd60e39..3b3886c9 100644 --- a/man/glances.1 +++ b/man/glances.1 @@ -9,7 +9,7 @@ Glances is a free (LGPL) curses-based monitoring tool which aims to present a m in a minimum of space, ideally to fit in a classical 80x24 terminal. Glances can adapt dynamicaly the displayed informations depending on the terminal size. .PP -This tool is written in Python and uses pystatgrab to fetch the statistical values from key elements. +This tool is written in Python and uses PsUtil to fetch the statistical values from key elements. .PP You can use the following keys to sort the processesi list: .PP diff --git a/setup.py b/setup.py index c6f21ba8..fcc77c6d 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,8 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name='Glances', - version='1.4', - download_url='https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz', + version='1.4b', + download_url='https://github.com/downloads/nicolargo/glances/glances-1.4b.tar.gz', url='https://github.com/nicolargo/glances', description='CLI curses-based monitoring tool', author='Nicolas Hennion', diff --git a/src/glances.py b/src/glances.py index c2695736..0fdf0b9d 100755 --- a/src/glances.py +++ b/src/glances.py @@ -19,11 +19,16 @@ # along with this program. If not, see ."; # +from __future__ import generators + +__appname__ = 'glances' +__version__ = "1.4b9" +__author__ = "Nicolas Hennion " +__licence__ = "LGPL" + # Libraries #========== -from __future__ import generators - import sys try: @@ -36,16 +41,14 @@ try: import multiprocessing import gettext except: - print "Error during Python libraries import:", sys.exc_info()[1] + print("Error during Python libraries import: "+str(sys.exc_info()[1])) sys.exit(1) -# Application informations -#========================= +# International +#============== -application = 'glances' -__version__ = "1.4b6" -gettext.install(application) +gettext.install(__appname__) # Test methods #============= @@ -267,8 +270,7 @@ class glancesGrabFs(): """ def __init__(self): - pass - + pass def __update__(self): """ @@ -319,6 +321,9 @@ class glancesStats(): except: self.glancesgrabfs = {} + # Process list refresh + self.process_list_refresh = True + def __update__(self): """ @@ -448,47 +453,51 @@ class glancesStats(): self.fs = {} # PROCESS - # Initialiation of the running processes list - process_first_grab = False - try: - self.process_all - except: - self.process_all = [proc for proc in psutil.process_iter()] - process_first_grab = True - self.process = [] - self.processcount = { 'total': 0 , 'running': 0, 'sleeping': 0 } - # Manage new processes - process_new = [proc.pid for proc in self.process_all] - for proc in psutil.process_iter(): - if proc.pid not in process_new: - self.process_all.append(proc) - # Grab stats from process list - for proc in self.process_all[:]: - if (proc.is_running()): - # Global stats - try: - self.processcount[str(proc.status)] += 1 - except: - self.processcount[str(proc.status)] = 1 - finally: - self.processcount['total'] += 1 - # Per process stats - try: - procstat = {} - procstat['process_name'] = proc.name - procstat['proctitle'] = " ".join(str(i) for i in proc.cmdline) - procstat['proc_size'] = proc.get_memory_info().vms - procstat['proc_resident'] = proc.get_memory_info().rss - if (psutil_get_cpu_percent_tag): - procstat['cpu_percent'] = proc.get_cpu_percent(interval=0) - self.process.append(procstat) - except: - pass - else: - self.process_all.remove(proc) - # If it is the first grab then empty process list - if (process_first_grab): + # Initialiation of the running processes list + # Data are refreshed every two cycle (refresh_time * 2) + if (self.process_list_refresh): + self.process_first_grab = False + try: + self.process_all + except: + self.process_all = [proc for proc in psutil.process_iter()] + self.process_first_grab = True self.process = [] + self.processcount = { 'total': 0 , 'running': 0, 'sleeping': 0 } + # Manage new processes + process_new = [proc.pid for proc in self.process_all] + for proc in psutil.process_iter(): + if proc.pid not in process_new: + self.process_all.append(proc) + # Grab stats from process list + for proc in self.process_all[:]: + if (proc.is_running()): + # Global stats + try: + self.processcount[str(proc.status)] += 1 + except: + self.processcount[str(proc.status)] = 1 + finally: + self.processcount['total'] += 1 + # Per process stats + try: + procstat = {} + procstat['process_name'] = proc.name + procstat['proctitle'] = " ".join(str(i) for i in proc.cmdline) + procstat['proc_size'] = proc.get_memory_info().vms + procstat['proc_resident'] = proc.get_memory_info().rss + if (psutil_get_cpu_percent_tag): + procstat['cpu_percent'] = proc.get_cpu_percent(interval=0) + self.process.append(procstat) + except: + pass + else: + self.process_all.remove(proc) + # If it is the first grab then empty process list + if (self.process_first_grab): + self.process = [] + + self.process_list_refresh = not self.process_list_refresh # Get the current date/time self.now = datetime.datetime.now() @@ -893,8 +902,7 @@ class glancesScreen(): # Flush display self.erase() self.display(stats) - #curses.panel.update_panels() - #curses.doupdate() + def update(self, stats): # flush display @@ -903,8 +911,6 @@ class glancesScreen(): # Wait countdown = Timer(self.__refresh_time) while (not countdown.finished()): - # Refresh the screen - self.term_window.refresh() # Getkey if (self.__catchKey() > -1): # flush display From 7cde28d62b765884d89315242b5437ec44d16ed3 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 18 Feb 2012 18:45:05 +0100 Subject: [PATCH 13/57] Try before removing process --- README.md | 253 +------------------------------------------------ src/glances.py | 9 +- 2 files changed, 7 insertions(+), 255 deletions(-) mode change 100644 => 120000 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 618cdb5d..00000000 --- a/README.md +++ /dev/null @@ -1,252 +0,0 @@ -[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) - -============================= -Glances -- Eye on your system -============================= - -## Description - -Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. - -Glances uses the PsUtil library to get information from your system. -It is developed in Python. - -![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) - -## Installation - -### From package manager (very easy way) - -Packages exist for Arch, Fedora, Redhat, Ubuntu (with PPA), FreeBSD... - -### From PyPi (easy way) - -PyPi is an official Python package manager. - -You first need to install pypi on your system. For exemple on Debian/Ubuntu: - - $ sudo apt-get install python-pip - -Then install the latest Glances version: - - $ sudo pip install glances - -### From source - -Get the latest version: - - $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz - -Glances use a standard GNU style installer: - - $ tar zxvf glances-1.4.tar.gz - $ cd glances-1.4 - $ ./configure - $ make - $ sudo make install - -Pre-requisites: - -* Python 2.6+ (not tested with Python 3+) -* psutil 0.4.1+ (did NOT work with psutil < 0.2 ) - -Notes: For Debian and Ubuntu < 12.04 -The officials repos only include the psutil version 0.2.1. -You had to install the version 0.4.1 using the following commands: - - $ sudo apt-get install python-dev python-pip - $ sudo pip install psutil - -## Running - -Easy: - - $ glances.py - -## User guide - -By default, stats are refreshed every second, to change this setting, you can -use the -t option. For exemple to set the refrech rate to 5 seconds: - - $ glances.py -t 5 - -Importants stats are colored: - -* GREEN: stat counter is "OK" -* BLUE: stat counter is "CAREFUL" -* MAGENTA: stat counter is "WARNING" -* RED: stat counter is "CRITICAL" - -When Glances is running, you can press: - -* 'h' to display an help message whith the keys you can press -* 'a' to set the automatic mode. The processes are sorted automatically - - If CPU > 70%, sort by process "CPU consumption" - - If MEM > 70%, sort by process "memory size" - -* 'c' to sort the processes list by CPU consumption -* 'd' Disable or enable the disk IO stats -* 'f' Disable or enable the file system stats -* 'l' Disable or enable the logs -* 'm' to sort the processes list by process size -* 'n' Disable or enable the network interfaces stats -* 'q' Exit - -### Header - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) - -The header shows the Glances version, the host name and the operating -system name, version and architecture. - -### CPU - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) - -The CPU states are shown as a percentage and for the configured refresh -time. - -If user|kernel|nice CPU is < 50%, then status is set to "OK". - -If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". - -If user|kernel|nice CPU is > 70%, then status is set to "WARNING". - -If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". - -### Load - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) - -On the Nosheep blog, Zach defines the average load: "In short it is the -average sum of the number of processes waiting in the run-queue plus the -number currently executing over 1, 5, and 15 minute time periods." - -Glances gets the number of CPU cores to adapt the alerts. With Glances, -alerts on average load are only set on 5 and 15 mins. - -If average load is < O.7*Core, then status is set to "OK". - -If average load is > O.7*Core, then status is set to "CAREFUL". - -If average load is > 1*Core, then status is set to "WARNING". - -If average load is > 5*Core, then status is set to "CRITICAL". - -### Memory - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) - -Glances uses tree columns: memory (RAM), swap and "real". - -Real used memory is: used - cache. - -Real free memory is: free + cache. - -With Glances, alerts are only set for on used swap and real memory. - -If memory is < 50%, then status is set to "OK". - -If memory is > 50%, then status is set to "CAREFUL". - -If memory is > 70%, then status is set to "WARNING". - -If memory is > 90%, then status is set to "CRITICAL". - -### Network bit rate - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) - -Glances display the network interface bit rate. The unit is adapted -dynamicaly (bits per second, Kbits per second, Mbits per second...). - -Alerts are set only if the network interface maximum speed is available. - -If bitrate is < 50%, then status is set to "OK". - -If bitrate is > 50%, then status is set to "CAREFUL". - -If bitrate is > 70%, then status is set to "WARNING". - -If bitrate is > 90%, then status is set to "CRITICAL". - -For exemple, on a 100 Mbps Ethernet interface, the warning status is set -if the bit rate is higher than 70 Mbps. - -### Disk I/O - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) - -Glances display the disk I/O throughput. The unit is adapted dynamicaly -(bytes per second, Kbytes per second, Mbytes per second...). - -There is no alert on this information. - -### Filesystem - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) - -Glances display the total and used filesytem disk space. The unit is -adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per -second...). - -Alerts are set for used disk space: - -If disk used is < 50%, then status is set to "OK". - -If disk used is > 50%, then status is set to "CAREFUL". - -If disk used is > 70%, then status is set to "WARNING". - -If disk used is > 90%, then status is set to "CRITICAL". - -### Processes - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) - -Glances displays a summary and a list of processes. - -By default (or if you hit the 'a' key) the process list is automaticaly -sorted by CPU of memory consumption. - -The number of processes in the list is adapted to the screen size. - -### Logs - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) - -A logs list is displayed in the bottom of the screen if (an only if): - -* at least one WARNING or CRITICAL alert was occured. -* space is available in the bottom of the console/terminal - -There is one line per alert with the following information: - -* start date -* end date -* alert name -* (min/avg/max) values - -### Footer - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) - -Glances displays a caption and the current time/date. - -## Localisation - -To generate french locale execute as root or sudo : -i18n_francais_generate.sh - -To generate spanish locale execute as root or sudo : -i18n_espanol_generate.sh - -## Todo - -You are welcome to contribute to this software. - -* Packaging for Debian, Ubuntu, BSD... -* Check the needed Python library in the configure.ac -* Add file system stats when the python-statgrab is corrected diff --git a/README.md b/README.md new file mode 120000 index 00000000..100b9382 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +README \ No newline at end of file diff --git a/src/glances.py b/src/glances.py index 0fdf0b9d..0a553d6e 100755 --- a/src/glances.py +++ b/src/glances.py @@ -2,7 +2,7 @@ # # Glances is a simple textual monitoring tool # -# Pre-requisites: Python 2.6+ and PsUtil 0.4.0+ +# Pre-requisites: Python 2.6+ and PsUtil 0.4.0+ (for full functions) # # Copyright (C) Nicolargo 2012 # @@ -22,7 +22,7 @@ from __future__ import generators __appname__ = 'glances' -__version__ = "1.4b9" +__version__ = "1.4b10" __author__ = "Nicolas Hennion " __licence__ = "LGPL" @@ -492,7 +492,10 @@ class glancesStats(): except: pass else: - self.process_all.remove(proc) + try: + self.process_all.remove(proc) + except: + pass # If it is the first grab then empty process list if (self.process_first_grab): self.process = [] From b88cfc396a2be9299e199f4e8d0ffaf774e9101d Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 21 Feb 2012 17:23:41 +0100 Subject: [PATCH 14/57] Excexclude rootfs and autofs --- README | 1 + README.md | 253 ++++++++++++++++++++++++++++++++++++++++++++++++- src/glances.py | 4 +- 3 files changed, 255 insertions(+), 3 deletions(-) mode change 120000 => 100644 README.md diff --git a/README b/README index 618cdb5d..3418338e 100644 --- a/README +++ b/README @@ -9,6 +9,7 @@ Glances -- Eye on your system Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. Glances uses the PsUtil library to get information from your system. + It is developed in Python. ![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) diff --git a/README.md b/README.md deleted file mode 120000 index 100b9382..00000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -README \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..618cdb5d --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) + +============================= +Glances -- Eye on your system +============================= + +## Description + +Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. + +Glances uses the PsUtil library to get information from your system. +It is developed in Python. + +![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) + +## Installation + +### From package manager (very easy way) + +Packages exist for Arch, Fedora, Redhat, Ubuntu (with PPA), FreeBSD... + +### From PyPi (easy way) + +PyPi is an official Python package manager. + +You first need to install pypi on your system. For exemple on Debian/Ubuntu: + + $ sudo apt-get install python-pip + +Then install the latest Glances version: + + $ sudo pip install glances + +### From source + +Get the latest version: + + $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz + +Glances use a standard GNU style installer: + + $ tar zxvf glances-1.4.tar.gz + $ cd glances-1.4 + $ ./configure + $ make + $ sudo make install + +Pre-requisites: + +* Python 2.6+ (not tested with Python 3+) +* psutil 0.4.1+ (did NOT work with psutil < 0.2 ) + +Notes: For Debian and Ubuntu < 12.04 +The officials repos only include the psutil version 0.2.1. +You had to install the version 0.4.1 using the following commands: + + $ sudo apt-get install python-dev python-pip + $ sudo pip install psutil + +## Running + +Easy: + + $ glances.py + +## User guide + +By default, stats are refreshed every second, to change this setting, you can +use the -t option. For exemple to set the refrech rate to 5 seconds: + + $ glances.py -t 5 + +Importants stats are colored: + +* GREEN: stat counter is "OK" +* BLUE: stat counter is "CAREFUL" +* MAGENTA: stat counter is "WARNING" +* RED: stat counter is "CRITICAL" + +When Glances is running, you can press: + +* 'h' to display an help message whith the keys you can press +* 'a' to set the automatic mode. The processes are sorted automatically + + If CPU > 70%, sort by process "CPU consumption" + + If MEM > 70%, sort by process "memory size" + +* 'c' to sort the processes list by CPU consumption +* 'd' Disable or enable the disk IO stats +* 'f' Disable or enable the file system stats +* 'l' Disable or enable the logs +* 'm' to sort the processes list by process size +* 'n' Disable or enable the network interfaces stats +* 'q' Exit + +### Header + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) + +The header shows the Glances version, the host name and the operating +system name, version and architecture. + +### CPU + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) + +The CPU states are shown as a percentage and for the configured refresh +time. + +If user|kernel|nice CPU is < 50%, then status is set to "OK". + +If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". + +If user|kernel|nice CPU is > 70%, then status is set to "WARNING". + +If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". + +### Load + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) + +On the Nosheep blog, Zach defines the average load: "In short it is the +average sum of the number of processes waiting in the run-queue plus the +number currently executing over 1, 5, and 15 minute time periods." + +Glances gets the number of CPU cores to adapt the alerts. With Glances, +alerts on average load are only set on 5 and 15 mins. + +If average load is < O.7*Core, then status is set to "OK". + +If average load is > O.7*Core, then status is set to "CAREFUL". + +If average load is > 1*Core, then status is set to "WARNING". + +If average load is > 5*Core, then status is set to "CRITICAL". + +### Memory + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) + +Glances uses tree columns: memory (RAM), swap and "real". + +Real used memory is: used - cache. + +Real free memory is: free + cache. + +With Glances, alerts are only set for on used swap and real memory. + +If memory is < 50%, then status is set to "OK". + +If memory is > 50%, then status is set to "CAREFUL". + +If memory is > 70%, then status is set to "WARNING". + +If memory is > 90%, then status is set to "CRITICAL". + +### Network bit rate + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) + +Glances display the network interface bit rate. The unit is adapted +dynamicaly (bits per second, Kbits per second, Mbits per second...). + +Alerts are set only if the network interface maximum speed is available. + +If bitrate is < 50%, then status is set to "OK". + +If bitrate is > 50%, then status is set to "CAREFUL". + +If bitrate is > 70%, then status is set to "WARNING". + +If bitrate is > 90%, then status is set to "CRITICAL". + +For exemple, on a 100 Mbps Ethernet interface, the warning status is set +if the bit rate is higher than 70 Mbps. + +### Disk I/O + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) + +Glances display the disk I/O throughput. The unit is adapted dynamicaly +(bytes per second, Kbytes per second, Mbytes per second...). + +There is no alert on this information. + +### Filesystem + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) + +Glances display the total and used filesytem disk space. The unit is +adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per +second...). + +Alerts are set for used disk space: + +If disk used is < 50%, then status is set to "OK". + +If disk used is > 50%, then status is set to "CAREFUL". + +If disk used is > 70%, then status is set to "WARNING". + +If disk used is > 90%, then status is set to "CRITICAL". + +### Processes + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) + +Glances displays a summary and a list of processes. + +By default (or if you hit the 'a' key) the process list is automaticaly +sorted by CPU of memory consumption. + +The number of processes in the list is adapted to the screen size. + +### Logs + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) + +A logs list is displayed in the bottom of the screen if (an only if): + +* at least one WARNING or CRITICAL alert was occured. +* space is available in the bottom of the console/terminal + +There is one line per alert with the following information: + +* start date +* end date +* alert name +* (min/avg/max) values + +### Footer + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) + +Glances displays a caption and the current time/date. + +## Localisation + +To generate french locale execute as root or sudo : +i18n_francais_generate.sh + +To generate spanish locale execute as root or sudo : +i18n_espanol_generate.sh + +## Todo + +You are welcome to contribute to this software. + +* Packaging for Debian, Ubuntu, BSD... +* Check the needed Python library in the configure.ac +* Add file system stats when the python-statgrab is corrected diff --git a/src/glances.py b/src/glances.py index 0a553d6e..d03eaa17 100755 --- a/src/glances.py +++ b/src/glances.py @@ -22,7 +22,7 @@ from __future__ import generators __appname__ = 'glances' -__version__ = "1.4b10" +__version__ = "1.4b11" __author__ = "Nicolas Hennion " __licence__ = "LGPL" @@ -279,7 +279,7 @@ class glancesGrabFs(): # Ignore the following fs ignore_fsname = ('', 'none', 'gvfs-fuse-daemon', 'fusectl', 'cgroup') - ignore_fstype = ('binfmt_misc', 'devpts', 'iso9660', 'none', 'proc', 'sysfs', 'usbfs') + ignore_fstype = ('binfmt_misc', 'devpts', 'iso9660', 'none', 'proc', 'sysfs', 'usbfs', 'rootfs', 'autofs') # Reset the list self.fs_list = [] From edee020529ccd6e30b59bace0e696391ef4b8587 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 21 Feb 2012 17:49:45 +0100 Subject: [PATCH 15/57] Ok when insert/remove external disk --- screenshot.png | Bin 102609 -> 95627 bytes src/glances.py | 72 +++++++++++++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/screenshot.png b/screenshot.png index a9efb2b59bb31b5180c97ba2942236ddf8f33df6..97a7fee3f82ee66769c82d5a2a11a91887d2c6fb 100644 GIT binary patch literal 95627 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 Gzy1eIE^(&- literal 102609 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 Date: Tue, 13 Mar 2012 14:23:00 +0100 Subject: [PATCH 16/57] HTML Output --- README | 17 +++++- README.md | 18 +++++- src/glances.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 181 insertions(+), 6 deletions(-) diff --git a/README b/README index 3418338e..54c5699c 100644 --- a/README +++ b/README @@ -18,7 +18,20 @@ It is developed in Python. ### From package manager (very easy way) -Packages exist for Arch, Fedora, Redhat, Ubuntu (with PPA), FreeBSD... +Packages exist for Arch, Fedora, Redhat, FreeBSD... + +### From PPA (easy way for Ubuntu/Mint...) + +Arnaud Hartmann (thanks to him !) maintains a PPA with the latest Glances version: + +To install the PPA just enter: + + $ sudo add-apt-repository ppa:arnaud-hartmann/glances-dev + $ sudo apt-get update + +Then install Glances: + + $ sudo apt-get install glances ### From PyPi (easy way) @@ -56,7 +69,7 @@ The officials repos only include the psutil version 0.2.1. You had to install the version 0.4.1 using the following commands: $ sudo apt-get install python-dev python-pip - $ sudo pip install psutil + $ sudo pip install --upgrade psutil ## Running diff --git a/README.md b/README.md index 618cdb5d..54c5699c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Glances -- Eye on your system Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. Glances uses the PsUtil library to get information from your system. + It is developed in Python. ![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) @@ -17,7 +18,20 @@ It is developed in Python. ### From package manager (very easy way) -Packages exist for Arch, Fedora, Redhat, Ubuntu (with PPA), FreeBSD... +Packages exist for Arch, Fedora, Redhat, FreeBSD... + +### From PPA (easy way for Ubuntu/Mint...) + +Arnaud Hartmann (thanks to him !) maintains a PPA with the latest Glances version: + +To install the PPA just enter: + + $ sudo add-apt-repository ppa:arnaud-hartmann/glances-dev + $ sudo apt-get update + +Then install Glances: + + $ sudo apt-get install glances ### From PyPi (easy way) @@ -55,7 +69,7 @@ The officials repos only include the psutil version 0.2.1. You had to install the version 0.4.1 using the following commands: $ sudo apt-get install python-dev python-pip - $ sudo pip install psutil + $ sudo pip install --upgrade psutil ## Running diff --git a/src/glances.py b/src/glances.py index 9ee8af72..adc38054 100755 --- a/src/glances.py +++ b/src/glances.py @@ -22,7 +22,7 @@ from __future__ import generators __appname__ = 'glances' -__version__ = "1.4b12" +__version__ = "1.4b13" __author__ = "Nicolas Hennion " __licence__ = "LGPL" @@ -114,6 +114,14 @@ except: else: psutil_network_io_tag = True +try: + # HTML output + import jinja2 +except: + jinja_tag = False +else: + jinja_tag = True + # Classes #======== @@ -1285,6 +1293,140 @@ class glancesScreen(): now_msg = now.strftime(_("%Y-%m-%d %H:%M:%S")) self.term_window.addnstr(max(self.now_y, screen_y-1), max(self.now_x, screen_x-1)-len(now_msg), now_msg, len(now_msg)) + +class glancesHtml(): + """ + This class manages the HTML output + """ + + def __init__(self, refresh_time = 1): + # Global information to display + + # Init refresh time + self.__refresh_time = refresh_time + + # Set the templates path + environment = jinja2.Environment(loader=jinja2.FileSystemLoader('html')) + + # Open the template + self.template = environment.get_template('default.html') + + # Define the colors list (hash table) for logged stats + self.__colors_list = { + # CAREFUL WARNING CRITICAL + 'DEFAULT': "bgcdefault fgdefault", + 'OK': "bgcok fgok", + 'CAREFUL': "bgccareful fgcareful", + 'WARNING': "bgcwarning fgcwarning", + 'CRITICAL': "bgcritical fgcritical" + } + + def __getAlert(self, current = 0, max = 100): + # If current < CAREFUL of max then alert = OK + # If current > CAREFUL of max then alert = CAREFUL + # If current > WARNING of max then alert = WARNING + # If current > CRITICAL of max then alert = CRITICAL + try: + (current * 100) / max + except ZeroDivisionError: + return 'DEFAULT' + + variable = (current * 100) / max + + if variable > limits.getSTDCritical(): + return 'CRITICAL' + elif variable > limits.getSTDWarning(): + return 'WARNING' + elif variable > limits.getSTDCareful(): + return 'CAREFUL' + + return 'OK' + + + def __getColor(self, current = 0, max = 100): + """ + Return colors for logged stats + """ + return self.__colors_list[self.__getAlert(current, max)] + + + def __getCpuColor(self, cpu, max = 100): + cpu['user_color'] = self.__getColor(cpu['user'], max) + cpu['kernel_color'] = self.__getColor(cpu['kernel'], max) + cpu['nice_color'] = self.__getColor(cpu['nice'], max) + return cpu + + + def __getLoadAlert(self, current = 0, core = 1): + # If current < CAREFUL*core of max then alert = OK + # If current > CAREFUL*core of max then alert = CAREFUL + # If current > WARNING*core of max then alert = WARNING + # If current > CRITICAL*core of max then alert = CRITICAL + if current > limits.getLOADCritical(core): + return 'CRITICAL' + elif current > limits.getLOADWarning(core): + return 'WARNING' + elif current > limits.getLOADCareful(core): + return 'CAREFUL' + return 'OK' + + + def __getLoadColor(self, load, core = 1): + load['min1_color'] = self.__colors_list[self.__getLoadAlert(load['min1'], core)] + load['min5_color'] = self.__colors_list[self.__getLoadAlert(load['min5'], core)] + load['min15_color'] = self.__colors_list[self.__getLoadAlert(load['min15'], core)] + return load + + + def __getMemColor(self, mem): + mem['used_color'] = self.__getColor(mem['used']-mem['cache'], mem['total']) + return mem + + + def __getMemSwapColor(self, memswap): + memswap['used_color'] = self.__getColor(memswap['used'], memswap['total']) + return memswap + + + def update(self, stats): + if (stats.getCpu()): + # Open the output file + f = open('glances.html', 'w') + + # Process color + + # Render it + data = self.template.render(refresh = self.__refresh_time, + host = stats.getHost(), + system = stats.getSystem(), + cpu = self.__getCpuColor(stats.getCpu()), + load = self.__getLoadColor(stats.getLoad(), stats.getCore()), + core = stats.getCore(), + mem = self.__getMemColor(stats.getMem()), + memswap = self.__getMemSwapColor(stats.getMemSwap()) ) + + # Write data into the file + f.write(data) + + # Close the file + f.close() + + # Display stats + #~ self.displayHost(stats.getHost()) + #~ self.displaySystem(stats.getSystem()) + #~ self.displayCpu(stats.getCpu()) + #~ self.displayLoad(stats.getLoad(), stats.getCore()) + #~ self.displayMem(stats.getMem(), stats.getMemSwap()) + #~ network_count = self.displayNetwork(stats.getNetwork()) + #~ diskio_count = self.displayDiskIO(stats.getDiskIO(), self.network_y + network_count) + #~ fs_count = self.displayFs(stats.getFs(), self.network_y + network_count + diskio_count) + #~ log_count = self.displayLog(self.network_y + network_count + diskio_count + fs_count) + #~ self.displayProcess(stats.getProcessCount(), stats.getProcessList(screen.getProcessSortedBy()), log_count) + #~ self.displayCaption() + #~ self.displayNow(stats.getNow()) + #~ self.displayHelp() + + # Global def #=========== @@ -1316,7 +1458,7 @@ def printSyntax(): def init(): - global limits, logs, stats, screen + global limits, logs, stats, screen, html global refresh_time refresh_time = 1 @@ -1357,6 +1499,9 @@ def init(): # Init screen screen = glancesScreen(refresh_time) + + # Init HTML output + html = glancesHtml(refresh_time) def main(): @@ -1370,6 +1515,9 @@ def main(): # Update the screen screen.update(stats) + + # Update the HTML output + html.update(stats) def end(): From 471db3b03e514dee592051a2a44a3ff92131aa8e Mon Sep 17 00:00:00 2001 From: Laurent Bachelier Date: Tue, 13 Mar 2012 22:32:22 +0100 Subject: [PATCH 17/57] Much simpler, pure-python packaging --- .gitignore | 6 + MANIFEST.in | 9 + Makefile.am | 21 - Makefile.in | 740 ----------------------------------- autogen.sh | 157 -------- buildout.cfg | 56 --- config.h.in | 25 -- configure.ac | 23 -- {src => glances}/__init__.py | 0 {src => glances}/glances.py | 0 m4/ax_python_module.m4 | 50 --- man/Makefile.am | 1 - setup.py | 47 ++- src/Makefile.am | 11 - src/Makefile.in | 423 -------------------- 15 files changed, 42 insertions(+), 1527 deletions(-) create mode 100644 .gitignore create mode 100644 MANIFEST.in delete mode 100644 Makefile.am delete mode 100644 Makefile.in delete mode 100755 autogen.sh delete mode 100644 buildout.cfg delete mode 100644 config.h.in delete mode 100644 configure.ac rename {src => glances}/__init__.py (100%) rename {src => glances}/glances.py (100%) delete mode 100644 m4/ax_python_module.m4 delete mode 100644 man/Makefile.am mode change 100644 => 100755 setup.py delete mode 100644 src/Makefile.am delete mode 100644 src/Makefile.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..033f8097 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.pyc +# python packaging artifacts +*.egg-info +/build/ +/dist/ + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..50401c68 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include README +include README-fr +include COPYING +include AUTHORS +include ChangeLog +include NEWS +include screenshot.png +recursive-include doc *.png +recursive-include man *.1 diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index dc0dec58..00000000 --- a/Makefile.am +++ /dev/null @@ -1,21 +0,0 @@ -## Process this file with automake to produce Makefile.in -## Created by Anjuta - -SUBDIRS = src man - -glancesdocdir = ${prefix}/doc/glances -glancesdoc_DATA = \ - README\ - COPYING\ - AUTHORS\ - ChangeLog\ - INSTALL\ - NEWS - - -EXTRA_DIST = $(glancesdoc_DATA) - - -# Remove doc directory on uninstall -uninstall-local: - -rm -r $(glancesdocdir) diff --git a/Makefile.in b/Makefile.in deleted file mode 100644 index bfe6825c..00000000 --- a/Makefile.in +++ /dev/null @@ -1,740 +0,0 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. -# @configure_input@ - -# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. -# This Makefile.in is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - -@SET_MAKE@ - -VPATH = @srcdir@ -pkgdatadir = $(datadir)/@PACKAGE@ -pkgincludedir = $(includedir)/@PACKAGE@ -pkglibdir = $(libdir)/@PACKAGE@ -pkglibexecdir = $(libexecdir)/@PACKAGE@ -am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd -install_sh_DATA = $(install_sh) -c -m 644 -install_sh_PROGRAM = $(install_sh) -c -install_sh_SCRIPT = $(install_sh) -c -INSTALL_HEADER = $(INSTALL_DATA) -transform = $(program_transform_name) -NORMAL_INSTALL = : -PRE_INSTALL = : -POST_INSTALL = : -NORMAL_UNINSTALL = : -PRE_UNINSTALL = : -POST_UNINSTALL = : -subdir = . -DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \ - $(srcdir)/Makefile.in $(srcdir)/config.h.in \ - $(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \ - TODO install-sh missing py-compile -ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 -am__aclocal_m4_deps = $(top_srcdir)/configure.ac -am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ - $(ACLOCAL_M4) -am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ - configure.lineno config.status.lineno -mkinstalldirs = $(install_sh) -d -CONFIG_HEADER = config.h -CONFIG_CLEAN_FILES = -CONFIG_CLEAN_VPATH_FILES = -AM_V_GEN = $(am__v_GEN_$(V)) -am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) -am__v_GEN_0 = @echo " GEN " $@; -AM_V_at = $(am__v_at_$(V)) -am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) -am__v_at_0 = @ -SOURCES = -DIST_SOURCES = -RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ - html-recursive info-recursive install-data-recursive \ - install-dvi-recursive install-exec-recursive \ - install-html-recursive install-info-recursive \ - install-pdf-recursive install-ps-recursive install-recursive \ - installcheck-recursive installdirs-recursive pdf-recursive \ - ps-recursive uninstall-recursive -am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; -am__vpath_adj = case $$p in \ - $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ - *) f=$$p;; \ - esac; -am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; -am__install_max = 40 -am__nobase_strip_setup = \ - srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` -am__nobase_strip = \ - for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" -am__nobase_list = $(am__nobase_strip_setup); \ - for p in $$list; do echo "$$p $$p"; done | \ - sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ - $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ - if (++n[$$2] == $(am__install_max)) \ - { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ - END { for (dir in files) print dir, files[dir] }' -am__base_list = \ - sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ - sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' -am__installdirs = "$(DESTDIR)$(glancesdocdir)" -DATA = $(glancesdoc_DATA) -RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ - distclean-recursive maintainer-clean-recursive -AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \ - $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \ - distdir dist dist-all distcheck -ETAGS = etags -CTAGS = ctags -DIST_SUBDIRS = $(SUBDIRS) -DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) -distdir = $(PACKAGE)-$(VERSION) -top_distdir = $(distdir) -am__remove_distdir = \ - { test ! -d "$(distdir)" \ - || { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ - && rm -fr "$(distdir)"; }; } -am__relativize = \ - dir0=`pwd`; \ - sed_first='s,^\([^/]*\)/.*$$,\1,'; \ - sed_rest='s,^[^/]*/*,,'; \ - sed_last='s,^.*/\([^/]*\)$$,\1,'; \ - sed_butlast='s,/*[^/]*$$,,'; \ - while test -n "$$dir1"; do \ - first=`echo "$$dir1" | sed -e "$$sed_first"`; \ - if test "$$first" != "."; then \ - if test "$$first" = ".."; then \ - dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ - dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ - else \ - first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ - if test "$$first2" = "$$first"; then \ - dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ - else \ - dir2="../$$dir2"; \ - fi; \ - dir0="$$dir0"/"$$first"; \ - fi; \ - fi; \ - dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ - done; \ - reldir="$$dir2" -DIST_ARCHIVES = $(distdir).tar.gz -GZIP_ENV = --best -distuninstallcheck_listfiles = find . -type f -print -distcleancheck_listfiles = find . -type f -print -ACLOCAL = @ACLOCAL@ -AMTAR = @AMTAR@ -AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ -AUTOCONF = @AUTOCONF@ -AUTOHEADER = @AUTOHEADER@ -AUTOMAKE = @AUTOMAKE@ -AWK = @AWK@ -CYGPATH_W = @CYGPATH_W@ -DEFS = @DEFS@ -ECHO_C = @ECHO_C@ -ECHO_N = @ECHO_N@ -ECHO_T = @ECHO_T@ -INSTALL = @INSTALL@ -INSTALL_DATA = @INSTALL_DATA@ -INSTALL_PROGRAM = @INSTALL_PROGRAM@ -INSTALL_SCRIPT = @INSTALL_SCRIPT@ -INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ -LIBOBJS = @LIBOBJS@ -LIBS = @LIBS@ -LTLIBOBJS = @LTLIBOBJS@ -MAKEINFO = @MAKEINFO@ -MKDIR_P = @MKDIR_P@ -PACKAGE = @PACKAGE@ -PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ -PACKAGE_NAME = @PACKAGE_NAME@ -PACKAGE_STRING = @PACKAGE_STRING@ -PACKAGE_TARNAME = @PACKAGE_TARNAME@ -PACKAGE_URL = @PACKAGE_URL@ -PACKAGE_VERSION = @PACKAGE_VERSION@ -PATH_SEPARATOR = @PATH_SEPARATOR@ -PYTHON = @PYTHON@ -PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ -PYTHON_PLATFORM = @PYTHON_PLATFORM@ -PYTHON_PREFIX = @PYTHON_PREFIX@ -PYTHON_VERSION = @PYTHON_VERSION@ -SET_MAKE = @SET_MAKE@ -SHELL = @SHELL@ -STRIP = @STRIP@ -VERSION = @VERSION@ -abs_builddir = @abs_builddir@ -abs_srcdir = @abs_srcdir@ -abs_top_builddir = @abs_top_builddir@ -abs_top_srcdir = @abs_top_srcdir@ -am__leading_dot = @am__leading_dot@ -am__tar = @am__tar@ -am__untar = @am__untar@ -bindir = @bindir@ -build_alias = @build_alias@ -builddir = @builddir@ -datadir = @datadir@ -datarootdir = @datarootdir@ -docdir = @docdir@ -dvidir = @dvidir@ -exec_prefix = @exec_prefix@ -host_alias = @host_alias@ -htmldir = @htmldir@ -includedir = @includedir@ -infodir = @infodir@ -install_sh = @install_sh@ -libdir = @libdir@ -libexecdir = @libexecdir@ -localedir = @localedir@ -localstatedir = @localstatedir@ -mandir = @mandir@ -mkdir_p = @mkdir_p@ -oldincludedir = @oldincludedir@ -pdfdir = @pdfdir@ -pkgpyexecdir = @pkgpyexecdir@ -pkgpythondir = @pkgpythondir@ -prefix = @prefix@ -program_transform_name = @program_transform_name@ -psdir = @psdir@ -pyexecdir = @pyexecdir@ -pythondir = @pythondir@ -sbindir = @sbindir@ -sharedstatedir = @sharedstatedir@ -srcdir = @srcdir@ -sysconfdir = @sysconfdir@ -target_alias = @target_alias@ -top_build_prefix = @top_build_prefix@ -top_builddir = @top_builddir@ -top_srcdir = @top_srcdir@ -SUBDIRS = src man -glancesdocdir = ${prefix}/doc/glances -glancesdoc_DATA = \ - README\ - COPYING\ - AUTHORS\ - ChangeLog\ - INSTALL\ - NEWS - -EXTRA_DIST = $(glancesdoc_DATA) -all: config.h - $(MAKE) $(AM_MAKEFLAGS) all-recursive - -.SUFFIXES: -am--refresh: - @: -$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) - @for dep in $?; do \ - case '$(am__configure_deps)' in \ - *$$dep*) \ - echo ' cd $(srcdir) && $(AUTOMAKE) --gnu'; \ - $(am__cd) $(srcdir) && $(AUTOMAKE) --gnu \ - && exit 0; \ - exit 1;; \ - esac; \ - done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu Makefile'; \ - $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu Makefile -.PRECIOUS: Makefile -Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status - @case '$?' in \ - *config.status*) \ - echo ' $(SHELL) ./config.status'; \ - $(SHELL) ./config.status;; \ - *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ - esac; - -$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) - $(SHELL) ./config.status --recheck - -$(top_srcdir)/configure: $(am__configure_deps) - $(am__cd) $(srcdir) && $(AUTOCONF) -$(ACLOCAL_M4): $(am__aclocal_m4_deps) - $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) -$(am__aclocal_m4_deps): - -config.h: stamp-h1 - @if test ! -f $@; then \ - rm -f stamp-h1; \ - $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ - else :; fi - -stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status - @rm -f stamp-h1 - cd $(top_builddir) && $(SHELL) ./config.status config.h -$(srcdir)/config.h.in: $(am__configure_deps) - ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) - rm -f stamp-h1 - touch $@ - -distclean-hdr: - -rm -f config.h stamp-h1 -install-glancesdocDATA: $(glancesdoc_DATA) - @$(NORMAL_INSTALL) - test -z "$(glancesdocdir)" || $(MKDIR_P) "$(DESTDIR)$(glancesdocdir)" - @list='$(glancesdoc_DATA)'; test -n "$(glancesdocdir)" || list=; \ - for p in $$list; do \ - if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ - echo "$$d$$p"; \ - done | $(am__base_list) | \ - while read files; do \ - echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(glancesdocdir)'"; \ - $(INSTALL_DATA) $$files "$(DESTDIR)$(glancesdocdir)" || exit $$?; \ - done - -uninstall-glancesdocDATA: - @$(NORMAL_UNINSTALL) - @list='$(glancesdoc_DATA)'; test -n "$(glancesdocdir)" || list=; \ - files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ - test -n "$$files" || exit 0; \ - echo " ( cd '$(DESTDIR)$(glancesdocdir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(glancesdocdir)" && rm -f $$files - -# This directory's subdirectories are mostly independent; you can cd -# into them and run `make' without going through this Makefile. -# To change the values of `make' variables: instead of editing Makefiles, -# (1) if the variable is set in `config.status', edit `config.status' -# (which will cause the Makefiles to be regenerated when you run `make'); -# (2) otherwise, pass the desired values on the `make' command line. -$(RECURSIVE_TARGETS): - @fail= failcom='exit 1'; \ - for f in x $$MAKEFLAGS; do \ - case $$f in \ - *=* | --[!k]*);; \ - *k*) failcom='fail=yes';; \ - esac; \ - done; \ - dot_seen=no; \ - target=`echo $@ | sed s/-recursive//`; \ - list='$(SUBDIRS)'; for subdir in $$list; do \ - echo "Making $$target in $$subdir"; \ - if test "$$subdir" = "."; then \ - dot_seen=yes; \ - local_target="$$target-am"; \ - else \ - local_target="$$target"; \ - fi; \ - ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ - || eval $$failcom; \ - done; \ - if test "$$dot_seen" = "no"; then \ - $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ - fi; test -z "$$fail" - -$(RECURSIVE_CLEAN_TARGETS): - @fail= failcom='exit 1'; \ - for f in x $$MAKEFLAGS; do \ - case $$f in \ - *=* | --[!k]*);; \ - *k*) failcom='fail=yes';; \ - esac; \ - done; \ - dot_seen=no; \ - case "$@" in \ - distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ - *) list='$(SUBDIRS)' ;; \ - esac; \ - rev=''; for subdir in $$list; do \ - if test "$$subdir" = "."; then :; else \ - rev="$$subdir $$rev"; \ - fi; \ - done; \ - rev="$$rev ."; \ - target=`echo $@ | sed s/-recursive//`; \ - for subdir in $$rev; do \ - echo "Making $$target in $$subdir"; \ - if test "$$subdir" = "."; then \ - local_target="$$target-am"; \ - else \ - local_target="$$target"; \ - fi; \ - ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ - || eval $$failcom; \ - done && test -z "$$fail" -tags-recursive: - list='$(SUBDIRS)'; for subdir in $$list; do \ - test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ - done -ctags-recursive: - list='$(SUBDIRS)'; for subdir in $$list; do \ - test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ - done - -ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) - list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ - unique=`for i in $$list; do \ - if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ - done | \ - $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ - END { if (nonempty) { for (i in files) print i; }; }'`; \ - mkid -fID $$unique -tags: TAGS - -TAGS: tags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ - $(TAGS_FILES) $(LISP) - set x; \ - here=`pwd`; \ - if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ - include_option=--etags-include; \ - empty_fix=.; \ - else \ - include_option=--include; \ - empty_fix=; \ - fi; \ - list='$(SUBDIRS)'; for subdir in $$list; do \ - if test "$$subdir" = .; then :; else \ - test ! -f $$subdir/TAGS || \ - set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ - fi; \ - done; \ - list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ - unique=`for i in $$list; do \ - if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ - done | \ - $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ - END { if (nonempty) { for (i in files) print i; }; }'`; \ - shift; \ - if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ - test -n "$$unique" || unique=$$empty_fix; \ - if test $$# -gt 0; then \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - "$$@" $$unique; \ - else \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$unique; \ - fi; \ - fi -ctags: CTAGS -CTAGS: ctags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ - $(TAGS_FILES) $(LISP) - list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ - unique=`for i in $$list; do \ - if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ - done | \ - $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ - END { if (nonempty) { for (i in files) print i; }; }'`; \ - test -z "$(CTAGS_ARGS)$$unique" \ - || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ - $$unique - -GTAGS: - here=`$(am__cd) $(top_builddir) && pwd` \ - && $(am__cd) $(top_srcdir) \ - && gtags -i $(GTAGS_ARGS) "$$here" - -distclean-tags: - -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags - -distdir: $(DISTFILES) - $(am__remove_distdir) - test -d "$(distdir)" || mkdir "$(distdir)" - @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - list='$(DISTFILES)'; \ - dist_files=`for file in $$list; do echo $$file; done | \ - sed -e "s|^$$srcdirstrip/||;t" \ - -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ - case $$dist_files in \ - */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ - sort -u` ;; \ - esac; \ - for file in $$dist_files; do \ - if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ - if test -d $$d/$$file; then \ - dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ - if test -d "$(distdir)/$$file"; then \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ - cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ - else \ - test -f "$(distdir)/$$file" \ - || cp -p $$d/$$file "$(distdir)/$$file" \ - || exit 1; \ - fi; \ - done - @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ - if test "$$subdir" = .; then :; else \ - test -d "$(distdir)/$$subdir" \ - || $(MKDIR_P) "$(distdir)/$$subdir" \ - || exit 1; \ - fi; \ - done - @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ - if test "$$subdir" = .; then :; else \ - dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ - $(am__relativize); \ - new_distdir=$$reldir; \ - dir1=$$subdir; dir2="$(top_distdir)"; \ - $(am__relativize); \ - new_top_distdir=$$reldir; \ - echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ - echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ - ($(am__cd) $$subdir && \ - $(MAKE) $(AM_MAKEFLAGS) \ - top_distdir="$$new_top_distdir" \ - distdir="$$new_distdir" \ - am__remove_distdir=: \ - am__skip_length_check=: \ - am__skip_mode_fix=: \ - distdir) \ - || exit 1; \ - fi; \ - done - -test -n "$(am__skip_mode_fix)" \ - || find "$(distdir)" -type d ! -perm -755 \ - -exec chmod u+rwx,go+rx {} \; -o \ - ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ - ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ - ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ - || chmod -R a+r "$(distdir)" -dist-gzip: distdir - tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz - $(am__remove_distdir) - -dist-bzip2: distdir - tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 - $(am__remove_distdir) - -dist-lzma: distdir - tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma - $(am__remove_distdir) - -dist-xz: distdir - tardir=$(distdir) && $(am__tar) | xz -c >$(distdir).tar.xz - $(am__remove_distdir) - -dist-tarZ: distdir - tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z - $(am__remove_distdir) - -dist-shar: distdir - shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz - $(am__remove_distdir) - -dist-zip: distdir - -rm -f $(distdir).zip - zip -rq $(distdir).zip $(distdir) - $(am__remove_distdir) - -dist dist-all: distdir - tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz - $(am__remove_distdir) - -# This target untars the dist file and tries a VPATH configuration. Then -# it guarantees that the distribution is self-contained by making another -# tarfile. -distcheck: dist - case '$(DIST_ARCHIVES)' in \ - *.tar.gz*) \ - GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ - *.tar.bz2*) \ - bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ - *.tar.lzma*) \ - lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\ - *.tar.xz*) \ - xz -dc $(distdir).tar.xz | $(am__untar) ;;\ - *.tar.Z*) \ - uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ - *.shar.gz*) \ - GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\ - *.zip*) \ - unzip $(distdir).zip ;;\ - esac - chmod -R a-w $(distdir); chmod a+w $(distdir) - mkdir $(distdir)/_build - mkdir $(distdir)/_inst - chmod a-w $(distdir) - test -d $(distdir)/_build || exit 0; \ - dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ - && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ - && am__cwd=`pwd` \ - && $(am__cd) $(distdir)/_build \ - && ../configure --srcdir=.. --prefix="$$dc_install_base" \ - $(DISTCHECK_CONFIGURE_FLAGS) \ - && $(MAKE) $(AM_MAKEFLAGS) \ - && $(MAKE) $(AM_MAKEFLAGS) dvi \ - && $(MAKE) $(AM_MAKEFLAGS) check \ - && $(MAKE) $(AM_MAKEFLAGS) install \ - && $(MAKE) $(AM_MAKEFLAGS) installcheck \ - && $(MAKE) $(AM_MAKEFLAGS) uninstall \ - && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ - distuninstallcheck \ - && chmod -R a-w "$$dc_install_base" \ - && ({ \ - (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ - distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ - } || { rm -rf "$$dc_destdir"; exit 1; }) \ - && rm -rf "$$dc_destdir" \ - && $(MAKE) $(AM_MAKEFLAGS) dist \ - && rm -rf $(DIST_ARCHIVES) \ - && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ - && cd "$$am__cwd" \ - || exit 1 - $(am__remove_distdir) - @(echo "$(distdir) archives ready for distribution: "; \ - list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ - sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' -distuninstallcheck: - @$(am__cd) '$(distuninstallcheck_dir)' \ - && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ - || { echo "ERROR: files left after uninstall:" ; \ - if test -n "$(DESTDIR)"; then \ - echo " (check DESTDIR support)"; \ - fi ; \ - $(distuninstallcheck_listfiles) ; \ - exit 1; } >&2 -distcleancheck: distclean - @if test '$(srcdir)' = . ; then \ - echo "ERROR: distcleancheck can only run from a VPATH build" ; \ - exit 1 ; \ - fi - @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ - || { echo "ERROR: files left in build directory after distclean:" ; \ - $(distcleancheck_listfiles) ; \ - exit 1; } >&2 -check-am: all-am -check: check-recursive -all-am: Makefile $(DATA) config.h -installdirs: installdirs-recursive -installdirs-am: - for dir in "$(DESTDIR)$(glancesdocdir)"; do \ - test -z "$$dir" || $(MKDIR_P) "$$dir"; \ - done -install: install-recursive -install-exec: install-exec-recursive -install-data: install-data-recursive -uninstall: uninstall-recursive - -install-am: all-am - @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am - -installcheck: installcheck-recursive -install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install -mostlyclean-generic: - -clean-generic: - -distclean-generic: - -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) - -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) - -maintainer-clean-generic: - @echo "This command is intended for maintainers to use" - @echo "it deletes files that may require special tools to rebuild." -clean: clean-recursive - -clean-am: clean-generic mostlyclean-am - -distclean: distclean-recursive - -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -f Makefile -distclean-am: clean-am distclean-generic distclean-hdr distclean-tags - -dvi: dvi-recursive - -dvi-am: - -html: html-recursive - -html-am: - -info: info-recursive - -info-am: - -install-data-am: install-glancesdocDATA - -install-dvi: install-dvi-recursive - -install-dvi-am: - -install-exec-am: - -install-html: install-html-recursive - -install-html-am: - -install-info: install-info-recursive - -install-info-am: - -install-man: - -install-pdf: install-pdf-recursive - -install-pdf-am: - -install-ps: install-ps-recursive - -install-ps-am: - -installcheck-am: - -maintainer-clean: maintainer-clean-recursive - -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -rf $(top_srcdir)/autom4te.cache - -rm -f Makefile -maintainer-clean-am: distclean-am maintainer-clean-generic - -mostlyclean: mostlyclean-recursive - -mostlyclean-am: mostlyclean-generic - -pdf: pdf-recursive - -pdf-am: - -ps: ps-recursive - -ps-am: - -uninstall-am: uninstall-glancesdocDATA uninstall-local - -.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) all \ - ctags-recursive install-am install-strip tags-recursive - -.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ - all all-am am--refresh check check-am clean clean-generic \ - ctags ctags-recursive dist dist-all dist-bzip2 dist-gzip \ - dist-lzma dist-shar dist-tarZ dist-xz dist-zip distcheck \ - distclean distclean-generic distclean-hdr distclean-tags \ - distcleancheck distdir distuninstallcheck dvi dvi-am html \ - html-am info info-am install install-am install-data \ - install-data-am install-dvi install-dvi-am install-exec \ - install-exec-am install-glancesdocDATA install-html \ - install-html-am install-info install-info-am install-man \ - install-pdf install-pdf-am install-ps install-ps-am \ - install-strip installcheck installcheck-am installdirs \ - installdirs-am maintainer-clean maintainer-clean-generic \ - mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ - tags-recursive uninstall uninstall-am uninstall-glancesdocDATA \ - uninstall-local - - -# Remove doc directory on uninstall -uninstall-local: - -rm -r $(glancesdocdir) - -# Tell versions [3.59,3.63) of GNU make to not export all variables. -# Otherwise a system limit (for SysV at least) may be exceeded. -.NOEXPORT: diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index d4437e3d..00000000 --- a/autogen.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/bin/sh -# Run this to generate all the initial makefiles, etc. - -srcdir=`dirname $0` -test -z "$srcdir" && srcdir=. - -DIE=0 - -if [ -n "$GNOME2_DIR" ]; then - ACLOCAL_FLAGS="-I $GNOME2_DIR/share/aclocal $ACLOCAL_FLAGS" - LD_LIBRARY_PATH="$GNOME2_DIR/lib:$LD_LIBRARY_PATH" - PATH="$GNOME2_DIR/bin:$PATH" - export PATH - export LD_LIBRARY_PATH -fi - -(test -f $srcdir/configure.ac) || { - echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" - echo " top-level package directory" - exit 1 -} - -(autoconf --version) < /dev/null > /dev/null 2>&1 || { - echo - echo "**Error**: You must have \`autoconf' installed." - echo "Download the appropriate package for your distribution," - echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" - DIE=1 -} - -(grep "^IT_PROG_INTLTOOL" $srcdir/configure.ac >/dev/null) && { - (intltoolize --version) < /dev/null > /dev/null 2>&1 || { - echo - echo "**Error**: You must have \`intltool' installed." - echo "You can get it from:" - echo " ftp://ftp.gnome.org/pub/GNOME/" - DIE=1 - } -} - -(grep "^AM_PROG_XML_I18N_TOOLS" $srcdir/configure.ac >/dev/null) && { - (xml-i18n-toolize --version) < /dev/null > /dev/null 2>&1 || { - echo - echo "**Error**: You must have \`xml-i18n-toolize' installed." - echo "You can get it from:" - echo " ftp://ftp.gnome.org/pub/GNOME/" - DIE=1 - } -} - -(grep "^LT_INIT" $srcdir/configure.ac >/dev/null) && { - (libtool --version) < /dev/null > /dev/null 2>&1 || { - echo - echo "**Error**: You must have \`libtool' installed." - echo "You can get it from: ftp://ftp.gnu.org/pub/gnu/" - DIE=1 - } -} - -(grep "^AM_GLIB_GNU_GETTEXT" $srcdir/configure.ac >/dev/null) && { - (grep "sed.*POTFILES" $srcdir/configure.ac) > /dev/null || \ - (glib-gettextize --version) < /dev/null > /dev/null 2>&1 || { - echo - echo "**Error**: You must have \`glib' installed." - echo "You can get it from: ftp://ftp.gtk.org/pub/gtk" - DIE=1 - } -} - -(automake --version) < /dev/null > /dev/null 2>&1 || { - echo - echo "**Error**: You must have \`automake' installed." - echo "You can get it from: ftp://ftp.gnu.org/pub/gnu/" - DIE=1 - NO_AUTOMAKE=yes -} - - -# if no automake, don't bother testing for aclocal -test -n "$NO_AUTOMAKE" || (aclocal --version) < /dev/null > /dev/null 2>&1 || { - echo - echo "**Error**: Missing \`aclocal'. The version of \`automake'" - echo "installed doesn't appear recent enough." - echo "You can get automake from ftp://ftp.gnu.org/pub/gnu/" - DIE=1 -} - -if test "$DIE" -eq 1; then - exit 1 -fi - -if test -z "$*"; then - echo "**Warning**: I am going to run \`configure' with no arguments." - echo "If you wish to pass any to it, please specify them on the" - echo \`$0\'" command line." - echo -fi - -case $CC in -xlc ) - am_opt=--include-deps;; -esac - -for coin in `find $srcdir -path $srcdir/CVS -prune -o -name configure.ac -print` -do - dr=`dirname $coin` - if test -f $dr/NO-AUTO-GEN; then - echo skipping $dr -- flagged as no auto-gen - else - echo processing $dr - ( cd $dr - - aclocalinclude="$ACLOCAL_FLAGS" - - if grep "^AM_GLIB_GNU_GETTEXT" configure.ac >/dev/null; then - echo "Creating $dr/aclocal.m4 ..." - test -r $dr/aclocal.m4 || touch $dr/aclocal.m4 - echo "Running glib-gettextize... Ignore non-fatal messages." - echo "no" | glib-gettextize --force --copy - echo "Making $dr/aclocal.m4 writable ..." - test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4 - fi - if grep "^IT_PROG_INTLTOOL" configure.ac >/dev/null; then - echo "Running intltoolize..." - intltoolize --copy --force --automake - fi - if grep "^AM_PROG_XML_I18N_TOOLS" configure.ac >/dev/null; then - echo "Running xml-i18n-toolize..." - xml-i18n-toolize --copy --force --automake - fi - if grep "^LT_INIT" configure.ac >/dev/null; then - if test -z "$NO_LIBTOOLIZE" ; then - echo "Running libtoolize..." - libtoolize --force --copy - fi - fi - echo "Running aclocal $aclocalinclude ..." - aclocal $aclocalinclude - if grep "^A[CM]_CONFIG_HEADER" configure.ac >/dev/null; then - echo "Running autoheader..." - autoheader - fi - echo "Running automake --gnu $am_opt ..." - automake --add-missing --gnu $am_opt - echo "Running autoconf ..." - autoconf - ) - fi -done - -if test x$NOCONFIGURE = x; then - echo Running $srcdir/configure "$@" ... - $srcdir/configure "$@" \ - && echo Now type \`make\' to compile. || exit 1 -else - echo Skipping configure process. -fi diff --git a/buildout.cfg b/buildout.cfg deleted file mode 100644 index 1a4ad910..00000000 --- a/buildout.cfg +++ /dev/null @@ -1,56 +0,0 @@ -# Using buildout to install glances (thx to Benoit !) -# -# Install system dependancies (debian example with python2.7 pinned from wheezy) -# $ sudo apt-get install build-essential python2.7-dev python-psutil -# -# Bootstrap buildout -# $ mkdir glances -# $ cd glances -# $ wget http://svn.zope.org/*checkout*/zc.buildout/trunk/bootstrap/bootstrap.py -# $ python2.7 bootstrap.py -d -# $ bin/buildout -# -# Run glances ! -# $ bin/glances -# -# Note: Having a console script entry point in setup.py will be cleanner that -# defining it in buildout and avoid the dirty extra-path (anyone know a -# cleanner solution ?) -# See http://guide.python-distribute.org/creation.html#entry-points - -[buildout] -parts = - psutil-src - psutil-install - glances - -include-site-packages = false -allowed-eggs-from-site-packages = false - -[config] -glances_version = 1.3.7 -psutil_version = 0.4.1 -psutil_download_url = http://psutil.googlecode.com/files - -[psutil-src] -recipe = hexagonit.recipe.download -url = ${config:psutil_download_url}/psutil-${config:psutil_version}.tar.gz - -[psutil-install] -recipe= iw.recipe.cmd -on_install = true -cmds = - cd ${buildout:directory}/parts/psutil-src/psutil-${config:psutil_version} - ${buildout:executable} setup.py install - -[glances] -recipe=zc.recipe.egg -extra-paths = - ${buildout:eggs-directory}/Glances-${config:glances_version}-py2.7.egg/src/ - -entry-points = glances=glances:main - -eggs = - glances == ${config:glances_version} - psutil == ${config:psutil_version} - diff --git a/config.h.in b/config.h.in deleted file mode 100644 index dd3b02c5..00000000 --- a/config.h.in +++ /dev/null @@ -1,25 +0,0 @@ -/* config.h.in. Generated from configure.ac by autoheader. */ - -/* Name of package */ -#undef PACKAGE - -/* Define to the address where bug reports for this package should be sent. */ -#undef PACKAGE_BUGREPORT - -/* Define to the full name of this package. */ -#undef PACKAGE_NAME - -/* Define to the full name and version of this package. */ -#undef PACKAGE_STRING - -/* Define to the one symbol short name of this package. */ -#undef PACKAGE_TARNAME - -/* Define to the home page for this package. */ -#undef PACKAGE_URL - -/* Define to the version of this package. */ -#undef PACKAGE_VERSION - -/* Version number of package */ -#undef VERSION diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 8c2806c0..00000000 --- a/configure.ac +++ /dev/null @@ -1,23 +0,0 @@ -dnl Process this file with autoconf to produce a configure script. -dnl Created by Anjuta application wizard. - -AC_INIT(Glances, 1.4b, , glances) - -AC_CONFIG_HEADERS([config.h]) - -AC_CONFIG_MACRO_DIR([m4]) - -AM_INIT_AUTOMAKE([1.11]) - -AM_SILENT_RULES([yes]) - -AM_PATH_PYTHON([2.6]) - -dnl AX_PYTHON_MODULE([psutil],[needed]) - -AC_OUTPUT([ -Makefile -src/Makefile -man/Makefile - -]) diff --git a/src/__init__.py b/glances/__init__.py similarity index 100% rename from src/__init__.py rename to glances/__init__.py diff --git a/src/glances.py b/glances/glances.py similarity index 100% rename from src/glances.py rename to glances/glances.py diff --git a/m4/ax_python_module.m4 b/m4/ax_python_module.m4 deleted file mode 100644 index 8db0287a..00000000 --- a/m4/ax_python_module.m4 +++ /dev/null @@ -1,50 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_python_module.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_PYTHON_MODULE(modname[, fatal]) -# -# DESCRIPTION -# -# Checks for Python module. -# -# If fatal is non-empty then absence of a module will trigger an error. -# -# LICENSE -# -# Copyright (c) 2008 Andrew Collier -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 5 - -AU_ALIAS([AC_PYTHON_MODULE], [AX_PYTHON_MODULE]) -AC_DEFUN([AX_PYTHON_MODULE],[ - if test -z $PYTHON; - then - PYTHON="python" - fi - PYTHON_NAME=`basename $PYTHON` - AC_MSG_CHECKING($PYTHON_NAME module: $1) - $PYTHON -c "import $1" 2>/dev/null - if test $? -eq 0; - then - AC_MSG_RESULT(yes) - eval AS_TR_CPP(HAVE_PYMOD_$1)=yes - else - AC_MSG_RESULT(no) - eval AS_TR_CPP(HAVE_PYMOD_$1)=no - # - if test -n "$2" - then - AC_MSG_ERROR(failed to find required module $1) - exit 1 - fi - fi -]) - diff --git a/man/Makefile.am b/man/Makefile.am deleted file mode 100644 index 677f5a02..00000000 --- a/man/Makefile.am +++ /dev/null @@ -1 +0,0 @@ -man_MANS = glances.1 diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index fcc77c6d..813b5521 --- a/setup.py +++ b/setup.py @@ -1,25 +1,32 @@ #!/usr/bin/env python -import os -from distutils.core import setup +from glob import glob -# Utility function to read the README file. -# Used for the long_description. It's nice, because now 1) we have a top level -# README file and 2) it's easier to type in the README file than to put a raw -# string in below ... -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() +from setuptools import setup -setup( name='Glances', - version='1.4b', - download_url='https://github.com/downloads/nicolargo/glances/glances-1.4b.tar.gz', - url='https://github.com/nicolargo/glances', - description='CLI curses-based monitoring tool', - author='Nicolas Hennion', - author_email='nicolas@nicolargo.com', - license = "LGPL", - keywords = "cli curse monitoring system", - long_description=read('README'), - packages=['src'], - install_requires=['psutil>=0.4.1'] +setup(name='Glances', + version='1.4b', + download_url='https://github.com/downloads/nicolargo/glances/glances-1.4b.tar.gz', + url='https://github.com/nicolargo/glances', + description='CLI curses-based monitoring tool', + author='Nicolas Hennion', + author_email='nicolas@nicolargo.com', + license="LGPL", + keywords="cli curses monitoring system", + long_description=open('README').read(), + install_requires=['psutil>=0.4.1'], + packages=['glances'], + include_package_data=True, + data_files=[ + ('share/man/man1', ['man/glances.1']), + ('share/doc/glances', ['README', + 'README-fr', + 'COPYING', + 'AUTHORS', + 'ChangeLog', + 'NEWS', + 'screenshot.png']), + ('share/doc/glances/doc', glob('doc/*.png')), + ], + entry_points={"console_scripts": ["glances = glances.glances:main"]}, ) diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index 5801d758..00000000 --- a/src/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -## Process this file with automake to produce Makefile.in -## Created by Anjuta - -## The main script -bin_SCRIPTS = glances.py - -## Directory where .class files will be installed -glancesdir = $(pythondir)/glances - -glances_PYTHON = \ - glances.py diff --git a/src/Makefile.in b/src/Makefile.in deleted file mode 100644 index 68cdb078..00000000 --- a/src/Makefile.in +++ /dev/null @@ -1,423 +0,0 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. -# @configure_input@ - -# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. -# This Makefile.in is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - -@SET_MAKE@ - -VPATH = @srcdir@ -pkgdatadir = $(datadir)/@PACKAGE@ -pkgincludedir = $(includedir)/@PACKAGE@ -pkglibdir = $(libdir)/@PACKAGE@ -pkglibexecdir = $(libexecdir)/@PACKAGE@ -am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd -install_sh_DATA = $(install_sh) -c -m 644 -install_sh_PROGRAM = $(install_sh) -c -install_sh_SCRIPT = $(install_sh) -c -INSTALL_HEADER = $(INSTALL_DATA) -transform = $(program_transform_name) -NORMAL_INSTALL = : -PRE_INSTALL = : -POST_INSTALL = : -NORMAL_UNINSTALL = : -PRE_UNINSTALL = : -POST_UNINSTALL = : -subdir = src -DIST_COMMON = $(glances_PYTHON) $(srcdir)/Makefile.am \ - $(srcdir)/Makefile.in -ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 -am__aclocal_m4_deps = $(top_srcdir)/configure.ac -am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ - $(ACLOCAL_M4) -mkinstalldirs = $(install_sh) -d -CONFIG_HEADER = $(top_builddir)/config.h -CONFIG_CLEAN_FILES = -CONFIG_CLEAN_VPATH_FILES = -am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; -am__vpath_adj = case $$p in \ - $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ - *) f=$$p;; \ - esac; -am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; -am__install_max = 40 -am__nobase_strip_setup = \ - srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` -am__nobase_strip = \ - for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" -am__nobase_list = $(am__nobase_strip_setup); \ - for p in $$list; do echo "$$p $$p"; done | \ - sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ - $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ - if (++n[$$2] == $(am__install_max)) \ - { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ - END { for (dir in files) print dir, files[dir] }' -am__base_list = \ - sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ - sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' -am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(glancesdir)" -SCRIPTS = $(bin_SCRIPTS) -AM_V_GEN = $(am__v_GEN_$(V)) -am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) -am__v_GEN_0 = @echo " GEN " $@; -AM_V_at = $(am__v_at_$(V)) -am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) -am__v_at_0 = @ -SOURCES = -DIST_SOURCES = -py_compile = $(top_srcdir)/py-compile -DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) -ACLOCAL = @ACLOCAL@ -AMTAR = @AMTAR@ -AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ -AUTOCONF = @AUTOCONF@ -AUTOHEADER = @AUTOHEADER@ -AUTOMAKE = @AUTOMAKE@ -AWK = @AWK@ -CYGPATH_W = @CYGPATH_W@ -DEFS = @DEFS@ -ECHO_C = @ECHO_C@ -ECHO_N = @ECHO_N@ -ECHO_T = @ECHO_T@ -INSTALL = @INSTALL@ -INSTALL_DATA = @INSTALL_DATA@ -INSTALL_PROGRAM = @INSTALL_PROGRAM@ -INSTALL_SCRIPT = @INSTALL_SCRIPT@ -INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ -LIBOBJS = @LIBOBJS@ -LIBS = @LIBS@ -LTLIBOBJS = @LTLIBOBJS@ -MAKEINFO = @MAKEINFO@ -MKDIR_P = @MKDIR_P@ -PACKAGE = @PACKAGE@ -PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ -PACKAGE_NAME = @PACKAGE_NAME@ -PACKAGE_STRING = @PACKAGE_STRING@ -PACKAGE_TARNAME = @PACKAGE_TARNAME@ -PACKAGE_URL = @PACKAGE_URL@ -PACKAGE_VERSION = @PACKAGE_VERSION@ -PATH_SEPARATOR = @PATH_SEPARATOR@ -PYTHON = @PYTHON@ -PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ -PYTHON_PLATFORM = @PYTHON_PLATFORM@ -PYTHON_PREFIX = @PYTHON_PREFIX@ -PYTHON_VERSION = @PYTHON_VERSION@ -SET_MAKE = @SET_MAKE@ -SHELL = @SHELL@ -STRIP = @STRIP@ -VERSION = @VERSION@ -abs_builddir = @abs_builddir@ -abs_srcdir = @abs_srcdir@ -abs_top_builddir = @abs_top_builddir@ -abs_top_srcdir = @abs_top_srcdir@ -am__leading_dot = @am__leading_dot@ -am__tar = @am__tar@ -am__untar = @am__untar@ -bindir = @bindir@ -build_alias = @build_alias@ -builddir = @builddir@ -datadir = @datadir@ -datarootdir = @datarootdir@ -docdir = @docdir@ -dvidir = @dvidir@ -exec_prefix = @exec_prefix@ -host_alias = @host_alias@ -htmldir = @htmldir@ -includedir = @includedir@ -infodir = @infodir@ -install_sh = @install_sh@ -libdir = @libdir@ -libexecdir = @libexecdir@ -localedir = @localedir@ -localstatedir = @localstatedir@ -mandir = @mandir@ -mkdir_p = @mkdir_p@ -oldincludedir = @oldincludedir@ -pdfdir = @pdfdir@ -pkgpyexecdir = @pkgpyexecdir@ -pkgpythondir = @pkgpythondir@ -prefix = @prefix@ -program_transform_name = @program_transform_name@ -psdir = @psdir@ -pyexecdir = @pyexecdir@ -pythondir = @pythondir@ -sbindir = @sbindir@ -sharedstatedir = @sharedstatedir@ -srcdir = @srcdir@ -sysconfdir = @sysconfdir@ -target_alias = @target_alias@ -top_build_prefix = @top_build_prefix@ -top_builddir = @top_builddir@ -top_srcdir = @top_srcdir@ -bin_SCRIPTS = glances.py -glancesdir = $(pythondir)/glances -glances_PYTHON = \ - glances.py - -all: all-am - -.SUFFIXES: -$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) - @for dep in $?; do \ - case '$(am__configure_deps)' in \ - *$$dep*) \ - ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ - && { if test -f $@; then exit 0; else break; fi; }; \ - exit 1;; \ - esac; \ - done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ - $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu src/Makefile -.PRECIOUS: Makefile -Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status - @case '$?' in \ - *config.status*) \ - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ - *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ - esac; - -$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh - -$(top_srcdir)/configure: $(am__configure_deps) - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh -$(ACLOCAL_M4): $(am__aclocal_m4_deps) - cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh -$(am__aclocal_m4_deps): -install-binSCRIPTS: $(bin_SCRIPTS) - @$(NORMAL_INSTALL) - test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" - @list='$(bin_SCRIPTS)'; test -n "$(bindir)" || list=; \ - for p in $$list; do \ - if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ - if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ - done | \ - sed -e 'p;s,.*/,,;n' \ - -e 'h;s|.*|.|' \ - -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ - $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ - { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ - if ($$2 == $$4) { files[d] = files[d] " " $$1; \ - if (++n[d] == $(am__install_max)) { \ - print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ - else { print "f", d "/" $$4, $$1 } } \ - END { for (d in files) print "f", d, files[d] }' | \ - while read type dir files; do \ - if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ - test -z "$$files" || { \ - echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(bindir)$$dir'"; \ - $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ - } \ - ; done - -uninstall-binSCRIPTS: - @$(NORMAL_UNINSTALL) - @list='$(bin_SCRIPTS)'; test -n "$(bindir)" || exit 0; \ - files=`for p in $$list; do echo "$$p"; done | \ - sed -e 's,.*/,,;$(transform)'`; \ - test -n "$$list" || exit 0; \ - echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(bindir)" && rm -f $$files -install-glancesPYTHON: $(glances_PYTHON) - @$(NORMAL_INSTALL) - test -z "$(glancesdir)" || $(MKDIR_P) "$(DESTDIR)$(glancesdir)" - @list='$(glances_PYTHON)'; dlist=; list2=; test -n "$(glancesdir)" || list=; \ - for p in $$list; do \ - if test -f "$$p"; then b=; else b="$(srcdir)/"; fi; \ - if test -f $$b$$p; then \ - $(am__strip_dir) \ - dlist="$$dlist $$f"; \ - list2="$$list2 $$b$$p"; \ - else :; fi; \ - done; \ - for file in $$list2; do echo $$file; done | $(am__base_list) | \ - while read files; do \ - echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(glancesdir)'"; \ - $(INSTALL_DATA) $$files "$(DESTDIR)$(glancesdir)" || exit $$?; \ - done || exit $$?; \ - if test -n "$$dlist"; then \ - if test -z "$(DESTDIR)"; then \ - PYTHON=$(PYTHON) $(py_compile) --basedir "$(glancesdir)" $$dlist; \ - else \ - PYTHON=$(PYTHON) $(py_compile) --destdir "$(DESTDIR)" --basedir "$(glancesdir)" $$dlist; \ - fi; \ - else :; fi - -uninstall-glancesPYTHON: - @$(NORMAL_UNINSTALL) - @list='$(glances_PYTHON)'; test -n "$(glancesdir)" || list=; \ - files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ - test -n "$$files" || exit 0; \ - filesc=`echo "$$files" | sed 's|$$|c|'`; \ - fileso=`echo "$$files" | sed 's|$$|o|'`; \ - echo " ( cd '$(DESTDIR)$(glancesdir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(glancesdir)" && rm -f $$files || exit $$?; \ - echo " ( cd '$(DESTDIR)$(glancesdir)' && rm -f" $$filesc ")"; \ - cd "$(DESTDIR)$(glancesdir)" && rm -f $$filesc || exit $$?; \ - echo " ( cd '$(DESTDIR)$(glancesdir)' && rm -f" $$fileso ")"; \ - cd "$(DESTDIR)$(glancesdir)" && rm -f $$fileso -tags: TAGS -TAGS: - -ctags: CTAGS -CTAGS: - - -distdir: $(DISTFILES) - @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - list='$(DISTFILES)'; \ - dist_files=`for file in $$list; do echo $$file; done | \ - sed -e "s|^$$srcdirstrip/||;t" \ - -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ - case $$dist_files in \ - */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ - sort -u` ;; \ - esac; \ - for file in $$dist_files; do \ - if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ - if test -d $$d/$$file; then \ - dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ - if test -d "$(distdir)/$$file"; then \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ - cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ - else \ - test -f "$(distdir)/$$file" \ - || cp -p $$d/$$file "$(distdir)/$$file" \ - || exit 1; \ - fi; \ - done -check-am: all-am -check: check-am -all-am: Makefile $(SCRIPTS) -installdirs: - for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(glancesdir)"; do \ - test -z "$$dir" || $(MKDIR_P) "$$dir"; \ - done -install: install-am -install-exec: install-exec-am -install-data: install-data-am -uninstall: uninstall-am - -install-am: all-am - @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am - -installcheck: installcheck-am -install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install -mostlyclean-generic: - -clean-generic: - -distclean-generic: - -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) - -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) - -maintainer-clean-generic: - @echo "This command is intended for maintainers to use" - @echo "it deletes files that may require special tools to rebuild." -clean: clean-am - -clean-am: clean-generic mostlyclean-am - -distclean: distclean-am - -rm -f Makefile -distclean-am: clean-am distclean-generic - -dvi: dvi-am - -dvi-am: - -html: html-am - -html-am: - -info: info-am - -info-am: - -install-data-am: install-glancesPYTHON - -install-dvi: install-dvi-am - -install-dvi-am: - -install-exec-am: install-binSCRIPTS - -install-html: install-html-am - -install-html-am: - -install-info: install-info-am - -install-info-am: - -install-man: - -install-pdf: install-pdf-am - -install-pdf-am: - -install-ps: install-ps-am - -install-ps-am: - -installcheck-am: - -maintainer-clean: maintainer-clean-am - -rm -f Makefile -maintainer-clean-am: distclean-am maintainer-clean-generic - -mostlyclean: mostlyclean-am - -mostlyclean-am: mostlyclean-generic - -pdf: pdf-am - -pdf-am: - -ps: ps-am - -ps-am: - -uninstall-am: uninstall-binSCRIPTS uninstall-glancesPYTHON - -.MAKE: install-am install-strip - -.PHONY: all all-am check check-am clean clean-generic distclean \ - distclean-generic distdir dvi dvi-am html html-am info info-am \ - install install-am install-binSCRIPTS install-data \ - install-data-am install-dvi install-dvi-am install-exec \ - install-exec-am install-glancesPYTHON install-html \ - install-html-am install-info install-info-am install-man \ - install-pdf install-pdf-am install-ps install-ps-am \ - install-strip installcheck installcheck-am installdirs \ - maintainer-clean maintainer-clean-generic mostlyclean \ - mostlyclean-generic pdf pdf-am ps ps-am uninstall uninstall-am \ - uninstall-binSCRIPTS uninstall-glancesPYTHON - - -# Tell versions [3.59,3.63) of GNU make to not export all variables. -# Otherwise a system limit (for SysV at least) may be exceeded. -.NOEXPORT: From 91adde8917b3b07b285d8a21d7479bef5020a8f9 Mon Sep 17 00:00:00 2001 From: Laurent Bachelier Date: Wed, 14 Mar 2012 00:14:02 +0100 Subject: [PATCH 18/57] Simple building and packaging of i18n --- MANIFEST.in | 1 + Makefile | 10 ++++++++++ i18n_espanol_generate.sh | 16 ---------------- i18n_francais_generate.sh | 16 ---------------- setup.py | 27 ++++++++++++++++----------- 5 files changed, 27 insertions(+), 43 deletions(-) create mode 100644 Makefile delete mode 100755 i18n_espanol_generate.sh delete mode 100755 i18n_francais_generate.sh diff --git a/MANIFEST.in b/MANIFEST.in index 50401c68..4f6c1a04 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,3 +7,4 @@ include NEWS include screenshot.png recursive-include doc *.png recursive-include man *.1 +recursive-include i18n *.mo diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..3de27b5d --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +MSGFMT = msgfmt -v + +.SUFFIXES: .po .mo + +MSGOBJ := $(patsubst %.po,%.mo,$(wildcard i18n/*/LC_MESSAGES/*.po)) + +.po.mo: + $(MSGFMT) -o $@ $< + +all: $(MSGOBJ) diff --git a/i18n_espanol_generate.sh b/i18n_espanol_generate.sh deleted file mode 100755 index 2d924b38..00000000 --- a/i18n_espanol_generate.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -### Generating Spanish Locale - -echo "Para instalar correctamente la traducción española,\n" -echo "debe ejecutar este script como root o con sudo.\n" -echo "\n\n" - -echo "Traducción de JeanBoB \n\n" - - -echo "Generación del idioma española...\n" -msgfmt i18n/es/LC_MESSAGES/glances.po -o i18n/es/LC_MESSAGES/glances.mo -echo "Instalación en el siguiente directorio: /usr/share/locale/es/LC_MESSAGES/\n" -cp i18n/es/LC_MESSAGES/glances.mo /usr/share/locale/es/LC_MESSAGES/glances.mo -echo "¡Instalación terminado!\n\n" \ No newline at end of file diff --git a/i18n_francais_generate.sh b/i18n_francais_generate.sh deleted file mode 100755 index a201f936..00000000 --- a/i18n_francais_generate.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -### Generating French Locale - -echo "Pour réussir l'installation de la localisation française,\n" -echo "vous devez executer ce script sous l'utilisateur root ou avec sudo.\n" -echo "\n\n" - -echo "Traduction par JeanBoB \n\n" - - -echo "Genération de la langue française...\n" -msgfmt i18n/fr/LC_MESSAGES/glances.po -o i18n/fr/LC_MESSAGES/glances.mo -echo "Installation dans le répertoire /usr/share/locale/fr/LC_MESSAGES/\n" -cp i18n/fr/LC_MESSAGES/glances.mo /usr/share/locale/fr/LC_MESSAGES/glances.mo -echo "Installation terminée\n\n" \ No newline at end of file diff --git a/setup.py b/setup.py index 813b5521..2a875b23 100755 --- a/setup.py +++ b/setup.py @@ -1,9 +1,24 @@ #!/usr/bin/env python from glob import glob +from os.path import dirname from setuptools import setup +data_files = [ + ('share/man/man1', ['man/glances.1']), + ('share/doc/glances', ['README', + 'README-fr', + 'COPYING', + 'AUTHORS', + 'ChangeLog', + 'NEWS', + 'screenshot.png']), + ('share/doc/glances/doc', glob('doc/*.png')), +] +for mo in glob('i18n/*/LC_MESSAGES/*.mo'): + data_files.append((dirname(mo).replace('i18n/', 'share/locale/'), [mo])) + setup(name='Glances', version='1.4b', download_url='https://github.com/downloads/nicolargo/glances/glances-1.4b.tar.gz', @@ -17,16 +32,6 @@ setup(name='Glances', install_requires=['psutil>=0.4.1'], packages=['glances'], include_package_data=True, - data_files=[ - ('share/man/man1', ['man/glances.1']), - ('share/doc/glances', ['README', - 'README-fr', - 'COPYING', - 'AUTHORS', - 'ChangeLog', - 'NEWS', - 'screenshot.png']), - ('share/doc/glances/doc', glob('doc/*.png')), - ], + data_files=data_files, entry_points={"console_scripts": ["glances = glances.glances:main"]}, ) From f6f69d475c23670731ec09c3ea488b2e382e58f5 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 14 Mar 2012 11:10:38 +0100 Subject: [PATCH 19/57] Update documentation: use setup.py for installation --- NEWS | 1 + README | 14 ++---------- README-fr | 65 +++++++++++++++++++++++++++---------------------------- README.md | 14 ++---------- TODO | 2 +- 5 files changed, 38 insertions(+), 58 deletions(-) diff --git a/NEWS b/NEWS index 50d8b695..8957bfc3 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Version 1.4 =========== * Goodby StatGrab... Welcome to the PsUtil library ! + * No more autotools, use setup.py to install * Sort by Process name ('p' key) * Only major stats (CPU, Load and memory) use background colors * Improve operating system name diff --git a/README b/README index 54c5699c..15e9547b 100644 --- a/README +++ b/README @@ -55,25 +55,15 @@ Glances use a standard GNU style installer: $ tar zxvf glances-1.4.tar.gz $ cd glances-1.4 - $ ./configure - $ make - $ sudo make install + $ sudo python setup.py install Pre-requisites: * Python 2.6+ (not tested with Python 3+) -* psutil 0.4.1+ (did NOT work with psutil < 0.2 ) - -Notes: For Debian and Ubuntu < 12.04 -The officials repos only include the psutil version 0.2.1. -You had to install the version 0.4.1 using the following commands: - - $ sudo apt-get install python-dev python-pip - $ sudo pip install --upgrade psutil ## Running -Easy: +Easy way (that's all folks !): $ glances.py diff --git a/README-fr b/README-fr index 1507bb21..f2cdd918 100644 --- a/README-fr +++ b/README-fr @@ -20,6 +20,32 @@ Le processus de packaging est actuellement en cours sur d'autres distribs, je vous conseille donc de rechercher Glances sur votre gestionnaire de package avant de faire une installation depuis les sources. +### Depuis le PPA (pour les systèmes Ubuntu/Mint...) + +Arnaud Hartmann (thanks to him !) maintient un PPA avec la dernière version: + +Pour installer le PPA: + + $ sudo add-apt-repository ppa:arnaud-hartmann/glances-dev + $ sudo apt-get update + +Pour installer Glances à partir de ce PPA: + + $ sudo apt-get install glances + +### Depuis PyPi + +PyPi est un gestionnaire officiel des paquets Python. +Il est disponible sous la plupart des distibutions GNU/Linux. + +On commence par installer PyPi sur son système (par exemple Debian/Ubuntu): + + $ sudo apt-get install python-pip + +Puis on installe la dernière version de Glances: + + $ sudo pip install glances + ### Depuis les sources Le projet Glances est hébergé sur GitHUB: https://github.com/nicolargo/glances @@ -27,47 +53,20 @@ Le projet Glances est hébergé sur GitHUB: https://github.com/nicolargo/glances Pour l'installer, il suffit de suivre les instructions suivantes depuis un terminal. -Récupération de la dernière version (1.3.7): +Récupération de la dernière version (1.4): - $ wget https://github.com/downloads/nicolargo/glances/glances-1.3.7.tar.gz + $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz Procédez ensuite à l'installation: - $ tar zxvf glances-1.3.7.tar.gz - $ cd glances-1.3.7 - $ ./configure - $ make - $ sudo make install + $ tar zxvf glances-1.4.tar.gz + $ cd glances-1.4 + $ sudo python setup.py install + Glances a besoin des dépendances suivantes: * Python 2.6+ (non testé avec Python 3+) -* libstatgrab 0.16+ -* python-statgrab 0.5+ (ne marchera PAS avec python-statgrab 0.4) - -Notes specifiques pour une installation sous Debian 6. -Debian Squeeze met à disposition la version 0.4 de python-statgrab. -Il faut donc installer la version 0.5 à la main: - - $ sudo apt-get install libstatgrab-dev pkg-config python-dev make - $ wget http://ftp.uk.i-scream.org/sites/ftp.i-scream.org/pub/i-scream/pystatgrab/pystatgrab-0.5.tar.gz - $ tar zxvf pystatgrab-0.5.tar.gz - $ cd pystatgrab-0.5/ - $ ./setup.py build - $ sudo ./setup.py install - -Notes specifiques pour une installation sous Ubuntu 10.04 et 10.10. -Ces versions d'Ubuntu mettent à disposition la version 0.4 de python-statgrab. -Il faut donc installer la version 0.5 à la main: - - $ sudo apt-get update - $ sudo apt-get install pkg-config build-essential autoconf automake python libstatgrab-dev python-all-dev - $ sudo apt-get remove python-statgrab - $ wget http://ftp.uk.i-scream.org/sites/ftp.i-scream.org/pub/i-scream/pystatgrab/pystatgrab-0.5.tar.gz - $ tar zxvf pystatgrab-0.5.tar.gz - $ cd pystatgrab-0.5/ - $ ./setup.py build - $ sudo ./setup.py install ## Lancement de Glances diff --git a/README.md b/README.md index 54c5699c..15e9547b 100644 --- a/README.md +++ b/README.md @@ -55,25 +55,15 @@ Glances use a standard GNU style installer: $ tar zxvf glances-1.4.tar.gz $ cd glances-1.4 - $ ./configure - $ make - $ sudo make install + $ sudo python setup.py install Pre-requisites: * Python 2.6+ (not tested with Python 3+) -* psutil 0.4.1+ (did NOT work with psutil < 0.2 ) - -Notes: For Debian and Ubuntu < 12.04 -The officials repos only include the psutil version 0.2.1. -You had to install the version 0.4.1 using the following commands: - - $ sudo apt-get install python-dev python-pip - $ sudo pip install --upgrade psutil ## Running -Easy: +Easy way (that's all folks !): $ glances.py diff --git a/TODO b/TODO index 84804ad7..c4ae30bc 100644 --- a/TODO +++ b/TODO @@ -1 +1 @@ -- Packaging for .deb (Debian|Ubuntu|Mint) Linux distributions (contributors needed) +- Packaging for Debian (contributors needed) From 67fdbe9520ac2fe8e89bcf4694984b05ef539d38 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 14 Mar 2012 11:17:14 +0100 Subject: [PATCH 20/57] Add additionnal file for HTML output --- glances/css/default.css | 130 ++++++++++++++++++++++++++++++++++++++ glances/html/base.html | 39 ++++++++++++ glances/html/default.html | 98 ++++++++++++++++++++++++++++ glances/img/bg.png | Bin 0 -> 20623 bytes 4 files changed, 267 insertions(+) create mode 100644 glances/css/default.css create mode 100644 glances/html/base.html create mode 100644 glances/html/default.html create mode 100644 glances/img/bg.png diff --git a/glances/css/default.css b/glances/css/default.css new file mode 100644 index 00000000..6ec91d7b --- /dev/null +++ b/glances/css/default.css @@ -0,0 +1,130 @@ +/* +Reset the sheet +*/ + +* { margin: 0; padding: 0; } +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } +[hidden] { display: none; } + +/* +Colors table +* bg: background color +* fg: foreground color +*/ + +.bgmain { background: transparent; } +.fgmain { color: #FFFFFF; } + +.bghost { background: transparent; } +.fghost { color: #E3D7BF; font-size: 50px; text-shadow: 1px 1px 1px #e88860, 2px 2px 1px #e88860, 3px 3px 1px #e88860, 3px 3px 1px #0c0d0d, 4px 4px 3px #0c0d0d;} + +.bgsystem { background: transparent; } +.fgsystem { color: #E88860; text-shadow: 1px 1px 1px #000; } + +.bgcpu { background: transparent; } +.fgcpu { color: #3C8AAD; } + +.bgload { background: transparent; } +.fgload { color: #3C8AAD; } + +.bgmem { background: transparent; } +.fgmem { color: #3C8AAD; } + +.bgcdefault { background: transparent; } +.fgcdefault { color: #FFFFFF; } +.bgcok { background: #60AC39; } +.fgcok { color: #FFFFFF; } +.bgccareful { background: #6039AC; } +.fgccareful { color: #FFFFFF; } +.bgcwarning { background: #FFAA00; } +.fgcwarning { color: #FFFFFF; } +.bgcritical { background: #D92626; } +.fgcritical { color: #FFFFFF; } + +/* +Main +*/ + +html { + background-image: url('../img/bg.png'); + font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; +} +html, button, input, select, textarea { font-family: sans-serif; } +body { margin: 0; font-size: 1em; line-height: 1.4; } +::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } +::selection { background: #fe57a1; color: #fff; text-shadow: none; } + +/* Tables */ + +table{ + font-family: 'Trebuchet MS', sans-serif; + font-size: 14px; + font-weight: bold; + font-style: normal; + border-collapse: separate; +} + +thead th{ + padding:10px; + border:1px solid #3C8AAD; + -webkit-border-top-left-radius:5px; + -webkit-border-top-right-radius:5px; + -moz-border-radius:5px 5px 0px 0px; + border-top-left-radius:5px; + border-top-right-radius:5px; +} + +thead th:empty{ + background:transparent; + border:none; +} + +tfoot td{ + font-size:16px; + text-align:center; + padding:10px 0px; +} + +tfoot th{ +} + +tbody td{ + width: 80px; + padding:10px; + text-align:center; + border:1px solid #3C8AAD; + -moz-border-radius:2px; + -webkit-border-radius:2px; + border-radius:2px; +} + +/* Header */ + +header { + text-align: center; + margin-bottom: 50px; +} + +/* Main */ + +#main { + text-align: center; +} + +/* First line stats: CLM (CPU, LOAD, MEM) */ + +#clm { + margin: 0 auto; +} + +#cpu, #load, #mem { + display: inline-block; + vertical-align: middle; +} + +#cpu, #load { + margin-right: 50px; +} + + +/* Footer */ diff --git a/glances/html/base.html b/glances/html/base.html new file mode 100644 index 00000000..0a408295 --- /dev/null +++ b/glances/html/base.html @@ -0,0 +1,39 @@ + + + + + {% block head %} + + {% block title %}Glances{% endblock %} + + {% endblock %} + + + + + +
+ {% block header %}{% endblock %} +
+ +
+
+
+ {% block cpu %}{% endblock %} +
+
+ {% block load %}{% endblock %} +
+
+ {% block mem %}{% endblock %} +
+
+
+ +
+ {% block footer %}{% endblock %} +
+ + + + diff --git a/glances/html/default.html b/glances/html/default.html new file mode 100644 index 00000000..40342ef2 --- /dev/null +++ b/glances/html/default.html @@ -0,0 +1,98 @@ +{% extends "base.html" %} + +{% block css %}css/default.css{% endblock %} + +{% block header %} +

Glances running on {{ host.hostname }}

+

{{ system.os_name }} {{ system.platform }} {{ system.os_version }}

+{% endblock %} + +{% block cpu %} + {% if cpu is defined %} + + + + + + + + + + + + + + + + + + + + + +
Cpu {{ (cpu.user + cpu.kernel + cpu.nice)|round(1) }}%
User{{ cpu.user|round(1) }}
Kernel{{ cpu.kernel|round(1) }}
Nice{{ cpu.nice|round(1) }}
+ {% endif %} +{% endblock %} + +{% block load %} + {% if (load is defined) and (core is defined) %} + + + + + + + + + + + + + + + + + + + + + +
Load {{ core }}-Core
1 min{{ load.min1|round(2) }}
5 mins{{ load.min5|round(2) }}
15 mins{{ load.min15|round(2) }}
+ {% endif %} +{% endblock %} + +{% block mem %} + {% if (mem is defined) and (memswap is defined) %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MemSwapReal
Total{{ mem.total|filesizeformat(binary = true) }}{{ memswap.total|filesizeformat(binary = true) }}
Used{{ mem.used|filesizeformat(binary = true) }}{{ memswap.used|filesizeformat(binary = true) }}{{ (mem.used-mem.cache)|filesizeformat(binary = true) }}
Free{{ mem.free|filesizeformat(binary = true) }}{{ memswap.free|filesizeformat(binary = true) }}{{ (mem.free+mem.cache)|filesizeformat(binary = true) }}
+ {% endif %} +{% endblock %} + diff --git a/glances/img/bg.png b/glances/img/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..3b0bb7e372ea6bc992090c5e9f4b56d93adecf13 GIT binary patch literal 20623 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
keP8W-XGwbT%8qLFjsV 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 Date: Wed, 14 Mar 2012 18:45:05 +0100 Subject: [PATCH 21/57] Include package data files --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 4f6c1a04..73946636 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,3 +8,4 @@ include screenshot.png recursive-include doc *.png recursive-include man *.1 recursive-include i18n *.mo +recursive-include glances *.html *.css *.png From 1e59f60e6ade34dd07882d0c60238964bc9ae725 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 14 Mar 2012 20:59:48 +0100 Subject: [PATCH 22/57] Add the MANIFEST.in --- README.md | 257 +----------------------------------------------------- 1 file changed, 1 insertion(+), 256 deletions(-) mode change 100644 => 120000 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 15e9547b..00000000 --- a/README.md +++ /dev/null @@ -1,256 +0,0 @@ -[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) - -============================= -Glances -- Eye on your system -============================= - -## Description - -Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. - -Glances uses the PsUtil library to get information from your system. - -It is developed in Python. - -![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) - -## Installation - -### From package manager (very easy way) - -Packages exist for Arch, Fedora, Redhat, FreeBSD... - -### From PPA (easy way for Ubuntu/Mint...) - -Arnaud Hartmann (thanks to him !) maintains a PPA with the latest Glances version: - -To install the PPA just enter: - - $ sudo add-apt-repository ppa:arnaud-hartmann/glances-dev - $ sudo apt-get update - -Then install Glances: - - $ sudo apt-get install glances - -### From PyPi (easy way) - -PyPi is an official Python package manager. - -You first need to install pypi on your system. For exemple on Debian/Ubuntu: - - $ sudo apt-get install python-pip - -Then install the latest Glances version: - - $ sudo pip install glances - -### From source - -Get the latest version: - - $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz - -Glances use a standard GNU style installer: - - $ tar zxvf glances-1.4.tar.gz - $ cd glances-1.4 - $ sudo python setup.py install - -Pre-requisites: - -* Python 2.6+ (not tested with Python 3+) - -## Running - -Easy way (that's all folks !): - - $ glances.py - -## User guide - -By default, stats are refreshed every second, to change this setting, you can -use the -t option. For exemple to set the refrech rate to 5 seconds: - - $ glances.py -t 5 - -Importants stats are colored: - -* GREEN: stat counter is "OK" -* BLUE: stat counter is "CAREFUL" -* MAGENTA: stat counter is "WARNING" -* RED: stat counter is "CRITICAL" - -When Glances is running, you can press: - -* 'h' to display an help message whith the keys you can press -* 'a' to set the automatic mode. The processes are sorted automatically - - If CPU > 70%, sort by process "CPU consumption" - - If MEM > 70%, sort by process "memory size" - -* 'c' to sort the processes list by CPU consumption -* 'd' Disable or enable the disk IO stats -* 'f' Disable or enable the file system stats -* 'l' Disable or enable the logs -* 'm' to sort the processes list by process size -* 'n' Disable or enable the network interfaces stats -* 'q' Exit - -### Header - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) - -The header shows the Glances version, the host name and the operating -system name, version and architecture. - -### CPU - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) - -The CPU states are shown as a percentage and for the configured refresh -time. - -If user|kernel|nice CPU is < 50%, then status is set to "OK". - -If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". - -If user|kernel|nice CPU is > 70%, then status is set to "WARNING". - -If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". - -### Load - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) - -On the Nosheep blog, Zach defines the average load: "In short it is the -average sum of the number of processes waiting in the run-queue plus the -number currently executing over 1, 5, and 15 minute time periods." - -Glances gets the number of CPU cores to adapt the alerts. With Glances, -alerts on average load are only set on 5 and 15 mins. - -If average load is < O.7*Core, then status is set to "OK". - -If average load is > O.7*Core, then status is set to "CAREFUL". - -If average load is > 1*Core, then status is set to "WARNING". - -If average load is > 5*Core, then status is set to "CRITICAL". - -### Memory - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) - -Glances uses tree columns: memory (RAM), swap and "real". - -Real used memory is: used - cache. - -Real free memory is: free + cache. - -With Glances, alerts are only set for on used swap and real memory. - -If memory is < 50%, then status is set to "OK". - -If memory is > 50%, then status is set to "CAREFUL". - -If memory is > 70%, then status is set to "WARNING". - -If memory is > 90%, then status is set to "CRITICAL". - -### Network bit rate - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) - -Glances display the network interface bit rate. The unit is adapted -dynamicaly (bits per second, Kbits per second, Mbits per second...). - -Alerts are set only if the network interface maximum speed is available. - -If bitrate is < 50%, then status is set to "OK". - -If bitrate is > 50%, then status is set to "CAREFUL". - -If bitrate is > 70%, then status is set to "WARNING". - -If bitrate is > 90%, then status is set to "CRITICAL". - -For exemple, on a 100 Mbps Ethernet interface, the warning status is set -if the bit rate is higher than 70 Mbps. - -### Disk I/O - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) - -Glances display the disk I/O throughput. The unit is adapted dynamicaly -(bytes per second, Kbytes per second, Mbytes per second...). - -There is no alert on this information. - -### Filesystem - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) - -Glances display the total and used filesytem disk space. The unit is -adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per -second...). - -Alerts are set for used disk space: - -If disk used is < 50%, then status is set to "OK". - -If disk used is > 50%, then status is set to "CAREFUL". - -If disk used is > 70%, then status is set to "WARNING". - -If disk used is > 90%, then status is set to "CRITICAL". - -### Processes - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) - -Glances displays a summary and a list of processes. - -By default (or if you hit the 'a' key) the process list is automaticaly -sorted by CPU of memory consumption. - -The number of processes in the list is adapted to the screen size. - -### Logs - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) - -A logs list is displayed in the bottom of the screen if (an only if): - -* at least one WARNING or CRITICAL alert was occured. -* space is available in the bottom of the console/terminal - -There is one line per alert with the following information: - -* start date -* end date -* alert name -* (min/avg/max) values - -### Footer - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) - -Glances displays a caption and the current time/date. - -## Localisation - -To generate french locale execute as root or sudo : -i18n_francais_generate.sh - -To generate spanish locale execute as root or sudo : -i18n_espanol_generate.sh - -## Todo - -You are welcome to contribute to this software. - -* Packaging for Debian, Ubuntu, BSD... -* Check the needed Python library in the configure.ac -* Add file system stats when the python-statgrab is corrected diff --git a/README.md b/README.md new file mode 120000 index 00000000..100b9382 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +README \ No newline at end of file From 78efbd1101df63030dc1b6edf5650c5b211602dd Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 14 Mar 2012 21:37:11 +0100 Subject: [PATCH 23/57] Add CLI option to manage HTML output --- glances/glances.py | 50 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/glances/glances.py b/glances/glances.py index adc38054..1fcc8034 100755 --- a/glances/glances.py +++ b/glances/glances.py @@ -22,7 +22,7 @@ from __future__ import generators __appname__ = 'glances' -__version__ = "1.4b13" +__version__ = "1.4b14" __author__ = "Nicolas Hennion " __licence__ = "LGPL" @@ -493,7 +493,18 @@ class glancesStats(): self.process_all.append(proc) # Grab stats from process list for proc in self.process_all[:]: - if (proc.is_running()): + try: + if (not proc.is_running()): + try: + self.process_all.remove(proc) + except: + pass + except psutil.error.NoSuchProcess: + try: + self.process_all.remove(proc) + except: + pass + else: # Global stats try: self.processcount[str(proc.status)] += 1 @@ -513,11 +524,6 @@ class glancesStats(): self.process.append(procstat) except: pass - else: - try: - self.process_all.remove(proc) - except: - pass # If it is the first grab then empty process list if (self.process_first_grab): self.process = [] @@ -1439,9 +1445,10 @@ def printSyntax(): printVersion() print _("Usage: glances.py [-t|--time sec] [-h|--help] [-v|--version]") print "" - print _("\t-h:\tDisplay the syntax and exit") - print _("\t-t sec:\tSet the refresh time in second default is 1") - print _("\t-v:\tDisplay the version and exit") + print _("\t-h:\t\tDisplay the syntax and exit") + print _("\t-o output:\tGenerate output (available: html)") + print _("\t-t sec:\t\tSet the refresh time in second default is 1") + print _("\t-v:\t\tDisplay the version and exit") print "" print _("When Glances is running, you can press:") print _("'a' to set the automatic mode. The processes are sorted automatically") @@ -1459,13 +1466,18 @@ def printSyntax(): def init(): global limits, logs, stats, screen, html + global html_tag global refresh_time + # Set default tags + html_tag = False + + # Set the default refresh time refresh_time = 1 # Manage args try: - opts, args = getopt.getopt(sys.argv[1:], "ht:v", ["help", "time", "version"]) + opts, args = getopt.getopt(sys.argv[1:], "ho:t:v", ["help", "output", "time", "version"]) except getopt.GetoptError, err: # Print help information and exit: print str(err) @@ -1475,6 +1487,16 @@ def init(): if opt in ("-v", "--version"): printVersion() sys.exit(0) + elif opt in ("-o", "--output"): + if arg == "html": + if (jinja_tag): + html_tag = True + else: + print _("Error: Need the Jinja library to export into HTML") + sys.exit(2) + else: + print _("Error: Unknown output %s" % arg) + sys.exit(2) elif opt in ("-t", "--time"): if int(arg) >= 1: refresh_time = int(arg) @@ -1501,7 +1523,8 @@ def init(): screen = glancesScreen(refresh_time) # Init HTML output - html = glancesHtml(refresh_time) + if (html_tag): + html = glancesHtml(refresh_time) def main(): @@ -1517,7 +1540,8 @@ def main(): screen.update(stats) # Update the HTML output - html.update(stats) + if (html_tag): + html.update(stats) def end(): From 0063dab724602c30c3e8990742cdd1e37181950e Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 16 Mar 2012 13:43:51 +0100 Subject: [PATCH 24/57] Correct for issue #37 --- README.md | 257 ++++++++++++++++++++++++++++++++++++++++++++- glances/glances.py | 8 +- 2 files changed, 262 insertions(+), 3 deletions(-) mode change 120000 => 100644 README.md diff --git a/README.md b/README.md deleted file mode 120000 index 100b9382..00000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -README \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..15e9547b --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) + +============================= +Glances -- Eye on your system +============================= + +## Description + +Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. + +Glances uses the PsUtil library to get information from your system. + +It is developed in Python. + +![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) + +## Installation + +### From package manager (very easy way) + +Packages exist for Arch, Fedora, Redhat, FreeBSD... + +### From PPA (easy way for Ubuntu/Mint...) + +Arnaud Hartmann (thanks to him !) maintains a PPA with the latest Glances version: + +To install the PPA just enter: + + $ sudo add-apt-repository ppa:arnaud-hartmann/glances-dev + $ sudo apt-get update + +Then install Glances: + + $ sudo apt-get install glances + +### From PyPi (easy way) + +PyPi is an official Python package manager. + +You first need to install pypi on your system. For exemple on Debian/Ubuntu: + + $ sudo apt-get install python-pip + +Then install the latest Glances version: + + $ sudo pip install glances + +### From source + +Get the latest version: + + $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz + +Glances use a standard GNU style installer: + + $ tar zxvf glances-1.4.tar.gz + $ cd glances-1.4 + $ sudo python setup.py install + +Pre-requisites: + +* Python 2.6+ (not tested with Python 3+) + +## Running + +Easy way (that's all folks !): + + $ glances.py + +## User guide + +By default, stats are refreshed every second, to change this setting, you can +use the -t option. For exemple to set the refrech rate to 5 seconds: + + $ glances.py -t 5 + +Importants stats are colored: + +* GREEN: stat counter is "OK" +* BLUE: stat counter is "CAREFUL" +* MAGENTA: stat counter is "WARNING" +* RED: stat counter is "CRITICAL" + +When Glances is running, you can press: + +* 'h' to display an help message whith the keys you can press +* 'a' to set the automatic mode. The processes are sorted automatically + + If CPU > 70%, sort by process "CPU consumption" + + If MEM > 70%, sort by process "memory size" + +* 'c' to sort the processes list by CPU consumption +* 'd' Disable or enable the disk IO stats +* 'f' Disable or enable the file system stats +* 'l' Disable or enable the logs +* 'm' to sort the processes list by process size +* 'n' Disable or enable the network interfaces stats +* 'q' Exit + +### Header + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) + +The header shows the Glances version, the host name and the operating +system name, version and architecture. + +### CPU + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) + +The CPU states are shown as a percentage and for the configured refresh +time. + +If user|kernel|nice CPU is < 50%, then status is set to "OK". + +If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". + +If user|kernel|nice CPU is > 70%, then status is set to "WARNING". + +If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". + +### Load + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) + +On the Nosheep blog, Zach defines the average load: "In short it is the +average sum of the number of processes waiting in the run-queue plus the +number currently executing over 1, 5, and 15 minute time periods." + +Glances gets the number of CPU cores to adapt the alerts. With Glances, +alerts on average load are only set on 5 and 15 mins. + +If average load is < O.7*Core, then status is set to "OK". + +If average load is > O.7*Core, then status is set to "CAREFUL". + +If average load is > 1*Core, then status is set to "WARNING". + +If average load is > 5*Core, then status is set to "CRITICAL". + +### Memory + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) + +Glances uses tree columns: memory (RAM), swap and "real". + +Real used memory is: used - cache. + +Real free memory is: free + cache. + +With Glances, alerts are only set for on used swap and real memory. + +If memory is < 50%, then status is set to "OK". + +If memory is > 50%, then status is set to "CAREFUL". + +If memory is > 70%, then status is set to "WARNING". + +If memory is > 90%, then status is set to "CRITICAL". + +### Network bit rate + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) + +Glances display the network interface bit rate. The unit is adapted +dynamicaly (bits per second, Kbits per second, Mbits per second...). + +Alerts are set only if the network interface maximum speed is available. + +If bitrate is < 50%, then status is set to "OK". + +If bitrate is > 50%, then status is set to "CAREFUL". + +If bitrate is > 70%, then status is set to "WARNING". + +If bitrate is > 90%, then status is set to "CRITICAL". + +For exemple, on a 100 Mbps Ethernet interface, the warning status is set +if the bit rate is higher than 70 Mbps. + +### Disk I/O + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) + +Glances display the disk I/O throughput. The unit is adapted dynamicaly +(bytes per second, Kbytes per second, Mbytes per second...). + +There is no alert on this information. + +### Filesystem + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) + +Glances display the total and used filesytem disk space. The unit is +adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per +second...). + +Alerts are set for used disk space: + +If disk used is < 50%, then status is set to "OK". + +If disk used is > 50%, then status is set to "CAREFUL". + +If disk used is > 70%, then status is set to "WARNING". + +If disk used is > 90%, then status is set to "CRITICAL". + +### Processes + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) + +Glances displays a summary and a list of processes. + +By default (or if you hit the 'a' key) the process list is automaticaly +sorted by CPU of memory consumption. + +The number of processes in the list is adapted to the screen size. + +### Logs + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) + +A logs list is displayed in the bottom of the screen if (an only if): + +* at least one WARNING or CRITICAL alert was occured. +* space is available in the bottom of the console/terminal + +There is one line per alert with the following information: + +* start date +* end date +* alert name +* (min/avg/max) values + +### Footer + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) + +Glances displays a caption and the current time/date. + +## Localisation + +To generate french locale execute as root or sudo : +i18n_francais_generate.sh + +To generate spanish locale execute as root or sudo : +i18n_espanol_generate.sh + +## Todo + +You are welcome to contribute to this software. + +* Packaging for Debian, Ubuntu, BSD... +* Check the needed Python library in the configure.ac +* Add file system stats when the python-statgrab is corrected diff --git a/glances/glances.py b/glances/glances.py index 1fcc8034..1f8e9d45 100755 --- a/glances/glances.py +++ b/glances/glances.py @@ -22,7 +22,7 @@ from __future__ import generators __appname__ = 'glances' -__version__ = "1.4b14" +__version__ = "1.4b15" __author__ = "Nicolas Hennion " __licence__ = "LGPL" @@ -508,7 +508,11 @@ class glancesStats(): # Global stats try: self.processcount[str(proc.status)] += 1 - except: + except psutil.error.NoSuchProcess: + # Process non longer exist + pass + except KeyError: + # Key did not exist, create it self.processcount[str(proc.status)] = 1 finally: self.processcount['total'] += 1 From 8737e3764d26e2f46cbf907a902b5d42246e5311 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 17 Mar 2012 11:36:54 +0100 Subject: [PATCH 25/57] Minor change --- README.md | 257 +----------------------------------------------------- 1 file changed, 1 insertion(+), 256 deletions(-) mode change 100644 => 120000 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 15e9547b..00000000 --- a/README.md +++ /dev/null @@ -1,256 +0,0 @@ -[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) - -============================= -Glances -- Eye on your system -============================= - -## Description - -Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. - -Glances uses the PsUtil library to get information from your system. - -It is developed in Python. - -![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) - -## Installation - -### From package manager (very easy way) - -Packages exist for Arch, Fedora, Redhat, FreeBSD... - -### From PPA (easy way for Ubuntu/Mint...) - -Arnaud Hartmann (thanks to him !) maintains a PPA with the latest Glances version: - -To install the PPA just enter: - - $ sudo add-apt-repository ppa:arnaud-hartmann/glances-dev - $ sudo apt-get update - -Then install Glances: - - $ sudo apt-get install glances - -### From PyPi (easy way) - -PyPi is an official Python package manager. - -You first need to install pypi on your system. For exemple on Debian/Ubuntu: - - $ sudo apt-get install python-pip - -Then install the latest Glances version: - - $ sudo pip install glances - -### From source - -Get the latest version: - - $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz - -Glances use a standard GNU style installer: - - $ tar zxvf glances-1.4.tar.gz - $ cd glances-1.4 - $ sudo python setup.py install - -Pre-requisites: - -* Python 2.6+ (not tested with Python 3+) - -## Running - -Easy way (that's all folks !): - - $ glances.py - -## User guide - -By default, stats are refreshed every second, to change this setting, you can -use the -t option. For exemple to set the refrech rate to 5 seconds: - - $ glances.py -t 5 - -Importants stats are colored: - -* GREEN: stat counter is "OK" -* BLUE: stat counter is "CAREFUL" -* MAGENTA: stat counter is "WARNING" -* RED: stat counter is "CRITICAL" - -When Glances is running, you can press: - -* 'h' to display an help message whith the keys you can press -* 'a' to set the automatic mode. The processes are sorted automatically - - If CPU > 70%, sort by process "CPU consumption" - - If MEM > 70%, sort by process "memory size" - -* 'c' to sort the processes list by CPU consumption -* 'd' Disable or enable the disk IO stats -* 'f' Disable or enable the file system stats -* 'l' Disable or enable the logs -* 'm' to sort the processes list by process size -* 'n' Disable or enable the network interfaces stats -* 'q' Exit - -### Header - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) - -The header shows the Glances version, the host name and the operating -system name, version and architecture. - -### CPU - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) - -The CPU states are shown as a percentage and for the configured refresh -time. - -If user|kernel|nice CPU is < 50%, then status is set to "OK". - -If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". - -If user|kernel|nice CPU is > 70%, then status is set to "WARNING". - -If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". - -### Load - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) - -On the Nosheep blog, Zach defines the average load: "In short it is the -average sum of the number of processes waiting in the run-queue plus the -number currently executing over 1, 5, and 15 minute time periods." - -Glances gets the number of CPU cores to adapt the alerts. With Glances, -alerts on average load are only set on 5 and 15 mins. - -If average load is < O.7*Core, then status is set to "OK". - -If average load is > O.7*Core, then status is set to "CAREFUL". - -If average load is > 1*Core, then status is set to "WARNING". - -If average load is > 5*Core, then status is set to "CRITICAL". - -### Memory - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) - -Glances uses tree columns: memory (RAM), swap and "real". - -Real used memory is: used - cache. - -Real free memory is: free + cache. - -With Glances, alerts are only set for on used swap and real memory. - -If memory is < 50%, then status is set to "OK". - -If memory is > 50%, then status is set to "CAREFUL". - -If memory is > 70%, then status is set to "WARNING". - -If memory is > 90%, then status is set to "CRITICAL". - -### Network bit rate - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) - -Glances display the network interface bit rate. The unit is adapted -dynamicaly (bits per second, Kbits per second, Mbits per second...). - -Alerts are set only if the network interface maximum speed is available. - -If bitrate is < 50%, then status is set to "OK". - -If bitrate is > 50%, then status is set to "CAREFUL". - -If bitrate is > 70%, then status is set to "WARNING". - -If bitrate is > 90%, then status is set to "CRITICAL". - -For exemple, on a 100 Mbps Ethernet interface, the warning status is set -if the bit rate is higher than 70 Mbps. - -### Disk I/O - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) - -Glances display the disk I/O throughput. The unit is adapted dynamicaly -(bytes per second, Kbytes per second, Mbytes per second...). - -There is no alert on this information. - -### Filesystem - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) - -Glances display the total and used filesytem disk space. The unit is -adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per -second...). - -Alerts are set for used disk space: - -If disk used is < 50%, then status is set to "OK". - -If disk used is > 50%, then status is set to "CAREFUL". - -If disk used is > 70%, then status is set to "WARNING". - -If disk used is > 90%, then status is set to "CRITICAL". - -### Processes - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) - -Glances displays a summary and a list of processes. - -By default (or if you hit the 'a' key) the process list is automaticaly -sorted by CPU of memory consumption. - -The number of processes in the list is adapted to the screen size. - -### Logs - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) - -A logs list is displayed in the bottom of the screen if (an only if): - -* at least one WARNING or CRITICAL alert was occured. -* space is available in the bottom of the console/terminal - -There is one line per alert with the following information: - -* start date -* end date -* alert name -* (min/avg/max) values - -### Footer - -![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) - -Glances displays a caption and the current time/date. - -## Localisation - -To generate french locale execute as root or sudo : -i18n_francais_generate.sh - -To generate spanish locale execute as root or sudo : -i18n_espanol_generate.sh - -## Todo - -You are welcome to contribute to this software. - -* Packaging for Debian, Ubuntu, BSD... -* Check the needed Python library in the configure.ac -* Add file system stats when the python-statgrab is corrected diff --git a/README.md b/README.md new file mode 120000 index 00000000..100b9382 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +README \ No newline at end of file From 8bc4a4d52704d18c6722f4aa17642da6f4982ae4 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 21 Mar 2012 15:16:31 +0100 Subject: [PATCH 26/57] Add test to solve issue #45 --- README.md | 257 +++++++++++++++++++++++++++++++++++++- glances/css/default.css | 65 ++++++++-- glances/glances.py | 49 +++++++- glances/html/base.html | 23 +++- glances/html/default.html | 129 +++++++++++++++++++ 5 files changed, 508 insertions(+), 15 deletions(-) mode change 120000 => 100644 README.md diff --git a/README.md b/README.md deleted file mode 120000 index 100b9382..00000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -README \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..15e9547b --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nicolargo&url=https://github.com/nicolargo/glances&title=Glances&language=&tags=github&category=software) + +============================= +Glances -- Eye on your system +============================= + +## Description + +Glances is a CLI curses based monitoring tool for GNU/Linux and BSD OS. + +Glances uses the PsUtil library to get information from your system. + +It is developed in Python. + +![screenshot](https://github.com/nicolargo/glances/raw/master/screenshot.png) + +## Installation + +### From package manager (very easy way) + +Packages exist for Arch, Fedora, Redhat, FreeBSD... + +### From PPA (easy way for Ubuntu/Mint...) + +Arnaud Hartmann (thanks to him !) maintains a PPA with the latest Glances version: + +To install the PPA just enter: + + $ sudo add-apt-repository ppa:arnaud-hartmann/glances-dev + $ sudo apt-get update + +Then install Glances: + + $ sudo apt-get install glances + +### From PyPi (easy way) + +PyPi is an official Python package manager. + +You first need to install pypi on your system. For exemple on Debian/Ubuntu: + + $ sudo apt-get install python-pip + +Then install the latest Glances version: + + $ sudo pip install glances + +### From source + +Get the latest version: + + $ wget https://github.com/downloads/nicolargo/glances/glances-1.4.tar.gz + +Glances use a standard GNU style installer: + + $ tar zxvf glances-1.4.tar.gz + $ cd glances-1.4 + $ sudo python setup.py install + +Pre-requisites: + +* Python 2.6+ (not tested with Python 3+) + +## Running + +Easy way (that's all folks !): + + $ glances.py + +## User guide + +By default, stats are refreshed every second, to change this setting, you can +use the -t option. For exemple to set the refrech rate to 5 seconds: + + $ glances.py -t 5 + +Importants stats are colored: + +* GREEN: stat counter is "OK" +* BLUE: stat counter is "CAREFUL" +* MAGENTA: stat counter is "WARNING" +* RED: stat counter is "CRITICAL" + +When Glances is running, you can press: + +* 'h' to display an help message whith the keys you can press +* 'a' to set the automatic mode. The processes are sorted automatically + + If CPU > 70%, sort by process "CPU consumption" + + If MEM > 70%, sort by process "memory size" + +* 'c' to sort the processes list by CPU consumption +* 'd' Disable or enable the disk IO stats +* 'f' Disable or enable the file system stats +* 'l' Disable or enable the logs +* 'm' to sort the processes list by process size +* 'n' Disable or enable the network interfaces stats +* 'q' Exit + +### Header + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/header.png) + +The header shows the Glances version, the host name and the operating +system name, version and architecture. + +### CPU + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/cpu.png) + +The CPU states are shown as a percentage and for the configured refresh +time. + +If user|kernel|nice CPU is < 50%, then status is set to "OK". + +If user|kernel|nice CPU is > 50%, then status is set to "CAREFUL". + +If user|kernel|nice CPU is > 70%, then status is set to "WARNING". + +If user|kernel|nice CPU is > 90%, then status is set to "CRITICAL". + +### Load + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/load.png) + +On the Nosheep blog, Zach defines the average load: "In short it is the +average sum of the number of processes waiting in the run-queue plus the +number currently executing over 1, 5, and 15 minute time periods." + +Glances gets the number of CPU cores to adapt the alerts. With Glances, +alerts on average load are only set on 5 and 15 mins. + +If average load is < O.7*Core, then status is set to "OK". + +If average load is > O.7*Core, then status is set to "CAREFUL". + +If average load is > 1*Core, then status is set to "WARNING". + +If average load is > 5*Core, then status is set to "CRITICAL". + +### Memory + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/mem.png) + +Glances uses tree columns: memory (RAM), swap and "real". + +Real used memory is: used - cache. + +Real free memory is: free + cache. + +With Glances, alerts are only set for on used swap and real memory. + +If memory is < 50%, then status is set to "OK". + +If memory is > 50%, then status is set to "CAREFUL". + +If memory is > 70%, then status is set to "WARNING". + +If memory is > 90%, then status is set to "CRITICAL". + +### Network bit rate + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/network.png) + +Glances display the network interface bit rate. The unit is adapted +dynamicaly (bits per second, Kbits per second, Mbits per second...). + +Alerts are set only if the network interface maximum speed is available. + +If bitrate is < 50%, then status is set to "OK". + +If bitrate is > 50%, then status is set to "CAREFUL". + +If bitrate is > 70%, then status is set to "WARNING". + +If bitrate is > 90%, then status is set to "CRITICAL". + +For exemple, on a 100 Mbps Ethernet interface, the warning status is set +if the bit rate is higher than 70 Mbps. + +### Disk I/O + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/diskio.png) + +Glances display the disk I/O throughput. The unit is adapted dynamicaly +(bytes per second, Kbytes per second, Mbytes per second...). + +There is no alert on this information. + +### Filesystem + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/fs.png) + +Glances display the total and used filesytem disk space. The unit is +adapted dynamicaly (bytes per second, Kbytes per second, Mbytes per +second...). + +Alerts are set for used disk space: + +If disk used is < 50%, then status is set to "OK". + +If disk used is > 50%, then status is set to "CAREFUL". + +If disk used is > 70%, then status is set to "WARNING". + +If disk used is > 90%, then status is set to "CRITICAL". + +### Processes + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/processlist.png) + +Glances displays a summary and a list of processes. + +By default (or if you hit the 'a' key) the process list is automaticaly +sorted by CPU of memory consumption. + +The number of processes in the list is adapted to the screen size. + +### Logs + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/logs.png) + +A logs list is displayed in the bottom of the screen if (an only if): + +* at least one WARNING or CRITICAL alert was occured. +* space is available in the bottom of the console/terminal + +There is one line per alert with the following information: + +* start date +* end date +* alert name +* (min/avg/max) values + +### Footer + +![screenshot](https://github.com/nicolargo/glances/raw/master/doc/footer.png) + +Glances displays a caption and the current time/date. + +## Localisation + +To generate french locale execute as root or sudo : +i18n_francais_generate.sh + +To generate spanish locale execute as root or sudo : +i18n_espanol_generate.sh + +## Todo + +You are welcome to contribute to this software. + +* Packaging for Debian, Ubuntu, BSD... +* Check the needed Python library in the configure.ac +* Add file system stats when the python-statgrab is corrected diff --git a/glances/css/default.css b/glances/css/default.css index 6ec91d7b..01e219db 100644 --- a/glances/css/default.css +++ b/glances/css/default.css @@ -30,6 +30,18 @@ Colors table .bgmem { background: transparent; } .fgmem { color: #3C8AAD; } +.bgnet { background: transparent; } +.fgnet { color: #3C8AAD; } + +.bgdiskio { background: transparent; } +.fgdiskio { color: #3C8AAD; } + +.bgfs { background: transparent; } +.fgfs { color: #3C8AAD; } + +.bgproc { background: transparent; } +.fgproc { color: #3C8AAD; } + .bgcdefault { background: transparent; } .fgcdefault { color: #FFFFFF; } .bgcok { background: #60AC39; } @@ -65,7 +77,7 @@ table{ } thead th{ - padding:10px; + padding:5px; border:1px solid #3C8AAD; -webkit-border-top-left-radius:5px; -webkit-border-top-right-radius:5px; @@ -89,8 +101,8 @@ tfoot th{ } tbody td{ - width: 80px; - padding:10px; + width:80px; + padding:5px; text-align:center; border:1px solid #3C8AAD; -moz-border-radius:2px; @@ -98,11 +110,25 @@ tbody td{ border-radius:2px; } +#item{ + width:60px; + border:none; + text-align:right; + color: #8cf; +} + +#command{ + width:240px; + font-size:11px; + text-align:left; + color: #8cf; +} + /* Header */ header { text-align: center; - margin-bottom: 50px; + margin-bottom: 25px; } /* Main */ @@ -111,13 +137,28 @@ header { text-align: center; } -/* First line stats: CLM (CPU, LOAD, MEM) */ - -#clm { +#firstline, #secondline { margin: 0 auto; } -#cpu, #load, #mem { +#secondline { + display: inline-block; + vertical-align: top; + margin-top: 20px; +} + +#sideleft { + width: 310px; + float: left; + margin-right: 25px; +} + +#sideright { + width: 550px; + float: left; +} + +#cpu, #load, #mem, #net, #diskio, #fs { display: inline-block; vertical-align: middle; } @@ -126,5 +167,13 @@ header { margin-right: 50px; } +#diskio, #fs { + margin-top: 25px; +} + +#proclist { + margin-top: 50px; +} + /* Footer */ diff --git a/glances/glances.py b/glances/glances.py index 1f8e9d45..d8824907 100755 --- a/glances/glances.py +++ b/glances/glances.py @@ -22,7 +22,7 @@ from __future__ import generators __appname__ = 'glances' -__version__ = "1.4b15" +__version__ = "1.4b16" __author__ = "Nicolas Hennion " __licence__ = "LGPL" @@ -371,12 +371,46 @@ class glancesStats(): self.cputime_old except: self.cputime_old = psutil.cpu_times() - self.cputime_total_old = self.cputime_old.user+self.cputime_old.nice+self.cputime_old.system+self.cputime_old.idle+self.cputime_old.iowait+self.cputime_old.irq+self.cputime_old.softirq + self.cputime_total_old = self.cputime_old.user+self.cputime_old.system+self.cputime_old.idle + # Only available on some OS + try: + self.cputime_total_old += self.cputime_old.nice + except: + pass + try: + self.cputime_total_old += self.cputime_old.iowait + except: + pass + try: + self.cputime_total_old += self.cputime_old.irq + except: + pass + try: + self.cputime_total_old += self.cputime_old.softirq + except: + pass self.cpu = {} else: try: self.cputime_new = psutil.cpu_times() - self.cputime_total_new = self.cputime_new.user+self.cputime_new.nice+self.cputime_new.system+self.cputime_new.idle+self.cputime_new.iowait+self.cputime_new.irq+self.cputime_new.softirq + self.cputime_total_new = self.cputime_new.user+self.cputime_new.system+self.cputime_new.idle + # Only available on some OS + try: + self.cputime_total_new += self.cputime_new.nice + except: + pass + try: + self.cputime_total_new += self.cputime_new.iowait + except: + pass + try: + self.cputime_total_new += self.cputime_new.irq + except: + pass + try: + self.cputime_total_new += self.cputime_new.softirq + except: + pass percent = 100/(self.cputime_total_new-self.cputime_total_old) self.cpu = { 'kernel': (self.cputime_new.system-self.cputime_old.system)*percent, 'user': (self.cputime_new.user-self.cputime_old.user)*percent, @@ -1316,7 +1350,7 @@ class glancesHtml(): self.__refresh_time = refresh_time # Set the templates path - environment = jinja2.Environment(loader=jinja2.FileSystemLoader('html')) + environment = jinja2.Environment(loader=jinja2.FileSystemLoader('html'), extensions=['jinja2.ext.loopcontrols']) # Open the template self.template = environment.get_template('default.html') @@ -1413,7 +1447,12 @@ class glancesHtml(): load = self.__getLoadColor(stats.getLoad(), stats.getCore()), core = stats.getCore(), mem = self.__getMemColor(stats.getMem()), - memswap = self.__getMemSwapColor(stats.getMemSwap()) ) + memswap = self.__getMemSwapColor(stats.getMemSwap()), + net = stats.getNetwork(), + diskio = stats.getDiskIO(), + fs = stats.getFs(), + proccount = stats.getProcessCount(), + proclist = stats.getProcessList() ) # Write data into the file f.write(data) diff --git a/glances/html/base.html b/glances/html/base.html index 0a408295..50ef3cc6 100644 --- a/glances/html/base.html +++ b/glances/html/base.html @@ -17,7 +17,7 @@
-
+
{% block cpu %}{% endblock %}
@@ -28,6 +28,27 @@ {% block mem %}{% endblock %}
+
+
+
+ {% block net %}{% endblock %} +
+
+ {% block diskio %}{% endblock %} +
+
+ {% block fs %}{% endblock %} +
+
+
+
+ {% block proccount %}{% endblock %} +
+
+ {% block proclist %}{% endblock %} +
+
+