Add system info
This commit is contained in:
73
app.py
73
app.py
@@ -48,6 +48,68 @@ def docker_stats() -> dict:
|
|||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
def docker_info() -> dict:
|
||||||
|
# docker info --format "{{json .}}" gives us structured host-level info
|
||||||
|
out = sh([DOCKER, "info", "--format", "{{json .}}"])
|
||||||
|
return json.loads(out)
|
||||||
|
|
||||||
|
def docker_system_df() -> dict:
|
||||||
|
# Parse `docker system df` (text). It's stable enough for a dashboard.
|
||||||
|
out = sh([DOCKER, "system", "df"])
|
||||||
|
# Example lines:
|
||||||
|
# Images 175 18 15.15GB 13.93GB (91%)
|
||||||
|
# Containers 27 26 145.1MB 16.57kB (0%)
|
||||||
|
# Local Volumes 47 1 817.7MB 817.7MB (100%)
|
||||||
|
# Build Cache 889 0 423B 423B
|
||||||
|
rows = {}
|
||||||
|
for line in out.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("TYPE"):
|
||||||
|
continue
|
||||||
|
parts = re.split(r"\s{2,}", line)
|
||||||
|
if len(parts) >= 5:
|
||||||
|
typ, total, active, size, reclaimable = parts[:5]
|
||||||
|
rows[typ] = {
|
||||||
|
"total": total,
|
||||||
|
"active": active,
|
||||||
|
"size": size,
|
||||||
|
"reclaimable": reclaimable,
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def system_summary() -> dict:
|
||||||
|
info = docker_info()
|
||||||
|
df = docker_system_df()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": info.get("Name", ""),
|
||||||
|
"server_version": info.get("ServerVersion", ""),
|
||||||
|
"operating_system": info.get("OperatingSystem", ""),
|
||||||
|
"os_type": info.get("OSType", ""),
|
||||||
|
"architecture": info.get("Architecture", ""),
|
||||||
|
"kernel_version": info.get("KernelVersion", ""),
|
||||||
|
"cpus": info.get("NCPU", ""),
|
||||||
|
"mem_total": info.get("MemTotal", ""), # bytes
|
||||||
|
"containers": info.get("Containers", ""),
|
||||||
|
"containers_running": info.get("ContainersRunning", ""),
|
||||||
|
"containers_stopped": info.get("ContainersStopped", ""),
|
||||||
|
"images": info.get("Images", ""),
|
||||||
|
"docker_root_dir": info.get("DockerRootDir", ""),
|
||||||
|
"system_df": df,
|
||||||
|
}
|
||||||
|
|
||||||
|
def format_bytes(n: int) -> str:
|
||||||
|
# for mem_total
|
||||||
|
units = ["B", "KB", "MB", "GB", "TB"]
|
||||||
|
f = float(n)
|
||||||
|
for u in units:
|
||||||
|
if f < 1024 or u == units[-1]:
|
||||||
|
return f"{f:.1f}{u}"
|
||||||
|
f /= 1024
|
||||||
|
return f"{n}B"
|
||||||
|
|
||||||
def docker_inspect_restart_count(container_name: str) -> int:
|
def docker_inspect_restart_count(container_name: str) -> int:
|
||||||
# RestartCount is useful when stuff is flapping / OOMing
|
# RestartCount is useful when stuff is flapping / OOMing
|
||||||
try:
|
try:
|
||||||
@@ -127,10 +189,21 @@ def collect():
|
|||||||
if a["restarts"] >= 3:
|
if a["restarts"] >= 3:
|
||||||
warnings.append(f"{a['app']} restarting (restarts={a['restarts']})")
|
warnings.append(f"{a['app']} restarting (restarts={a['restarts']})")
|
||||||
|
|
||||||
|
sysinfo = system_summary()
|
||||||
|
|
||||||
|
# format mem bytes nicely
|
||||||
|
try:
|
||||||
|
mem_total_h = format_bytes(int(sysinfo["mem_total"]))
|
||||||
|
except Exception:
|
||||||
|
mem_total_h = ""
|
||||||
|
|
||||||
|
sysinfo["mem_total_h"] = mem_total_h
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"generated_at": datetime.utcnow().isoformat() + "Z",
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
||||||
"poll_seconds": POLL_SECONDS,
|
"poll_seconds": POLL_SECONDS,
|
||||||
"domain": APP_DOMAIN,
|
"domain": APP_DOMAIN,
|
||||||
|
"system": sysinfo,
|
||||||
"apps": apps,
|
"apps": apps,
|
||||||
"infra": infra,
|
"infra": infra,
|
||||||
"warnings": warnings,
|
"warnings": warnings,
|
||||||
|
|||||||
@@ -1,3 +1,50 @@
|
|||||||
|
<h2>System</h2>
|
||||||
|
<div style="display:grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 12px 0 18px 0;">
|
||||||
|
<div style="border:1px solid #ddd; border-radius: 10px; padding: 12px;">
|
||||||
|
<div class="muted">Host</div>
|
||||||
|
<div><strong>{{ data.system.name or "—" }}</strong></div>
|
||||||
|
<div class="muted">{{ data.system.operating_system }} · {{ data.system.kernel_version }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="border:1px solid #ddd; border-radius: 10px; padding: 12px;">
|
||||||
|
<div class="muted">Compute</div>
|
||||||
|
<div><strong>{{ data.system.cpus or "—" }}</strong> CPUs</div>
|
||||||
|
<div><strong>{{ data.system.mem_total_h or "—" }}</strong> RAM</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="border:1px solid #ddd; border-radius: 10px; padding: 12px;">
|
||||||
|
<div class="muted">Docker</div>
|
||||||
|
<div>Engine: <strong>{{ data.system.server_version or "—" }}</strong></div>
|
||||||
|
<div>Images: <strong>{{ data.system.images or "—" }}</strong></div>
|
||||||
|
<div>Containers: <strong>{{ data.system.containers_running or "—" }}</strong> running / <strong>{{
|
||||||
|
data.system.containers_stopped or "—" }}</strong> stopped</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Docker disk usage</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Total</th>
|
||||||
|
<th>Active</th>
|
||||||
|
<th>Size</th>
|
||||||
|
<th>Reclaimable</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for typ, r in data.system.system_df.items() %}
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ typ }}</strong></td>
|
||||||
|
<td>{{ r.total }}</td>
|
||||||
|
<td>{{ r.active }}</td>
|
||||||
|
<td>{{ r.size }}</td>
|
||||||
|
<td>{{ r.reclaimable }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="muted">Generated at: {{ data.generated_at }}</div>
|
<div class="muted">Generated at: {{ data.generated_at }}</div>
|
||||||
|
|
||||||
{% if data.warnings %}
|
{% if data.warnings %}
|
||||||
|
|||||||
Reference in New Issue
Block a user