2 * collectd - src/virt.c
3 * Copyright (C) 2006-2008 Red Hat Inc.
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.
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.
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
19 * Richard W.M. Jones <rjones@redhat.com>
26 #include "utils_complain.h"
27 #include "utils_ignorelist.h"
29 #include <libgen.h> /* for basename(3) */
30 #include <libvirt/libvirt.h>
31 #include <libvirt/virterror.h>
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include <libxml/xpath.h>
37 #define PLUGIN_NAME "virt"
39 static const char *config_keys[] = {"Connection",
46 "BlockDeviceFormatBasename",
53 "PluginInstanceFormat",
56 #define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
59 static virConnectPtr conn = 0;
60 static char *conn_string = NULL;
61 static c_complain_t conn_complain = C_COMPLAIN_INIT_STATIC;
63 /* Seconds between list refreshes, 0 disables completely. */
64 static int interval = 60;
66 /* List of domains, if specified. */
67 static ignorelist_t *il_domains = NULL;
68 /* List of block devices, if specified. */
69 static ignorelist_t *il_block_devices = NULL;
70 /* List of network interface devices, if specified. */
71 static ignorelist_t *il_interface_devices = NULL;
73 static int ignore_device_match(ignorelist_t *, const char *domname,
76 /* Actual list of domains found on last refresh. */
77 static virDomainPtr *domains = NULL;
78 static int nr_domains = 0;
80 static void free_domains(void);
81 static int add_domain(virDomainPtr dom);
83 /* Actual list of block devices found on last refresh. */
85 virDomainPtr dom; /* domain */
86 char *path; /* name of block device */
89 static struct block_device *block_devices = NULL;
90 static int nr_block_devices = 0;
92 static void free_block_devices(void);
93 static int add_block_device(virDomainPtr dom, const char *path);
95 /* Actual list of network interfaces found on last refresh. */
96 struct interface_device {
97 virDomainPtr dom; /* domain */
98 char *path; /* name of interface device */
99 char *address; /* mac address of interface device */
100 char *number; /* interface device number */
103 static struct interface_device *interface_devices = NULL;
104 static int nr_interface_devices = 0;
106 static void free_interface_devices(void);
107 static int add_interface_device(virDomainPtr dom, const char *path,
108 const char *address, unsigned int number);
110 /* HostnameFormat. */
111 #define HF_MAX_FIELDS 3
113 enum hf_field { hf_none = 0, hf_hostname, hf_name, hf_uuid };
115 static enum hf_field hostname_format[HF_MAX_FIELDS] = {hf_name};
117 /* PluginInstanceFormat */
118 #define PLGINST_MAX_FIELDS 2
120 enum plginst_field { plginst_none = 0, plginst_name, plginst_uuid };
122 static enum plginst_field plugin_instance_format[PLGINST_MAX_FIELDS] = {
125 /* BlockDeviceFormat */
126 enum bd_field { target, source };
128 /* InterfaceFormat. */
129 enum if_field { if_address, if_name, if_number };
131 /* BlockDeviceFormatBasename */
132 _Bool blockdevice_format_basename = 0;
133 static enum bd_field blockdevice_format = target;
134 static enum if_field interface_format = if_name;
136 /* Time that we last refreshed. */
137 static time_t last_refresh = (time_t)0;
139 static int refresh_lists(void);
141 /* ERROR(...) macro for virterrors. */
142 #define VIRT_ERROR(conn, s) \
145 err = (conn) ? virConnGetLastError((conn)) : virGetLastError(); \
147 ERROR("%s: %s", (s), err->message); \
150 static void init_value_list(value_list_t *vl, virDomainPtr dom) {
153 char uuid[VIR_UUID_STRING_BUFLEN];
155 sstrncpy(vl->plugin, PLUGIN_NAME, sizeof(vl->plugin));
159 /* Construct the hostname field according to HostnameFormat. */
160 for (int i = 0; i < HF_MAX_FIELDS; ++i) {
161 if (hostname_format[i] == hf_none)
164 n = DATA_MAX_NAME_LEN - strlen(vl->host) - 2;
166 if (i > 0 && n >= 1) {
167 strncat(vl->host, ":", 1);
171 switch (hostname_format[i]) {
175 strncat(vl->host, hostname_g, n);
178 name = virDomainGetName(dom);
180 strncat(vl->host, name, n);
183 if (virDomainGetUUIDString(dom, uuid) == 0)
184 strncat(vl->host, uuid, n);
189 vl->host[sizeof(vl->host) - 1] = '\0';
191 /* Construct the plugin instance field according to PluginInstanceFormat. */
192 for (int i = 0; i < PLGINST_MAX_FIELDS; ++i) {
193 if (plugin_instance_format[i] == plginst_none)
196 n = sizeof(vl->plugin_instance) - strlen(vl->plugin_instance) - 2;
198 if (i > 0 && n >= 1) {
199 strncat(vl->plugin_instance, ":", 1);
203 switch (plugin_instance_format[i]) {
207 name = virDomainGetName(dom);
209 strncat(vl->plugin_instance, name, n);
212 if (virDomainGetUUIDString(dom, uuid) == 0)
213 strncat(vl->plugin_instance, uuid, n);
218 vl->plugin_instance[sizeof(vl->plugin_instance) - 1] = '\0';
220 } /* void init_value_list */
222 static void submit(virDomainPtr dom, char const *type,
223 char const *type_instance, value_t *values,
225 value_list_t vl = VALUE_LIST_INIT;
226 init_value_list(&vl, dom);
229 vl.values_len = values_len;
231 sstrncpy(vl.type, type, sizeof(vl.type));
232 if (type_instance != NULL)
233 sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
235 plugin_dispatch_values(&vl);
238 static void memory_submit(gauge_t value, virDomainPtr dom) {
239 submit(dom, "memory", "total", &(value_t){.gauge = value}, 1);
242 static void memory_stats_submit(gauge_t value, virDomainPtr dom,
244 static const char *tags[] = {"swap_in", "swap_out", "major_fault",
245 "minor_fault", "unused", "available",
246 "actual_balloon", "rss"};
248 if ((tag_index < 0) || (tag_index >= STATIC_ARRAY_SIZE(tags))) {
249 ERROR("virt plugin: Array index out of bounds: tag_index = %d", tag_index);
253 submit(dom, "memory", tags[tag_index], &(value_t){.gauge = value}, 1);
256 static void cpu_submit(unsigned long long value, virDomainPtr dom,
258 submit(dom, type, NULL, &(value_t){.derive = (derive_t)value}, 1);
261 static void vcpu_submit(derive_t value, virDomainPtr dom, int vcpu_nr,
263 char type_instance[DATA_MAX_NAME_LEN];
265 ssnprintf(type_instance, sizeof(type_instance), "%d", vcpu_nr);
267 submit(dom, type, type_instance, &(value_t){.derive = value}, 1);
270 static void submit_derive2(const char *type, derive_t v0, derive_t v1,
271 virDomainPtr dom, const char *devname) {
273 {.derive = v0}, {.derive = v1},
276 submit(dom, type, devname, values, STATIC_ARRAY_SIZE(values));
277 } /* void submit_derive2 */
279 static int lv_init(void) {
280 if (virInitialize() != 0)
286 static int lv_config(const char *key, const char *value) {
287 if (virInitialize() != 0)
290 if (il_domains == NULL)
291 il_domains = ignorelist_create(1);
292 if (il_block_devices == NULL)
293 il_block_devices = ignorelist_create(1);
294 if (il_interface_devices == NULL)
295 il_interface_devices = ignorelist_create(1);
297 if (strcasecmp(key, "Connection") == 0) {
298 char *tmp = strdup(value);
300 ERROR(PLUGIN_NAME " plugin: Connection strdup failed.");
308 if (strcasecmp(key, "RefreshInterval") == 0) {
310 interval = strtol(value, &eptr, 10);
311 if (eptr == NULL || *eptr != '\0')
316 if (strcasecmp(key, "Domain") == 0) {
317 if (ignorelist_add(il_domains, value))
321 if (strcasecmp(key, "BlockDevice") == 0) {
322 if (ignorelist_add(il_block_devices, value))
327 if (strcasecmp(key, "BlockDeviceFormat") == 0) {
328 if (strcasecmp(value, "target") == 0)
329 blockdevice_format = target;
330 else if (strcasecmp(value, "source") == 0)
331 blockdevice_format = source;
333 ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s", value);
338 if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
339 blockdevice_format_basename = IS_TRUE(value);
342 if (strcasecmp(key, "InterfaceDevice") == 0) {
343 if (ignorelist_add(il_interface_devices, value))
348 if (strcasecmp(key, "IgnoreSelected") == 0) {
349 if (IS_TRUE(value)) {
350 ignorelist_set_invert(il_domains, 0);
351 ignorelist_set_invert(il_block_devices, 0);
352 ignorelist_set_invert(il_interface_devices, 0);
354 ignorelist_set_invert(il_domains, 1);
355 ignorelist_set_invert(il_block_devices, 1);
356 ignorelist_set_invert(il_interface_devices, 1);
361 if (strcasecmp(key, "HostnameFormat") == 0) {
363 char *fields[HF_MAX_FIELDS];
366 value_copy = strdup(value);
367 if (value_copy == NULL) {
368 ERROR(PLUGIN_NAME " plugin: strdup failed.");
372 n = strsplit(value_copy, fields, HF_MAX_FIELDS);
375 ERROR(PLUGIN_NAME " plugin: HostnameFormat: no fields");
379 for (int i = 0; i < n; ++i) {
380 if (strcasecmp(fields[i], "hostname") == 0)
381 hostname_format[i] = hf_hostname;
382 else if (strcasecmp(fields[i], "name") == 0)
383 hostname_format[i] = hf_name;
384 else if (strcasecmp(fields[i], "uuid") == 0)
385 hostname_format[i] = hf_uuid;
387 ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
395 for (int i = n; i < HF_MAX_FIELDS; ++i)
396 hostname_format[i] = hf_none;
401 if (strcasecmp(key, "PluginInstanceFormat") == 0) {
403 char *fields[PLGINST_MAX_FIELDS];
406 value_copy = strdup(value);
407 if (value_copy == NULL) {
408 ERROR(PLUGIN_NAME " plugin: strdup failed.");
412 n = strsplit(value_copy, fields, PLGINST_MAX_FIELDS);
415 ERROR(PLUGIN_NAME " plugin: PluginInstanceFormat: no fields");
419 for (int i = 0; i < n; ++i) {
420 if (strcasecmp(fields[i], "none") == 0) {
421 plugin_instance_format[i] = plginst_none;
423 } else if (strcasecmp(fields[i], "name") == 0)
424 plugin_instance_format[i] = plginst_name;
425 else if (strcasecmp(fields[i], "uuid") == 0)
426 plugin_instance_format[i] = plginst_uuid;
428 ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
436 for (int i = n; i < PLGINST_MAX_FIELDS; ++i)
437 plugin_instance_format[i] = plginst_none;
442 if (strcasecmp(key, "InterfaceFormat") == 0) {
443 if (strcasecmp(value, "name") == 0)
444 interface_format = if_name;
445 else if (strcasecmp(value, "address") == 0)
446 interface_format = if_address;
447 else if (strcasecmp(value, "number") == 0)
448 interface_format = if_number;
450 ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", value);
456 /* Unrecognised option. */
460 static int lv_read(void) {
464 /* `conn_string == NULL' is acceptable. */
465 conn = virConnectOpenReadOnly(conn_string);
467 c_complain(LOG_ERR, &conn_complain,
468 PLUGIN_NAME " plugin: Unable to connect: "
469 "virConnectOpenReadOnly failed.");
473 c_release(LOG_NOTICE, &conn_complain,
474 PLUGIN_NAME " plugin: Connection established.");
478 /* Need to refresh domain or device lists? */
479 if ((last_refresh == (time_t)0) ||
480 ((interval > 0) && ((last_refresh + interval) <= t))) {
481 if (refresh_lists() != 0) {
483 virConnectClose(conn);
491 for (int i = 0; i < nr_domains; ++i)
492 fprintf (stderr, "domain %s\n", virDomainGetName (domains[i]));
493 for (int i = 0; i < nr_block_devices; ++i)
494 fprintf (stderr, "block device %d %s:%s\n",
495 i, virDomainGetName (block_devices[i].dom),
496 block_devices[i].path);
497 for (int i = 0; i < nr_interface_devices; ++i)
498 fprintf (stderr, "interface device %d %s:%s\n",
499 i, virDomainGetName (interface_devices[i].dom),
500 interface_devices[i].path);
503 /* Get CPU usage, memory, VCPU usage for each domain. */
504 for (int i = 0; i < nr_domains; ++i) {
506 virVcpuInfoPtr vinfo = NULL;
507 virDomainMemoryStatPtr minfo = NULL;
510 status = virDomainGetInfo(domains[i], &info);
512 ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
517 if (info.state != VIR_DOMAIN_RUNNING) {
518 /* only gather stats for running domains */
522 cpu_submit(info.cpuTime, domains[i], "virt_cpu_total");
523 memory_submit((gauge_t)info.memory * 1024, domains[i]);
525 vinfo = malloc(info.nrVirtCpu * sizeof(vinfo[0]));
527 ERROR(PLUGIN_NAME " plugin: malloc failed.");
531 status = virDomainGetVcpus(domains[i], vinfo, info.nrVirtCpu,
532 /* cpu map = */ NULL, /* cpu map length = */ 0);
534 ERROR(PLUGIN_NAME " plugin: virDomainGetVcpus failed with status %i.",
540 for (int j = 0; j < info.nrVirtCpu; ++j)
541 vcpu_submit(vinfo[j].cpuTime, domains[i], vinfo[j].number, "virt_vcpu");
546 malloc(VIR_DOMAIN_MEMORY_STAT_NR * sizeof(virDomainMemoryStatStruct));
548 ERROR("virt plugin: malloc failed.");
553 virDomainMemoryStats(domains[i], minfo, VIR_DOMAIN_MEMORY_STAT_NR, 0);
556 ERROR("virt plugin: virDomainMemoryStats failed with status %i.", status);
561 for (int j = 0; j < status; j++) {
562 memory_stats_submit((gauge_t)minfo[j].val * 1024, domains[i],
569 /* Get block device stats for each domain. */
570 for (int i = 0; i < nr_block_devices; ++i) {
571 struct _virDomainBlockStats stats;
573 if (virDomainBlockStats(block_devices[i].dom, block_devices[i].path, &stats,
577 char *type_instance = NULL;
578 if (blockdevice_format_basename && blockdevice_format == source)
579 type_instance = strdup(basename(block_devices[i].path));
581 type_instance = strdup(block_devices[i].path);
583 if ((stats.rd_req != -1) && (stats.wr_req != -1))
584 submit_derive2("disk_ops", (derive_t)stats.rd_req, (derive_t)stats.wr_req,
585 block_devices[i].dom, type_instance);
587 if ((stats.rd_bytes != -1) && (stats.wr_bytes != -1))
588 submit_derive2("disk_octets", (derive_t)stats.rd_bytes,
589 (derive_t)stats.wr_bytes, block_devices[i].dom,
592 sfree(type_instance);
593 } /* for (nr_block_devices) */
595 /* Get interface stats for each domain. */
596 for (int i = 0; i < nr_interface_devices; ++i) {
597 struct _virDomainInterfaceStats stats;
598 char *display_name = NULL;
600 switch (interface_format) {
602 display_name = interface_devices[i].address;
605 display_name = interface_devices[i].number;
609 display_name = interface_devices[i].path;
612 if (virDomainInterfaceStats(interface_devices[i].dom,
613 interface_devices[i].path, &stats,
617 if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
618 submit_derive2("if_octets", (derive_t)stats.rx_bytes,
619 (derive_t)stats.tx_bytes, interface_devices[i].dom,
622 if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
623 submit_derive2("if_packets", (derive_t)stats.rx_packets,
624 (derive_t)stats.tx_packets, interface_devices[i].dom,
627 if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
628 submit_derive2("if_errors", (derive_t)stats.rx_errs,
629 (derive_t)stats.tx_errs, interface_devices[i].dom,
632 if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
633 submit_derive2("if_dropped", (derive_t)stats.rx_drop,
634 (derive_t)stats.tx_drop, interface_devices[i].dom,
636 } /* for (nr_interface_devices) */
641 static int refresh_lists(void) {
644 n = virConnectNumOfDomains(conn);
646 VIRT_ERROR(conn, "reading number of domains");
653 /* Get list of domains. */
654 domids = malloc(sizeof(*domids) * n);
655 if (domids == NULL) {
656 ERROR(PLUGIN_NAME " plugin: malloc failed.");
660 n = virConnectListDomains(conn, domids, n);
662 VIRT_ERROR(conn, "reading list of domains");
667 free_block_devices();
668 free_interface_devices();
671 /* Fetch each domain and add it to the list, unless ignore. */
672 for (int i = 0; i < n; ++i) {
673 virDomainPtr dom = NULL;
676 xmlDocPtr xml_doc = NULL;
677 xmlXPathContextPtr xpath_ctx = NULL;
678 xmlXPathObjectPtr xpath_obj = NULL;
680 dom = virDomainLookupByID(conn, domids[i]);
682 VIRT_ERROR(conn, "virDomainLookupByID");
683 /* Could be that the domain went away -- ignore it anyway. */
687 name = virDomainGetName(dom);
689 VIRT_ERROR(conn, "virDomainGetName");
693 if (il_domains && ignorelist_match(il_domains, name) != 0)
696 if (add_domain(dom) < 0) {
697 ERROR(PLUGIN_NAME " plugin: malloc failed.");
701 /* Get a list of devices for this domain. */
702 xml = virDomainGetXMLDesc(dom, 0);
704 VIRT_ERROR(conn, "virDomainGetXMLDesc");
708 /* Yuck, XML. Parse out the devices. */
709 xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
710 if (xml_doc == NULL) {
711 VIRT_ERROR(conn, "xmlReadDoc");
715 xpath_ctx = xmlXPathNewContext(xml_doc);
718 char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
719 if (blockdevice_format == source)
720 bd_xmlpath = "/domain/devices/disk/source[@dev]";
721 xpath_obj = xmlXPathEval((xmlChar *)bd_xmlpath, xpath_ctx);
723 if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
724 xpath_obj->nodesetval == NULL)
727 for (int j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
731 node = xpath_obj->nodesetval->nodeTab[j];
734 path = (char *)xmlGetProp(node, (xmlChar *)"dev");
738 if (il_block_devices &&
739 ignore_device_match(il_block_devices, name, path) != 0)
742 add_block_device(dom, path);
747 xmlXPathFreeObject(xpath_obj);
749 /* Network interfaces. */
750 xpath_obj = xmlXPathEval(
751 (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
752 if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
753 xpath_obj->nodesetval == NULL)
756 xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
758 for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
760 char *address = NULL;
761 xmlNodePtr xml_interface;
763 xml_interface = xml_interfaces->nodeTab[j];
767 for (xmlNodePtr child = xml_interface->children; child;
768 child = child->next) {
769 if (child->type != XML_ELEMENT_NODE)
772 if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
773 path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
776 } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
777 address = (char *)xmlGetProp(child, (const xmlChar *)"address");
783 if (il_interface_devices &&
784 (ignore_device_match(il_interface_devices, name, path) != 0 ||
785 ignore_device_match(il_interface_devices, name, address) != 0))
788 add_interface_device(dom, path, address, j + 1);
798 xmlXPathFreeObject(xpath_obj);
800 xmlXPathFreeContext(xpath_ctx);
812 static void free_domains(void) {
814 for (int i = 0; i < nr_domains; ++i)
815 virDomainFree(domains[i]);
822 static int add_domain(virDomainPtr dom) {
823 virDomainPtr *new_ptr;
824 int new_size = sizeof(domains[0]) * (nr_domains + 1);
827 new_ptr = realloc(domains, new_size);
829 new_ptr = malloc(new_size);
835 domains[nr_domains] = dom;
839 static void free_block_devices(void) {
841 for (int i = 0; i < nr_block_devices; ++i)
842 sfree(block_devices[i].path);
843 sfree(block_devices);
845 block_devices = NULL;
846 nr_block_devices = 0;
849 static int add_block_device(virDomainPtr dom, const char *path) {
850 struct block_device *new_ptr;
851 int new_size = sizeof(block_devices[0]) * (nr_block_devices + 1);
854 path_copy = strdup(path);
859 new_ptr = realloc(block_devices, new_size);
861 new_ptr = malloc(new_size);
863 if (new_ptr == NULL) {
867 block_devices = new_ptr;
868 block_devices[nr_block_devices].dom = dom;
869 block_devices[nr_block_devices].path = path_copy;
870 return nr_block_devices++;
873 static void free_interface_devices(void) {
874 if (interface_devices) {
875 for (int i = 0; i < nr_interface_devices; ++i) {
876 sfree(interface_devices[i].path);
877 sfree(interface_devices[i].address);
878 sfree(interface_devices[i].number);
880 sfree(interface_devices);
882 interface_devices = NULL;
883 nr_interface_devices = 0;
886 static int add_interface_device(virDomainPtr dom, const char *path,
887 const char *address, unsigned int number) {
888 struct interface_device *new_ptr;
889 int new_size = sizeof(interface_devices[0]) * (nr_interface_devices + 1);
890 char *path_copy, *address_copy, number_string[15];
892 if ((path == NULL) || (address == NULL))
895 path_copy = strdup(path);
899 address_copy = strdup(address);
905 snprintf(number_string, sizeof(number_string), "interface-%u", number);
907 if (interface_devices)
908 new_ptr = realloc(interface_devices, new_size);
910 new_ptr = malloc(new_size);
912 if (new_ptr == NULL) {
917 interface_devices = new_ptr;
918 interface_devices[nr_interface_devices].dom = dom;
919 interface_devices[nr_interface_devices].path = path_copy;
920 interface_devices[nr_interface_devices].address = address_copy;
921 interface_devices[nr_interface_devices].number = strdup(number_string);
922 return nr_interface_devices++;
925 static int ignore_device_match(ignorelist_t *il, const char *domname,
926 const char *devpath) {
930 if ((domname == NULL) || (devpath == NULL))
933 n = sizeof(char) * (strlen(domname) + strlen(devpath) + 2);
936 ERROR(PLUGIN_NAME " plugin: malloc failed.");
939 ssnprintf(name, n, "%s:%s", domname, devpath);
940 r = ignorelist_match(il, name);
945 static int lv_shutdown(void) {
946 free_block_devices();
947 free_interface_devices();
951 virConnectClose(conn);
954 ignorelist_free(il_domains);
956 ignorelist_free(il_block_devices);
957 il_block_devices = NULL;
958 ignorelist_free(il_interface_devices);
959 il_interface_devices = NULL;
964 void module_register(void) {
965 plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
966 plugin_register_init(PLUGIN_NAME, lv_init);
967 plugin_register_read(PLUGIN_NAME, lv_read);
968 plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
972 * vim: shiftwidth=4 tabstop=8 softtabstop=4 expandtab fdm=marker