diff --git a/chrony.texi.in b/chrony.texi.in index 41727b1..108a263 100644 --- a/chrony.texi.in +++ b/chrony.texi.in @@ -1169,6 +1169,7 @@ directives can occur in any order in the file. * broadcast directive:: Make chronyd act as an NTP broadcast server * cmdallow directive:: Give control access to chronyc on other computers * cmddeny directive:: Deny control access to chronyc on other computers +* combinelimit directive:: Limit sources included in combining algorithm * commandkey directive:: Set runtime command key * corrtimeratio directive:: Set correction time ratio * cmdport directive:: Set port to use for runtime commanding @@ -1447,6 +1448,30 @@ The syntax is identical. There is also a @code{cmddeny all} directive with similar behaviour to the @code{cmdallow all} directive. @c }}} +@c {{{ combinelimit +@node combinelimit directive +@subsection combinelimit +When @code{chronyd} has multiple sources available for synchronization, it has +to select one source as the synchronization source. The measured offsets and +frequencies of the system clock relative to the other sources, however, can be +combined with the selected source to improve the accuracy of the system clock. + +The @code{combinelimit} directive limits which sources are included in the +combining algorithm. Their synchronization distance has to be shorter than the +distance of the selected source multiplied by the value of the limit. Also, +their measured frequencies have to be close to the frequency of the selected +source. + +By default, the limit is 3. Setting the limit to 0 effectively disables the +source combining algorithm and only the selected source will be used to +control the system clock. + +The syntax is + +@example +combinelimit +@end example +@c }}} @c {{{ commandkey @node commandkey directive @subsection commandkey @@ -4080,12 +4105,15 @@ reference clock. @item S This column indicates the state of the sources. @code{*} indicates the -source to which @code{chronyd} is current synchronised. @code{+} indicates -other acceptable sources. @code{?} indicates sources to which -connectivity has been lost. @code{x} indicates a clock which @code{chronyd} +source to which @code{chronyd} is currently synchronised. @code{+} +indicates acceptable sources which are combined with the selected +source. @code{-} indicates acceptable sources which are excluded by +the combining algorithm. @code{?} indicates sources to which +connectivity has been lost or whose packets don't pass all tests. +@code{x} indicates a clock which @code{chronyd} thinks is is a falseticker (i.e. its time is inconsistent with a majority of other sources). @code{~} indicates a source whose time -appears to have too much variability. The @code{~} condition is also +appears to have too much variability. The @code{?} condition is also shown at start-up, until at least 3 samples have been gathered from it. @item Name/IP address diff --git a/client.c b/client.c index 1ddaeef..fec503b 100644 --- a/client.c +++ b/client.c @@ -1693,8 +1693,8 @@ process_cmd_sources(char *line) if (verbose) { printf("\n"); printf(" .-- Source mode '^' = server, '=' = peer, '#' = local clock.\n"); - printf(" / .- Source state '*' = current synced, '+' = OK for sync, '?' = unreachable,\n"); - printf("| / 'x' = time may be in error, '~' = time is too variable.\n"); + printf(" / .- Source state '*' = current synced, '+' = combined , '-' = not combined,\n"); + printf("| / '?' = unreachable, 'x' = time may be in error, '~' = time too variable.\n"); printf("|| .- xxxx [ yyyy ] +/- zzzz\n"); printf("|| / xxxx = adjusted offset,\n"); printf("|| Log2(Polling interval) -. | yyyy = measured offset,\n"); diff --git a/cmdmon.c b/cmdmon.c index 7bc2f96..c9fa39b 100644 --- a/cmdmon.c +++ b/cmdmon.c @@ -1050,6 +1050,9 @@ handle_source_data(CMD_Request *rx_message, CMD_Reply *tx_message) case RPT_CANDIDATE: tx_message->data.source_data.state = htons(RPY_SD_ST_CANDIDATE); break; + case RPT_OUTLYER: + tx_message->data.source_data.state = htons(RPY_SD_ST_OUTLYER); + break; } switch (report.mode) { case RPT_NTP_CLIENT: diff --git a/conf.c b/conf.c index 734d1f7..76db3e6 100644 --- a/conf.c +++ b/conf.c @@ -66,6 +66,7 @@ static void parse_clientloglimit(char *); static void parse_cmdallow(char *); static void parse_cmddeny(char *); static void parse_cmdport(char *); +static void parse_combinelimit(char *); static void parse_commandkey(char *); static void parse_corrtimeratio(char *); static void parse_deny(char *); @@ -126,6 +127,7 @@ static double max_clock_error = 1.0; /* in ppm */ static double reselect_distance = 1e-4; static double stratum_weight = 1.0; +static double combine_limit = 3.0; static int cmd_port = DEFAULT_CANDM_PORT; @@ -374,6 +376,8 @@ CNF_ReadFile(const char *filename) parse_cmddeny(p); } else if (!strcasecmp(command, "cmdport")) { parse_cmdport(p); + } else if (!strcasecmp(command, "combinelimit")) { + parse_combinelimit(p); } else if (!strcasecmp(command, "commandkey")) { parse_commandkey(p); } else if (!strcasecmp(command, "corrtimeratio")) { @@ -757,6 +761,17 @@ parse_stratumweight(char *line) /* ================================================== */ +static void +parse_combinelimit(char *line) +{ + check_number_of_args(line, 1); + if (sscanf(line, "%lf", &combine_limit) != 1) { + command_parse_error(); + } +} + +/* ================================================== */ + static void parse_driftfile(char *line) { @@ -1630,6 +1645,14 @@ CNF_GetStratumWeight(void) /* ================================================== */ +double +CNF_GetCombineLimit(void) +{ + return combine_limit; +} + +/* ================================================== */ + int CNF_GetManualEnabled(void) { diff --git a/conf.h b/conf.h index af8e2a0..2d986e6 100644 --- a/conf.h +++ b/conf.h @@ -83,6 +83,7 @@ extern double CNF_GetCorrectionTimeRatio(void); extern double CNF_GetReselectDistance(void); extern double CNF_GetStratumWeight(void); +extern double CNF_GetCombineLimit(void); extern int CNF_AllowLocalReference(int *stratum); diff --git a/reports.h b/reports.h index 6462106..3eeb5ab 100644 --- a/reports.h +++ b/reports.h @@ -37,7 +37,7 @@ typedef struct { int stratum; int poll; enum {RPT_NTP_CLIENT, RPT_NTP_PEER, RPT_LOCAL_REFERENCE} mode; - enum {RPT_SYNC, RPT_UNREACH, RPT_FALSETICKER, RPT_JITTERY, RPT_CANDIDATE} state; + enum {RPT_SYNC, RPT_UNREACH, RPT_FALSETICKER, RPT_JITTERY, RPT_CANDIDATE, RPT_OUTLYER} state; enum {RPT_NORMAL, RPT_PREFER, RPT_NOSELECT} sel_option; int reachability; diff --git a/sources.c b/sources.c index a8d17fb..f44451a 100644 --- a/sources.c +++ b/sources.c @@ -96,6 +96,9 @@ struct SRC_Instance_Record { /* Reachability register */ int reachability; + /* Updates left before resetting outlyer status */ + int outlyer; + /* Flag indicating the status of the source */ SRC_Status status; @@ -138,8 +141,12 @@ static int selected_source_index; /* Which source index is currently /* Score needed to replace the currently selected source */ #define SCORE_LIMIT 10.0 +/* Number of updates needed to reset the outlyer status */ +#define OUTLYER_PENALTY 32 + static double reselect_distance; static double stratum_weight; +static double combine_limit; /* ================================================== */ /* Forward prototype */ @@ -162,6 +169,7 @@ void SRC_Initialise(void) { selected_source_index = INVALID_SOURCE; reselect_distance = CNF_GetReselectDistance(); stratum_weight = CNF_GetStratumWeight(); + combine_limit = CNF_GetCombineLimit(); initialised = 1; LCL_AddParameterChangeHandler(slew_sources, NULL); @@ -211,6 +219,7 @@ SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, SRC_SelectOpt result->ip_addr = addr; result->selectable = 0; result->reachability = 0; + result->outlyer = 0; result->status = SRC_BAD_STATS; result->type = type; result->sel_score = 1.0; @@ -431,6 +440,23 @@ combine_sources(int n_sel_sources, struct timeval *ref_time, double *offset, &src_frequency, &src_skew, &src_root_delay, &src_root_dispersion); + /* Don't include this source if its distance is longer than the distance of + the selected source multiplied by the limit, their estimated frequencies + are not close, or it was recently marked as outlyer */ + + if (index != selected_source_index && + (sources[index]->sel_info.root_distance > combine_limit * + (reselect_distance + sources[selected_source_index]->sel_info.root_distance) || + fabs(*frequency - src_frequency) > + combine_limit * (*skew + src_skew + LCL_GetMaxClockError()))) { + sources[index]->outlyer = OUTLYER_PENALTY; + } + + if (sources[index]->outlyer) { + sources[index]->outlyer--; + continue; + } + UTI_DiffTimevalsToDouble(&elapsed, ref_time, &src_ref_time); src_offset += elapsed * src_frequency; weight = 1.0 / sources[index]->sel_info.root_distance; @@ -817,6 +843,7 @@ SRC_SelectSource(uint32_t match_refid) /* Reset score for non-selectable sources */ if (sources[i]->status != SRC_SELECTABLE) { sources[i]->sel_score = 1.0; + sources[i]->outlyer = OUTLYER_PENALTY; continue; } @@ -880,6 +907,7 @@ SRC_SelectSource(uint32_t match_refid) /* New source has been selected, reset all scores */ for (i=0; i < n_sources; i++) { sources[i]->sel_score = 1.0; + sources[i]->outlyer = 0; } } @@ -1156,7 +1184,7 @@ SRC_ReportSource(int index, RPT_SourceReport *report, struct timeval *now) report->state = RPT_FALSETICKER; break; case SRC_SELECTABLE: - report->state = RPT_CANDIDATE; + report->state = src->outlyer ? RPT_OUTLYER : RPT_CANDIDATE; break; default: assert(0);