Skip to content

Commit 34a6821

Browse files
committed
contest: fetcher: compute test stability
As the first step for HW test ingest we need to auto-judge if given test is stable and passing or not supported by HW. Create a DB of all test cases we have seen and compute pass/fail statistics. We will judge a test as stable / passing if it had 15 clean runs. "undoing" the passing status will be manual for now. Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 19e035e commit 34a6821

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

contest/results-fetcher.py

+93
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,36 @@
2626
combined=name-of-manifest.json
2727
[db]
2828
db=db-name
29+
stability-name=table-name
2930
results-name=table-name
3031
branches-name=table-name
3132
"""
3233

34+
def result_flatten(full):
35+
"""
36+
Take in a full result dict (for one run, with subtests).
37+
Return a list of dicts:
38+
[
39+
{ "group": str, "test": str, "subtest": str/None, "result": bool },
40+
]
41+
"""
42+
flat = []
43+
44+
for test in full["results"]:
45+
l1 = { "group": test["group"],
46+
"test": test["test"],
47+
"subtest": None,
48+
"result": test["result"].lower() == "pass"
49+
}
50+
flat.append(l1)
51+
for case in test.get("results", []):
52+
data = l1.copy()
53+
data["subtest"] = case["test"]
54+
data["result"] = case["result"].lower() == "pass"
55+
flat.append(data)
56+
57+
return flat
58+
3359

3460
class FetcherState:
3561
def __init__(self):
@@ -39,6 +65,7 @@ def __init__(self):
3965
# "fetched" is more of a "need state rebuild"
4066
self.fetched = True
4167

68+
self.tbl_stb = self.config.get("db", "stability-name", fallback="stability")
4269
self.tbl_res = self.config.get("db", "results-name", fallback="results")
4370
self.tbl_brn = self.config.get("db", "branches-name", fallback="branches")
4471

@@ -104,6 +131,70 @@ def psql_json_split(self, data):
104131
full = json.dumps(data)
105132
return json.dumps(normal), full
106133

134+
def psql_stability_selector(self, cur, data, row):
135+
base = cur.mogrify("WHERE remote = %s AND executor = %s AND grp = %s AND test = %s",
136+
(data["remote"], data["executor"], row["group"], row["test"],)).decode('utf-8')
137+
138+
if row["subtest"] is None:
139+
return base + " AND subtest is NULL"
140+
return base + cur.mogrify(" AND subtest = %s", (row["subtest"],)).decode('utf-8')
141+
142+
def psql_get_stability(self, data, row):
143+
with self.psql_conn.cursor() as cur:
144+
cur.execute(f"SELECT pass_cnt, fail_cnt, pass_srk, fail_srk, pass_cur, fail_cur, passing FROM {self.tbl_stb} " +
145+
self.psql_stability_selector(cur, data, row))
146+
rows = cur.fetchall()
147+
if rows and len(rows) > 0:
148+
res = rows[0]
149+
else:
150+
res = [0] * 10
151+
res[6] = None # passing
152+
return {
153+
"pass_cnt": res[0],
154+
"fail_cnt": res[1],
155+
"pass_srk": res[2],
156+
"fail_srk": res[3],
157+
"pass_cur": res[4],
158+
"fail_cur": res[5],
159+
"passing": res[6],
160+
"exists": bool(rows),
161+
}
162+
163+
def psql_insert_stability(self, data):
164+
flat = result_flatten(data)
165+
166+
for row in flat:
167+
# Fetch current state
168+
stability = self.psql_get_stability(data, row)
169+
if not stability["exists"]:
170+
with self.psql_conn.cursor() as cur:
171+
cur.execute(f"INSERT INTO {self.tbl_stb} (remote, executor, grp, test, subtest) " +
172+
cur.mogrify("VALUES (%s, %s, %s, %s, %s)",
173+
(data["remote"], data["executor"], row["group"], row["test"], row["subtest"],)).decode('utf-8'))
174+
# Update state
175+
if row["result"]:
176+
key_pfx = "pass"
177+
stability["fail_cur"] = 0
178+
else:
179+
key_pfx = "fail"
180+
stability["pass_cur"] = 0
181+
182+
stability[key_pfx + "_cnt"] += 1
183+
stability[key_pfx + "_cur"] += 1
184+
stability[key_pfx + "_srk"] = max(stability[key_pfx + "_cur"], stability[key_pfx + "_srk"])
185+
186+
now = datetime.datetime.now().isoformat() + "+00:00"
187+
if stability[key_pfx + "_srk"] > 15 and not stability["passing"]: # 5 clean days for HW
188+
print("Test reached stability", row["remote"], row["test"], row["subtest"])
189+
stability["passing"] = now
190+
191+
with self.psql_conn.cursor() as cur:
192+
cur.execute(f"UPDATE {self.tbl_stb} SET " +
193+
cur.mogrify("pass_cnt = %s, fail_cnt = %s, pass_srk = %s, fail_srk = %s, pass_cur = %s, fail_cur = %s, passing = %s, last_update = %s",
194+
(stability["pass_cnt"], stability["fail_cnt"], stability["pass_srk"], stability["fail_srk"],
195+
stability["pass_cur"], stability["fail_cur"], stability["passing"], now)).decode('utf-8') +
196+
self.psql_stability_selector(cur, data, row))
197+
107198
def insert_real(self, remote, run):
108199
data = run.copy()
109200
data["remote"] = remote["name"]
@@ -119,6 +210,8 @@ def insert_real(self, remote, run):
119210
q = f"UPDATE {self.tbl_res} " + vals + ' ' + selector
120211
cur.execute(q)
121212

213+
self.psql_insert_stability(data)
214+
122215

123216
def write_json_atomic(path, data):
124217
tmp = path + '.new'

deploy/contest/db

+16
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,19 @@ CREATE TABLE db_monitor (
6565
disk_pct REAL,
6666
disk_pct_metal REAL
6767
);
68+
69+
CREATE TABLE stability (
70+
remote varchar(80),
71+
executor varchar(80),
72+
grp varchar(80),
73+
test varchar(128),
74+
subtest varchar(256),
75+
pass_cnt integer NOT NULL DEFAULT 0,
76+
fail_cnt integer NOT NULL DEFAULT 0,
77+
pass_srk integer NOT NULL DEFAULT 0,
78+
fail_srk integer NOT NULL DEFAULT 0,
79+
pass_cur integer NOT NULL DEFAULT 0,
80+
fail_cur integer NOT NULL DEFAULT 0,
81+
last_update timestamp,
82+
passing timestamp
83+
);

0 commit comments

Comments
 (0)