The jitter test fed a receive timestamp that went backwards (1001.02 then 1000.97), putting the second reading 1.03 s from the prediction — twice the threshold, so not jitter under any reading. That is a digit slip for 1001.97. It had been worked around by making the threshold one-sided, which passes the test but never fires when the producer's clock steps forward: the prediction stays ahead of the wall clock, the error stays negative, and the trace sits in the future for the rest of the run. Restored std::fabs, corrected the test data, and added the forward-jump case that the one-sided version silently failed. Plan amended so the bad data does not come back. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
75 lines
2.4 KiB
C++
75 lines
2.4 KiB
C++
#include "TimeBase.h"
|
|
|
|
#include <cmath>
|
|
|
|
namespace udpscope {
|
|
|
|
/* UDPS_T_UINT64 == 6 in Common/UDP/UDPSProtocol.h. Spelled numerically so this
|
|
* translation unit stays free of the C client header. */
|
|
static constexpr uint8_t kTypeUint64 = 6u;
|
|
|
|
double TimeSignalScale(uint8_t typeCode) {
|
|
return (typeCode == kTypeUint64) ? 1.0e-9 : 1.0e-6;
|
|
}
|
|
|
|
double ClockOffset::map(double producerSec, double wallSec) {
|
|
/* Symmetric on purpose. Delivery jitter of a few tens of ms either side of
|
|
* the prediction must not reset the offset or the whole trace wobbles, but a
|
|
* producer clock that steps in EITHER direction has to be picked up: a
|
|
* restart leaves the prediction behind the wall clock, an NTP correction on
|
|
* the producer's host leaves it ahead. A one-sided test silently never fires
|
|
* for the second case and the trace sits in the future for the whole run. */
|
|
if (!valid_ || std::fabs(wallSec - (offset_ + producerSec)) > kRecalibThresholdS) {
|
|
offset_ = wallSec - producerSec;
|
|
valid_ = true;
|
|
}
|
|
return offset_ + producerSec;
|
|
}
|
|
|
|
void HrtRateFit::reset() {
|
|
samples_.clear();
|
|
n_ = 0;
|
|
rate_ = 0.0;
|
|
}
|
|
|
|
void HrtRateFit::add(uint64_t hrt, double wallSec) {
|
|
samples_.push_back(Sample{static_cast<double>(hrt), wallSec});
|
|
if (samples_.size() > kWindow) { samples_.pop_front(); }
|
|
n_++;
|
|
if (n_ >= kMinSamples) { refit(); }
|
|
}
|
|
|
|
void HrtRateFit::refit() {
|
|
const size_t n = samples_.size();
|
|
if (n < 2) { return; }
|
|
|
|
/* Least squares slope of hrt against wall time. Both are subtracted from
|
|
* their first value first: raw hrt counts and epoch seconds are large
|
|
* enough that the naive sums lose precision. */
|
|
const double h0 = samples_.front().hrt;
|
|
const double w0 = samples_.front().wall;
|
|
|
|
double sw = 0.0, sh = 0.0, sww = 0.0, swh = 0.0;
|
|
for (const Sample& s : samples_) {
|
|
const double w = s.wall - w0;
|
|
const double h = s.hrt - h0;
|
|
sw += w;
|
|
sh += h;
|
|
sww += w * w;
|
|
swh += w * h;
|
|
}
|
|
const double dn = static_cast<double>(n);
|
|
const double denom = dn * sww - sw * sw;
|
|
if (std::fabs(denom) < 1e-12) { return; }
|
|
|
|
const double slope = (dn * swh - sw * sh) / denom;
|
|
if (slope > 0.0 && std::isfinite(slope)) { rate_ = slope; }
|
|
}
|
|
|
|
double HrtRateFit::toSeconds(uint64_t hrt) const {
|
|
if (rate_ <= 0.0) { return 0.0; }
|
|
return static_cast<double>(hrt) / rate_;
|
|
}
|
|
|
|
} /* namespace udpscope */
|