*
* Author: Adam Tkac <vonsch@gmail.com>
*
- * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2, see file LICENSE in this source tree.
*
* Parts of OpenNTPD clock syncronization code is replaced by
* code which is based on ntp-4.2.6, whuch carries the following
#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" */
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
};
p->filter_offset, x,
p->filter_dispersion,
p->filter_jitter);
-
}
static void
for (i = 0; i < NUM_DATAPOINTS; i++) {
if (small_ofs) {
- p->filter_datapoint[i].d_recv_time -= offset;
+ p->filter_datapoint[i].d_recv_time += offset;
if (p->filter_datapoint[i].d_offset != 0) {
- p->filter_datapoint[i].d_offset -= offset;
+ p->filter_datapoint[i].d_offset += offset;
}
} else {
p->filter_datapoint[i].d_recv_time = G.cur_time;
}
}
if (small_ofs) {
- p->lastpkt_recv_time -= offset;
+ p->lastpkt_recv_time += offset;
} else {
p->reachable_bits = 0;
p->lastpkt_recv_time = G.cur_time;
}
filter_datapoints(p); /* recalc p->filter_xxx */
- p->next_action_time -= offset;
VERB5 bb_error_msg("%s->lastpkt_recv_time=%f", p->p_dotted, p->lastpkt_recv_time);
}
}
+/* 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];
/* Don't want to wait: it may run hwclock --systohc, and that
* may take some time (seconds): */
- /*wait4pid(spawn(argv));*/
+ /*spawn_and_wait(argv);*/
spawn(argv);
unsetenv("stratum");
for (item = G.ntp_peers; item != NULL; item = item->link) {
peer_t *pp = (peer_t *) item->data;
reset_peer_stats(pp, offset);
+ //bb_error_msg("offset:%f pp->next_action_time:%f -> %f",
+ // offset, pp->next_action_time, pp->next_action_time + offset);
+ pp->next_action_time += offset;
}
/* Globals: */
- G.cur_time -= offset;
- G.last_update_recv_time -= offset;
+ G.cur_time += offset;
+ G.last_update_recv_time += offset;
+ G.last_script_run += offset;
}
peer_t *p;
int type;
double edge;
+ double opt_rd; /* optimization */
} point_t;
static int
compare_point_edge(const void *aa, const void *bb)
static peer_t*
select_and_cluster(void)
{
+ peer_t *p;
llist_t *item;
int i, j;
int size = 3 * G.peer_cnt;
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;
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;
}
*/
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++;
*/
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;
}
}
+ 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;
}
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;
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);
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);
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:
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;
}
#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" */
}
if (opts & OPT_N)
setpriority(PRIO_PROCESS, 0, -15);
- bb_signals((1 << SIGTERM) | (1 << SIGINT), record_signo);
- /* Removed SIGHUP here: */
- bb_signals((1 << SIGPIPE) | (1 << SIGCHLD), SIG_IGN);
+ /* If network is up, syncronization occurs in ~10 seconds.
+ * We give "ntpd -q" a full minute to finish, then we exit.
+ *
+ * I tested ntpd 4.2.6p1 and apparently it never exits
+ * (will try forever), but it does not feel right.
+ * The goal of -q is to act like ntpdate: set time
+ * after a reasonably small period of polling, or fail.
+ */
+ if (opts & OPT_q)
+ alarm(60);
+
+ bb_signals(0
+ | (1 << SIGTERM)
+ | (1 << SIGINT)
+ | (1 << SIGALRM)
+ , record_signo
+ );
+ bb_signals(0
+ | (1 << SIGPIPE)
+ | (1 << SIGCHLD)
+ , SIG_IGN
+ );
}
int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE;
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 */
static double
direct_freq(double fp_offset)
{
-
#ifdef KERNEL_PLL
/*
* If the kernel is enabled, we need the residual offset to