"""
run_scenario_campaign.py
长尾接口多场景压测 campaign(依次逐个:每接口按 sample→normal→non_contig→multithread→error→consistent
顺序执行已就绪场景,门禁与 AGENTS.md Step 3 一致)。
每场景流水线:目录切换(op→op_back) → 拷贝CPP → make → 预检/调试门禁 → 全量 →
plog 窗口差量归档 → 统计+错误码分类三态判定 → update_batch_results.py 落表 → 状态文件
用法:
python run_scenario_campaign.py --worker 0 --device 0 --fw turing_sec --apis /tmp/scn_apis_w0.txt
"""
import argparse
import glob
import json
import os
import re
import shutil
import subprocess
import sys
import time
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
PLOG_DIR = "/root/ascend/log/debug/plog"
UPDATER = os.path.join(ROOT, "scripts", "update_batch_results.py")
ENV_BASE = {
"ASCEND_HOME_PATH": "/usr/local/Ascend/ascend-toolkit/latest/x86_64-linux",
"ASCEND_OPP_PATH": "/usr/local/Ascend/ascend-toolkit/latest/opp",
"PATH": os.environ.get("PATH", ""),
}
SCN_ORDER = ["sample", "normal", "non_contig", "multithread", "error", "consistent"]
def logw(msg):
print("[{}] {}".format(time.strftime("%H:%M:%S"), msg), flush=True)
def load_state(p):
return json.load(open(p)) if os.path.isfile(p) else {}
def save_state(p, st):
tmp = p + ".tmp"
json.dump(st, open(tmp, "w"), ensure_ascii=False, indent=1)
os.replace(tmp, p)
def run_cmd(fw, args, timeout, out_log, device, remap_visible=True):
env = dict(ENV_BASE)
env["LD_LIBRARY_PATH"] = ENV_BASE["ASCEND_HOME_PATH"] + "/lib64:" + os.environ.get("LD_LIBRARY_PATH", "")
if remap_visible:
env["ASCEND_RT_VISIBLE_DEVICES"] = str(device)
before = set(glob.glob(os.path.join(PLOG_DIR, "plog-*.log")))
t0 = time.time()
with open(out_log, "w") as f:
try:
rc = subprocess.call(args, stdout=f, stderr=subprocess.STDOUT, env=env,
cwd=os.path.join(fw, "out"), timeout=timeout)
except subprocess.TimeoutExpired:
rc = -9
return rc, before, t0
def capture_plog(before, t0, dest):
with open(dest, "w") as out:
for p in glob.glob(os.path.join(PLOG_DIR, "plog-*.log")):
if p in before:
continue
try:
if os.path.getmtime(p) < t0:
continue
with open(p, errors="ignore") as f:
out.write(f.read())
except OSError:
pass
def stat_log(path, api):
"""统计执行结果 + 错误码分布 + aicore 初扫"""
c = open(path, errors="ignore").read()
ok = len(re.findall(r"\[FUZZ_RESULT\] case_\d+: success", c))
fail = len(re.findall(r"\[FUZZ_RESULT\] case_\d+: faild", c))
skip = len(re.findall(r"\[FUZZ_RESULT\] case_\d+: skip", c))
codes = {}
for m in re.finditer(r"execute failed :(-?\d+)", c):
codes[m.group(1)] = codes.get(m.group(1), 0) + 1
verify = re.findall(r"\[FUZZ_VERIFY\] case_\d+: .*match=(YES|NO)", c)
scen = re.findall(r"== Scenario\s+\d+ \| ret=(-?\d+)\s*\| (\w+)\s*\| EZ:(PASS|FAIL)", c)
base_ok = sum(1 for _, v, _ in scen if v == "BASE_CHECK_OK")
unexp = sum(1 for _, v, _ in scen if v == "UNEXPECTED_SUCCESS")
err_scen = [s for s in scen if s[1] not in ("BASE_CHECK_OK",)]
ez_pass = sum(1 for _, _, z in err_scen if z == "PASS")
ez_fail = sum(1 for _, _, z in err_scen if z == "FAIL")
return {"ok": ok, "fail": fail, "skip": skip,
"scen_total": len(scen), "base_ok": base_ok, "ez_pass": ez_pass, "ez_fail": ez_fail, "unexp_success": unexp,
"err_codes": sorted(codes.items(), key=lambda kv: -kv[1])[:4],
"verify_total": len(verify), "verify_no": sum(1 for v in verify if v == "NO")}
def scan_aicore(plog_path):
if not plog_path or not os.path.isfile(plog_path):
return False
c = open(plog_path, errors="ignore").read().lower()
for m in ("the error is aivec error", "aivec error", "aicore error", "aicore_error"):
if m in c:
return True
for line in c.splitlines():
if "kernel_name" in line and ("[error]" in line or " error " in line):
return True
return False
def classify(full, scenario):
"""三态判定(基础版:错误码分类;详细根因由后续分析)"""
ok, fail = full.get("ok", 0), full.get("fail", 0)
codes = full.get("err_codes", [])
total = ok + fail
if scenario == "error":
if full.get("scen_total", 0) > 0:
if full.get("unexp_success", 0) > 0:
return "needs_review", "是bug候选(意外成功待复核)"
if full.get("ez_fail", 0) == 0 and full.get("ez_pass", 0) > 0:
return "done", "不是bug(全部用例按资料预期报错,维测信息完备)"
if full.get("ez_fail", 0) > 0:
return "needs_review", "需要人工确认(错误码不符或维测信息缺失待复核)"
return "needs_review", "需要人工确认(error场景无可解析错误结果)"
if total > 0:
return "needs_review", "需要人工确认(error场景无可解析输出)"
return "crash", "crash"
if total == 0:
return "crash", "crash"
rate = ok * 100.0 / total
if rate >= 95:
return "done", "不是bug(全部用例执行通过)" if fail == 0 else "不是bug(执行通过率达标)"
top = codes[0][0] if codes else ""
if top in ("161001", "161002"):
return "done", "不是bug(用例违反接口约束按资料预期报错)"
if top in ("561103", "561112"):
return "needs_review", "不是bug(环境问题)"
return "needs_review", "需要人工确认(成功率未达标)"
def upd_xlsx(api, scenario, total, ok, fail, reason, bug, aicore, card):
subprocess.run(["python3", UPDATER, "--api", api, "--scenario", scenario,
"--total", str(total), "--success", str(ok), "--fail", str(fail),
"--fail_reason", reason[:250], "--has_bug", bug[:60],
"--has_aicore", "是" if aicore else "否", "--card", str(card),
"--analyzed", "是" if bug.startswith("不是bug") else "否"],
capture_output=True, text=True)
def swap_dir(fw, sub, fname, rename=None):
"""清空目标场景目录并放入单个 CPP(op/op_multithread 机制);
multithread 场景文件必须重命名为 {api}.cpp(执行器按 目录stem+_fuzz 取符号)"""
d = os.path.join(fw, "src", sub)
if os.path.exists(d):
shutil.rmtree(d)
os.makedirs(d)
if fname:
dst = os.path.join(d, rename) if rename else os.path.join(d, os.path.basename(fname))
shutil.copy(fname, dst)
def make_fw(fw, target):
env = dict(ENV_BASE)
env["LD_LIBRARY_PATH"] = ENV_BASE["ASCEND_HOME_PATH"] + "/lib64:" + os.environ.get("LD_LIBRARY_PATH", "")
r = subprocess.run(["make", "op_fuzz"] + (["TARGET=" + target] if target else []),
cwd=fw, env=env, capture_output=True, text=True, timeout=1200)
return r.returncode == 0, r.stderr[-800:]
def run_scenario(fw, api, scn, device, state):
d = os.path.join(ROOT, "output", api, "fuzz_logs")
os.makedirs(d, exist_ok=True)
cpp = os.path.join(ROOT, "output", api, f"{api}_{'non_contig' if scn == 'non_contig' else scn}.cpp") \
if scn in ("normal", "non_contig", "error") else None
if scn == "normal":
cpp = os.path.join(ROOT, "output", api, f"{api}.cpp")
if scn == "multithread":
cpp = os.path.join(ROOT, "output", api, f"{api}_multithread.cpp")
if scn == "consistent":
cpp = os.path.join(ROOT, "output", api, f"{api}_consistent.cpp")
if scn == "sample":
okc, err = make_fw(fw, None)
return None
if scn in ("normal", "non_contig", "error"):
swap_dir(fw, "op", cpp)
okc, err = make_fw(fw, None)
exe_args = lambda n: [os.path.join(fw, "out", "aclnnAPI_fuzz"),
f"{api}{'_non_contig' if scn == 'non_contig' else ''}{'_error' if scn == 'error' else ''}_fuzz"] + ([str(n)] if scn != "error" else [])
elif scn == "multithread":
swap_dir(fw, "op_multithread", cpp, rename=f"{api}.cpp")
okc, err = make_fw(fw, "multithread")
exe_args = lambda n: [os.path.join(fw, "out", "aclnnAPI_fuzz_multithread"), "5", str(n), str(device), "5", "0"]
else:
swap_dir(fw, "src_consistent", cpp)
okc, err = make_fw(fw, "consistent")
exe_args = lambda n: [os.path.join(fw, "out", "aclnnAPI_fuzz_consistent"), f"{api}_consistent_fuzz", str(n)]
if not okc:
state.setdefault(api, {})[scn] = {"status": "compile_fail", "error": err}
logw(f" [{scn}] COMPILE_FAIL")
return "compile_fail"
runs = []
if scn in ("normal", "multithread"):
pre_name = f"{api}_fuzz_precheck.log" if scn == "normal" else f"{api}_{scn}_precheck.log"
full_name = f"{api}_fuzz_output.log" if scn == "normal" else f"{api}_{scn}_fuzz_output.log"
runs = [(1000, pre_name, 2400), (10000 if scn == "normal" else 20000, full_name, 3600)]
elif scn == "consistent":
runs = [(100, f"{api}_consistent_precheck.log", 600), (1000, f"{api}_consistent_fuzz_output.log", 1800)]
elif scn == "non_contig":
runs = [(10000, f"{api}_non_contig_fuzz_output.log", 2400)]
else:
runs = [(0, f"{api}_error_fuzz_output.log", 1200)]
result = {}
gated = False
for num, logname, tmo in runs:
logpath = os.path.join(d, logname)
args = exe_args(num) if (scn != "error" and num) else (
[os.path.join(fw, "out", "aclnnAPI_fuzz"), f"{api}_error_fuzz"] if scn == "error" else exe_args(num))
if scn == "error":
args = [os.path.join(fw, "out", "aclnnAPI_fuzz"), f"{api}_error_fuzz", "1000"]
rc, before, t0 = run_cmd(fw, args, tmo, logpath, device, remap_visible=(scn != "multithread"))
plog_name = f"{api}_fuzz_plog.log" if scn == "normal" else f"{api}_{scn}_fuzz_plog.log"
capture_plog(before, t0, os.path.join(d, plog_name) if not logname.endswith("precheck.log") else "/dev/null")
st = stat_log(logpath, api)
result["precheck" if "precheck" in logname else "full"] = {**st, "rc": rc}
if "precheck" in logname or "调试" in logname:
total = st["ok"] + st["fail"]
if rc == -9 or total == 0 or (scn != "consistent" and st["ok"] * 100.0 / max(total, 1) < 95):
if scn == "consistent" and rc == 0 and total > 0:
pass
else:
gated = True
break
if gated:
pre = result.get("precheck", {})
if pre.get("ok", 0) == 0 and pre.get("fail", 0) == 0 and pre.get("skip", 0) > 0:
reason = f"全部用例被约束拒绝(skip={pre.get('skip')}): CPP输入构造需修复, err={pre.get('err_codes')}"
bug = "需要人工确认(CPP输入构造需修复)"
else:
reason = f"预检未达标: {pre.get('ok', 0)}/{pre.get('ok', 0) + pre.get('fail', 0) + pre.get('skip', 0)} err={pre.get('err_codes')}"
bug = "需要人工确认(预检门禁未过)"
status = "needs_review"
upd_xlsx(api, scn, 1000, pre.get("ok", 0), pre.get("fail", 0) + pre.get("skip", 0), reason, bug, False, device)
state.setdefault(api, {})[scn] = {"status": "gate_fail", **pre}
logw(f" [{scn}] GATE_FAIL {reason[:80]}")
return "gate_fail"
full = result.get("full") or result.get("precheck")
if scn != "consistent" and full.get("ok", 0) == 0 and full.get("fail", 0) == 0 and full.get("skip", 0) > 0:
num_allskip = 10000 if scn == "non_contig" else (20000 if scn == "multithread" else 1000)
aicore_allskip = scan_aicore(os.path.join(d, f"{api}_{'fuzz' if scn=='normal' else scn}_fuzz_plog.log"))
reason = f"全部用例被约束拒绝(skip={full['skip']}), err={full['err_codes']}"
upd_xlsx(api, scn, num_allskip, 0, full["skip"], reason, "需要人工确认(CPP输入构造需修复)", aicore_allskip, device)
state.setdefault(api, {})[scn] = {"status": "needs_review", **full}
logw(f" [{scn}] ALL_SKIP skip={full['skip']}")
return "needs_review"
num = 10000 if scn == "non_contig" else (20000 if scn == "multithread" else (0 if scn == "error" else 1000))
total = full["ok"] + full["fail"] if full["ok"] + full["fail"] > 0 else (num or full["verify_total"])
aicore = scan_aicore(os.path.join(d, f"{api}_{scn}_fuzz_plog.log"))
produced = full.get("ok", 0) + full.get("fail", 0) + full.get("skip", 0)
planned = num if num else produced
if scn != "consistent" and produced > 0 and produced * 100 < planned * 50:
reason = f"用例产出率过低: {produced}/{planned} (ok={full['ok']} fail={full['fail']} skip={full.get('skip',0)}), CPP生成有效用例不足"
upd_xlsx(api, scn, planned, full["ok"], full["fail"] + full.get("skip", 0), reason,
"需要人工确认(用例产出率过低)", aicore, device)
state.setdefault(api, {})[scn] = {"status": "needs_review", **full}
logw(f" [{scn}] LOW_COVERAGE {produced}/{planned}")
return "needs_review"
if scn == "consistent":
c = open(os.path.join(d, f"{api}_consistent_fuzz_output.log"), errors="ignore").read() if os.path.isfile(
os.path.join(d, f"{api}_consistent_fuzz_output.log")) else ""
mm = c.count("type=hash_mismatch") + c.count("hash_consistent=mismatch")
al = c.count("hash_consistent=all")
status = "done" if mm == 0 else "needs_review"
bug = "不是bug(hash一致)" if mm == 0 else "需要人工确认(mismatch待甄别)"
reason = f"{al}条hash一致; mismatch={mm}; 无aicore" if not aicore else f"{al}条hash一致; mismatch={mm}; 有aicore"
upd_xlsx(api, scn, 1000, al, mm, reason, bug, aicore, device)
else:
status, bug = classify(full, scn)
if scn == "error":
reason = (f"scenarios={full.get('scen_total',0)} base_ok={full.get('base_ok',0)} "
f"ez_pass={full.get('ez_pass',0)} ez_fail={full.get('ez_fail',0)} "
f"意外成功={full.get('unexp_success',0)}")
else:
reason = f"ok={full['ok']} fail={full['fail']}" + (f" err={full['err_codes']}" if full["err_codes"] else "")
reason += "; 有aicore" if aicore else "; 无aicore"
if scn == "error" and full.get("scen_total", 0) > 0:
upd_xlsx(api, scn, full["scen_total"] - full.get("base_ok", 0), full["ez_pass"],
full["ez_fail"] + full.get("unexp_success", 0), reason, bug, aicore, device)
else:
upd_xlsx(api, scn, num or full["verify_total"], full["ok"], full["fail"], reason, bug, aicore, device)
state.setdefault(api, {})[scn] = {"status": status, **full}
logw(f" [{scn}] {status} ok={full['ok']} fail={full['fail']} aicore={aicore}")
return status
def run_sample(fw, api, device, state):
d = os.path.join(ROOT, "output", api, "fuzz_logs")
os.makedirs(d, exist_ok=True)
scpp = os.path.join(fw, "src", "op_sample", f"{api}.cpp")
if not os.path.isfile(scpp):
state.setdefault(api, {})["sample"] = {"status": "no_sample"}
return "no_sample"
logp = os.path.join(d, f"{api}_sample_output.log")
rc, before, t0 = run_cmd(fw, [os.path.join(fw, "out", "aclnnAPI_sample_fuzz"), api], 600, logp, device)
capture_plog(before, t0, os.path.join(d, f"{api}_sample_plog.log"))
st = "done" if rc == 0 else "needs_review"
bug = "不是bug(样例通过)" if rc == 0 else "是bug(官方样例失败)" if rc and rc > 0 else "需要人工确认(样例超时)"
upd_xlsx(api, "sample", 1, 1 if rc == 0 else 0, 0 if rc == 0 else 1,
f"样例执行 rc={rc}", bug, scan_aicore(os.path.join(d, f"{api}_sample_plog.log")), device)
state.setdefault(api, {})["sample"] = {"status": st, "rc": rc}
logw(f" [sample] rc={rc}")
return st
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--worker", required=True)
ap.add_argument("--device", required=True)
ap.add_argument("--fw", required=True)
ap.add_argument("--apis", required=True)
args = ap.parse_args()
fw = os.path.abspath(args.fw)
fw = fw if fw.endswith("aclnnAPI_fuzz") else os.path.join(fw, "aclnnAPI_fuzz")
apis = [l.strip() for l in open(args.apis) if l.strip()]
state_path = os.path.join(ROOT, "out_res", f"scenario_campaign_worker{args.worker}.json")
state = load_state(state_path)
logw(f"worker{args.worker} device{args.device} 共{len(apis)} 接口")
for api in apis:
logw(f"worker{args.worker} >>> {api}")
try:
run_sample(fw, api, args.device, state)
except Exception as e:
state.setdefault(api, {})["sample"] = {"status": "error", "e": str(e)[:200]}
save_state(state_path, state)
for scn in SCN_ORDER:
if scn == "sample":
continue
done_logs = {"normal": f"{api}_fuzz_output.log", "non_contig": f"{api}_non_contig_fuzz_output.log",
"multithread": f"{api}_multithread_fuzz_output.log", "error": f"{api}_error_fuzz_output.log",
"consistent": f"{api}_consistent_fuzz_output.log"}
dlog = os.path.join(ROOT, "output", api, "fuzz_logs", done_logs[scn])
if os.path.isfile(dlog) and "FUZZ_RESULT" in open(dlog, errors="ignore").read(20 * 1024 * 1024):
continue
cppmap = {"normal": f"{api}.cpp", "non_contig": f"{api}_non_contig.cpp",
"multithread": f"{api}_multithread.cpp", "error": f"{api}_error.cpp",
"consistent": f"{api}_consistent.cpp"}
if not os.path.isfile(os.path.join(ROOT, "output", api, cppmap[scn])):
continue
try:
run_scenario(fw, api, scn, args.device, state)
except Exception as e:
state.setdefault(api, {})[scn] = {"status": "error", "e": str(e)[:200]}
logw(f" [{scn}] ERROR {e}")
save_state(state_path, state)
logw(f"worker{args.worker} ALL DONE")
if __name__ == "__main__":
sys.exit(main())