mirror of
https://github.com/soxoj/maigret.git
synced 2026-09-05 01:17:40 +02:00
128 lines
3.9 KiB
Python
128 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Triage report for `maigret --self-check --diagnose` output.
|
|
|
|
False negatives (claimed user not detected) and check errors are invisible
|
|
in normal usage: users just don't see the account. This tool surfaces them.
|
|
|
|
Workflow:
|
|
|
|
# 1. Run self-check on a TEMP COPY of the DB (scans/self-check may rewrite it)
|
|
cp maigret/resources/data.json /tmp/data.json
|
|
echo n | python3 -m maigret --self-check --diagnose --top-sites 500 \
|
|
--db /tmp/data.json --no-progressbar --no-color > /tmp/selfcheck.log 2>&1
|
|
|
|
# 2. Classify and rank failures by site popularity
|
|
python3 utils/selfcheck_triage.py /tmp/selfcheck.log /tmp/data.json
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
KINDS = ("FALSE_NEGATIVE", "ERROR", "CONNECT_FAIL", "FALSE_POSITIVE", "OTHER")
|
|
|
|
|
|
def parse_diagnosis_blocks(log_text: str) -> dict:
|
|
"""Extract {site_name: [issue, ...]} from --diagnose output."""
|
|
blocks = {}
|
|
cur = None
|
|
for line in log_text.splitlines():
|
|
m = re.match(r"^--- (.+) DIAGNOSIS ---$", line.strip())
|
|
if m:
|
|
cur = m.group(1)
|
|
blocks[cur] = []
|
|
continue
|
|
if cur is None:
|
|
continue
|
|
s = line.strip()
|
|
if s.startswith("- "):
|
|
blocks[cur].append(s[2:])
|
|
elif s.startswith(("Check type:", "Issues:", "Recommendations:", "->")):
|
|
continue
|
|
elif s and not line.startswith(" "):
|
|
cur = None
|
|
return blocks
|
|
|
|
|
|
def classify(issues: list) -> set:
|
|
kinds = set()
|
|
for i in issues:
|
|
if re.search(r"Claimed user .* not detected as claimed", i):
|
|
kinds.add("FALSE_NEGATIVE")
|
|
elif re.search(r"Unclaimed user .* detected as claimed", i):
|
|
kinds.add("FALSE_POSITIVE")
|
|
elif i.startswith("Error checking"):
|
|
kinds.add("ERROR")
|
|
elif "Cannot connect to host" in i:
|
|
kinds.add("CONNECT_FAIL")
|
|
else:
|
|
kinds.add("OTHER")
|
|
return kinds
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Classify and rank maigret --self-check --diagnose failures."
|
|
)
|
|
parser.add_argument("log", type=Path, help="Captured self-check output")
|
|
parser.add_argument(
|
|
"db",
|
|
type=Path,
|
|
nargs="?",
|
|
default=Path(__file__).parent.parent / "maigret" / "resources" / "data.json",
|
|
help="data.json used for the run (for alexaRank ordering)",
|
|
)
|
|
parser.add_argument(
|
|
"--kind",
|
|
choices=KINDS,
|
|
help="Show only one failure kind",
|
|
)
|
|
parser.add_argument(
|
|
"--limit", type=int, default=40, help="Max sites per kind (default: 40)"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
sites_cfg = json.loads(args.db.read_text())["sites"]
|
|
blocks = parse_diagnosis_blocks(args.log.read_text(errors="replace"))
|
|
|
|
rows = []
|
|
for name, issues in blocks.items():
|
|
cfg = sites_cfg.get(name, {})
|
|
rows.append(
|
|
(
|
|
cfg.get("alexaRank", 9999999),
|
|
name,
|
|
classify(issues),
|
|
issues,
|
|
cfg.get("checkType"),
|
|
)
|
|
)
|
|
rows.sort()
|
|
|
|
counts = {}
|
|
for _, _, kinds, _, _ in rows:
|
|
for k in kinds:
|
|
counts[k] = counts.get(k, 0) + 1
|
|
print(f"Total failing sites: {len(rows)}; kinds: {counts}\n")
|
|
|
|
shown_kinds = (args.kind,) if args.kind else KINDS
|
|
for label in shown_kinds:
|
|
subset = [r for r in rows if label in r[2]]
|
|
print(f"=== {label} ({len(subset)}) ===")
|
|
for rank, name, kinds, issues, ctype in subset[: args.limit]:
|
|
extra = "+".join(sorted(kinds - {label}))
|
|
extra = f" [{extra}]" if extra else ""
|
|
print(f" rank={rank:<8} {name} (checkType={ctype}){extra}")
|
|
for i in issues:
|
|
print(f" * {i[:160]}")
|
|
if len(subset) > args.limit:
|
|
print(f" ... and {len(subset) - args.limit} more")
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|