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:
Miroslav Lichvar 2011-01-24 17:09:00 +01:00
parent 222198acf3
commit db510a9558
4 changed files with 121 additions and 27 deletions

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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;