]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
Add fallback drifts
authorMiroslav Lichvar <mlichvar@redhat.com>
Fri, 9 Apr 2010 12:29:11 +0000 (14:29 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Tue, 27 Apr 2010 12:27:05 +0000 (14:27 +0200)
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.

chrony.texi
conf.c
conf.h
reference.c

index a22e6790b8a64c532420dca03abf98936f3e4280..01be63538b0ae556d1976212dbf8f16cb9abec46 100644 (file)
@@ -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
diff --git a/conf.c b/conf.c
index 5942ca421ccd5a4c1f527987778ce52eb76ca377..d1d075845b513cd6ee70311fcb36b6f05d67cb53 100644 (file)
--- a/conf.c
+++ b/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)
 {
diff --git a/conf.h b/conf.h
index 480bfed1d1fd31dfaae8c750a6f4687fdc6fce52..1ec99f2ea0072ab4a9236340f2bf5365b721fa6a 100644 (file)
--- a/conf.h
+++ b/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);
index dc705d9cff34f2a57d8bde5f4759b3d34c8279db..df31420bfe39b89b8964103da58dce65929087eb 100644 (file)
@@ -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,