+
+ double time_ref = ntohl(chrony_resp.body.tracking.f_ref_time.tv_nsec);
+ time_ref /= 1000000000.0;
+ time_ref += ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_low);
+ if (chrony_resp.body.tracking.f_ref_time.tv_sec_high) {
+ double secs_high = ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_high);
+ secs_high *= 4294967296.0;
+ time_ref += secs_high;
+ }
+
+ /* Forward results to collectd-daemon */
+ /* Type_instance is always 'chrony' to tag daemon-wide data */
+ /* Type Type_instan Value */
+ chrony_push_data("clock_stratum", DAEMON_NAME,
+ ntohs(chrony_resp.body.tracking.f_stratum));
+ chrony_push_data("time_ref", DAEMON_NAME, time_ref); /* unit: s */
+ chrony_push_data(
+ "time_offset_ntp", DAEMON_NAME,
+ ntohf(chrony_resp.body.tracking.f_current_correction)); /* Offset between
+ system time and
+ NTP, unit: s */
+ chrony_push_data(
+ "time_offset", DAEMON_NAME,
+ ntohf(
+ chrony_resp.body.tracking
+ .f_last_offset)); /* Estimated Offset of the NTP time, unit: s */
+ chrony_push_data(
+ "time_offset_rms", DAEMON_NAME,
+ ntohf(chrony_resp.body.tracking
+ .f_rms_offset)); /* averaged value of the above, unit: s */
+ chrony_push_data(
+ "frequency_error", DAEMON_NAME,
+ ntohf(chrony_resp.body.tracking
+ .f_freq_ppm)); /* Frequency error of the local osc, unit: ppm */
+ chrony_push_data("clock_skew_ppm", DAEMON_NAME,
+ ntohf(chrony_resp.body.tracking.f_skew_ppm));
+ chrony_push_data(
+ "root_delay", DAEMON_NAME,
+ ntohf(chrony_resp.body.tracking.f_root_delay)); /* Network latency between
+ local daemon and the
+ current source */
+ chrony_push_data("root_dispersion", DAEMON_NAME,
+ ntohf(chrony_resp.body.tracking.f_root_dispersion));
+ chrony_push_data("clock_last_update", DAEMON_NAME,
+ ntohf(chrony_resp.body.tracking.f_last_update_interval));
+
+ return CHRONY_RC_OK;
+}
+
+static int chrony_request_sources_count(unsigned int *p_count) {
+ /* Requests the number of time sources from the chrony daemon */
+ int rc;
+ size_t chrony_resp_size;
+ tChrony_Request chrony_req;
+ tChrony_Response chrony_resp;
+
+ DEBUG(PLUGIN_NAME ": Requesting data");
+ chrony_init_req(&chrony_req);
+ rc =
+ chrony_query(REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
+ if (rc != 0) {
+ ERROR(PLUGIN_NAME ": chrony_query (REQ_N_SOURCES) failed with status %i",
+ rc);
+ return rc;
+ }
+
+ *p_count = ntohl(chrony_resp.body.n_sources.f_n_sources);
+ DEBUG(PLUGIN_NAME ": Getting data of %d clock sources", *p_count);
+
+ return CHRONY_RC_OK;
+}
+
+static int chrony_request_source_data(int p_src_idx, char *src_addr,
+ size_t addr_size, int *p_is_reachable) {
+ /* Perform Source data request for source #p_src_idx */
+ int rc;
+ size_t chrony_resp_size;
+ tChrony_Request chrony_req;
+ tChrony_Response chrony_resp;
+
+ chrony_init_req(&chrony_req);
+ chrony_req.body.source_data.f_index = htonl(p_src_idx);
+ rc = chrony_query(REQ_SOURCE_DATA, &chrony_req, &chrony_resp,
+ &chrony_resp_size);
+ if (rc != 0) {
+ ERROR(PLUGIN_NAME ": chrony_query (REQ_SOURCE_DATA) failed with status %i",
+ rc);
+ return rc;
+ }
+
+ if (ntohs(chrony_resp.body.source_data.f_mode) == MODE_REFCLOCK)
+ nreftostr(chrony_resp.body.source_data.addr.addr.ip4, src_addr, addr_size);
+ else
+ niptoha(&chrony_resp.body.source_data.addr, src_addr, addr_size);
+
+ DEBUG(PLUGIN_NAME ": Source[%d] data: .addr = %s, .poll = %u, .stratum = %u, "
+ ".state = %u, .mode = %u, .flags = %u, .reach = %u, "
+ ".latest_meas_ago = %u, .orig_latest_meas = %f, "
+ ".latest_meas = %f, .latest_meas_err = %f",
+ p_src_idx, src_addr, ntohs(chrony_resp.body.source_data.f_poll),
+ ntohs(chrony_resp.body.source_data.f_stratum),
+ ntohs(chrony_resp.body.source_data.f_state),
+ ntohs(chrony_resp.body.source_data.f_mode),
+ ntohs(chrony_resp.body.source_data.f_flags),
+ ntohs(chrony_resp.body.source_data.f_reachability),
+ ntohl(chrony_resp.body.source_data.f_since_sample),
+ ntohf(chrony_resp.body.source_data.f_origin_latest_meas),
+ ntohf(chrony_resp.body.source_data.f_latest_meas),
+ ntohf(chrony_resp.body.source_data.f_latest_meas_err));
+
+ /* Push NaN if source is currently not reachable */
+ int is_reachable = ntohs(chrony_resp.body.source_data.f_reachability) & 0x01;
+ *p_is_reachable = is_reachable;
+
+ /* Forward results to collectd-daemon */
+ chrony_push_data_valid("clock_stratum", src_addr, is_reachable,
+ ntohs(chrony_resp.body.source_data.f_stratum));
+ chrony_push_data_valid("clock_state", src_addr, is_reachable,
+ ntohs(chrony_resp.body.source_data.f_state));
+ chrony_push_data_valid("clock_mode", src_addr, is_reachable,
+ ntohs(chrony_resp.body.source_data.f_mode));
+ chrony_push_data_valid("clock_reachability", src_addr, is_reachable,
+ ntohs(chrony_resp.body.source_data.f_reachability));
+ chrony_push_data_valid("clock_last_meas", src_addr, is_reachable,
+ ntohl(chrony_resp.body.source_data.f_since_sample));
+ chrony_push_data_valid(
+ "time_offset", src_addr, is_reachable,
+ ntohf(chrony_resp.body.source_data.f_origin_latest_meas));
+
+ return CHRONY_RC_OK;
+}
+
+static int chrony_request_source_stats(int p_src_idx, const char *src_addr,
+ const int *p_is_reachable) {
+ /* Perform Source stats request for source #p_src_idx */
+ int rc;
+ size_t chrony_resp_size;
+ tChrony_Request chrony_req;
+ tChrony_Response chrony_resp;
+ double skew_ppm, frequency_error;
+
+ if (*p_is_reachable == 0) {
+ skew_ppm = 0;
+ frequency_error = 0;
+ } else {
+ chrony_init_req(&chrony_req);
+ chrony_req.body.source_stats.f_index = htonl(p_src_idx);
+ rc = chrony_query(REQ_SOURCE_STATS, &chrony_req, &chrony_resp,
+ &chrony_resp_size);
+ if (rc != 0) {
+ ERROR(PLUGIN_NAME
+ ": chrony_query (REQ_SOURCE_STATS) failed with status %i",
+ rc);
+ return rc;
+ }
+
+ skew_ppm = ntohf(chrony_resp.body.source_stats.f_skew_ppm);
+ frequency_error = ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm);
+
+ DEBUG(PLUGIN_NAME
+ ": Source[%d] stat: .addr = %s, .ref_id= %u, .n_samples = %u, "
+ ".n_runs = %u, .span_seconds = %u, .rtc_seconds_fast = %f, "
+ ".rtc_gain_rate_ppm = %f, .skew_ppm= %f, .est_offset = %f, "
+ ".est_offset_err = %f",
+ p_src_idx, src_addr, ntohl(chrony_resp.body.source_stats.f_ref_id),
+ ntohl(chrony_resp.body.source_stats.f_n_samples),
+ ntohl(chrony_resp.body.source_stats.f_n_runs),
+ ntohl(chrony_resp.body.source_stats.f_span_seconds),
+ ntohf(chrony_resp.body.source_stats.f_rtc_seconds_fast),
+ frequency_error, skew_ppm,
+ ntohf(chrony_resp.body.source_stats.f_est_offset),
+ ntohf(chrony_resp.body.source_stats.f_est_offset_err));
+
+ } /* if (*is_reachable) */
+
+ /* Forward results to collectd-daemon */
+ chrony_push_data_valid("clock_skew_ppm", src_addr, *p_is_reachable, skew_ppm);
+ chrony_push_data_valid("frequency_error", src_addr, *p_is_reachable,
+ frequency_error); /* unit: ppm */
+
+ return CHRONY_RC_OK;
+}
+
+static int chrony_read(void) {
+ /* collectd read callback: Perform data acquisition */
+ int rc;
+ unsigned int n_sources;
+
+ if (g_chrony_seq_is_initialized == 0) {
+ /* Seed RNG for sequence number generation */
+ rc = chrony_init_seq();
+ if (rc != CHRONY_RC_OK)
+ return rc;
+
+ g_chrony_seq_is_initialized = 1;
+ }
+
+ /* Ignore late responses that may have been received */
+ chrony_flush_recv_queue();
+
+ /* Get daemon stats */
+ rc = chrony_request_daemon_stats();
+ if (rc != CHRONY_RC_OK)
+ return rc;
+
+ /* Get number of time sources, then check every source for status */
+ rc = chrony_request_sources_count(&n_sources);
+ if (rc != CHRONY_RC_OK)
+ return rc;
+
+ for (unsigned int now_src = 0; now_src < n_sources; ++now_src) {
+ char src_addr[IPV6_STR_MAX_SIZE] = {0};
+ int is_reachable;
+ rc = chrony_request_source_data(now_src, src_addr, sizeof(src_addr),
+ &is_reachable);
+ if (rc != CHRONY_RC_OK)
+ return rc;
+
+ rc = chrony_request_source_stats(now_src, src_addr, &is_reachable);
+ if (rc != CHRONY_RC_OK)
+ return rc;
+ }
+ return CHRONY_RC_OK;