feat(compare-alert): 末帧列显「停留Xs」(dwell-only,不显总帧数)
failed 与 cancelled 兜底两条末帧路径用 _fmt_last 显示环节·页面+末段停留; 只带 dwell、不带总帧数,规避原注释担心的「总帧数配末段时长」误导; 缺 ts/时钟回退降级只显环节。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,16 @@ def _fmt_stuck(sp: trace_stuck.StuckPoint) -> str:
|
||||
return s
|
||||
|
||||
|
||||
def _fmt_last(sp: trace_stuck.StuckPoint) -> str:
|
||||
"""末帧路径(failed/兜底):环节·页面 + 末段停留时长,不显总帧数(总帧数配末段时长会误导)。
|
||||
dwell_ms 为 None(缺 ts/时钟回退) → 只显环节。"""
|
||||
s = sp.label()
|
||||
if sp.dwell_ms is not None:
|
||||
sec = round(sp.dwell_ms / 1000)
|
||||
s += f" 停留{sec}s" if sec >= 1 else " 停留<1s"
|
||||
return s
|
||||
|
||||
|
||||
def build_hits(
|
||||
records: list,
|
||||
*,
|
||||
@@ -134,9 +144,9 @@ def build_hits(
|
||||
cancelled_ms_threshold=cancelled_ms_threshold,
|
||||
cancelled_step_threshold=cancelled_step_threshold,
|
||||
)
|
||||
# 没卡点但命中(超阈值放弃)→ 用末帧标「退出前在哪屏」(平台·环节·页面)
|
||||
# 没卡点但命中(超阈值放弃)→ 末帧标「退出前在哪屏 + 在那屏停多久」(dwell-only,不显总帧数)
|
||||
if hit is not None and res is not None and res.last is not None:
|
||||
hit = _dc_replace(hit, stuck_point=res.last.label())
|
||||
hit = _dc_replace(hit, stuck_point=_fmt_last(res.last))
|
||||
if hit is not None:
|
||||
hits.append(hit)
|
||||
else:
|
||||
@@ -159,9 +169,9 @@ def build_hits(
|
||||
sp = trace_stuck.last_step(td, max_tail=max_tail)
|
||||
reads += 1
|
||||
if sp is not None:
|
||||
# failed 是「末帧停在哪」:last_step 返回的 frames 是该平台总帧数(非"卡住"帧数)、
|
||||
# stuck_ms=None,带上帧数/时长会误导,故只用 label() 显示环节
|
||||
hit = _dc_replace(hit, stuck_point=sp.label())
|
||||
# failed 是「末帧停在哪 + 在那屏停多久」:只显环节·页面 + 末段停留 dwell,
|
||||
# 不显总帧数(总帧数配末段时长会误导);缺 ts 时 _fmt_last 自动退化为只显环节
|
||||
hit = _dc_replace(hit, stuck_point=_fmt_last(sp))
|
||||
if hit is not None:
|
||||
hits.append(hit)
|
||||
return hits
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from app.core.compare_alert_worker import _fmt_stuck, build_hits
|
||||
from app.core.compare_alert_worker import _fmt_last, _fmt_stuck, build_hits
|
||||
from app.services.trace_stuck import StuckPoint
|
||||
|
||||
|
||||
@@ -28,6 +28,15 @@ def _frame(pdir: Path, idx: int, step: str, page: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _frame_ts(pdir: Path, idx: int, step: str, page: str, ts: str) -> None:
|
||||
pdir.mkdir(parents=True, exist_ok=True)
|
||||
body = {"pipeline_step": step, "detected_page": page, "timestamp": ts,
|
||||
"windows": [{"n": ["x" * 200]}]}
|
||||
(pdir / f"step_{idx:03d}.json").write_text(
|
||||
json.dumps(body, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
_KW = dict(
|
||||
stuck_threshold=15, max_tail=40, max_trace_reads=30,
|
||||
cancelled_ms_threshold=90000, cancelled_step_threshold=30,
|
||||
@@ -144,3 +153,54 @@ def test_fmt_stuck_with_ms():
|
||||
def test_fmt_stuck_without_ms():
|
||||
sp = StuckPoint(platform="meituan", pipeline_step="add_one_dish", frames=110, stuck_ms=None)
|
||||
assert _fmt_stuck(sp) == "美团·加菜 110帧"
|
||||
|
||||
|
||||
# ---- _fmt_last 单测(末帧路径:环节·页面 + 停留,不显总帧数)----
|
||||
|
||||
def test_fmt_last_with_dwell():
|
||||
sp = StuckPoint("meituan", "checkout", frames=480,
|
||||
detected_page="checkout_page", dwell_ms=8000)
|
||||
assert _fmt_last(sp) == "美团·结算·checkout_page 停留8s"
|
||||
|
||||
|
||||
def test_fmt_last_sub_second():
|
||||
sp = StuckPoint("meituan", "checkout", frames=480,
|
||||
detected_page="checkout_page", dwell_ms=300)
|
||||
assert _fmt_last(sp) == "美团·结算·checkout_page 停留<1s"
|
||||
|
||||
|
||||
def test_fmt_last_without_dwell():
|
||||
sp = StuckPoint("meituan", "checkout", frames=480,
|
||||
detected_page="checkout_page", dwell_ms=None)
|
||||
assert _fmt_last(sp) == "美团·结算·checkout_page"
|
||||
|
||||
|
||||
# ---- 末帧路径带 dwell 集成 ----
|
||||
|
||||
def test_failed_stuck_point_has_dwell(tmp_path):
|
||||
# failed 末帧 5 帧都在 checkout,ts :00→:08 → stuck_point 附「停留8s」
|
||||
p = tmp_path / "20260807_f" / "meituan"
|
||||
for i in range(5):
|
||||
_frame_ts(p, i, "checkout", "checkout_page",
|
||||
f"2026-08-07T12:00:{i * 2:02d}.000000")
|
||||
rec = _Rec(status="failed", fail_reason="启动超时",
|
||||
trace_url="https://x/traces/20260807_f/")
|
||||
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
|
||||
assert len(hits) == 1
|
||||
assert hits[0].alert_type == "T2"
|
||||
assert hits[0].stuck_point == "美团·结算·checkout_page 停留8s"
|
||||
|
||||
|
||||
def test_cancelled_fallback_stuck_point_has_dwell(tmp_path):
|
||||
# cancelled 超阈值(95s)但末段 5<15 不卡死 → 兜底,末帧带 ts → 附「停留8s」
|
||||
p = tmp_path / "20260807_c" / "eleme"
|
||||
_frame_ts(p, 0, "set_address", "home", "2026-08-07T12:00:00.000000")
|
||||
for i in range(1, 6): # enter_store 5 帧 :02→:10
|
||||
_frame_ts(p, i, "enter_store", "store",
|
||||
f"2026-08-07T12:00:{i * 2:02d}.000000")
|
||||
rec = _Rec(status="cancelled", trace_url="https://x/traces/20260807_c/",
|
||||
total_ms=95000, step_count=40)
|
||||
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
|
||||
assert len(hits) == 1
|
||||
assert hits[0].alert_type == "T5"
|
||||
assert hits[0].stuck_point == "饿了么·进店·store 停留8s"
|
||||
|
||||
Reference in New Issue
Block a user