#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/common.sh" STEP="5g-link-logger" resolve_target_ip() { if [[ -n "${BLITZ_TIME_SERVER_IP:-}" ]]; then printf '%s\n' "${BLITZ_TIME_SERVER_IP}" return 0 fi for candidate in ${BLITZ_5G_ROUTE_TARGETS//,/ }; do if [[ -n "${candidate}" ]]; then printf '%s\n' "${candidate}" return 0 fi done return 1 } emit_sample_json() { local interface_name="${1:-}" local target_ip="${2:-}" python3 - "${interface_name}" "${target_ip}" <<'PY' import json import subprocess import sys import time interface_name = sys.argv[1] target_ip = sys.argv[2] payload = { "ts_unix_ms": time.time_ns() // 1_000_000, "interface": interface_name, "target_ip": target_ip, "link_present": False, "route_output": "", "route_ok": False, "probe_ok": False, "ping_rtt_ms": None, "rx_bytes": 0, "tx_bytes": 0, "rx_packets": 0, "tx_packets": 0, "rx_errors": 0, "tx_errors": 0, "rx_drops": 0, "tx_drops": 0, } if interface_name: try: output = subprocess.check_output( ["ip", "-j", "-s", "link", "show", "dev", interface_name], text=True, stderr=subprocess.DEVNULL, ) stats = json.loads(output) if stats: item = stats[0] payload["link_present"] = True rx = item.get("stats64", {}).get("rx", {}) tx = item.get("stats64", {}).get("tx", {}) if not rx and not tx: rx = item.get("stats", {}).get("rx", {}) tx = item.get("stats", {}).get("tx", {}) payload["rx_bytes"] = int(rx.get("bytes") or 0) payload["tx_bytes"] = int(tx.get("bytes") or 0) payload["rx_packets"] = int(rx.get("packets") or 0) payload["tx_packets"] = int(tx.get("packets") or 0) payload["rx_errors"] = int(rx.get("errors") or 0) payload["tx_errors"] = int(tx.get("errors") or 0) payload["rx_drops"] = int(rx.get("dropped") or 0) payload["tx_drops"] = int(tx.get("dropped") or 0) except Exception: pass if target_ip: try: route = subprocess.check_output( ["ip", "route", "get", target_ip], text=True, stderr=subprocess.STDOUT, ).strip() payload["route_output"] = route.splitlines()[0] if route else "" payload["route_ok"] = bool(payload["route_output"]) and ( not interface_name or f" dev {interface_name}" in payload["route_output"] ) except Exception as exc: payload["route_output"] = str(exc) ping_cmd = ["ping", "-c", "1", "-W", "2", target_ip] if interface_name: ping_cmd[1:1] = ["-I", interface_name] ping = subprocess.run(ping_cmd, capture_output=True, text=True) payload["probe_ok"] = ping.returncode == 0 output = (ping.stdout or "") + "\n" + (ping.stderr or "") for token in output.replace("\n", " ").split(): if token.startswith("time="): value = token.split("=", 1)[1].rstrip("ms") try: payload["ping_rtt_ms"] = float(value) except ValueError: pass break print(json.dumps(payload, separators=(",", ":"), ensure_ascii=False)) PY } if [[ "${OMNI_BOOT_MODE:-0}" == "1" ]]; then blitz_load_boot_env blitz_require_run_context fi if [[ -z "${BLITZ_RUN_DIR:-}" && -f "${BLITZ_RUN_CONTEXT_FILE:-}" ]]; then blitz_load_run_context_env || true fi blitz_ensure_instance_id export BLITZ_5G_LINK_LOG_PATH="${BLITZ_5G_LINK_LOG_PATH:-${BLITZ_RUN_DIR}/b-5g-link-quality.${BLITZ_INSTANCE_ID}.jsonl}" target_ip="$(resolve_target_ip || true)" blitz_log "${STEP}" "start" "start" "path=${BLITZ_5G_LINK_LOG_PATH} interval_sec=${BLITZ_5G_LINK_LOG_INTERVAL_SEC}" 0 while true; do interface_name="$(blitz_resolve_5g_interface || true)" line="$(emit_sample_json "${interface_name}" "${target_ip}")" blitz_jsonl_append_line "${BLITZ_5G_LINK_LOG_PATH}" "${line}" sleep "${BLITZ_5G_LINK_LOG_INTERVAL_SEC}" done