]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
cmdmon: add selectdata command
authorMiroslav Lichvar <mlichvar@redhat.com>
Mon, 25 May 2020 12:10:40 +0000 (14:10 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 4 Jun 2020 12:40:18 +0000 (14:40 +0200)
Add a command to report selection-specific data.

candm.h
client.c
cmdmon.c
doc/chronyc.adoc
pktlength.c
reports.h
sources.c
sources.h

diff --git a/candm.h b/candm.h
index e7a76d2a57de9062aa20e6a2600a96ab9bd7e631..0880df02698e63566e3a7b19082bb0eef38c7aa8 100644 (file)
--- a/candm.h
+++ b/candm.h
 #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;
index 8fcf449bfa3b7f6dd5fb9438ba534329800d6db9..4cd95f82e4d5245d247c9619a9b76809f7a66edb 100644 (file)
--- a/client.c
+++ b/client.c
@@ -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);
index 7ac79bc00227bef07ad56151c8f3146473e98513..5854badcefe344b2926b960328f0d64830fdab3a 100644 (file)
--- a/cmdmon.c
+++ b/cmdmon.c
@@ -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);
index cecd5cfb65e5311cadb4dea43d9d21042a575573..3e32f13ca01976c431a7d476797f3b3247b366b4 100644 (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
index 0721f09c223e9267299ed1e145cdd06a5ac8fac5..bc0055fe33bd379e5125c5d4810ab3085098d6c8 100644 (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 */
 };
 
 /* ================================================== */
index a41573ba653bd505bd49ac19ff34066010325c2d..a1244db6892fccac46872a5d25d6bcd92437e155 100644 (file)
--- a/reports.h
+++ b/reports.h
@@ -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 */
index 931406906b704d5085ab1b4996f39d4eabb6fead..160977bfb39bf7e323cc919502fbe51ca08b68ab 100644 (file)
--- a/sources.c
+++ b/sources.c
@@ -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)
 {
index d4c6f55287665c984aa58cbb9154bd7fee2452c7..a22faab933ac9800fa52eefab69d3f829e1d255f 100644 (file)
--- a/sources.h
+++ b/sources.h
@@ -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);