55 lines
2.2 KiB
Python
55 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
||||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""ECAT native 主站的采样公共件:HTTP 调用 + 计数器差分。
|
|||
|
|
|
|||
|
|
固件 1.6.15 覆盖窗口布局下,各计数器的口径并不一致,混用会静默算错:
|
|||
|
|
* bridge.rx_count / head.global_seq —— u32,**回绕**
|
|||
|
|
* bridge.drop —— u16,**回绕**;段 1/段 3 单格"未取先覆"数,恒等于 λ − 实测Hz
|
|||
|
|
* rx_batch.lost(主站按 seq 空洞算的单拍丢帧) / pending(从站窗口 OVF) ——
|
|||
|
|
**每拍回填清零的单拍值**(ecat_native.c:461),不是累计计数:拿它做差得速率恒为 0,
|
|||
|
|
拿轮询见过的最大值当总丢帧也只是下界
|
|||
|
|
* app/load.rx_torn / pdo_reentry —— u16,**饱和**(到 65535 就停,不是回绕)
|
|||
|
|
饱和量按回绕算会把"已封顶"误报成"发生了 6 万多次",故二者分开处理。
|
|||
|
|
"""
|
|||
|
|
import json
|
|||
|
|
import urllib.request
|
|||
|
|
|
|||
|
|
SATURATE = 65535 # u16 饱和计数的顶
|
|||
|
|
|
|||
|
|
|
|||
|
|
def post(url, path, body=None, timeout=6.0):
|
|||
|
|
req = urllib.request.Request(
|
|||
|
|
url + path, data=json.dumps(body or {}).encode("utf-8"),
|
|||
|
|
headers={"Content-Type": "application/json"}, method="POST")
|
|||
|
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|||
|
|
return json.loads(r.read().decode("utf-8"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def delta_wrap(a, b, bits=16):
|
|||
|
|
"""回绕计数器的增量。真值下降(重启清零)会表现为一个巨大的正数,
|
|||
|
|
调用方须用 sanity 上界剔除 —— 这里只负责把模数算对。"""
|
|||
|
|
m = 1 << bits
|
|||
|
|
return (b - a) % m
|
|||
|
|
|
|||
|
|
|
|||
|
|
def delta_saturate(a, b):
|
|||
|
|
"""饱和计数的增量。返回 (增量, 是否已封顶)。
|
|||
|
|
|
|||
|
|
封顶后增量恒为 0 —— 读数 0 既可能是"真没发生"也可能是"早发生了但计不动",
|
|||
|
|
报告里必须写成下界而不是精确值。
|
|||
|
|
"""
|
|||
|
|
if b >= SATURATE:
|
|||
|
|
return 0, True
|
|||
|
|
if b < a:
|
|||
|
|
return 0, False # 计数器被回填清零:该窗口作废,不臆造增量
|
|||
|
|
return b - a, False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def percentile(values, q):
|
|||
|
|
"""小样本分位数(最近邻)。不引 numpy,采样窗口只有百来个点。"""
|
|||
|
|
if not values:
|
|||
|
|
return None
|
|||
|
|
s = sorted(values)
|
|||
|
|
i = min(len(s) - 1, max(0, int(round(q * (len(s) - 1)))))
|
|||
|
|
return s[i]
|