]> git.ipfire.org Git - thirdparty/chrony.git/blame - samplefilt.c
test: update hwclock unit test
[thirdparty/chrony.git] / samplefilt.c
CommitLineData
c498c21f
ML
1/*
2 chronyd/chronyc - Programs for keeping computer clocks accurate.
3
4 **********************************************************************
5 * Copyright (C) Miroslav Lichvar 2009-2011, 2014, 2016, 2018
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of version 2 of the GNU General Public License as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 **********************************************************************
21
22 =======================================================================
23
24 Routines implementing a median sample filter.
25
26 */
27
28#include "config.h"
29
30#include "local.h"
31#include "logging.h"
32#include "memory.h"
33#include "regress.h"
34#include "samplefilt.h"
35#include "util.h"
36
37#define MIN_SAMPLES 1
38#define MAX_SAMPLES 256
39
40struct SPF_Instance_Record {
41 int min_samples;
42 int max_samples;
43 int index;
44 int used;
45 int last;
46 int avg_var_n;
47 double avg_var;
48 double max_var;
49 double combine_ratio;
50 NTP_Sample *samples;
51 int *selected;
52 double *x_data;
53 double *y_data;
54 double *w_data;
55};
56
57/* ================================================== */
58
59SPF_Instance
60SPF_CreateInstance(int min_samples, int max_samples, double max_dispersion, double combine_ratio)
61{
62 SPF_Instance filter;
63
64 filter = MallocNew(struct SPF_Instance_Record);
65
66 min_samples = CLAMP(MIN_SAMPLES, min_samples, MAX_SAMPLES);
67 max_samples = CLAMP(MIN_SAMPLES, max_samples, MAX_SAMPLES);
68 max_samples = MAX(min_samples, max_samples);
69 combine_ratio = CLAMP(0.0, combine_ratio, 1.0);
70
71 filter->min_samples = min_samples;
72 filter->max_samples = max_samples;
73 filter->index = -1;
74 filter->used = 0;
75 filter->last = -1;
76 /* Set the first estimate to the system precision */
77 filter->avg_var_n = 0;
78 filter->avg_var = LCL_GetSysPrecisionAsQuantum() * LCL_GetSysPrecisionAsQuantum();
0b709ab1 79 filter->max_var = SQUARE(max_dispersion);
c498c21f
ML
80 filter->combine_ratio = combine_ratio;
81 filter->samples = MallocArray(NTP_Sample, filter->max_samples);
82 filter->selected = MallocArray(int, filter->max_samples);
83 filter->x_data = MallocArray(double, filter->max_samples);
84 filter->y_data = MallocArray(double, filter->max_samples);
85 filter->w_data = MallocArray(double, filter->max_samples);
86
87 return filter;
88}
89
90/* ================================================== */
91
92void
93SPF_DestroyInstance(SPF_Instance filter)
94{
95 Free(filter->samples);
96 Free(filter->selected);
97 Free(filter->x_data);
98 Free(filter->y_data);
99 Free(filter->w_data);
100 Free(filter);
101}
102
103/* ================================================== */
104
105void
106SPF_AccumulateSample(SPF_Instance filter, NTP_Sample *sample)
107{
108 filter->index++;
109 filter->index %= filter->max_samples;
110 filter->last = filter->index;
111 if (filter->used < filter->max_samples)
112 filter->used++;
113
114 filter->samples[filter->index] = *sample;
115
116 DEBUG_LOG("filter sample %d t=%s offset=%.9f peer_disp=%.9f",
117 filter->index, UTI_TimespecToString(&sample->time),
118 sample->offset, sample->peer_dispersion);
119}
120
121/* ================================================== */
122
123int
124SPF_GetLastSample(SPF_Instance filter, NTP_Sample *sample)
125{
126 if (filter->last < 0)
127 return 0;
128
129 *sample = filter->samples[filter->last];
130 return 1;
131}
132
133/* ================================================== */
134
135int
136SPF_GetNumberOfSamples(SPF_Instance filter)
137{
138 return filter->used;
139}
140
141/* ================================================== */
142
143double
144SPF_GetAvgSampleDispersion(SPF_Instance filter)
145{
146 return sqrt(filter->avg_var);
147}
148
149/* ================================================== */
150
151void
152SPF_DropSamples(SPF_Instance filter)
153{
154 filter->index = -1;
155 filter->used = 0;
156}
157
158/* ================================================== */
159
160static const NTP_Sample *tmp_sort_samples;
161
162static int
163compare_samples(const void *a, const void *b)
164{
165 const NTP_Sample *s1, *s2;
166
167 s1 = &tmp_sort_samples[*(int *)a];
168 s2 = &tmp_sort_samples[*(int *)b];
169
170 if (s1->offset < s2->offset)
171 return -1;
172 else if (s1->offset > s2->offset)
173 return 1;
174 return 0;
175}
176
177/* ================================================== */
178
179static int
180select_samples(SPF_Instance filter)
181{
182 int i, j, k, o, from, to, *selected;
183 double min_dispersion;
184
185 if (filter->used < filter->min_samples)
186 return 0;
187
188 selected = filter->selected;
189
190 /* With 4 or more samples, select those that have peer dispersion smaller
191 than 1.5x of the minimum dispersion */
192 if (filter->used > 4) {
193 for (i = 1, min_dispersion = filter->samples[0].peer_dispersion; i < filter->used; i++) {
194 if (min_dispersion > filter->samples[i].peer_dispersion)
195 min_dispersion = filter->samples[i].peer_dispersion;
196 }
197
198 for (i = j = 0; i < filter->used; i++) {
199 if (filter->samples[i].peer_dispersion <= 1.5 * min_dispersion)
200 selected[j++] = i;
201 }
202 } else {
203 j = 0;
204 }
205
206 if (j < 4) {
207 /* Select all samples */
208
209 for (j = 0; j < filter->used; j++)
210 selected[j] = j;
211 }
212
213 /* And sort their indices by offset */
214 tmp_sort_samples = filter->samples;
215 qsort(selected, j, sizeof (int), compare_samples);
216
217 /* Select samples closest to the median */
218 if (j > 2) {
219 from = j * (1.0 - filter->combine_ratio) / 2.0;
220 from = CLAMP(1, from, (j - 1) / 2);
221 } else {
222 from = 0;
223 }
224
225 to = j - from;
226
227 /* Mark unused samples and sort the rest by their time */
228
229 o = filter->used - filter->index - 1;
230
231 for (i = 0; i < from; i++)
232 selected[i] = -1;
233 for (; i < to; i++)
234 selected[i] = (selected[i] + o) % filter->used;
235 for (; i < filter->used; i++)
236 selected[i] = -1;
237
238 for (i = from; i < to; i++) {
239 j = selected[i];
240 selected[i] = -1;
241 while (j != -1 && selected[j] != j) {
242 k = selected[j];
243 selected[j] = j;
244 j = k;
245 }
246 }
247
248 for (i = j = 0, k = -1; i < filter->used; i++) {
249 if (selected[i] != -1)
250 selected[j++] = (selected[i] + filter->used - o) % filter->used;
251 }
252
253 assert(j > 0 && j <= filter->max_samples);
254
255 return j;
256}
257
258/* ================================================== */
259
260static int
261combine_selected_samples(SPF_Instance filter, int n, NTP_Sample *result)
262{
263 double mean_peer_dispersion, mean_root_dispersion, mean_peer_delay, mean_root_delay;
264 double mean_x, mean_y, disp, var, prev_avg_var;
265 NTP_Sample *sample, *last_sample;
266 int i, dof;
267
268 last_sample = &filter->samples[filter->selected[n - 1]];
269
270 /* Prepare data */
271 for (i = 0; i < n; i++) {
272 sample = &filter->samples[filter->selected[i]];
273
274 filter->x_data[i] = UTI_DiffTimespecsToDouble(&sample->time, &last_sample->time);
275 filter->y_data[i] = sample->offset;
276 filter->w_data[i] = sample->peer_dispersion;
277 }
278
279 /* Calculate mean offset and interval since the last sample */
280 for (i = 0, mean_x = mean_y = 0.0; i < n; i++) {
281 mean_x += filter->x_data[i];
282 mean_y += filter->y_data[i];
283 }
284 mean_x /= n;
285 mean_y /= n;
286
287 if (n >= 4) {
288 double b0, b1, s2, sb0, sb1;
289
290 /* Set y axis to the mean sample time */
291 for (i = 0; i < n; i++)
292 filter->x_data[i] -= mean_x;
293
294 /* Make a linear fit and use the estimated standard deviation of the
295 intercept as dispersion */
296 RGR_WeightedRegression(filter->x_data, filter->y_data, filter->w_data, n,
297 &b0, &b1, &s2, &sb0, &sb1);
298 var = s2;
299 disp = sb0;
300 dof = n - 2;
301 } else if (n >= 2) {
302 for (i = 0, disp = 0.0; i < n; i++)
303 disp += (filter->y_data[i] - mean_y) * (filter->y_data[i] - mean_y);
304 var = disp / (n - 1);
305 disp = sqrt(var);
306 dof = n - 1;
307 } else {
308 var = filter->avg_var;
309 disp = sqrt(var);
310 dof = 1;
311 }
312
313 /* Avoid working with zero dispersion */
314 if (var < 1e-20) {
315 var = 1e-20;
316 disp = sqrt(var);
317 }
318
319 /* Drop the sample if the variance is larger than the maximum */
320 if (filter->max_var > 0.0 && var > filter->max_var) {
321 DEBUG_LOG("filter dispersion too large disp=%.9f max=%.9f",
322 sqrt(var), sqrt(filter->max_var));
323 return 0;
324 }
325
326 prev_avg_var = filter->avg_var;
327
328 /* Update the exponential moving average of the variance */
329 if (filter->avg_var_n > 50) {
330 filter->avg_var += dof / (dof + 50.0) * (var - filter->avg_var);
331 } else {
332 filter->avg_var = (filter->avg_var * filter->avg_var_n + var * dof) /
333 (dof + filter->avg_var_n);
334 if (filter->avg_var_n == 0)
335 prev_avg_var = filter->avg_var;
336 filter->avg_var_n += dof;
337 }
338
339 /* Use the long-term average of variance instead of the estimated value
340 unless it is significantly smaller in order to reduce the noise in
341 sourcestats weights */
342 if (var * dof / RGR_GetChi2Coef(dof) < prev_avg_var)
343 disp = sqrt(filter->avg_var) * disp / sqrt(var);
344
345 mean_peer_dispersion = mean_root_dispersion = mean_peer_delay = mean_root_delay = 0.0;
346
347 for (i = 0; i < n; i++) {
348 sample = &filter->samples[filter->selected[i]];
349
350 mean_peer_dispersion += sample->peer_dispersion;
351 mean_root_dispersion += sample->root_dispersion;
352 mean_peer_delay += sample->peer_delay;
353 mean_root_delay += sample->root_delay;
354 }
355
356 mean_peer_dispersion /= n;
357 mean_root_dispersion /= n;
358 mean_peer_delay /= n;
359 mean_root_delay /= n;
360
361 UTI_AddDoubleToTimespec(&last_sample->time, mean_x, &result->time);
362 result->offset = mean_y;
363 result->peer_dispersion = MAX(disp, mean_peer_dispersion);
364 result->root_dispersion = MAX(disp, mean_root_dispersion);
365 result->peer_delay = mean_peer_delay;
366 result->root_delay = mean_root_delay;
367 result->stratum = last_sample->stratum;
368 result->leap = last_sample->leap;
369
370 return 1;
371}
372
373/* ================================================== */
374
375int
376SPF_GetFilteredSample(SPF_Instance filter, NTP_Sample *sample)
377{
378 int n;
379
380 n = select_samples(filter);
381
382 if (n < 1)
383 return 0;
384
385 if (!combine_selected_samples(filter, n, sample))
386 return 0;
387
388 SPF_DropSamples(filter);
389
390 return 1;
391}
392
393/* ================================================== */
394
395void
396SPF_SlewSamples(SPF_Instance filter, struct timespec *when, double dfreq, double doffset)
397{
398 int i, first, last;
399 double delta_time;
400
401 if (filter->last < 0)
402 return;
403
404 /* Always slew the last sample as it may be returned even if no new
405 samples were accumulated */
406 if (filter->used > 0) {
407 first = 0;
408 last = filter->used - 1;
409 } else {
410 first = last = filter->last;
411 }
412
413 for (i = first; i <= last; i++) {
414 UTI_AdjustTimespec(&filter->samples[i].time, when, &filter->samples[i].time,
415 &delta_time, dfreq, doffset);
416 filter->samples[i].offset -= delta_time;
417 }
418}
419
420/* ================================================== */
421
422void
423SPF_AddDispersion(SPF_Instance filter, double dispersion)
424{
425 int i;
426
427 for (i = 0; i < filter->used; i++) {
428 filter->samples[i].peer_dispersion += dispersion;
429 filter->samples[i].root_dispersion += dispersion;
430 }
431}