"""
run_consistent_campaign.py
consistent 场景批量压测 campaign 驱动(继 Foreach 修复后的全量扫测)。
流程(每接口):
批编译(output CPP → {fw}/src/src_consistent → make TARGET=consistent)
→ 预检 100 条(hash_mismatch 门禁)
→ 全量 1000 条
→ plog mtime 窗口差量回传
→ parse_consistent_log.py 生成 analysis/report
→ bug 三态判定(含 issue #20 复盘的输入一致性交叉验证:mismatch 用例中输入 tensor
两轮 hash 是否一致——输入变=框架/CPP 问题不提单;输入同输出异=非确定性候选提单)
→ update_batch_results.py 实时写 xlsx
→ 状态写 out_res/consistent_campaign_worker{N}.json
用法:
python run_consistent_campaign.py --worker 0 --device 0 --fw turing_sec \
--apis /tmp/campaign_apis_w0.txt --batch-size 40
"""
import argparse
import glob
import json
import os
import re
import shutil
import subprocess
import sys
import time
import yaml
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
PARSER = os.path.join(ROOT, "skills/gen-consistent-from-normal/scripts/parse_consistent_log.py")
UPDATER = os.path.join(ROOT, "scripts/update_batch_results.py")
PLOG_DIR = "/root/ascend/log/debug/plog"
PRECHECK_N = 100
FULL_N = 1000
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", ""),
}
def logw(msg):
print("[{}] {}".format(time.strftime("%H:%M:%S"), msg), flush=True)
def load_state(path):
if os.path.isfile(path):
with open(path) as f:
return json.load(f)
return {}
def save_state(path, st):
tmp = path + ".tmp"
with open(tmp, "w") as f:
json.dump(st, f, ensure_ascii=False, indent=1)
os.replace(tmp, path)
def input_tensors(api):
"""从 03_参数定义.yaml 提取 input tensor 名(用于输入一致性交叉验证)"""
p = os.path.join(ROOT, "output", api, "test-design", "03_参数定义.yaml")
try:
with open(p) as f:
d = yaml.safe_load(f)
names = []
for prm in d.get("parameters", []):
if prm.get("type") == "aclTensor" and str(prm.get("io_type", "")).lower() == "input":
names.append(prm.get("name"))
return set(names)
except Exception:
return set()
RE_DETAIL = re.compile(r"\[FUZZ_CONSISTENT_DETAIL\] case_(\d+): (\w+) hash:(.*)")
RE_RUNHASH = re.compile(r"run(\d+)=(\d+)")
def cross_check_inputs(log_path, in_tensors):
"""mismatch 用例输入一致性交叉验证(issue #20 开发方法论)。
返回 (input_changed_cases, output_only_cases)"""
input_changed, output_only = [], []
if not in_tensors:
return input_changed, output_only
details = {}
with open(log_path, errors="ignore") as f:
for line in f:
m = RE_DETAIL.search(line)
if m:
cid = int(m.group(1))
t = m.group(2)
hashes = dict((int(a), b) for a, b in RE_RUNHASH.findall(m.group(3)))
details.setdefault(cid, {})[t] = hashes
for cid, tensors in details.items():
chg = False
for t, hashes in tensors.items():
if t in in_tensors and len(hashes) >= 2 and hashes[0] != hashes.get(1):
chg = True
(input_changed if chg else output_only).append(cid)
return sorted(input_changed), sorted(output_only)
def capture_plog(before_list, t0, dest):
"""mtime 窗口差量回传 plog(防共享目录陈旧内容污染)"""
with open(dest, "w") as out:
for p in glob.glob(os.path.join(PLOG_DIR, "plog-*.log")):
if p in before_list:
continue
try:
if os.path.getmtime(p) < t0:
continue
with open(p, errors="ignore") as f:
out.write(f.read())
except OSError:
pass
def run_executor(fw, api, num, device, timeout, out_log):
env = dict(ENV_BASE)
env["LD_LIBRARY_PATH"] = ENV_BASE["ASCEND_HOME_PATH"] + "/lib64:" + os.environ.get("LD_LIBRARY_PATH", "")
env["ASCEND_RT_VISIBLE_DEVICES"] = str(device)
exe = os.path.join(fw, "out", "aclnnAPI_fuzz_consistent")
cmd = [exe, f"{api}_consistent_fuzz", str(num)]
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(cmd, 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 stat_log(path):
c = open(path, errors="ignore").read()
codes = {}
for m in re.finditer(r"execute failed :(-?\d+)", c):
codes[m.group(1)] = codes.get(m.group(1), 0) + 1
return {
"all": c.count("hash_consistent=all"),
"mismatch": c.count("type=hash_mismatch") + c.count("hash_consistent=mismatch"),
"exec_err": c.count("type=execute_error"),
"cases": len(re.findall(r"\[FUZZ_CONSISTENT\] case_\d+:", c)),
"err_codes": sorted(codes.items(), key=lambda kv: -kv[1])[:5],
}
def classify_exec_err(err_codes):
"""execute_error 错误码归类:161001/161002=约束拒绝(合法);561103/561112=kernel加载/环境"""
total = sum(n for _, n in err_codes)
if not total:
return None, 0
top_code, top_n = err_codes[0]
if top_code in ("161001", "161002"):
return "约束拒绝", top_n / total
if top_code in ("561103", "561112"):
return "环境问题", top_n / total
return "其他", top_n / total
def compile_batch(fw, apis, state):
"""清空 src_consistent → 拷贝批次 CPP → make;失败剔除坏文件重试(最多3轮)"""
srcdir = os.path.join(fw, "src", "src_consistent")
os.makedirs(srcdir, exist_ok=True)
for f in glob.glob(os.path.join(srcdir, "*.cpp")):
os.remove(f)
pending = list(apis)
for _ in range(3):
for api in pending:
shutil.copy(os.path.join(ROOT, "output", api, f"{api}_consistent.cpp"), srcdir)
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=consistent"], cwd=fw,
env=env, capture_output=True, text=True, timeout=900)
if r.returncode == 0:
return pending
bad = set()
for stem in re.findall(r"src_consistent/(\w+)\.cpp", r.stderr):
bad.add(stem[:-len("_consistent")] if stem.endswith("_consistent") else stem)
if not bad:
print(r.stderr[-2000:])
raise RuntimeError("make 失败且无法定位坏文件")
for api in bad:
if api in pending:
state[api] = {"status": "compile_fail", "error": r.stderr[-500:]}
logw(f" [COMPILE_FAIL] {api} 剔除")
else:
logw(f" [COMPILE_FAIL] 未知名 {api}(跳过)")
pending = [a for a in pending if a not in bad]
if not pending:
return []
for api in pending:
state[api] = {"status": "compile_blocked", "error": "批编译3轮失败"}
logw(f" [COMPILE_BLOCKED] {len(pending)} 个接口标记编译阻断: {pending[:5]}...")
return []
def process_api(fw, api, device, state):
d = os.path.join(ROOT, "output", api, "fuzz_logs")
os.makedirs(d, exist_ok=True)
pre_log = os.path.join(d, f"{api}_consistent_precheck.log")
full_log = os.path.join(d, f"{api}_consistent_fuzz_output.log")
rc, before, t0 = run_executor(fw, api, PRECHECK_N, device, 600, pre_log)
if rc != 0 and rc != -9 and not os.path.getsize(pre_log):
state[api] = {"status": "crash", "rc": rc}
return "crash"
ps = stat_log(pre_log)
logw(f" [PRE] {api} rc={rc} all={ps['all']} mm={ps['mismatch']} ex={ps['exec_err']} cases={ps['cases']}")
if rc == -9:
subprocess.run(["python3", UPDATER, "--api", api, "--scenario", "consistent",
"--total", str(PRECHECK_N), "--success", str(ps["all"]),
"--fail", "0",
"--fail_reason", f"预检100条执行超时(>10min),已完成{ps['all']}条hash一致,mismatch=0; 疑shape过大或极慢",
"--has_bug", "不是bug(超时未完成)", "--has_aicore", "否",
"--card", str(device), "--analyzed", "否"], capture_output=True, text=True)
state[api] = {"status": "timeout", "stage": "precheck", **ps}
return "timeout"
if ps["cases"] == 0:
state[api] = {"status": "crash", "stage": "precheck", **ps}
return "crash"
rc2, before2, t02 = run_executor(fw, api, FULL_N, device, 3600, full_log)
fs = stat_log(full_log)
capture_plog(before2, t02, os.path.join(d, f"{api}_consistent_fuzz_plog.log"))
logw(f" [FULL] {api} rc={rc2} all={fs['all']} mm={fs['mismatch']} ex={fs['exec_err']}")
if rc2 == -9:
capture_plog(before2, t02, os.path.join(d, f"{api}_consistent_fuzz_plog.log"))
subprocess.run(["python3", UPDATER, "--api", api, "--scenario", "consistent",
"--total", str(FULL_N), "--success", str(fs["all"]),
"--fail", "0",
"--fail_reason", f"全量执行超时(>60min),已完成{fs['all']}条hash一致,mismatch=0; 疑shape过大单用例卡死",
"--has_bug", "不是bug(超时未完成)", "--has_aicore", "否",
"--card", str(device), "--analyzed", "否"], capture_output=True, text=True)
state[api] = {"status": "timeout_review", "stage": "full", **fs}
return "timeout"
r = subprocess.run(["python3", PARSER, "--log", full_log, "--api", api,
"--plog", os.path.join(d, f"{api}_consistent_fuzz_plog.log")],
capture_output=True, text=True)
aicore = "aicore_error=False" not in r.stdout
in_chg, out_only = (cross_check_inputs(full_log, input_tensors(api))
if fs["mismatch"] > 0 else ([], []))
if fs["mismatch"] > 0:
if in_chg:
bug = "需要人工确认(输入hash两轮不一致,疑似框架/CPP问题)"
status = "needs_review"
else:
bug = "是bug(非确定性实现,同输入两次执行输出hash不一致,提单)"
status = "bug_candidate"
elif fs["exec_err"] > 0:
kind, ratio = classify_exec_err(fs["err_codes"])
if kind == "约束拒绝":
bug = "不是bug(用例违反接口约束按资料预期报错)"
status = "done"
elif kind == "环境问题":
bug = "不是bug(环境问题)"
status = "needs_review"
else:
bug = "需要人工确认(错误码待复核)"
status = "needs_review"
else:
bug = "不是bug(全部用例执行通过)"
status = "done"
reason = (f"{fs['all']}条hash一致; mismatch={fs['mismatch']}"
+ (f"; 输入hash两轮不一致用例{in_chg[:10]}" if in_chg else "")
+ (f"; 仅输出不一致用例{out_only[:10]}" if out_only else "")
+ f"; execute_error={fs['exec_err']}" + (f" err_codes={fs['err_codes']}" if fs["err_codes"] else "")
+ ("; 有aicore" if aicore else "; 无aicore"))
subprocess.run(["python3", UPDATER, "--api", api, "--scenario", "consistent",
"--total", str(FULL_N), "--success", str(fs["all"]),
"--fail", str(fs["mismatch"]), "--fail_reason", reason,
"--has_bug", bug, "--has_aicore", "是" if aicore else "否",
"--card", str(device), "--analyzed", "是" if status == "done" else "否"],
capture_output=True, text=True)
state[api] = {"status": status, "bug": bug, "aicore": aicore,
"input_changed": in_chg[:20], "output_only": out_only[:20], **fs}
return status
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--worker", required=True)
ap.add_argument("--device", required=True)
ap.add_argument("--fw", required=True, help="turing_sec 实例目录(turing_sec 或 turing_sec_card1)")
ap.add_argument("--apis", required=True, help="接口清单文件(每行一个 aclnnApiName)")
ap.add_argument("--batch-size", type=int, default=40)
ap.add_argument("--limit", type=int, default=0, help="本次最多处理接口数(0=全部)")
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"consistent_campaign_worker{args.worker}.json")
state = load_state(state_path)
todo = [a for a in apis if a not in state]
if args.limit:
todo = todo[:args.limit]
logw(f"worker{args.worker} device{args.device} 待处理 {len(todo)}/{len(apis)}")
i = 0
while i < len(todo):
batch = todo[i:i + args.batch_size]
i += len(batch)
logw(f"worker{args.worker} 批次编译 {len(batch)} 个: {batch[0]}..{batch[-1]}")
try:
ok = compile_batch(fw, batch, state)
except RuntimeError as e:
logw(f"worker{args.worker} 批次编译失败: {e}")
save_state(state_path, state)
continue
save_state(state_path, state)
for api in ok:
logw(f"worker{args.worker} [{sum(1 for s in state.values() if s.get('status') in ('done','bug_candidate'))+1}] {api}")
try:
process_api(fw, api, args.device, state)
except Exception as e:
state[api] = {"status": "error", "error": str(e)[:300]}
logw(f"worker{args.worker} [ERR] {api}: {e}")
save_state(state_path, state)
save_state(state_path, state)
logw(f"worker{args.worker} ALL DONE")
if __name__ == "__main__":
sys.exit(main())