d9439df6f67f5ecb663f9246e51cdf35f1655652
1 /* chrony plugin for collectd
2 (c) 2015 by Claudius M Zingerli, ZSeng
3 Internas roughly based on the ntpd plugin
4 License: GPL2
5 */
6 /* TODO:
7 * - More robust udp parsing (using offsets instead of structs?)
8 * - Plausibility checks on values received
9 *
10 *
11 */
13 /* getaddrinfo */
14 #include <sys/types.h>
15 #include <sys/socket.h>
16 #include <netdb.h>
18 #include "collectd.h"
19 #include "common.h" /* auxiliary functions */
20 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
22 static const char *g_config_keys[] =
23 {
24 "Host",
25 "Port",
26 "Timeout"
27 };
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 */
36 /*BEGIN*/
37 #define PROTO_VERSION_NUMBER 6
39 #define REQ_N_SOURCES 14
40 #define REQ_SOURCE_DATA 15
41 #define REQ_SOURCE_STATS 34
43 #define PKT_TYPE_CMD_REQUEST 1
44 #define PKT_TYPE_CMD_REPLY 2
46 #define RPY_NULL 1
47 #define RPY_N_SOURCES 2
48 #define RPY_SOURCE_DATA 3
49 #define RPY_MANUAL_TIMESTAMP 4
50 #define RPY_TRACKING 5
51 #define RPY_SOURCE_STATS 6
52 #define RPY_RTC 7
54 #define IPADDR_UNSPEC 0
55 #define IPADDR_INET4 1
56 #define IPADDR_INET6 2
58 #define ATTRIB_PACKED __attribute__((packed))
59 typedef struct ATTRIB_PACKED
60 {
61 int32_t f;
62 } Float;
63 /*END*/
65 typedef enum
66 {
67 STT_SUCCESS = 0,
68 STT_FAILED = 1,
69 STT_UNAUTH = 2,
70 STT_INVALID = 3,
71 STT_NOSUCHSOURCE = 4,
72 STT_INVALIDTS = 5,
73 STT_NOTENABLED = 6,
74 STT_BADSUBNET = 7,
75 STT_ACCESSALLOWED = 8,
76 STT_ACCESSDENIED = 9,
77 STT_NOHOSTACCESS = 10,
78 STT_SOURCEALREADYKNOWN = 11,
79 STT_TOOMANYSOURCES = 12,
80 STT_NORTC = 13,
81 STT_BADRTCFILE = 14,
82 STT_INACTIVE = 15,
83 STT_BADSAMPLE = 16,
84 STT_INVALIDAF = 17,
85 STT_BADPKTVERSION = 18,
86 STT_BADPKTLENGTH = 19,
87 } eChrony_Status;
89 typedef struct ATTRIB_PACKED
90 {
91 uint32_t f_n_sources;
92 } tChrony_N_Sources;
94 typedef struct ATTRIB_PACKED
95 {
96 int32_t f_index;
97 uint8_t f_dummy0[44];
98 } tChrony_Req_Source_data;
100 typedef struct ATTRIB_PACKED
101 {
102 int32_t f_index;
103 uint8_t f_dummy0[56];
104 } tChrony_Req_Source_stats;
106 #define IPV6_STR_MAX_SIZE 40
107 typedef struct ATTRIB_PACKED
108 {
109 union
110 {
111 uint32_t ip4;
112 uint8_t ip6[16];
113 } addr;
114 uint16_t f_family;
115 } tChrony_IPAddr;
117 typedef struct ATTRIB_PACKED
118 {
119 tChrony_IPAddr addr;
120 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
121 int16_t f_poll;
122 uint16_t f_stratum;
123 uint16_t f_state;
124 uint16_t f_mode;
125 uint16_t f_flags;
126 uint16_t f_reachability;
128 uint32_t f_since_sample;
129 Float f_origin_latest_meas;
130 Float f_latest_meas;
131 Float f_latest_meas_err;
132 } tChrony_Resp_Source_data;
134 typedef struct ATTRIB_PACKED
135 {
136 uint32_t f_ref_id;
137 tChrony_IPAddr addr;
138 uint16_t dummy; /* FIXME: Strange dummy space. Needed on gcc 4.8.3 on x86_64 */
139 uint32_t f_n_samples; //Number of measurements done
140 uint32_t f_n_runs; //How many measurements to come
141 uint32_t f_span_seconds; //For how long we're measuring
142 Float f_rtc_seconds_fast;
143 Float f_rtc_gain_rate_ppm; //Estimated relative frequency error
144 Float f_skew_ppm; //Clock skew
145 Float f_est_offset; //Estimated offset of source
146 Float f_est_offset_err; //Error of estimation
147 } tChrony_Resp_Source_stats;
150 typedef struct ATTRIB_PACKED
151 {
152 struct
153 {
154 uint8_t f_version;
155 uint8_t f_type;
156 uint8_t f_dummy0;
157 uint8_t f_dummy1;
158 uint16_t f_cmd;
159 uint16_t f_cmd_try;
160 uint32_t f_seq;
162 uint32_t f_dummy2;
163 uint32_t f_dummy3;
164 } header; /* Packed: 20Bytes */
165 union
166 {
167 tChrony_N_Sources n_sources; /* Packed: 4 Bytes */
168 tChrony_Req_Source_data source_data;
169 tChrony_Req_Source_stats source_stats;
170 } body;
171 uint8_t padding[4+16]; /* Padding to match minimal response size */
172 } tChrony_Request;
174 typedef struct ATTRIB_PACKED
175 {
176 struct
177 {
178 uint8_t f_version;
179 uint8_t f_type;
180 uint8_t f_dummy0;
181 uint8_t f_dummy1;
182 uint16_t f_cmd;
183 uint16_t f_reply;
184 uint16_t f_status;
185 uint16_t f_dummy2;
186 uint16_t f_dummy3;
187 uint16_t f_dummy4;
188 uint32_t f_seq;
189 uint32_t f_dummy5;
190 uint32_t f_dummy6;
191 } header; /* Packed: 28 Bytes */
193 /*uint32_t EOR;*/
195 union
196 {
197 tChrony_N_Sources n_sources;
198 tChrony_Resp_Source_data source_data;
199 tChrony_Resp_Source_stats source_stats;
200 } body;
202 uint8_t padding[1024];
203 } tChrony_Response;
205 static int g_is_connected = 0;
206 static int g_chrony_socket = -1;
207 static time_t g_chrony_timeout = 0;
208 static char *g_chrony_host = NULL;
209 static char *g_chrony_port = NULL;
210 static uint32_t g_chrony_seq = 0;
212 /*****************************************************************************/
213 /* Internal functions */
214 /*****************************************************************************/
215 /* Code from: http://long.ccaba.upc.edu/long/045Guidelines/eva/ipv6.html#daytimeClient6 */
216 /*BEGIN*/
217 static int
218 connect_client (const char *hostname,
219 const char *service,
220 int family,
221 int socktype)
222 {
223 struct addrinfo hints, *res, *ressave;
224 int n, sockfd;
226 memset(&hints, 0, sizeof(struct addrinfo));
228 hints.ai_family = family;
229 hints.ai_socktype = socktype;
231 n = getaddrinfo(hostname, service, &hints, &res);
233 if (n <0)
234 {
235 ERROR ("chrony plugin: getaddrinfo error:: [%s]", gai_strerror(n));
236 return -1;
237 }
239 ressave = res;
241 sockfd=-1;
242 while (res)
243 {
244 sockfd = socket(res->ai_family,
245 res->ai_socktype,
246 res->ai_protocol);
248 if (!(sockfd < 0))
249 {
250 if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
251 {
252 break;
253 }
255 close(sockfd);
256 sockfd=-1;
257 }
258 res=res->ai_next;
259 }
261 freeaddrinfo(ressave);
262 return sockfd;
263 }
264 /*Code originally from: https://github.com/mlichvar/chrony/blob/master/util.c */
265 /*char * UTI_IPToString(IPAddr *addr)*/
266 char * niptoha(const tChrony_IPAddr *addr,char *p_buf, size_t p_buf_size)
267 {
268 unsigned long a, b, c, d, ip;
269 const uint8_t *ip6;
271 switch (ntohs(addr->f_family))
272 {
273 case IPADDR_UNSPEC:
274 snprintf(p_buf, p_buf_size, "[UNSPEC]");
275 break;
276 case IPADDR_INET4:
277 ip = ntohl(addr->addr.ip4);
278 a = (ip>>24) & 0xff;
279 b = (ip>>16) & 0xff;
280 c = (ip>> 8) & 0xff;
281 d = (ip>> 0) & 0xff;
282 snprintf(p_buf, p_buf_size, "%ld.%ld.%ld.%ld", a, b, c, d);
283 break;
284 case IPADDR_INET6:
285 ip6 = addr->addr.ip6;
286 #if 0
287 /* FIXME: Detect little endian systems */
288 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
289 ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5], ip6[6], ip6[7],
290 ip6[8], ip6[9], ip6[10], ip6[11], ip6[12], ip6[13], ip6[14], ip6[15]);
291 #else
292 snprintf(p_buf, p_buf_size, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
293 ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0], ip6[9], ip6[8],
294 ip6[7], ip6[6], ip6[5], ip6[4], ip6[3], ip6[2], ip6[1], ip6[0]);
295 #endif
296 break;
297 default:
298 snprintf(p_buf, p_buf_size, "[UNKNOWN]");
299 }
300 return p_buf;
301 }
302 /*END*/
304 static int chrony_set_timeout()
305 {
306 struct timeval tv;
307 tv.tv_sec = g_chrony_timeout;
308 tv.tv_usec = 0;
310 assert(g_chrony_socket>=0);
311 if (setsockopt(g_chrony_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) < 0)
312 {
313 return (1);
314 }
315 return (0);
316 }
318 static int chrony_connect()
319 {
320 if (g_chrony_host == NULL)
321 {
322 g_chrony_host = strdup(CHRONY_DEFAULT_HOST);
323 }
324 if (g_chrony_port == NULL)
325 {
326 g_chrony_port = strdup(CHRONY_DEFAULT_PORT);
327 }
328 if (g_chrony_timeout <= 0)
329 {
330 g_chrony_timeout = CHRONY_DEFAULT_TIMEOUT;
331 }
334 DEBUG("chrony plugin: Connecting to %s:%s", g_chrony_host, g_chrony_port);
335 int socket = connect_client(g_chrony_host, g_chrony_port, AF_UNSPEC, SOCK_DGRAM);
336 if (socket < 0)
337 {
338 ERROR ("chrony plugin: Error connecting to daemon. Errno = %d", errno);
339 return 1;
340 }
341 DEBUG("chrony plugin: Connected");
342 g_chrony_socket = socket;
344 if (chrony_set_timeout())
345 {
346 ERROR ("chrony plugin: Error setting timeout to %lds. Errno = %d", g_chrony_timeout, errno);
347 return 1;
348 }
349 return 0;
350 }
352 static int chrony_send_request(const tChrony_Request *p_req, size_t p_req_size)
353 {
354 if (send(g_chrony_socket,p_req,p_req_size,0) < 0)
355 {
356 ERROR ("chrony plugin: Error sending packet. Errno = %d", errno);
357 return 1;
358 } else {
359 return 0;
360 }
361 }
363 static int chrony_recv_response(tChrony_Response *p_resp, size_t p_resp_max_size, size_t *p_resp_size)
364 {
365 ssize_t rc = recv(g_chrony_socket,p_resp,p_resp_max_size,0);
366 if (rc <= 0)
367 {
368 ERROR ("chrony plugin: Error receiving packet. Errno = %d", errno);
369 return 1;
370 } else {
371 *p_resp_size = rc;
372 return 0;
373 }
374 }
376 static int chrony_query(const int p_command, tChrony_Request *p_req, tChrony_Response *p_resp, size_t *p_resp_size)
377 {
378 /* Check connection. We simply perform one try as collectd already handles retries */
379 assert(p_req);
380 assert(p_resp);
381 assert(p_resp_size);
383 if (g_is_connected == 0)
384 {
385 if (chrony_connect() == 0)
386 {
387 g_is_connected = 1;
388 } else {
389 ERROR ("chrony plugin: Unable to connect. Errno = %d", errno);
390 return 1;
391 }
392 }
395 do
396 {
397 int valid_command = 0;
398 size_t req_size = sizeof(p_req->header) + sizeof(p_req->padding);
399 size_t resp_size = sizeof(p_resp->header);
400 uint16_t resp_code = RPY_NULL;
401 switch (p_command)
402 {
403 case REQ_N_SOURCES:
404 req_size += sizeof(p_req->body.n_sources);
405 resp_size += sizeof(p_resp->body.n_sources);
406 resp_code = RPY_N_SOURCES;
407 valid_command = 1;
408 break;
409 case REQ_SOURCE_DATA:
410 req_size += sizeof(p_req->body.source_data);
411 resp_size += sizeof(p_resp->body.source_data);
412 resp_code = RPY_SOURCE_DATA;
413 valid_command = 1;
414 break;
415 case REQ_SOURCE_STATS:
416 req_size += sizeof(p_req->body.source_stats);
417 resp_size += sizeof(p_resp->body.source_stats);
418 resp_code = RPY_SOURCE_STATS;
419 valid_command = 1;
420 break;
421 default:
422 ERROR ("chrony plugin: Unknown request command (Was: %d)", p_command);
423 break;
424 }
426 if (valid_command == 0)
427 {
428 break;
429 }
431 p_req->header.f_cmd = htons(p_command);
432 p_req->header.f_cmd_try = 0;
433 p_req->header.f_seq = htonl(g_chrony_seq++);
435 DEBUG("chrony plugin: Sending request");
436 if (chrony_send_request(p_req,req_size) != 0)
437 {
438 break;
439 }
441 DEBUG("chrony plugin: Waiting for response");
442 if (chrony_recv_response(p_resp,resp_size,p_resp_size) != 0)
443 {
444 break;
445 }
446 DEBUG("chrony plugin: Received response: .version = %u, .type = %u, .cmd = %u, .reply = %u, .status = %u, .seq = %u",p_resp->header.f_version,p_resp->header.f_type,ntohs(p_resp->header.f_cmd),ntohs(p_resp->header.f_reply),ntohs(p_resp->header.f_status),ntohl(p_resp->header.f_seq));
448 if (p_resp->header.f_version != p_req->header.f_version)
449 {
450 ERROR("chrony plugin: Wrong protocol version (Was: %d, expected: %d)", p_resp->header.f_version, p_req->header.f_version);
451 return 1;
452 }
453 if (p_resp->header.f_type != PKT_TYPE_CMD_REPLY)
454 {
455 ERROR("chrony plugin: Wrong packet type (Was: %d, expected: %d)", p_resp->header.f_type, PKT_TYPE_CMD_REPLY);
456 return 1;
457 }
458 if (p_resp->header.f_seq != p_req->header.f_seq)
459 {
460 /* FIXME: Implement sequence number handling */
461 ERROR("chrony plugin: Unexpected sequence number (Was: %d, expected: %d)", p_resp->header.f_seq, p_req->header.f_seq);
462 return 1;
463 }
464 if (p_resp->header.f_cmd != p_req->header.f_cmd)
465 {
466 ERROR("chrony plugin: Wrong reply command (Was: %d, expected: %d)", p_resp->header.f_cmd, p_req->header.f_cmd);
467 return 1;
468 }
470 if (ntohs(p_resp->header.f_reply) != resp_code)
471 {
472 ERROR("chrony plugin: Wrong reply code (Was: %d, expected: %d)", ntohs(p_resp->header.f_reply), p_command);
473 return 1;
474 }
476 switch (p_resp->header.f_status)
477 {
478 case STT_SUCCESS:
479 DEBUG("chrony plugin: Reply packet status STT_SUCCESS");
480 break;
481 default:
482 ERROR("chrony plugin: Reply packet contains error status: %d (expected: %d)", p_resp->header.f_status, STT_SUCCESS);
483 return 1;
484 }
486 //Good result
487 return 0;
488 } while (0);
490 //Some error occured
491 return 1;
492 }
494 static void chrony_init_req(tChrony_Request *p_req)
495 {
496 DEBUG("chrony plugin: Clearing %ld bytes",sizeof(*p_req));
497 memset(p_req,0,sizeof(*p_req));
498 p_req->header.f_version = PROTO_VERSION_NUMBER;
499 p_req->header.f_type = PKT_TYPE_CMD_REQUEST;
500 p_req->header.f_dummy0 = 0;
501 p_req->header.f_dummy1 = 0;
502 p_req->header.f_dummy2 = 0;
503 p_req->header.f_dummy3 = 0;
504 }
506 /* Code from: https://github.com/mlichvar/chrony/blob/master/util.c (GPLv2) */
507 /*BEGIN*/
508 #define FLOAT_EXP_BITS 7
509 #define FLOAT_EXP_MIN (-(1 << (FLOAT_EXP_BITS - 1)))
510 #define FLOAT_EXP_MAX (-FLOAT_EXP_MIN - 1)
511 #define FLOAT_COEF_BITS ((int)sizeof (int32_t) * 8 - FLOAT_EXP_BITS)
512 #define FLOAT_COEF_MIN (-(1 << (FLOAT_COEF_BITS - 1)))
513 #define FLOAT_COEF_MAX (-FLOAT_COEF_MIN - 1)
515 /* double UTI_FloatNetworkToHost(Float f) */
516 double ntohf(Float f)
517 {
518 int32_t exp, coef, x;
520 x = ntohl(f.f);
521 exp = (x >> FLOAT_COEF_BITS) - FLOAT_COEF_BITS;
522 coef = x << FLOAT_EXP_BITS >> FLOAT_EXP_BITS;
523 return coef * pow(2.0, exp);
524 }
525 /*END*/
527 /* Code from: collectd/src/ntpd.c (MIT) */
528 /*BEGIN*/
529 static void chrony_push_data(char *type, char *type_inst, double value)
530 {
531 value_t values[1];
532 value_list_t vl = VALUE_LIST_INIT;
534 values[0].gauge = value;
536 vl.values = values;
537 vl.values_len = 1;
538 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
539 sstrncpy (vl.plugin, "chrony", sizeof (vl.plugin));
540 sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
541 sstrncpy (vl.type, type, sizeof (vl.type));
542 sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
544 plugin_dispatch_values (&vl);
545 }
546 /*END*/
548 /*****************************************************************************/
549 /* Exported functions */
550 /*****************************************************************************/
551 static int chrony_config(const char *p_key, const char *p_value)
552 {
553 assert(p_key);
554 assert(p_value);
555 /* Parse config variables */
556 if (strcasecmp(p_key, "Host") == 0)
557 {
558 if (g_chrony_host != NULL)
559 {
560 free (g_chrony_host);
561 }
562 if ((g_chrony_host = strdup (p_value)) == NULL)
563 {
564 ERROR ("chrony plugin: Error duplicating host name");
565 return 1;
566 }
567 } else if (strcasecmp(p_key, "Port") == 0)
568 {
569 if (g_chrony_port != NULL)
570 {
571 free (g_chrony_port);
572 }
573 if ((g_chrony_port = strdup (p_value)) == NULL)
574 {
575 ERROR ("chrony plugin: Error duplicating port name");
576 return 1;
577 }
578 } else if (strcasecmp(p_key, "Timeout") == 0)
579 {
580 time_t tosec = strtol(p_value,NULL,0);
581 g_chrony_timeout = tosec;
582 } else {
583 WARNING("chrony plugin: Unknown configuration variable: %s %s",p_key,p_value);
584 return 1;
585 }
586 return 0;
587 }
589 static int chrony_request_sources_count(unsigned int *p_count)
590 {
591 int rc;
592 size_t chrony_resp_size;
593 tChrony_Request chrony_req;
594 tChrony_Response chrony_resp;
596 DEBUG("chrony plugin: Requesting data");
597 chrony_init_req(&chrony_req);
598 rc = chrony_query (REQ_N_SOURCES, &chrony_req, &chrony_resp, &chrony_resp_size);
599 if (rc != 0)
600 {
601 ERROR ("chrony plugin: chrony_query (REQ_N_SOURCES) failed with status %i", rc);
602 return rc;
603 }
605 *p_count = ntohl(chrony_resp.body.n_sources.f_n_sources);
606 DEBUG("chrony plugin: Getting data of %d clock sources", *p_count);
607 return 0;
608 }
610 static int chrony_request_source_data(int p_src_idx)
611 {
612 //Source data request
613 size_t chrony_resp_size;
614 tChrony_Request chrony_req;
615 tChrony_Response chrony_resp;
616 char src_addr[IPV6_STR_MAX_SIZE];
618 chrony_init_req(&chrony_req);
619 chrony_req.body.source_data.f_index = htonl(p_src_idx);
620 int rc = chrony_query(REQ_SOURCE_DATA, &chrony_req, &chrony_resp, &chrony_resp_size);
621 if (rc != 0)
622 {
623 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_DATA) failed with status %i", rc);
624 return rc;
625 }
626 memset(src_addr, 0, sizeof(src_addr));
627 niptoha(&chrony_resp.body.source_data.addr, src_addr, sizeof(src_addr));
628 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",
629 p_src_idx,
630 src_addr,
631 ntohs(chrony_resp.body.source_data.f_poll),
632 ntohs(chrony_resp.body.source_data.f_stratum),
633 ntohs(chrony_resp.body.source_data.f_state),
634 ntohs(chrony_resp.body.source_data.f_mode),
635 ntohs(chrony_resp.body.source_data.f_flags),
636 ntohs(chrony_resp.body.source_data.f_reachability),
637 ntohl(chrony_resp.body.source_data.f_since_sample),
638 ntohf(chrony_resp.body.source_data.f_origin_latest_meas),
639 ntohf(chrony_resp.body.source_data.f_latest_meas),
640 ntohf(chrony_resp.body.source_data.f_latest_meas_err)
641 );
642 chrony_push_data("clock_stratum", src_addr,ntohs(chrony_resp.body.source_data.f_stratum));
643 chrony_push_data("clock_state", src_addr,ntohs(chrony_resp.body.source_data.f_state));
644 chrony_push_data("clock_mode", src_addr,ntohs(chrony_resp.body.source_data.f_mode));
645 chrony_push_data("clock_reachability",src_addr,ntohs(chrony_resp.body.source_data.f_reachability));
646 chrony_push_data("clock_last_meas", src_addr,ntohs(chrony_resp.body.source_data.f_since_sample));
647 return 0;
648 }
651 static int chrony_request_source_stats(int p_src_idx)
652 {
653 //Source stats request
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 chrony_req.body.source_stats.f_index = htonl(p_src_idx);
661 int rc = chrony_query(REQ_SOURCE_STATS, &chrony_req, &chrony_resp, &chrony_resp_size);
662 if (rc != 0)
663 {
664 ERROR ("chrony plugin: chrony_query (REQ_SOURCE_STATS) failed with status %i", rc);
665 return rc;
666 }
668 memset(src_addr, 0, sizeof(src_addr));
669 niptoha(&chrony_resp.body.source_stats.addr, src_addr, sizeof(src_addr));
670 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",
671 p_src_idx,
672 src_addr,
673 ntohl(chrony_resp.body.source_stats.f_ref_id),
674 ntohl(chrony_resp.body.source_stats.f_n_samples),
675 ntohl(chrony_resp.body.source_stats.f_n_runs),
676 ntohl(chrony_resp.body.source_stats.f_span_seconds),
677 ntohf(chrony_resp.body.source_stats.f_rtc_seconds_fast),
678 ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm),
679 ntohf(chrony_resp.body.source_stats.f_skew_ppm),
680 ntohf(chrony_resp.body.source_stats.f_est_offset),
681 ntohf(chrony_resp.body.source_stats.f_est_offset_err)
682 );
683 chrony_push_data("clock_skew_ppm", src_addr,ntohf(chrony_resp.body.source_stats.f_skew_ppm));
684 chrony_push_data("frequency_error", src_addr,ntohf(chrony_resp.body.source_stats.f_rtc_gain_rate_ppm)); /* unit: ppm */
685 chrony_push_data("time_offset", src_addr,ntohf(chrony_resp.body.source_stats.f_est_offset)); /* unit: s */
686 return 0;
687 }
689 static int chrony_read (void)
690 {
691 //Get number of time sources, then check every source for status
692 int rc;
694 unsigned int now_src, n_sources;
695 rc = chrony_request_sources_count(&n_sources);
696 if (rc != 0)
697 {
698 return rc;
699 }
701 for (now_src = 0; now_src < n_sources; ++now_src)
702 {
703 rc = chrony_request_source_data(now_src);
704 if (rc != 0)
705 {
706 return rc;
707 }
709 rc = chrony_request_source_stats(now_src);
710 if (rc != 0)
711 {
712 return rc;
713 }
714 }
715 return 0;
716 }
718 static int chrony_shutdown()
719 {
720 if (g_is_connected != 0)
721 {
722 close(g_chrony_socket);
723 g_is_connected = 0;
724 }
725 if (g_chrony_host != NULL)
726 {
727 free (g_chrony_host);
728 g_chrony_host = NULL;
729 }
730 if (g_chrony_port != NULL)
731 {
732 free (g_chrony_port);
733 g_chrony_port = NULL;
734 }
735 return 0;
736 }
738 void module_register (void)
739 {
740 plugin_register_config( "chrony", chrony_config, g_config_keys, g_config_keys_num);
741 plugin_register_read( "chrony", chrony_read);
742 plugin_register_shutdown("chrony", chrony_shutdown);
743 }