libvirtstats plugin: Added a plugin to collect virtual host statistics.
[collectd.git] / src / libvirtstats.c
1 /**
2  * collectd - src/libvirtstats.c
3  * Copyright (C) 2006,2007  Red Hat Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the license is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Richard W.M. Jones <rjones@redhat.com>
20  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "configfile.h"
26 #include "utils_ignorelist.h"
27
28 #include <libvirt/libvirt.h>
29 #include <libvirt/virterror.h>
30 #include <libxml/parser.h>
31 #include <libxml/tree.h>
32 #include <libxml/xpath.h>
33
34 #define LIBVIRTSTATS_DEBUG 0
35
36 static const char *config_keys[] = {
37     "Connection",
38
39     "RefreshInterval",
40
41     "Domain",
42     "BlockDevice",
43     "InterfaceDevice",
44     "IgnoreSelected",
45     NULL
46 };
47 #define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
48
49 /* Connection. */
50 static virConnectPtr conn = 0;
51
52 /* Seconds between list refreshes, 0 disables completely. */
53 static int interval = 60;
54
55 /* List of domains, if specified. */
56 static ignorelist_t *il_domains = NULL;
57 /* List of block devices, if specified. */
58 static ignorelist_t *il_block_devices = NULL;
59 /* List of network interface devices, if specified. */
60 static ignorelist_t *il_interface_devices = NULL;
61
62 static int ignore_device_match (ignorelist_t *,
63                                 const char *domname, const char *devpath);
64
65 /* Actual list of domains found on last refresh. */
66 static virDomainPtr *domains = NULL;
67 static int nr_domains = 0;
68
69 static void free_domains (void);
70 static int add_domain (virDomainPtr dom);
71
72 /* Actual list of block devices found on last refresh. */
73 struct block_device {
74     virDomainPtr dom;           /* domain */
75     char *path;                 /* name of block device */
76 };
77
78 static struct block_device *block_devices = NULL;
79 static int nr_block_devices = 0;
80
81 static void free_block_devices (void);
82 static int add_block_device (virDomainPtr dom, const char *path);
83
84 /* Actual list of network interfaces found on last refresh. */
85 struct interface_device {
86     virDomainPtr dom;           /* domain */
87     char *path;                 /* name of interface device */
88 };
89
90 static struct interface_device *interface_devices = NULL;
91 static int nr_interface_devices = 0;
92
93 static void free_interface_devices (void);
94 static int add_interface_device (virDomainPtr dom, const char *path);
95
96 /* Time that we last refreshed. */
97 static time_t last_refresh = (time_t) 0;
98
99 static int refresh_lists (void);
100
101 /* Submit functions. */
102 static void cpu_submit (unsigned long long cpu_time,
103                         time_t t,
104                         const char *domname, const char *type);
105 static void vcpu_submit (unsigned long long cpu_time,
106                          time_t t,
107                          const char *domname, int vcpu_nr, const char *type);
108 static void disk_submit (long long read, long long write,
109                          time_t t,
110                          const char *domname, const char *devname,
111                          const char *type);
112 static void if_submit (long long rx, long long tx,
113                        time_t t,
114                        const char *domname, const char *devname,
115                        const char *type);
116
117 /* ERROR(...) macro for virterrors. */
118 #define VIRT_ERROR(conn,s) do {                 \
119         virErrorPtr err;                        \
120         err = (conn) ? virConnGetLastError ((conn)) : virGetLastError (); \
121         if (err) ERROR ("%s: %s", (s), err->message);                   \
122     } while(0)
123
124 static int
125 libvirtstats_init (void)
126 {
127     if (virInitialize () == -1)
128         return -1;
129
130         return 0;
131 }
132
133 static int
134 libvirtstats_config (const char *key, const char *value)
135 {
136     if (virInitialize () == -1)
137         return 1;
138
139     if (il_domains == NULL)
140         il_domains = ignorelist_create (1);
141     if (il_block_devices == NULL)
142         il_block_devices = ignorelist_create (1);
143     if (il_interface_devices == NULL)
144         il_interface_devices = ignorelist_create (1);
145
146     if (strcasecmp (key, "Connection") == 0) {
147         if (conn != 0) {
148             ERROR ("Connection may only be given once in config file");
149             return 1;
150         }
151         conn = virConnectOpenReadOnly (value);
152         if (!conn) {
153             VIRT_ERROR (NULL, "connection failed");
154             return 1;
155         }
156         return 0;
157     }
158
159     if (strcasecmp (key, "RefreshInterval") == 0) {
160         char *eptr = NULL;
161         interval = strtol (value, &eptr, 10);
162         if (eptr == NULL || *eptr != '\0') return 1;
163         return 0;
164     }
165
166     if (strcasecmp (key, "Domain") == 0) {
167         if (ignorelist_add (il_domains, value)) return 1;
168         return 0;
169     }
170     if (strcasecmp (key, "BlockDevice") == 0) {
171         if (ignorelist_add (il_block_devices, value)) return 1;
172         return 0;
173     }
174     if (strcasecmp (key, "InterfaceDevice") == 0) {
175         if (ignorelist_add (il_interface_devices, value)) return 1;
176         return 0;
177     }
178
179     if (strcasecmp (key, "IgnoreSelected") == 0) {
180         if (strcasecmp (value, "True") == 0 ||
181             strcasecmp (value, "Yes") == 0 ||
182             strcasecmp (value, "On") == 0)
183         {
184             ignorelist_set_invert (il_domains, 0);
185             ignorelist_set_invert (il_block_devices, 0);
186             ignorelist_set_invert (il_interface_devices, 0);
187         }
188         else
189         {
190             ignorelist_set_invert (il_domains, 1);
191             ignorelist_set_invert (il_block_devices, 1);
192             ignorelist_set_invert (il_interface_devices, 1);
193         }
194     }
195
196     /* Unrecognised option. */
197     return -1;
198 }
199
200 static int
201 libvirtstats_read (void)
202 {
203     time_t t;
204     int i;
205
206     if (conn == NULL) {
207         ERROR ("Not connected.  Use Connection in config file to supply connection URI.  For more information see http://libvirt.org/uri.html");
208         return -1;
209     }
210
211     time (&t);
212
213     /* Need to refresh domain or device lists? */
214     if (last_refresh == (time_t) 0 ||
215         (interval > 0 && last_refresh + interval <= t)) {
216         if (refresh_lists () == -1) return -1;
217         last_refresh = t;
218     }
219
220 #if LIBVIRTSTATS_DEBUG
221     for (i = 0; i < nr_domains; ++i)
222         fprintf (stderr, "domain %s\n", virDomainGetName (domains[i]));
223     for (i = 0; i < nr_block_devices; ++i)
224         fprintf  (stderr, "block device %d %s:%s\n",
225                   i, virDomainGetName (block_devices[i].dom),
226                   block_devices[i].path);
227     for (i = 0; i < nr_interface_devices; ++i)
228         fprintf (stderr, "interface device %d %s:%s\n",
229                  i, virDomainGetName (interface_devices[i].dom),
230                  interface_devices[i].path);
231 #endif
232
233     /* Get CPU usage, VCPU usage for each domain. */
234     for (i = 0; i < nr_domains; ++i) {
235         const char *name;
236         virDomainInfo info;
237         virVcpuInfoPtr vinfo = NULL;
238         int j;
239
240         name = virDomainGetName (domains[i]);
241         if (name == NULL) continue;
242
243         if (virDomainGetInfo (domains[i], &info) == -1) continue;
244
245         cpu_submit (info.cpuTime, t, name, "virt_cpu_total");
246
247         vinfo = malloc (info.nrVirtCpu * sizeof vinfo[0]);
248         if (vinfo == NULL) {
249             ERROR ("malloc: %s", strerror (errno));
250             continue;
251         }
252
253         if (virDomainGetVcpus (domains[i], vinfo, info.nrVirtCpu,
254                                NULL, 0) == -1) {
255             free (vinfo);
256             continue;
257         }
258
259         for (j = 0; j < info.nrVirtCpu; ++j)
260             vcpu_submit (vinfo[j].cpuTime,
261                          t, name, vinfo[j].number, "virt_vcpu");
262
263         free (vinfo);
264     }
265
266     /* Get block device stats for each domain. */
267     for (i = 0; i < nr_block_devices; ++i) {
268         const char *name;
269         struct _virDomainBlockStats stats;
270
271         name = virDomainGetName (block_devices[i].dom);
272         if (name == NULL) continue;
273
274         if (virDomainBlockStats (block_devices[i].dom, block_devices[i].path,
275                                  &stats, sizeof stats) == -1)
276             continue;
277
278         disk_submit (stats.rd_req, stats.wr_req,
279                      t, name, block_devices[i].path,
280                      "disk_ops");
281         disk_submit (stats.rd_bytes, stats.wr_bytes,
282                      t, name, block_devices[i].path,
283                      "disk_octets");
284     }
285
286     /* Get interface stats for each domain. */
287     for (i = 0; i < nr_interface_devices; ++i) {
288         const char *name;
289         struct _virDomainInterfaceStats stats;
290
291         name = virDomainGetName (interface_devices[i].dom);
292         if (name == NULL) continue;
293
294         if (virDomainInterfaceStats (interface_devices[i].dom,
295                                      interface_devices[i].path,
296                                      &stats, sizeof stats) == -1)
297             continue;
298
299         if_submit (stats.rx_bytes, stats.tx_bytes,
300                    t, name, interface_devices[i].path,
301                    "if_octets");
302         if_submit (stats.rx_packets, stats.tx_packets,
303                    t, name, interface_devices[i].path,
304                    "if_packets");
305         if_submit (stats.rx_errs, stats.tx_errs,
306                    t, name, interface_devices[i].path,
307                    "if_errors");
308         if_submit (stats.rx_drop, stats.tx_drop,
309                    t, name, interface_devices[i].path,
310                    "if_dropped");
311     }
312
313     return 0;
314 }
315
316 static int
317 refresh_lists (void)
318 {
319     int n;
320
321     n = virConnectNumOfDomains (conn);
322     if (n == -1) {
323         VIRT_ERROR (conn, "reading number of domains");
324         return -1;
325     }
326
327     if (n > 0) {
328         int i;
329         int *domids;
330
331         /* Get list of domains. */
332         domids = malloc (sizeof (int) * n);
333         if (domids == 0) {
334             ERROR ("malloc failed: %s", strerror (errno));
335             return -1;
336         }
337
338         n = virConnectListDomains (conn, domids, n);
339         if (n == -1) {
340             VIRT_ERROR (conn, "reading list of domains");
341             free (domids);
342             return -1;
343         }
344
345         free_block_devices ();
346         free_interface_devices ();
347         free_domains ();
348
349         /* Fetch each domain and add it to the list, unless ignore. */
350         for (i = 0; i < n; ++i) {
351             virDomainPtr dom = NULL;
352             const char *name;
353             char *xml = NULL;
354             xmlDocPtr xml_doc = NULL;
355             xmlXPathContextPtr xpath_ctx = NULL;
356             xmlXPathObjectPtr xpath_obj = NULL;
357             int j;
358
359             dom = virDomainLookupByID (conn, domids[i]);
360             if (dom == NULL) {
361                 VIRT_ERROR (conn, "virDomainLookupByID");
362                 /* Could be that the domain went away -- ignore it anyway. */
363                 continue;
364             }
365
366             name = virDomainGetName (dom);
367             if (name == NULL) {
368                 VIRT_ERROR (conn, "virDomainGetName");
369                 goto cont;
370             }
371
372             if (il_domains && ignorelist_match (il_domains, name) != 0)
373                 goto cont;
374
375             if (add_domain (dom) == -1) {
376                 ERROR ("malloc: %s", strerror (errno));
377                 goto cont;
378             }
379
380             /* Get a list of devices for this domain. */
381             xml = virDomainGetXMLDesc (dom, 0);
382             if (!xml) {
383                 VIRT_ERROR (conn, "virDomainGetXMLDesc");
384                 goto cont;
385             }
386
387             /* Yuck, XML.  Parse out the devices. */
388             xml_doc = xmlReadDoc ((xmlChar *) xml, NULL, NULL, XML_PARSE_NONET);
389             if (xml_doc == NULL) {
390                 VIRT_ERROR (conn, "xmlReadDoc");
391                 goto cont;
392             }
393
394             xpath_ctx = xmlXPathNewContext (xml_doc);
395
396             /* Block devices. */
397             xpath_obj = xmlXPathEval
398                 ((xmlChar *) "/domain/devices/disk/target[@dev]",
399                  xpath_ctx);
400             if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
401                 xpath_obj->nodesetval == NULL)
402                 goto cont;
403
404             for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
405                 xmlNodePtr node;
406                 char *path = NULL;
407
408                 node = xpath_obj->nodesetval->nodeTab[j];
409                 if (!node) continue;
410                 path = (char *) xmlGetProp (node, (xmlChar *) "dev");
411                 if (!path) continue;
412
413                 if (il_block_devices &&
414                     ignore_device_match (il_block_devices, name, path) != 0)
415                     goto cont2;
416
417                 add_block_device (dom, path);
418             cont2:
419                 if (path) xmlFree (path);
420             }
421             xmlXPathFreeObject (xpath_obj);
422
423             /* Network interfaces. */
424             xpath_obj = xmlXPathEval
425                 ((xmlChar *) "/domain/devices/interface/target[@dev]",
426                  xpath_ctx);
427             if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
428                 xpath_obj->nodesetval == NULL)
429                 goto cont;
430
431             for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
432                 xmlNodePtr node;
433                 char *path = NULL;
434
435                 node = xpath_obj->nodesetval->nodeTab[j];
436                 if (!node) continue;
437                 path = (char *) xmlGetProp (node, (xmlChar *) "dev");
438                 if (!path) continue;
439
440                 if (il_interface_devices &&
441                     ignore_device_match (il_interface_devices, name, path) != 0)
442                     goto cont3;
443
444                 add_interface_device (dom, path);
445             cont3:
446                 if (path) xmlFree (path);
447             }
448
449         cont:
450             if (xpath_obj) xmlXPathFreeObject (xpath_obj);
451             if (xpath_ctx) xmlXPathFreeContext (xpath_ctx);
452             if (xml_doc) xmlFreeDoc (xml_doc);
453             if (xml) free (xml);
454         }
455
456         free (domids);
457     }
458
459     return 0;
460 }
461
462 static void
463 free_domains ()
464 {
465     int i;
466
467     if (domains) {
468         for (i = 0; i < nr_domains; ++i)
469             virDomainFree (domains[i]);
470         free (domains);
471     }
472     domains = NULL;
473     nr_domains = 0;
474 }
475
476 static int
477 add_domain (virDomainPtr dom)
478 {
479     virDomainPtr *new_ptr;
480     int new_size = sizeof (domains[0]) * (nr_domains+1);
481
482     if (domains)
483         new_ptr = realloc (domains, new_size);
484     else
485         new_ptr = malloc (new_size);
486
487     if (new_ptr == NULL) return -1;
488     domains = new_ptr;
489     domains[nr_domains] = dom;
490     return nr_domains++;
491 }
492
493 static void
494 free_block_devices ()
495 {
496     int i;
497
498     if (block_devices) {
499         for (i = 0; i < nr_block_devices; ++i)
500             free (block_devices[i].path);
501         free (block_devices);
502     }
503     block_devices = NULL;
504     nr_block_devices = 0;
505 }
506
507 static int
508 add_block_device (virDomainPtr dom, const char *path)
509 {
510     struct block_device *new_ptr;
511     int new_size = sizeof (block_devices[0]) * (nr_block_devices+1);
512     char *path_copy;
513
514     path_copy = strdup (path);
515     if (!path_copy) return -1;
516
517     if (block_devices)
518         new_ptr = realloc (block_devices, new_size);
519     else
520         new_ptr = malloc (new_size);
521
522     if (new_ptr == NULL) {
523         free (path_copy);
524         return -1;
525     }
526     block_devices = new_ptr;
527     block_devices[nr_block_devices].dom = dom;
528     block_devices[nr_block_devices].path = path_copy;
529     return nr_block_devices++;
530 }
531
532 static void
533 free_interface_devices ()
534 {
535     int i;
536
537     if (interface_devices) {
538         for (i = 0; i < nr_interface_devices; ++i)
539             free (interface_devices[i].path);
540         free (interface_devices);
541     }
542     interface_devices = NULL;
543     nr_interface_devices = 0;
544 }
545
546 static int
547 add_interface_device (virDomainPtr dom, const char *path)
548 {
549     struct interface_device *new_ptr;
550     int new_size = sizeof (interface_devices[0]) * (nr_interface_devices+1);
551     char *path_copy;
552
553     path_copy = strdup (path);
554     if (!path_copy) return -1;
555
556     if (interface_devices)
557         new_ptr = realloc (interface_devices, new_size);
558     else
559         new_ptr = malloc (new_size);
560
561     if (new_ptr == NULL) {
562         free (path_copy);
563         return -1;
564     }
565     interface_devices = new_ptr;
566     interface_devices[nr_interface_devices].dom = dom;
567     interface_devices[nr_interface_devices].path = path_copy;
568     return nr_interface_devices++;
569 }
570
571 static int
572 ignore_device_match (ignorelist_t *il, const char *domname, const char *devpath)
573 {
574     char *name;
575     int n, r;
576
577     n = sizeof (char) * (strlen (domname) + strlen (devpath) + 2);
578     name = malloc (n);
579     if (name == NULL) {
580         ERROR ("malloc: %s", strerror (errno));
581         return 0;
582     }
583     snprintf (name, n, "%s:%s", domname, devpath);
584     r = ignorelist_match (il, name);
585     free (name);
586     return r;
587 }
588
589 static void
590 cpu_submit (unsigned long long cpu_time,
591             time_t t,
592             const char *domname, const char *type)
593 {
594     value_t values[1];
595     value_list_t vl = VALUE_LIST_INIT;
596
597     values[0].counter = cpu_time;
598
599     vl.values = values;
600     vl.values_len = 1;
601     vl.time = t;
602     vl.interval = interval_g;
603     strncpy (vl.plugin, "libvirtstats", DATA_MAX_NAME_LEN);
604     strncpy (vl.host, domname, DATA_MAX_NAME_LEN);
605     /*strncpy (vl.type_instance, ?, DATA_MAX_NAME_LEN);*/
606
607     plugin_dispatch_values (type, &vl);
608 }
609
610 static void
611 vcpu_submit (unsigned long long cpu_time,
612              time_t t,
613              const char *domname, int vcpu_nr, const char *type)
614 {
615     value_t values[1];
616     value_list_t vl = VALUE_LIST_INIT;
617
618     values[0].counter = cpu_time;
619
620     vl.values = values;
621     vl.values_len = 1;
622     vl.time = t;
623     vl.interval = interval_g;
624     strncpy (vl.plugin, "libvirtstats", DATA_MAX_NAME_LEN);
625     strncpy (vl.host, domname, DATA_MAX_NAME_LEN);
626     snprintf (vl.type_instance, DATA_MAX_NAME_LEN, "%d", vcpu_nr);
627
628     plugin_dispatch_values (type, &vl);
629 }
630
631 static void
632 disk_submit (long long read, long long write,
633              time_t t,
634              const char *domname, const char *devname,
635              const char *type)
636 {
637     value_t values[2];
638     value_list_t vl = VALUE_LIST_INIT;
639
640     values[0].counter = read >= 0 ? (unsigned long long) read : 0;
641     values[1].counter = write >= 0 ? (unsigned long long) write : 0;
642
643     vl.values = values;
644     vl.values_len = 2;
645     vl.time = t;
646     vl.interval = interval_g;
647     strncpy (vl.plugin, "libvirtstats", DATA_MAX_NAME_LEN);
648     strncpy (vl.host, domname, DATA_MAX_NAME_LEN);
649     strncpy (vl.type_instance, devname, DATA_MAX_NAME_LEN);
650
651     plugin_dispatch_values (type, &vl);
652 }
653
654 static void
655 if_submit (long long rx, long long tx,
656            time_t t,
657            const char *domname, const char *devname,
658            const char *type)
659 {
660     value_t values[2];
661     value_list_t vl = VALUE_LIST_INIT;
662
663     values[0].counter = rx >= 0 ? (unsigned long long) rx : 0;
664     values[1].counter = tx >= 0 ? (unsigned long long) tx : 0;
665
666     vl.values = values;
667     vl.values_len = 2;
668     vl.time = t;
669     vl.interval = interval_g;
670     strncpy (vl.plugin, "libvirtstats", DATA_MAX_NAME_LEN);
671     strncpy (vl.host, domname, DATA_MAX_NAME_LEN);
672     strncpy (vl.type_instance, devname, DATA_MAX_NAME_LEN);
673
674     plugin_dispatch_values (type, &vl);
675 }
676
677 static int
678 libvirtstats_shutdown (void)
679 {
680     free_block_devices ();
681     free_interface_devices ();
682     free_domains ();
683
684     if (conn) virConnectClose (conn);
685     conn = NULL;
686
687     ignorelist_free (il_domains);
688     il_domains = NULL;
689     ignorelist_free (il_block_devices);
690     il_block_devices = NULL;
691     ignorelist_free (il_interface_devices);
692     il_interface_devices = NULL;
693
694     return 0;
695 }
696
697 void
698 module_register (void)
699 {
700         plugin_register_config ("libvirtstats",
701                             libvirtstats_config,
702                             config_keys, NR_CONFIG_KEYS);
703     plugin_register_init ("libvirtstats", libvirtstats_init);
704         plugin_register_read ("libvirtstats", libvirtstats_read);
705         plugin_register_shutdown ("libvirtstats", libvirtstats_shutdown);
706 }
707
708 /*
709  * vim: set tabstop=4:
710  * vim: set shiftwidth=4:
711  * vim: set expandtab:
712  */
713 /*
714  * Local variables:
715  *  indent-tabs-mode: nil
716  *  c-indent-level: 4
717  *  c-basic-offset: 4
718  *  tab-width: 4
719  * End:
720  */