virt plugin: Add option to disable extra stats
[collectd.git] / src / virt.c
1 /**
2  * collectd - src/virt.c
3  * Copyright (C) 2006-2008  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
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_complain.h"
27 #include "utils_ignorelist.h"
28
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>
35 #include <libxml/xpathInternals.h>
36
37 /* Plugin name */
38 #define PLUGIN_NAME "virt"
39
40 #ifdef LIBVIR_CHECK_VERSION
41 #if LIBVIR_CHECK_VERSION(0, 9, 5)
42 #define HAVE_BLOCK_STATS_FLAGS 1
43 #endif
44 #endif
45
46 static const char *config_keys[] = {"Connection",
47
48                                     "RefreshInterval",
49
50                                     "Domain",
51                                     "BlockDevice",
52                                     "BlockDeviceFormat",
53                                     "BlockDeviceFormatBasename",
54                                     "InterfaceDevice",
55                                     "IgnoreSelected",
56
57                                     "HostnameFormat",
58                                     "InterfaceFormat",
59
60                                     "PluginInstanceFormat",
61
62                                     "Instances",
63                                     "ExtraStats",
64
65                                     NULL};
66 #define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
67
68 /* Connection. */
69 static virConnectPtr conn = 0;
70 static char *conn_string = NULL;
71 static c_complain_t conn_complain = C_COMPLAIN_INIT_STATIC;
72
73 /* Seconds between list refreshes, 0 disables completely. */
74 static int interval = 60;
75
76 /* List of domains, if specified. */
77 static ignorelist_t *il_domains = NULL;
78 /* List of block devices, if specified. */
79 static ignorelist_t *il_block_devices = NULL;
80 /* List of network interface devices, if specified. */
81 static ignorelist_t *il_interface_devices = NULL;
82
83 static int ignore_device_match(ignorelist_t *, const char *domname,
84                                const char *devpath);
85
86 /* Actual list of block devices found on last refresh. */
87 struct block_device {
88   virDomainPtr dom; /* domain */
89   char *path;       /* name of block device */
90 };
91
92 /* Actual list of network interfaces found on last refresh. */
93 struct interface_device {
94   virDomainPtr dom; /* domain */
95   char *path;       /* name of interface device */
96   char *address;    /* mac address of interface device */
97   char *number;     /* interface device number */
98 };
99
100 struct lv_read_state {
101   /* Actual list of domains found on last refresh. */
102   virDomainPtr *domains;
103   int nr_domains;
104
105   struct block_device *block_devices;
106   int nr_block_devices;
107
108   struct interface_device *interface_devices;
109   int nr_interface_devices;
110 };
111
112 static void free_domains(struct lv_read_state *state);
113 static int add_domain(struct lv_read_state *state, virDomainPtr dom);
114
115 static void free_block_devices(struct lv_read_state *state);
116 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
117                             const char *path);
118
119 static void free_interface_devices(struct lv_read_state *state);
120 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
121                                 const char *path, const char *address,
122                                 unsigned int number);
123
124 #define METADATA_VM_PARTITION_URI "http://ovirt.org/ovirtmap/tag/1.0"
125 #define METADATA_VM_PARTITION_ELEMENT "tag"
126 #define METADATA_VM_PARTITION_PREFIX "ovirtmap"
127
128 #define BUFFER_MAX_LEN 256
129 #define PARTITION_TAG_MAX_LEN 32
130
131 struct lv_read_instance {
132   struct lv_read_state read_state;
133   char tag[PARTITION_TAG_MAX_LEN];
134   size_t id;
135 };
136
137 struct lv_user_data {
138   struct lv_read_instance inst;
139   user_data_t ud;
140 };
141
142 #define NR_INSTANCES_DEFAULT 1
143 #define NR_INSTANCES_MAX 128
144 static int nr_instances = NR_INSTANCES_DEFAULT;
145 static struct lv_user_data lv_read_user_data[NR_INSTANCES_MAX];
146
147 /* HostnameFormat. */
148 #define HF_MAX_FIELDS 3
149
150 enum hf_field { hf_none = 0, hf_hostname, hf_name, hf_uuid };
151
152 static enum hf_field hostname_format[HF_MAX_FIELDS] = {hf_name};
153
154 /* PluginInstanceFormat */
155 #define PLGINST_MAX_FIELDS 2
156
157 enum plginst_field { plginst_none = 0, plginst_name, plginst_uuid };
158
159 static enum plginst_field plugin_instance_format[PLGINST_MAX_FIELDS] = {
160     plginst_none};
161
162 /* BlockDeviceFormat */
163 enum bd_field { target, source };
164
165 /* InterfaceFormat. */
166 enum if_field { if_address, if_name, if_number };
167
168 /* ExtraStats */
169 #define EX_STATS_MAX_FIELDS 8
170 #define EX_STATS_DISK "disk"
171 enum ex_stats { ex_stats_none = 0, ex_stats_disk = 1 };
172 static enum ex_stats extra_stats = ex_stats_none;
173
174 /* BlockDeviceFormatBasename */
175 _Bool blockdevice_format_basename = 0;
176 static enum bd_field blockdevice_format = target;
177 static enum if_field interface_format = if_name;
178
179 /* Time that we last refreshed. */
180 static time_t last_refresh = (time_t)0;
181
182 static int refresh_lists(struct lv_read_instance *inst);
183
184 struct lv_block_info {
185   virDomainBlockStatsStruct bi;
186
187   long long rd_total_times;
188   long long wr_total_times;
189
190   long long fl_req;
191   long long fl_total_times;
192 };
193
194 static void init_block_info(struct lv_block_info *binfo) {
195   if (binfo == NULL)
196     return;
197
198   binfo->bi.rd_req = -1;
199   binfo->bi.wr_req = -1;
200   binfo->bi.rd_bytes = -1;
201   binfo->bi.wr_bytes = -1;
202
203   binfo->rd_total_times = -1;
204   binfo->wr_total_times = -1;
205   binfo->fl_req = -1;
206   binfo->fl_total_times = -1;
207 }
208
209 #ifdef HAVE_BLOCK_STATS_FLAGS
210
211 #define GET_BLOCK_INFO_VALUE(NAME, FIELD)                                      \
212   do {                                                                         \
213     if (!strcmp(param[i].field, NAME)) {                                       \
214       binfo->FIELD = param[i].value.l;                                         \
215       continue;                                                                \
216     }                                                                          \
217   } while (0)
218
219 static int get_block_info(struct lv_block_info *binfo,
220                           virTypedParameterPtr param, int nparams) {
221   if (binfo == NULL || param == NULL)
222     return -1;
223
224   for (int i = 0; i < nparams; ++i) {
225     /* ignore type. Everything must be LLONG anyway. */
226     GET_BLOCK_INFO_VALUE("rd_operations", bi.rd_req);
227     GET_BLOCK_INFO_VALUE("wr_operations", bi.wr_req);
228     GET_BLOCK_INFO_VALUE("rd_bytes", bi.rd_bytes);
229     GET_BLOCK_INFO_VALUE("wr_bytes", bi.wr_bytes);
230     GET_BLOCK_INFO_VALUE("rd_total_times", rd_total_times);
231     GET_BLOCK_INFO_VALUE("wr_total_times", wr_total_times);
232     GET_BLOCK_INFO_VALUE("flush_operations", fl_req);
233     GET_BLOCK_INFO_VALUE("flush_total_times", fl_total_times);
234   }
235
236   return 0;
237 }
238
239 #undef GET_BLOCK_INFO_VALUE
240
241 #endif /* HAVE_BLOCK_STATS_FLAGS */
242
243 /* ERROR(...) macro for virterrors. */
244 #define VIRT_ERROR(conn, s)                                                    \
245   do {                                                                         \
246     virErrorPtr err;                                                           \
247     err = (conn) ? virConnGetLastError((conn)) : virGetLastError();            \
248     if (err)                                                                   \
249       ERROR("%s: %s", (s), err->message);                                      \
250   } while (0)
251
252 static void init_value_list(value_list_t *vl, virDomainPtr dom) {
253   int n;
254   const char *name;
255   char uuid[VIR_UUID_STRING_BUFLEN];
256
257   sstrncpy(vl->plugin, PLUGIN_NAME, sizeof(vl->plugin));
258
259   vl->host[0] = '\0';
260
261   /* Construct the hostname field according to HostnameFormat. */
262   for (int i = 0; i < HF_MAX_FIELDS; ++i) {
263     if (hostname_format[i] == hf_none)
264       continue;
265
266     n = DATA_MAX_NAME_LEN - strlen(vl->host) - 2;
267
268     if (i > 0 && n >= 1) {
269       strncat(vl->host, ":", 1);
270       n--;
271     }
272
273     switch (hostname_format[i]) {
274     case hf_none:
275       break;
276     case hf_hostname:
277       strncat(vl->host, hostname_g, n);
278       break;
279     case hf_name:
280       name = virDomainGetName(dom);
281       if (name)
282         strncat(vl->host, name, n);
283       break;
284     case hf_uuid:
285       if (virDomainGetUUIDString(dom, uuid) == 0)
286         strncat(vl->host, uuid, n);
287       break;
288     }
289   }
290
291   vl->host[sizeof(vl->host) - 1] = '\0';
292
293   /* Construct the plugin instance field according to PluginInstanceFormat. */
294   for (int i = 0; i < PLGINST_MAX_FIELDS; ++i) {
295     if (plugin_instance_format[i] == plginst_none)
296       continue;
297
298     n = sizeof(vl->plugin_instance) - strlen(vl->plugin_instance) - 2;
299
300     if (i > 0 && n >= 1) {
301       strncat(vl->plugin_instance, ":", 1);
302       n--;
303     }
304
305     switch (plugin_instance_format[i]) {
306     case plginst_none:
307       break;
308     case plginst_name:
309       name = virDomainGetName(dom);
310       if (name)
311         strncat(vl->plugin_instance, name, n);
312       break;
313     case plginst_uuid:
314       if (virDomainGetUUIDString(dom, uuid) == 0)
315         strncat(vl->plugin_instance, uuid, n);
316       break;
317     }
318   }
319
320   vl->plugin_instance[sizeof(vl->plugin_instance) - 1] = '\0';
321
322 } /* void init_value_list */
323
324 static void submit(virDomainPtr dom, char const *type,
325                    char const *type_instance, value_t *values,
326                    size_t values_len) {
327   value_list_t vl = VALUE_LIST_INIT;
328   init_value_list(&vl, dom);
329
330   vl.values = values;
331   vl.values_len = values_len;
332
333   sstrncpy(vl.type, type, sizeof(vl.type));
334   if (type_instance != NULL)
335     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
336
337   plugin_dispatch_values(&vl);
338 }
339
340 static void memory_submit(gauge_t value, virDomainPtr dom) {
341   submit(dom, "memory", "total", &(value_t){.gauge = value}, 1);
342 }
343
344 static void memory_stats_submit(gauge_t value, virDomainPtr dom,
345                                 int tag_index) {
346   static const char *tags[] = {"swap_in",        "swap_out", "major_fault",
347                                "minor_fault",    "unused",   "available",
348                                "actual_balloon", "rss"};
349
350   if ((tag_index < 0) || (tag_index >= STATIC_ARRAY_SIZE(tags))) {
351     ERROR("virt plugin: Array index out of bounds: tag_index = %d", tag_index);
352     return;
353   }
354
355   submit(dom, "memory", tags[tag_index], &(value_t){.gauge = value}, 1);
356 }
357
358 static void cpu_submit(unsigned long long value, virDomainPtr dom,
359                        const char *type) {
360   submit(dom, type, NULL, &(value_t){.derive = (derive_t)value}, 1);
361 }
362
363 static void vcpu_submit(derive_t value, virDomainPtr dom, int vcpu_nr,
364                         const char *type) {
365   char type_instance[DATA_MAX_NAME_LEN];
366
367   ssnprintf(type_instance, sizeof(type_instance), "%d", vcpu_nr);
368
369   submit(dom, type, type_instance, &(value_t){.derive = value}, 1);
370 }
371
372 static void submit_derive2(const char *type, derive_t v0, derive_t v1,
373                            virDomainPtr dom, const char *devname) {
374   value_t values[] = {
375       {.derive = v0}, {.derive = v1},
376   };
377
378   submit(dom, type, devname, values, STATIC_ARRAY_SIZE(values));
379 } /* void submit_derive2 */
380
381 static void disk_submit(struct lv_block_info *binfo, virDomainPtr dom,
382                         const char *type_instance) {
383   char flush_type_instance[DATA_MAX_NAME_LEN];
384
385   ssnprintf(flush_type_instance, sizeof(flush_type_instance), "flush-%s",
386             type_instance);
387
388   if ((binfo->bi.rd_req != -1) && (binfo->bi.wr_req != -1))
389     submit_derive2("disk_ops", (derive_t)binfo->bi.rd_req,
390                    (derive_t)binfo->bi.wr_req, dom, type_instance);
391
392   if ((binfo->bi.rd_bytes != -1) && (binfo->bi.wr_bytes != -1))
393     submit_derive2("disk_octets", (derive_t)binfo->bi.rd_bytes,
394                    (derive_t)binfo->bi.wr_bytes, dom, type_instance);
395
396   if (extra_stats & ex_stats_disk) {
397     if ((binfo->rd_total_times != -1) && (binfo->wr_total_times != -1))
398       submit_derive2("disk_time", (derive_t)binfo->rd_total_times,
399                      (derive_t)binfo->wr_total_times, dom, type_instance);
400
401     if (binfo->fl_req != -1)
402       submit(dom, "total_requests", flush_type_instance,
403              &(value_t){.derive = (derive_t)binfo->fl_req}, 1);
404     if (binfo->fl_total_times != -1) {
405       derive_t value = binfo->fl_total_times / 1000; // ns -> ms
406       submit(dom, "total_time_in_ms", flush_type_instance,
407              &(value_t){.derive = value}, 1);
408     }
409   }
410 }
411
412 static int lv_config(const char *key, const char *value) {
413   if (virInitialize() != 0)
414     return 1;
415
416   if (il_domains == NULL)
417     il_domains = ignorelist_create(1);
418   if (il_block_devices == NULL)
419     il_block_devices = ignorelist_create(1);
420   if (il_interface_devices == NULL)
421     il_interface_devices = ignorelist_create(1);
422
423   if (strcasecmp(key, "Connection") == 0) {
424     char *tmp = strdup(value);
425     if (tmp == NULL) {
426       ERROR(PLUGIN_NAME " plugin: Connection strdup failed.");
427       return 1;
428     }
429     sfree(conn_string);
430     conn_string = tmp;
431     return 0;
432   }
433
434   if (strcasecmp(key, "RefreshInterval") == 0) {
435     char *eptr = NULL;
436     interval = strtol(value, &eptr, 10);
437     if (eptr == NULL || *eptr != '\0')
438       return 1;
439     return 0;
440   }
441
442   if (strcasecmp(key, "Domain") == 0) {
443     if (ignorelist_add(il_domains, value))
444       return 1;
445     return 0;
446   }
447   if (strcasecmp(key, "BlockDevice") == 0) {
448     if (ignorelist_add(il_block_devices, value))
449       return 1;
450     return 0;
451   }
452
453   if (strcasecmp(key, "BlockDeviceFormat") == 0) {
454     if (strcasecmp(value, "target") == 0)
455       blockdevice_format = target;
456     else if (strcasecmp(value, "source") == 0)
457       blockdevice_format = source;
458     else {
459       ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s", value);
460       return -1;
461     }
462     return 0;
463   }
464   if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
465     blockdevice_format_basename = IS_TRUE(value);
466     return 0;
467   }
468   if (strcasecmp(key, "InterfaceDevice") == 0) {
469     if (ignorelist_add(il_interface_devices, value))
470       return 1;
471     return 0;
472   }
473
474   if (strcasecmp(key, "IgnoreSelected") == 0) {
475     if (IS_TRUE(value)) {
476       ignorelist_set_invert(il_domains, 0);
477       ignorelist_set_invert(il_block_devices, 0);
478       ignorelist_set_invert(il_interface_devices, 0);
479     } else {
480       ignorelist_set_invert(il_domains, 1);
481       ignorelist_set_invert(il_block_devices, 1);
482       ignorelist_set_invert(il_interface_devices, 1);
483     }
484     return 0;
485   }
486
487   if (strcasecmp(key, "HostnameFormat") == 0) {
488     char *value_copy;
489     char *fields[HF_MAX_FIELDS];
490     int n;
491
492     value_copy = strdup(value);
493     if (value_copy == NULL) {
494       ERROR(PLUGIN_NAME " plugin: strdup failed.");
495       return -1;
496     }
497
498     n = strsplit(value_copy, fields, HF_MAX_FIELDS);
499     if (n < 1) {
500       sfree(value_copy);
501       ERROR(PLUGIN_NAME " plugin: HostnameFormat: no fields");
502       return -1;
503     }
504
505     for (int i = 0; i < n; ++i) {
506       if (strcasecmp(fields[i], "hostname") == 0)
507         hostname_format[i] = hf_hostname;
508       else if (strcasecmp(fields[i], "name") == 0)
509         hostname_format[i] = hf_name;
510       else if (strcasecmp(fields[i], "uuid") == 0)
511         hostname_format[i] = hf_uuid;
512       else {
513         ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
514               fields[i]);
515         sfree(value_copy);
516         return -1;
517       }
518     }
519     sfree(value_copy);
520
521     for (int i = n; i < HF_MAX_FIELDS; ++i)
522       hostname_format[i] = hf_none;
523
524     return 0;
525   }
526
527   if (strcasecmp(key, "PluginInstanceFormat") == 0) {
528     char *value_copy;
529     char *fields[PLGINST_MAX_FIELDS];
530     int n;
531
532     value_copy = strdup(value);
533     if (value_copy == NULL) {
534       ERROR(PLUGIN_NAME " plugin: strdup failed.");
535       return -1;
536     }
537
538     n = strsplit(value_copy, fields, PLGINST_MAX_FIELDS);
539     if (n < 1) {
540       sfree(value_copy);
541       ERROR(PLUGIN_NAME " plugin: PluginInstanceFormat: no fields");
542       return -1;
543     }
544
545     for (int i = 0; i < n; ++i) {
546       if (strcasecmp(fields[i], "none") == 0) {
547         plugin_instance_format[i] = plginst_none;
548         break;
549       } else if (strcasecmp(fields[i], "name") == 0)
550         plugin_instance_format[i] = plginst_name;
551       else if (strcasecmp(fields[i], "uuid") == 0)
552         plugin_instance_format[i] = plginst_uuid;
553       else {
554         ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
555               fields[i]);
556         sfree(value_copy);
557         return -1;
558       }
559     }
560     sfree(value_copy);
561
562     for (int i = n; i < PLGINST_MAX_FIELDS; ++i)
563       plugin_instance_format[i] = plginst_none;
564
565     return 0;
566   }
567
568   if (strcasecmp(key, "InterfaceFormat") == 0) {
569     if (strcasecmp(value, "name") == 0)
570       interface_format = if_name;
571     else if (strcasecmp(value, "address") == 0)
572       interface_format = if_address;
573     else if (strcasecmp(value, "number") == 0)
574       interface_format = if_number;
575     else {
576       ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", value);
577       return -1;
578     }
579     return 0;
580   }
581
582   if (strcasecmp(key, "Instances") == 0) {
583     char *eptr = NULL;
584     double val = strtod(value, &eptr);
585
586     if (*eptr != '\0') {
587       ERROR(PLUGIN_NAME " plugin: Invalid value for Instances = '%s'", value);
588       return 1;
589     }
590     if (val <= 0) {
591       ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
592       return 1;
593     }
594     if (val > NR_INSTANCES_MAX) {
595       ERROR(PLUGIN_NAME " plugin: Instances=%f > NR_INSTANCES_MAX=%i"
596                         " use a lower setting or recompile the plugin.",
597             val, NR_INSTANCES_MAX);
598       return 1;
599     }
600
601     nr_instances = (int)val;
602     DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
603     return 0;
604   }
605
606   if (strcasecmp(key, "ExtraStats") == 0) {
607     char *localvalue = sstrdup(value);
608     if (localvalue != NULL) {
609       char *exstats[EX_STATS_MAX_FIELDS];
610       int numexstats;
611
612       numexstats = strsplit(localvalue, exstats, STATIC_ARRAY_SIZE(exstats));
613       for (int i = 0; i < numexstats; i++) {
614         if (strcasecmp(exstats[i], EX_STATS_DISK) == 0) {
615           DEBUG(PLUGIN_NAME " plugin: enabling extra stats for '%s'",
616                 EX_STATS_DISK);
617           extra_stats |= ex_stats_disk;
618         }
619       }
620       sfree(localvalue);
621     }
622   }
623
624   /* Unrecognised option. */
625   return -1;
626 }
627
628 static int lv_connect(void) {
629   if (conn == NULL) {
630     /* `conn_string == NULL' is acceptable. */
631     conn = virConnectOpenReadOnly(conn_string);
632     if (conn == NULL) {
633       c_complain(LOG_ERR, &conn_complain,
634                  PLUGIN_NAME " plugin: Unable to connect: "
635                              "virConnectOpenReadOnly failed.");
636       return -1;
637     }
638   }
639   c_release(LOG_NOTICE, &conn_complain,
640             PLUGIN_NAME " plugin: Connection established.");
641   return 0;
642 }
643
644 static void lv_disconnect(void) {
645   if (conn != NULL)
646     virConnectClose(conn);
647   conn = NULL;
648   WARNING(PLUGIN_NAME " plugin: closed connection to libvirt");
649 }
650
651 static int lv_domain_block_info(virDomainPtr dom, const char *path,
652                                 struct lv_block_info *binfo) {
653 #ifdef HAVE_BLOCK_STATS_FLAGS
654   virTypedParameterPtr params = NULL;
655   int nparams = 0;
656   int rc = -1;
657   int ret;
658
659   ret = virDomainBlockStatsFlags(dom, path, NULL, &nparams, 0);
660   if (ret < 0 || nparams == 0) {
661     VIRT_ERROR(conn, "getting the disk params count");
662     return -1;
663   }
664
665   params = calloc(nparams, sizeof(virTypedParameter));
666   if (params == NULL) {
667     ERROR("virt plugin: alloc(%i) for block=%s parameters failed.", nparams,
668           path);
669     return -1;
670   }
671   ret = virDomainBlockStatsFlags(dom, path, params, &nparams, 0);
672   if (ret < 0) {
673     VIRT_ERROR(conn, "getting the disk params values");
674     goto done;
675   }
676
677   rc = get_block_info(binfo, params, nparams);
678
679 done:
680   virTypedParamsFree(params, nparams);
681   return rc;
682 #else
683   return virDomainBlockStats(dom, path, &(binfo->bi), sizeof(binfo->bi));
684 #endif /* HAVE_BLOCK_STATS_FLAGS */
685 }
686
687 static int lv_read(user_data_t *ud) {
688   time_t t;
689   struct lv_read_instance *inst = NULL;
690   struct lv_read_state *state = NULL;
691
692   if (ud->data == NULL) {
693     ERROR(PLUGIN_NAME " plugin: NULL userdata");
694     return -1;
695   }
696
697   inst = ud->data;
698   state = &inst->read_state;
699
700   if (inst->id == 0) {
701     if (lv_connect() < 0)
702       return -1;
703   }
704
705   time(&t);
706
707   /* Need to refresh domain or device lists? */
708   if ((last_refresh == (time_t)0) ||
709       ((interval > 0) && ((last_refresh + interval) <= t))) {
710     if (refresh_lists(inst) != 0) {
711       if (inst->id == 0)
712         lv_disconnect();
713       return -1;
714     }
715     last_refresh = t;
716   }
717
718 #if 0
719     for (int i = 0; i < nr_domains; ++i)
720         fprintf (stderr, "domain %s\n", virDomainGetName (domains[i]));
721     for (int i = 0; i < nr_block_devices; ++i)
722         fprintf  (stderr, "block device %d %s:%s\n",
723                   i, virDomainGetName (block_devices[i].dom),
724                   block_devices[i].path);
725     for (int i = 0; i < nr_interface_devices; ++i)
726         fprintf (stderr, "interface device %d %s:%s\n",
727                  i, virDomainGetName (interface_devices[i].dom),
728                  interface_devices[i].path);
729 #endif
730
731   /* Get CPU usage, memory, VCPU usage for each domain. */
732   for (int i = 0; i < state->nr_domains; ++i) {
733     virDomainInfo info;
734     virVcpuInfoPtr vinfo = NULL;
735     virDomainMemoryStatPtr minfo = NULL;
736     int status;
737
738     status = virDomainGetInfo(state->domains[i], &info);
739     if (status != 0) {
740       ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
741             status);
742       continue;
743     }
744
745     if (info.state != VIR_DOMAIN_RUNNING) {
746       /* only gather stats for running domains */
747       continue;
748     }
749
750     cpu_submit(info.cpuTime, state->domains[i], "virt_cpu_total");
751     memory_submit((gauge_t)info.memory * 1024, state->domains[i]);
752
753     vinfo = malloc(info.nrVirtCpu * sizeof(vinfo[0]));
754     if (vinfo == NULL) {
755       ERROR(PLUGIN_NAME " plugin: malloc failed.");
756       continue;
757     }
758
759     status = virDomainGetVcpus(state->domains[i], vinfo, info.nrVirtCpu,
760                                /* cpu map = */ NULL, /* cpu map length = */ 0);
761     if (status < 0) {
762       ERROR(PLUGIN_NAME " plugin: virDomainGetVcpus failed with status %i.",
763             status);
764       sfree(vinfo);
765       continue;
766     }
767
768     for (int j = 0; j < info.nrVirtCpu; ++j)
769       vcpu_submit(vinfo[j].cpuTime, state->domains[i], vinfo[j].number,
770                   "virt_vcpu");
771
772     sfree(vinfo);
773
774     minfo =
775         malloc(VIR_DOMAIN_MEMORY_STAT_NR * sizeof(virDomainMemoryStatStruct));
776     if (minfo == NULL) {
777       ERROR("virt plugin: malloc failed.");
778       continue;
779     }
780
781     status = virDomainMemoryStats(state->domains[i], minfo,
782                                   VIR_DOMAIN_MEMORY_STAT_NR, 0);
783
784     if (status < 0) {
785       ERROR("virt plugin: virDomainMemoryStats failed with status %i.", status);
786       sfree(minfo);
787       continue;
788     }
789
790     for (int j = 0; j < status; j++) {
791       memory_stats_submit((gauge_t)minfo[j].val * 1024, state->domains[i],
792                           minfo[j].tag);
793     }
794
795     sfree(minfo);
796   }
797
798   /* Get block device stats for each domain. */
799   for (int i = 0; i < state->nr_block_devices; ++i) {
800     struct block_device *bdev = &(state->block_devices[i]);
801     struct lv_block_info binfo;
802     init_block_info(&binfo);
803
804     if (lv_domain_block_info(bdev->dom, bdev->path, &binfo) < 0)
805       continue;
806
807     char *type_instance = NULL;
808     if (blockdevice_format_basename && blockdevice_format == source)
809       type_instance = strdup(basename(bdev->path));
810     else
811       type_instance = strdup(bdev->path);
812
813     disk_submit(&binfo, bdev->dom, type_instance);
814
815     sfree(type_instance);
816   } /* for (nr_block_devices) */
817
818   /* Get interface stats for each domain. */
819   for (int i = 0; i < state->nr_interface_devices; ++i) {
820     struct _virDomainInterfaceStats stats;
821     char *display_name = NULL;
822
823     switch (interface_format) {
824     case if_address:
825       display_name = state->interface_devices[i].address;
826       break;
827     case if_number:
828       display_name = state->interface_devices[i].number;
829       break;
830     case if_name:
831     default:
832       display_name = state->interface_devices[i].path;
833     }
834
835     if (virDomainInterfaceStats(state->interface_devices[i].dom,
836                                 state->interface_devices[i].path, &stats,
837                                 sizeof stats) != 0)
838       continue;
839
840     if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
841       submit_derive2("if_octets", (derive_t)stats.rx_bytes,
842                      (derive_t)stats.tx_bytes, state->interface_devices[i].dom,
843                      display_name);
844
845     if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
846       submit_derive2("if_packets", (derive_t)stats.rx_packets,
847                      (derive_t)stats.tx_packets,
848                      state->interface_devices[i].dom, display_name);
849
850     if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
851       submit_derive2("if_errors", (derive_t)stats.rx_errs,
852                      (derive_t)stats.tx_errs, state->interface_devices[i].dom,
853                      display_name);
854
855     if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
856       submit_derive2("if_dropped", (derive_t)stats.rx_drop,
857                      (derive_t)stats.tx_drop, state->interface_devices[i].dom,
858                      display_name);
859   } /* for (nr_interface_devices) */
860
861   return 0;
862 }
863
864 static int lv_init_instance(size_t i, plugin_read_cb callback) {
865   struct lv_user_data *lv_ud = &(lv_read_user_data[i]);
866   struct lv_read_instance *inst = &(lv_ud->inst);
867
868   memset(lv_ud, 0, sizeof(*lv_ud));
869
870   ssnprintf(inst->tag, sizeof(inst->tag), "%s-%zu", PLUGIN_NAME, i);
871   inst->id = i;
872
873   user_data_t *ud = &(lv_ud->ud);
874   ud->data = inst;
875   ud->free_func = NULL;
876
877   INFO(PLUGIN_NAME " plugin: reader %s initialized", inst->tag);
878   return plugin_register_complex_read(NULL, inst->tag, callback, 0, ud);
879 }
880
881 static void lv_clean_read_state(struct lv_read_state *state) {
882   free_block_devices(state);
883   free_interface_devices(state);
884   free_domains(state);
885 }
886
887 static void lv_fini_instance(size_t i) {
888   struct lv_read_instance *inst = &(lv_read_user_data[i].inst);
889   struct lv_read_state *state = &(inst->read_state);
890
891   lv_clean_read_state(state);
892   INFO(PLUGIN_NAME " plugin: reader %s finalized", inst->tag);
893 }
894
895 static int lv_init(void) {
896   if (virInitialize() != 0)
897     return -1;
898
899   if (lv_connect() != 0)
900     return -1;
901
902   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
903
904   for (int i = 0; i < nr_instances; ++i)
905     lv_init_instance(i, lv_read);
906
907   return 0;
908 }
909
910 /*
911  * returns 0 on success and <0 on error
912  */
913 static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
914                              char *dom_tag) {
915   char xpath_str[BUFFER_MAX_LEN] = {'\0'};
916   xmlXPathObjectPtr xpath_obj = NULL;
917   xmlNodePtr xml_node = NULL;
918   int ret = -1;
919   int err;
920
921   err = xmlXPathRegisterNs(xpath_ctx,
922                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
923                            (const xmlChar *)METADATA_VM_PARTITION_URI);
924   if (err) {
925     ERROR(PLUGIN_NAME " plugin: xmlXpathRegisterNs(%s, %s) failed on domain %s",
926           METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_URI, dom_name);
927     goto done;
928   }
929
930   ssnprintf(xpath_str, sizeof(xpath_str), "/domain/metadata/%s:%s/text()",
931             METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_ELEMENT);
932   xpath_obj = xmlXPathEvalExpression((xmlChar *)xpath_str, xpath_ctx);
933   if (xpath_obj == NULL) {
934     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed on domain %s",
935           xpath_str, dom_name);
936     goto done;
937   }
938
939   if (xpath_obj->type != XPATH_NODESET) {
940     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d "
941                       "(wanted %d) on domain %s",
942           xpath_str, xpath_obj->type, XPATH_NODESET, dom_name);
943     goto done;
944   }
945
946   /*
947    * from now on there is no real error, it's ok if a domain
948    * doesn't have the metadata partition tag.
949    */
950   ret = 0;
951   if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) {
952     DEBUG(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i "
953                       "expected=1 on domain %s",
954           xpath_str,
955           (xpath_obj->nodesetval == NULL) ? 0 : xpath_obj->nodesetval->nodeNr,
956           dom_name);
957   } else {
958     xml_node = xpath_obj->nodesetval->nodeTab[0];
959     sstrncpy(dom_tag, (const char *)xml_node->content, PARTITION_TAG_MAX_LEN);
960   }
961
962 done:
963   /* deregister to clean up */
964   err = xmlXPathRegisterNs(xpath_ctx,
965                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX, NULL);
966   if (err) {
967     /* we can't really recover here */
968     ERROR(PLUGIN_NAME
969           " plugin: deregistration of namespace %s failed for domain %s",
970           METADATA_VM_PARTITION_PREFIX, dom_name);
971   }
972   if (xpath_obj)
973     xmlXPathFreeObject(xpath_obj);
974
975   return ret;
976 }
977
978 static int is_known_tag(const char *dom_tag) {
979   for (int i = 0; i < nr_instances; ++i)
980     if (!strcmp(dom_tag, lv_read_user_data[i].inst.tag))
981       return 1;
982   return 0;
983 }
984
985 static int lv_instance_include_domain(struct lv_read_instance *inst,
986                                       const char *dom_name,
987                                       const char *dom_tag) {
988   if ((dom_tag[0] != '\0') && (strcmp(dom_tag, inst->tag) == 0))
989     return 1;
990
991   /* instance#0 will always be there, so it is in charge of extra duties */
992   if (inst->id == 0) {
993     if (dom_tag[0] == '\0' || !is_known_tag(dom_tag)) {
994       DEBUG(PLUGIN_NAME " plugin#%s: refreshing domain %s "
995                         "with unknown tag '%s'",
996             inst->tag, dom_name, dom_tag);
997       return 1;
998     }
999   }
1000
1001   return 0;
1002 }
1003
1004 static int refresh_lists(struct lv_read_instance *inst) {
1005   struct lv_read_state *state = &inst->read_state;
1006   int n;
1007
1008   n = virConnectNumOfDomains(conn);
1009   if (n < 0) {
1010     VIRT_ERROR(conn, "reading number of domains");
1011     return -1;
1012   }
1013
1014   lv_clean_read_state(state);
1015
1016   if (n > 0) {
1017     int *domids;
1018
1019     /* Get list of domains. */
1020     domids = malloc(sizeof(*domids) * n);
1021     if (domids == NULL) {
1022       ERROR(PLUGIN_NAME " plugin: malloc failed.");
1023       return -1;
1024     }
1025
1026     n = virConnectListDomains(conn, domids, n);
1027     if (n < 0) {
1028       VIRT_ERROR(conn, "reading list of domains");
1029       sfree(domids);
1030       return -1;
1031     }
1032
1033     /* Fetch each domain and add it to the list, unless ignore. */
1034     for (int i = 0; i < n; ++i) {
1035       virDomainPtr dom = NULL;
1036       const char *name;
1037       char *xml = NULL;
1038       xmlDocPtr xml_doc = NULL;
1039       xmlXPathContextPtr xpath_ctx = NULL;
1040       xmlXPathObjectPtr xpath_obj = NULL;
1041       char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
1042
1043       dom = virDomainLookupByID(conn, domids[i]);
1044       if (dom == NULL) {
1045         VIRT_ERROR(conn, "virDomainLookupByID");
1046         /* Could be that the domain went away -- ignore it anyway. */
1047         continue;
1048       }
1049
1050       name = virDomainGetName(dom);
1051       if (name == NULL) {
1052         VIRT_ERROR(conn, "virDomainGetName");
1053         goto cont;
1054       }
1055
1056       if (il_domains && ignorelist_match(il_domains, name) != 0)
1057         goto cont;
1058
1059       /* Get a list of devices for this domain. */
1060       xml = virDomainGetXMLDesc(dom, 0);
1061       if (!xml) {
1062         VIRT_ERROR(conn, "virDomainGetXMLDesc");
1063         goto cont;
1064       }
1065
1066       /* Yuck, XML.  Parse out the devices. */
1067       xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
1068       if (xml_doc == NULL) {
1069         VIRT_ERROR(conn, "xmlReadDoc");
1070         goto cont;
1071       }
1072
1073       xpath_ctx = xmlXPathNewContext(xml_doc);
1074
1075       if (lv_domain_get_tag(xpath_ctx, name, tag) < 0) {
1076         ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
1077         goto cont;
1078       }
1079
1080       if (!lv_instance_include_domain(inst, name, tag))
1081         goto cont;
1082
1083       if (add_domain(state, dom) < 0) {
1084         ERROR(PLUGIN_NAME " plugin: malloc failed.");
1085         goto cont;
1086       }
1087
1088       /* Block devices. */
1089       const char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
1090       if (blockdevice_format == source)
1091         bd_xmlpath = "/domain/devices/disk/source[@dev]";
1092       xpath_obj = xmlXPathEval((const xmlChar *)bd_xmlpath, xpath_ctx);
1093
1094       if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
1095           xpath_obj->nodesetval == NULL)
1096         goto cont;
1097
1098       for (int j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
1099         xmlNodePtr node;
1100         char *path = NULL;
1101
1102         node = xpath_obj->nodesetval->nodeTab[j];
1103         if (!node)
1104           continue;
1105         path = (char *)xmlGetProp(node, (xmlChar *)"dev");
1106         if (!path)
1107           continue;
1108
1109         if (il_block_devices &&
1110             ignore_device_match(il_block_devices, name, path) != 0)
1111           goto cont2;
1112
1113         add_block_device(state, dom, path);
1114       cont2:
1115         if (path)
1116           xmlFree(path);
1117       }
1118       xmlXPathFreeObject(xpath_obj);
1119
1120       /* Network interfaces. */
1121       xpath_obj = xmlXPathEval(
1122           (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
1123       if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
1124           xpath_obj->nodesetval == NULL)
1125         goto cont;
1126
1127       xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
1128
1129       for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
1130         char *path = NULL;
1131         char *address = NULL;
1132         xmlNodePtr xml_interface;
1133
1134         xml_interface = xml_interfaces->nodeTab[j];
1135         if (!xml_interface)
1136           continue;
1137
1138         for (xmlNodePtr child = xml_interface->children; child;
1139              child = child->next) {
1140           if (child->type != XML_ELEMENT_NODE)
1141             continue;
1142
1143           if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
1144             path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
1145             if (!path)
1146               continue;
1147           } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
1148             address = (char *)xmlGetProp(child, (const xmlChar *)"address");
1149             if (!address)
1150               continue;
1151           }
1152         }
1153
1154         if (il_interface_devices &&
1155             (ignore_device_match(il_interface_devices, name, path) != 0 ||
1156              ignore_device_match(il_interface_devices, name, address) != 0))
1157           goto cont3;
1158
1159         add_interface_device(state, dom, path, address, j + 1);
1160       cont3:
1161         if (path)
1162           xmlFree(path);
1163         if (address)
1164           xmlFree(address);
1165       }
1166
1167     cont:
1168       if (xpath_obj)
1169         xmlXPathFreeObject(xpath_obj);
1170       if (xpath_ctx)
1171         xmlXPathFreeContext(xpath_ctx);
1172       if (xml_doc)
1173         xmlFreeDoc(xml_doc);
1174       sfree(xml);
1175     }
1176
1177     sfree(domids);
1178   }
1179
1180   DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
1181                     " domains=%i block_devices=%i iface_devices=%i",
1182         inst->tag, state->nr_domains, state->nr_block_devices,
1183         state->nr_interface_devices);
1184
1185   return 0;
1186 }
1187
1188 static void free_domains(struct lv_read_state *state) {
1189   if (state->domains) {
1190     for (int i = 0; i < state->nr_domains; ++i)
1191       virDomainFree(state->domains[i]);
1192     sfree(state->domains);
1193   }
1194   state->domains = NULL;
1195   state->nr_domains = 0;
1196 }
1197
1198 static int add_domain(struct lv_read_state *state, virDomainPtr dom) {
1199   virDomainPtr *new_ptr;
1200   int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
1201
1202   if (state->domains)
1203     new_ptr = realloc(state->domains, new_size);
1204   else
1205     new_ptr = malloc(new_size);
1206
1207   if (new_ptr == NULL)
1208     return -1;
1209
1210   state->domains = new_ptr;
1211   state->domains[state->nr_domains] = dom;
1212   return state->nr_domains++;
1213 }
1214
1215 static void free_block_devices(struct lv_read_state *state) {
1216   if (state->block_devices) {
1217     for (int i = 0; i < state->nr_block_devices; ++i)
1218       sfree(state->block_devices[i].path);
1219     sfree(state->block_devices);
1220   }
1221   state->block_devices = NULL;
1222   state->nr_block_devices = 0;
1223 }
1224
1225 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
1226                             const char *path) {
1227   struct block_device *new_ptr;
1228   int new_size =
1229       sizeof(state->block_devices[0]) * (state->nr_block_devices + 1);
1230   char *path_copy;
1231
1232   path_copy = strdup(path);
1233   if (!path_copy)
1234     return -1;
1235
1236   if (state->block_devices)
1237     new_ptr = realloc(state->block_devices, new_size);
1238   else
1239     new_ptr = malloc(new_size);
1240
1241   if (new_ptr == NULL) {
1242     sfree(path_copy);
1243     return -1;
1244   }
1245   state->block_devices = new_ptr;
1246   state->block_devices[state->nr_block_devices].dom = dom;
1247   state->block_devices[state->nr_block_devices].path = path_copy;
1248   return state->nr_block_devices++;
1249 }
1250
1251 static void free_interface_devices(struct lv_read_state *state) {
1252   if (state->interface_devices) {
1253     for (int i = 0; i < state->nr_interface_devices; ++i) {
1254       sfree(state->interface_devices[i].path);
1255       sfree(state->interface_devices[i].address);
1256       sfree(state->interface_devices[i].number);
1257     }
1258     sfree(state->interface_devices);
1259   }
1260   state->interface_devices = NULL;
1261   state->nr_interface_devices = 0;
1262 }
1263
1264 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
1265                                 const char *path, const char *address,
1266                                 unsigned int number) {
1267   struct interface_device *new_ptr;
1268   int new_size =
1269       sizeof(state->interface_devices[0]) * (state->nr_interface_devices + 1);
1270   char *path_copy, *address_copy, number_string[15];
1271
1272   if ((path == NULL) || (address == NULL))
1273     return EINVAL;
1274
1275   path_copy = strdup(path);
1276   if (!path_copy)
1277     return -1;
1278
1279   address_copy = strdup(address);
1280   if (!address_copy) {
1281     sfree(path_copy);
1282     return -1;
1283   }
1284
1285   snprintf(number_string, sizeof(number_string), "interface-%u", number);
1286
1287   if (state->interface_devices)
1288     new_ptr = realloc(state->interface_devices, new_size);
1289   else
1290     new_ptr = malloc(new_size);
1291
1292   if (new_ptr == NULL) {
1293     sfree(path_copy);
1294     sfree(address_copy);
1295     return -1;
1296   }
1297   state->interface_devices = new_ptr;
1298   state->interface_devices[state->nr_interface_devices].dom = dom;
1299   state->interface_devices[state->nr_interface_devices].path = path_copy;
1300   state->interface_devices[state->nr_interface_devices].address = address_copy;
1301   state->interface_devices[state->nr_interface_devices].number =
1302       strdup(number_string);
1303   return state->nr_interface_devices++;
1304 }
1305
1306 static int ignore_device_match(ignorelist_t *il, const char *domname,
1307                                const char *devpath) {
1308   char *name;
1309   int n, r;
1310
1311   if ((domname == NULL) || (devpath == NULL))
1312     return 0;
1313
1314   n = sizeof(char) * (strlen(domname) + strlen(devpath) + 2);
1315   name = malloc(n);
1316   if (name == NULL) {
1317     ERROR(PLUGIN_NAME " plugin: malloc failed.");
1318     return 0;
1319   }
1320   ssnprintf(name, n, "%s:%s", domname, devpath);
1321   r = ignorelist_match(il, name);
1322   sfree(name);
1323   return r;
1324 }
1325
1326 static int lv_shutdown(void) {
1327   for (int i = 0; i < nr_instances; ++i) {
1328     lv_fini_instance(i);
1329   }
1330
1331   lv_disconnect();
1332
1333   ignorelist_free(il_domains);
1334   il_domains = NULL;
1335   ignorelist_free(il_block_devices);
1336   il_block_devices = NULL;
1337   ignorelist_free(il_interface_devices);
1338   il_interface_devices = NULL;
1339
1340   return 0;
1341 }
1342
1343 void module_register(void) {
1344   plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
1345   plugin_register_init(PLUGIN_NAME, lv_init);
1346   plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
1347 }