| """Join filtered integration failures with CI bisect attribution and cluster. |
| |
| CI's `new_failures_with_bad_commit_grouped_by_authors.json` shape is: |
| { author_or_null: { model: { "single-gpu" | "multi-gpu": [ {test, bad_commit, |
| status, pr_number, parent, failure_at_*_commit, job_link}, ... ] } } } |
| |
| A status of `"git bisect found the bad commit."` is the only one we trust. |
| Anything starting with `"flaky:"` we tag as flaky and segregate. |
| Anything missing entirely becomes `unpinned`. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from collections import defaultdict |
|
|
|
|
| _GOOD_STATUS = "git bisect found the bad commit." |
|
|
|
|
| def _index_attribution(new_failures: dict) -> dict[tuple[str, str, str], dict]: |
| """Flatten `{author -> {model -> {gpu -> [records]}}}` to |
| `{(model, gpu, test) -> record}`. Adds `author` to each record.""" |
| out: dict[tuple[str, str, str], dict] = {} |
| if not new_failures: |
| return out |
| for author, by_model in (new_failures or {}).items(): |
| if not isinstance(by_model, dict): |
| continue |
| for model, by_gpu in by_model.items(): |
| if not isinstance(by_gpu, dict): |
| continue |
| for gpu_label, items in by_gpu.items(): |
| gpu = gpu_label.replace("-gpu", "") |
| for rec in items or []: |
| test = rec.get("test", "") |
| enriched = {**rec, "author": author if author != "null" else None} |
| out[(model, gpu, test)] = enriched |
| return out |
|
|
|
|
| def cluster_failures( |
| filtered: list[dict], |
| new_failures_latest: dict | None, |
| classifier, |
| ) -> dict: |
| """Produce the triage report data structure. |
| |
| Inputs: |
| filtered ββ output of filter.intersect_across_days |
| new_failures_latest ββ most-recent day's new_failures JSON (may be None) |
| classifier ββ classify.classify |
| |
| Returns: dict with keys |
| `clusters` {bad_commit: {meta..., failures: [...]}}, sorted by size desc |
| `flaky` [failure, ...] (CI marked status="flaky:...") |
| `unpinned` [failure, ...] (no CI attribution found) |
| `totals` {total, clusters, flaky, unpinned} |
| """ |
| attr = _index_attribution(new_failures_latest or {}) |
|
|
| clusters: dict[str, dict] = {} |
| flaky: list[dict] = [] |
| unpinned: list[dict] = [] |
|
|
| for f in filtered: |
| key = (f["model"], f["gpu"], f["test"]) |
| rec = attr.get(key) |
| f = { |
| **f, |
| "failure_mode": classifier(f.get("latest_trace") or f.get("trace") or ""), |
| } |
| if rec is None: |
| unpinned.append(f) |
| continue |
| status = rec.get("status") or "" |
| if status.startswith("flaky"): |
| flaky.append({**f, "status": status, "author": rec.get("author")}) |
| continue |
| if status != _GOOD_STATUS: |
| unpinned.append({**f, "status": status, "author": rec.get("author")}) |
| continue |
| bc = rec.get("bad_commit") |
| if not bc: |
| unpinned.append({**f, "author": rec.get("author")}) |
| continue |
| c = clusters.setdefault( |
| bc, |
| { |
| "bad_commit": bc, |
| "pr_number": rec.get("pr_number"), |
| "author": rec.get("author"), |
| "merged_by": rec.get("merged_by"), |
| "parent": rec.get("parent"), |
| "job_link": rec.get("job_link"), |
| "failure_excerpt": (rec.get("failure_at_bad_commit") or "").strip(), |
| "failures": [], |
| }, |
| ) |
| c["failures"].append(f) |
|
|
| |
| clusters_sorted = dict( |
| sorted(clusters.items(), key=lambda kv: (-len(kv[1]["failures"]), kv[1].get("author") or "")) |
| ) |
|
|
| return { |
| "clusters": clusters_sorted, |
| "flaky": flaky, |
| "unpinned": unpinned, |
| "totals": { |
| "total": len(filtered), |
| "in_clusters": sum(len(c["failures"]) for c in clusters_sorted.values()), |
| "clusters": len(clusters_sorted), |
| "flaky": len(flaky), |
| "unpinned": len(unpinned), |
| }, |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| BIG_MODEL_SKIP: set[str] = { |
| |
| "llama4_maverick", |
| |
| "deepseek_v4_flash_base", |
| |
| "cohere2_moe", |
| |
| "qwen3_next", |
| "qwen3_235b", |
| |
| "deepseek_r1", |
| "dbrx", |
| } |
|
|
|
|
| def is_big_model(failure: dict) -> bool: |
| return failure["model"] in BIG_MODEL_SKIP |
|
|
|
|
| if __name__ == "__main__": |
| import sys |
|
|
| from fetch import fetch_last_n |
| from filter import per_day_integration_failures, intersect_across_days |
| from classify import classify |
|
|
| cache = sys.argv[1] if len(sys.argv) > 1 else None |
| daily = fetch_last_n(7, cache_dir=cache) |
| per_day = per_day_integration_failures(daily) |
| kept = intersect_across_days(per_day, min_days=5) |
| |
| latest_date = max(daily.keys()) |
| nf_latest = daily[latest_date].get("new_failures") |
| report = cluster_failures(kept, nf_latest, classify) |
|
|
| t = report["totals"] |
| print( |
| f"total={t['total']} clusters={t['clusters']} in_clusters={t['in_clusters']} " |
| f"flaky={t['flaky']} unpinned={t['unpinned']}" |
| ) |
| print("\nTop 8 clusters by size:") |
| for bc, c in list(report["clusters"].items())[:8]: |
| models = ", ".join(sorted({f["model"] for f in c["failures"]})[:5]) |
| more = "" if len({f["model"] for f in c["failures"]}) <= 5 else " β¦" |
| print( |
| f" {bc[:10]} PR #{c['pr_number'] or 'β':<5} " |
| f"{c['author'] or '?':<22} {len(c['failures']):>3} failures " |
| f"[{models}{more}]" |
| ) |
| print(f"\nUnpinned: {len(report['unpinned'])}") |
| if report["unpinned"]: |
| from collections import Counter |
| modes = Counter(f["failure_mode"] for f in report["unpinned"]) |
| for m, n in modes.most_common(): |
| print(f" {m}: {n}") |
|
|