-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathASDP_ClockSynchronizer.cpp
More file actions
358 lines (330 loc) · 13.6 KB
/
Copy pathASDP_ClockSynchronizer.cpp
File metadata and controls
358 lines (330 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*
* Copyright (C) 2024: Arizona Board of Regents on Behalf of the University of Arizona
*/
#include "ASDP_ClockSynchronizer.h"
#include <utility>
#include <vector>
#include <iostream>
using namespace asdp;
ClockSynchronizer::ClockSynchronizer(std::shared_ptr<Timer> timer, Time transmissionDelay)
: m_timer(timer)
, m_transmissionDelay(transmissionDelay.seconds * int64_t(1000000) + transmissionDelay.microseconds)
{
}
void ClockSynchronizer::ClearHistory()
{
m_offsetsInBlock.clear();
m_BlockOffsets.clear();
}
int64_t ClockSynchronizer::ComputeOffsetMicroseconds(Time serverTime, const std::chrono::steady_clock::time_point localTime) const
{
int64_t localTimeMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(localTime.time_since_epoch()).count();
int64_t serverTimeMicroseconds = serverTime.seconds * int64_t(1000000) + serverTime.microseconds;
// The transmission delay caused an increase in our local time, which was subtracted from the server time.
// We add back in the expected transmission delay to get the actual offset.
return serverTimeMicroseconds - localTimeMicroseconds + m_transmissionDelay;
}
// Function to perform least squares fit
static std::pair<double, double> leastSquaresFit(const std::vector<std::pair<double, double>>& points)
{
double sumX = 0.0, sumY = 0.0, sumXY = 0.0, sumX2 = 0.0;
int n = points.size();
for (const auto& point : points) {
double x = point.first;
double y = point.second;
sumX += x;
sumY += y;
sumXY += x * y;
sumX2 += x * x;
}
double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
double intercept = (sumY - slope * sumX) / n;
return { slope, intercept };
}
bool ClockSynchronizer::AddDataPoint(Time serverTime, const std::chrono::steady_clock::time_point localTime)
{
// Compute the offset and push it into the current block.
int64_t offsetMicroseconds = ComputeOffsetMicroseconds(serverTime, localTime);
m_offsetsInBlock.push_back(offsetMicroseconds);
// Find the maximum offset in the current block.
int64_t maxBlockOffsetMicroseconds = offsetMicroseconds;
for (int64_t o : m_offsetsInBlock) {
if (o > maxBlockOffsetMicroseconds) {
maxBlockOffsetMicroseconds = o;
}
}
// If we are the maximum in the block, record our time as the maximum offset.
// This will be true for the first entry in the block, and may be true for subsequent entries.
if (maxBlockOffsetMicroseconds == offsetMicroseconds) {
m_blockMaxOffsetTime = localTime;
}
// If the current block is full, add it to the history and clear it.
if (m_offsetsInBlock.size() >= 100) {
m_BlockOffsets.push_back({ maxBlockOffsetMicroseconds, m_blockMaxOffsetTime });
m_offsetsInBlock.clear();
}
// Find the maximum of all the current blocks; if there are fewer than 10, we'll use this to set the offset.
int64_t maxOffset = maxBlockOffsetMicroseconds;
for (auto const &block : m_BlockOffsets) {
if (block.maxOffset > maxOffset) {
maxOffset = block.maxOffset;
}
}
// If we have at least 10 blocks, compute the least-squares line fit to the whole set of blocks,
// indexed by time before now -- the intercept is the offset.
if (m_BlockOffsets.size() >= 10) {
// Make a vector of (X,Y) pairs, where X is the time in microseconds between the max time in the block
// and the current time and Y is the difference between maxOffset and the current offset (measured now).
// This gives us the best baseline for both of these values, keeping them near zero.
std::vector<std::pair<double, double>> points;
for (auto const &block : m_BlockOffsets) {
int64_t time = std::chrono::duration_cast<std::chrono::microseconds>(localTime - block.maxOffsetTime).count();
points.push_back({static_cast<double>(time), static_cast<double>(block.maxOffset - offsetMicroseconds)});
}
// Compute the least-squares line fit. Add its intercept to the current offset. This is the expected
// difference between the server and local times at differential time zero (which is now).
std::pair<double, double> fit = leastSquaresFit(points);
maxOffset = offsetMicroseconds + fit.second;
}
// If we have more than 100 blocks, drop the oldest one.
while (m_BlockOffsets.size() > 100) {
m_BlockOffsets.pop_front();
}
// Set the timer offset based on what we determined it to be above.
Status status = OKAY;
if (maxOffset < 0) {
status = m_timer->SetCoreNegativeOffset(Time( (-maxOffset) / 1000000, (-maxOffset) % 1000000));
if (status != OKAY) { return false; }
status = m_timer->SetCorePositiveOffset(Time(0, 0));
} else {
status = m_timer->SetCorePositiveOffset(Time(maxOffset / 1000000, maxOffset % 1000000));
if (status != OKAY) { return false; }
status = m_timer->SetCoreNegativeOffset(Time(0, 0));
}
return status == OKAY;
}
std::string ClockSynchronizer::Test()
{
// Test leastSquaresFit
{
std::vector<std::pair<double, double>> points = {
{1.0, 2.0},
{2.0, 3.0},
{3.0, 4.0},
{4.0, 5.0},
{5.0, 6.0}
};
auto fit = leastSquaresFit(points);
if (fit.first != 1.0 || fit.second != 1.0) {
return "leastSquaresFit failed: slope = " + std::to_string(fit.first) + ", intercept = " + std::to_string(fit.second);
}
}
// Test the initial offset computation for a single positive and single negative offset.
{
Timer* tPtr = new Timer;
std::shared_ptr<Timer> timer(tPtr);
ClockSynchronizer synchronizer(timer, Time(0,0));
auto now = std::chrono::steady_clock::now();
double nowSeconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
Time positive(nowSeconds + 1, 0);
if (!synchronizer.AddDataPoint(positive, now)) {
return "Initial positive offset AddDataPoint() failed";
}
Time coreTime;
Status status = timer->GetCoreTime(coreTime, now);
if (status != OKAY) {
return "Initial positive offset GetCoreTime() failed";
}
// We should have truncated the microseoconds to 0 and increased the seconds by 1.
if ((coreTime.seconds != nowSeconds + 1) || (coreTime.microseconds != 0)) {
return "Initial positive offset failed";
}
}
{
Timer* tPtr = new Timer;
std::shared_ptr<Timer> timer(tPtr);
ClockSynchronizer synchronizer(timer, Time(0, 0));
auto now = std::chrono::steady_clock::now();
double nowSeconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
Time negative(nowSeconds - 1, 0);
if (!synchronizer.AddDataPoint(negative, now)) {
return "Initial negative offset AddDataPoint() failed";
}
Time coreTime;
Status status = timer->GetCoreTime(coreTime, now);
if (status != OKAY) {
return "Initial negative offset GetCoreTime() failed";
}
// We should have truncated the microseoconds to 0 and increased the seconds by 1.
if ((coreTime.seconds != nowSeconds - 1) || (coreTime.microseconds != 0)) {
return "Initial negative offset failed";
}
}
// Test the use of a transmission delay.
{
Timer* tPtr = new Timer;
std::shared_ptr<Timer> timer(tPtr);
ClockSynchronizer synchronizer(timer, Time(3, 0));
auto now = std::chrono::steady_clock::now();
double nowSeconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
Time positive(nowSeconds + 1, 0);
if (!synchronizer.AddDataPoint(positive, now)) {
return "Transmission delay AddDataPoint() failed";
}
Time coreTime;
Status status = timer->GetCoreTime(coreTime, now);
if (status != OKAY) {
return "Transmission delay GetCoreTime() failed";
}
// We should have increased the seconds by a net of 4 (+1 then +3 for transmission delay).
if (coreTime.seconds != nowSeconds + 4) {
return "Transmission delay failed";
}
}
// Test adding ten points and ensure that we're using the most-positive offset.
{
Timer* tPtr = new Timer;
std::shared_ptr<Timer> timer(tPtr);
ClockSynchronizer synchronizer(timer, Time(0, 0));
auto now = std::chrono::steady_clock::now();
double nowSeconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
Time positive(nowSeconds + 1, 0);
Time negative(nowSeconds - 1, 0);
// Fill in the negative offset on the first point, then positive on the rest.
if (!synchronizer.AddDataPoint(negative, now)) {
return "Ten points AddDataPoint() failed";
}
for (int i = 0; i < 9; i++) {
if (!synchronizer.AddDataPoint(positive, now)) {
return "Ten points AddDataPoint() failed";
}
}
Time coreTime;
Status status = timer->GetCoreTime(coreTime, now);
if (status != OKAY) {
return "Ten points GetCoreTime() failed";
}
// We should have increased the seconds by 1.
if (coreTime.seconds != nowSeconds + 1) {
return "Ten points failed";
}
}
// Test adding 900 points (9 blocks) and ensure that we're using the most-positive offset.
{
Timer* tPtr = new Timer;
std::shared_ptr<Timer> timer(tPtr);
ClockSynchronizer synchronizer(timer, Time(0, 0));
auto now = std::chrono::steady_clock::now();
double nowSeconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
Time positive(nowSeconds + 1, 0);
Time negative(nowSeconds - 1, 0);
// Fill in the negative offset on the first point, then positive on the rest.
if (!synchronizer.AddDataPoint(negative, now)) {
return "900 points AddDataPoint() failed";
}
for (int i = 0; i < 899; i++) {
if (!synchronizer.AddDataPoint(positive, now)) {
return "900 points AddDataPoint() failed";
}
}
Time coreTime;
Status status = timer->GetCoreTime(coreTime, now);
if (status != OKAY) {
return "900 points GetCoreTime() failed";
}
// We should have increased the seconds by 1.
if (coreTime.seconds != nowSeconds + 1) {
return "900 points failed";
}
}
// Test adding 50000 points in groups of 100 blocks, with the insertion time and server time
// increasing by 1 second each block. Then add a final point that is not on that line and make
// sure that the result is on the line.
{
Timer* tPtr = new Timer;
std::shared_ptr<Timer> timer(tPtr);
ClockSynchronizer synchronizer(timer, Time(0, 0));
auto now = std::chrono::steady_clock::now();
double nowSeconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
Time serverTime(nowSeconds, 0);
for (int i = 0; i < 50; i++) {
for (int j = 0; j < 100; j++) {
if (!synchronizer.AddDataPoint(serverTime, now)) {
return "50000 points AddDataPoint() failed";
}
}
serverTime.seconds++;
now += std::chrono::seconds(1);
}
// Add a point that is below the line.
if (serverTime.seconds < 25000) {
serverTime.seconds = 0;
} else {
serverTime.seconds -= 25000;
}
if (!synchronizer.AddDataPoint(serverTime, now)) {
return "50000 points AddDataPoint() failed";
}
Time coreTime;
auto testTime = now;
double testSeconds = std::chrono::duration_cast<std::chrono::seconds>(testTime.time_since_epoch()).count();
Status status = timer->GetCoreTime(coreTime, testTime);
if (status != OKAY) {
return "50000 points GetCoreTime() failed";
}
if (synchronizer.m_BlockOffsets.size() != 50) {
return "50000 points had unexpected number of blocks";
}
// The time should match.
if (coreTime.seconds != testSeconds) {
return "50000 points failed: got "+std::to_string(coreTime.seconds)+", expected "+std::to_string(testSeconds);
}
// Add a point that is above the line.
if (serverTime.seconds < 25000) {
serverTime.seconds = 0;
} else {
serverTime.seconds -= 25000;
}
if (!synchronizer.AddDataPoint(serverTime, now)) {
return "50000 points AddDataPoint() failed";
}
testTime = now;
testSeconds = std::chrono::duration_cast<std::chrono::seconds>(testTime.time_since_epoch()).count();
status = timer->GetCoreTime(coreTime, testTime);
if (status != OKAY) {
return "50000 points GetCoreTime() failed";
}
if (synchronizer.m_BlockOffsets.size() != 50) {
return "50000 points had unexpected number of blocks";
}
// The time should match.
if (coreTime.seconds != testSeconds) {
return "50000 points failed: got " + std::to_string(coreTime.seconds) + ", expected " + std::to_string(testSeconds);
}
}
// Add 200*100 points, which should overflow the 100 block limit and make sure we have 100 blocks.
{
{
Timer* tPtr = new Timer;
std::shared_ptr<Timer> timer(tPtr);
ClockSynchronizer synchronizer(timer, Time(0, 0));
auto now = std::chrono::steady_clock::now();
double nowSeconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
Time serverTime(nowSeconds, 0);
for (int i = 0; i < 200; i++) {
for (int j = 0; j < 100; j++) {
if (!synchronizer.AddDataPoint(serverTime, now)) {
return "200000 points AddDataPoint() failed";
}
}
serverTime.seconds++;
now += std::chrono::seconds(1);
}
if (synchronizer.m_BlockOffsets.size() != 100) {
return "200000 points had unexpected number of blocks";
}
}
}
// Everything worked.
return "";
}