"""Deterministic, realistic demo data for UI development and screenshots.""" from __future__ import annotations import datetime as dt import math import random import time from typing import Any, Dict, List from .metrics import build_alerts class DemoSource: """A drop-in metrics source that simulates a busy Linux edge server.""" def __init__(self) -> None: self.step = 0 self.rng = random.Random(20260609) self._cores = 16 self._total_mem = 32 * 1024 ** 3 now = dt.datetime.now().astimezone() offset = now.utcoffset() self._tz_offset = int(offset.total_seconds()) if offset else 0 self._tz_name = now.tzname() or "LOCAL" self._boot_base = time.time() - (12 * 86400 + 4 * 3600 + 27 * 60 + 18) def snapshot(self) -> Dict[str, Any]: return self.sample() def sample(self) -> Dict[str, Any]: self.step += 1 s = self.step now = time.time() uptime = 12 * 86400 + 4 * 3600 + 27 * 60 + 18 + s cpu_base = 36 + 15 * math.sin(s / 15.0) + 5 * math.sin(s / 3.7) + self.rng.uniform(-2.2, 2.2) cpu_base = max(8.0, min(88.0, cpu_base)) per_core = [] for index in range(self._cores): value = cpu_base + 13 * math.sin(s / (7.0 + index * 0.6) + index * 0.9) + self.rng.uniform(-4, 4) per_core.append(round(max(1.0, min(99.0, value)), 1)) cpu_percent = round(sum(per_core) / len(per_core), 1) mem_percent = 61.5 + 4.5 * math.sin(s / 37.0) + 1.2 * math.sin(s / 9.0) mem_percent = max(30.0, min(92.0, mem_percent)) total_mem = self._total_mem used_mem = int(total_mem * mem_percent / 100.0) available = total_mem - used_mem free = int(available * 0.32) buffers = int(total_mem * 0.018) cached = int(total_mem * 0.235 + 1.5 * 1024 ** 3 * math.sin(s / 25.0)) apps = max(0, total_mem - free - buffers - cached) swap_total = 8 * 1024 ** 3 swap_used = int(swap_total * (0.045 + 0.012 * math.sin(s / 19.0))) swap_percent = round(swap_used / swap_total * 100, 1) load1 = max(0.15, self._cores * cpu_percent / 100.0 * 0.92 + 0.25) load5 = load1 * 0.94 + 0.08 load15 = load1 * 0.87 + 0.14 total_root = 512 * 1024 ** 3 total_data = 4 * 1024 ** 4 total_var = 128 * 1024 ** 3 total_boot = 1024 ** 3 disks = [ self._disk("/dev/nvme0n1p2", "/", "ext4", total_root, 46.4 + 0.3 * math.sin(s / 40)), self._disk("/dev/nvme1n1", "/data", "xfs", total_data, 91.2 + 0.08 * math.sin(s / 55)), self._disk("/dev/nvme0n1p3", "/var", "ext4", total_var, 67.8 + 0.4 * math.sin(s / 30)), self._disk("/dev/nvme0n1p1", "/boot", "ext4", total_boot, 23.6), ] read_mbps = 17.4 + 7.5 * math.sin(s / 6.0) + self.rng.uniform(-2, 2) write_mbps = 6.1 + 3.2 * math.cos(s / 8.0) + self.rng.uniform(-1, 1) read2 = 2.1 + 1.7 * math.sin(s / 5.0 + 1.4) write2 = 1.2 + 0.9 * math.cos(s / 7.0) disk_devices = [ { "device": "nvme0n1", "read_bps": int(read_mbps * 1024 ** 2), "write_bps": int(write_mbps * 1024 ** 2), "read_iops": round(780 + 240 * math.sin(s / 5), 1), "write_iops": round(310 + 130 * math.cos(s / 6), 1), "util_percent": round(min(99.0, 31 + 18 * math.sin(s / 9)), 1), "read_await_ms": 0.31, "write_await_ms": 0.44, }, { "device": "nvme1n1", "read_bps": int(max(0, read2) * 1024 ** 2), "write_bps": int(max(0, write2) * 1024 ** 2), "read_iops": round(112 + 44 * math.sin(s / 4), 1), "write_iops": round(86 + 25 * math.cos(s / 5), 1), "util_percent": round(min(99.0, 12 + 8 * math.sin(s / 7 + 2)), 1), "read_await_ms": 0.18, "write_await_ms": 0.22, }, ] rx_mbps = 12.6 + 5.2 * math.sin(s / 11.0) + 1.5 * math.sin(s / 2.9) + self.rng.uniform(-0.5, 0.5) tx_mbps = 3.4 + 1.8 * math.cos(s / 9.0) + self.rng.uniform(-0.25, 0.25) rx2_mbps = 1.35 + 0.55 * math.sin(s / 13.0 + 1.1) tx2_mbps = 0.42 + 0.2 * math.cos(s / 10.0) rx_bps = int(max(0, rx_mbps) * 1024 ** 2) tx_bps = int(max(0, tx_mbps) * 1024 ** 2) interfaces = [ self._iface("ens18", "10.20.4.17", rx_bps, tx_bps, 10_000, primary=True), self._iface("ens19", "172.18.4.21", int(max(0, rx2_mbps) * 1024 ** 2), int(max(0, tx2_mbps) * 1024 ** 2), 1000), self._iface("wg0", "10.88.0.2", int(0.82 * 1024 ** 2), int(0.31 * 1024 ** 2), None, virtual=True), ] processes = self._processes(total_mem) temp = round(53.2 + 2.6 * math.sin(s / 17.0), 1) freq = int(3798 + 120 * math.sin(s / 5.0)) alerts = build_alerts( cpu_percent=cpu_percent, memory_percent=mem_percent, swap_percent=swap_percent, load1=load1, cores=self._cores, disks=disks, temp_c=temp, ) return { "ts": now, "version": "1.0.0", "host": { "hostname": "orion-edge-07", "os": "Debian GNU/Linux 12 (bookworm)", "kernel": "6.1.0-28-amd64", "arch": "x86_64", "cpu_model": "AMD Ryzen 7 5800X 8-Core Processor", "cores": self._cores, "physical_cores": 8, "primary_ip": "10.20.4.17", "ips": ["10.20.4.17", "10.88.0.2", "172.18.4.21"], "boot_epoch": now - uptime, "uptime_seconds": uptime, "tz_offset": self._tz_offset, "tz_name": self._tz_name, "process_count": 214, "thread_count": 438, "version": "1.0.0", "demo": True, }, "cpu": { "percent": cpu_percent, "per_core": per_core, "breakdown": { "user": round(cpu_percent * 0.61, 1), "nice": 0.3, "system": round(cpu_percent * 0.27, 1), "iowait": round(cpu_percent * 0.07, 1), "irq": round(cpu_percent * 0.02, 1), "softirq": round(cpu_percent * 0.02, 1), "steal": 0.1, }, "freq_mhz": freq, "temp_c": temp, "temp_label": "x86_pkg_temp", "model": "AMD Ryzen 7 5800X 8-Core Processor", "cores": self._cores, "physical_cores": 8, }, "memory": { "total": total_mem, "used": used_mem, "free": free, "available": available, "buffers": buffers, "cached": cached, "apps": apps, "percent": round(mem_percent, 1), "composition": { "apps": apps, "cache": cached, "buffers": buffers, "free": free, }, "swap_total": swap_total, "swap_used": swap_used, "swap_free": swap_total - swap_used, "swap_percent": swap_percent, "dirty": 48 * 1024 ** 2, "writeback": 0, }, "load": { "load1": round(load1, 2), "load5": round(load5, 2), "load15": round(load15, 2), "running": 3, "threads": 438, "percent": round(load1 / self._cores * 100, 1), }, "disks": disks, "disk_io": { "devices": disk_devices, "read_bps": sum(d["read_bps"] for d in disk_devices), "write_bps": sum(d["write_bps"] for d in disk_devices), }, "network": { "interfaces": interfaces, "interface_count": 3, "total_rx_bps": rx_bps + int(max(0, rx2_mbps) * 1024 ** 2), "total_tx_bps": tx_bps + int(max(0, tx2_mbps) * 1024 ** 2), "primary_iface": "ens18", "primary_ip": "10.20.4.17", "all_ips": ["10.20.4.17", "10.88.0.2", "172.18.4.21"], "errors": 0, "drops": 0, }, "processes": processes, "alerts": alerts, } def _disk(self, device: str, mount: str, fstype: str, total: int, percent: float) -> Dict[str, Any]: used = int(total * percent / 100) free = total - used return { "device": device, "mount": mount, "fstype": fstype, "total": total, "used": used, "free": free, "percent": round(percent, 1), "readonly": False, "inodes_total": 100_000_000, "inodes_free": 80_000_000, } def _iface( self, name: str, ip: str, rx_bps: int, tx_bps: int, speed: int | None, primary: bool = False, virtual: bool = False, ) -> Dict[str, Any]: return { "name": name, "ip": ip, "virtual": virtual, "primary": primary, "rx_bps": rx_bps, "tx_bps": tx_bps, "rx_total": 18_400_000_000_000 + rx_bps * 37, "tx_total": 4_700_000_000_000 + tx_bps * 29, "errors": 0, "drops": 0, "speed_mbps": speed, "link": True, } def _processes(self, total_mem: int) -> List[Dict[str, Any]]: rows = [ ("node", 2203, 18.4, 3.1, "node /opt/edge-api/dist/server.js --cluster 4"), ("java", 4080, 14.1, 12.6, "java -Xms4g -Xmx6g -jar /opt/events/event-worker.jar"), ("postgres", 1442, 12.8, 8.4, "postgres: checkpointer"), ("python3", 3120, 7.6, 1.5, "python3 /opt/telemetry/collector.py --workers 8"), ("nginx", 2101, 4.2, 0.8, "nginx: worker process"), ("dockerd", 910, 2.1, 1.2, "/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock"), ("systemd-journald", 512, 1.8, 0.3, "/lib/systemd/systemd-journald"), ("redis-server", 1750, 1.2, 0.7, "redis-server 0.0.0.0:6379"), ("containerd", 1002, 0.8, 0.4, "/usr/bin/containerd"), ("kworker/3:2", 88, 0.4, 0.0, "[kworker/3:2-events]"), ("rsyslogd", 702, 0.2, 0.1, "/usr/sbin/rsyslogd -n -iNONE"), ("sshd", 2402, 0.1, 0.1, "sshd: /usr/sbin/sshd -D [listener]"), ] out = [] for name, pid, cpu, mem, cmd in rows: rss = int(total_mem * mem / 100) out.append({ "pid": pid, "name": name, "cmd": cmd, "cpu_percent": cpu, "mem_percent": mem, "rss_bytes": rss, "threads": 1 if name.startswith("kworker") else 24, }) return out