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