阿克森云登录 / 注册English/中文/cn繁体/tw

mtr-report.py

AxionServe-Docsai-code2026-10-13♥ 0
🤖 AIGC 披露:本作品由「AI-assisted generation + human testing」辅助创作,经社区公约审核发布。
#!/usr/bin/env python3
# mtr-report.py (FIXED) — MTR report parsing and evidence generation (standard library only)
# Fixes (R7 audit P1):
#   1) The original NUM regex did not match real mtr output: after the hop number comes "N.|--"
#      (no space after the dot, with a pipe), while the old pattern required "\d+\.\s+--?" → always a miss.
#      The Loss column also carries a "%", and the old ([\d.]+) would swallow the digits before the %
#      and then break on it → the whole line failed to match.
#   2) Removed the never-used LINE regex (dead code, and its parentheses did not balance).
#   3) Tolerates "???" (timed-out hop) hostnames and reverse-resolved hostnames (-z still recommended).
# Usage: mtr -rwzbc 100 TARGET_IP > raw.txt && python3 mtr-report.py raw.txt TARGET_IP
import sys, re, json, datetime

NUM = re.compile(
    r"^\s*(\d+)\.\s*\|?--\s*"          # hop number + optional pipe + double dash ("1.|--" / "1. --")
    r"(\S+.*?)\s+"                      # host (including "???")
    r"([\d.]+)%\s+"                     # Loss% (the percent sign is required)
    r"(\d+)\s+"                         # Snt
    r"([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s*$"  # Last Avg Best Wrst StDev
)

def parse(path):
    hops = []
    for ln in open(path, encoding="utf-8", errors="replace"):
        m = NUM.match(ln.rstrip())
        if not m:
            continue
        hops.append(dict(
            hop=int(m.group(1)), host=m.group(2).strip(),
            loss=float(m.group(3)), snt=int(m.group(4)),
            last=float(m.group(5)), avg=float(m.group(6)),
            best=float(m.group(7)), wrst=float(m.group(8)),
            stdev=float(m.group(9))))
    return hops

def judge(hops):
    flags = []
    last = hops[-1] if hops else None
    for h in hops:
        if h["loss"] > 0 and h is not last:
            after = [x for x in hops if x["hop"] > h["hop"]]
            if after and all(x["loss"] == 0 for x in after):
                flags.append((h["hop"], "ICMP_RATE_LIMITED",
                              "intermediate hop loss with clean hops after it: suspected ICMP rate limiting by the router, not a fault"))
        if h["avg"] > 0 and h["stdev"] > h["avg"] * 0.5:
            flags.append((h["hop"], "HIGH_JITTER",
                          f"StDev {h['stdev']} > Avg {h['avg']}×0.5: significant jitter"))
    verdict = "PATH_OK"
    if last and last["loss"] > 0:
        verdict = "REAL_LOSS"
        flags.append((last["hop"], "REAL_LOSS",
                      f"sustained loss at the final hop {last['loss']}%: real loss, safe to open a ticket"))
    return verdict, flags

def main():
    if len(sys.argv) < 3:
        print(__doc__); sys.exit(1)
    src, target = sys.argv[1], sys.argv[2]
    out = sys.argv[3] if len(sys.argv) > 3 else "mtr-evidence"
    hops = parse(src)
    if not hops:
        print("no hops parsed (confirm the input is mtr --report / -rwzbc format)"); sys.exit(2)
    verdict, flags = judge(hops)
    ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    meta = dict(target=target, generated=ts, hops=len(hops),
                verdict=verdict, source_file=src)
    with open(f"{out}-{ts}.json", "w", encoding="utf-8") as f:
        json.dump(dict(meta=meta, hops=hops,
                       flags=[dict(hop=a, tag=b, note=c) for a, b, c in flags]),
                  f, ensure_ascii=False, indent=1)
    md = ["# MTR evidence file", "",
          f"- Target: `{target}`  · Generated: {ts}  · Verdict: **{verdict}**", "",
          "| Hop | Host | Loss% | Snt | Avg | Wrst | StDev |",
          "|---|---|---|---|---|---|---|"]
    for h in hops:
        md.append(f"| {h['hop']} | {h['host']} | {h['loss']} | {h['snt']} "
                  f"| {h['avg']} | {h['wrst']} | {h['stdev']} |")
    if flags:
        md += ["", "## Automatic verdict"]
        md += [f"- hop {a} [{b}] {c}" for a, b, c in flags]
    with open(f"{out}-{ts}.md", "w", encoding="utf-8") as f:
        f.write("\n".join(md) + "\n")
    print(f"verdict={verdict} · wrote {out}-{ts}.json / {out}-{ts}.md")

if __name__ == "__main__":
    main()

──────────────────────
[Test log · fixed version] Parses a real `mtr -rwzbc 100` sample correctly (6 hops, including a "???" timed-out hop and a 100.0% loss line); the REAL_LOSS / ICMP_RATE_LIMITED / HIGH_JITTER verdicts were validated against constructed samples.
[What was fixed] The R7 audit found the original regex did not match real mtr output (the hop column is "N.|--" and the Loss column carries a %). It is now \d+\.\s*\|?-- with a ([\d.]+)% capture, and the unused LINE dead code is gone.
[Ticket convention] Paste the .md file straight into the ticket: verdict line + per-hop table + automatic reading.
[Known limits] It is most stable on -z (no reverse DNS) output; if you want reverse-resolved names, rerun with `mtr --report-wide -z`.
复现提示词(全文)
Write a Python 3 script mtr-report.py: parse the text output of `mtr --report` (or `mtr -rwzbc N`) — HOST: lines of the form "hop host loss% snt last avg best wrst stdev" — and emit two files: JSON (structured per-hop data plus metadata) and a Markdown evidence file (table plus automatic verdict). Verdict rules: loss > 0 at the final hop → REAL_LOSS; loss on an intermediate hop while all later hops are clean → ICMP_RATE_LIMITED (suspected rate limiting, not a fault); stdev > avg × 0.5 → HIGH_JITTER. CLI arguments: input file, target name, optional --out prefix. Standard library only.
♥ 0 赞会员登录后可点赞支持 TA举报
相关作品cost-calc.js (FIXED)first-hour-init.shWhy datacenters are called HKG, SIN and NRT