libvirtstats plugin: Minor cleanups.
[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 static const char *config_keys[] = {
35     "Connection",
36
37     "RefreshInterval",
38
39     "Domain",
40     "BlockDevice",
41     "InterfaceDevice",
42     "IgnoreSelected",
43
44     "HostnameFormat",
45
46     NULL
47 };
48 #define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
49
50 /* Connection. */
51 static virConnectPtr conn = 0;
52
53 /* Seconds between list refreshes, 0 disables completely. */
54 static int interval = 60;
55
56 /* List of domains, if specified. */
57 static ignorelist_t *il_domains = NULL;
58 /* List of block devices, if specified. */
59 static ignorelist_t *il_block_devices = NULL;
60 /* List of network interface devices, if specified. */
61 static ignorelist_t *il_interface_devices = NULL;
62
63 static int ignore_device_match (ignorelist_t *,
64                                 const char *domname, const char *devpath);
65
66 /* Actual list of domains found on last refresh. */
67 static virDomainPtr *domains = NULL;
68 static int nr_domains = 0;
69
70 static void free_domains (void);
71 static int add_domain (virDomainPtr dom);
72
73 /* Actual list of block devices found on last refresh. */
74 struct block_device {
75     virDomainPtr dom;           /* domain */
76     char *path;                 /* name of block device */
77 };
78
79 static struct block_device *block_devices = NULL;
80 static int nr_block_devices = 0;
81
82 static void free_block_devices (void);
83 static int add_block_device (virDomainPtr dom, const char *path);
84
85 /* Actual list of network interfaces found on last refresh. */
86 struct interface_device {
87     virDomainPtr dom;           /* domain */
88     char *path;                 /* name of interface device */
89 };
90
91 static struct interface_device *interface_devices = NULL;
92 static int nr_interface_devices = 0;
93
94 static void free_interface_devices (void);
95 static int add_interface_device (virDomainPtr dom, const char *path);
96
97 /* HostnameFormat. */
98 #define HF_MAX_FIELDS 3
99
100 enum hf_field {
101     hf_none = 0,
102     hf_hostname,
103     hf_name,
104     hf_uuid
105 };
106
107 static enum hf_field hostname_format[HF_MAX_FIELDS] =
108     { hf_name };
109
110 /* Time that we last refreshed. */
111 static time_t last_refresh = (time_t) 0;
112
113 static int refresh_lists (void);
114
115 /* Submit functions. */
116 static void cpu_submit (unsigned long long cpu_time,
117                         time_t t,
118                         virDomainPtr dom, const char *type);
119 static void vcpu_submit (unsigned long long cpu_time,
120                          time_t t,
121                          virDomainPtr dom, int vcpu_nr, const char *type);
122 static void submit_counter2 (long long read, long long write,
123                          time_t t,
124                          virDomainPtr dom, const char *devname,
125                          const char *type);
126
127 /* ERROR(...) macro for virterrors. */
128 #define VIRT_ERROR(conn,s) do {                 \
129         virErrorPtr err;                        \
130         err = (conn) ? virConnGetLastError ((conn)) : virGetLastError (); \
131         if (err) ERROR ("%s: %s", (s), err->message);                   \
132     } while(0)
133
134 static int
135 libvirtstats_init (void)
136 {
137     if (virInitialize () != 0)
138         return -1;
139
140         return 0;
141 }
142
143 static int
144 libvirtstats_config (const char *key, const char *value)
145 {
146     if (virInitialize () != 0)
147         return 1;
148
149     if (il_domains == NULL)
150         il_domains = ignorelist_create (1);
151     if (il_block_devices == NULL)
152         il_block_devices = ignorelist_create (1);
153     if (il_interface_devices == NULL)
154         il_interface_devices = ignorelist_create (1);
155
156     if (strcasecmp (key, "Connection") == 0) {
157         if (conn != 0) {
158             ERROR ("Connection may only be given once in config file");
159             return 1;
160         }
161         conn = virConnectOpenReadOnly (value);
162         if (!conn) {
163             VIRT_ERROR (NULL, "connection failed");
164             return 1;
165         }
166         return 0;
167     }
168
169     if (strcasecmp (key, "RefreshInterval") == 0) {
170         char *eptr = NULL;
171         interval = strtol (value, &eptr, 10);
172         if (eptr == NULL || *eptr != '\0') return 1;
173         return 0;
174     }
175
176     if (strcasecmp (key, "Domain") == 0) {
177         if (ignorelist_add (il_domains, value)) return 1;
178         return 0;
179     }
180     if (strcasecmp (key, "BlockDevice") == 0) {
181         if (ignorelist_add (il_block_devices, value)) return 1;
182         return 0;
183     }
184     if (strcasecmp (key, "InterfaceDevice") == 0) {
185         if (ignorelist_add (il_interface_devices, value)) return 1;
186         return 0;
187     }
188
189     if (strcasecmp (key, "IgnoreSelected") == 0) {
190         if (strcasecmp (value, "True") == 0 ||
191             strcasecmp (value, "Yes") == 0 ||
192             strcasecmp (value, "On") == 0)
193         {
194             ignorelist_set_invert (il_domains, 0);
195             ignorelist_set_invert (il_block_devices, 0);
196             ignorelist_set_invert (il_interface_devices, 0);
197         }
198         else
199         {
200             ignorelist_set_invert (il_domains, 1);
201             ignorelist_set_invert (il_block_devices, 1);
202             ignorelist_set_invert (il_interface_devices, 1);
203         }
204         return 0;
205     }
206
207     if (strcasecmp (key, "HostnameFormat") == 0) {
208         char *value_copy;
209         char *fields[HF_MAX_FIELDS];
210         int i, n;
211
212         value_copy = strdup (value);
213         if (value_copy == NULL) {
214             ERROR ("libvirtstats plugin: strdup failed.");
215             return -1;
216         }
217
218         n = strsplit (value_copy, fields, HF_MAX_FIELDS);
219         if (n < 1) {
220             free (value_copy);
221             ERROR ("HostnameFormat: no fields");
222             return -1;
223         }
224
225         for (i = 0; i < n; ++i) {
226             if (strcasecmp (fields[i], "hostname") == 0)
227                 hostname_format[i] = hf_hostname;
228             else if (strcasecmp (fields[i], "name") == 0)
229                 hostname_format[i] = hf_name;
230             else if (strcasecmp (fields[i], "uuid") == 0)
231                 hostname_format[i] = hf_uuid;
232             else {
233                 free (value_copy);
234                 ERROR ("unknown HostnameFormat field: %s", fields[i]);
235                 return -1;
236             }
237         }
238         free (value_copy);
239
240         for (i = n; i < HF_MAX_FIELDS; ++i)
241             hostname_format[i] = hf_none;
242
243         return 0;
244     }
245
246     /* Unrecognised option. */
247     return -1;
248 }
249
250 static int
251 libvirtstats_read (void)
252 {
253     time_t t;
254     int i;
255
256     if (conn == NULL) {
257         ERROR ("libvirtstats plugin: Not connected. Use Connection in "
258                 "config file to supply connection URI.  For more information "
259                 "see <http://libvirt.org/uri.html>");
260         return -1;
261     }
262
263     time (&t);
264
265     /* Need to refresh domain or device lists? */
266     if ((last_refresh == (time_t) 0) ||
267             ((interval > 0) && ((last_refresh + interval) <= t))) {
268         if (refresh_lists () != 0)
269             return -1;
270         last_refresh = t;
271     }
272
273 #if 0
274     for (i = 0; i < nr_domains; ++i)
275         fprintf (stderr, "domain %s\n", virDomainGetName (domains[i]));
276     for (i = 0; i < nr_block_devices; ++i)
277         fprintf  (stderr, "block device %d %s:%s\n",
278                   i, virDomainGetName (block_devices[i].dom),
279                   block_devices[i].path);
280     for (i = 0; i < nr_interface_devices; ++i)
281         fprintf (stderr, "interface device %d %s:%s\n",
282                  i, virDomainGetName (interface_devices[i].dom),
283                  interface_devices[i].path);
284 #endif
285
286     /* Get CPU usage, VCPU usage for each domain. */
287     for (i = 0; i < nr_domains; ++i) {
288         virDomainInfo info;
289         virVcpuInfoPtr vinfo = NULL;
290         int j;
291
292         if (virDomainGetInfo (domains[i], &info) != 0)
293             continue;
294
295         cpu_submit (info.cpuTime, t, domains[i], "virt_cpu_total");
296
297         vinfo = malloc (info.nrVirtCpu * sizeof vinfo[0]);
298         if (vinfo == NULL) {
299             ERROR ("libvirtstats plugin: malloc failed.");
300             continue;
301         }
302
303         if (virDomainGetVcpus (domains[i], vinfo, info.nrVirtCpu,
304                     NULL, 0) != 0) {
305             free (vinfo);
306             continue;
307         }
308
309         for (j = 0; j < info.nrVirtCpu; ++j)
310             vcpu_submit (vinfo[j].cpuTime,
311                     t, domains[i], vinfo[j].number, "virt_vcpu");
312
313         free (vinfo);
314     }
315
316     /* Get block device stats for each domain. */
317     for (i = 0; i < nr_block_devices; ++i) {
318         struct _virDomainBlockStats stats;
319
320         if (virDomainBlockStats (block_devices[i].dom, block_devices[i].path,
321                     &stats, sizeof stats) != 0)
322             continue;
323
324         if ((stats.rd_req != -1) && (stats.wr_req != -1))
325             submit_counter2 ("disk_ops",
326                     (counter_t) stats.rd_req, (counter_t) stats.wr_req,
327                     t, block_devices[i].dom, block_devices[i].path);
328
329         if ((stats.rd_bytes != -1) && (stats.wr_bytes != -1))
330             submit_counter2 ("disk_octets",
331                     (counter_t) stats.rd_bytes, (counter_t) stats.wr_bytes,
332                     t, block_devices[i].dom, block_devices[i].path);
333     } /* for (nr_block_devices) */
334
335     /* Get interface stats for each domain. */
336     for (i = 0; i < nr_interface_devices; ++i) {
337         struct _virDomainInterfaceStats stats;
338
339         if (virDomainInterfaceStats (interface_devices[i].dom,
340                     interface_devices[i].path,
341                     &stats, sizeof stats) != 0)
342             continue;
343
344         if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
345             submit_counter2 ("if_octets",
346                     (counter_t) stats.rx_bytes, (counter_t) stats.tx_bytes,
347                     t, interface_devices[i].dom, interface_devices[i].path);
348
349         if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
350             submit_counter2 ("if_packets",
351                     (counter_t) stats.rx_packets, (counter_t) stats.tx_packets,
352                     t, interface_devices[i].dom, interface_devices[i].path);
353
354         if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
355             submit_counter2 ("if_errors",
356                     (counter_t) stats.rx_errs, (counter_t) stats.tx_errs,
357                     t, interface_devices[i].dom, interface_devices[i].path);
358
359         if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
360             submit_counter2 ("if_dropped",
361                     (counter_t) stats.rx_drop, (counter_t) stats.tx_drop,
362                     t, interface_devices[i].dom, interface_devices[i].path);
363     } /* for (nr_interface_devices) */
364
365     return 0;
366 }
367
368 static int
369 refresh_lists (void)
370 {
371     int n;
372
373     n = virConnectNumOfDomains (conn);
374     if (n < 0) {
375         VIRT_ERROR (conn, "reading number of domains");
376         return -1;
377     }
378
379     if (n > 0) {
380         int i;
381         int *domids;
382
383         /* Get list of domains. */
384         domids = malloc (sizeof (int) * n);
385         if (domids == 0) {
386             ERROR ("libvirtstats plugin: malloc failed.");
387             return -1;
388         }
389
390         n = virConnectListDomains (conn, domids, n);
391         if (n < 0) {
392             VIRT_ERROR (conn, "reading list of domains");
393             free (domids);
394             return -1;
395         }
396
397         free_block_devices ();
398         free_interface_devices ();
399         free_domains ();
400
401         /* Fetch each domain and add it to the list, unless ignore. */
402         for (i = 0; i < n; ++i) {
403             virDomainPtr dom = NULL;
404             const char *name;
405             char *xml = NULL;
406             xmlDocPtr xml_doc = NULL;
407             xmlXPathContextPtr xpath_ctx = NULL;
408             xmlXPathObjectPtr xpath_obj = NULL;
409             int j;
410
411             dom = virDomainLookupByID (conn, domids[i]);
412             if (dom == NULL) {
413                 VIRT_ERROR (conn, "virDomainLookupByID");
414                 /* Could be that the domain went away -- ignore it anyway. */
415                 continue;
416             }
417
418             name = virDomainGetName (dom);
419             if (name == NULL) {
420                 VIRT_ERROR (conn, "virDomainGetName");
421                 goto cont;
422             }
423
424             if (il_domains && ignorelist_match (il_domains, name) != 0)
425                 goto cont;
426
427             if (add_domain (dom) < 0) {
428                 ERROR ("libvirtstats plugin: malloc failed.");
429                 goto cont;
430             }
431
432             /* Get a list of devices for this domain. */
433             xml = virDomainGetXMLDesc (dom, 0);
434             if (!xml) {
435                 VIRT_ERROR (conn, "virDomainGetXMLDesc");
436                 goto cont;
437             }
438
439             /* Yuck, XML.  Parse out the devices. */
440             xml_doc = xmlReadDoc ((xmlChar *) xml, NULL, NULL, XML_PARSE_NONET);
441             if (xml_doc == NULL) {
442                 VIRT_ERROR (conn, "xmlReadDoc");
443                 goto cont;
444             }
445
446             xpath_ctx = xmlXPathNewContext (xml_doc);
447
448             /* Block devices. */
449             xpath_obj = xmlXPathEval
450                 ((xmlChar *) "/domain/devices/disk/target[@dev]",
451                  xpath_ctx);
452             if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
453                 xpath_obj->nodesetval == NULL)
454                 goto cont;
455
456             for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
457                 xmlNodePtr node;
458                 char *path = NULL;
459
460                 node = xpath_obj->nodesetval->nodeTab[j];
461                 if (!node) continue;
462                 path = (char *) xmlGetProp (node, (xmlChar *) "dev");
463                 if (!path) continue;
464
465                 if (il_block_devices &&
466                     ignore_device_match (il_block_devices, name, path) != 0)
467                     goto cont2;
468
469                 add_block_device (dom, path);
470             cont2:
471                 if (path) xmlFree (path);
472             }
473             xmlXPathFreeObject (xpath_obj);
474
475             /* Network interfaces. */
476             xpath_obj = xmlXPathEval
477                 ((xmlChar *) "/domain/devices/interface/target[@dev]",
478                  xpath_ctx);
479             if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
480                 xpath_obj->nodesetval == NULL)
481                 goto cont;
482
483             for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
484                 xmlNodePtr node;
485                 char *path = NULL;
486
487                 node = xpath_obj->nodesetval->nodeTab[j];
488                 if (!node) continue;
489                 path = (char *) xmlGetProp (node, (xmlChar *) "dev");
490                 if (!path) continue;
491
492                 if (il_interface_devices &&
493                     ignore_device_match (il_interface_devices, name, path) != 0)
494                     goto cont3;
495
496                 add_interface_device (dom, path);
497             cont3:
498                 if (path) xmlFree (path);
499             }
500
501         cont:
502             if (xpath_obj) xmlXPathFreeObject (xpath_obj);
503             if (xpath_ctx) xmlXPathFreeContext (xpath_ctx);
504             if (xml_doc) xmlFreeDoc (xml_doc);
505             if (xml) free (xml);
506         }
507
508         free (domids);
509     }
510
511     return 0;
512 }
513
514 static void
515 free_domains ()
516 {
517     int i;
518
519     if (domains) {
520         for (i = 0; i < nr_domains; ++i)
521             virDomainFree (domains[i]);
522         free (domains);
523     }
524     domains = NULL;
525     nr_domains = 0;
526 }
527
528 static int
529 add_domain (virDomainPtr dom)
530 {
531     virDomainPtr *new_ptr;
532     int new_size = sizeof (domains[0]) * (nr_domains+1);
533
534     if (domains)
535         new_ptr = realloc (domains, new_size);
536     else
537         new_ptr = malloc (new_size);
538
539     if (new_ptr == NULL)
540         return -1;
541
542     domains = new_ptr;
543     domains[nr_domains] = dom;
544     return nr_domains++;
545 }
546
547 static void
548 free_block_devices ()
549 {
550     int i;
551
552     if (block_devices) {
553         for (i = 0; i < nr_block_devices; ++i)
554             free (block_devices[i].path);
555         free (block_devices);
556     }
557     block_devices = NULL;
558     nr_block_devices = 0;
559 }
560
561 static int
562 add_block_device (virDomainPtr dom, const char *path)
563 {
564     struct block_device *new_ptr;
565     int new_size = sizeof (block_devices[0]) * (nr_block_devices+1);
566     char *path_copy;
567
568     path_copy = strdup (path);
569     if (!path_copy)
570         return -1;
571
572     if (block_devices)
573         new_ptr = realloc (block_devices, new_size);
574     else
575         new_ptr = malloc (new_size);
576
577     if (new_ptr == NULL) {
578         free (path_copy);
579         return -1;
580     }
581     block_devices = new_ptr;
582     block_devices[nr_block_devices].dom = dom;
583     block_devices[nr_block_devices].path = path_copy;
584     return nr_block_devices++;
585 }
586
587 static void
588 free_interface_devices ()
589 {
590     int i;
591
592     if (interface_devices) {
593         for (i = 0; i < nr_interface_devices; ++i)
594             free (interface_devices[i].path);
595         free (interface_devices);
596     }
597     interface_devices = NULL;
598     nr_interface_devices = 0;
599 }
600
601 static int
602 add_interface_device (virDomainPtr dom, const char *path)
603 {
604     struct interface_device *new_ptr;
605     int new_size = sizeof (interface_devices[0]) * (nr_interface_devices+1);
606     char *path_copy;
607
608     path_copy = strdup (path);
609     if (!path_copy) return -1;
610
611     if (interface_devices)
612         new_ptr = realloc (interface_devices, new_size);
613     else
614         new_ptr = malloc (new_size);
615
616     if (new_ptr == NULL) {
617         free (path_copy);
618         return -1;
619     }
620     interface_devices = new_ptr;
621     interface_devices[nr_interface_devices].dom = dom;
622     interface_devices[nr_interface_devices].path = path_copy;
623     return nr_interface_devices++;
624 }
625
626 static int
627 ignore_device_match (ignorelist_t *il, const char *domname, const char *devpath)
628 {
629     char *name;
630     int n, r;
631
632     n = sizeof (char) * (strlen (domname) + strlen (devpath) + 2);
633     name = malloc (n);
634     if (name == NULL) {
635         ERROR ("libvirtstats plugin: malloc failed.");
636         return 0;
637     }
638     snprintf (name, n, "%s:%s", domname, devpath);
639     r = ignorelist_match (il, name);
640     free (name);
641     return r;
642 }
643
644 static void
645 init_value_list (value_list_t *vl, time_t t, virDomainPtr dom)
646 {
647     int i;
648     char  *host_ptr;
649     size_t host_len;
650
651     vl->time = t;
652     vl->interval = interval_g;
653
654     strncpy (vl->plugin, "libvirtstats", sizeof (vl->plugin));
655     vl->plugin[sizeof (vl->plugin) - 1] = '\0';
656
657     vl->host[0] = '\0';
658     host_ptr = vl->host;
659     host_len = sizeof (vl->host);
660
661     /* Construct the hostname field according to HostnameFormat. */
662     for (i = 0; i < HF_MAX_FIELDS; ++i) {
663         int status = 0;
664
665         switch (hostname_format[i]) {
666             case hf_none;
667                 /* do nothing */
668                 break;
669
670             case hf_hostname:
671                 status = snprintf (host_ptr, host_len, ":%s", hostname_g);
672                 break;
673
674             case hf_name:
675             {
676                 const char *name = virDomainGetName (dom);
677                 if (name != NULL)
678                     status = snprintf (host_ptr, host_len, ":%s", name);
679                 break;
680             }
681             case hf_uuid:
682             {
683                 char uuid[VIR_UUID_STRING_BUFLEN];
684                 if (virDomainGetUUIDString (dom, uuid) == 0) {
685                     uuid[sizeof (uuid) - 1] = '\0';
686                     status = snprintf (host_ptr, host_len, ":%s", uuid);
687                 }
688                 break;
689             }
690         } /* switch (hostname_format[i]) */
691
692         /* If status >= host_len
693          * => the buffer is full, there's no null-byte at the end and
694          *    continuing with this loop doesn't make any sense. */
695         if (status >= host_len) {
696             host_len = 0;
697             host_ptr = NULL;
698         }
699         /* else: Test if anything was added to the buffer */
700         else if (status > 0) {
701             host_len -= status;
702             host_ptr += status;
703         }
704
705         if (host_len <= 0)
706             break;
707     } /* for (i) */
708
709     vl->host[sizeof (host) - 1] = '\0';
710 } /* void init_value_list */
711
712 static void
713 cpu_submit (unsigned long long cpu_time,
714             time_t t,
715             virDomainPtr dom, const char *type)
716 {
717     value_t values[1];
718     value_list_t vl = VALUE_LIST_INIT;
719
720     init_value_list (&vl, t, dom);
721
722     values[0].counter = cpu_time;
723
724     vl.values = values;
725     vl.values_len = 1;
726
727     plugin_dispatch_values (type, &vl);
728 }
729
730 static void
731 vcpu_submit (counter_t cpu_time,
732              time_t t,
733              virDomainPtr dom, int vcpu_nr, const char *type)
734 {
735     value_t values[1];
736     value_list_t vl = VALUE_LIST_INIT;
737
738     init_value_list (&vl, t, dom);
739
740     values[0].counter = cpu_time;
741     vl.values = values;
742     vl.values_len = 1;
743
744     snprintf (vl.type_instance, sizeof (vl.type_instance), "%d", vcpu_nr);
745     vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
746
747     plugin_dispatch_values (type, &vl);
748 }
749
750 static void
751 submit_counter2 (const char *type, counter_t v0, counter_t v1,
752              time_t t,
753              virDomainPtr dom, const char *devname)
754 {
755     value_t values[2];
756     value_list_t vl = VALUE_LIST_INIT;
757
758     init_value_list (&vl, t, dom);
759
760     values[0].counter = v0;
761     values[1].counter = v1;
762     vl.values = values;
763     vl.values_len = 2;
764
765     strncpy (vl.type_instance, devname, sizeof (vl.type_instance));
766     vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
767
768     plugin_dispatch_values (type, &vl);
769 } /* void submit_counter2 */
770
771 static int
772 libvirtstats_shutdown (void)
773 {
774     free_block_devices ();
775     free_interface_devices ();
776     free_domains ();
777
778     if (conn != NULL)
779         virConnectClose (conn);
780     conn = NULL;
781
782     ignorelist_free (il_domains);
783     il_domains = NULL;
784     ignorelist_free (il_block_devices);
785     il_block_devices = NULL;
786     ignorelist_free (il_interface_devices);
787     il_interface_devices = NULL;
788
789     return 0;
790 }
791
792 void
793 module_register (void)
794 {
795     plugin_register_config ("libvirtstats",
796             libvirtstats_config,
797             config_keys, NR_CONFIG_KEYS);
798     plugin_register_init ("libvirtstats", libvirtstats_init);
799     plugin_register_read ("libvirtstats", libvirtstats_read);
800     plugin_register_shutdown ("libvirtstats", libvirtstats_shutdown);
801 }
802
803 /*
804  * vim: shiftwidth=4 tabstop=8 softtabstop=4 expandtab fdm=marker
805  */