fix: a clock glitch is bridged and a channel is known by name, so a capture keeps its t0
Two defects behind one report ("a trigger during a capture overrides the first,
and the hold-off does not apply"); the second is also what made a spike in one
channel look like the acquisition resetting.
The trigger engine keyed its invalidation on the trigger channel's *index*, which
every rebuild of the channel list renumbers -- enabling a signal that sorts before
it is enough. Any rebuild therefore abandoned an open capture and set no hold-off,
so the next edge inside the post window became t0. It keys on the channel's name
now, which changes only when the source genuinely does.
A packet whose stamps jump more than 256 packet spans ahead is now rejected as a
fault and *bridged* at the declared cadence, instead of poisoning the monotonic
mark (which pinned every later packet flat at the glitched value) and condemning
the next good packet. Only four consecutive failures abandon the declared time
signal, and that fallback no longer wipes the rings: the arrival clock is anchored
to continue from the monotonic mark, so the history is still on one timeline --
the wipe is what made one glitching packet look like the stream restarting from 0.
The declared unit is resolved before those checks, since they weigh the stamps
against a cadence in seconds.
Guards: UDPSCOPE_PROBE_TRIGRENUM_MS prints t0_held=1 through the real widgets,
and the mock's --time-glitch-at/--time-glitch-by injects the clock fault.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
4003084648
commit
f179fbbb44
13 files changed
+406
-96
No files matched your search
@@ -67,8 +67,15 @@ def build_config(rate, time_mode=TIME_FULL_ARRAY, unit="V"):
|
||||
return header(PKT_CONFIG, 0, len(payload)) + payload
|
||||
|
||||
|
||||
def build_data(counter, t0, dt, batch, freq, time_mode=TIME_FULL_ARRAY, noise=0.0):
|
||||
def build_data(
|
||||
counter, t0, dt, batch, freq, time_mode=TIME_FULL_ARRAY, noise=0.0, time_spike=0.0
|
||||
):
|
||||
stamps = [t0 + i * dt for i in range(batch)]
|
||||
# `time_spike` models a device whose clock glitches for one packet: the stamps jump
|
||||
# far ahead and then the next packet carries on from where it would have been. The
|
||||
# waveforms are still computed from the *true* stamps, so only the time signal is
|
||||
# wrong -- which is what a glitching clock looks like on the wire.
|
||||
wire_stamps = [t + time_spike for t in stamps] if time_spike > 0.0 else stamps
|
||||
# The interferer is a tone at 100x the fundamental with a randomly scaled amplitude, which
|
||||
# is the shape that is hardest on anything counting crossings: near the sine's own mid level
|
||||
# the fundamental's slope is 2*pi*f while the interferer's is 100x that, so the signal
|
||||
@@ -92,7 +99,9 @@ def build_data(counter, t0, dt, batch, freq, time_mode=TIME_FULL_ARRAY, noise=0.
|
||||
# signal in Accumulate mode still occupies `batch` slots on the wire (the client
|
||||
# reads `num_samples` values for any 1-element signal), so the anchor is repeated --
|
||||
# which is exactly the shape that made udpscope derive a step of zero.
|
||||
wire_time = [t0] * batch if time_mode == TIME_FIRST_SAMPLE else stamps
|
||||
wire_time = (
|
||||
[wire_stamps[0]] * batch if time_mode == TIME_FIRST_SAMPLE else wire_stamps
|
||||
)
|
||||
payload = struct.pack("<QI", int(t0 * 1e9), batch)
|
||||
for arr in (wire_time, sine, square, ramp):
|
||||
payload += struct.pack("<%dd" % batch, *arr)
|
||||
@@ -154,6 +163,20 @@ def main():
|
||||
"grid, so the stream's timestamps wobble about one. Non-cumulative "
|
||||
"(the mean rate is unchanged); 0 = the ideal grid",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--time-glitch-at",
|
||||
type=float,
|
||||
default=-1.0,
|
||||
help="stream time (s) at which ONE packet's time signal jumps ahead by "
|
||||
"--time-glitch-by and then carries on normally: a device whose clock "
|
||||
"glitches. -1 = never",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--time-glitch-by",
|
||||
type=float,
|
||||
default=10.0,
|
||||
help="how far ahead the glitching packet's stamps jump, in stream seconds",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
dt = args.stream_scale / args.rate # stream seconds per sample
|
||||
@@ -181,6 +204,7 @@ def main():
|
||||
|
||||
peer = None
|
||||
counter = 1
|
||||
glitched = False
|
||||
t_stream = 0.0
|
||||
started = None
|
||||
next_send = time.monotonic()
|
||||
@@ -193,6 +217,7 @@ def main():
|
||||
if magic == MAGIC and ptype == PKT_CONNECT:
|
||||
peer = addr
|
||||
counter = 1
|
||||
glitched = False
|
||||
t_stream = 0.0
|
||||
started = time.monotonic()
|
||||
next_send = started
|
||||
@@ -223,9 +248,25 @@ def main():
|
||||
pdt = dt
|
||||
if args.clock_jitter > 0.0:
|
||||
pdt = dt * (1.0 + random.uniform(-args.clock_jitter, args.clock_jitter))
|
||||
spike = 0.0
|
||||
if args.time_glitch_at >= 0.0 and not glitched and t_stream >= args.time_glitch_at:
|
||||
spike = args.time_glitch_by
|
||||
glitched = True
|
||||
print(
|
||||
"mock udpstreamer: time-signal glitch at t=%.3f, +%.3f s for one packet"
|
||||
% (t_stream, spike),
|
||||
flush=True,
|
||||
)
|
||||
sock.sendto(
|
||||
build_data(
|
||||
counter, t_stream, pdt, args.batch, args.freq, time_mode, args.noise
|
||||
counter,
|
||||
t_stream,
|
||||
pdt,
|
||||
args.batch,
|
||||
args.freq,
|
||||
time_mode,
|
||||
args.noise,
|
||||
spike,
|
||||
),
|
||||
peer,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user