perf: batch resolve accept dest IPs in activity (4x speedup)

- json_helper: batch_resolve_dest() resolves all dest IPs in one Python call
- json.sh: json::batch_resolve_dest wrapper
- activity: _DEST_RESOLVE_CACHE pre-populated before render loop
- _render_peer_accept_dests: cache lookup instead of resolve::dest per row
- activity: 1.8s -> 0.48s
This commit is contained in:
Nuno Duque Nunes 2026-05-29 00:27:43 +00:00
parent d26e67b940
commit b153f222a5
4 changed files with 99 additions and 20 deletions

View file

@ -188,27 +188,50 @@ function cmd::activity::run() {
# ── Accept dest inline renderer ── # ── Accept dest inline renderer ──
_render_peer_accept_dests() { _render_peer_accept_dests() {
local peer_name="$1" local peer_name="$1"
local keys="${_ACCEPT_DEST_KEYS[$peer_name]:-}" local keys="${_ACCEPT_DEST_KEYS[$peer_name]:-}"
[[ -z "$keys" ]] && return 0 [[ -z "$keys" ]] && return 0
for d_key in $keys; do for d_key in $keys; do
local dest_stats="${_ACCEPT_DEST[$d_key]:-}" local dest_stats="${_ACCEPT_DEST[$d_key]:-}"
[[ -z "$dest_stats" ]] && continue [[ -z "$dest_stats" ]] && continue
local d_bytes_orig d_bytes_reply d_count local d_bytes_orig d_bytes_reply d_count
IFS='|' read -r d_bytes_orig d_bytes_reply d_count <<< "$dest_stats" IFS='|' read -r d_bytes_orig d_bytes_reply d_count <<< "$dest_stats"
local rest_key="${d_key#${peer_name}:}" local rest_key="${d_key#${peer_name}:}"
local d_ip="${rest_key%%:*}" local d_ip="${rest_key%%:*}"
local pp="${rest_key#*:}" local pp="${rest_key#*:}"
local d_port="${pp%%:*}" local d_port="${pp%%:*}"
local d_proto="${pp##*:}" local d_proto="${pp##*:}"
local dest_display local spec="${d_ip}:${d_port}:${d_proto}"
dest_display=$(resolve::dest "$d_ip" "$d_port" "$d_proto" 2>/dev/null \ local dest_display="${_DEST_RESOLVE_CACHE[$spec]:-${d_ip}:${d_port}/${d_proto}}"
|| echo "${d_ip}:${d_port}/${d_proto}") ui::activity::accept_dest_row \
ui::activity::accept_dest_row \ "$dest_display" "$d_bytes_orig" "$d_bytes_reply" \
"$dest_display" "$d_bytes_orig" "$d_bytes_reply" \ "$d_count" "$drops_col" "$w_count"
"$d_count" "$drops_col" "$w_count" done
}
declare -gA _DEST_RESOLVE_CACHE=()
local -a _dest_specs=()
for _dk in "${!_ACCEPT_DEST[@]}"; do
# key format: peer:ip:port:proto — strip peer prefix
local _rest="${_dk#*:}"
local _dip="${_rest%%:*}"
local _pp="${_rest#*:}"
local _dport="${_pp%%:*}"
local _dproto="${_pp##*:}"
local _spec="${_dip}:${_dport}:${_dproto}"
# Deduplicate
local _found=false
for _s in "${_dest_specs[@]:-}"; do
[[ "$_s" == "$_spec" ]] && _found=true && break
done done
} $_found || _dest_specs+=("$_spec")
done
if [[ ${#_dest_specs[@]} -gt 0 ]]; then
while IFS='|' read -r _spec _display; do
[[ -n "$_spec" ]] && _DEST_RESOLVE_CACHE["$_spec"]="$_display"
done < <(json::batch_resolve_dest "${_dest_specs[@]}" 2>/dev/null)
fi
local first_peer=true skip_peer=false current_name="" local first_peer=true skip_peer=false current_name=""
local -a rendered_peers=() local -a rendered_peers=()

View file

@ -157,6 +157,7 @@ function json::endpoint_cache_get() { python3 "$JSON_HELPER" endpoint_cach
# Accept Events # Accept Events
function json::accept_events() { python3 "$JSON_HELPER" accept_events "$@" </dev/null; } function json::accept_events() { python3 "$JSON_HELPER" accept_events "$@" </dev/null; }
function json::accept_aggregate() { python3 "$JSON_HELPER" accept_aggregate "$@" </dev/null; } function json::accept_aggregate() { python3 "$JSON_HELPER" accept_aggregate "$@" </dev/null; }
function json::batch_resolve_dest() { python3 "$JSON_HELPER" batch_resolve_dest "$(ctx::net)" "$(ctx::hosts)" "$@" </dev/null; }
function json::peer_transfer() { function json::peer_transfer() {
ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \ ACTIVITY_TOTAL_LOW="$(config::activity_total_low)" \

View file

@ -1821,6 +1821,60 @@ def endpoint_cache_get(cache_file, peer):
except Exception: except Exception:
print('') print('')
def batch_resolve_dest(net_file, hosts_file, *dest_specs):
"""
Resolve multiple ip:port:proto specs at once.
Input: "ip:port:proto" strings
Output: "ip:port:proto|display_name" per line
Uses same logic as resolve::dest bash function.
"""
from lib.util import load_net_data, load_hosts_data, reverse_lookup, hosts_lookup
net_data = load_net_data(net_file)
hosts_data = load_hosts_data(hosts_file)
seen = set()
for spec in dest_specs:
if not spec or spec in seen:
continue
seen.add(spec)
parts = spec.split(':')
if len(parts) < 3:
print(f"{spec}|{spec}")
continue
ip = parts[0]
port = parts[1]
proto = parts[2]
# Try service name first
svc = reverse_lookup(net_data, ip, port, proto)
if svc and svc != ip:
if port:
display = f"{svc}:{proto}-{port}" if False else f"{svc}"
# Use same format as resolve::dest: "svcname/proto" or "svcname:port"
display = svc
else:
display = svc
print(f"{spec}|{display}")
continue
# Try host name
host = hosts_lookup(hosts_data, ip)
if host and host != ip:
if port:
print(f"{spec}|{host}:{port}/{proto}")
else:
print(f"{spec}|{host}")
continue
# Raw fallback
if port:
print(f"{spec}|{ip}:{port}/{proto}")
else:
print(f"{spec}|{ip}")
# ====================================================== # ======================================================
def _net_read(file): def _net_read(file):
@ -2238,6 +2292,7 @@ commands = {
args[3] if len(args) > 3 else '', args[3] if len(args) > 3 else '',
args[4] if len(args) > 4 else '', args[4] if len(args) > 4 else '',
args[5] if len(args) > 5 else '0'), args[5] if len(args) > 5 else '0'),
'batch_resolve_dest': lambda args: batch_resolve_dest(args[0], args[1], *args[2:]),
} }
# ── Main ───────────────────────────────────────────────────────────────────── # ── Main ─────────────────────────────────────────────────────────────────────