Adds TimeBase.h/cpp with ClockOffset (latched wall-clock offset with one-sided recalibration on positive drift only, so early-arriving packets do not wobble the trace) and HrtRateFit (sliding-window OLS that recovers an unknown hrt tick rate from receive timestamps). Also adds TimeSignalScale() which maps UDPS type codes to seconds-per-count. 9 new tests, all 28 pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
73 lines
2.2 KiB
C++
73 lines
2.2 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) {
|
|
/* Recalibrate only when wall is significantly ahead of the prediction.
|
|
* Early-arriving packets (wall < prediction) are normal network jitter and
|
|
* must not reset the offset — doing so would wobble the trace. A large
|
|
* positive error means the producer clock jumped back or restarted. */
|
|
if (!valid_ || (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 */
|