Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
c729e434ac |
3
dashboard/README.md
Normal file
3
dashboard/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Dashboard
|
||||||
|
|
||||||
|
Allows using a tablet, smartphone, ebook reader or any other low-power internet-connected hardware as system monitor for an host on the same network.
|
95
dashboard/dashboard.cfg.example
Normal file
95
dashboard/dashboard.cfg.example
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
|
||||||
|
# The webpage will be available at http://this.host.ip.address:PORT
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
# The webpage will be updated every REFRESH_SECONDS. Set to 0 to disable autorefresh.
|
||||||
|
REFRESH_SECONDS=1
|
||||||
|
|
||||||
|
#### SENSORS ####
|
||||||
|
# Every sensor value is obtained on a command being executed, its result being parsed with a regexp
|
||||||
|
# to extract (as a single group) the numeric or string value, and the value being used to plot the
|
||||||
|
# graph. This sensor definitions are ready to be used, just enable the ones you need.
|
||||||
|
# You can add your own declaring another section like this:
|
||||||
|
#
|
||||||
|
# [my_sensor]
|
||||||
|
# DISABLED=False
|
||||||
|
# COMMAND=/my/custom/binary --with parameters
|
||||||
|
# REGEXP=my regex to parse (awesome|disappointing) command output
|
||||||
|
# TYPE=TIMEGRAPH # May also be GRAPH or ERROR
|
||||||
|
|
||||||
|
[system_load_1min]
|
||||||
|
# The system load average in the last minute
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=uptime
|
||||||
|
REGEXP=.*load average: (\d+[,.]\d+), \d+[,.]\d+, \d+[,.]\d+
|
||||||
|
TYPE=TIMEGRAPH
|
||||||
|
|
||||||
|
[system_load_5min]
|
||||||
|
# The system load average in the last 5 minutes
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=uptime
|
||||||
|
REGEXP=.*load average: \d+[,.]\d+, (\d+[,.]\d+), \d+[,.]\d+
|
||||||
|
TYPE=TIMEGRAPH
|
||||||
|
|
||||||
|
[system_load_15min]
|
||||||
|
# The system load average in the last 15 minutes
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=uptime
|
||||||
|
REGEXP=.*load average: \d+[,.]\d+, \d+[,.]\d+, (\d+[,.]\d+)
|
||||||
|
TYPE=TIMEGRAPH
|
||||||
|
|
||||||
|
[used_disk_space]
|
||||||
|
# Used disk space in percent
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=df -h /dev/sda1
|
||||||
|
REGEXP=(\d{1,3})%
|
||||||
|
TYPE=GRAPH
|
||||||
|
|
||||||
|
[raid_status]
|
||||||
|
# Raid status
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=cat /proc/mdstat
|
||||||
|
REGEXP=.*\] \[([U_]+)\]\n
|
||||||
|
TYPE=ERROR
|
||||||
|
|
||||||
|
[laptop_charger_disconnected]
|
||||||
|
# Laptop charger disconnected
|
||||||
|
# For laptops used as servers, apparently common among the self hosters. Requires acpi package installed.
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=acpi -a
|
||||||
|
REGEXP=Adapter \d: (.+)
|
||||||
|
TYPE=ERROR
|
||||||
|
|
||||||
|
[free_ram]
|
||||||
|
# Free ram in %
|
||||||
|
# Shows another approach: does all the computation in the command and picks up
|
||||||
|
# all the output (by not declaring a regexp).
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=free | grep Mem | awk '{print int($4/$2 * 100.0)}'
|
||||||
|
TYPE=TIMEGRAPH
|
||||||
|
|
||||||
|
[available_ram]
|
||||||
|
# Like Free ram, but shows available instead of free. You may want to use this if you use a memcache.
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=free | grep Mem | awk '{print int($7/$2 * 100.0)}'
|
||||||
|
TYPE=TIMEGRAPH
|
||||||
|
|
||||||
|
[cpu_temperature]
|
||||||
|
# CPU Temperature alarm: requires lm-sensors installed and configured (check your distribution's guide)
|
||||||
|
# The regexp must be adapted to your configuration: run `sensors` in the command line
|
||||||
|
# to find the name of the temperature sensor in your system. In this case is `Core 0`,
|
||||||
|
# but may be called Tdie or a lot of different names, there is no standard.
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=sensors
|
||||||
|
REGEXP=Core 0: +\+?(-?\d{1,3}).\d°[CF]
|
||||||
|
TYPE=TIMEGRAPH
|
||||||
|
|
||||||
|
[fan_speed]
|
||||||
|
# Fan speed alarm: requires lm-sensors installed and configured (check your distribution's guide)
|
||||||
|
# The regexp must be adapted to your configuration: run `sensors` in the command line
|
||||||
|
# to find the name of the fan speed sensor in your system.
|
||||||
|
DISABLED=True
|
||||||
|
COMMAND=sensors
|
||||||
|
REGEXP=cpu_fan: +(\d) RPM
|
||||||
|
TYPE=TIMEGRAPH
|
117
dashboard/dashboard.py
Executable file
117
dashboard/dashboard.py
Executable file
@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
import re
|
||||||
|
import locale
|
||||||
|
import subprocess
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
|
""" @package docstring
|
||||||
|
Resources dashboard
|
||||||
|
|
||||||
|
Starts a webserver on a specific port of the monitored server and serves a simple webpage containing
|
||||||
|
the monitored sensors graphs.
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
- Copy dashboard.cfg in /usr/local/etc/dashboard.cfg and customize it
|
||||||
|
- Copy dashboard.py in /usr/local/bin/dashboard.py
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
Start the server:
|
||||||
|
/usr/local/bin/dashboard.py /usr/local/etc/dashboard.cfg
|
||||||
|
|
||||||
|
@author Daniele Verducci <daniele.verducci@ichibi.eu>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
NAME = 'dashboard'
|
||||||
|
VERSION = '0.1'
|
||||||
|
DESCRIPTION = 'A simple system resources dashboard'
|
||||||
|
|
||||||
|
|
||||||
|
class WebServer(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def __init__(self, configPath):
|
||||||
|
''' Sets up locale (needed for parsing numbers) '''
|
||||||
|
# Get system locale from $LANG (i.e. "en_GB.UTF-8")
|
||||||
|
systemLocale = os.getenv('LANG')
|
||||||
|
if not systemLocale:
|
||||||
|
raise ValueError('System environment variabile $LANG is not set!')
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_ALL, systemLocale)
|
||||||
|
|
||||||
|
''' Reads the config '''
|
||||||
|
self._log = logging.getLogger('main')
|
||||||
|
|
||||||
|
if not os.path.exists(configPath) or not os.path.isfile(configPath):
|
||||||
|
raise ValueError('configPath must be a file')
|
||||||
|
|
||||||
|
self.config = configparser.ConfigParser(interpolation=None) # Disable interpolation because contains regexp
|
||||||
|
self.config.read(configPath)
|
||||||
|
self.hostname = os.uname()[1]
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.readSensors()
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", "text/html")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
|
||||||
|
self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
|
||||||
|
self.wfile.write(bytes("<body>", "utf-8"))
|
||||||
|
self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
|
||||||
|
self.wfile.write(bytes("</body></html>", "utf-8"))
|
||||||
|
|
||||||
|
def readSensors():
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog = NAME + '.py',
|
||||||
|
description = NAME + ' ' + VERSION + '\n' + DESCRIPTION,
|
||||||
|
formatter_class = argparse.RawTextHelpFormatter
|
||||||
|
)
|
||||||
|
parser.add_argument('configFile', help="configuration file path")
|
||||||
|
parser.add_argument('-q', '--quiet', action='store_true', help="suppress non-essential output")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.quiet:
|
||||||
|
level = logging.WARNING
|
||||||
|
else:
|
||||||
|
level = logging.INFO
|
||||||
|
logging.basicConfig(level=level)
|
||||||
|
|
||||||
|
port =
|
||||||
|
httpd = HTTPServer(('localhost', port), Server)
|
||||||
|
logging.info('Serving on port {}'.format(port))
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logging.critical(traceback.format_exc())
|
||||||
|
print('ERROR: {}'.format(e))
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
httpd.server_close()
|
||||||
|
|
||||||
|
sys.exit(0)
|
@ -43,8 +43,6 @@ MAILTO=root@localhost, user@localhost
|
|||||||
# Every health check is based on a command being executed, its result being parsed with a regexp
|
# Every health check is based on a command being executed, its result being parsed with a regexp
|
||||||
# to extract (as a single group) the numeric or string value, and the value being compared with
|
# to extract (as a single group) the numeric or string value, and the value being compared with
|
||||||
# a configured value. This checks are ready to be used, just enable the ones you need.
|
# a configured value. This checks are ready to be used, just enable the ones you need.
|
||||||
#
|
|
||||||
# CUSTOM CHECKS:
|
|
||||||
# You can add your own custom check declaring another section like this:
|
# You can add your own custom check declaring another section like this:
|
||||||
#
|
#
|
||||||
# [my_custom_check_name]
|
# [my_custom_check_name]
|
||||||
@ -57,12 +55,6 @@ MAILTO=root@localhost, user@localhost
|
|||||||
# ALARM_VALUE_LESS_THAN=12
|
# ALARM_VALUE_LESS_THAN=12
|
||||||
# COMMAND=/my/custom/binary --with parameters
|
# COMMAND=/my/custom/binary --with parameters
|
||||||
# REGEXP=my regex to parse (awesome|disappointing) command output
|
# REGEXP=my regex to parse (awesome|disappointing) command output
|
||||||
#
|
|
||||||
# First test your custom command executing it in the command line
|
|
||||||
# Take the text output and write a regex to match it. Check every case:
|
|
||||||
# success result, error result, command failure. Then paste the command
|
|
||||||
# and regex in this config, enable the check and run to verify is working.
|
|
||||||
|
|
||||||
|
|
||||||
[system_load_1min]
|
[system_load_1min]
|
||||||
# The system load average in the last minute
|
# The system load average in the last minute
|
||||||
@ -71,7 +63,6 @@ ALARM_VALUE_MORE_THAN=1.0
|
|||||||
COMMAND=uptime
|
COMMAND=uptime
|
||||||
REGEXP=.*load average: (\d+[,.]\d+), \d+[,.]\d+, \d+[,.]\d+
|
REGEXP=.*load average: (\d+[,.]\d+), \d+[,.]\d+, \d+[,.]\d+
|
||||||
|
|
||||||
|
|
||||||
[system_load_5min]
|
[system_load_5min]
|
||||||
# The system load average in the last 5 minutes
|
# The system load average in the last 5 minutes
|
||||||
DISABLED=True
|
DISABLED=True
|
||||||
@ -79,7 +70,6 @@ ALARM_VALUE_MORE_THAN=1.0
|
|||||||
COMMAND=uptime
|
COMMAND=uptime
|
||||||
REGEXP=.*load average: \d+[,.]\d+, (\d+[,.]\d+), \d+[,.]\d+
|
REGEXP=.*load average: \d+[,.]\d+, (\d+[,.]\d+), \d+[,.]\d+
|
||||||
|
|
||||||
|
|
||||||
[system_load_15min]
|
[system_load_15min]
|
||||||
# The system load average in the last 15 minutes
|
# The system load average in the last 15 minutes
|
||||||
DISABLED=True
|
DISABLED=True
|
||||||
@ -87,7 +77,6 @@ ALARM_VALUE_MORE_THAN=1.0
|
|||||||
COMMAND=uptime
|
COMMAND=uptime
|
||||||
REGEXP=.*load average: \d+[,.]\d+, \d+[,.]\d+, (\d+[,.]\d+)
|
REGEXP=.*load average: \d+[,.]\d+, \d+[,.]\d+, (\d+[,.]\d+)
|
||||||
|
|
||||||
|
|
||||||
[used_disk_space]
|
[used_disk_space]
|
||||||
# Used disk space (in percent, i.e. ALARM_VALUE_MORE_THAN=75 -> alarm if disk is more than 75% full)
|
# Used disk space (in percent, i.e. ALARM_VALUE_MORE_THAN=75 -> alarm if disk is more than 75% full)
|
||||||
DISABLED=True
|
DISABLED=True
|
||||||
@ -95,7 +84,6 @@ ALARM_VALUE_MORE_THAN=75
|
|||||||
COMMAND=df -h /dev/sda1
|
COMMAND=df -h /dev/sda1
|
||||||
REGEXP=(\d{1,3})%
|
REGEXP=(\d{1,3})%
|
||||||
|
|
||||||
|
|
||||||
[raid_status]
|
[raid_status]
|
||||||
# Issues an alarm when the raid is corrupted
|
# Issues an alarm when the raid is corrupted
|
||||||
# Checks this part of the /proc/mdstat file:
|
# Checks this part of the /proc/mdstat file:
|
||||||
@ -107,7 +95,6 @@ ALARM_STRING_NOT_EQUAL=UU
|
|||||||
COMMAND=cat /proc/mdstat
|
COMMAND=cat /proc/mdstat
|
||||||
REGEXP=.*\] \[([U_]+)\]\n
|
REGEXP=.*\] \[([U_]+)\]\n
|
||||||
|
|
||||||
|
|
||||||
[battery_level]
|
[battery_level]
|
||||||
# Issues an alarm when battery is discharging below a certain level (long blackout, pulled power cord...)
|
# Issues an alarm when battery is discharging below a certain level (long blackout, pulled power cord...)
|
||||||
# For laptops used as servers, apparently common among the self hosters. Requires acpi package installed.
|
# For laptops used as servers, apparently common among the self hosters. Requires acpi package installed.
|
||||||
@ -117,7 +104,6 @@ COMMAND=acpi -b
|
|||||||
REGEXP=Battery \d: .*, (\d{1,3})%
|
REGEXP=Battery \d: .*, (\d{1,3})%
|
||||||
ALARM_VALUE_LESS_THAN=90
|
ALARM_VALUE_LESS_THAN=90
|
||||||
|
|
||||||
|
|
||||||
[laptop_charger_disconnected]
|
[laptop_charger_disconnected]
|
||||||
# Issues an alarm when laptop charger is disconnected
|
# Issues an alarm when laptop charger is disconnected
|
||||||
# For laptops used as servers, apparently common among the self hosters. Requires acpi package installed.
|
# For laptops used as servers, apparently common among the self hosters. Requires acpi package installed.
|
||||||
@ -126,7 +112,6 @@ COMMAND=acpi -a
|
|||||||
REGEXP=Adapter \d: (.+)
|
REGEXP=Adapter \d: (.+)
|
||||||
ALARM_STRING_EQUAL=off-line
|
ALARM_STRING_EQUAL=off-line
|
||||||
|
|
||||||
|
|
||||||
[free_ram]
|
[free_ram]
|
||||||
# Free ram in %
|
# Free ram in %
|
||||||
# Shows another approach: does all the computation in the command and picks up
|
# Shows another approach: does all the computation in the command and picks up
|
||||||
@ -135,14 +120,12 @@ DISABLED=True
|
|||||||
COMMAND=free | grep Mem | awk '{print int($4/$2 * 100.0)}'
|
COMMAND=free | grep Mem | awk '{print int($4/$2 * 100.0)}'
|
||||||
ALARM_VALUE_LESS_THAN=20
|
ALARM_VALUE_LESS_THAN=20
|
||||||
|
|
||||||
|
|
||||||
[available_ram]
|
[available_ram]
|
||||||
# Like Free ram, but shows available instead of free. You may want to use this if you use a memcache.
|
# Like Free ram, but shows available instead of free. You may want to use this if you use a memcache.
|
||||||
DISABLED=True
|
DISABLED=True
|
||||||
COMMAND=free | grep Mem | awk '{print int($7/$2 * 100.0)}'
|
COMMAND=free | grep Mem | awk '{print int($7/$2 * 100.0)}'
|
||||||
ALARM_VALUE_LESS_THAN=20
|
ALARM_VALUE_LESS_THAN=20
|
||||||
|
|
||||||
|
|
||||||
[cpu_temperature]
|
[cpu_temperature]
|
||||||
# CPU Temperature alarm: requires lm-sensors installed and configured (check your distribution's guide)
|
# CPU Temperature alarm: requires lm-sensors installed and configured (check your distribution's guide)
|
||||||
# The regexp must be adapted to your configuration: run `sensors` in the command line
|
# The regexp must be adapted to your configuration: run `sensors` in the command line
|
||||||
@ -153,7 +136,6 @@ ALARM_VALUE_MORE_THAN=80
|
|||||||
COMMAND=sensors
|
COMMAND=sensors
|
||||||
REGEXP=Core 0: +\+?(-?\d{1,3}).\d°[CF]
|
REGEXP=Core 0: +\+?(-?\d{1,3}).\d°[CF]
|
||||||
|
|
||||||
|
|
||||||
[fan_speed]
|
[fan_speed]
|
||||||
# Fan speed alarm: requires lm-sensors installed and configured (check your distribution's guide)
|
# Fan speed alarm: requires lm-sensors installed and configured (check your distribution's guide)
|
||||||
# The regexp must be adapted to your configuration: run `sensors` in the command line
|
# The regexp must be adapted to your configuration: run `sensors` in the command line
|
||||||
@ -162,31 +144,3 @@ DISABLED=True
|
|||||||
ALARM_VALUE_LESS_THAN=300
|
ALARM_VALUE_LESS_THAN=300
|
||||||
COMMAND=sensors
|
COMMAND=sensors
|
||||||
REGEXP=cpu_fan: +(\d) RPM
|
REGEXP=cpu_fan: +(\d) RPM
|
||||||
|
|
||||||
|
|
||||||
[host_reachability]
|
|
||||||
# Check if a remote host is alive with Ping. You can replace the ip with a domain name (e.g. COMMAND=ping debian.org -c 1)
|
|
||||||
#
|
|
||||||
# Shows another approach: uses the return value to print a string. Leverages ping's ability to return different error codes:
|
|
||||||
# 0 = success
|
|
||||||
# 1 = the host is unreachable
|
|
||||||
# 2 = an error has occurred (and will be logged to stderr)
|
|
||||||
# We are throwing away stdout and replacing it with a custom text.
|
|
||||||
# If there is a different text (the stderr), something bad happened, and it will be reported in the mail.
|
|
||||||
DISABLED=True
|
|
||||||
ALARM_STRING_NOT_EQUAL=Online
|
|
||||||
COMMAND=ping 192.168.1.123 -c 1 > /dev/null && echo "Online" || echo "Offline"
|
|
||||||
|
|
||||||
|
|
||||||
[service_webserver]
|
|
||||||
# Check if a webserver is running on port 80. You can replace the ip with a domain name.
|
|
||||||
# You can check different services changing the port number. Some examples:
|
|
||||||
# 80 HTTP Webserver
|
|
||||||
# 443 HTTPS Webserver
|
|
||||||
# 21 FTP
|
|
||||||
# 22 SSH
|
|
||||||
# 5900 VNC (Linux remote desktop)
|
|
||||||
# 3389 RDP (Windows remote desktop)
|
|
||||||
DISABLED=True
|
|
||||||
ALARM_STRING_NOT_EQUAL=Online
|
|
||||||
COMMAND=nc -z -w 3 192.168.1.123 80 > /dev/null && echo "Online" || echo "Offline"
|
|
||||||
|
@ -112,15 +112,12 @@ class Main:
|
|||||||
stdout = ""
|
stdout = ""
|
||||||
ret = subprocess.run(config.command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
ret = subprocess.run(config.command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||||
if ret.stderr:
|
if ret.stderr:
|
||||||
self._log.info('{} subprocess stderr:\n{}'.format(config.command, ret.stderr.decode()))
|
self._log.info('{} subprocess stderr:\n{}', config.command, ret.stderr.decode())
|
||||||
if ret.stdout:
|
if ret.stdout:
|
||||||
stdout = ret.stdout.decode()
|
stdout = ret.stdout.decode()
|
||||||
self._log.debug('{} subprocess stdout:\n{}'.format(config.command, stdout))
|
self._log.debug('{} subprocess stdout:\n{}', config.command, stdout)
|
||||||
if ret.returncode != 0:
|
if ret.returncode != 0:
|
||||||
return 'the command exited with error code {} {}'.format(
|
return 'subprocess {} exited with error code {}'.format(config.command, ret.returncode)
|
||||||
ret.returncode,
|
|
||||||
'and error message "{}"'.format(ret.stderr.decode().strip()) if ret.stderr else ''
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse result with regex
|
# Parse result with regex
|
||||||
match = re.search(config.regexp, stdout, re.MULTILINE)
|
match = re.search(config.regexp, stdout, re.MULTILINE)
|
||||||
|
Reference in New Issue
Block a user