cmdmon: add selectdata command

Add a command to report selection-specific data.
This commit is contained in:
Miroslav Lichvar 2020-05-25 14:10:40 +02:00
parent 39a462496a
commit 03541f3626
8 changed files with 335 additions and 4 deletions

33
candm.h
View file

@ -106,7 +106,8 @@
#define REQ_RESET_SOURCES 66
#define REQ_AUTH_DATA 67
#define REQ_CLIENT_ACCESSES_BY_INDEX3 68
#define N_REQUEST_TYPES 69
#define REQ_SELECT_DATA 69
#define N_REQUEST_TYPES 70
/* Structure used to exchange timespecs independent of time_t size */
typedef struct {
@ -360,6 +361,11 @@ typedef struct {
int32_t EOR;
} REQ_AuthData;
typedef struct {
uint32_t index;
int32_t EOR;
} REQ_SelectData;
/* ================================================== */
#define PKT_TYPE_CMD_REQUEST 1
@ -464,6 +470,7 @@ typedef struct {
REQ_NTPData ntp_data;
REQ_NTPSourceName ntp_source_name;
REQ_AuthData auth_data;
REQ_SelectData select_data;
} data; /* Command specific parameters */
/* Padding used to prevent traffic amplification. It only defines the
@ -504,7 +511,8 @@ typedef struct {
#define RPY_AUTH_DATA 20
#define RPY_CLIENT_ACCESSES_BY_INDEX3 21
#define RPY_SERVER_STATS2 22
#define N_REPLY_TYPES 23
#define RPY_SELECT_DATA 23
#define N_REPLY_TYPES 24
/* Status codes */
#define STT_SUCCESS 0
@ -744,6 +752,26 @@ typedef struct {
int32_t EOR;
} RPY_AuthData;
#define RPY_SD_OPTION_NOSELECT 0x1
#define RPY_SD_OPTION_PREFER 0x2
#define RPY_SD_OPTION_TRUST 0x4
#define RPY_SD_OPTION_REQUIRE 0x8
typedef struct {
uint32_t ref_id;
IPAddr ip_addr;
uint8_t state_char;
uint8_t authentication;
uint8_t pad[2];
uint16_t conf_options;
uint16_t eff_options;
uint32_t last_sample_ago;
Float score;
Float lo_limit;
Float hi_limit;
int32_t EOR;
} RPY_SelectData;
typedef struct {
uint8_t version;
uint8_t pkt_type;
@ -775,6 +803,7 @@ typedef struct {
RPY_NTPData ntp_data;
RPY_NTPSourceName ntp_source_name;
RPY_AuthData auth_data;
RPY_SelectData select_data;
} data; /* Reply specific parameters */
} CMD_Reply;

View file

@ -1209,6 +1209,7 @@ give_help(void)
"Time sources:\0\0"
"sources [-a] [-v]\0Display information about current sources\0"
"sourcestats [-a] [-v]\0Display statistics about collected measurements\0"
"selectdata [-a] [-v]\0Display information about source selection\0"
"reselect\0Force reselecting synchronisation source\0"
"reselectdist <dist>\0Modify reselection distance\0"
"\0\0"
@ -1303,6 +1304,7 @@ enum {
TAB_COMPLETE_RESET_OPTS,
TAB_COMPLETE_SOURCES_OPTS,
TAB_COMPLETE_SOURCESTATS_OPTS,
TAB_COMPLETE_SELECTDATA_OPTS,
TAB_COMPLETE_MAX_INDEX
};
@ -1319,7 +1321,7 @@ command_name_generator(const char *text, int state)
"manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll",
"maxupdateskew", "minpoll", "minstratum", "ntpdata", "offline", "online", "onoffline",
"polltarget", "quit", "refresh", "rekey", "reselect", "reselectdist", "reset",
"retries", "rtcdata", "serverstats", "settime", "shutdown", "smoothing",
"retries", "rtcdata", "selectdata", "serverstats", "settime", "shutdown", "smoothing",
"smoothtime", "sourcename", "sources", "sourcestats",
"timeout", "tracking", "trimrtc", "waitsync", "writertc",
NULL
@ -1327,6 +1329,7 @@ command_name_generator(const char *text, int state)
const char *add_options[] = { "peer", "pool", "server", NULL };
const char *manual_options[] = { "on", "off", "delete", "list", "reset", NULL };
const char *reset_options[] = { "sources", NULL };
const char *selectdata_options[] = { "-a", "-v", NULL };
const char *sources_options[] = { "-a", "-v", NULL };
const char *sourcestats_options[] = { "-a", "-v", NULL };
static int list_index, len;
@ -1335,6 +1338,7 @@ command_name_generator(const char *text, int state)
names[TAB_COMPLETE_ADD_OPTS] = add_options;
names[TAB_COMPLETE_MANUAL_OPTS] = manual_options;
names[TAB_COMPLETE_RESET_OPTS] = reset_options;
names[TAB_COMPLETE_SELECTDATA_OPTS] = selectdata_options;
names[TAB_COMPLETE_SOURCES_OPTS] = sources_options;
names[TAB_COMPLETE_SOURCESTATS_OPTS] = sourcestats_options;
@ -1368,6 +1372,8 @@ command_name_completion(const char *text, int start, int end)
tab_complete_index = TAB_COMPLETE_MANUAL_OPTS;
} else if (!strcmp(first, "reset ")) {
tab_complete_index = TAB_COMPLETE_RESET_OPTS;
} else if (!strcmp(first, "selectdata ")) {
tab_complete_index = TAB_COMPLETE_SELECTDATA_OPTS;
} else if (!strcmp(first, "sources ")) {
tab_complete_index = TAB_COMPLETE_SOURCES_OPTS;
} else if (!strcmp(first, "sourcestats ")) {
@ -2557,6 +2563,81 @@ process_cmd_ntpdata(char *line)
/* ================================================== */
static int
process_cmd_selectdata(char *line)
{
CMD_Request request;
CMD_Reply reply;
uint32_t i, n_sources;
int all, verbose, conf_options, eff_options;
char name[256];
IPAddr ip_addr;
parse_sources_options(line, &all, &verbose);
request.command = htons(REQ_N_SOURCES);
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
return 0;
n_sources = ntohl(reply.data.n_sources.n_sources);
if (verbose) {
printf( " .-- State: N - noselect, M - missing samples, d/D - large distance,\n");
printf( " / ~ - jittery, w/W - waits for others, T - not trusted,\n");
printf( "| x - falseticker, P - not preferred, U - waits for update,\n");
printf( "| S - stale, O - orphan, + - combined, * - best.\n");
printf( "| Effective options ------. (N - noselect, P - prefer\n");
printf( "| Configured options -. \\ T - trust, R - require)\n");
printf( "| Auth. enabled (Y/N) -. \\ \\ Offset interval --.\n");
printf( "| | | | |\n");
}
print_header("S Name/IP Address Auth COpts EOpts Last Score Interval ");
/* "S NNNNNNNNNNNNNNNNNNNNNNNNN A OOOO- OOOO- LLLL SSSSS LLLLLLL LLLLLLL" */
for (i = 0; i < n_sources; i++) {
request.command = htons(REQ_SELECT_DATA);
request.data.source_data.index = htonl(i);
if (!request_reply(&request, &reply, RPY_SELECT_DATA, 0))
return 0;
UTI_IPNetworkToHost(&reply.data.select_data.ip_addr, &ip_addr);
if (!all && ip_addr.family == IPADDR_ID)
continue;
format_name(name, sizeof (name), 25, ip_addr.family == IPADDR_UNSPEC,
ntohl(reply.data.select_data.ref_id), 1, &ip_addr);
conf_options = ntohs(reply.data.select_data.conf_options);
eff_options = ntohs(reply.data.select_data.eff_options);
print_report("%c %-25s %c %c%c%c%c%c %c%c%c%c%c %I %5.1f %+S %+S\n",
reply.data.select_data.state_char,
name,
reply.data.select_data.authentication ? 'Y' : 'N',
conf_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
conf_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
conf_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
conf_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
'-',
eff_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
eff_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
eff_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
eff_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
'-',
(unsigned long)ntohl(reply.data.select_data.last_sample_ago),
UTI_FloatNetworkToHost(reply.data.select_data.score),
UTI_FloatNetworkToHost(reply.data.select_data.lo_limit),
UTI_FloatNetworkToHost(reply.data.select_data.hi_limit),
REPORT_END);
}
return 1;
}
/* ================================================== */
static int
process_cmd_serverstats(char *line)
{
@ -3272,6 +3353,9 @@ process_line(char *line)
} else if (!strcmp(command, "rtcdata")) {
do_normal_submit = 0;
ret = process_cmd_rtcreport(line);
} else if (!strcmp(command, "selectdata")) {
do_normal_submit = 0;
ret = process_cmd_selectdata(line);
} else if (!strcmp(command, "serverstats")) {
do_normal_submit = 0;
ret = process_cmd_serverstats(line);

View file

@ -138,6 +138,7 @@ static const char permissions[] = {
PERMIT_AUTH, /* RESET_SOURCES */
PERMIT_AUTH, /* AUTH_DATA */
PERMIT_AUTH, /* CLIENT_ACCESSES_BY_INDEX3 */
PERMIT_AUTH, /* SELECT_DATA */
};
/* ================================================== */
@ -1282,6 +1283,43 @@ handle_auth_data(CMD_Request *rx_message, CMD_Reply *tx_message)
tx_message->data.auth_data.nak = htons(report.nak);
}
/* ================================================== */
static uint16_t
convert_select_options(int options)
{
return (options & SRC_SELECT_PREFER ? RPY_SD_OPTION_PREFER : 0) |
(options & SRC_SELECT_NOSELECT ? RPY_SD_OPTION_NOSELECT : 0) |
(options & SRC_SELECT_TRUST ? RPY_SD_OPTION_TRUST : 0) |
(options & SRC_SELECT_REQUIRE ? RPY_SD_OPTION_REQUIRE : 0);
}
/* ================================================== */
static void
handle_select_data(CMD_Request *rx_message, CMD_Reply *tx_message)
{
RPT_SelectReport report;
if (!SRC_GetSelectReport(ntohl(rx_message->data.select_data.index), &report)) {
tx_message->status = htons(STT_NOSUCHSOURCE);
return;
}
tx_message->reply = htons(RPY_SELECT_DATA);
tx_message->data.select_data.ref_id = htonl(report.ref_id);
UTI_IPHostToNetwork(&report.ip_addr, &tx_message->data.select_data.ip_addr);
tx_message->data.select_data.state_char = report.state_char;
tx_message->data.select_data.authentication = report.authentication;
tx_message->data.select_data.conf_options = htons(convert_select_options(report.conf_options));
tx_message->data.select_data.eff_options = htons(convert_select_options(report.eff_options));
tx_message->data.select_data.last_sample_ago = htonl(report.last_sample_ago);
tx_message->data.select_data.score = UTI_FloatHostToNetwork(report.score);
tx_message->data.select_data.hi_limit = UTI_FloatHostToNetwork(report.hi_limit);
tx_message->data.select_data.lo_limit = UTI_FloatHostToNetwork(report.lo_limit);
}
/* ================================================== */
/* Read a packet and process it */
@ -1668,6 +1706,10 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
handle_auth_data(&rx_message, &tx_message);
break;
case REQ_SELECT_DATA:
handle_select_data(&rx_message, &tx_message);
break;
default:
DEBUG_LOG("Unhandled command %d", rx_command);
tx_message.status = htons(STT_FAILED);

View file

@ -414,6 +414,92 @@ This is the estimated offset of the source.
*Std Dev*:::
This is the estimated sample standard deviation.
[[selectdata]]*selectdata* [*-a*] [*-v*]::
The *selectdata* command displays information specific to the selection of time
sources. If the *-a* option is specified, all sources are displayed, including
those that do not have a known address yet. With the *-v* option, extra caption
lines are shown as a reminder of the meanings of the columns.
+
An example of the output is shown below.
+
----
S Name/IP Address Auth COpts EOpts Last Score Interval
====================================================================
D foo.example.net Y ----- --TR- 4 1.0 -61ms +62ms
* bar.example.net N ----- ----- 0 1.0 -6846us +7305us
+ baz.example.net N ----- ----- 10 1.0 -7381us +7355us
----
+
The columns are as follows:
+
*S*:::
This column indicates the state of the source after the last source selection.
It is similar to the state reported by the *sources* command, but more
states are reported.
:::
The following states indicate the source is not considered selectable for
synchronisation:
* _N_ - has the *noselect* option.
* _M_ - does not have enough measurements.
* _d_ - has a root distance larger than the maximum distance (configured by the
<<chrony.conf.adoc#maxdistance,*maxdistance*>> directive).
* _~_ - has a jitter larger than the maximum jitter (configured by the
<<chrony.conf.adoc#maxjitter,*maxjitter*>> directive).
* _w_ - waits for other sources to get out of the _M_ state.
* _S_ - has older measurements than other sources.
* _O_ - has a stratum equal or larger than the orphan stratum (configured by
the <<chrony.conf.adoc#local,*local*>> directive).
* _T_ - does not fully agree with sources that have the *trust* option.
* _x_ - does not agree with other sources (falseticker).
:::
The following states indicate the source is considered selectable, but it is
not currently used for synchronisation:
* _W_ - waits for other sources to be selectable (required by the
<<chrony.conf.adoc#minsources,*minsources*>> directive, or
the *require* option of another source).
* _P_ - another selectable source is preferred due to the *prefer* option.
* _U_ - waits for a new measurement (after selecting a different best source).
* _D_ - has, or recently had, a root distance which is too large to be combined
with other sources (configured by the
<<chrony.conf.adoc#combinelimit,*combinelimit*>> directive).
:::
The following states indicate the source is used for synchronisation of the
local clock:
* _+_ - combined with the best source.
* _*_ - selected as the best source to update the reference data (e.g. root
delay, root dispersion).
*Name/IP address*:::
This column shows the name or IP address of the source if it is an NTP server,
or the reference ID if it is a reference clock.
*Auth*:::
This column indicites whether an authentication mechanism is enabled for the
source. _Y_ means yes and _N_ means no.
*COpts*:::
This column displays the configured selection options of the source.
* _N_ indicates the *noselect* option.
* _P_ indicates the *prefer* option.
* _T_ indicates the *trust* option.
* _R_ indicates the *require* option.
*EOpts*:::
This column displays the current effective selection options of the source,
which can be different from the configured options due to the authentication
selection mode (configured by the
<<chrony.conf.adoc#authselmode,*authselmode*>> directive). The symbols are the
same as in the *COpts* column.
*Last*:::
This column displays how long ago was the last measurement of the source made
when the selection was performed.
*Score*:::
This column displays the current score against the source in the _*_ state. The
scoring system avoids frequent reselection when multiple sources have a similar
root distance. A value larger than 1 indicates this source was better than the
_*_ source in recent selections. If the score reaches 10, the best source will
be reselected and the scores will be reset to 1.
*Interval*:::
This column displays the lower and upper endpoint of the interval which was
expected to contain the true offset of the local clock considering the root
distance at the time of the selection.
[[reselect]]*reselect*::
To avoid excessive switching between sources, *chronyd* can stay synchronised
to a source even when it is not currently the best one among the available

View file

@ -126,6 +126,7 @@ static const struct request_length request_lengths[] = {
REQ_LENGTH_ENTRY(auth_data, auth_data), /* AUTH_DATA */
REQ_LENGTH_ENTRY(client_accesses_by_index,
client_accesses_by_index), /* CLIENT_ACCESSES_BY_INDEX3 */
REQ_LENGTH_ENTRY(select_data, select_data), /* SELECT_DATA */
};
static const uint16_t reply_lengths[] = {
@ -152,6 +153,7 @@ static const uint16_t reply_lengths[] = {
RPY_LENGTH_ENTRY(auth_data), /* AUTH_DATA */
RPY_LENGTH_ENTRY(client_accesses_by_index), /* CLIENT_ACCESSES_BY_INDEX3 */
RPY_LENGTH_ENTRY(server_stats), /* SERVER_STATS2 */
RPY_LENGTH_ENTRY(select_data), /* SELECT_DATA */
};
/* ================================================== */

View file

@ -178,4 +178,17 @@ typedef struct {
int nak;
} RPT_AuthReport;
typedef struct {
uint32_t ref_id;
IPAddr ip_addr;
char state_char;
int authentication;
int conf_options;
int eff_options;
uint32_t last_sample_ago;
double score;
double lo_limit;
double hi_limit;
} RPT_SelectReport;
#endif /* GOT_REPORTS_H */

View file

@ -316,6 +316,8 @@ SRC_ResetInstance(SRC_Instance instance)
instance->leap = LEAP_Unsynchronised;
instance->leap_vote = 0;
memset(&instance->sel_info, 0, sizeof (instance->sel_info));
SST_ResetInstance(instance->stats);
}
@ -1509,6 +1511,78 @@ SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report, struct timespec
/* ================================================== */
static char
get_status_char(SRC_Status status)
{
switch (status) {
case SRC_UNSELECTABLE:
return 'N';
case SRC_BAD_STATS:
return 'M';
case SRC_BAD_DISTANCE:
return 'd';
case SRC_JITTERY:
return '~';
case SRC_WAITS_STATS:
return 'w';
case SRC_STALE:
return 'S';
case SRC_ORPHAN:
return 'O';
case SRC_UNTRUSTED:
return 'T';
case SRC_FALSETICKER:
return 'x';
case SRC_WAITS_SOURCES:
return 'W';
case SRC_NONPREFERRED:
return 'P';
case SRC_WAITS_UPDATE:
return 'U';
case SRC_DISTANT:
return 'D';
case SRC_OUTLIER:
return 'L';
case SRC_UNSELECTED:
return '+';
case SRC_SELECTED:
return '*';
default:
return '?';
}
}
/* ================================================== */
int
SRC_GetSelectReport(int index, RPT_SelectReport *report)
{
SRC_Instance inst;
if (index >= n_sources || index < 0)
return 0;
inst = sources[index];
report->ref_id = inst->ref_id;
if (inst->ip_addr)
report->ip_addr = *inst->ip_addr;
else
report->ip_addr.family = IPADDR_UNSPEC;
report->state_char = get_status_char(inst->status);
report->authentication = inst->authenticated;
report->conf_options = inst->conf_sel_options;
report->eff_options = inst->sel_options;
report->last_sample_ago = inst->sel_info.last_sample_ago;
report->score = inst->sel_score;
report->lo_limit = inst->sel_info.lo_limit;
report->hi_limit = inst->sel_info.hi_limit;
return 1;
}
/* ================================================== */
SRC_Type
SRC_GetType(int index)
{

View file

@ -130,9 +130,10 @@ extern int SRC_IsSyncPeer(SRC_Instance inst);
extern int SRC_IsReachable(SRC_Instance inst);
extern int SRC_ReadNumberOfSources(void);
extern int SRC_ActiveSources(void);
extern int SRC_ReportSource(int index, RPT_SourceReport *report, struct timespec *now);
extern int SRC_ReportSource(int index, RPT_SourceReport *report, struct timespec *now);
extern int SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report, struct timespec *now);
extern int SRC_GetSelectReport(int index, RPT_SelectReport *report);
extern SRC_Type SRC_GetType(int index);