+ chrony_init_req(&chrony_req);
+ rc =
+ chrony_query(REQ_TRACKING, &chrony_req, &chrony_resp, &chrony_resp_size);
+ if (rc != 0)
+ {
+ ERROR(PLUGIN_NAME ": chrony_query (REQ_TRACKING) failed with status %i",
+ rc);
+ return rc;
+ }
+#if COLLECT_DEBUG
+ {
+ char src_addr[IPV6_STR_MAX_SIZE];
+ memset(src_addr, 0, sizeof(src_addr));
+ niptoha(&chrony_resp.body.tracking.addr, src_addr, sizeof(src_addr));
+ DEBUG(PLUGIN_NAME ": Daemon stat: .addr = %s, .ref_id= %u, .stratum = %u, .leap_status = %u, .ref_time = %u:%u:%u, .current_correction = %f, .last_offset = %f, .rms_offset = %f, .freq_ppm = %f, .skew_ppm = %f, .root_delay = %f, .root_dispersion = %f, .last_update_interval = %f", src_addr, ntohs(chrony_resp.body.tracking.f_ref_id),
+ ntohs(chrony_resp.body.tracking.f_stratum),
+ ntohs(chrony_resp.body.tracking.f_leap_status),
+ ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_high),
+ ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_low),
+ ntohl(chrony_resp.body.tracking.f_ref_time.tv_nsec),
+ ntohf(chrony_resp.body.tracking.f_current_correction),
+ ntohf(chrony_resp.body.tracking.f_last_offset),
+ ntohf(chrony_resp.body.tracking.f_rms_offset),
+ ntohf(chrony_resp.body.tracking.f_freq_ppm),
+ ntohf(chrony_resp.body.tracking.f_skew_ppm),
+ ntohf(chrony_resp.body.tracking.f_root_delay),
+ ntohf(chrony_resp.body.tracking.f_root_dispersion),
+ ntohf(chrony_resp.body.tracking.f_last_update_interval));
+ }
+#endif
+
+ 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, 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;
+
+ char src_addr[IPV6_STR_MAX_SIZE];
+ memset(src_addr, 0, sizeof(src_addr));
+
+ 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;
+ }
+
+ niptoha(&chrony_resp.body.source_data.addr, src_addr, sizeof(src_addr));
+ 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,
+ ntohs(chrony_resp.body.source_data.f_since_sample));
+
+ return CHRONY_RC_OK;
+}
+
+
+static int
+chrony_request_source_stats(int p_src_idx, 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, time_offset;
+
+ char src_addr[IPV6_STR_MAX_SIZE];
+ memset(src_addr, 0, sizeof(src_addr));
+
+ if (*p_is_reachable == 0)
+ {
+ skew_ppm = 0;
+ frequency_error = 0;
+ time_offset = 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);
+ time_offset = ntohf(chrony_resp.body.source_stats.f_est_offset);
+
+ niptoha(&chrony_resp.body.source_stats.addr, src_addr, sizeof(src_addr));
+ 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, time_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 */
+ chrony_push_data_valid("time_offset", src_addr, *p_is_reachable, time_offset); /* unit: s */
+
+ return CHRONY_RC_OK;