actions-queue-status: count a PMC's active repos against its estate A PMC row said how many of its repos had jobs, which is only half a fact: four busy repos read very differently for a PMC of four than for a PMC of forty-seven, and the row gave no way to tell those apart. The Repos column now reads active / total, counting the repos the sweep actually covered -- so a --repos-file run measures against that file rather than against an organisation it never looked at. group_by_pmc() takes that population as an argument and falls back to the active repos when it has none, which is all a caller without a wider list can honestly claim. The TOTAL row carries the same ratio, 60 / 1258 for a full org sweep, and the CSV gains a repos_total column beside repos. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LefMKj1fh7D2RxwNC6A4L5
diff --git a/README.md b/README.md index 0048d49..ab0c10f 100644 --- a/README.md +++ b/README.md
@@ -615,7 +615,7 @@ | `--suites N` | Check suites read per commit (default: 5). | | `--workers N` | Batched queries in flight (default: 3). | | `--top N` | Rows shown per table (default: 25). | -| `--by-pmc` | Group rows by PMC — the repository name's prefix before the first hyphen. | +| `--by-pmc` | Group rows by PMC — the repository name's prefix before the first hyphen. The `Repos` column reads active / total. | | `--include-archived` | Include archived repositories. | | `--repos-file PATH` | Skip discovery and read repository names from a file; `#` comment lines are ignored. | | `--save-repos PATH` | Write the discovered repository list to a file, sorted and with a header. | @@ -696,16 +696,23 @@ ```text Sorted by RUNNING jobs -┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ PMC ┃ Queued ┃ Running ┃ Repos ┃ Repositories ┃ -┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ airflow │ 13 │ 133 │ 2 │ airflow, airflow-client-python │ -│ spark │ 1 │ 83 │ 1 │ spark │ -├─────────┼────────┼─────────┼───────┼────────────────────────────────┤ -│ TOTAL │ 14 │ 216 │ 3 │ 2 PMCs │ -└─────────┴────────┴─────────┴───────┴────────────────────────────────┘ +┏━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ PMC ┃ Queued ┃ Running ┃ Repos ┃ Repositories ┃ +┡━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ airflow │ 13 │ 133 │ 2 / 10 │ airflow, airflow-client-python │ +│ fineract │ 0 │ 96 │ 1 / 8 │ fineract │ +│ datafusion │ 21 │ 49 │ 2 / 12 │ datafusion, datafusion-comet │ +├────────────┼────────┼─────────┼───────────┼────────────────────────────────┤ +│ TOTAL │ 330 │ 881 │ 60 / 1258 │ 49 PMCs │ +└────────────┴────────┴─────────┴───────────┴────────────────────────────────┘ ``` +The `Repos` column reads *active / total*: how many of the PMC's repositories have jobs right +now, out of every repository of theirs the sweep covered. Four busy repositories mean something +different for a PMC of four than for a PMC of forty-seven, and the denominator is what says +which one you are looking at. It counts the repositories the sweep actually ran over, so a +`--repos-file` run measures against that file rather than the whole organisation. + A repository's PMC is the text before the first hyphen in its name, and the whole name when there is no hyphen — so `spark`, `spark-connect-go` and `spark-docker` group under `spark`. That is the same rule [`--pmc` uses in `actions-audit.py`](#how-pmc-filtering-works), so the two scripts agree
diff --git a/utils/actions-queue-status.py b/utils/actions-queue-status.py index 4dd1d8a..ce8988c 100644 --- a/utils/actions-queue-status.py +++ b/utils/actions-queue-status.py
@@ -170,6 +170,7 @@ "queued_jobs", "running_jobs", "repos", + "repos_total", "open_prs", "runs_awaiting_approval", "source", @@ -769,8 +770,15 @@ return repo.split("/")[-1].split("-", 1)[0] -def group_by_pmc(rows: list[dict]) -> list[dict]: - """Aggregate per-repo summaries into one row per PMC.""" +def group_by_pmc(rows: list[dict], population: list[str] | None = None) -> list[dict]: + """Aggregate per-repo summaries into one row per PMC. + + `population` is every repo the sweep covered, active or not. It is what makes the + active count mean something: four busy repos is a different picture for a PMC of + four than for a PMC of forty-seven. Without it a PMC's total is just its active + repos, which is what a caller with no wider list can honestly say. + """ + totals: dict[str, int] = collections.Counter(pmc_of(name) for name in population or []) groups: dict[str, dict] = {} for row in rows: name = row["repo"].split("/")[-1] @@ -794,6 +802,10 @@ group["sources"].add(row.get("source", "graphql")) for group in groups.values(): group["repos"].sort() + # A repo with jobs is by definition part of its PMC's estate, so the count can + # never be smaller than what was found active -- even if the population somehow + # did not list it. + group["repos_total"] = max(totals.get(group["pmc"], 0), len(group["repos"])) # A PMC counted partly each way is neither: say so rather than pick a winner. group["source"] = group["sources"].pop() if len(group["sources"]) == 1 else "mixed" del group["sources"] @@ -827,6 +839,7 @@ row["queued_jobs"], row["running_jobs"], len(row["repos"]), + row["repos_total"], row["open_prs"], row["runs_awaiting_approval"], row["source"], @@ -891,7 +904,14 @@ writer.writerow(pmc_csv_row(row) if by_pmc else csv_row(row)) total_row = ["TOTAL", totals["queued_jobs"], totals["running_jobs"]] if by_pmc: - total_row += [totals["repos_active"], "", "", "", f"pmcs={totals['pmcs_active']}"] + total_row += [ + totals["repos_active"], + totals["repos_with_actions"], + "", + "", + "", + f"pmcs={totals['pmcs_active']}", + ] else: total_row += ["", "", "", f"repos={totals['repos_active']}"] writer.writerow(total_row) @@ -923,7 +943,7 @@ row["pmc"], str(row["queued_jobs"]), str(row["running_jobs"]), - str(len(row["repos"])), + f"{len(row['repos'])} / {row['repos_total']}", escape(", ".join(row["repos"])[:50]), ) continue @@ -955,7 +975,7 @@ "[bold]TOTAL[/]", f"[bold]{totals['queued_jobs']}[/]", f"[bold]{totals['running_jobs']}[/]", - f"[bold]{totals['repos_active']}[/]" if by_pmc else "", + f"[bold]{totals['repos_active']} / {totals['repos_with_actions']}[/]" if by_pmc else "", f"[dim]{active} {unit}{'' if active == 1 else 's'}[/]", ) if len(rows) > top: @@ -1119,7 +1139,7 @@ reporter.log(" No REST re-count needed — the GraphQL sample covered every repo", "green") active = [row for row in results if row["queued_jobs"] or row["running_jobs"]] - grouped = group_by_pmc(active) + grouped = group_by_pmc(active, repos) if args.by_pmc: key = "pmc" rows = grouped