From d64c14abfec4f9cba03d74f907e2a35fd10a573f Mon Sep 17 00:00:00 2001 From: Ignace Date: Wed, 12 Aug 2026 14:07:50 +0200 Subject: [PATCH] config graphs to show --- README.md | 14 +++++++++++++- cheap-system-monitor.conf.example | 5 +++++ generate_graphs.py | 23 ++++++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2d27ae..fac645f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The executable application files are installed under separate. Edit `/opt/cheap_system_monitor/monitor.conf` to choose network interfaces, -mountpoints, and the graph directory. For example, use +mountpoints, graphs, and the graph directory. For example, use `mountpoints = /, /boot/firmware, /mnt/data` to monitor two additional disks; every configured path must be mounted and accessible. Restarting is unnecessary after configuration changes—the next timer runs use @@ -44,6 +44,18 @@ the new values. If the RRD has already been created, do not change the order of mountpoints unless you intentionally want the historical graph labels to refer to different disks. +Use the `graphs` setting to choose which graphs are generated and shown, and +to set their dashboard order. Available names are `load`, `cpu`, `thermal`, +`trouble`, `network`, `disk`, `storage-health`, and `uptime`. For a virtual +machine where temperature is not meaningful, for example: + +```ini +graphs = load, cpu, trouble, network, disk, storage-health, uptime +``` + +Omitting a graph does not disable collection of its underlying metrics. This +keeps the RRD schema consistent if the graph is enabled later. + The generated PNG files and four responsive, auto-refreshing dashboard pages are placed in `/var/www/html/system-monitor` by default. `index.html` shows the last 3 hours and links to the 1-day, 2-week, and 1-year pages. diff --git a/cheap-system-monitor.conf.example b/cheap-system-monitor.conf.example index c774d56..1d7b6e4 100644 --- a/cheap-system-monitor.conf.example +++ b/cheap-system-monitor.conf.example @@ -11,3 +11,8 @@ interfaces = auto # CPU percentages are measured over this interval each minute. cpu_sample_seconds = 1.0 + +# Graphs to generate and show, in dashboard order. Available names: +# load, cpu, thermal, trouble, network, disk, storage-health, uptime +# For example, omit "thermal" on a virtual machine. +graphs = load, cpu, thermal, trouble, network, disk, storage-health, uptime diff --git a/generate_graphs.py b/generate_graphs.py index e665c66..e4f1bc3 100755 --- a/generate_graphs.py +++ b/generate_graphs.py @@ -13,6 +13,10 @@ import subprocess PERIODS = {"3hours": "3h", "1day": "1d", "2weeks": "2w", "1year": "1y"} COLORS = ["#2563EB", "#DC2626", "#16A34A", "#9333EA"] LOAD_COLORS = ["#FACC15", "#F59E0B", "#DC2626"] # bottom to top +DEFAULT_GRAPHS = ( + "load", "cpu", "thermal", "trouble", "network", "disk", + "storage-health", "uptime", +) def graph(output: Path, rrd: Path, start: str, title: str, unit: str, @@ -107,7 +111,7 @@ def main() -> None: mounts = [item.strip() for item in config["mountpoints"].split(",") if item.strip()] if not mounts or mounts[0] != "/": mounts.insert(0, "/") - definitions = [ + all_definitions = [ ("load", "Processor load", "load", [("load15", "15 minutes", "AVERAGE"), ("load5", "5 minutes", "AVERAGE"), ("load1", "1 minute", "AVERAGE")], False, True, False, False, False, False), ("cpu", "CPU usage", "%", [("cpu_user", "user", "AVERAGE"), ("cpu_system", "system", "AVERAGE"), ("cpu_nice", "nice", "AVERAGE"), ("io_wait", "I/O wait", "AVERAGE")], False, True, False, False, False, False), ("thermal", "CPU temperature and throttling", "°C", [("temperature", "temperature", "AVERAGE"), ("throttled", "throttled", "MAX")], False, False, False, False, True, False), @@ -117,6 +121,23 @@ def main() -> None: ("storage-health", "Storage operational health", "0–10", [(f"health_{'root' if i == 0 else i}", mount, "MIN") for i, mount in enumerate(mounts)], False, False, False, False, False, False), ("uptime", "System uptime", "days", [("uptime", "uptime", "AVERAGE")], False, False, False, False, False, False), ] + configured_graphs = [ + item.strip() + for item in config.get("graphs", ",".join(DEFAULT_GRAPHS)).split(",") + if item.strip() + ] + if not configured_graphs: + raise SystemExit("At least one graph must be configured in 'graphs'") + definitions_by_name = {definition[0]: definition for definition in all_definitions} + unknown = [name for name in configured_graphs if name not in definitions_by_name] + if unknown: + available = ", ".join(DEFAULT_GRAPHS) + raise SystemExit( + f"Unknown graph name(s): {', '.join(unknown)}. Available: {available}" + ) + if len(configured_graphs) != len(set(configured_graphs)): + raise SystemExit("Graph names in 'graphs' must not be repeated") + definitions = [definitions_by_name[name] for name in configured_graphs] for period_name, start in PERIODS.items(): for filename, title, unit, series, logarithmic, stacked, disk_memory, network_mixed, thermal_mixed, trouble_mixed in definitions: graph(output_dir / f"{filename}-{period_name}.png", rrd, start,