monitoring/merge: origin ladder+metrics + stage domains/profiles
This commit is contained in:
commit
d6f4f649c1
86 changed files with 8199 additions and 256 deletions
|
|
@ -98,6 +98,77 @@ check_legal_doc() {
|
|||
rm -f "$f"
|
||||
}
|
||||
|
||||
# Shared shape for bank mint JSON (demo-withdraw + auto-account).
|
||||
# Args: $1=json file $2=auto|demo
|
||||
# stdout (ok): one detail line; for demo, optional "\twithdrawal_id" suffix.
|
||||
# stderr (fail): short reason. Exit 0/1.
|
||||
validate_bank_withdraw_json() {
|
||||
python3 - "$1" "$2" <<'PY'
|
||||
import json, re, sys
|
||||
from urllib.parse import urlparse
|
||||
|
||||
def check_withdraw_uri(wuri: str):
|
||||
"""HOST or HOST:non-default-port; default :443/:80 must be stripped (wallet fix)."""
|
||||
m = re.match(
|
||||
r"^taler://withdraw/([^/]+)/taler-integration/([0-9a-fA-F-]+)$",
|
||||
wuri or "",
|
||||
)
|
||||
if not m:
|
||||
print(
|
||||
"need taler://withdraw/HOST/taler-integration/ID:",
|
||||
(wuri or "")[:120],
|
||||
file=sys.stderr,
|
||||
)
|
||||
return None
|
||||
host = m.group(1)
|
||||
if not host or host.startswith(":"):
|
||||
print("bad withdraw host:", host, file=sys.stderr)
|
||||
return None
|
||||
if ":" in host:
|
||||
h, _, p = host.rpartition(":")
|
||||
if not h or not p.isdigit():
|
||||
print("bad withdraw host:port:", host, file=sys.stderr)
|
||||
return None
|
||||
if p in ("443", "80"):
|
||||
print("withdraw must strip default port :%s:" % p, host, file=sys.stderr)
|
||||
return None
|
||||
return host, m.group(2), wuri
|
||||
|
||||
path, mode = sys.argv[1], sys.argv[2]
|
||||
d = json.load(open(path))
|
||||
wuri = d.get("taler_withdraw_uri") or (
|
||||
d.get("qr_payload") if mode == "auto" else ""
|
||||
) or ""
|
||||
|
||||
if mode == "auto":
|
||||
if not d.get("ok"):
|
||||
print("ok!=true", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if d.get("payto_uri"):
|
||||
print("payto_uri must not be present", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not check_withdraw_uri(wuri):
|
||||
sys.exit(1)
|
||||
webui = d.get("login_url") or d.get("webui") or d.get("account_url") or ""
|
||||
u = urlparse(webui)
|
||||
if u.scheme not in ("http", "https") or "webui" not in (u.path or ""):
|
||||
print("login webui missing:", webui[:80], file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print("%s · %s" % (d.get("username", ""), wuri[:72]))
|
||||
elif mode == "demo":
|
||||
if not d.get("ok", True) and "taler_withdraw_uri" not in d:
|
||||
print("not ok", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not check_withdraw_uri(wuri):
|
||||
sys.exit(1)
|
||||
print("%s\t%s" % (wuri[:80], d.get("withdrawal_id") or ""))
|
||||
else:
|
||||
print("bad mode:", mode, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
sys.exit(0)
|
||||
PY
|
||||
}
|
||||
|
||||
# --- exchange (core; always required) --- www-001 …
|
||||
check_url "exchange /config" 200 "$EXCHANGE_PUBLIC/config"
|
||||
code=$(http_body "$EXCHANGE_PUBLIC/config" "$tmp/ec.json")
|
||||
|
|
@ -154,6 +225,8 @@ section "www · performance · public HTTPS latency (outside-in)"
|
|||
# ≥ PERF_WARN_MS → WARN. ≥ PERF_FAIL_MS → ERROR (suite fails).
|
||||
PERF_WARN_MS="${PERF_WARN_MS:-8000}"
|
||||
PERF_FAIL_MS="${PERF_FAIL_MS:-20000}"
|
||||
PERF_TSV="$tmp/perf.tsv"
|
||||
: >"$PERF_TSV"
|
||||
|
||||
# Measure one URL: require HTTP expect (default 200), report time_total in ms.
|
||||
# $1=label $2=url $3=optional expected codes (default 200)
|
||||
|
|
@ -169,6 +242,8 @@ check_perf() {
|
|||
if (ms>0 && ms<1) ms=1
|
||||
printf "%d", int(ms+0.5)
|
||||
}')
|
||||
# Always record sample for rollup (label, ms, http, url)
|
||||
printf '%s\t%s\t%s\t%s\n' "$label" "$ms" "$code" "$url" >>"$PERF_TSV"
|
||||
case ",$expect," in
|
||||
*",$code,"*)
|
||||
if [ "$ms" -ge "${PERF_FAIL_MS}" ] 2>/dev/null; then
|
||||
|
|
@ -323,6 +398,76 @@ if [ "${CHECK_LANDING:-1}" = "1" ] || [ "${LOCAL_STACK:-0}" = "1" ]; then
|
|||
report_landing_load_stats "merchant" "$MERCHANT_PUBLIC"
|
||||
info "perf landing-stats note" "from /intro/stats.json (public); SSH container fallback if needed"
|
||||
fi
|
||||
# Rollup + optional JSON for metrics_print_overall
|
||||
if [ -s "$PERF_TSV" ]; then
|
||||
PERF_JSON="$tmp/perf-summary.json"
|
||||
perf_line=$(python3 - "$PERF_TSV" "$PERF_JSON" "$PERF_WARN_MS" "$PERF_FAIL_MS" <<'PY'
|
||||
import json, sys
|
||||
rows = []
|
||||
for line in open(sys.argv[1]):
|
||||
parts = line.rstrip("\n").split("\t")
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
label, ms_s, code = parts[0], parts[1], parts[2]
|
||||
try:
|
||||
ms = int(float(ms_s))
|
||||
except Exception:
|
||||
continue
|
||||
rows.append({"label": label, "ms": ms, "http": code})
|
||||
vals = sorted(r["ms"] for r in rows)
|
||||
n = len(vals)
|
||||
if n == 0:
|
||||
print("n=0")
|
||||
json.dump({"n": 0}, open(sys.argv[2], "w"))
|
||||
raise SystemExit(0)
|
||||
def pct(p):
|
||||
if n == 1:
|
||||
return vals[0]
|
||||
i = min(n - 1, max(0, int(round((p / 100.0) * (n - 1)))))
|
||||
return vals[i]
|
||||
avg = int(round(sum(vals) / n))
|
||||
warn_ms = int(sys.argv[3])
|
||||
fail_ms = int(sys.argv[4])
|
||||
slow = [r for r in rows if r["ms"] >= warn_ms]
|
||||
rep = {
|
||||
"n": n,
|
||||
"min_ms": vals[0],
|
||||
"p50_ms": pct(50),
|
||||
"avg_ms": avg,
|
||||
"max_ms": vals[-1],
|
||||
"warn_ms": warn_ms,
|
||||
"fail_ms": fail_ms,
|
||||
"slow": [{"label": r["label"], "ms": r["ms"]} for r in slow],
|
||||
"samples": rows,
|
||||
}
|
||||
# metrics_print_overall expects named buckets with n/min/p50/avg/max
|
||||
out = {
|
||||
"www_public_https": {
|
||||
"n": n,
|
||||
"min_ms": vals[0],
|
||||
"p50_ms": pct(50),
|
||||
"avg_ms": avg,
|
||||
"max_ms": vals[-1],
|
||||
},
|
||||
**{r["label"].replace(" ", "_"): {"n": 1, "min_ms": r["ms"], "p50_ms": r["ms"], "avg_ms": r["ms"], "max_ms": r["ms"]} for r in rows},
|
||||
}
|
||||
json.dump(out, open(sys.argv[2], "w"), indent=2)
|
||||
extra = ""
|
||||
if slow:
|
||||
extra = " slow: " + ", ".join("%s=%dms" % (r["label"].replace("perf ", ""), r["ms"]) for r in slow)
|
||||
print(
|
||||
"n=%d min=%dms p50=%dms avg=%dms max=%dms (warn≥%dms fail≥%dms)%s"
|
||||
% (n, vals[0], pct(50), avg, vals[-1], warn_ms, fail_ms, extra)
|
||||
)
|
||||
PY
|
||||
)
|
||||
info "perf summary" "$perf_line"
|
||||
# Keep a copy if METRICS_DIR is set (e2e/ladder overall stats)
|
||||
if [ -n "${METRICS_DIR:-}" ] && [ -d "${METRICS_DIR}" ]; then
|
||||
cp -f "$PERF_JSON" "${METRICS_DIR}/perf-summary.json" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
info "perf note" "measured from this host (outside-in); thresholds PERF_WARN_MS=${PERF_WARN_MS} PERF_FAIL_MS=${PERF_FAIL_MS}"
|
||||
|
||||
|
||||
# Terms + privacy (legal docs)
|
||||
|
|
@ -361,48 +506,25 @@ if [ "$code" = "200" ]; then
|
|||
check_url_soft "bank /taler-integration/config" 200 "$BANK_PUBLIC/taler-integration/config"
|
||||
fi
|
||||
check_url_soft "bank /webui/" 200 "$BANK_PUBLIC/webui/"
|
||||
if [ "${CHECK_LANDING:-1}" = "1" ]; then
|
||||
check_url_soft "bank /intro/" 200 "$BANK_PUBLIC/intro/"
|
||||
check_url_soft "bank /" 302,301,200 "$BANK_PUBLIC/"
|
||||
# Auto-account: credentials + shared-pool taler://withdraw (like step 2)
|
||||
aa_code=$(http_body "$BANK_PUBLIC/intro/auto-account.json" "$tmp/aa.json")
|
||||
case "$aa_code" in
|
||||
200)
|
||||
if python3 - "$tmp/aa.json" <<'PY'
|
||||
import json, re, sys
|
||||
from urllib.parse import urlparse
|
||||
d = json.load(open(sys.argv[1]))
|
||||
if not d.get("ok"):
|
||||
print("ok!=true"); sys.exit(1)
|
||||
if "payto_uri" in d and d.get("payto_uri"):
|
||||
print("payto_uri must not be present"); sys.exit(1)
|
||||
wuri = d.get("taler_withdraw_uri") or d.get("qr_payload") or ""
|
||||
wm = re.match(r"^taler://withdraw/([^/]+)/taler-integration/([0-9a-fA-F-]+)$", wuri)
|
||||
if not wm:
|
||||
print("need taler://withdraw/HOST:PORT/taler-integration/ID:", wuri[:120]); sys.exit(1)
|
||||
if ":" not in wm.group(1):
|
||||
print("withdraw missing port:", wm.group(1)); sys.exit(1)
|
||||
webui = d.get("login_url") or d.get("webui") or d.get("account_url") or ""
|
||||
u = urlparse(webui)
|
||||
if u.scheme not in ("http", "https") or "webui" not in (u.path or ""):
|
||||
print("login webui missing:", webui[:80]); sys.exit(1)
|
||||
print("user=%s withdraw=%s login=%s" % (d.get("username"), wm.group(1), webui))
|
||||
sys.exit(0)
|
||||
PY
|
||||
then
|
||||
ok "bank /intro/auto-account.json" "$(python3 -c 'import json;d=json.load(open("'"$tmp/aa.json"'"));print(d.get("username",""),"·",(d.get("taler_withdraw_uri") or "")[:72])' 2>/dev/null || true)"
|
||||
else
|
||||
fail "bank /intro/auto-account.json" "invalid withdraw/login (HTTP body bad)"
|
||||
fi
|
||||
;;
|
||||
405|501|404|502|503|000)
|
||||
fail "bank /intro/auto-account.json" "HTTP $aa_code (want 200; 405/501 = broken)"
|
||||
;;
|
||||
*)
|
||||
fail "bank /intro/auto-account.json" "HTTP $aa_code want 200"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
check_url_soft "bank /intro/" 200 "$BANK_PUBLIC/intro/"
|
||||
check_url_soft "bank /" 302,301,200 "$BANK_PUBLIC/"
|
||||
# Auto-account: credentials + shared-pool taler://withdraw (like step 2)
|
||||
aa_code=$(http_body "$BANK_PUBLIC/intro/auto-account.json" "$tmp/aa.json")
|
||||
case "$aa_code" in
|
||||
200)
|
||||
if aa_detail=$(validate_bank_withdraw_json "$tmp/aa.json" auto 2>"$tmp/aa-val"); then
|
||||
ok "bank /intro/auto-account.json" "$aa_detail"
|
||||
else
|
||||
fail "bank /intro/auto-account.json" "invalid withdraw/login ($(tr '\n' ' ' <"$tmp/aa-val" | sed 's/[[:space:]]*$//'))"
|
||||
fi
|
||||
;;
|
||||
405|501|404|502|503|000)
|
||||
fail "bank /intro/auto-account.json" "HTTP $aa_code (want 200; 405/501 = broken)"
|
||||
;;
|
||||
*)
|
||||
fail "bank /intro/auto-account.json" "HTTP $aa_code want 200"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Bank legal docs (landing nginx via Caddy /terms* /privacy* or /intro/*)
|
||||
|
|
@ -802,23 +924,11 @@ if [ "${LOCAL_STACK:-1}" = "1" ] || [ -n "${BANK_PUBLIC:-}" ]; then
|
|||
dw_code=$(http_body "$BANK_PUBLIC/intro/demo-withdraw.json" "$tmp/dw.json")
|
||||
case "$dw_code" in
|
||||
200)
|
||||
if python3 - "$tmp/dw.json" <<'PY'
|
||||
import json, re, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
if not d.get("ok", True) and "taler_withdraw_uri" not in d:
|
||||
print("not ok"); sys.exit(1)
|
||||
u = d.get("taler_withdraw_uri") or ""
|
||||
m = re.match(r"^taler://withdraw/([^/]+)/taler-integration/([0-9a-fA-F-]+)$", u)
|
||||
if not m:
|
||||
print("bad uri:", u[:120]); sys.exit(1)
|
||||
if ":" not in m.group(1):
|
||||
print("missing port:", m.group(1)); sys.exit(1)
|
||||
print(u[:88])
|
||||
sys.exit(0)
|
||||
PY
|
||||
then
|
||||
_ba_ok=$((_ba_ok + 1))
|
||||
wid=$(python3 -c 'import json;print(json.load(open("'"$tmp/dw.json"'")).get("withdrawal_id",""))' 2>/dev/null || true)
|
||||
if dw_out=$(validate_bank_withdraw_json "$tmp/dw.json" demo 2>"$tmp/dw-val"); then
|
||||
dw_detail=${dw_out%%$'\t'*}
|
||||
wid=${dw_out#*$'\t'}
|
||||
[ "$wid" = "$dw_out" ] && wid=
|
||||
ok "bank /intro/demo-withdraw.json" "$dw_detail"
|
||||
if [ -n "$wid" ]; then
|
||||
if _landing_probe "$BANK_PUBLIC/taler-integration/withdrawal-operation/${wid}"; then
|
||||
_ba_ok=$((_ba_ok + 1))
|
||||
|
|
@ -829,6 +939,7 @@ PY
|
|||
ok "landing bank withdraw/shop" "demo-withdraw + shop assets ok (${_ba_ok} checks)"
|
||||
else
|
||||
fail "landing bank demo-withdraw" "invalid taler://withdraw shape"
|
||||
fail "bank /intro/demo-withdraw.json" "invalid taler://withdraw ($(tr '\n' ' ' <"$tmp/dw-val" | sed 's/[[:space:]]*$//'))"
|
||||
fi
|
||||
;;
|
||||
405|501|404|502|503|000)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue