* 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
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
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 *);
/* 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;
{"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},
/* ================================================== */
+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)
{
/* ================================================== */
+void
+CNF_GetFallbackDrifts(int *min, int *max)
+{
+ *min = fb_drift_min;
+ *max = fb_drift_max;
+}
+
+/* ================================================== */
+
void
CNF_GetBindAddress(int family, IPAddr *addr)
{
#include "conf.h"
#include "logging.h"
#include "local.h"
+#include "sched.h"
/* ================================================== */
/* ================================================== */
+/* 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)
{
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);
LCL_SetLeap(0);
}
+ Free(fb_drifts);
+
initialised = 0;
return;
}
/* ================================================== */
+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"
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;
LCL_ReadCookedTime(&now, NULL);
+ if (fb_drifts) {
+ schedule_fb_drift(&now);
+ }
+
write_log(&now,
"0.0.0.0",
0,