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"
|
||||
|
|
|
|||
|
|
@ -1,38 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""GOA (and generic CUR:amount) alt-unit formatting for landing stats.
|
||||
|
||||
Matches exchange currency_specification.alt_unit_names:
|
||||
0→GOA, 3→Kilo-GOA, 6→Mega-GOA, … and fractional scales.
|
||||
Display prefers compact alt names for large values so landing tiles fit.
|
||||
Display uses compact SI-style names (kGOA, MGOA, GGOA, …) so tiles stay short.
|
||||
Exchange /config may still send long names (Kilo-GOA); we compact them.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
# Default GOA ladder (same shape as exchange /config)
|
||||
# Compact display ladder (preferred on landings)
|
||||
DEFAULT_ALT: Dict[str, str] = {
|
||||
"24": "Yotta-GOA",
|
||||
"21": "Zetta-GOA",
|
||||
"18": "Exa-GOA",
|
||||
"15": "Peta-GOA",
|
||||
"12": "Tera-GOA",
|
||||
"9": "Giga-GOA",
|
||||
"6": "Mega-GOA",
|
||||
"3": "Kilo-GOA",
|
||||
"24": "YGOA",
|
||||
"21": "ZGOA",
|
||||
"18": "EGOA",
|
||||
"15": "PGOA",
|
||||
"12": "TGOA",
|
||||
"9": "GGOA",
|
||||
"6": "MGOA",
|
||||
"3": "kGOA",
|
||||
"0": "GOA",
|
||||
"-1": "Deci-GOA",
|
||||
"-2": "Centi-GOA",
|
||||
"-3": "Milli-GOA",
|
||||
"-6": "Micro-GOA",
|
||||
"-7": "Deci-Micro-GOA",
|
||||
"-8": "Atomic-GOA",
|
||||
"-1": "dGOA",
|
||||
"-2": "cGOA",
|
||||
"-3": "mGOA",
|
||||
"-6": "µGOA",
|
||||
"-7": "dµGOA",
|
||||
"-8": "aGOA",
|
||||
}
|
||||
|
||||
# Use alt unit when |value| >= this (base units). Below: keep compact CUR:n.
|
||||
# Long names from exchange /config → compact
|
||||
_LONG_TO_COMPACT = {
|
||||
"yotta-goa": "YGOA",
|
||||
"zetta-goa": "ZGOA",
|
||||
"exa-goa": "EGOA",
|
||||
"peta-goa": "PGOA",
|
||||
"tera-goa": "TGOA",
|
||||
"giga-goa": "GGOA",
|
||||
"mega-goa": "MGOA",
|
||||
"kilo-goa": "kGOA",
|
||||
"goa": "GOA",
|
||||
"deci-goa": "dGOA",
|
||||
"centi-goa": "cGOA",
|
||||
"milli-goa": "mGOA",
|
||||
"micro-goa": "µGOA",
|
||||
"deci-micro-goa": "dµGOA",
|
||||
"atomic-goa": "aGOA",
|
||||
}
|
||||
|
||||
# Use alt unit when |value| >= this (base units). Below: keep CUR:n.
|
||||
ALT_THRESHOLD = Decimal("1000")
|
||||
|
||||
|
||||
def compact_alt_map(alt: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
||||
"""Normalize any alt_unit_names map to compact display labels."""
|
||||
base = dict(DEFAULT_ALT)
|
||||
if not alt:
|
||||
return base
|
||||
out: Dict[str, str] = {}
|
||||
for k, v in alt.items():
|
||||
sk = str(k)
|
||||
name = str(v).strip()
|
||||
low = name.lower().replace(" ", "")
|
||||
if low in _LONG_TO_COMPACT:
|
||||
out[sk] = _LONG_TO_COMPACT[low]
|
||||
elif sk in DEFAULT_ALT and (
|
||||
"goa" in low or name == DEFAULT_ALT.get(sk) or name in DEFAULT_ALT.values()
|
||||
):
|
||||
# keep scale key, prefer compact for GOA family
|
||||
out[sk] = DEFAULT_ALT.get(sk, name)
|
||||
else:
|
||||
out[sk] = name
|
||||
# ensure full default ladder for missing scales
|
||||
for sk, name in DEFAULT_ALT.items():
|
||||
out.setdefault(sk, name)
|
||||
return out
|
||||
|
||||
|
||||
def parse_amount(s: Any) -> Tuple[str, Decimal]:
|
||||
"""Parse 'GOA:12.5' or bare number → (currency, value)."""
|
||||
if s is None:
|
||||
|
|
@ -51,7 +94,6 @@ def parse_amount(s: Any) -> Tuple[str, Decimal]:
|
|||
def fmt_coeff(v: Decimal) -> str:
|
||||
if v == v.to_integral_value():
|
||||
return format(int(v), "d")
|
||||
# up to 4 significant fractional digits, strip trailing zeros
|
||||
q = v.quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)
|
||||
s = format(q, "f").rstrip("0").rstrip(".")
|
||||
return s or "0"
|
||||
|
|
@ -60,7 +102,6 @@ def fmt_coeff(v: Decimal) -> str:
|
|||
def fmt_base(cur: str, val: Decimal) -> str:
|
||||
if val == val.to_integral_value():
|
||||
return f"{cur}:{int(val)}"
|
||||
# preserve up to 8 fractional digits (Taler style)
|
||||
s = format(val, "f").rstrip("0").rstrip(".")
|
||||
return f"{cur}:{s}"
|
||||
|
||||
|
|
@ -76,12 +117,12 @@ def format_amount_alt(
|
|||
|
||||
Keys:
|
||||
amount canonical CUR:n
|
||||
amount_alt short display (e.g. "1.23 Mega-GOA")
|
||||
amount_full "1.23 Mega-GOA (GOA:1230000)" when alt used
|
||||
value float for JSON (may lose precision above 2^53 — also value_str)
|
||||
amount_alt short display (e.g. "1.23 MGOA")
|
||||
amount_full "1.23 MGOA (GOA:1230000)" when alt used
|
||||
value float for JSON
|
||||
value_str exact decimal string
|
||||
"""
|
||||
alt = alt or DEFAULT_ALT
|
||||
alt = compact_alt_map(alt)
|
||||
try:
|
||||
cur, val = parse_amount(amount)
|
||||
except (InvalidOperation, ValueError, ArithmeticError):
|
||||
|
|
@ -95,17 +136,21 @@ def format_amount_alt(
|
|||
}
|
||||
|
||||
base = fmt_base(cur, val)
|
||||
value_str = format(val, "f").rstrip("0").rstrip(".") if val != val.to_integral_value() else str(int(val))
|
||||
value_str = (
|
||||
format(val, "f").rstrip("0").rstrip(".")
|
||||
if val != val.to_integral_value()
|
||||
else str(int(val))
|
||||
)
|
||||
try:
|
||||
value_f = float(val)
|
||||
except Exception:
|
||||
value_f = 0.0
|
||||
|
||||
base_name = alt.get("0") or cur
|
||||
# Never rename foreign currencies with a GOA alt map (merchant dual CHF+GOA).
|
||||
cur_u = (cur or "").upper()
|
||||
base_u = str(base_name).upper()
|
||||
if cur_u and cur_u != "GOA" and (base_u == "GOA" or base_u.endswith("-GOA") or "GOA" in base_u):
|
||||
# Foreign currencies must not pick up kGOA / MGOA labels
|
||||
if cur_u and cur_u != "GOA" and ("GOA" in base_u or base_u.endswith("GOA")):
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": base,
|
||||
|
|
@ -115,7 +160,6 @@ def format_amount_alt(
|
|||
}
|
||||
|
||||
if val == 0:
|
||||
# Keep canonical CUR:0 (avoids "0 GOA" for empty foreign balances)
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": base,
|
||||
|
|
@ -133,9 +177,7 @@ def format_amount_alt(
|
|||
scales.sort(key=lambda x: -x[0])
|
||||
|
||||
absval = abs(val)
|
||||
# Below threshold: short base form (saves noise on small demo amounts)
|
||||
if absval < threshold:
|
||||
# Prefer "GOA:12.5" style (matches existing landings) when small
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": base,
|
||||
|
|
@ -165,7 +207,7 @@ def format_amount_alt(
|
|||
|
||||
sc, name, coeff = chosen
|
||||
short = f"{fmt_coeff(coeff)} {name}"
|
||||
full = f"{short} ({base})" if with_base or True else short
|
||||
full = f"{short} ({base})"
|
||||
return {
|
||||
"amount": base,
|
||||
"amount_alt": short,
|
||||
|
|
@ -175,7 +217,9 @@ def format_amount_alt(
|
|||
}
|
||||
|
||||
|
||||
def attach_alt(obj: Dict[str, Any], amount_key: str = "amount", alt: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
def attach_alt(
|
||||
obj: Dict[str, Any], amount_key: str = "amount", alt: Optional[Dict[str, str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Mutate obj: ensure amount_alt / amount_full from amount_key."""
|
||||
if not isinstance(obj, dict):
|
||||
return obj
|
||||
|
|
@ -193,7 +237,7 @@ def attach_alt(obj: Dict[str, Any], amount_key: str = "amount", alt: Optional[Di
|
|||
|
||||
|
||||
def load_alt_from_config(url: str, timeout: float = 8.0) -> Dict[str, str]:
|
||||
"""GET exchange/bank /config → alt_unit_names (fallback DEFAULT_ALT)."""
|
||||
"""GET exchange/bank /config → compact alt_unit_names (fallback DEFAULT_ALT)."""
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
|
|
@ -215,16 +259,16 @@ def load_alt_from_config(url: str, timeout: float = 8.0) -> Dict[str, str]:
|
|||
break
|
||||
if not isinstance(au, dict) or "0" not in au:
|
||||
return dict(DEFAULT_ALT)
|
||||
return {str(k): str(v) for k, v in au.items()}
|
||||
return compact_alt_map({str(k): str(v) for k, v in au.items()})
|
||||
|
||||
|
||||
def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
"""Walk common landing stats.json shapes and add amount_alt fields."""
|
||||
alt = alt or DEFAULT_ALT
|
||||
alt = compact_alt_map(alt)
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
# bank-style
|
||||
# bank-style nested blocks
|
||||
for path in (
|
||||
("flow", "incoming"),
|
||||
("flow", "withdraw"),
|
||||
|
|
@ -252,6 +296,7 @@ def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None
|
|||
f = format_amount_alt(cur["last_amount"], alt)
|
||||
cur["last_amount"] = f["amount"]
|
||||
cur["last_amount_alt"] = f["amount_alt"]
|
||||
cur["last_amount_full"] = f["amount_full"]
|
||||
|
||||
if isinstance(data.get("flow"), dict):
|
||||
fl = data["flow"]
|
||||
|
|
@ -295,6 +340,7 @@ def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None
|
|||
f = format_amount_alt(row["value"], alt)
|
||||
row["value"] = f["amount"]
|
||||
row["value_alt"] = f["amount_alt"]
|
||||
row["value_full"] = f["amount_full"]
|
||||
|
||||
# merchant dual currency
|
||||
for block in data.get("by_currency") or []:
|
||||
|
|
@ -315,4 +361,10 @@ def enrich_stats_tree(data: Dict[str, Any], alt: Optional[Dict[str, str]] = None
|
|||
attach_alt(row, "amount", alt)
|
||||
|
||||
data["alt_unit_names"] = alt
|
||||
# small legend for UI (positive scales only, compact)
|
||||
data["alt_unit_legend"] = [
|
||||
{"scale": int(k), "name": alt[k], "factor": f"10^{k}"}
|
||||
for k in sorted(alt.keys(), key=lambda x: -int(x))
|
||||
if int(k) >= 0
|
||||
]
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -63,12 +63,46 @@ def merge(stats: Dict[str, Any], resources: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
||||
if resources.get("loadavg"):
|
||||
perf["loadavg"] = resources["loadavg"]
|
||||
perf["loadavg_source"] = "container"
|
||||
# rootless: /proc/loadavg is almost always the host's
|
||||
perf["loadavg_source"] = resources.get("loadavg_source") or "host_via_container_proc"
|
||||
if resources.get("loadavg_note"):
|
||||
perf["loadavg_note"] = resources["loadavg_note"]
|
||||
|
||||
if resources.get("cpu_percent") is not None:
|
||||
perf["cpu_percent"] = resources["cpu_percent"]
|
||||
perf["cpu_percent_label"] = resources.get("cpu_percent_label") or (
|
||||
f'{resources["cpu_percent"]:.2f}%'
|
||||
)
|
||||
|
||||
if resources.get("pids") is not None:
|
||||
perf["pids"] = resources["pids"]
|
||||
|
||||
mem = resources.get("memory")
|
||||
if isinstance(mem, dict) and mem:
|
||||
perf["memory"] = compact_memory(dict(mem))
|
||||
perf["memory"]["source"] = resources.get("source") or "mem-snapshot"
|
||||
m = compact_memory(dict(mem))
|
||||
# normalize empty groups for UI
|
||||
for key in ("taler", "java", "nginx", "postgres", "redis", "other"):
|
||||
try:
|
||||
n = int(m.get(f"{key}_n") or 0)
|
||||
b = int(m.get(f"{key}_rss_bytes") or 0)
|
||||
except Exception:
|
||||
n, b = 0, 0
|
||||
if n <= 0 or b <= 0:
|
||||
m[f"{key}_n"] = 0
|
||||
m[f"{key}_rss_bytes"] = 0
|
||||
m[f"{key}_rss_human"] = None
|
||||
h = m.get("container_rss_human") or "—"
|
||||
has_cgroup = bool(m.get("cgroup_bytes"))
|
||||
if m.get("cgroup_limit_human") and h != "—":
|
||||
m["container_rss_label"] = f"{h} / {m['cgroup_limit_human']}"
|
||||
m["memory_basis"] = "cgroup"
|
||||
elif not has_cgroup and h != "—":
|
||||
m["container_rss_label"] = f"{h} (proc)"
|
||||
m["memory_basis"] = m.get("memory_basis") or "proc_rss_sum"
|
||||
elif not m.get("container_rss_label"):
|
||||
m["container_rss_label"] = h
|
||||
m["source"] = resources.get("source") or "mem-snapshot"
|
||||
perf["memory"] = m
|
||||
return stats
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ bank = {
|
|||
"recent_withdraws": [{"amount": "GOA:4503599627370495"}],
|
||||
}
|
||||
enrich_stats_tree(bank, DEFAULT_ALT)
|
||||
assert "Kilo-GOA" in bank["withdraws"]["total_amount_alt"]
|
||||
assert "Peta-GOA" in bank["recent_withdraws"][0]["amount_alt"]
|
||||
assert "kGOA" in bank["withdraws"]["total_amount_alt"]
|
||||
assert "PGOA" in bank["recent_withdraws"][0]["amount_alt"]
|
||||
|
||||
ex = {
|
||||
"ok": True,
|
||||
|
|
@ -77,8 +77,8 @@ ex = {
|
|||
"by_denom": [{"value": "GOA:1000"}],
|
||||
}
|
||||
enrich_stats_tree(ex, DEFAULT_ALT)
|
||||
assert "Peta-GOA" in ex["wire_in_amount_alt"]
|
||||
assert "Mega-GOA" in ex["withdraw_amount_alt"]
|
||||
assert "PGOA" in ex["wire_in_amount_alt"]
|
||||
assert "MGOA" in ex["withdraw_amount_alt"]
|
||||
assert ex["coins_remaining_amount_alt"] in ("GOA:0", "0 GOA")
|
||||
|
||||
mer = {
|
||||
|
|
@ -92,10 +92,10 @@ mer = {
|
|||
],
|
||||
}
|
||||
enrich_stats_tree(mer, DEFAULT_ALT)
|
||||
# CHF must NOT become Kilo-GOA
|
||||
assert "GOA" not in mer["by_currency"][1]["amount_paid_sum_alt"] or mer["by_currency"][1]["amount_paid_sum_alt"].startswith("CHF")
|
||||
# CHF must NOT become kGOA
|
||||
assert mer["by_currency"][1]["amount_paid_sum_alt"].startswith("CHF")
|
||||
assert "Kilo-GOA" in mer["recent_activity_by_currency"][0]["items"][0]["amount_alt"]
|
||||
assert "kGOA" in mer["recent_activity_by_currency"][0]["items"][0]["amount_alt"]
|
||||
assert mer.get("alt_unit_legend")
|
||||
print("unit ok")
|
||||
PY
|
||||
then ok "enrich shapes bank/exchange/merchant"
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ for item in $SCHEMAS; do
|
|||
done
|
||||
|
||||
# --- recent activity: 5 latest events per currency (GOA + CHF), payments + refunds ---
|
||||
ACT_PER_CURRENCY="${ACT_PER_CURRENCY:-5}"
|
||||
ACT_PER_CURRENCY="${ACT_PER_CURRENCY:-10}"
|
||||
|
||||
# Query latest ACT_PER_CURRENCY events for one currency code (payments ∪ refunds).
|
||||
# Writes TSV rows: ts kind order_id amount summary status
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue