Files
DCU_DesktopController/scripts/check_esf_latency.py
T

119 lines
3.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""A/B:JSON motor/send vs ESF1 快发(HTTP send_raw / 持久 TCP)软件段延迟。
需要:tools/ecat ecat_native 已运行(总线可不必在 OP,未连接时会报未连接但仍计时)。
用法:
python scripts/check_esf_latency.py
python scripts/check_esf_latency.py --n 40
"""
from __future__ import annotations
import argparse
import json
import os
import socket
import statistics
import struct
import sys
import time
import urllib.request
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from esp32_server import pack_esf, unpack_fsok # noqa: E402
PAGE = os.environ.get("DCU_PAGE_BASE", "http://127.0.0.1:2625")
ECAT = PAGE + "/ecat/api"
def http(url, data: bytes, ctype="application/json", timeout=5.0):
req = urllib.request.Request(
url, data=data,
headers={"Content-Type": ctype, "Accept": "application/json"},
method="POST",
)
t0 = time.perf_counter()
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
body = r.read()
ms = (time.perf_counter() - t0) * 1000.0
try:
return ms, json.loads(body.decode("utf-8", "replace") or "{}")
except Exception:
return ms, {"_raw_len": len(body)}
except Exception as e:
return (time.perf_counter() - t0) * 1000.0, {"ok": False, "error": str(e)}
def pct(xs, p):
if not xs:
return None
xs = sorted(xs)
k = max(0, min(len(xs) - 1, int(round((p / 100.0) * (len(xs) - 1)))))
return xs[k]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--n", type=int, default=30)
args = ap.parse_args()
frame = {"ch": 2, "id": 0x00030200, "dlc": 32,
"data": "02" + "00" * 29 + "000000"} # 静止 CMD
frames = [frame]
esf = pack_esf(frames)
js = json.dumps({"frames": frames}, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
ms_h, health = http(ECAT + "/native/health", b"{}")
print(f"health {ms_h:.2f}ms {json.dumps(health, ensure_ascii=False)[:200]}")
fsp = int(health.get("fast_send_port") or 0)
print(f"fast_send_port={fsp}")
json_ms, raw_ms = [], []
for _ in range(args.n):
ms, _ = http(ECAT + "/motor/send", js)
json_ms.append(ms)
ms, _ = http(ECAT + "/motor/send_raw", esf, "application/octet-stream")
raw_ms.append(ms)
tcp_ms = []
if fsp > 0:
sock = socket.create_connection(("127.0.0.1", fsp), timeout=None)
sock.settimeout(2.0)
try:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except Exception:
pass
for _ in range(args.n):
t0 = time.perf_counter()
sock.sendall(esf)
resp = b""
while len(resp) < 8:
c = sock.recv(8 - len(resp))
if not c:
raise OSError("closed")
resp += c
tcp_ms.append((time.perf_counter() - t0) * 1000.0)
r = unpack_fsok(resp)
if not r.get("ok") and not tcp_ms:
print("TCP 快发 ok=false", r)
sock.close()
def show(name, xs):
if not xs:
print(f"{name}: (skip)")
return
print(f"{name}: n={len(xs)} mean={statistics.mean(xs):.3f}ms "
f"p50={pct(xs,50):.3f} p90={pct(xs,90):.3f} min={min(xs):.3f} max={max(xs):.3f}")
show("JSON motor/send ", json_ms)
show("HTTP send_raw ", raw_ms)
show("TCP fast_send ", tcp_ms)
print("口径:HTTP 客户端侧 POST 往返(含本机代理时);不含浏览器 JS/pywebview hop。")
return 0
if __name__ == "__main__":
sys.exit(main())