[PATCH] Use fallback drifts when running unsynchronised

[ Thread Index | Date Index | More chrony.tuxfamily.org/chrony-dev Archives ]


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 |   28 ++++++++++
 conf.c      |   25 +++++++++
 conf.h      |    1 +
 reference.c |  160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 214 insertions(+), 0 deletions(-)

diff --git a/chrony.texi b/chrony.texi
index a22e679..c0e2e69 100644
--- a/chrony.texi
+++ b/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,33 @@ 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 14 16
+@end example
+
+In this example, @code{chronyd} will set the system clock frequency to the
+first fallback 4.6 hours after the synchronisation was lost, to the second
+after 9.1 hours and to the third after 18.2 hours.
+
+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.
+
+By default, the minimum interval is 16 (18.2 hours) and maximum interval is
+19 (6 days).
+@c }}}
 @c {{{ initstepslew
 @node initstepslew directive
 @subsection initstepslew
diff --git a/conf.c b/conf.c
index 5942ca4..0fdf773 100644
--- 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 = 16;
+static int fb_drift_max = 19;
+
 /* 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},
@@ -806,6 +812,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)
 {
   if (sscanf(line, "%lf %d", &make_step_threshold, &make_step_limit) != 2) {
@@ -1425,6 +1441,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)
 {
   if (family == IPADDR_INET4)
diff --git a/conf.h b/conf.h
index 480bfed..1ec99f2 100644
--- 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);
diff --git a/reference.c b/reference.c
index 3e85e35..7d9c7fd 100644
--- a/reference.c
+++ b/reference.c
@@ -38,6 +38,7 @@
 #include "logging.h"
 #include "local.h"
 #include "mkdirpp.h"
+#include "sched.h"
 
 /* ================================================== */
 
@@ -96,6 +97,27 @@ static unsigned long logwrites = 0;
 
 /* ================================================== */
 
+/* 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)
 {
@@ -169,6 +191,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);
@@ -190,6 +224,8 @@ REF_Finalise(void)
     fclose(logfile);
   }
 
+  Free(fb_drifts);
+
   initialised = 0;
   return;
 }
@@ -269,6 +305,115 @@ 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);
+
+  /* Reset drifts that were used when we were unsynchronised */
+  if (next_fb_drift > 0) {
+    for (i = 0; i < next_fb_drift - fb_drift_min; i++)
+      fb_drifts[i].secs = 0.0;
+    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"
 
@@ -520,6 +665,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;
@@ -578,6 +734,10 @@ REF_SetUnsynchronised(void)
 
   LCL_ReadCookedTime(&now, NULL);
 
+  if (fb_drifts) {
+    schedule_fb_drift(&now);
+  }
+
   write_log(&now,
             "0.0.0.0",
             0,
-- 
1.6.6.1


--YZ5djTAD1cGYuMQK--

---
To unsubscribe email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "unsubscribe" in the subject.
For help email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "help" in the subject.
Trouble?  Email listmaster@xxxxxxxxxxxxxxxxxxxx.


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/