fix: 前后端时钟问题
This commit is contained in:
@@ -677,9 +677,9 @@ class NetworkTelemetryService:
|
|||||||
d_to_b_video = _coerce_float(d_to_b_video_raw) if d_to_b_video_raw is not None else None
|
d_to_b_video = _coerce_float(d_to_b_video_raw) if d_to_b_video_raw is not None else None
|
||||||
ack_estimate = self._control_ack_tracker.get_latest_estimate()
|
ack_estimate = self._control_ack_tracker.get_latest_estimate()
|
||||||
capture_to_send_raw = video_receiver_status.get("latest_capture_to_send_ms")
|
capture_to_send_raw = video_receiver_status.get("latest_capture_to_send_ms")
|
||||||
a_recv_to_paint_raw = display_probe_status.get("a_recv_to_paint_ms")
|
request_to_paint_raw = display_probe_status.get("request_to_paint_ms")
|
||||||
capture_to_send_ms = _coerce_float(capture_to_send_raw) if capture_to_send_raw is not None else None
|
capture_to_send_ms = _coerce_float(capture_to_send_raw) if capture_to_send_raw is not None else None
|
||||||
a_recv_to_paint_ms = _coerce_float(a_recv_to_paint_raw) if a_recv_to_paint_raw is not None else None
|
request_to_paint_ms = _coerce_float(request_to_paint_raw) if request_to_paint_raw is not None else None
|
||||||
video_network_oneway_est_ms = (
|
video_network_oneway_est_ms = (
|
||||||
round((a_to_d_video + d_to_b_video) / 2.0, 3)
|
round((a_to_d_video + d_to_b_video) / 2.0, 3)
|
||||||
if a_to_d_video is not None and d_to_b_video is not None
|
if a_to_d_video is not None and d_to_b_video is not None
|
||||||
@@ -689,8 +689,8 @@ class NetworkTelemetryService:
|
|||||||
if capture_to_send_ms is not None and video_network_oneway_est_ms is not None:
|
if capture_to_send_ms is not None and video_network_oneway_est_ms is not None:
|
||||||
video_partial_est_ms = round(capture_to_send_ms + video_network_oneway_est_ms, 3)
|
video_partial_est_ms = round(capture_to_send_ms + video_network_oneway_est_ms, 3)
|
||||||
video_e2e_est_ms = None
|
video_e2e_est_ms = None
|
||||||
if video_partial_est_ms is not None and a_recv_to_paint_ms is not None:
|
if video_partial_est_ms is not None and request_to_paint_ms is not None:
|
||||||
video_e2e_est_ms = round(video_partial_est_ms + a_recv_to_paint_ms, 3)
|
video_e2e_est_ms = round(video_partial_est_ms + request_to_paint_ms, 3)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"control_loop_rtt_ms": ack_estimate.get("control_loop_rtt_ms"),
|
"control_loop_rtt_ms": ack_estimate.get("control_loop_rtt_ms"),
|
||||||
@@ -710,7 +710,7 @@ class NetworkTelemetryService:
|
|||||||
"video_e2e_est_ms": video_e2e_est_ms,
|
"video_e2e_est_ms": video_e2e_est_ms,
|
||||||
"estimate_method": {
|
"estimate_method": {
|
||||||
"control": "ack_loop" if ack_estimate.get("ack_available") else "srtt_fallback",
|
"control": "ack_loop" if ack_estimate.get("ack_available") else "srtt_fallback",
|
||||||
"video": "capture_to_send+srtt/2+recv_to_paint" if video_e2e_est_ms is not None else "capture_to_send+srtt/2",
|
"video": "capture_to_send+srtt/2+request_to_paint" if video_e2e_est_ms is not None else "capture_to_send+srtt/2",
|
||||||
},
|
},
|
||||||
"clock_sync_required": False,
|
"clock_sync_required": False,
|
||||||
"assumptions": [
|
"assumptions": [
|
||||||
|
|||||||
@@ -48,18 +48,27 @@ class VideoDisplayProbeStore:
|
|||||||
input_to_next_fresh_frame_ms=None,
|
input_to_next_fresh_frame_ms=None,
|
||||||
input_to_next_changed_frame_ms=None,
|
input_to_next_changed_frame_ms=None,
|
||||||
input_to_next_paint_ms=None,
|
input_to_next_paint_ms=None,
|
||||||
a_recv_to_paint_ms=None,
|
request_to_paint_ms=None,
|
||||||
|
response_to_paint_ms=None,
|
||||||
|
backend_to_request_ms=None,
|
||||||
|
backend_to_paint_ms_raw=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def record_event(self, payload: dict[str, Any]) -> None:
|
def record_event(self, payload: dict[str, Any]) -> None:
|
||||||
backend_received_unix_ns = payload.get("backend_received_unix_ns")
|
backend_received_unix_ns = payload.get("backend_received_unix_ns")
|
||||||
|
request_started_unix_ms = payload.get("request_started_unix_ms")
|
||||||
|
response_received_unix_ms = payload.get("response_received_unix_ms")
|
||||||
paint_unix_ms = payload.get("paint_unix_ms")
|
paint_unix_ms = payload.get("paint_unix_ms")
|
||||||
a_recv_to_paint_ms = None
|
request_to_paint_ms = self._duration_ms(paint_unix_ms, request_started_unix_ms, clamp_floor_zero=True)
|
||||||
|
response_to_paint_ms = self._duration_ms(paint_unix_ms, response_received_unix_ms, clamp_floor_zero=True)
|
||||||
|
backend_received_unix_ms = None
|
||||||
try:
|
try:
|
||||||
if backend_received_unix_ns is not None and paint_unix_ms is not None:
|
if backend_received_unix_ns is not None:
|
||||||
a_recv_to_paint_ms = round(float(paint_unix_ms) - (int(backend_received_unix_ns) / 1_000_000.0), 3)
|
backend_received_unix_ms = int(backend_received_unix_ns) / 1_000_000.0
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
a_recv_to_paint_ms = None
|
backend_received_unix_ms = None
|
||||||
|
backend_to_request_ms = self._duration_ms(request_started_unix_ms, backend_received_unix_ms, clamp_floor_zero=False)
|
||||||
|
backend_to_paint_ms_raw = self._duration_ms(paint_unix_ms, backend_received_unix_ms, clamp_floor_zero=False)
|
||||||
|
|
||||||
status = VideoDisplayProbeStatus(
|
status = VideoDisplayProbeStatus(
|
||||||
updated_at=str(payload.get("updated_at") or ""),
|
updated_at=str(payload.get("updated_at") or ""),
|
||||||
@@ -68,7 +77,10 @@ class VideoDisplayProbeStore:
|
|||||||
input_to_next_fresh_frame_ms=self._coerce_float(payload.get("input_to_next_fresh_frame_ms")),
|
input_to_next_fresh_frame_ms=self._coerce_float(payload.get("input_to_next_fresh_frame_ms")),
|
||||||
input_to_next_changed_frame_ms=self._coerce_float(payload.get("input_to_next_changed_frame_ms")),
|
input_to_next_changed_frame_ms=self._coerce_float(payload.get("input_to_next_changed_frame_ms")),
|
||||||
input_to_next_paint_ms=self._coerce_float(payload.get("input_to_next_paint_ms")),
|
input_to_next_paint_ms=self._coerce_float(payload.get("input_to_next_paint_ms")),
|
||||||
a_recv_to_paint_ms=a_recv_to_paint_ms,
|
request_to_paint_ms=request_to_paint_ms,
|
||||||
|
response_to_paint_ms=response_to_paint_ms,
|
||||||
|
backend_to_request_ms=backend_to_request_ms,
|
||||||
|
backend_to_paint_ms_raw=backend_to_paint_ms_raw,
|
||||||
)
|
)
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._latest = status
|
self._latest = status
|
||||||
@@ -84,7 +96,10 @@ class VideoDisplayProbeStore:
|
|||||||
"input_to_next_fresh_frame_ms": latest.input_to_next_fresh_frame_ms,
|
"input_to_next_fresh_frame_ms": latest.input_to_next_fresh_frame_ms,
|
||||||
"input_to_next_changed_frame_ms": latest.input_to_next_changed_frame_ms,
|
"input_to_next_changed_frame_ms": latest.input_to_next_changed_frame_ms,
|
||||||
"input_to_next_paint_ms": latest.input_to_next_paint_ms,
|
"input_to_next_paint_ms": latest.input_to_next_paint_ms,
|
||||||
"a_recv_to_paint_ms": latest.a_recv_to_paint_ms,
|
"request_to_paint_ms": latest.request_to_paint_ms,
|
||||||
|
"response_to_paint_ms": latest.response_to_paint_ms,
|
||||||
|
"backend_to_request_ms": latest.backend_to_request_ms,
|
||||||
|
"backend_to_paint_ms_raw": latest.backend_to_paint_ms_raw,
|
||||||
}
|
}
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
@@ -99,6 +114,17 @@ class VideoDisplayProbeStore:
|
|||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _duration_ms(cls, end_ms: Any, start_ms: Any, *, clamp_floor_zero: bool) -> float | None:
|
||||||
|
end_value = cls._coerce_float(end_ms)
|
||||||
|
start_value = cls._coerce_float(start_ms)
|
||||||
|
if end_value is None or start_value is None:
|
||||||
|
return None
|
||||||
|
delta = round(end_value - start_value, 3)
|
||||||
|
if clamp_floor_zero:
|
||||||
|
delta = max(0.0, delta)
|
||||||
|
return delta
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FrameTrailerMetadata:
|
class FrameTrailerMetadata:
|
||||||
@@ -123,7 +149,10 @@ class VideoDisplayProbeStatus:
|
|||||||
input_to_next_fresh_frame_ms: float | None
|
input_to_next_fresh_frame_ms: float | None
|
||||||
input_to_next_changed_frame_ms: float | None
|
input_to_next_changed_frame_ms: float | None
|
||||||
input_to_next_paint_ms: float | None
|
input_to_next_paint_ms: float | None
|
||||||
a_recv_to_paint_ms: float | None
|
request_to_paint_ms: float | None
|
||||||
|
response_to_paint_ms: float | None
|
||||||
|
backend_to_request_ms: float | None
|
||||||
|
backend_to_paint_ms_raw: float | None
|
||||||
|
|
||||||
|
|
||||||
class OmniSocketVideoReceiver:
|
class OmniSocketVideoReceiver:
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ function formatNumber(value: number | null | undefined, suffix = '') {
|
|||||||
return `${value.toFixed(1)}${suffix}`
|
return `${value.toFixed(1)}${suffix}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wallClockNowMs() {
|
||||||
|
return Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
let frameTimer: number | null = null
|
let frameTimer: number | null = null
|
||||||
let statusTimer: number | null = null
|
let statusTimer: number | null = null
|
||||||
let probeTimer: number | null = null
|
let probeTimer: number | null = null
|
||||||
@@ -151,7 +155,7 @@ async function runDisplayProbe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
probeRequestPending = true
|
probeRequestPending = true
|
||||||
const requestStartedUnixMs = performance.timeOrigin + performance.now()
|
const requestStartedUnixMs = wallClockNowMs()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
probeKey += 1
|
probeKey += 1
|
||||||
@@ -167,7 +171,7 @@ async function runDisplayProbe() {
|
|||||||
const frameHashHeader = response.headers.get('X-Blitz-Frame-Hash') ?? ''
|
const frameHashHeader = response.headers.get('X-Blitz-Frame-Hash') ?? ''
|
||||||
const frameSeq = frameSeqHeader ? Number(frameSeqHeader) : null
|
const frameSeq = frameSeqHeader ? Number(frameSeqHeader) : null
|
||||||
const backendReceivedUnixNs = backendReceivedHeader ? Number(backendReceivedHeader) : null
|
const backendReceivedUnixNs = backendReceivedHeader ? Number(backendReceivedHeader) : null
|
||||||
const responseReceivedUnixMs = performance.timeOrigin + performance.now()
|
const responseReceivedUnixMs = wallClockNowMs()
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const objectUrl = URL.createObjectURL(blob)
|
const objectUrl = URL.createObjectURL(blob)
|
||||||
|
|
||||||
@@ -175,11 +179,11 @@ async function runDisplayProbe() {
|
|||||||
const probeImage = new Image()
|
const probeImage = new Image()
|
||||||
probeImage.src = objectUrl
|
probeImage.src = objectUrl
|
||||||
await probeImage.decode()
|
await probeImage.decode()
|
||||||
const decodedUnixMs = performance.timeOrigin + performance.now()
|
const decodedUnixMs = wallClockNowMs()
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
requestAnimationFrame(() => resolve())
|
requestAnimationFrame(() => resolve())
|
||||||
})
|
})
|
||||||
const paintUnixMs = performance.timeOrigin + performance.now()
|
const paintUnixMs = wallClockNowMs()
|
||||||
|
|
||||||
let inputToNextFreshFrameMs: number | null = null
|
let inputToNextFreshFrameMs: number | null = null
|
||||||
let inputToNextChangedFrameMs: number | null = null
|
let inputToNextChangedFrameMs: number | null = null
|
||||||
@@ -339,7 +343,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span>Paint Delay</span>
|
<span>Paint Delay</span>
|
||||||
<strong>{{ formatNumber(displayVideo?.display_probe?.a_recv_to_paint_ms, ' ms') }}</strong>
|
<strong>{{ formatNumber(displayVideo?.display_probe?.request_to_paint_ms, ' ms') }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -367,7 +371,7 @@ watch(
|
|||||||
<p><strong>Input to next seq:</strong> {{ formatNumber(operatorMetrics.input_to_next_fresh_frame_ms, ' ms') }}</p>
|
<p><strong>Input to next seq:</strong> {{ formatNumber(operatorMetrics.input_to_next_fresh_frame_ms, ' ms') }}</p>
|
||||||
<p><strong>Input to changed frame:</strong> {{ formatNumber(operatorMetrics.input_to_next_changed_frame_ms, ' ms') }}</p>
|
<p><strong>Input to changed frame:</strong> {{ formatNumber(operatorMetrics.input_to_next_changed_frame_ms, ' ms') }}</p>
|
||||||
<p><strong>Input to paint:</strong> {{ formatNumber(operatorMetrics.input_to_next_paint_ms, ' ms') }}</p>
|
<p><strong>Input to paint:</strong> {{ formatNumber(operatorMetrics.input_to_next_paint_ms, ' ms') }}</p>
|
||||||
<p><strong>Display probe recv-to-paint:</strong> {{ formatNumber(displayVideo?.display_probe?.a_recv_to_paint_ms, ' ms') }}</p>
|
<p><strong>Display probe request-to-paint:</strong> {{ formatNumber(displayVideo?.display_probe?.request_to_paint_ms, ' ms') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,10 @@ export interface VideoStatus {
|
|||||||
input_to_next_fresh_frame_ms: number | null
|
input_to_next_fresh_frame_ms: number | null
|
||||||
input_to_next_changed_frame_ms: number | null
|
input_to_next_changed_frame_ms: number | null
|
||||||
input_to_next_paint_ms: number | null
|
input_to_next_paint_ms: number | null
|
||||||
a_recv_to_paint_ms: number | null
|
request_to_paint_ms: number | null
|
||||||
|
response_to_paint_ms: number | null
|
||||||
|
backend_to_request_ms: number | null
|
||||||
|
backend_to_paint_ms_raw: number | null
|
||||||
}
|
}
|
||||||
receiver?: {
|
receiver?: {
|
||||||
backend_ready: boolean
|
backend_ready: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user