tarekziade HF Staff commited on
Commit
66de927
·
1 Parent(s): 297b07b
Files changed (2) hide show
  1. app.py +151 -0
  2. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Transformers CI — most common test failures.
2
+
3
+ A tiny Gradio dashboard over the public `transformers-ci-telemetry` bucket
4
+ (daily-partitioned Parquet produced by the CI telemetry publisher). It ranks
5
+ the tests and exception types that fail most often, with a few headline stats.
6
+
7
+ Data location: set ``TELEMETRY_DIR`` to the bucket mount. We otherwise probe a
8
+ short list of common paths (the Space's bucket mount, the local checkout) and
9
+ use the first one that actually contains a ``daily/`` tree.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import glob
15
+ import os
16
+
17
+ import gradio as gr
18
+ import pandas as pd
19
+
20
+ # Candidate locations for the bucket contents, in priority order. The Space
21
+ # mounts the bucket at a configured path; locally it's the synced checkout.
22
+ _CANDIDATE_DIRS = [
23
+ os.environ.get("TELEMETRY_DIR", ""),
24
+ "/data/transformers-ci-telemetry",
25
+ "/data",
26
+ "/bucket",
27
+ os.path.join(os.path.dirname(__file__), "data"),
28
+ "/Users/tarek/Dev/transformers-ci-telemetry",
29
+ ]
30
+
31
+
32
+ def _telemetry_dir() -> str | None:
33
+ """First candidate dir that contains a non-empty ``daily/`` tree."""
34
+ for candidate in _CANDIDATE_DIRS:
35
+ if candidate and glob.glob(os.path.join(candidate, "daily", "*", "test_rows.parquet")):
36
+ return candidate
37
+ return None
38
+
39
+
40
+ def load_test_rows() -> pd.DataFrame:
41
+ """Concatenate every ``daily/*/test_rows.parquet`` into one frame."""
42
+ base = _telemetry_dir()
43
+ if base is None:
44
+ return pd.DataFrame()
45
+ files = sorted(glob.glob(os.path.join(base, "daily", "*", "test_rows.parquet")))
46
+ frames = []
47
+ for path in files:
48
+ try:
49
+ frames.append(pd.read_parquet(path))
50
+ except Exception: # noqa: BLE001 - skip a corrupt/partial partition
51
+ continue
52
+ if not frames:
53
+ return pd.DataFrame()
54
+ return pd.concat(frames, ignore_index=True)
55
+
56
+
57
+ def _summary_md(df: pd.DataFrame) -> str:
58
+ if df.empty:
59
+ return (
60
+ "### No data found\n\n"
61
+ "No `daily/*/test_rows.parquet` under any known bucket path. "
62
+ "Set `TELEMETRY_DIR` to the mounted bucket."
63
+ )
64
+ total = len(df)
65
+ failures = int((df["status_code"] == "ERROR").sum())
66
+ rate = (failures / total * 100) if total else 0.0
67
+ runs = df["run_id"].nunique()
68
+ days = df["date"].nunique()
69
+ return (
70
+ f"**{total}** test executions across **{runs}** run(s) / **{days}** day(s) · "
71
+ f"**{failures}** failures · **{rate:.1f}%** failure rate"
72
+ )
73
+
74
+
75
+ def _top_failing_tests(df: pd.DataFrame, limit: int = 20) -> pd.DataFrame:
76
+ if df.empty:
77
+ return pd.DataFrame(columns=["test_nodeid", "failures"])
78
+ errors = df[df["status_code"] == "ERROR"]
79
+ if errors.empty:
80
+ return pd.DataFrame(columns=["test_nodeid", "failures"])
81
+ out = (
82
+ errors.groupby("test_nodeid")
83
+ .size()
84
+ .reset_index(name="failures")
85
+ .sort_values("failures", ascending=False)
86
+ .head(limit)
87
+ .reset_index(drop=True)
88
+ )
89
+ return out
90
+
91
+
92
+ def _failures_by(df: pd.DataFrame, column: str, label: str) -> pd.DataFrame:
93
+ cols = [label, "failures"]
94
+ if df.empty:
95
+ return pd.DataFrame(columns=cols)
96
+ errors = df[df["status_code"] == "ERROR"].copy()
97
+ if errors.empty:
98
+ return pd.DataFrame(columns=cols)
99
+ errors[column] = errors[column].fillna("").replace("", "(none)")
100
+ out = (
101
+ errors.groupby(column)
102
+ .size()
103
+ .reset_index(name="failures")
104
+ .sort_values("failures", ascending=False)
105
+ .reset_index(drop=True)
106
+ )
107
+ return out.rename(columns={column: label})
108
+
109
+
110
+ def refresh():
111
+ df = load_test_rows()
112
+ top_tests = _top_failing_tests(df)
113
+ by_type = _failures_by(df, "exception_type", "exception_type")
114
+ by_model = _failures_by(df, "model", "model")
115
+ # BarPlot wants a tidy frame; reuse the top-tests table (trim the nodeid for
116
+ # readability on the axis).
117
+ plot_df = top_tests.head(10).copy()
118
+ if not plot_df.empty:
119
+ plot_df["test"] = plot_df["test_nodeid"].str.split("::").str[-1]
120
+ else:
121
+ plot_df = pd.DataFrame({"test": [], "failures": []})
122
+ return _summary_md(df), plot_df, top_tests, by_type, by_model
123
+
124
+
125
+ with gr.Blocks(title="Transformers CI — common failures") as demo:
126
+ gr.Markdown("# ⚡ Transformers CI — most common test failures")
127
+ gr.Markdown(
128
+ "Built on the public "
129
+ "[`transformers-ci-telemetry`](https://huggingface.co/datasets/huggingface/transformers-ci-telemetry) "
130
+ "bucket — CI test telemetry, refreshed hourly."
131
+ )
132
+ summary = gr.Markdown()
133
+ refresh_btn = gr.Button("↻ Refresh", variant="secondary")
134
+
135
+ gr.Markdown("## Top failing tests")
136
+ fail_plot = gr.BarPlot(
137
+ x="test", y="failures", title="Failures by test (top 10)", height=320
138
+ )
139
+ top_tests_tbl = gr.Dataframe(label="Top failing tests", interactive=False)
140
+
141
+ with gr.Row():
142
+ by_type_tbl = gr.Dataframe(label="Failures by exception type", interactive=False)
143
+ by_model_tbl = gr.Dataframe(label="Failures by model", interactive=False)
144
+
145
+ outputs = [summary, fail_plot, top_tests_tbl, by_type_tbl, by_model_tbl]
146
+ refresh_btn.click(refresh, outputs=outputs)
147
+ demo.load(refresh, outputs=outputs)
148
+
149
+
150
+ if __name__ == "__main__":
151
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ pandas
2
+ pyarrow