landing: kGOA units, full money stats, rootless resource display
Public bank/exchange/merchant landing stats and performance UI only. Secret goa-ui overlay stays on local/merchant-secret-ui (never push).
This commit is contained in:
parent
bf8a915a01
commit
fb1a0b0b8e
12 changed files with 1046 additions and 333 deletions
|
|
@ -1,7 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
# Snapshot loadavg + RSS groups inside a podman container (for landing stats).
|
||||
# Snapshot RSS process groups inside a podman container + host-side CPU/PIDs.
|
||||
# Usage: collect_container_resources.sh CONTAINER [OUT.json]
|
||||
# Prints JSON to stdout (and writes OUT when given).
|
||||
#
|
||||
# Note: rootless podman often has no memory cgroup controller delegated
|
||||
# (memory.current missing → podman stats shows 0B). We therefore use
|
||||
# /proc VmRSS sums inside the container for memory, and podman stats for
|
||||
# CPU% + PID count. /proc/loadavg inside the container is the *host* load.
|
||||
set -euo pipefail
|
||||
|
||||
CTR="${1:-}"
|
||||
|
|
@ -11,21 +16,24 @@ if [ -z "$CTR" ]; then
|
|||
exit 2
|
||||
fi
|
||||
|
||||
ADMIN_LOG="${ADMIN_LOG:-${HOME}/src/koopa/koopa-admin-log}"
|
||||
ADMIN_LOG="${ADMIN_LOG:-${HOME}/koopa-admin-log}"
|
||||
if [ ! -d "$ADMIN_LOG" ] && [ -d "${HOME}/src/koopa/koopa-admin-log" ]; then
|
||||
ADMIN_LOG="${HOME}/src/koopa/koopa-admin-log"
|
||||
fi
|
||||
MEM_SRC="${MEM_SNAPSHOT_SRC:-$ADMIN_LOG/scripts/taler-shared/mem-snapshot.sh}"
|
||||
MEM_DST="${MEM_SNAPSHOT_DST:-/usr/local/lib/landing-mem-snapshot.sh}"
|
||||
|
||||
if ! podman inspect -f '{{.State.Running}}' "$CTR" 2>/dev/null | grep -qx true; then
|
||||
echo "{\"ok\":false,\"error\":\"container not running: $CTR\"}"
|
||||
echo "{\"ok\":false,\"error\":\"container not running: $CTR\"}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$MEM_SRC" ]; then
|
||||
podman exec "$CTR" mkdir -p "$(dirname "$MEM_DST")" 2>/dev/null || true
|
||||
podman cp "$MEM_SRC" "${CTR}:${MEM_DST}"
|
||||
# Always refresh helper (fixes stale copies missing json_str / emit)
|
||||
podman cp "$MEM_SRC" "${CTR}:${MEM_DST}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Run emit inside container (needs /proc + cgroup of that container)
|
||||
set +e
|
||||
raw=$(podman exec "$CTR" bash -c '
|
||||
set -e
|
||||
|
|
@ -37,7 +45,6 @@ raw=$(podman exec "$CTR" bash -c '
|
|||
# shellcheck disable=SC1091
|
||||
. /usr/local/lib/landing-mem-snapshot.sh
|
||||
if ! declare -F mem_snapshot_emit >/dev/null 2>&1; then
|
||||
# older helper without emit — synthesize
|
||||
mem_snapshot_json
|
||||
loadavg=""
|
||||
[ -r /proc/loadavg ] && loadavg=$(awk "{print \$1\",\"\$2\",\"\$3}" /proc/loadavg)
|
||||
|
|
@ -58,13 +65,115 @@ if [ "$ec" -ne 0 ] || [ -z "$raw" ]; then
|
|||
fi
|
||||
rm -f /tmp/landing-mem-err.$$
|
||||
|
||||
# Validate JSON
|
||||
if ! printf '%s' "$raw" | python3 -c 'import json,sys; json.load(sys.stdin)' 2>/dev/null; then
|
||||
printf '{"ok":false,"error":"invalid json from container"}\n'
|
||||
exit 1
|
||||
# Host-side: CPU% + PIDs via podman stats (works without memory controller)
|
||||
stats_json=$(podman stats --no-stream --format json "$CTR" 2>/dev/null || true)
|
||||
|
||||
# Merge + normalize with Python
|
||||
merged=$(
|
||||
RAW_JSON="$raw" STATS_JSON="$stats_json" CTR_NAME="$CTR" python3 - <<'PY'
|
||||
import json, os, re
|
||||
|
||||
raw = os.environ.get("RAW_JSON") or ""
|
||||
stats_raw = os.environ.get("STATS_JSON") or ""
|
||||
ctr = os.environ.get("CTR_NAME") or ""
|
||||
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception as e:
|
||||
print(json.dumps({"ok": False, "error": f"invalid mem json: {e}"}))
|
||||
raise SystemExit(0)
|
||||
|
||||
if not isinstance(data, dict) or not data.get("ok"):
|
||||
print(json.dumps(data if isinstance(data, dict) else {"ok": False}))
|
||||
raise SystemExit(0)
|
||||
|
||||
mem = data.get("memory") if isinstance(data.get("memory"), dict) else {}
|
||||
data["memory"] = mem
|
||||
|
||||
# Zero-group cleanup: UI should not show "0 B"
|
||||
for key in ("taler", "java", "nginx", "postgres", "redis", "other"):
|
||||
n = mem.get(f"{key}_n")
|
||||
b = mem.get(f"{key}_rss_bytes")
|
||||
try:
|
||||
n = int(n) if n is not None else 0
|
||||
except Exception:
|
||||
n = 0
|
||||
try:
|
||||
b = int(b) if b is not None else 0
|
||||
except Exception:
|
||||
b = 0
|
||||
if n <= 0 or b <= 0:
|
||||
mem[f"{key}_n"] = 0
|
||||
mem[f"{key}_rss_bytes"] = 0
|
||||
mem[f"{key}_rss_human"] = None # UI → "—"
|
||||
mem[f"{key}_rss_label"] = "—"
|
||||
|
||||
# Prefer process-sum label when cgroup missing
|
||||
if not mem.get("cgroup_bytes") and mem.get("proc_sum_rss_human"):
|
||||
hum = mem.get("container_rss_human") or mem.get("proc_sum_rss_human")
|
||||
mem["container_rss_label"] = f"{hum} (proc)"
|
||||
mem["memory_basis"] = "proc_rss_sum"
|
||||
elif mem.get("cgroup_limit_human") and mem.get("container_rss_human"):
|
||||
mem["container_rss_label"] = f"{mem['container_rss_human']} / {mem['cgroup_limit_human']}"
|
||||
mem["memory_basis"] = "cgroup"
|
||||
else:
|
||||
mem["memory_basis"] = mem.get("memory_basis") or "proc_rss_sum"
|
||||
if mem.get("container_rss_human") and not mem.get("container_rss_label"):
|
||||
mem["container_rss_label"] = mem["container_rss_human"]
|
||||
|
||||
# loadavg inside rootless containers is usually the host's
|
||||
data["loadavg_note"] = "host (shared; rootless container /proc/loadavg)"
|
||||
data["loadavg_source"] = "host_via_container_proc"
|
||||
|
||||
# podman stats enrichment
|
||||
cpu = None
|
||||
pids = None
|
||||
if stats_raw.strip():
|
||||
try:
|
||||
st = json.loads(stats_raw)
|
||||
if isinstance(st, list) and st:
|
||||
st = st[0]
|
||||
if isinstance(st, dict):
|
||||
cp = st.get("CPUPerc") or st.get("cpu_percent") or st.get("CPU")
|
||||
if cp is not None:
|
||||
s = str(cp).strip().rstrip("%")
|
||||
try:
|
||||
cpu = float(s)
|
||||
except Exception:
|
||||
cpu = None
|
||||
p = st.get("PIDs") or st.get("pids")
|
||||
if p is not None:
|
||||
try:
|
||||
pids = int(str(p).strip())
|
||||
except Exception:
|
||||
pids = None
|
||||
# mem from stats only if non-zero (usually 0B without memory controller)
|
||||
mu = st.get("MemUsage") or st.get("mem_usage") or ""
|
||||
m = re.match(r"^\s*([0-9.]+)\s*([KMGT]?i?B)\s*/\s*([0-9.]+)\s*([KMGT]?i?B)", str(mu), re.I)
|
||||
if m and float(m.group(1)) > 0:
|
||||
mem["podman_stats_mem"] = str(mu).strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if cpu is not None:
|
||||
data["cpu_percent"] = cpu
|
||||
data["cpu_percent_label"] = f"{cpu:.2f}%"
|
||||
if pids is not None:
|
||||
data["pids"] = pids
|
||||
mem["pids"] = pids
|
||||
|
||||
data["container"] = ctr
|
||||
data["source"] = "mem-snapshot+podman-stats"
|
||||
print(json.dumps(data, ensure_ascii=False))
|
||||
PY
|
||||
)
|
||||
|
||||
if ! printf '%s' "$merged" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("ok")' 2>/dev/null; then
|
||||
# fall back to raw if merge failed
|
||||
merged="$raw"
|
||||
fi
|
||||
|
||||
if [ -n "$OUT" ]; then
|
||||
printf '%s\n' "$raw" >"$OUT"
|
||||
printf '%s\n' "$merged" >"$OUT"
|
||||
fi
|
||||
printf '%s\n' "$raw"
|
||||
printf '%s\n' "$merged"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue