Files
BT411/scratchpad/night6/digest.py
T

90 lines
3.7 KiB
Python

"""Deterministically digest the tester logs into compact per-session event timelines.
The logs are 1.4-5.8 MB each and mostly per-frame telemetry. Everything that
matters for the ghost/desync triage is a small set of tagged event lines. This
extracts them in order (so causality survives) and counts the rest, turning each
log into a few KB an agent can reason over in one read instead of 90 greps.
"""
import os
import re
import sys
DIR = os.path.dirname(os.path.abspath(__file__))
FILES = {
"vex_LEGION.log": "VexUbiquity (LEGION)",
"elengil_ALIA.log": "Elengil (ALIA)",
"rajel_GAMERSLAB.log": "RajelAran (GAMERSLAB)",
"sauron_XIAOLONG.log": "SAURON (XIAOLONG) -- HOST of match 2",
"connman_MSFIREFLY.log": "Conn Man (MS-FIREFLY)",
}
# Ordered-timeline events: anything bearing on death, respawn, entity identity,
# replication, or a reported symptom.
KEEP = re.compile(
r"\[respawn\]|\[damage\]|\[deathfx\]|\[paint\]|DESTROYED|SWALLOWED|RECOVERED"
r"|\[matchlog\]|PLAYER_LINK|\[score\]|\[kd\]|KILL|DEATH"
r"|\[steamnet\]|PEER|ROUTE|HELLO|JOIN|join|LOBBY|relay"
r"|MakeMessage|make message|entity create|CreateEntity|DynamicEntity|interest"
r"|collision volume|CollisionVolume|\[warp\]|translocat|drop zone|dropzone"
r"|WARNING|ERROR|FAIL|Fail\(|assert|Assert|crash|EXCEPTION"
r"|acquireFails|source census|pool exhaust|DROP transient"
r"|\[gimp|limp|gaitSM state=2[2-7]|\[exp\]|\[tick\]|BUG \(Gitea"
r"|SESSION build=",
re.I)
# High-frequency telemetry we only COUNT (never list).
NOISE = re.compile(r"\[syncF\]|\[gaitSM\]|\[drive\]|\[mppr|\[spatial\]|\[audio\]|\[startreq\]"
r"|\[mrec-rx\]|\[rx0\]|\[wire\]|\[gait\]|\[target\]|\[zone-armor\]"
r"|\[animind\]|\[seqev\]|SetupPatch", re.I)
MAXPERSESS = 260 # ordered events per session (last sessions get the budget)
def digest(path, who):
raw = open(path, encoding="latin-1", errors="replace").read().splitlines()
# split into sessions
bounds = [i for i, l in enumerate(raw) if "BT411 SESSION build=" in l]
if not bounds:
bounds = [0]
bounds.append(len(raw))
out = [f"##### {who} ({os.path.basename(path)}, {len(raw)} lines, "
f"{len(bounds)-1} sessions)"]
sess = list(zip(bounds, bounds[1:]))
# only the last 3 sessions in detail; earlier ones get one summary line
for idx, (s, e) in enumerate(sess):
head = raw[s][:150] if s < len(raw) else ""
if idx < len(sess) - 3:
out.append(f"-- session {idx+1}: {head} [{e-s} lines, skipped: pre-match]")
continue
out.append("")
out.append(f"===== session {idx+1} of {len(sess)} lines {s+1}-{e} {head}")
kept, noise = [], 0
for i in range(s, e):
l = raw[i]
if NOISE.search(l):
noise += 1
continue
if KEEP.search(l):
kept.append(f" {i+1}: {l[:190]}")
if len(kept) > MAXPERSESS:
half = MAXPERSESS // 2
kept = kept[:half] + [f" ... {len(kept)-MAXPERSESS} events elided ..."] + kept[-half:]
out.extend(kept)
out.append(f" [telemetry lines suppressed in this session: {noise}]")
return "\n".join(out)
total = 0
for f, who in FILES.items():
p = os.path.join(DIR, f)
if not os.path.exists(p):
print("MISSING", p)
continue
d = digest(p, who)
o = os.path.join(DIR, "digest_" + f.replace(".log", ".txt"))
open(o, "w", encoding="utf-8").write(d)
total += len(d)
print("%-34s %7.1f KB -> %6.1f KB (%s)" % (
f, os.path.getsize(p)/1024, len(d)/1024, os.path.basename(o)))
print("total digest size: %.1f KB" % (total/1024))