82 lines
3.9 KiB
Python
82 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Generate system-monitor PNG graphs from its RRD database."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import configparser
|
||
|
|
import html
|
||
|
|
from pathlib import Path
|
||
|
|
import subprocess
|
||
|
|
|
||
|
|
|
||
|
|
PERIODS = {"3hours": "3h", "1day": "1d", "2weeks": "2w", "1year": "1y"}
|
||
|
|
COLORS = ["#2563EB", "#DC2626", "#16A34A", "#9333EA"]
|
||
|
|
|
||
|
|
|
||
|
|
def graph(output: Path, rrd: Path, start: str, title: str, unit: str,
|
||
|
|
series: list[tuple[str, str, str]], logarithmic: bool = False) -> None:
|
||
|
|
args = ["rrdtool", "graph", str(output), "--start", f"end-{start}",
|
||
|
|
"--end", "now", "--width", "900", "--height", "260",
|
||
|
|
"--title", title, "--vertical-label", unit, "--slope-mode",
|
||
|
|
"--watermark", "cheap-system-monitor"]
|
||
|
|
if logarithmic:
|
||
|
|
args += ["--logarithmic", "--lower-limit", "1"]
|
||
|
|
for index, (ds, label, cf) in enumerate(series):
|
||
|
|
var = f"v{index}"
|
||
|
|
args += [f"DEF:{var}={rrd}:{ds}:{cf}",
|
||
|
|
f"LINE{2 if index < 3 else 1}:{var}{COLORS[index % len(COLORS)]}:{label}",
|
||
|
|
f"GPRINT:{var}:LAST:Current\\:%8.2lf%s",
|
||
|
|
f"GPRINT:{var}:AVERAGE:Average\\:%8.2lf%s",
|
||
|
|
f"GPRINT:{var}:MAX:Maximum\\:%8.2lf%s\\n"]
|
||
|
|
subprocess.run(args, check=True)
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
||
|
|
parser.add_argument("--config", default="/etc/cheap-system-monitor.conf")
|
||
|
|
args = parser.parse_args()
|
||
|
|
config_parser = configparser.ConfigParser()
|
||
|
|
if not config_parser.read(args.config):
|
||
|
|
raise SystemExit(f"Configuration file not found: {args.config}")
|
||
|
|
config = config_parser["monitor"]
|
||
|
|
rrd = Path(config["rrd_file"])
|
||
|
|
if not rrd.exists():
|
||
|
|
raise SystemExit(f"RRD file does not exist: {rrd}")
|
||
|
|
output_dir = Path(config.get("graph_dir", "/var/www/html/system-monitor"))
|
||
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
mounts = [item.strip() for item in config["mountpoints"].split(",") if item.strip()]
|
||
|
|
if not mounts or mounts[0] != "/":
|
||
|
|
mounts.insert(0, "/")
|
||
|
|
definitions = [
|
||
|
|
("load", "Processor load", "load", [("load1", "1 minute", "AVERAGE"), ("load5", "5 minutes", "AVERAGE"), ("load15", "15 minutes", "AVERAGE")], False),
|
||
|
|
("cpu", "CPU usage", "%", [("cpu_user", "user", "AVERAGE"), ("cpu_system", "system", "AVERAGE"), ("cpu_nice", "nice", "AVERAGE")], False),
|
||
|
|
("network", "Network throughput", "bytes/s", [("net_in", "received", "AVERAGE"), ("net_out", "sent", "AVERAGE")], False),
|
||
|
|
("disk", "Disk usage", "%", [(f"disk_{'root' if i == 0 else i}", mount, "AVERAGE") for i, mount in enumerate(mounts)], False),
|
||
|
|
("memory", "Memory usage", "%", [("memory", "used", "AVERAGE")], False),
|
||
|
|
]
|
||
|
|
for period_name, start in PERIODS.items():
|
||
|
|
for filename, title, unit, series, logarithmic in definitions:
|
||
|
|
graph(output_dir / f"{filename}-{period_name}.png", rrd, start,
|
||
|
|
f"{title} — {period_name}", unit, series, logarithmic)
|
||
|
|
cards = "\n".join(
|
||
|
|
f'<section><h2>{html.escape(title)}</h2>' + "".join(
|
||
|
|
f'<figure><figcaption>{period}</figcaption>'
|
||
|
|
f'<img src="{filename}-{period}.png" alt="{html.escape(title)}, {period}"></figure>'
|
||
|
|
for period in PERIODS
|
||
|
|
) + "</section>"
|
||
|
|
for filename, title, _unit, _series, _logarithmic in definitions
|
||
|
|
)
|
||
|
|
page = f"""<!doctype html>
|
||
|
|
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
||
|
|
<meta http-equiv="refresh" content="300"><title>System monitor</title>
|
||
|
|
<style>body{{font:16px system-ui;margin:auto;max-width:1100px;padding:1rem;background:#f8fafc;color:#172033}}
|
||
|
|
section{{background:white;padding:1rem;margin:1rem 0;border-radius:.5rem;box-shadow:0 1px 4px #0002}}
|
||
|
|
figure{{margin:1rem 0}} img{{width:100%;height:auto}} figcaption{{font-weight:600}}</style>
|
||
|
|
</head><body><h1>System monitor</h1>{cards}</body></html>"""
|
||
|
|
(output_dir / "index.html").write_text(page)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|