Select source with minimum distance using a scoring system
Each source has a score against currently selected source which is updated (multiplied by ratio of their distances) when one of the two sources has a new sample. When the score reaches a limit, the source will be selected. This should allow to slowly select the source with minimum distance without frequent reselecting. To avoid switching between sources with very variable distances (e.g. on LAN or when upstream server uses a longer polling interval), sources that are currently not selected are penalized by a fixed distance. This can be configured with new reselectdist directive (100 microseconds by default).
This commit is contained in:
parent
222198acf3
commit
db510a9558
4 changed files with 121 additions and 27 deletions
17
chrony.texi
17
chrony.texi
|
@ -1199,6 +1199,7 @@ directives can occur in any order in the file.
|
|||
* pidfile directive:: Specify the file where chronyd's pid is written
|
||||
* port directive:: Set port to use for NTP packets
|
||||
* refclock directive:: Specify a reference clock
|
||||
* reselectdist directive:: Set improvement in distance needed to reselect a source
|
||||
* rtcdevice directive:: Specify name of enhanced RTC device (if not /dev/rtc)
|
||||
* rtcfile directive:: Specify the file where real-time clock data is stored
|
||||
* rtconutc directive:: Specify that the real time clock keeps UTC not local time
|
||||
|
@ -2424,6 +2425,22 @@ Prefer this source over sources without prefer option.
|
|||
Never select this source. This is particularly useful for monitoring.
|
||||
@end table
|
||||
|
||||
@c }}}
|
||||
@c {{{ reselectdist
|
||||
@node reselectdist directive
|
||||
@subsection reselectdist
|
||||
When @code{chronyd} selects synchronisation source from available sources, it
|
||||
will prefer the one with minimum synchronisation distance. However, to
|
||||
avoid frequent reselecting when there are sources with similar distance, a
|
||||
fixed distance is added to the distance for sources that are currently not
|
||||
selected. This can be set with the @code{reselectdist} option. By default, the
|
||||
distance is 100 microseconds.
|
||||
|
||||
The syntax is
|
||||
|
||||
@example
|
||||
reselectdist <dist-in-seconds>
|
||||
@end example
|
||||
@c }}}
|
||||
@c {{{ rtcdevice
|
||||
@node rtcdevice directive
|
||||
|
|
23
conf.c
23
conf.c
|
@ -78,6 +78,7 @@ static void parse_logbanner(const char *);
|
|||
static void parse_logdir(const char *);
|
||||
static void parse_maxupdateskew(const char *);
|
||||
static void parse_maxclockerror(const char *);
|
||||
static void parse_reselectdist(const char *);
|
||||
static void parse_peer(const char *);
|
||||
static void parse_acquisitionport(const char *);
|
||||
static void parse_port(const char *);
|
||||
|
@ -123,6 +124,8 @@ static unsigned long command_key_id;
|
|||
static double max_update_skew = 1000.0;
|
||||
static double max_clock_error = 10; /* in ppm */
|
||||
|
||||
static double reselect_distance = 1e-4;
|
||||
|
||||
static int cmd_port = -1;
|
||||
|
||||
static int do_log_measurements = 0;
|
||||
|
@ -258,6 +261,7 @@ static const Command commands[] = {
|
|||
{"pidfile", 7, parse_pidfile},
|
||||
{"broadcast", 9, parse_broadcast},
|
||||
{"tempcomp", 8, parse_tempcomp},
|
||||
{"reselectdist", 12, parse_reselectdist},
|
||||
{"linux_hz", 8, parse_linux_hz},
|
||||
{"linux_freq_scale", 16, parse_linux_freq_scale},
|
||||
{"sched_priority", 14, parse_sched_priority},
|
||||
|
@ -609,6 +613,16 @@ parse_maxclockerror(const char *line)
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
parse_reselectdist(const char *line)
|
||||
{
|
||||
if (sscanf(line, "%lf", &reselect_distance) != 1) {
|
||||
LOG(LOGS_WARN, LOGF_Configure, "Could not read reselect distance at line %d in file", line_number);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
static void
|
||||
parse_driftfile(const char *line)
|
||||
{
|
||||
|
@ -1431,6 +1445,14 @@ CNF_GetMaxClockError(void)
|
|||
|
||||
/* ================================================== */
|
||||
|
||||
double
|
||||
CNF_GetReselectDistance(void)
|
||||
{
|
||||
return reselect_distance;
|
||||
}
|
||||
|
||||
/* ================================================== */
|
||||
|
||||
int
|
||||
CNF_GetManualEnabled(void)
|
||||
{
|
||||
|
@ -1637,3 +1659,4 @@ CNF_GetTempComp(char **file, double *interval, double *T0, double *k0, double *k
|
|||
*k1 = tempcomp_k1;
|
||||
*k2 = tempcomp_k2;
|
||||
}
|
||||
|
||||
|
|
1
conf.h
1
conf.h
|
@ -78,6 +78,7 @@ extern void CNF_GetLinuxFreqScale(int *set, double *freq_scale);
|
|||
/* Value returned in ppm, as read from file */
|
||||
extern double CNF_GetMaxUpdateSkew(void);
|
||||
extern double CNF_GetMaxClockError(void);
|
||||
extern double CNF_GetReselectDistance(void);
|
||||
extern int CNF_AllowLocalReference(int *stratum);
|
||||
|
||||
extern void CNF_SetupAccessRestrictions(void);
|
||||
|
|
107
sources.c
107
sources.c
|
@ -105,6 +105,9 @@ struct SRC_Instance_Record {
|
|||
/* Options used when selecting sources */
|
||||
SRC_SelectOption sel_option;
|
||||
|
||||
/* Score against currently selected source */
|
||||
double sel_score;
|
||||
|
||||
struct SelectInfo sel_info;
|
||||
};
|
||||
|
||||
|
@ -132,6 +135,11 @@ static int selected_source_index; /* Which source index is currently
|
|||
/* Keep reachability status for last 8 samples */
|
||||
#define REACH_BITS 8
|
||||
|
||||
/* Score needed to replace the currently selected source */
|
||||
#define SCORE_LIMIT 10.0
|
||||
|
||||
static double reselect_distance;
|
||||
|
||||
/* ================================================== */
|
||||
/* Forward prototype */
|
||||
|
||||
|
@ -151,6 +159,7 @@ void SRC_Initialise(void) {
|
|||
n_sources = 0;
|
||||
max_n_sources = 0;
|
||||
selected_source_index = INVALID_SOURCE;
|
||||
reselect_distance = CNF_GetReselectDistance();
|
||||
initialised = 1;
|
||||
|
||||
LCL_AddParameterChangeHandler(slew_sources, NULL);
|
||||
|
@ -205,6 +214,7 @@ SRC_Instance SRC_CreateNewInstance(unsigned long ref_id, SRC_Type type, SRC_Sele
|
|||
result->reachability = 0;
|
||||
result->status = SRC_BAD_STATS;
|
||||
result->type = type;
|
||||
result->sel_score = 1.0;
|
||||
result->sel_option = sel_option;
|
||||
|
||||
n_sources++;
|
||||
|
@ -420,11 +430,12 @@ SRC_SelectSource(unsigned long match_addr)
|
|||
int n_sel_sources;
|
||||
double distance, min_distance;
|
||||
int stratum, min_stratum;
|
||||
int min_distance_index;
|
||||
struct SelectInfo *si;
|
||||
double total_root_dispersion;
|
||||
int n_badstats_sources;
|
||||
int max_sel_reach, max_badstat_reach;
|
||||
int max_score_index;
|
||||
double max_score;
|
||||
|
||||
NTP_Leap leap_status = LEAP_Normal;
|
||||
old_selected_index = selected_source_index;
|
||||
|
@ -734,47 +745,89 @@ SRC_SelectSource(unsigned long match_addr)
|
|||
if (stratum < min_stratum) min_stratum = stratum;
|
||||
}
|
||||
|
||||
/* Find the best source with minimum stratum */
|
||||
min_distance_index = INVALID_SOURCE;
|
||||
for (i=0; i<n_sel_sources; i++) {
|
||||
index = sel_sources[i];
|
||||
if (sources[index]->sel_info.stratum == min_stratum) {
|
||||
if ((min_distance_index == INVALID_SOURCE) ||
|
||||
(sources[index]->sel_info.root_distance < min_distance)) {
|
||||
min_distance = sources[index]->sel_info.root_distance;
|
||||
min_distance_index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Sources, "min_stratum=%d", min_stratum);
|
||||
#endif
|
||||
|
||||
/* Does the current source have this stratum, doesn't have distance
|
||||
much worse than the best source and is it still a survivor? */
|
||||
/* Update scores and find source with maximum score */
|
||||
|
||||
max_score_index = INVALID_SOURCE;
|
||||
max_score = 0.0;
|
||||
|
||||
for (i = 0; i < n_sources; i++) {
|
||||
|
||||
/* Reset score for non-selectable sources */
|
||||
if (sources[i]->status != SRC_SELECTABLE) {
|
||||
sources[i]->sel_score = 1.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* And for sources with stratum higher than the minimum */
|
||||
if (sources[i]->sel_info.stratum > min_stratum) {
|
||||
sources[i]->sel_score = 1.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
distance = sources[i]->sel_info.root_distance + reselect_distance;
|
||||
|
||||
if (selected_source_index != INVALID_SOURCE) {
|
||||
|
||||
/* Update score, but only for source pairs where one source
|
||||
has a new sample */
|
||||
if (sources[i]->ref_id == match_addr ||
|
||||
sources[selected_source_index]->ref_id == match_addr) {
|
||||
|
||||
sources[i]->sel_score *=
|
||||
sources[selected_source_index]->sel_info.root_distance / distance;
|
||||
|
||||
if (sources[i]->sel_score < 1.0)
|
||||
sources[i]->sel_score = 1.0;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* When there is no selected source yet, assign scores so the
|
||||
source with minimum distance will have maximum score. The scores
|
||||
will be immediately reset. */
|
||||
|
||||
sources[i]->sel_score = 1.0 / distance;
|
||||
}
|
||||
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Sources, "select score=%f refid=%lx match_refid=%lx status=%d dist=%f",
|
||||
sources[i]->sel_score, sources[i]->ref_id, match_addr, sources[i]->status, distance);
|
||||
#endif
|
||||
|
||||
if (max_score < sources[i]->sel_score) {
|
||||
max_score = sources[i]->sel_score;
|
||||
max_score_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
assert(max_score_index != INVALID_SOURCE);
|
||||
|
||||
/* Does the current source have this stratum, is it still a survivor
|
||||
and no other source has reached the score limit? */
|
||||
|
||||
if ((selected_source_index == INVALID_SOURCE) ||
|
||||
(sources[selected_source_index]->status != SRC_SELECTABLE) ||
|
||||
(sources[selected_source_index]->sel_info.stratum > min_stratum) ||
|
||||
(sources[selected_source_index]->sel_info.root_distance > 10 * min_distance)) {
|
||||
(max_score_index != selected_source_index && max_score > SCORE_LIMIT)) {
|
||||
|
||||
/* We have to elect a new synchronisation source */
|
||||
|
||||
selected_source_index = min_distance_index;
|
||||
selected_source_index = max_score_index;
|
||||
LOG(LOGS_INFO, LOGF_Sources, "Selected source %s",
|
||||
source_to_string(sources[selected_source_index]));
|
||||
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Sources, "new_sel_index=%d", selected_source_index);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Sources, "new_sel_index=%d", min_distance_index);
|
||||
#endif
|
||||
} else {
|
||||
/* We retain the existing sync source, see p40 of RFC1305b.ps */
|
||||
#if 0
|
||||
LOG(LOGS_INFO, LOGF_Sources, "existing reference retained", min_distance_index);
|
||||
#endif
|
||||
|
||||
/* New source has been selected, reset all scores */
|
||||
for (i=0; i < n_sources; i++) {
|
||||
sources[i]->sel_score = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
sources[selected_source_index]->status = SRC_SYNC;
|
||||
|
|
Loading…
Reference in a new issue