commit 870ecaac23c3c2a4cf2c1d30aee9d8633db657f0 Author: Sasha MOREL Date: Tue Feb 20 17:12:45 2018 +0100 Validation initiale diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8cffccc --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9cd90ba --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Qu'est-ce que c'est ? + +Il s'agit d'un moniteur instantanné. + +# Puis-je l'utiliser ? + +Tout à fait, la [licence](LICENSE) permet tout à fait une utilisation en toute occasion. + +# Comment l'utiliser ? + +Il faut installer les bibliothèques curses et psutil pour Python3, ainsi que le démon hddtemp qui doit écouter sur le port 7634. \ No newline at end of file diff --git a/core.py b/core.py new file mode 100644 index 0000000..4936b28 --- /dev/null +++ b/core.py @@ -0,0 +1,42 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import wrapper as start, init_pair as set_pair, color_pair as get_pair, COLOR_BLACK as black, COLOR_RED as red, COLOR_GREEN as green, COLOR_WHITE as white, COLOR_BLUE as blue, COLOR_YELLOW as yellow + +# C'est pour importer des modules du repertoire courant +from os.path import dirname +from sys import path as pythonpath +pythonpath.insert (0, dirname (__file__)) + +from utils import groupbythree, seconds_to_time, sleep_to_next_second +from modules import title, uptime, loadavg, ram, network, sensors, hddtemp, df, dnsmasq_bounds, user +modules = (title, uptime, loadavg, ram, network, sensors, hddtemp, df, dnsmasq_bounds, user) + +def Main (screen): + old_line = 0 + screen.clear () + set_pair (1, white, black) + set_pair (2, blue, black) + set_pair (3, green, black) + set_pair (4, red, black) + set_pair (5, yellow, black) + while True: + line = 0 + height, width = screen.getmaxyx () + for i in range (0, old_line + 1): + for j in range (0, width - 1): + screen.addstr (i, j, ' ', get_pair (1)) + for i in modules: + line = i.main (line, screen) + line += 1 + + screen.refresh () + old_line = line + try: + sleep_to_next_second () + except KeyboardInterrupt: + break + + +start (Main) +print ('Quit') \ No newline at end of file diff --git a/modules/df.py b/modules/df.py new file mode 100644 index 0000000..b4d55ab --- /dev/null +++ b/modules/df.py @@ -0,0 +1,83 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair +mountpoints = ( + '/', + ) + +# C'est pour importer des modules du repertoire parent +from os.path import dirname +from sys import path as pythonpath +pythonpath.insert (0, dirname (dirname (__file__))) +from utils import bytes_human + +def diskfree (path): + from os import statvfs + fs = statvfs (path) + def total (): + return (int (fs.f_blocks*fs.f_bsize)) + def percent (): + return (int ((fs.f_blocks - fs.f_bfree)/(fs.f_blocks*0.01))) + def used (): + return (int ((fs.f_blocks - fs.f_bfree)*fs.f_bsize)) + def free (): + return (int (fs.f_bavail * fs.f_frsize)) + return [path, total (), used (), free (), percent ()] + +def main (line, screen): + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Espace disque :', get_pair (1)) + line += 1 + + data = [] + for i in mountpoints: + data.append (diskfree (i)) + table=[] + for i in data: + table.append ((i[0], bytes_human (i [1]), bytes_human (i[2]), bytes_human (i[3]), i [4])) + max_lengths = [0, 0, 0, 0, 0] + for i in table: + length = len (i [0]) + if length > max_lengths [0]: + max_lengths [0] = length + length = len (i [1]) + if length > max_lengths [1]: + max_lengths [1] = length + length = len (i [2]) + if length > max_lengths [2]: + max_lengths [2] = length + length = len (i [3]) + if length > max_lengths [3]: + max_lengths [3] = length + length = len (str (i [4]) + '%') + if length > max_lengths [4]: + max_lengths [4] = length + for i in table: + x = 0 + f = '%-' + str (max_lengths [0]) + 's:' + screen.addstr (line, x, f%(i [0]), get_pair (2)) + x += max_lengths [0] + 2 + + f = '%' + str (max_lengths [1]) + 's' + screen.addstr (line, x, f%((i[1])), get_pair (3)) + x += max_lengths [1] + 3 + + f = '%' + str (max_lengths [2]) + 's' + screen.addstr (line, x, f%((i[2])), get_pair (3)) + x += max_lengths [2] + 3 + + f = '%' + str (max_lengths [3]) + 's' + screen.addstr (line, x, f%((i[3])), get_pair (3)) + x += max_lengths [3] + 3 + + f = '%' + str (max_lengths [4]) + 's' + if i[4] < 90: + screen.addstr (line, x, f%((i[4])), get_pair (3)) + else: + screen.addstr (line, x, f%((i[4])), get_pair (4)) + screen.addstr (line, x + max_lengths [4] + 1, '%', get_pair (1)) + x += max_lengths [4] + 5 + + line += 1 + return (line) \ No newline at end of file diff --git a/modules/dnsmasq_bounds.py b/modules/dnsmasq_bounds.py new file mode 100644 index 0000000..751878a --- /dev/null +++ b/modules/dnsmasq_bounds.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair +border = '|' +critical_bounds = 60 +bound_file = '/var/lib/misc/dnsmasq.leases' + +# C'est pour importer des modules du repertoire parent +from os.path import dirname +from sys import path as pythonpath +pythonpath.insert (0, dirname (dirname (__file__))) +from utils import seconds_to_time + +def dnsmasq_dhcpbounds (): + ''' + Return a tuple of the active leases and the remaining time in second + tuple format for each bound : + (remaining_time, mac, ip, hostname) + ''' + from csv import reader + from socket import inet_aton + from time import time + bounds1 = [] + for i in reader (open (bound_file), delimiter = ' '): + if i [0] != 'duid': + bounds1.append (i) + bounds2 = [] + for i in sorted (bounds1, key=lambda item: inet_aton(item[2])): + remain = int (i [0]) - int (time ()) + bounds2.append ((remain, i[1], i[2], i[3])) + return bounds2 + +def main (line, screen): + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Baux DHCP', get_pair (1)) + line += 1 + bounds = dnsmasq_dhcpbounds () + max_lengths = [0, 0, 0, 0] + border_length = len (border) + for i in bounds: + length = len (seconds_to_time (i [0])) + if length > max_lengths [0]: + max_lengths [0] = length + length = len (i [1]) + if length > max_lengths [1]: + max_lengths [1] = length + length = len (i [2]) + if length > max_lengths [2]: + max_lengths [2] = length + length = len (i [3]) + if length > max_lengths [3]: + max_lengths [3] = length + for i in bounds: + x=0 + if i [0] < critical_bounds: + screen.addstr (line, x, seconds_to_time (i [0]), get_pair (4)) + else: + screen.addstr (line, x, seconds_to_time (i [0]), get_pair (3)) + x += max_lengths [0] + 1 + screen.addstr (line, x, '|', get_pair (1)) + x += border_length + 1 + screen.addstr (line, x, i [1], get_pair (2)) + x += max_lengths [1] + 1 + screen.addstr (line, x, '|', get_pair (1)) + x += border_length + 1 + screen.addstr (line, x, i [2], get_pair (2)) + x += max_lengths [2] + 1 + screen.addstr (line, x, '|', get_pair (1)) + x += border_length + 1 + screen.addstr (line, x, i [3], get_pair (2)) + x += max_lengths [3] + 1 + line += 1 + return (line) \ No newline at end of file diff --git a/modules/hddtemp.py b/modules/hddtemp.py new file mode 100644 index 0000000..3bdcade --- /dev/null +++ b/modules/hddtemp.py @@ -0,0 +1,57 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair +hddtemp_addressport = ('localhost',7634) + +def hddtemp (): + ''' + Return an tuple of disks formated like (name, temp) where name is a string and temp a integer + ''' + from socket import socket, AF_INET, SOCK_STREAM + from re import sub + data = [] + tries = 0 + while True: + tries += 1 + s = socket(AF_INET, SOCK_STREAM) + s.connect(hddtemp_addressport) + d = s.recv(4096) + s.close() + data = d.decode ('utf-8').split('|') + if d != b'|': + break + temps = [] + index = 0 + while True: + try: + index += 1 + name = sub ('/dev/', '', data [index]) + index += 2 + temp = int (data [index]) + index +=2 + temps.append ((name, temp)) + except IndexError: + break + return (temps) + +def main (line, screen): + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Températures stockage :', get_pair (1)) + line += 1 + + data = hddtemp () + x = 0 + for i in data: + screen.addstr (line, x, i [0] + ':', get_pair (2)) + x += len (i [0]) + 2 + if i [1] < 45: + screen.addstr (line, x, str (i [1]), get_pair (3)) + else: + screen.addstr (line, x, str (i [1]), get_pair (4)) + x += 2 + screen.addstr (line, x, '°C', get_pair (1)) + x += 4 + line += 1 + + return (line) \ No newline at end of file diff --git a/modules/loadavg.py b/modules/loadavg.py new file mode 100644 index 0000000..2652008 --- /dev/null +++ b/modules/loadavg.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair + +def loadavg (sep=', '): + ''' + Returns average load formated separated by sep + ''' + from os import getloadavg as loadavg + load = loadavg () + return ('%.2f'%load [0] + sep + '%.2f'%load [1] + sep + '%.2f'%load [2]) + + +def main (line, screen): + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Charges moyennes (1, 5, 15 minutes) :', get_pair (1)) + screen.addstr (line, 41, loadavg (), get_pair (2)) + line += 1 + return (line) \ No newline at end of file diff --git a/modules/network.py b/modules/network.py new file mode 100644 index 0000000..e39a5b5 --- /dev/null +++ b/modules/network.py @@ -0,0 +1,90 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair +network_interfaces = ('eth0',) + +# C'est pour importer des modules du repertoire parent +from os.path import dirname +from sys import path as pythonpath +pythonpath.insert (0, dirname (dirname (__file__))) +from utils import groupbythree + +def netdevs(iface): + ''' + RX and TX statistics for the iface network interface + ''' + from collections import namedtuple + with open('/proc/net/dev') as f: + net_dump = f.readlines() + device_data={} + data = namedtuple('data',['rx','tx']) + for line in net_dump[2:]: + line = line.split(':') + device_data[line[0].strip()] = data(int (line[1].split()[0]), int (line[1].split()[8])) + return (device_data [iface].rx, device_data [iface].tx) + +def main (line, screen): + for i in ifaces: + i [2] = i [1] + i [1] = netdevs (i [0]) + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Réseau :', get_pair (1)) + line += 1 + + table = [] + for i in ifaces: + table.append ((i [0], groupbythree (i[1][0]), groupbythree (i[1][1]), groupbythree (i[1][0] - i[2][0]), groupbythree (i[1][1] - i[2][1]))) + max_lengths = [0, 0, 0, 0, 0] + for i in table: + length = len (i [0]) + if length > max_lengths [0]: + max_lengths [0] = length + length = len (i [1]) + if length > max_lengths [1]: + max_lengths [1] = length + length = len (i [2]) + if length > max_lengths [2]: + max_lengths [2] = length + length = len (i [3]) + if length > max_lengths [3]: + max_lengths [3] = length + length = len (i [4]) + if length > max_lengths [4]: + max_lengths [4] = length + for i in table: + x = 0 + f = '%-' + str (max_lengths [0]) + 's:' + screen.addstr (line, x, f%(i [0]), get_pair (2)) + x += max_lengths [0] + 2 + + screen.addstr (line, x, '↓', get_pair (1)) + f = '%' + str (max_lengths [1]) + 's' + screen.addstr (line, x + 2, f%((i[1])), get_pair (3)) + screen.addstr (line, x + 3 + max_lengths [1], 'o', get_pair (1)) + x += max_lengths [1] + 5 + + screen.addstr (line, x,'↑', get_pair (1)) + f = '%' + str (max_lengths [2]) + 's' + screen.addstr (line, x + 2, f%((i[2])), get_pair (3)) + screen.addstr (line, x + 3 + max_lengths [2], 'o', get_pair (1)) + x += max_lengths [2] + 5 + + screen.addstr (line, x, '↓', get_pair (1)) + f = '%' + str (max_lengths [3]) + 's' + screen.addstr (line, x + 2, f%((i[3])), get_pair (3)) + screen.addstr (line, x + 3 + max_lengths [3], 'o/s', get_pair (1)) + x += max_lengths [3] + 7 + + screen.addstr (line, x, '↑', get_pair (1)) + f = '%' + str (max_lengths [4]) + 's' + screen.addstr (line, x + 2, f%((i[4])), get_pair (3)) + screen.addstr (line, x + 3 + max_lengths [4], 'o/s', get_pair (1)) + x += max_lengths [4] + 7 + + line += 1 + return (line) + +ifaces = [] +for i in network_interfaces: + ifaces.append ([i, (0,0), (0,0)]) \ No newline at end of file diff --git a/modules/sensors.py b/modules/sensors.py new file mode 100644 index 0000000..872a543 --- /dev/null +++ b/modules/sensors.py @@ -0,0 +1,58 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair + +def sensors_value (sensor): + ''' + Returns the float value given by the sensor + To get the sensor's name, start the sensors executable + Warning, this function matches the first sensor named like that, hope that the good one + ''' + from os import popen + from re import findall + sensors = popen ('sensors') + data = '' + for i in sensors: + data += i + sensors_line = findall (sensor + '.*', data) [0] + value = findall ('[0-9][0-9\.]+', sensors_line) [0] + #value = findall ('[0-9]*', findall (':[ +]*[0-9]*', sensors_line) [0]) [0] + return (float (value)) + +def main (line, screen): + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Températures carte mère :', get_pair (1)) + line += 1 + + cpu = sensors_value ('CPU Temperature') + cpu_fan = sensors_value ('CPU FAN Speed') + mb = sensors_value ('MB Temperature') + power = sensors_value ('POWER FAN Speed') + + screen.addstr (line, 0, 'CPU:', get_pair (2)) + if cpu < 60: + screen.addstr (line, 5, '%2.0f'%cpu, get_pair (3)) + else: + screen.addstr (line, 5, '%2.0f'%cpu, get_pair (4)) + screen.addstr (line, 8, '°C', get_pair (1)) + screen.addstr (line, 12, 'MB:', get_pair (2)) + if mb < 45: + screen.addstr (line, 16, '%2.0f'%mb, get_pair (3)) + else: + screen.addstr (line, 16, '%2.0f'%mb, get_pair (4)) + screen.addstr (line, 19, '°C', get_pair (1)) + screen.addstr (line, 23, 'CPU fan:', get_pair (2)) + if cpu_fan < 7200 and cpu_fan > 800: + screen.addstr (line, 32, '%4.0f'%cpu_fan, get_pair (3)) + else: + screen.addstr (line, 32, '%4.0f'%cpu_fan, get_pair (4)) + screen.addstr (line, 37, 'RPM', get_pair (1)) + screen.addstr (line, 41, 'POWER fan:', get_pair (2)) + if power < 7200 and power > 1800: + screen.addstr (line, 52, '%4.0f'%power, get_pair (3)) + else: + screen.addstr (line, 52, '%4.0f'%power, get_pair (4)) + screen.addstr (line, 57, 'RPM', get_pair (1)) + line += 1 + return (line) \ No newline at end of file diff --git a/modules/title.py b/modules/title.py new file mode 100644 index 0000000..403731c --- /dev/null +++ b/modules/title.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair + +def main (line, screen): + from socket import gethostname as hostname + from time import strftime + from locale import setlocale, LC_ALL + setlocale (LC_ALL, 'fr_FR.UTF-8') + screen.addstr (line, 0, hostname () + ': ' + strftime ('%A %d %B %Y %T'), get_pair (1)) + line += 1 + return (line) \ No newline at end of file diff --git a/modules/uptime.py b/modules/uptime.py new file mode 100644 index 0000000..f2eab34 --- /dev/null +++ b/modules/uptime.py @@ -0,0 +1,23 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair + +# C'est pour importer des modules du repertoire parent +from os.path import dirname +from sys import path as pythonpath +pythonpath.insert (0, dirname (dirname (__file__))) +from utils import seconds_to_time + +def uptime (): + ''' + Returns formated uptime + ''' + return seconds_to_time (round (float (open ('/proc/uptime').readline ().split ()[0]))) + +def main (line, screen): + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Durée de fonctionnement :', get_pair (1)) + screen.addstr (line, 29, uptime (), get_pair (2)) + line += 1 + return (line) \ No newline at end of file diff --git a/modules/user.py b/modules/user.py new file mode 100644 index 0000000..d61742b --- /dev/null +++ b/modules/user.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from curses import color_pair as get_pair + +def main (line, screen): + from os import popen + screen.addstr (line, 0, '-->', get_pair (5)) + screen.addstr (line, 3, 'Utilisateurs', get_pair (1)) + line += 1 + + w = popen ('w') + users = [] + for i in w: + users.append (i) + for i in range (1, len (users)): + screen.addstr (line, 0, users [i], get_pair (1)) + line += 1 + + return (line) \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..ef9c78c --- /dev/null +++ b/utils.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +def groupbythree (integer, sep = ' '): + ''' + Group integer by three figures + Optionnal sep parameter is to define the char to put between groups and is space by default + ''' + from re import sub + return (sub (',', sep, '{:,}'.format (integer))) + +def seconds_to_time (time): + ''' + Converts a number of second time to formated time + ''' + def leading_zero (value): + return "%02d" % (value,) + days = int (time/86400) + hours = int ((time/3600) - (24*days)) + minutes = int ((time/60) - (1440*days) - (60*hours)) + seconds = int (time - (86400*days) - (3600*hours) - (60*minutes)) + if days == 0: + if hours == 0: + if minutes == 0: + return (str (leading_zero (seconds)) + 's') + else: + return (str (leading_zero (minutes)) + 'm ' + str (leading_zero (seconds)) + 's') + else: + return (str (leading_zero (hours)) + 'h ' + str (leading_zero (minutes)) + 'm ' + str (leading_zero (seconds)) + 's') + else: + return (str (days) + "j " + str (leading_zero (hours)) + 'h ' + str (leading_zero (minutes)) + 'm ' + str (leading_zero (seconds)) + 's') + +def sleep_to_next_second (): + from time import sleep, time + sleep (1-(time ()-int (time()))) + +def bytes_human (num): + for x in ['B','KiB','MiB','GiB','TiB']: + if num < 1024.0: + return "%3.1f %s" % (num, x) + num /= 1024.0 \ No newline at end of file