ntpd: explain why scripts can be run in quick succession
[oweals/busybox.git] / networking / ntpd.c
index b2cd0a3c0823d5ce63dcaf34f818efe34e75c562..04df3fa7fa060d91e8b35458b811a3287fff34ee 100644 (file)
@@ -294,7 +294,6 @@ struct globals {
 #define G_precision_sec  (1.0 / (1 << (- G_precision_exp)))
        uint8_t  stratum;
        /* Bool. After set to 1, never goes back to 0: */
-       smallint adjtimex_was_done;
        smallint initial_poll_complete;
 
 #define STATE_NSET      0       /* initial state, "nothing is set" */
@@ -306,13 +305,15 @@ struct globals {
        uint8_t  poll_exp;              // s.poll
        int      polladj_count;         // c.count
        long     kernel_freq_drift;
+       peer_t   *last_update_peer;
        double   last_update_offset;    // c.last
        double   last_update_recv_time; // s.t
        double   discipline_jitter;     // c.jitter
-//TODO: add s.jitter - grep for it here and see clock_combine() in doc
+       //double   cluster_offset;        // s.offset
+       //double   cluster_jitter;        // s.jitter
 #if !USING_KERNEL_PLL_LOOP
        double   discipline_freq_drift; // c.freq
-//TODO: conditionally calculate wander? it's used only for logging
+       /* Maybe conditionally calculate wander? it's used only for logging */
        double   discipline_wander;     // c.wander
 #endif
 };
@@ -734,6 +735,13 @@ send_query_to_peer(peer_t *p)
 }
 
 
+/* Note that there is no provision to prevent several run_scripts
+ * to be done in quick succession. In fact, it happens rather often
+ * if initial syncronization results in a step.
+ * You will see "step" and then "stratum" script runs, sometimes
+ * as close as only 0.002 seconds apart.
+ * Script should be ready to deal with this.
+ */
 static void run_script(const char *action, double offset)
 {
        char *argv[3];
@@ -811,6 +819,7 @@ step_time(double offset)
        /* Globals: */
        G.cur_time -= offset;
        G.last_update_recv_time -= offset;
+       G.last_script_run -= offset;
 }
 
 
@@ -821,6 +830,7 @@ typedef struct {
        peer_t *p;
        int    type;
        double edge;
+       double opt_rd; /* optimization */
 } point_t;
 static int
 compare_point_edge(const void *aa, const void *bb)
@@ -876,6 +886,7 @@ fit(peer_t *p, double rd)
 static peer_t*
 select_and_cluster(void)
 {
+       peer_t     *p;
        llist_t    *item;
        int        i, j;
        int        size = 3 * G.peer_cnt;
@@ -893,10 +904,11 @@ select_and_cluster(void)
        num_points = 0;
        item = G.ntp_peers;
        if (G.initial_poll_complete) while (item != NULL) {
-               peer_t *p = (peer_t *) item->data;
-               double rd = root_distance(p);
-               double offset = p->filter_offset;
+               double rd, offset;
 
+               p = (peer_t *) item->data;
+               rd = root_distance(p);
+               offset = p->filter_offset;
                if (!fit(p, rd)) {
                        item = item->link;
                        continue;
@@ -911,14 +923,17 @@ select_and_cluster(void)
                point[num_points].p = p;
                point[num_points].type = -1;
                point[num_points].edge = offset - rd;
+               point[num_points].opt_rd = rd;
                num_points++;
                point[num_points].p = p;
                point[num_points].type = 0;
                point[num_points].edge = offset;
+               point[num_points].opt_rd = rd;
                num_points++;
                point[num_points].p = p;
                point[num_points].type = 1;
                point[num_points].edge = offset + rd;
+               point[num_points].opt_rd = rd;
                num_points++;
                item = item->link;
        }
@@ -999,14 +1014,12 @@ select_and_cluster(void)
         */
        num_survivors = 0;
        for (i = 0; i < num_points; i++) {
-               peer_t *p;
-
                if (point[i].edge < low || point[i].edge > high)
                        continue;
                p = point[i].p;
                survivor[num_survivors].p = p;
-//TODO: save root_distance in point_t and reuse here?
-               survivor[num_survivors].metric = MAXDIST * p->lastpkt_stratum + root_distance(p);
+               /* x.opt_rd == root_distance(p); */
+               survivor[num_survivors].metric = MAXDIST * p->lastpkt_stratum + point[i].opt_rd;
                VERB4 bb_error_msg("survivor[%d] metric:%f peer:%s",
                        num_survivors, survivor[num_survivors].metric, p->p_dotted);
                num_survivors++;
@@ -1050,8 +1063,8 @@ select_and_cluster(void)
                 */
                for (i = 0; i < num_survivors; i++) {
                        double selection_jitter_sq;
-                       peer_t *p = survivor[i].p;
 
+                       p = survivor[i].p;
                        if (i == 0 || p->filter_jitter < min_jitter)
                                min_jitter = p->filter_jitter;
 
@@ -1093,18 +1106,54 @@ select_and_cluster(void)
                }
        }
 
+       if (0) {
+               /* Combine the offsets of the clustering algorithm survivors
+                * using a weighted average with weight determined by the root
+                * distance. Compute the selection jitter as the weighted RMS
+                * difference between the first survivor and the remaining
+                * survivors. In some cases the inherent clock jitter can be
+                * reduced by not using this algorithm, especially when frequent
+                * clockhopping is involved. bbox: thus we don't do it.
+                */
+               double x, y, z, w;
+               y = z = w = 0;
+               for (i = 0; i < num_survivors; i++) {
+                       p = survivor[i].p;
+                       x = root_distance(p);
+                       y += 1 / x;
+                       z += p->filter_offset / x;
+                       w += SQUARE(p->filter_offset - survivor[0].p->filter_offset) / x;
+               }
+               //G.cluster_offset = z / y;
+               //G.cluster_jitter = SQRT(w / y);
+       }
+
        /* Pick the best clock. If the old system peer is on the list
         * and at the same stratum as the first survivor on the list,
         * then don't do a clock hop. Otherwise, select the first
         * survivor on the list as the new system peer.
         */
-//TODO - see clock_combine()
+       p = survivor[0].p;
+       if (G.last_update_peer
+        && G.last_update_peer->lastpkt_stratum <= p->lastpkt_stratum
+       ) {
+               /* Starting from 1 is ok here */
+               for (i = 1; i < num_survivors; i++) {
+                       if (G.last_update_peer == survivor[i].p) {
+                               VERB4 bb_error_msg("keeping old synced peer");
+                               p = G.last_update_peer;
+                               goto keep_old;
+                       }
+               }
+       }
+       G.last_update_peer = p;
+ keep_old:
        VERB3 bb_error_msg("selected peer %s filter_offset:%f age:%f",
-                       survivor[0].p->p_dotted,
-                       survivor[0].p->filter_offset,
-                       G.cur_time - survivor[0].p->lastpkt_recv_time
+                       p->p_dotted,
+                       p->filter_offset,
+                       G.cur_time - p->lastpkt_recv_time
        );
-       return survivor[0].p;
+       return p;
 }
 
 
@@ -1129,8 +1178,8 @@ static NOINLINE int
 update_local_clock(peer_t *p)
 {
        int rc;
-       long old_tmx_offset;
        struct timex tmx;
+       /* Note: can use G.cluster_offset instead: */
        double offset = p->filter_offset;
        double recv_time = p->lastpkt_recv_time;
        double abs_offset;
@@ -1143,8 +1192,8 @@ update_local_clock(peer_t *p)
        abs_offset = fabs(offset);
 
 #if 0
-       /* If needed, -S script can detect this by looking at $offset
-        * env var and kill parent */
+       /* If needed, -S script can do it by looking at $offset
+        * env var and killing parent */
        /* If the offset is too large, give up and go home */
        if (abs_offset > PANIC_THRESHOLD) {
                bb_error_msg_and_die("offset %f far too big, exiting", offset);
@@ -1343,7 +1392,7 @@ update_local_clock(peer_t *p)
        G.ntp_status = p->lastpkt_status;
        G.refid = p->lastpkt_refid;
        G.rootdelay = p->lastpkt_rootdelay + p->lastpkt_delay;
-       dtemp = p->filter_jitter; // SQRT(SQUARE(p->filter_jitter) + SQUARE(s.jitter));
+       dtemp = p->filter_jitter; // SQRT(SQUARE(p->filter_jitter) + SQUARE(G.cluster_jitter));
        dtemp += MAXD(p->filter_dispersion + FREQ_TOLERANCE * (G.cur_time - p->lastpkt_recv_time) + abs_offset, MINDISP);
        G.rootdisp = p->lastpkt_rootdisp + dtemp;
        VERB3 bb_error_msg("updating leap/refid/reftime/rootdisp from peer %s", p->p_dotted);
@@ -1380,17 +1429,6 @@ update_local_clock(peer_t *p)
                                tmx.freq, tmx.offset, tmx.constant, tmx.status);
        }
 
-       old_tmx_offset = 0;
-       if (!G.adjtimex_was_done) {
-               G.adjtimex_was_done = 1;
-               /* When we use adjtimex for the very first time,
-                * we need to ADD to pre-existing tmx.offset - it may be !0
-                */
-               memset(&tmx, 0, sizeof(tmx));
-               if (adjtimex(&tmx) < 0)
-                       bb_perror_msg_and_die("adjtimex");
-               old_tmx_offset = tmx.offset;
-       }
        memset(&tmx, 0, sizeof(tmx));
 #if 0
 //doesn't work, offset remains 0 (!) in kernel:
@@ -1403,9 +1441,8 @@ update_local_clock(peer_t *p)
        tmx.offset = G.last_update_offset * 1000000; /* usec */
 #endif
        tmx.modes = ADJ_OFFSET | ADJ_STATUS | ADJ_TIMECONST;// | ADJ_MAXERROR | ADJ_ESTERROR;
-       tmx.offset = (G.last_update_offset * 1000000) /* usec */
+       tmx.offset = (G.last_update_offset * 1000000); /* usec */
                        /* + (G.last_update_offset < 0 ? -0.5 : 0.5) - too small to bother */
-                       + old_tmx_offset; /* almost always 0 */
        tmx.status = STA_PLL;
        if (G.ntp_status & LI_PLUSSEC)
                tmx.status |= STA_INS;
@@ -1433,7 +1470,8 @@ update_local_clock(peer_t *p)
        }
 #endif
        G.kernel_freq_drift = tmx.freq / 65536;
-       VERB2 bb_error_msg("update offset:%f, clock drift:%ld ppm", G.last_update_offset, G.kernel_freq_drift);
+       VERB2 bb_error_msg("update peer:%s, offset:%f, clock drift:%ld ppm",
+                       p->p_dotted, G.last_update_offset, G.kernel_freq_drift);
 
        return 1; /* "ok to increase poll interval" */
 }
@@ -1976,9 +2014,7 @@ int ntpd_main(int argc UNUSED_PARAM, char **argv)
                nfds = poll(pfd, i, timeout * 1000);
                gettime1900d(); /* sets G.cur_time */
                if (nfds <= 0) {
-                       if (G.adjtimex_was_done
-                        && G.cur_time - G.last_script_run > 11*60
-                       ) {
+                       if (G.script_name && G.cur_time - G.last_script_run > 11*60) {
                                /* Useful for updating battery-backed RTC and such */
                                run_script("periodic", G.last_update_offset);
                                gettime1900d(); /* sets G.cur_time */