#!/usr/bin/env python3 """查 pdo 线程亲和 + 尝试 ITR 200/400 在 0.25ms 静默下的表现。""" import json, os, subprocess, time, urllib.request ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LAB = os.path.join(ROOT, "lab", "skip_meter") NIC = "以太网 2" def ps(cmd, timeout=60): return subprocess.check_output(["powershell","-NoProfile","-Command",cmd], text=True, encoding="utf-8", errors="replace", timeout=timeout).strip() def post(p, b=None): d = json.dumps(b or {}).encode() r = urllib.request.Request("http://127.0.0.1:8090"+p, data=d, headers={"Content-Type":"application/json"}) return json.loads(urllib.request.urlopen(r, timeout=8).read().decode()) def wait_ok(timeout=35): t0 = time.time() while time.time()-t0 < timeout: try: h = post("/api/native/health") if h.get("loop")==1 and int(h.get("wkc") or 0)>=3: return h except Exception: pass time.sleep(0.4) return None def set_rt(): p = int(ps("(Get-Process ecat_native).Id")) ps(f"$p=Get-Process -Id {p}; $p.PriorityClass='RealTime'; $p.PriorityClass") return p def quiet_rate(sec=8): st = post("/api/motor/state") dbg = post("/api/motor/dbg") c0, s0 = st.get("cycles") or 0, dbg.get("skip_total") or 0 t0 = time.time() time.sleep(sec) st = post("/api/motor/state") dbg = post("/api/motor/dbg") dt = time.time()-t0 c1, s1 = st.get("cycles") or 0, dbg.get("skip_total") or 0 return { "pdo_hz": round((c1-c0)/dt, 1), "late_per_s": round((s1-s0)/dt, 2), "cyc_us": st.get("cyc_us"), "period": st.get("period_ms"), } def bus_restart_period(ms): try: post("/api/motor/stop") except Exception: pass time.sleep(0.15) post("/api/motor/period", {"period_ms": ms}) time.sleep(0.15) post("/api/motor/start") def nic_set(kw, val): out, err = "", "" r = subprocess.run(["powershell","-NoProfile","-Command", f"Set-NetAdapterAdvancedProperty -Name '{NIC}' -RegistryKeyword '{kw}' -RegistryValue '{val}' -NoRestart"], capture_output=True, text=True, encoding="utf-8", errors="replace") return r.stdout.strip(), r.stderr.strip() # affinity of ecat threads via .NET is limited; dump processor affinity mask print("=== process affinity ===") print(ps(r''' $p = Get-Process ecat_native "PID $($p.Id) Priority=$($p.PriorityClass)" # ProcessorAffinity is a bitmask "Affinity=$($p.ProcessorAffinity)" ''')) print("=== I210 current ITR/moderation ===") print(ps(r''' Get-NetAdapterAdvancedProperty -Name '以太网 2' | Where-Object { $_.RegistryKeyword -in 'ITR','*InterruptModeration','*TransmitBuffers' } | Select-Object RegistryKeyword,DisplayValue | ConvertTo-Json -Compress ''')) results = [] # try ITR values at 0.25ms quiet for itr in ("200", "400", "950", "65535"): print(f"\n===== ITR={itr} @0.25ms =====") o, e = nic_set("*InterruptModeration", "1") o2, e2 = nic_set("ITR", itr) print("set", o2 or o, e2 or e) # restart nic only if needed - ITR may need restart ps(f"Restart-NetAdapter -Name '{NIC}' -Confirm:$false") time.sleep(2) h = wait_ok(30) print("health", bool(h)) if not h: try: post("/api/op/start"); post("/api/motor/start") h = wait_ok(20) except Exception as ex: print(ex) set_rt() bus_restart_period(0.25) time.sleep(3) set_rt() r = quiet_rate(8) print(json.dumps({k:r[k] for k in ("pdo_hz","late_per_s","period")}, ensure_ascii=False)) print("work/send/rx", r["cyc_us"]["work"]["p50"], r["cyc_us"]["send"]["p50"], r["cyc_us"]["rx"]["p50"]) results.append({"itr": itr, **{k:r[k] for k in ("pdo_hz","late_per_s")}, "work_p50": r["cyc_us"]["work"]["p50"], "send_p50": r["cyc_us"]["send"]["p50"], "rx_p50": r["cyc_us"]["rx"]["p50"]}) print("\nSUMMARY", json.dumps(results, ensure_ascii=False, indent=2)) # restore adaptive nic_set("ITR", "65535") ps(f"Restart-NetAdapter -Name '{NIC}' -Confirm:$false") time.sleep(2) wait_ok(25) set_rt() # leave at 1ms for safety bus_restart_period(1.0) time.sleep(1) print("restored 1ms", post("/api/native/health")) path = os.path.join(LAB, f"summary_itr_4k_{int(time.time())}.json") json.dump(results, open(path,"w",encoding="utf-8"), ensure_ascii=False, indent=2) print("WROTE", path)