1 /* chrony plugin for collectd
2 (c) 2015 by Claudius M Zingerli, ZSeng
3 Internas roughly based on the ntpd plugin
7 * - More robust udp parsing (using offsets instead of structs?)
8 * - Plausibility checks on values received
14 #include <sys/types.h>
15 #include <sys/socket.h>
19 #include "common.h" /* auxiliary functions */
20 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
22 static const char *g_config_keys[] =
29 static int g_config_keys_num = STATIC_ARRAY_SIZE (g_config_keys);
31 # define CHRONY_DEFAULT_HOST "localhost"
32 # define CHRONY_DEFAULT_PORT "323"
33 # define CHRONY_DEFAULT_TIMEOUT 2
35 /* Copied from chrony/candm.h */
37 #define PROTO_VERSION_NUMBER 6
47 #define PKT_TYPE_CMD_REQUEST 1
48 #define PKT_TYPE_CMD_REPLY 2
51 #define RPY_N_SOURCES 2
52 #define RPY_SOURCE_DATA 3
53 #define RPY_MANUAL_TIMESTAMP 4
54 #define RPY_TRACKING 5
55 #define RPY_SOURCE_STATS 6
58 #define IPADDR_UNSPEC 0
59 #define IPADDR_INET4 1
60 #define IPADDR_INET6 2
62 #define ATTRIB_PACKED __attribute__((packed))
63 typedef struct ATTRIB_PACKED
68 typedef struct ATTRIB_PACKED
87 STT_ACCESSALLOWED = 8,
89 STT_NOHOSTACCESS = 10,
90 STT_SOURCEALREADYKNOWN = 11,
91 STT_TOOMANYSOURCES = 12,
97 STT_BADPKTVERSION = 18,
98 STT_BADPKTLENGTH = 19,
101 typedef struct ATTRIB_PACKED
103 uint8_t f_dummy0[80];
104 } tChrony_Req_Tracking;
106 typedef struct ATTRIB_PACKED
108 uint32_t f_n_sources;
111 typedef struct ATTRIB_PACKED
114 uint8_t f_dummy0[44];
115 } tChrony_Req_Source_data;
117 typedef struct ATTRIB_PACKED
120 uint8_t f_dummy0[56];
121 } tChrony_Req_Source_stats;
123 #define IPV6_STR_MAX_SIZE 40
124 typedef struct ATTRIB_PACKED
134 typedef struct ATTRIB_PACKED
137 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
138 int16_t f_poll; // 2^f_poll = Time between polls (s)
139 uint16_t f_stratum; //Remote clock stratum
140 uint16_t f_state; //0 = RPY_SD_ST_SYNC, 1 = RPY_SD_ST_UNREACH, 2 = RPY_SD_ST_FALSETICKER
141 //3 = RPY_SD_ST_JITTERY, 4 = RPY_SD_ST_CANDIDATE, 5 = RPY_SD_ST_OUTLIER
142 uint16_t f_mode; //0 = RPY_SD_MD_CLIENT, 1 = RPY_SD_MD_PEER, 2 = RPY_SD_MD_REF
143 uint16_t f_flags; //unused
144 uint16_t f_reachability; //???
146 uint32_t f_since_sample; //Time since last sample (s)
147 tFloat f_origin_latest_meas; //
148 tFloat f_latest_meas; //
149 tFloat f_latest_meas_err; //
150 } tChrony_Resp_Source_data;
152 typedef struct ATTRIB_PACKED
156 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
157 uint32_t f_n_samples; //Number of measurements done
158 uint32_t f_n_runs; //How many measurements to come
159 uint32_t f_span_seconds; //For how long we're measuring
160 tFloat f_rtc_seconds_fast; //???
161 tFloat f_rtc_gain_rate_ppm; //Estimated relative frequency error
162 tFloat f_skew_ppm; //Clock skew (ppm) (worst case freq est error (skew: peak2peak))
163 tFloat f_est_offset; //Estimated offset of source
164 tFloat f_est_offset_err; //Error of estimation
165 } tChrony_Resp_Source_stats;
167 typedef struct ATTRIB_PACKED
171 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
173 uint16_t f_leap_status;
175 tFloat f_current_correction;
176 tFloat f_last_offset;
179 tFloat f_resid_freq_ppm;
182 tFloat f_root_dispersion;
183 tFloat f_last_update_interval;
184 } tChrony_Resp_Tracking;
186 typedef struct ATTRIB_PACKED
200 } header; /* Packed: 20Bytes */
203 tChrony_N_Sources n_sources;
204 tChrony_Req_Source_data source_data;
205 tChrony_Req_Source_stats source_stats;
206 tChrony_Req_Tracking tracking;
208 uint8_t padding[4+16]; /* Padding to match minimal response size */
211 typedef struct ATTRIB_PACKED
228 } header; /* Packed: 28 Bytes */
232 tChrony_N_Sources n_sources;
233 tChrony_Resp_Source_data source_data;
234 tChrony_Resp_Source_stats source_stats;
235 tChrony_Resp_Tracking tracking;
238 uint8_t padding[1024];
241 static int g_is_connected = 0;
242 static int g_chrony_socket = -1;
243 static time_t g_chrony_timeout = 0;
244 static char *g_chrony_host = NULL;
245 static char *g_chrony_port = NULL;
246 static uint32_t g_chrony_seq = 0;
248 /*****************************************************************************/
249 /* Internal functions */
250 /*****************************************************************************/
251 /* Code from: http://long.ccaba.upc.edu/long/045Guidelines/eva/ipv6.html#daytimeClient6 */
254 connect_client (const char *p_hostname,
259 struct addrinfo hints, *res, *ressave;
262 memset(&hints, 0, sizeof(struct addrinfo));
264 hints.ai_family = family;
265 hints.ai_socktype = socktype;
267 n = getaddrinfo(p_hostname, service, &hints, &res);
271 ERROR ("chrony plugin: getaddrinfo error:: [%s]", gai_strerror(n));
280 sockfd = socket(res->ai_family,
286 if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
297 freeaddrinfo(ressave);
300 /*Code originally from: git://git.tuxfamily.org/gitroot/chrony/chrony.git:util.c */
301 /*char * UTI_IPToString(IPAddr *addr)*/
302 char * niptoha(const tChrony_IPAddr *addr,char *p_buf, size_t p_buf_size)
304 unsigned long a, b, c, d, ip;
307 switch (ntohs(addr->f_family))
310 snprintf(p_buf, p_buf_size, "[UNSPEC]");
313 ip = ntohl(addr->addr.ip4);
318 snprintf(p_buf, p_buf_size, "%ld.%ld.%ld.%ld", a, b, c, d);
321 ip6 = addr->addr.ip6;
324 inet_ntop(AF_INET6, ip6, p_buf, p_bug_size);
327 /* FIXME: Detect little endian systems */
328 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
329 ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5], ip6[6], ip6[7],
330 ip6[8], ip6[9], ip6[10], ip6[11], ip6[12], ip6[13], ip6[14], ip6[15]);
332 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
333 ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0], ip6[9], ip6[8],
334 ip6[7], ip6[6], ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0]);
339 snprintf(p_buf, p_buf_size, "[UNKNOWN]");
345 static int chrony_set_timeout()
348 tv.tv_sec = g_chrony_timeout;
351 assert(g_chrony_socket>=0);
352 if (setsockopt(g_chrony_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) < 0)
359 static int chrony_connect()
361 if (g_chrony_host == NULL)
363 g_chrony_host = strdup(CHRONY_DEFAULT_HOST);
365 if (g_chrony_port == NULL)
367 g_chrony_port = strdup(CHRONY_DEFAULT_PORT);
369 if (g_chrony_timeout <= 0)
371 g_chrony_timeout = CHRONY_DEFAULT_TIMEOUT;
375 DEBUG("chrony plugin: Connecting to %s:%s", g_chrony_host, g_chrony_port);
376 int socket = connect_client(g_chrony_host, g_chrony_port, AF_UNSPEC, SOCK_DGRAM);
379 ERROR ("chrony plugin: Error connecting to daemon. Errno = %d", errno);
382 DEBUG("chrony plugin: Connected");
383 g_chrony_socket = socket;
385 if (chrony_set_timeout())
387 ERROR ("chrony plugin: Error setting timeout to %lds. Errno = %d", g_chrony_timeout, errno);
393 static int chrony_send_request(const tChrony_Request *p_req, size_t p_req_size)
395 if (send(g_chrony_socket,p_req,p_req_size,0) < 0)
397 ERROR ("chrony plugin: Error sending packet. Errno = %d", errno);
404 static int chrony_recv_response(tChrony_Response *p_resp, size_t p_resp_max_size, size_t *p_resp_size)
406 ssize_t rc = recv(g_chrony_socket,p_resp,p_resp_max_size,0);
409 ERROR ("chrony plugin: Error receiving packet. Errno = %d", errno);
417 static int chrony_query(const int p_command, tChrony_Request *p_req, tChrony_Response *p_resp, size_t *p_resp_size)
419 /* Check connection. We simply perform one try as collectd already handles retries */
424 if (g_is_connected == 0)
426 if (chrony_connect() == 0)
430 ERROR ("chrony plugin: Unable to connect. Errno = %d", errno);
438 int valid_command = 0;
439 size_t req_size = sizeof(p_req->header) + sizeof(p_req->padding);
440 size_t resp_size = sizeof(p_resp->header);
441 uint16_t resp_code = RPY_NULL;
445 req_size += sizeof(p_req->body.tracking);
446 resp_size += sizeof(p_resp->body.tracking);
447 resp_code = RPY_TRACKING;
451 req_size += sizeof(p_req->body.n_sources);
452 resp_size += sizeof(p_resp->body.n_sources);
453 resp_code = RPY_N_SOURCES;
456 case REQ_SOURCE_DATA:
457 req_size += sizeof(p_req->body.source_data);
458 resp_size += sizeof(p_resp->body.source_data);
459 resp_code = RPY_SOURCE_DATA;
462 case REQ_SOURCE_STATS:
463 req_size += sizeof(p_req->body.source_stats);
464 resp_size += sizeof(p_resp->body.source_stats);
465 resp_code = RPY_SOURCE_STATS;
469 ERROR ("chrony plugin: Unknown request command (Was: %d)", p_command);
473 if (valid_command == 0)
478 p_req->header.f_cmd = htons(p_command);
479 p_req->header.f_cmd_try = 0;
480 p_req->header.f_seq = htonl(g_chrony_seq++);
482 DEBUG("chrony plugin: Sending request (.cmd = %d, .seq = %d)",p_command,g_chrony_seq-1);
483 if (chrony_send_request(p_req,req_size) != 0)
488 DEBUG("chrony plugin: Waiting for response");
489 if (chrony_recv_response(p_resp,resp_size,p_resp_size) != 0)
493 DEBUG("chrony plugin: Received response: .version = %u, .type = %u, .cmd = %u, .reply = %u, .status = %u, .seq = %u",
494 p_resp->header.f_version,p_resp->header.f_type,ntohs(p_resp->header.f_cmd),
495 ntohs(p_resp->header.f_reply),ntohs(p_resp->header.f_status),ntohl(p_resp->header.f_seq));
497 if (p_resp->header.f_version != p_req->header.f_version)
499 ERROR("chrony plugin: Wrong protocol version (Was: %d, expected: %d)", p_resp->header.f_version, p_req->header.f_version);
502 if (p_resp->header.f_type != PKT_TYPE_CMD_REPLY)
504 ERROR("chrony plugin: Wrong packet type (Was: %d, expected: %d)", p_resp->header.f_type, PKT_TYPE_CMD_REPLY);
507 if (p_resp->header.f_seq != p_req->header.f_seq)
509 /* FIXME: Implement sequence number handling */
510 ERROR("chrony plugin: Unexpected sequence number (Was: %d, expected: %d)", p_resp->header.f_seq, p_req->header.f_seq);
513 if (p_resp->header.f_cmd != p_req->header.f_cmd)
515 ERROR("chrony plugin: Wrong reply command (Was: %d, expected: %d)", p_resp->header.f_cmd, p_req->header.f_cmd);
519 if (ntohs(p_resp->header.f_reply) != resp_code)
521 ERROR("chrony plugin: Wrong reply code (Was: %d, expected: %d)", ntohs(p_resp->header.f_reply), resp_code);
525 switch (p_resp->header.f_status)
528 DEBUG("chrony plugin: Reply packet status STT_SUCCESS");
531 ERROR("chrony plugin: Reply packet contains error status: %d (expected: %d)", p_resp->header.f_status, STT_SUCCESS);
543 static void chrony_init_req(tChrony_Request *p_req)
545 DEBUG("chrony plugin: Clearing %ld bytes",sizeof(*p_req));
546 memset(p_req,0,sizeof(*p_req));
547 p_req->header.f_version = PROTO_VERSION_NUMBER;
548 p_req->header.f_type = PKT_TYPE_CMD_REQUEST;
549 p_req->header.f_dummy0 = 0;
550 p_req->header.f_dummy1 = 0;
551 p_req->header.f_dummy2 = 0;
552 p_req->header.f_dummy3 = 0;
555 /* Code from: git://git.tuxfamily.org/gitroot/chrony/chrony.git:util.c (GPLv2) */
557 #define FLOAT_EXP_BITS 7
558 #define FLOAT_EXP_MIN (-(1 << (FLOAT_EXP_BITS - 1)))
559 #define FLOAT_EXP_MAX (-FLOAT_EXP_MIN - 1)
560 #define FLOAT_COEF_BITS ((int)sizeof (int32_t) * 8 - FLOAT_EXP_BITS)
561 #define FLOAT_COEF_MIN (-(1 << (FLOAT_COEF_BITS - 1)))
562 #define FLOAT_COEF_MAX (-FLOAT_COEF_MIN - 1)
564 /* double UTI_tFloatNetworkToHost(tFloat f) */
565 double ntohf(tFloat p_float)
570 uval = ntohl(p_float.value);
571 exp = (uval >> FLOAT_COEF_BITS) - FLOAT_COEF_BITS;
572 if (exp >= 1 << (FLOAT_EXP_BITS - 1))
574 exp -= 1 << FLOAT_EXP_BITS;
577 //coef = (x << FLOAT_EXP_BITS) >> FLOAT_EXP_BITS;
578 coef = uval % (1U << FLOAT_COEF_BITS);
579 if (coef >= 1 << (FLOAT_COEF_BITS - 1))
581 coef -= 1 << FLOAT_COEF_BITS;
583 return coef * pow(2.0, exp);
587 /* Code from: collectd/src/ntpd.c (MIT) */
589 static void chrony_push_data(char *type, char *type_inst, double value)
592 value_list_t vl = VALUE_LIST_INIT;
594 values[0].gauge = value; //TODO: Check type??? (counter, gauge, derive, absolute)
598 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
599 sstrncpy (vl.plugin, "chrony", sizeof (vl.plugin));
600 sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
601 sstrncpy (vl.type, type, sizeof (vl.type));
602 sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
604 plugin_dispatch_values (&vl);
608 /*****************************************************************************/
609 /* Exported functions */
610 /*****************************************************************************/
611 static int chrony_config(const char *p_key, const char *p_value)
615 /* Parse config variables */
616 if (strcasecmp(p_key, "Host") == 0)
618 if (g_chrony_host != NULL)
620 free (g_chrony_host);
622 if ((g_chrony_host = strdup (p_value)) == NULL)
624 ERROR ("chrony plugin: Error duplicating host name");
627 } else if (strcasecmp(p_key, "Port") == 0)
629 if (g_chrony_port != NULL)
631 free (g_chrony_port);
633 if ((g_chrony_port = strdup (p_value)) == NULL)
635 ERROR ("chrony plugin: Error duplicating port name");
638 } else if (strcasecmp(p_key, "Timeout") == 0)
640 time_t tosec = strtol(p_value,NULL,0);
641 g_chrony_timeout = tosec;
643 WARNING("chrony plugin: Unknown configuration variable: %s %s",p_key,p_value);
651 static int chrony_request_daemon_stats()
654 size_t chrony_resp_size;
655 tChrony_Request chrony_req;
656 tChrony_Response chrony_resp;
657 char src_addr[IPV6_STR_MAX_SIZE];
659 chrony_init_req(&chrony_req);
660 int rc = chrony_query(REQ_TRACKING, &chrony_req, &chrony_resp, &chrony_resp_size);
663 ERROR ("chrony plugin: chrony_query (REQ_TRACKING) failed with status %i", rc);
667 memset(src_addr, 0, sizeof(src_addr));
668 niptoha(&chrony_resp.body.tracking.addr, src_addr, sizeof(src_addr));
669 DEBUG("chrony plugin: Daemon stat: .addr = %s, .ref_id= %u, .stratum = %u, .leap_status = %u, .ref_time = %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",
671 ntohs(chrony_resp.body.tracking.f_ref_id), //FIXME: 16bit
672 ntohs(chrony_resp.body.tracking.f_stratum),
673 ntohs(chrony_resp.body.tracking.f_leap_status),
674 ntohl(chrony_resp.body.tracking.f_ref_time.tv_sec_high), //tTimeval
675 ntohf(chrony_resp.body.tracking.f_current_correction),
676 ntohf(chrony_resp.body.tracking.f_last_offset),
677 ntohf(chrony_resp.body.tracking.f_rms_offset),
678 ntohf(chrony_resp.body.tracking.f_freq_ppm),
679 ntohf(chrony_resp.body.tracking.f_skew_ppm),
680 ntohf(chrony_resp.body.tracking.f_root_delay),
681 ntohf(chrony_resp.body.tracking.f_root_dispersion),
682 ntohf(chrony_resp.body.tracking.f_last_update_interval)
685 chrony_push_data("clock_skew_ppm", src_addr,ntohf(chrony_resp.body.source_stats.f_skew_ppm));
686 chrony_push_data("frequency_error", src_addr,ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm)); /* unit: ppm */
687 chrony_push_data("time_offset", src_addr,ntohf(chrony_resp.body.source_stats.f_est_offset)); /* unit: s */
693 static int chrony_request_sources_count(unsigned int *p_count)
696 size_t chrony_resp_size;
697 tChrony_Request chrony_req;
698 tChrony_Response chrony_resp;
700 DEBUG("chrony plugin: Requesting data");
701 chrony_init_req(&chrony_req);
702 rc = chrony_query (REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
705 ERROR ("chrony plugin: chrony_query (REQ_N_SOURCES) failed with status %i", rc);
709 *p_count = ntohl(chrony_resp.body.n_sources.f_n_sources);
710 DEBUG("chrony plugin: Getting data of %d clock sources", *p_count);
716 static int chrony_request_source_data(int p_src_idx)
718 //Source data request
719 size_t chrony_resp_size;
720 tChrony_Request chrony_req;
721 tChrony_Response chrony_resp;
722 char src_addr[IPV6_STR_MAX_SIZE];
724 chrony_init_req(&chrony_req);
725 chrony_req.body.source_data.f_index = htonl(p_src_idx);
726 int rc = chrony_query(REQ_SOURCE_DATA, &chrony_req, &chrony_resp, &chrony_resp_size);
729 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_DATA) failed with status %i", rc);
732 memset(src_addr, 0, sizeof(src_addr));
733 niptoha(&chrony_resp.body.source_data.addr, src_addr, sizeof(src_addr));
734 DEBUG("chrony plugin: 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",
737 ntohs(chrony_resp.body.source_data.f_poll),
738 ntohs(chrony_resp.body.source_data.f_stratum),
739 ntohs(chrony_resp.body.source_data.f_state),
740 ntohs(chrony_resp.body.source_data.f_mode),
741 ntohs(chrony_resp.body.source_data.f_flags),
742 ntohs(chrony_resp.body.source_data.f_reachability),
743 ntohl(chrony_resp.body.source_data.f_since_sample),
744 ntohf(chrony_resp.body.source_data.f_origin_latest_meas),
745 ntohf(chrony_resp.body.source_data.f_latest_meas),
746 ntohf(chrony_resp.body.source_data.f_latest_meas_err)
748 chrony_push_data("clock_stratum", src_addr,ntohs(chrony_resp.body.source_data.f_stratum));
749 chrony_push_data("clock_state", src_addr,ntohs(chrony_resp.body.source_data.f_state));
750 chrony_push_data("clock_mode", src_addr,ntohs(chrony_resp.body.source_data.f_mode));
751 chrony_push_data("clock_reachability",src_addr,ntohs(chrony_resp.body.source_data.f_reachability));
752 chrony_push_data("clock_last_meas", src_addr,ntohs(chrony_resp.body.source_data.f_since_sample));
758 static int chrony_request_source_stats(int p_src_idx)
760 //Source stats request
761 size_t chrony_resp_size;
762 tChrony_Request chrony_req;
763 tChrony_Response chrony_resp;
764 char src_addr[IPV6_STR_MAX_SIZE];
766 chrony_init_req(&chrony_req);
767 chrony_req.body.source_stats.f_index = htonl(p_src_idx);
768 int rc = chrony_query(REQ_SOURCE_STATS, &chrony_req, &chrony_resp, &chrony_resp_size);
771 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_STATS) failed with status %i", rc);
775 memset(src_addr, 0, sizeof(src_addr));
776 niptoha(&chrony_resp.body.source_stats.addr, src_addr, sizeof(src_addr));
777 DEBUG("chrony plugin: 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",
780 ntohl(chrony_resp.body.source_stats.f_ref_id),
781 ntohl(chrony_resp.body.source_stats.f_n_samples),
782 ntohl(chrony_resp.body.source_stats.f_n_runs),
783 ntohl(chrony_resp.body.source_stats.f_span_seconds),
784 ntohf(chrony_resp.body.source_stats.f_rtc_seconds_fast),
785 ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm),
786 ntohf(chrony_resp.body.source_stats.f_skew_ppm),
787 ntohf(chrony_resp.body.source_stats.f_est_offset),
788 ntohf(chrony_resp.body.source_stats.f_est_offset_err)
790 chrony_push_data("clock_skew_ppm", src_addr,ntohf(chrony_resp.body.source_stats.f_skew_ppm));
791 chrony_push_data("frequency_error", src_addr,ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm)); /* unit: ppm */
792 chrony_push_data("time_offset", src_addr,ntohf(chrony_resp.body.source_stats.f_est_offset)); /* unit: s */
797 static int chrony_read()
802 rc = chrony_request_daemon_stats();
808 //Get number of time sources, then check every source for status
809 unsigned int now_src, n_sources;
810 rc = chrony_request_sources_count(&n_sources);
816 for (now_src = 0; now_src < n_sources; ++now_src)
818 rc = chrony_request_source_data(now_src);
824 rc = chrony_request_source_stats(now_src);
834 static int chrony_shutdown()
836 if (g_is_connected != 0)
838 close(g_chrony_socket);
841 if (g_chrony_host != NULL)
843 free (g_chrony_host);
844 g_chrony_host = NULL;
846 if (g_chrony_port != NULL)
848 free (g_chrony_port);
849 g_chrony_port = NULL;
855 void module_register (void)
857 plugin_register_config( "chrony", chrony_config, g_config_keys, g_config_keys_num);
858 plugin_register_read( "chrony", chrony_read);
859 plugin_register_shutdown("chrony", chrony_shutdown);