#!/usr/bin/env python3 """在「以太网 2」(I210) 上关闭非 EtherCAT 绑定,测跳拍率;可 --restore 还原。""" from __future__ import annotations import json import os import subprocess import sys import time ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) LAB = os.path.join(ROOT, "lab", "skip_meter") NIC = "以太网 2" def ps(cmd, timeout=60): r = subprocess.run( ["powershell", "-NoProfile", "-Command", cmd], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout, ) return (r.stdout or "").strip(), (r.stderr or "").strip() def snapshot(): out, err = ps( f"Get-NetAdapterBinding -Name '{NIC}' | " "Select-Object DisplayName,ComponentID,Enabled | ConvertTo-Json -Compress" ) data = json.loads(out) return data if isinstance(data, list) else [data] def disable(component_ids): logs = [] for cid in component_ids: out, err = ps( f"Disable-NetAdapterBinding -Name '{NIC}' -ComponentID '{cid}' -Confirm:$false; " f"Get-NetAdapterBinding -Name '{NIC}' -ComponentID '{cid}' | " "Select-Object ComponentID,Enabled | ConvertTo-Json -Compress" ) logs.append({"cid": cid, "out": out[:300], "err": err[:300]}) return logs def enable(component_ids): logs = [] for cid in component_ids: out, err = ps( f"Enable-NetAdapterBinding -Name '{NIC}' -ComponentID '{cid}' -Confirm:$false; " f"Get-NetAdapterBinding -Name '{NIC}' -ComponentID '{cid}' | " "Select-Object ComponentID,Enabled | ConvertTo-Json -Compress" ) logs.append({"cid": cid, "out": out[:300], "err": err[:300]}) return logs def set_prio(level="RealTime"): p = int(subprocess.check_output( ["powershell", "-NoProfile", "-Command", "(Get-Process ecat_native).Id"], text=True).strip()) subprocess.check_output( ["powershell", "-NoProfile", "-Command", f"$p=Get-Process -Id {p}; $p.PriorityClass='{level}'; $p.PriorityClass"], text=True) return p def meter(tag, secs=18): r = subprocess.run( [sys.executable, os.path.join(ROOT, "scripts", "skip_rate_meter.py"), "--tag", tag, "--secs", str(secs)], capture_output=True, text=True, encoding="utf-8", errors="replace") files = sorted(f for f in os.listdir(LAB) if f.startswith(tag + "_") and f.endswith(".json")) return json.load(open(os.path.join(LAB, files[-1]), encoding="utf-8")) # Offloads that are useless/dangerous for raw EtherCAT on a dedicated NIC OFFLOADS = [ "vmware_bridge", # VMware Bridge — intercepts/injects, classic ECAT killer "ms_pacer", # QoS packet scheduler "ms_lldp", # LLDP "ms_rspndr", # LLTD responder "ms_lltdio", # LLTD mapper "ms_server", # file/printer sharing "ms_msclient", # Microsoft network client # keep ms_tcpip / ms_tcpip6 for now in case anything uses IP on this NIC # Npcap must stay: INSECURE_NPCAP ] def main(): os.makedirs(LAB, exist_ok=True) snap = snapshot() snap_path = os.path.join(LAB, f"nic_bind_{int(time.time())}.json") json.dump(snap, open(snap_path, "w", encoding="utf-8"), ensure_ascii=False, indent=2) print("bind snapshot", snap_path) if "--restore" in sys.argv: print(enable(OFFLOADS)) return 0 print("disable", OFFLOADS) print(json.dumps(disable(OFFLOADS), ensure_ascii=False, indent=2)) print("set RealTime") print("pid", set_prio("RealTime")) # binding change usually doesn't reset link; brief settle time.sleep(3) data = meter("bind_off", 18) row = { "late_per_s": data["late_per_s"], "skip_per_s": data["skip_per_s"], "late_frac_pct": data["late_frac_pct"], "achieved_hz": data["achieved_hz"], "n_late": data["n_late"], "send_stall_frac": data["send_stall_frac"], "rx_stall_frac": data["rx_stall_frac"], "work_p50": data["window_cyc_us"]["work"]["p50"], "rx_p50": data["window_cyc_us"]["rx"]["p50"], "send_p50": data["window_cyc_us"]["send"]["p50"], "work_p90": data["window_cyc_us"]["work"]["p90"], } print("ROW", json.dumps(row, ensure_ascii=False, indent=2)) out = os.path.join(LAB, f"summary_bind_off_{int(time.time())}.json") json.dump({"row": row, "disabled": OFFLOADS, "snap": snap_path}, open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=2) print("WROTE", out) if __name__ == "__main__": main()