lessismore/bench_real.py
jing 0d4c5464b9 Two-tier template mining + faithful collapse + equal-budget receipt; 0.4.0
alias_templates is now two tiers with exact char accounting:
- tier A masks hex/digit runs INSIDE tokens (run-level constants — dates,
  IP prefixes, the 2 in ssh2 — inline into the template); groups are
  re-costed under whole-token value encoding and take the cheaper
- tier B masks whole digit-bearing tokens on what tier A left, so paths/ids
  varying in letters still group (compile/build logs)
- dedupe_similar skeleton is hex-aware but keeps a leading @tN verbatim:
  refs from different templates can no longer merge as "similar" — an
  earlier build scored 8.9x on LogHub from exactly that silent value
  destruction, and v0.3.0's HDFS number had the same phantom; both gone

Measured (bench_real.py): LogHub 3.0x -> 3.9x, LogDx-CI 2.2x -> 2.3x at
unchanged 95.2% critical-signal retention, LogChunks 1.9x -> 2.1x.
Equal-budget receipt added to bench_real logdx: at a 2k cap,
compress-then-truncate retains 46.6% of critical signals vs 24.7% for
plain truncation. Synthetic eval re-run on shipped code: 8/8 at 6.4x.
Graveyard: Drain-style letter-merging measured flat (3.0x -> 3.0x, Linux
regressed) and documented dead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 18:27:39 +10:00

143 lines
5.9 KiB
Python

"""bench_real.py — measure lessismore on REAL public log corpora, not synthetic ones.
Three benchmarks, all free, all with a deterministic metric (no model calls):
loghub 10 LogHub sample logs (HDFS, BGL, OpenSSH, ...) — token ratios on
real system logs. Downloads ~2.8 MB from GitHub on first run.
https://github.com/logpai/loghub (CC-BY-4.0)
logchunks 797 Travis CI failure logs with HUMAN-labeled failure-explaining
chunks (MSR 2020). Metric: do the labeled chunk lines survive?
curl -L -o LogChunks.zip \\
"https://zenodo.org/api/records/3632351/files/LogChunks.zip/content"
unzip LogChunks.zip
python3 bench_real.py logchunks ./LogChunks
logdx LogDx-CI: 35 real GitHub Actions failures with per-case
required-signal ground truth (arXiv 2605.28876, CC-BY-4.0).
Metric: are the critical diagnostic signals still present?
pip install huggingface_hub
hf download eyuansu71/logdx-ci --repo-type dataset --local-dir ./logdx-ci
python3 bench_real.py logdx ./logdx-ci
Chunk/signal matching normalizes whitespace and ANSI on both sides (the labels
themselves contain terminal color remnants), nothing else.
"""
import json
import re
import sys
import urllib.request
import xml.etree.ElementTree as ET
from pathlib import Path
from lessismore import budget, compress, count_tokens
_CSI = re.compile(r"\x1b?\[[0-9;]*[A-Za-z]") # ANSI with or without the ESC byte
def norm(s):
return re.sub(r"\s+", " ", _CSI.sub("", s)).strip()
LOGHUB = ["HDFS", "BGL", "OpenStack", "Zookeeper", "Apache",
"OpenSSH", "Spark", "Thunderbird", "Linux", "HealthApp"]
def bench_loghub(cache=Path("loghub_samples")):
cache.mkdir(exist_ok=True)
tot_b = tot_a = 0
print(f"{'dataset':14} {'raw tok':>8} {'small':>8} {'ratio':>6}")
for name in LOGHUB:
f = cache / f"{name}_2k.log"
if not f.exists():
url = f"https://raw.githubusercontent.com/logpai/loghub/master/{name}/{name}_2k.log"
f.write_bytes(urllib.request.urlopen(url, timeout=60).read())
text = f.read_text(encoding="utf-8", errors="replace")
b, a = count_tokens(text), count_tokens(compress(text, 2))
tot_b += b; tot_a += a
print(f"{name:14} {b:>8,} {a:>8,} {b/a:>5.1f}x")
print(f"{'TOTAL':14} {tot_b:>8,} {tot_a:>8,} {tot_b/tot_a:>5.1f}x")
def bench_logchunks(root):
root = Path(root)
tot_b = tot_a = n = lv = la = 0
full_v = 0
for xf in sorted(root.glob("build-failure-reason/*/*.xml")):
try:
tree = ET.parse(xf)
except ET.ParseError:
continue
for ex in tree.getroot().iter("Example"):
p = root / "logs" / ex.findtext("Log", "").strip()
chunk = ex.findtext("Chunk", "")
if not (p.exists() and chunk.strip()):
continue
out = compress(p.read_text(encoding="utf-8", errors="replace"), 2)
tot_b += count_tokens(p.read_text(encoding="utf-8", errors="replace"))
tot_a += count_tokens(out)
n += 1
nout = norm(out)
clines = [norm(l) for l in chunk.split("\n") if norm(l)]
v = sum(1 for l in clines if l in nout)
lv += v; la += len(clines); full_v += v == len(clines)
print(f"cases: {n} tokens {tot_b:,} -> {tot_a:,} ({tot_b/tot_a:.1f}x, {1-tot_a/tot_b:.0%} saved)")
print(f"labeled chunk lines retained verbatim: {lv}/{la} ({lv/la:.1%})")
print(f"chunks fully verbatim: {full_v}/{n} ({full_v/n:.1%})")
print("(the rest is mostly similar-line collapse: the message survives once "
"with a value summary — see README)")
def bench_logdx(root):
root = Path(root)
gts = sorted(set(root.glob("cases/**/ground_truth.json")))
caps = (2000, 8000, 32000)
tot_b = tot_a = ck = ca = 0
cap_raw = {c: 0 for c in caps} # truncate-only retention
cap_cmp = {c: 0 for c in caps} # compress-then-truncate retention
for gt_path in gts:
raw_p = gt_path.parent / "raw.log"
if not raw_p.exists():
continue
raw = raw_p.read_text(encoding="utf-8", errors="replace")
out = compress(raw, 2)
tot_b += count_tokens(raw); tot_a += count_tokens(out)
nout = norm(out)
ntr = {c: norm(budget(raw, c)) for c in caps}
ncm = {c: norm(budget(out, c)) for c in caps}
for sig in json.loads(gt_path.read_text()).get("required_signals", []):
if sig.get("importance") != "critical":
continue
texts = [norm(t) for t in [sig.get("value"), sig.get("file")]
+ sig.get("aliases", []) if t]
ca += 1
ck += any(t in nout for t in texts)
for c in caps:
cap_raw[c] += any(t in ntr[c] for t in texts)
cap_cmp[c] += any(t in ncm[c] for t in texts)
print(f"cases: {len(gts)} tokens {tot_b:,} -> {tot_a:,} ({tot_b/tot_a:.1f}x, {1-tot_a/tot_b:.0%} saved)")
print(f"critical diagnostic signals retained: {ck}/{ca} ({ck/ca:.1%})")
print("equal-budget (the question a harness actually faces):")
for c in caps:
print(f" {c:>6,} tokens: truncate-only {cap_raw[c]/ca:.1%} "
f"compress-then-truncate {cap_cmp[c]/ca:.1%}")
def main():
if len(sys.argv) < 2 or sys.argv[1] not in ("loghub", "logchunks", "logdx"):
sys.exit(__doc__)
if sys.argv[1] == "loghub":
bench_loghub()
elif sys.argv[1] == "logchunks":
if len(sys.argv) < 3:
sys.exit("usage: bench_real.py logchunks <path-to-extracted-LogChunks>")
bench_logchunks(sys.argv[2])
else:
if len(sys.argv) < 3:
sys.exit("usage: bench_real.py logdx <path-to-logdx-ci-dataset>")
bench_logdx(sys.argv[2])
if __name__ == "__main__":
main()