collectdctl: Moved examples section from help output to manpage.
[collectd.git] / src / collectdctl.c
1 /**
2  * collectd - src/collectdctl.c
3  * Copyright (C) 2010 Håkon J Dugstad Johnsen
4  * Copyright (C) 2010 Sebastian Harl
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Håkon J Dugstad Johnsen <hakon-dugstad.johnsen at telenor.com>
21  *   Sebastian "tokkee" Harl <sh@tokkee.org>
22  **/
23
24 #if HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include "libcollectdclient/client.h"
29
30 #include <assert.h>
31
32 #include <errno.h>
33
34 #include <getopt.h>
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #include <unistd.h>
41
42 #define DEFAULT_SOCK LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
43
44 extern char *optarg;
45 extern int   optind;
46
47 static void exit_usage (const char *name, int status) {
48   fprintf ((status == 0) ? stdout : stderr,
49       "Usage: %s [options] <command> [cmd options]\n\n"
50
51       "Available options:\n"
52       "  -s       Path to collectd's UNIX socket.\n"
53       "           Default: "DEFAULT_SOCK"\n"
54
55       "\n  -h       Display this help and exit.\n"
56
57       "\nAvailable commands:\n\n"
58
59       " * getval <identifier>\n"
60       " * flush [timeout=<seconds>] [plugin=<name>] [identifier=<id>]\n"
61       " * listval\n"
62
63       "\nIdentifiers:\n\n"
64
65       "An identifier has the following format:\n\n"
66
67       "  [<hostname>/]<plugin>[-<plugin_instance>]/<type>[-<type_instance>]\n\n"
68
69       "Hostname defaults to the local hostname if omitted (e.g., uptime/uptime).\n"
70       "No error is returned if the specified identifier does not exist.\n"
71
72       "\n"PACKAGE" "VERSION", http://collectd.org/\n"
73       "by Florian octo Forster <octo@verplant.org>\n"
74       "for contributions see `AUTHORS'\n"
75       , name);
76   exit (status);
77 }
78
79 /* Count the number of occurrences of the character 'chr'
80  * in the specified string. */
81 static int count_chars (const char *str, char chr) {
82   int count = 0;
83
84   while (*str != '\0') {
85     if (*str == chr) {
86       count++;
87     }
88     str++;
89   }
90
91   return count;
92 } /* count_chars */
93
94 static int array_grow (void **array, int *array_len, size_t elem_size)
95 {
96   void *tmp;
97
98   assert ((array != NULL) && (array_len != NULL));
99
100   tmp = realloc (*array, (*array_len + 1) * elem_size);
101   if (tmp == NULL) {
102     fprintf (stderr, "ERROR: Failed to allocate memory.\n");
103     return (-1);
104   }
105
106   *array = tmp;
107   ++(*array_len);
108   return (0);
109 } /* array_grow */
110
111 static int parse_identifier (lcc_connection_t *c,
112     const char *value, lcc_identifier_t *ident)
113 {
114   char hostname[1024];
115   char ident_str[1024] = "";
116   int  n_slashes;
117
118   int status;
119
120   n_slashes = count_chars (value, '/');
121   if (n_slashes == 1) {
122     /* The user has omitted the hostname part of the identifier
123      * (there is only one '/' in the identifier)
124      * Let's add the local hostname */
125     if (gethostname (hostname, sizeof (hostname)) != 0) {
126       fprintf (stderr, "ERROR: Failed to get local hostname: %s",
127           strerror (errno));
128       return (-1);
129     }
130     hostname[sizeof (hostname) - 1] = '\0';
131
132     snprintf (ident_str, sizeof (ident_str), "%s/%s", hostname, value);
133     ident_str[sizeof(ident_str) - 1] = '\0';
134   }
135   else {
136     strncpy (ident_str, value, sizeof (ident_str));
137     ident_str[sizeof (ident_str) - 1] = '\0';
138   }
139
140   status = lcc_string_to_identifier (c, ident, ident_str);
141   if (status != 0) {
142     fprintf (stderr, "ERROR: Failed to parse identifier ``%s'': %s.\n",
143         ident_str, lcc_strerror(c));
144     return (-1);
145   }
146   return (0);
147 } /* parse_identifier */
148
149 static int getval (lcc_connection_t *c, int argc, char **argv)
150 {
151   lcc_identifier_t ident;
152
153   size_t   ret_values_num   = 0;
154   gauge_t *ret_values       = NULL;
155   char   **ret_values_names = NULL;
156
157   int status;
158   size_t i;
159
160   assert (strcasecmp (argv[0], "getval") == 0);
161
162   if (argc != 2) {
163     fprintf (stderr, "ERROR: getval: Missing identifier.\n");
164     return (-1);
165   }
166
167   memset (&ident, 0, sizeof (ident));
168   status = parse_identifier (c, argv[1], &ident);
169   if (status != 0)
170     return (status);
171
172 #define BAIL_OUT(s) \
173   do { \
174     if (ret_values != NULL) \
175       free (ret_values); \
176     if (ret_values_names != NULL) { \
177       for (i = 0; i < ret_values_num; ++i) \
178         free (ret_values_names[i]); \
179       free (ret_values_names); \
180     } \
181     ret_values_num = 0; \
182     return (s); \
183   } while (0)
184
185   status = lcc_getval (c, &ident,
186       &ret_values_num, &ret_values, &ret_values_names);
187   if (status != 0) {
188     fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
189     BAIL_OUT (-1);
190   }
191
192   for (i = 0; i < ret_values_num; ++i)
193     printf ("%s=%e\n", ret_values_names[i], ret_values[i]);
194   BAIL_OUT (0);
195 #undef BAIL_OUT
196 } /* getval */
197
198 static int flush (lcc_connection_t *c, int argc, char **argv)
199 {
200   int timeout = -1;
201
202   lcc_identifier_t *identifiers = NULL;
203   int identifiers_num = 0;
204
205   char **plugins = NULL;
206   int plugins_num = 0;
207
208   int status;
209   int i;
210
211   assert (strcasecmp (argv[0], "flush") == 0);
212
213 #define BAIL_OUT(s) \
214   do { \
215     if (identifiers != NULL) \
216       free (identifiers); \
217     identifiers_num = 0; \
218     if (plugins != NULL) \
219       free (plugins); \
220     plugins_num = 0; \
221     return (s); \
222   } while (0)
223
224   for (i = 1; i < argc; ++i) {
225     char *key, *value;
226
227     key   = argv[i];
228     value = strchr (argv[i], (int)'=');
229
230     if (! value) {
231       fprintf (stderr, "ERROR: flush: Invalid option ``%s''.\n", argv[i]);
232       BAIL_OUT (-1);
233     }
234
235     *value = '\0';
236     ++value;
237
238     if (strcasecmp (key, "timeout") == 0) {
239       char *endptr = NULL;
240
241       timeout = strtol (value, &endptr, 0);
242
243       if (endptr == value) {
244         fprintf (stderr, "ERROR: Failed to parse timeout as number: %s.\n",
245             value);
246         BAIL_OUT (-1);
247       }
248       else if ((endptr != NULL) && (*endptr != '\0')) {
249         fprintf (stderr, "WARNING: Ignoring trailing garbage after timeout: "
250             "%s.\n", endptr);
251       }
252     }
253     else if (strcasecmp (key, "plugin") == 0) {
254       status = array_grow ((void **)&plugins, &plugins_num,
255           sizeof (*plugins));
256       if (status != 0)
257         BAIL_OUT (status);
258
259       plugins[plugins_num - 1] = value;
260     }
261     else if (strcasecmp (key, "identifier") == 0) {
262       status = array_grow ((void **)&identifiers, &identifiers_num,
263           sizeof (*identifiers));
264       if (status != 0)
265         BAIL_OUT (status);
266
267       memset (identifiers + (identifiers_num - 1), 0, sizeof (*identifiers));
268       status = parse_identifier (c, value,
269           identifiers + (identifiers_num - 1));
270       if (status != 0)
271         BAIL_OUT (status);
272     }
273   }
274
275   if (plugins_num == 0) {
276     status = array_grow ((void **)&plugins, &plugins_num, sizeof (*plugins));
277     if (status != 0)
278       BAIL_OUT (status);
279
280     assert (plugins_num == 1);
281     plugins[0] = NULL;
282   }
283
284   for (i = 0; i < plugins_num; ++i) {
285     if (identifiers_num == 0) {
286       status = lcc_flush (c, plugins[i], NULL, timeout);
287       if (status != 0)
288         fprintf (stderr, "ERROR: Failed to flush plugin `%s': %s.\n",
289             (plugins[i] == NULL) ? "(all)" : plugins[i], lcc_strerror (c));
290     }
291     else {
292       int j;
293
294       for (j = 0; j < identifiers_num; ++j) {
295         status = lcc_flush (c, plugins[i], identifiers + j, timeout);
296         if (status != 0) {
297           char id[1024];
298
299           lcc_identifier_to_string (c, id, sizeof (id), identifiers + j);
300           fprintf (stderr, "ERROR: Failed to flush plugin `%s', "
301               "identifier `%s': %s.\n",
302               (plugins[i] == NULL) ? "(all)" : plugins[i],
303               id, lcc_strerror (c));
304         }
305       }
306     }
307   }
308
309   BAIL_OUT (0);
310 #undef BAIL_OUT
311 } /* flush */
312
313 static int listval (lcc_connection_t *c, int argc, char **argv)
314 {
315   lcc_identifier_t *ret_ident     = NULL;
316   size_t            ret_ident_num = 0;
317
318   int status;
319   size_t i;
320
321   assert (strcasecmp (argv[0], "listval") == 0);
322
323   if (argc != 1) {
324     fprintf (stderr, "ERROR: listval: Does not accept any arguments.\n");
325     return (-1);
326   }
327
328 #define BAIL_OUT(s) \
329   do { \
330     if (ret_ident != NULL) \
331       free (ret_ident); \
332     ret_ident_num = 0; \
333     return (s); \
334   } while (0)
335
336   status = lcc_listval (c, &ret_ident, &ret_ident_num);
337   if (status != 0) {
338     fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
339     BAIL_OUT (status);
340   }
341
342   for (i = 0; i < ret_ident_num; ++i) {
343     char id[1024];
344
345     status = lcc_identifier_to_string (c, id, sizeof (id), ret_ident + i);
346     if (status != 0) {
347       fprintf (stderr, "ERROR: listval: Failed to convert returned "
348           "identifier to a string: %s\n", lcc_strerror (c));
349       continue;
350     }
351
352     printf ("%s\n", id);
353   }
354   BAIL_OUT (0);
355 #undef BAIL_OUT
356 } /* listval */
357
358 int main (int argc, char **argv) {
359   char address[1024] = "unix:"DEFAULT_SOCK;
360
361   lcc_connection_t *c;
362
363   int status;
364
365   while (42) {
366     int c;
367
368     c = getopt (argc, argv, "s:h");
369
370     if (c == -1)
371       break;
372
373     switch (c) {
374       case 's':
375         snprintf (address, sizeof (address), "unix:%s", optarg);
376         address[sizeof (address) - 1] = '\0';
377         break;
378       case 'h':
379         exit_usage (argv[0], 0);
380         break;
381       default:
382         exit_usage (argv[0], 1);
383     }
384   }
385
386   if (optind >= argc) {
387     fprintf (stderr, "%s: missing command\n", argv[0]);
388     exit_usage (argv[0], 1);
389   }
390
391   c = NULL;
392   status = lcc_connect (address, &c);
393   if (status != 0) {
394     fprintf (stderr, "ERROR: Failed to connect to daemon at %s: %s.\n",
395         address, strerror (errno));
396     return (1);
397   }
398
399   if (strcasecmp (argv[optind], "getval") == 0)
400     status = getval (c, argc - optind, argv + optind);
401   else if (strcasecmp (argv[optind], "flush") == 0)
402     status = flush (c, argc - optind, argv + optind);
403   else if (strcasecmp (argv[optind], "listval") == 0)
404     status = listval (c, argc - optind, argv + optind);
405   else {
406     fprintf (stderr, "%s: invalid command: %s\n", argv[0], argv[optind]);
407     return (1);
408   }
409
410   LCC_DESTROY (c);
411
412   if (status != 0)
413     return (status);
414   return (0);
415 } /* main */
416
417 /* vim: set sw=2 ts=2 tw=78 expandtab : */
418