Diagnose iter cadence stutter

BanterLab iterations are arriving on an irregular cadence, throughput is noticeably worse when the window is visible than when it's hidden, or the engine occasionally hangs at the moment you switch virtual desktops. This guide explains the symptom set, the underlying mechanism, and the diagnostic signals.

The symptom set

The pattern looks like one or more of:

  • "Smooth when hidden, stuttery when visible" — iter arrivals on a desktop where BanterLab is occluded behave nicely; bringing the window forward immediately degrades cadence.
  • "Visible iteration count lags hidden by 5-20 %" over a 5-minute run.
  • "Hangs on virtual-desktop transition" — a fraction of desktop switches leave the engine quiescent for several seconds before iters resume.
  • "Audible TX micro-gaps" — short underrun events in the speaker output that correlate to window paint events.

The mechanism

Before the audio-compositor-decoupling spec, two stages of the BanterLab pipeline ran on the Qt GUI thread:

  1. The mic drain timer (mMicTimer, 5 ms Qt::PreciseTimer) — read samples out of AudioInputNode and posted them to BanterClient.
  2. The iter scheduler — mSettleTimer, mTimeoutTimer, and QTimer::singleShot(gapMs, …, runNextIteration).

Wayland compositors stop sending frame callbacks to occluded surfaces, then flood the queue when the surface becomes visible. The Qt event loop processes those callbacks in order, which means any QTimer sharing the loop is at the mercy of the compositor. Under sustained pressure Qt coalesces ticks (documented behaviour in qtcore/qtimer.html: "Qt will emit timeout() only once, even if multiple timeouts have expired"). The mic drain misses chunks; the scheduler misses gap deadlines.

After the spec lands, both stages run on PinnedWatchdogThread instances driven directly by the OS scheduler. Cadence becomes invariant to compositor work.

Diagnostic signals

Symptom Where to look What it means
Audible underrun [HUB-EVENT] log lines from AudioOutputNode — N underrun(s) in last 5006 ms Producer pipeline missed QAudioSink's pull rate. Under 1/min is acceptable; sustained burst means the codec worker is starved, not the GUI thread.
Iter cadence variance Wall-clock timestamps on runNextIteration log lines Compare a 100-iter window with the window visible vs. hidden. > 5 % delta on a quiet system suggests the GUI thread is still in the path somewhere.
Mic chunk gaps BanterNodeWidget[X] mic: got=N peak=… rms=… lines Lines should appear every ~2 s under normal load. A 5+ second gap means the mic drain thread missed ticks (rare — the pinned thread is invariant to compositor pacing).

Measurement procedure (5 % parity)

To verify the spec's hidden-vs-visible parity target on a host:

  1. qbs build -f project.qbs -p hub
  2. Hidden run — launch with the window on another virtual desktop the entire time:
    default/install-root/usr/local/bin/hub \
      --base-dir /tmp/banter-hidden-$(date +%s) \
      --activity BanterLabActivity \
      --open test/testBanter/specs/tier4_rl_training/rl_fsk_maxbaud.json \
      > /tmp/hub-hidden.log 2>&1 &
    
    Wait for iter 500. Record wall-clock between iter 50 and iter 500:
    grep -oE "\[[0-9:.]+\].*\[(50|500)/" /tmp/hub-hidden.log
    
  3. Visible run — repeat, keep the window in focus on the active desktop. Log to /tmp/hub-visible.log.
  4. Compute delta = abs(visible_rate - hidden_rate) / hidden_rate. The spec target is delta < 0.05.

Repeat for rl_mcfsk_maxbaud.json and rl_dpsk_maxbaud.json — heavier codecs surface regressions that FSK alone hides.

When the spec doesn't help

The decoupling spec moves work off the Qt event loop. Symptoms that persist after the fix:

  • Codec worker thread saturated — the codec's processRx is the bottleneck, not the GUI thread. Profile with perf against BanterClient::onTxTick and processRx.
  • PipeWire reconnect storms — if pactl list short sinks shows your device flapping between RUNNING and SUSPENDED, the audio server itself is unstable. See diagnose-audio-stall for the recovery checklist.
  • Audio device unrecoverable — see diagnose-audio-unhealthy-banner. If the red banner appeared, BanterAutoTest has paused iters waiting for audio to come back.

Reference