#!/usr/bin/env python3
"""隔离测量 showmap 去重相位:内容级预去重 vs 全量 showmap(无 MPI,单 StreamingShowmap 子进程)。
直接对 StreamingShowmap.get_edges + CoverageBitmap.merge 计时,复现 D5③ 的 showmap_dedup 相位。"""
import sys, os, glob, subprocess, tempfile, time, hashlib
sys.path.insert(0, "util")
import mpi_fuzzing_helper as m

ROOT = "/home/ubuntu/code/symcc"
SYMCC_BIN = os.path.join(ROOT, "benchmark/public/bin/google-fts/xml_read_fuzzer")
AFL_BIN = os.path.join(ROOT, "benchmark/public/bin/google-fts-afl/xml_read_fuzzer")
SEEDS = os.path.join(ROOT, "benchmark/public/seeds/google-fts/xml_read_fuzzer")
AFL_SHOWMAP = "/usr/local/bin/afl-showmap"
# xml harness:afl-showmap 喂法探测——持久/shmem(含 ##SIG_AFL_PERSISTENT##)→ 无 @@,否则 @@
_bin = open(AFL_BIN, "rb").read()
ARGS = [] if b"##SIG_AFL_PERSISTENT##" in _bin else ["@@"]
print(f"target 喂法: {'shmem/persistent(无@@)' if not ARGS else '文件(@@)'}")

# 1) 用 SymCC 生成一批真实输出(文件模式:SYMCC_INPUT_FILE + argv 传种子,同 D5②)
od = tempfile.mkdtemp(prefix="mb_")
for s in sorted(glob.glob(os.path.join(SEEDS, "*")))[:10]:
    env = dict(os.environ, SYMCC_OUTPUT_DIR=od, SYMCC_ENABLE_LINEARIZATION="1",
               SYMCC_INPUT_FILE=s)
    try:
        subprocess.run([SYMCC_BIN, s], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
                       stderr=subprocess.DEVNULL, env=env, timeout=90)
    except Exception:
        pass
contents = []
for f in os.listdir(od):
    p = os.path.join(od, f)
    if os.path.isfile(p):
        contents.append(open(p, "rb").read())
n = len(contents)
byte_dup = n - len({hashlib.blake2b(c, digest_size=16).digest() for c in contents})
print(f"生成输出 = {n},字节级重复 = {byte_dup} ({100*byte_dup/max(1,n):.1f}%)")

def run_dedup(prefilter: bool) -> "tuple[int,int,int,float]":
    """返回 (interesting_TC 数, get_edges 调用数, 跳过数, 去重相位耗时秒)。"""
    sm = m.StreamingShowmap(AFL_SHOWMAP, [AFL_BIN] + ARGS)
    cov = m.CoverageBitmap()
    seen: set[bytes] = set()
    interesting = calls = skipped = 0
    t0 = time.monotonic()
    for c in contents:
        if prefilter:
            k = hashlib.blake2b(c, digest_size=16).digest()
            if k in seen:
                skipped += 1
                continue
            seen.add(k)
        e = sm.get_edges(c)
        calls += 1
        if e is not None and cov.merge(e):
            interesting += 1
    dt = time.monotonic() - t0
    sm.close()
    return interesting, calls, skipped, dt

# 预热(丢弃首个 forkserver 冷启动)
run_dedup(False)
b_i, b_calls, _, b_t = run_dedup(False)      # baseline:全量 showmap
o_i, o_calls, o_dup, o_t = run_dedup(True)   # optimized:内容预去重
print(f"\n{'':10} {'interesting':>11} {'get_edges调用':>13} {'跳过':>6} {'去重耗时':>10}")
print(f"{'baseline':10} {b_i:>11} {b_calls:>13} {0:>6} {b_t*1000:>8.1f}ms")
print(f"{'optimized':10} {o_i:>11} {o_calls:>13} {o_dup:>6} {o_t*1000:>8.1f}ms")
print(f"\n正确性:interesting 一致 = {b_i == o_i} (baseline {b_i} vs optimized {o_i})")
print(f"showmap 调用减少 = {b_calls-o_calls} = {100*(b_calls-o_calls)/max(1,b_calls):.1f}%")
print(f"去重相位提速 = {100*(b_t-o_t)/max(1e-9,b_t):.1f}%  ({b_t*1000:.1f}ms → {o_t*1000:.1f}ms)")
import shutil; shutil.rmtree(od, ignore_errors=True)
