1
0
Files
scripts/bin/stats
T
2024-12-01 20:50:25 +01:00

139 lines
4.4 KiB
Python
Executable File

#!/usr/bin/env python3
# Simple stat daemon
# Listens on a port for requests, responds with some basic information about the current state of the system.
import configparser
import os
import subprocess
import time
import argparse
import json
import http.server
import socketserver
def get_default_config():
config = configparser.ConfigParser(interpolation=None)
config['stat.system.uptime'] = {
'command': 'uptime -p | cut -d" " -f2-',
'format': '{}'
}
config['stat.cpu.temp'] = {
'command': 'sensors -j | jq \'.["k10temp-pci-00c3"]["Tctl"]["temp1_input"]\'',
'transform': 'lambda x: float(x)',
'format': '{:.1f}',
'unit': '°C',
'historical': '1'
}
config['stat.cpu.act'] = {
'command': 'top -bn1 | grep "Cpu(s)" | sed "s/.*, *\\([0-9.]*\\)%%* id.*/\\1/" | awk \'{print 100-$1}\'',
'transform': 'lambda x: float(x)',
'format': '{:.1f}',
'unit': '%',
'historical': '1'
}
config['stat.memory.pct'] = {
'command': 'free | awk \'NR==2 {printf "%.1f", $3*100/$2}\'',
'transform': 'lambda x: float(x)',
'format': '{:.1f}',
'unit': '%',
'historical': '1'
}
config['stat.memory.total'] = {
'command': 'free -m | awk \'NR==2 {print $2}\'',
'transform': 'lambda x: int(x)',
'format': '{}',
'unit': 'MB'
}
config['stat.disk.root'] = {
'command': 'df -h / | awk \'NR==2 {printf "%s/%s [%d%%]", $3, $2, $5}\'',
'format': '{}'
}
return config
def get_config():
config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
config_dir = os.path.join(config_home, "stats")
if not os.path.exists(config_dir):
os.makedirs(config_dir)
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser(interpolation=None)
if os.path.exists(config_file):
config.read(config_file)
else:
config = get_default_config()
with open(config_file, 'w') as f:
config.write(f)
return config
def get_stats(config):
stats = {}
stat_sections = [s for s in config.sections() if s.startswith('stat.')]
for section in stat_sections:
# Split into category and name
_, category, name = section.split('.')
stat_config = config[section]
if category not in stats:
stats[category] = {}
try:
value = subprocess.getoutput(stat_config['command']).strip()
transform = stat_config.get('transform', None)
format = stat_config.get('format', None)
unit = stat_config.get('unit', None)
historical = stat_config.get('historical', False)
if transform:
transform_func = eval(transform)
value = transform_func(value)
if format:
value = format.format(value)
data = {
'value': value
}
if unit:
data['unit'] = unit
if historical:
data['track'] = bool(historical)
stats[category][name] = data
except Exception as e:
print(e)
#stats[category][name] = f"Error: {str(e)}"
return stats
class StatsHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
data = {
'timestamp': time.time(),
'stats': get_stats(get_config())
}
response = json.dumps(data).encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', len(response))
self.end_headers()
self.wfile.write(response)
class StatsServer(socketserver.TCPServer):
allow_reuse_address = True
def main():
parser = argparse.ArgumentParser(description='Stats HTTP Server')
parser.add_argument('--port', type=int, default=5747, help='Port to listen on')
parser.add_argument('--host', default='0.0.0.0', help='Host to bind to')
args = parser.parse_args()
with StatsServer((args.host, args.port), StatsHandler) as httpd:
print(f"Serving stats on http://{args.host}:{args.port}")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nShutting down server...")
httpd.server_close()
if __name__ == '__main__':
main()