Skip to content

Commit b2a1624

Browse files
committed
ci: scratch probe for jitter export determinism on Blacksmith (not for merge)
1 parent b8a8b01 commit b2a1624

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

‎.github/workflows/jitter-probe.yml‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Jitter export determinism probe (scratch)
2+
3+
on:
4+
push:
5+
branches: [scratch/jitter-probe]
6+
7+
permissions:
8+
contents: read
9+
10+
env:
11+
CARGO_TERM_COLOR: always
12+
CARGO_INCREMENTAL: "0"
13+
CAP_VERIFY_CARGO_PROFILE: ci-verify
14+
CAP_VERIFY_LOCKED: "1"
15+
CAP_VERIFY_TIER: full
16+
17+
jobs:
18+
probe:
19+
name: probe ${{ matrix.copy }}
20+
runs-on: blacksmith-6vcpu-macos-15
21+
timeout-minutes: 120
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
copy: [a, b]
26+
steps:
27+
- uses: actions/checkout@v4
28+
with:
29+
persist-credentials: false
30+
- name: Instrument the AVAssetReader decoder (probe only)
31+
run: git apply probe-patches/avf-instrument.patch && git diff --stat
32+
- name: Setup
33+
uses: ./.github/actions/engine-verify-setup
34+
with:
35+
target: aarch64-apple-darwin
36+
tier: full
37+
- name: Build
38+
run: scripts/verify/run.sh full --suites export --no-run
39+
- name: Repeat the 120 s jitter export
40+
run: |
41+
set +e
42+
mkdir -p "$RUNNER_TEMP/probe"
43+
for i in $(seq 1 8); do
44+
export CAP_VERIFY_REPORT_DIR="$RUNNER_TEMP/probe/report-$i"
45+
CAP_VERIFY_CASE='export/jitter-camera-120s/full/mp460/avf' cargo nextest run --cargo-profile ci-verify --locked \
46+
-p cap-verify --test export --no-fail-fast --no-capture -E 'test(=jitter_camera)' > "$RUNNER_TEMP/probe/log-$i.txt" 2>&1
47+
echo "rep $i: $(grep -h -o 'nondeterministic: [^(]*\|camera: [0-9]* of [0-9]* frames wrong' "$CAP_VERIFY_REPORT_DIR"/*.jsonl | sort -u | tr '\n' ' ')"
48+
done
49+
true
50+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
51+
if: always()
52+
with:
53+
name: jitter-probe-${{ matrix.copy }}
54+
path: ${{ runner.temp }}/probe
55+
retention-days: 7

‎probe-patches/avf-instrument.patch‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
diff --git a/crates/rendering/src/decoder/avassetreader.rs b/crates/rendering/src/decoder/avassetreader.rs
2+
index d9ab926e6..fd90fdd5b 100644
3+
--- a/crates/rendering/src/decoder/avassetreader.rs
4+
+++ b/crates/rendering/src/decoder/avassetreader.rs
5+
@@ -810,6 +810,7 @@ impl AVAssetReaderDecoder {
6+
for request in pending_requests.drain(..) {
7+
if let Some(cached) = cache.get(&request.frame) {
8+
let data = cached.data().clone();
9+
+ eprintln!("AVFDBG {:?} s1 req={} ans={}", std::thread::current().id(), request.frame, data.number);
10+
let _ = request.sender.send(data.to_decoded_frame());
11+
last_sent_data = Some(data);
12+
} else {
13+
@@ -848,6 +849,7 @@ impl AVAssetReaderDecoder {
14+
if let Some(data) = data {
15+
gap_hold = Some(data.clone());
16+
*last_sent_frame.borrow_mut() = Some(data.clone());
17+
+ eprintln!("AVFDBG {:?} s2 req={} ans={}", std::thread::current().id(), req.frame, data.number);
18+
let _ = req.sender.send(data.to_decoded_frame());
19+
} else {
20+
still_unfulfilled.push(req);
21+
@@ -895,6 +897,7 @@ impl AVAssetReaderDecoder {
22+
let this = this.as_mut().expect("Video reader initialized above");
23+
24+
let (decoder_idx, was_reset) = this.select_best_decoder(requested_time, is_scrubbing);
25+
+ eprintln!("AVFDBG {:?} select t={requested_time:.4} idx={decoder_idx} reset={was_reset} scrub={is_scrubbing}", std::thread::current().id());
26+
27+
let cache_min = if was_reset {
28+
min_requested_frame.saturating_sub(FRAME_CACHE_SIZE as u32 * 2)
29+
@@ -1032,11 +1035,13 @@ impl AVAssetReaderDecoder {
30+
if req.frame == current_frame {
31+
let data = cache_frame.data().clone();
32+
*last_sent_frame.borrow_mut() = Some(data.clone());
33+
+ eprintln!("AVFDBG {:?} s3 req={} ans={}", std::thread::current().id(), req.frame, data.number);
34+
let _ = req.sender.send(data.to_decoded_frame());
35+
} else if req.frame < current_frame {
36+
if let Some(cached) = cache.get(&req.frame) {
37+
let data = cached.data().clone();
38+
*last_sent_frame.borrow_mut() = Some(data.clone());
39+
+ eprintln!("AVFDBG {:?} s4 req={} ans={}", std::thread::current().id(), req.frame, data.number);
40+
let _ = req.sender.send(data.to_decoded_frame());
41+
} else {
42+
// Always answer. Prefer the newest frame at-or-before
43+
@@ -1072,6 +1077,7 @@ impl AVAssetReaderDecoder {
44+
if let Some(data) = best_before {
45+
gap_hold = Some(data.clone());
46+
*last_sent_frame.borrow_mut() = Some(data.clone());
47+
+ eprintln!("AVFDBG {:?} s5 req={} ans={}", std::thread::current().id(), req.frame, data.number);
48+
let _ = req.sender.send(data.to_decoded_frame());
49+
} else if let Some((&frame_num, cached)) =
50+
cache.range(req.frame..).next()
51+
@@ -1081,6 +1087,7 @@ impl AVAssetReaderDecoder {
52+
nearest_frame = frame_num,
53+
"serving forward frame across pts gap"
54+
);
55+
+ eprintln!("AVFDBG {:?} s6 req={} ans={}", std::thread::current().id(), req.frame, cached.data().number);
56+
let _ = req.sender.send(cached.data().to_decoded_frame());
57+
} else {
58+
tracing::warn!(
59+
@@ -1203,6 +1210,7 @@ impl AVAssetReaderDecoder {
60+
for req in pending_requests.drain(..) {
61+
if let Some(cached) = cache.get(&req.frame) {
62+
let data = cached.data().clone();
63+
+ eprintln!("AVFDBG {:?} s7 req={} ans={}", std::thread::current().id(), req.frame, data.number);
64+
let _ = req.sender.send(data.to_decoded_frame());
65+
} else {
66+
let allow_relaxed_fallback = is_scrubbing
67+
@@ -1246,6 +1254,7 @@ impl AVAssetReaderDecoder {
68+
if let Some(data) = best_before {
69+
gap_hold = Some(data.clone());
70+
*last_sent_frame.borrow_mut() = Some(data.clone());
71+
+ eprintln!("AVFDBG {:?} s8 req={} ans={}", std::thread::current().id(), req.frame, data.number);
72+
let _ = req.sender.send(data.to_decoded_frame());
73+
} else if let Some((&frame_num, cached)) = cache.range(req.frame..).next() {
74+
if req.frame.abs_diff(frame_num) > fallback_distance {
75+
@@ -1255,14 +1264,17 @@ impl AVAssetReaderDecoder {
76+
"serving forward frame across pts gap"
77+
);
78+
}
79+
+ eprintln!("AVFDBG {:?} s9 req={} ans={}", std::thread::current().id(), req.frame, cached.data().number);
80+
let _ = req.sender.send(cached.data().to_decoded_frame());
81+
} else if allow_relaxed_fallback
82+
&& let Some(ref last) = *last_sent_frame.borrow()
83+
{
84+
+ eprintln!("AVFDBG {:?} s10 req={} ans={}", std::thread::current().id(), req.frame, last.number);
85+
let _ = req.sender.send(last.to_decoded_frame());
86+
} else if allow_relaxed_fallback
87+
&& let Some(ref first) = *first_ever_frame.borrow()
88+
{
89+
+ eprintln!("AVFDBG {:?} s11 req={} ans={}", std::thread::current().id(), req.frame, first.number);
90+
let _ = req.sender.send(first.to_decoded_frame());
91+
} else {
92+
unfulfilled_count += 1;

0 commit comments

Comments
 (0)