Add fallback drifts
Fallback drifts are long-term averages of the system clock drift calculated over exponentially increasing intervals. They are used when the clock is unsynchronised to avoid quickly drifting away from true time if there was a short-term deviation in drift before the synchronisation was lost.
This commit is contained in:
parent
99d18abf59
commit
f12bc10917
4 changed files with 217 additions and 0 deletions
29
chrony.texi
29
chrony.texi
|
@ -1175,6 +1175,7 @@ directives can occur in any order in the file.
|
|||
* driftfile directive:: Specify location of file containing drift data
|
||||
* dumpdir directive:: Specify directory for dumping measurements
|
||||
* dumponexit directive:: Dump measurements when daemon exits
|
||||
* fallbackdrift directive:: Specify fallback drift intervals
|
||||
* initstepslew directive:: Trim the system clock on boot-up.
|
||||
* keyfile directive:: Specify location of file containing keys
|
||||
* linux_hz directive:: Define a non-standard value of the kernel HZ constant
|
||||
|
@ -1563,6 +1564,34 @@ If this command is present, it indicates that @code{chronyd} should save
|
|||
the measurement history for each of its time sources recorded whenever
|
||||
the program exits. (See the dumpdir command above).
|
||||
@c }}}
|
||||
@c {{{ fallbackdrift
|
||||
@node fallbackdrift directive
|
||||
@subsection fallbackdrift
|
||||
Fallback drifts are long-term averages of the system clock drift
|
||||
calculated over exponentially increasing intervals. They are used
|
||||
when the clock is unsynchronised to avoid quickly drifting away from
|
||||
true time if there was a short-term deviation in drift before the
|
||||
synchronisation was lost.
|
||||
|
||||
The directive specifies the minimum and maximum interval for how long
|
||||
the system clock has to be unsynchronised to switch between fallback
|
||||
drifts. They are defined as a power of 2 (in seconds). The syntax is
|
||||
as follows
|
||||
|
||||
@example
|
||||
fallbackdrift 16 19
|
||||
@end example
|
||||
|
||||
In this example, the minimum interval is 16 (18 hours) and maximum
|
||||
interval is 19 (6 days). The system clock frequency will be set to
|
||||
the first fallback 18 hours after the synchronisation was lost, to the
|
||||
second after 36 hours, etc. This might be a good setting to cover
|
||||
daily and weekly temperature fluctuations.
|
||||
|
||||
By default (or if the specified maximum or minimum is 0), no fallbacks
|
||||
will be used and the clock frequency will stay at the last value
|
||||
calculated before synchronisation was lost.
|
||||
@c }}}
|
||||
@c {{{ initstepslew
|
||||
@node initstepslew directive
|
||||
@subsection initstepslew
|
||||
|
|
25
conf.c
25
conf.c
|
@ -92,6 +92,7 @@ static void parse_cmdport(const char *);
|
|||
static void parse_rtconutc(const char *);
|
||||
static void parse_noclientlog(const char *);
|
||||
static void parse_clientloglimit(const char *);
|
||||
static void parse_fallbackdrift(const char *);
|
||||
static void parse_makestep(const char *);
|
||||
static void parse_logchange(const char *);
|
||||
static void parse_mailonchange(const char *);
|
||||
|
@ -166,6 +167,10 @@ static int no_client_log = 0;
|
|||
/* Limit memory allocated for the clients log */
|
||||
static unsigned long client_log_limit = 524288;
|
||||
|
||||
/* Minimum and maximum fallback drift intervals */
|
||||
static int fb_drift_min = 0;
|
||||
static int fb_drift_max = 0;
|
||||
|
||||
/* IP addresses for binding the NTP socket to. UNSPEC family means INADDR_ANY
|
||||
will be used */
|
||||
static IPAddr bind_address4, bind_address6;
|
||||
|
@ -225,6 +230,7 @@ static const Command commands[] = {
|
|||
{"rtconutc", 8, parse_rtconutc},
|
||||
{"noclientlog", 11, parse_noclientlog},
|
||||
{"clientloglimit", 14, parse_clientloglimit},
|
||||
{"fallbackdrift", 13, parse_fallbackdrift},
|
||||
{"makestep", 8, parse_makestep},
|
||||
{"logchange", 9, parse_logchange},
|
||||
{"mailonchange", 12, parse_mailonchange},
|
||||
|
@ -805,6 +811,16 @@ parse_clientloglimit(const char *line)
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
parse_fallbackdrift(const char *line)
|
||||
{
|
||||
if (sscanf(line, "%d %d", &fb_drift_min, &fb_drift_max) != 2) {
|
||||
LOG(LOGS_WARN, LOGF_Configure, "Could not read fallback drift intervals at line %d", line_number);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
parse_makestep(const char *line)
|
||||
{
|
||||
|
@ -1424,6 +1440,15 @@ CNF_GetClientLogLimit(void)
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
void
|
||||
CNF_GetFallbackDrifts(int *min, int *max)
|
||||
{
|
||||
*min = fb_drift_min;
|
||||
*max = fb_drift_max;
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
void
|
||||
CNF_GetBindAddress(int family, IPAddr *addr)
|
||||
{
|
||||
|
|
1
conf.h
1
conf.h
|
@ -65,6 +65,7 @@ extern void CNF_GetLogChange(int *enabled, double *threshold);
|
|||
extern void CNF_GetMailOnChange(int *enabled, double *threshold, char **user);
|
||||
extern int CNF_GetNoClientLog(void);
|
||||
extern unsigned long CNF_GetClientLogLimit(void);
|
||||
extern void CNF_GetFallbackDrifts(int *min, int *max);
|
||||
extern void CNF_GetBindAddress(int family, IPAddr *addr);
|
||||
extern void CNF_GetBindCommandAddress(int family, IPAddr *addr);
|
||||
extern char *CNF_GetPidFile(void);
|
||||
|
|
162
reference.c
162
reference.c
|
@ -37,6 +37,7 @@
|
|||
#include "conf.h"
|
||||
#include "logging.h"
|
||||
#include "local.h"
|
||||
#include "sched.h"
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
|
@ -91,6 +92,27 @@ static LOG_FileID logfileid;
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
/* Exponential moving averages of absolute clock frequencies
|
||||
used as a fallback when synchronisation is lost. */
|
||||
|
||||
struct fb_drift {
|
||||
double freq;
|
||||
double secs;
|
||||
};
|
||||
|
||||
static int fb_drift_min;
|
||||
static int fb_drift_max;
|
||||
|
||||
static struct fb_drift *fb_drifts = NULL;
|
||||
static int next_fb_drift;
|
||||
static SCH_TimeoutID fb_drift_timeout_id;
|
||||
|
||||
/* Timestamp of last reference update */
|
||||
static struct timeval last_ref_update;
|
||||
static double last_ref_update_interval;
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
void
|
||||
REF_Initialise(void)
|
||||
{
|
||||
|
@ -150,6 +172,18 @@ REF_Initialise(void)
|
|||
CNF_GetLogChange(&do_log_change, &log_change_threshold);
|
||||
CNF_GetMailOnChange(&do_mail_change, &mail_change_threshold, &mail_change_user);
|
||||
|
||||
CNF_GetFallbackDrifts(&fb_drift_min, &fb_drift_max);
|
||||
|
||||
if (fb_drift_max >= fb_drift_min && fb_drift_min > 0) {
|
||||
fb_drifts = MallocArray(struct fb_drift, fb_drift_max - fb_drift_min + 1);
|
||||
memset(fb_drifts, 0, sizeof (struct fb_drift) * (fb_drift_max - fb_drift_min + 1));
|
||||
next_fb_drift = 0;
|
||||
fb_drift_timeout_id = -1;
|
||||
last_ref_update.tv_sec = 0;
|
||||
last_ref_update.tv_usec = 0;
|
||||
last_ref_update_interval = 0;
|
||||
}
|
||||
|
||||
/* And just to prevent anything wierd ... */
|
||||
if (do_log_change) {
|
||||
log_change_threshold = fabs(log_change_threshold);
|
||||
|
@ -167,6 +201,8 @@ REF_Finalise(void)
|
|||
LCL_SetLeap(0);
|
||||
}
|
||||
|
||||
Free(fb_drifts);
|
||||
|
||||
initialised = 0;
|
||||
return;
|
||||
}
|
||||
|
@ -246,6 +282,117 @@ update_drift_file(double freq_ppm, double skew)
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
update_fb_drifts(double freq_ppm, double update_interval)
|
||||
{
|
||||
int i, secs;
|
||||
|
||||
assert(are_we_synchronised);
|
||||
|
||||
if (next_fb_drift > 0) {
|
||||
#if 0
|
||||
/* Reset drifts that were used when we were unsynchronised */
|
||||
for (i = 0; i < next_fb_drift - fb_drift_min; i++)
|
||||
fb_drifts[i].secs = 0.0;
|
||||
#endif
|
||||
next_fb_drift = 0;
|
||||
}
|
||||
|
||||
if (fb_drift_timeout_id != -1) {
|
||||
SCH_RemoveTimeout(fb_drift_timeout_id);
|
||||
fb_drift_timeout_id = -1;
|
||||
}
|
||||
|
||||
if (update_interval < 0.0 || update_interval > last_ref_update_interval * 4.0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < fb_drift_max - fb_drift_min + 1; i++) {
|
||||
/* Don't allow differences larger than 10 ppm */
|
||||
if (fabs(freq_ppm - fb_drifts[i].freq) > 10.0)
|
||||
fb_drifts[i].secs = 0.0;
|
||||
|
||||
secs = 1 << (i + fb_drift_min);
|
||||
if (fb_drifts[i].secs < secs) {
|
||||
/* Calculate average over 2 * secs interval before switching to
|
||||
exponential updating */
|
||||
fb_drifts[i].freq = (fb_drifts[i].freq * fb_drifts[i].secs +
|
||||
update_interval * 0.5 * freq_ppm) / (update_interval * 0.5 + fb_drifts[i].secs);
|
||||
fb_drifts[i].secs += update_interval * 0.5;
|
||||
} else {
|
||||
/* Update exponential moving average. The smoothing factor for update
|
||||
interval equal to secs is about 0.63, for half interval about 0.39,
|
||||
for double interval about 0.86. */
|
||||
fb_drifts[i].freq += (1 - 1.0 / exp(update_interval / secs)) *
|
||||
(freq_ppm - fb_drifts[i].freq);
|
||||
}
|
||||
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Reference, "Fallback drift %d updated: %f ppm %f seconds",
|
||||
i + fb_drift_min, fb_drifts[i].freq, fb_drifts[i].secs);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
fb_drift_timeout(void *arg)
|
||||
{
|
||||
assert(are_we_synchronised == 0);
|
||||
assert(next_fb_drift >= fb_drift_min && next_fb_drift <= fb_drift_max);
|
||||
|
||||
fb_drift_timeout_id = -1;
|
||||
|
||||
LCL_SetAbsoluteFrequency(fb_drifts[next_fb_drift - fb_drift_min].freq);
|
||||
REF_SetUnsynchronised();
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
schedule_fb_drift(struct timeval *now)
|
||||
{
|
||||
int i, c, secs;
|
||||
double unsynchronised;
|
||||
struct timeval when;
|
||||
|
||||
if (fb_drift_timeout_id != -1)
|
||||
return; /* already scheduled */
|
||||
|
||||
UTI_DiffTimevalsToDouble(&unsynchronised, now, &last_ref_update);
|
||||
|
||||
for (c = 0, i = fb_drift_min; i <= fb_drift_max; i++) {
|
||||
secs = 1 << i;
|
||||
|
||||
if (fb_drifts[i - fb_drift_min].secs < secs)
|
||||
continue;
|
||||
|
||||
if (unsynchronised < secs && i > next_fb_drift)
|
||||
break;
|
||||
|
||||
c = i;
|
||||
}
|
||||
|
||||
if (c > next_fb_drift) {
|
||||
LCL_SetAbsoluteFrequency(fb_drifts[c - fb_drift_min].freq);
|
||||
next_fb_drift = c;
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Reference, "Fallback drift %d set", c);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (i <= fb_drift_max) {
|
||||
next_fb_drift = i;
|
||||
UTI_AddDoubleToTimeval(now, secs - unsynchronised, &when);
|
||||
fb_drift_timeout_id = SCH_AddTimeout(&when, fb_drift_timeout, NULL);
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Reference, "Fallback drift %d scheduled", i);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
#define BUFLEN 255
|
||||
#define S_MAX_USER_LEN "128"
|
||||
|
||||
|
@ -487,6 +634,17 @@ REF_SetReference(int stratum,
|
|||
update_drift_file(abs_freq_ppm, our_skew);
|
||||
}
|
||||
|
||||
/* Update fallback drifts */
|
||||
if (fb_drifts) {
|
||||
double update_interval;
|
||||
|
||||
UTI_DiffTimevalsToDouble(&update_interval, ref_time, &last_ref_update);
|
||||
|
||||
update_fb_drifts(abs_freq_ppm, update_interval);
|
||||
last_ref_update = *ref_time;
|
||||
last_ref_update_interval = update_interval;
|
||||
}
|
||||
|
||||
/* And now set the freq and offset to zero */
|
||||
our_frequency = 0.0;
|
||||
our_offset = 0.0;
|
||||
|
@ -545,6 +703,10 @@ REF_SetUnsynchronised(void)
|
|||
|
||||
LCL_ReadCookedTime(&now, NULL);
|
||||
|
||||
if (fb_drifts) {
|
||||
schedule_fb_drift(&now);
|
||||
}
|
||||
|
||||
write_log(&now,
|
||||
"0.0.0.0",
|
||||
0,
|
||||
|
|
Loading…
Reference in a new issue