+
+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] = { 0 };
+
+ 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] = { 0 };
+
+ 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;
+}
+
+
+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;
+ }
+
+ /* 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)
+ {
+ int is_reachable;
+ rc = chrony_request_source_data(now_src, &is_reachable);
+ if (rc != CHRONY_RC_OK)
+ return rc;
+
+ rc = chrony_request_source_stats(now_src, &is_reachable);
+ if (rc != CHRONY_RC_OK)
+ return rc;
+
+ }
+ return CHRONY_RC_OK;
+}
+
+
+static int
+chrony_shutdown(void)
+{
+ /* Collectd shutdown callback: Free mem */
+ if (g_chrony_is_connected != 0)
+ {
+ close(g_chrony_socket);
+ g_chrony_is_connected = 0;
+ }
+ if (g_chrony_host != NULL)
+ sfree(g_chrony_host);
+
+ if (g_chrony_port != NULL)
+ sfree(g_chrony_port);
+
+ if (g_chrony_plugin_instance != NULL)
+ sfree(g_chrony_plugin_instance);
+
+ return CHRONY_RC_OK;
+}
+
+
+void
+module_register(void)