Mads
Multi-Agent Distributed System
Loading...
Searching...
No Matches
clock_offset.hpp
Go to the documentation of this file.
1/*
2 ____ _ _ ___ __ __ _
3 / ___| | ___ ___| | __ / _ \ / _|/ _|___ ___| |_
4 | | | |/ _ \ / __| |/ /| | | | |_| |_/ __|/ _ \ __|
5 | |___| | (_) | (__| < | |_| | _| _\__ \ __/ |_
6 \____|_|\___/ \___|_|\_\ \___/|_| |_| |___/\___|\__|
7
8Pure, socket-free clock-offset math and cross-agent consensus, shared by the
9broker exchange (source A) and the bus ping/pong (source B) that measure
10`Agent::clock_offset()`. No ZMQ, no Agent dependency: callers pass in
11timestamps and a `now`, exactly like Mads::TopicStats, so the NTP-style
12four-timestamp estimate, the domain-consensus rule, and the passive skew
13tracker used by `mads top` are all directly unit-testable with synthetic
14values -- no sockets, no real sleeps.
15
16Author(s): Paolo Bosetti
17*/
18#pragma once
19
20#include <chrono>
21#include <cstdint>
22#include <deque>
23#include <map>
24#include <mutex>
25#include <string>
26#include <vector>
27
28namespace Mads {
29
35 int64_t t1 = 0, t2 = 0, t3 = 0, t4 = 0;
38 int64_t local_elapsed_us = 0;
39};
40
42enum class ClockSource { None = 0, Broker = 1, Peer = 2 };
43
52 int64_t offset_us = 0;
53 int64_t delay_us = 0;
54 int64_t jitter_us = 0;
55 size_t samples = 0;
59 uint8_t hops = 0;
63 std::string origin_agent_id;
64 uint64_t seq = 0;
67 std::chrono::steady_clock::time_point measured_at{};
68 bool valid = false;
69
72 std::string clock_ref() const {
73 return origin_agent_id + ":" + std::to_string(seq);
74 }
75};
76
94
101public:
102 explicit ClockOffsetEstimator(size_t keep = 8);
103
106 void add(const ClockSample &s);
107
111
116
117 void reset();
118
119private:
120 int64_t jitter_us() const;
121 size_t _keep;
122 std::vector<ClockOffsetResult> _results;
123};
124
139public:
141 std::chrono::milliseconds stale = std::chrono::seconds(30));
142
148 void record(const std::string &domain, const ClockOffsetResult &r,
149 std::chrono::steady_clock::time_point now =
150 std::chrono::steady_clock::now());
151
154 ClockOffsetResult adopted(const std::string &domain,
155 std::chrono::steady_clock::time_point now =
156 std::chrono::steady_clock::now()) const;
157
162 bool is_winner(const std::string &domain, const std::string &agent_id,
163 std::chrono::steady_clock::time_point now =
164 std::chrono::steady_clock::now()) const;
165
166 void clear();
167
168private:
169 mutable std::mutex _mtx;
170 std::chrono::milliseconds _stale;
171 // domain -> (origin_agent_id -> its latest announced/measured result).
172 std::map<std::string, std::map<std::string, ClockOffsetResult>> _domains;
173};
174
180 std::string host;
184 int64_t min_skew_us = 0;
185 bool has_value = false;
186 size_t samples = 0;
187 std::chrono::steady_clock::time_point last_seen{};
188};
189
208public:
210 std::chrono::milliseconds window = std::chrono::seconds(10));
211
212 void record(const std::string &host, int64_t skew_us,
213 std::chrono::steady_clock::time_point now =
214 std::chrono::steady_clock::now());
215
216 std::vector<HostSkewStat>
217 snapshot(std::chrono::steady_clock::time_point now =
218 std::chrono::steady_clock::now()) const;
219
220 void clear();
221
222private:
223 struct Entry {
224 std::deque<std::pair<std::chrono::steady_clock::time_point, int64_t>>
225 events;
226 std::chrono::steady_clock::time_point last_seen{};
227 };
228
229 mutable std::mutex _mtx;
230 std::chrono::milliseconds _window;
231 std::map<std::string, Entry> _entries;
232};
233
234} // namespace Mads
MADS_EXPORT const char * agent_id(agent_t agent)
Returns the current agent identifier.
Per-clock-domain adoption of the smallest-delay measurement heard from any agent sharing that domain ...
void record(const std::string &domain, const ClockOffsetResult &r, std::chrono::steady_clock::time_point now=std::chrono::steady_clock::now())
bool is_winner(const std::string &domain, const std::string &agent_id, std::chrono::steady_clock::time_point now=std::chrono::steady_clock::now()) const
ClockConsensus(std::chrono::milliseconds stale=std::chrono::seconds(30))
ClockOffsetResult adopted(const std::string &domain, std::chrono::steady_clock::time_point now=std::chrono::steady_clock::now()) const
Accumulates ClockSamples and returns the classic NTP "best of N": the retained sample with the smalle...
void add(const ClockSample &s)
ClockOffsetResult best() const
ClockOffsetResult median() const
ClockOffsetEstimator(size_t keep=8)
Thread-safe sliding-window tracker of per-host clock skew, fed by mads top's passive mode: for every ...
HostSkewStats(std::chrono::milliseconds window=std::chrono::seconds(10))
std::vector< HostSkewStat > snapshot(std::chrono::steady_clock::time_point now=std::chrono::steady_clock::now()) const
void record(const std::string &host, int64_t skew_us, std::chrono::steady_clock::time_point now=std::chrono::steady_clock::now())
Definition agent.hpp:67
ClockOffsetResult estimate(const ClockSample &s)
NTP-style estimate from one four-timestamp exchange.
ClockSource
Which mechanism produced a ClockOffsetResult.
Result of a clock-offset measurement: offset_us added to a clock in the measured domain yields broker...
std::chrono::steady_clock::time_point measured_at
std::string clock_ref() const
One four-timestamp exchange, all fields microseconds since the Unix epoch: t1/t4 read from the initia...
Snapshot of one host's passive clock skew, as computed by HostSkewStats::snapshot() at a given instan...
std::chrono::steady_clock::time_point last_seen