virt plugin: Remove optional virDomainGetCPUStats() from main flow
[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  *   Przemyslaw Szczerbik <przemyslawx.szczerbik@intel.com>
21  **/
22
23 #include "collectd.h"
24
25 #include "common.h"
26 #include "plugin.h"
27 #include "utils_complain.h"
28 #include "utils_ignorelist.h"
29
30 #include <libgen.h> /* for basename(3) */
31 #include <libvirt/libvirt.h>
32 #include <libvirt/virterror.h>
33 #include <libxml/parser.h>
34 #include <libxml/tree.h>
35 #include <libxml/xpath.h>
36 #include <libxml/xpathInternals.h>
37
38 /* Plugin name */
39 #define PLUGIN_NAME "virt"
40
41 #ifdef LIBVIR_CHECK_VERSION
42
43 #if LIBVIR_CHECK_VERSION(0, 9, 2)
44 #define HAVE_DOM_REASON 1
45 #endif
46
47 #if LIBVIR_CHECK_VERSION(0, 9, 5)
48 #define HAVE_BLOCK_STATS_FLAGS 1
49 #define HAVE_DOM_REASON_PAUSED_SHUTTING_DOWN 1
50 #endif
51
52 #if LIBVIR_CHECK_VERSION(0, 9, 10)
53 #define HAVE_DISK_ERR 1
54 #endif
55
56 #if LIBVIR_CHECK_VERSION(0, 9, 11)
57 #define HAVE_CPU_STATS 1
58 #define HAVE_DOM_STATE_PMSUSPENDED 1
59 #define HAVE_DOM_REASON_RUNNING_WAKEUP 1
60 #endif
61
62 #if LIBVIR_CHECK_VERSION(1, 0, 1)
63 #define HAVE_DOM_REASON_PAUSED_SNAPSHOT 1
64 #endif
65
66 #if LIBVIR_CHECK_VERSION(1, 1, 1)
67 #define HAVE_DOM_REASON_PAUSED_CRASHED 1
68 #endif
69
70 #if LIBVIR_CHECK_VERSION(1, 2, 9)
71 #define HAVE_JOB_STATS 1
72 #endif
73
74 #if LIBVIR_CHECK_VERSION(1, 2, 10)
75 #define HAVE_DOM_REASON_CRASHED 1
76 #endif
77
78 #if LIBVIR_CHECK_VERSION(1, 2, 11)
79 #define HAVE_FS_INFO 1
80 #endif
81
82 #if LIBVIR_CHECK_VERSION(1, 2, 15)
83 #define HAVE_DOM_REASON_PAUSED_STARTING_UP 1
84 #endif
85
86 #if LIBVIR_CHECK_VERSION(1, 3, 3)
87 #define HAVE_PERF_STATS 1
88 #define HAVE_DOM_REASON_POSTCOPY 1
89 #endif
90
91 #endif /* LIBVIR_CHECK_VERSION */
92
93 static const char *config_keys[] = {"Connection",
94
95                                     "RefreshInterval",
96
97                                     "Domain",
98                                     "BlockDevice",
99                                     "BlockDeviceFormat",
100                                     "BlockDeviceFormatBasename",
101                                     "InterfaceDevice",
102                                     "IgnoreSelected",
103
104                                     "HostnameFormat",
105                                     "InterfaceFormat",
106
107                                     "PluginInstanceFormat",
108
109                                     "Instances",
110                                     "ExtraStats",
111                                     NULL};
112
113 const char *domain_states[] = {
114         [VIR_DOMAIN_NOSTATE] = "no state",
115         [VIR_DOMAIN_RUNNING] = "the domain is running",
116         [VIR_DOMAIN_BLOCKED] = "the domain is blocked on resource",
117         [VIR_DOMAIN_PAUSED] = "the domain is paused by user",
118         [VIR_DOMAIN_SHUTDOWN] = "the domain is being shut down",
119         [VIR_DOMAIN_SHUTOFF] = "the domain is shut off",
120         [VIR_DOMAIN_CRASHED] = "the domain is crashed",
121 #ifdef HAVE_DOM_STATE_PMSUSPENDED
122         [VIR_DOMAIN_PMSUSPENDED] =
123             "the domain is suspended by guest power management",
124 #endif
125 };
126
127 #ifdef HAVE_DOM_REASON
128 #define DOMAIN_STATE_REASON_MAX_SIZE 20
129 const char *domain_reasons[][DOMAIN_STATE_REASON_MAX_SIZE] = {
130         [VIR_DOMAIN_NOSTATE][VIR_DOMAIN_NOSTATE_UNKNOWN] =
131             "the reason is unknown",
132
133         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_UNKNOWN] =
134             "the reason is unknown",
135         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_BOOTED] =
136             "normal startup from boot",
137         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_MIGRATED] =
138             "migrated from another host",
139         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_RESTORED] =
140             "restored from a state file",
141         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_FROM_SNAPSHOT] =
142             "restored from snapshot",
143         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_UNPAUSED] =
144             "returned from paused state",
145         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_MIGRATION_CANCELED] =
146             "returned from migration",
147         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_SAVE_CANCELED] =
148             "returned from failed save process",
149 #ifdef HAVE_DOM_REASON_RUNNING_WAKEUP
150         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_WAKEUP] =
151             "returned from pmsuspended due to wakeup event",
152 #endif
153 #ifdef HAVE_DOM_REASON_CRASHED
154         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_CRASHED] =
155             "resumed from crashed",
156 #endif
157 #ifdef HAVE_DOM_REASON_POSTCOPY
158         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_POSTCOPY] =
159             "running in post-copy migration mode",
160 #endif
161
162         [VIR_DOMAIN_BLOCKED][VIR_DOMAIN_BLOCKED_UNKNOWN] =
163             "the reason is unknown",
164
165         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_UNKNOWN] =
166             "the reason is unknown",
167         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_USER] = "paused on user request",
168         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_MIGRATION] =
169             "paused for offline migration",
170         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_SAVE] = "paused for save",
171         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_DUMP] =
172             "paused for offline core dump",
173         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_IOERROR] =
174             "paused due to a disk I/O error",
175         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_WATCHDOG] =
176             "paused due to a watchdog event",
177         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_FROM_SNAPSHOT] =
178             "paused after restoring from snapshot",
179 #ifdef HAVE_DOM_REASON_PAUSED_SHUTTING_DOWN
180         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_SHUTTING_DOWN] =
181             "paused during shutdown process",
182 #endif
183 #ifdef HAVE_DOM_REASON_PAUSED_SNAPSHOT
184         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_SNAPSHOT] =
185             "paused while creating a snapshot",
186 #endif
187 #ifdef HAVE_DOM_REASON_PAUSED_CRASHED
188         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_CRASHED] =
189             "paused due to a guest crash",
190 #endif
191 #ifdef HAVE_DOM_REASON_PAUSED_STARTING_UP
192         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_STARTING_UP] =
193             "the domain is being started",
194 #endif
195 #ifdef HAVE_DOM_REASON_POSTCOPY
196         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_POSTCOPY] =
197             "paused for post-copy migration",
198         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_POSTCOPY_FAILED] =
199             "paused after failed post-copy",
200 #endif
201
202         [VIR_DOMAIN_SHUTDOWN][VIR_DOMAIN_SHUTDOWN_UNKNOWN] =
203             "the reason is unknown",
204         [VIR_DOMAIN_SHUTDOWN][VIR_DOMAIN_SHUTDOWN_USER] =
205             "shutting down on user request",
206
207         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_UNKNOWN] =
208             "the reason is unknown",
209         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_SHUTDOWN] = "normal shutdown",
210         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_DESTROYED] = "forced poweroff",
211         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_CRASHED] = "domain crashed",
212         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_MIGRATED] =
213             "migrated to another host",
214         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_SAVED] = "saved to a file",
215         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_FAILED] =
216             "domain failed to start",
217         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT] =
218             "restored from a snapshot which was taken while domain was shutoff",
219
220         [VIR_DOMAIN_CRASHED][VIR_DOMAIN_CRASHED_UNKNOWN] =
221             "the reason is unknown",
222 #ifdef VIR_DOMAIN_CRASHED_PANICKED
223         [VIR_DOMAIN_CRASHED][VIR_DOMAIN_CRASHED_PANICKED] = "domain panicked",
224 #endif
225
226 #ifdef HAVE_DOM_STATE_PMSUSPENDED
227         [VIR_DOMAIN_PMSUSPENDED][VIR_DOMAIN_PMSUSPENDED_UNKNOWN] =
228             "the reason is unknown",
229 #endif
230 };
231 #endif /* HAVE_DOM_REASON */
232
233 #define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
234 #define NANOSEC_IN_SEC 1e9
235
236 #define GET_STATS(_f, _name, ...)                                              \
237   do {                                                                         \
238     status = _f(__VA_ARGS__);                                                  \
239     if (status != 0)                                                           \
240       ERROR(PLUGIN_NAME ": Failed to get " _name);                             \
241   } while (0)
242
243 static void submit_derive2(const char *type, derive_t v0, derive_t v1,
244                            virDomainPtr dom, const char *devname);
245
246 /* Connection. */
247 static virConnectPtr conn = 0;
248 static char *conn_string = NULL;
249 static c_complain_t conn_complain = C_COMPLAIN_INIT_STATIC;
250
251 /* Node information required for %CPU */
252 static virNodeInfo nodeinfo;
253
254 /* Seconds between list refreshes, 0 disables completely. */
255 static int interval = 60;
256
257 /* List of domains, if specified. */
258 static ignorelist_t *il_domains = NULL;
259 /* List of block devices, if specified. */
260 static ignorelist_t *il_block_devices = NULL;
261 /* List of network interface devices, if specified. */
262 static ignorelist_t *il_interface_devices = NULL;
263
264 static int ignore_device_match(ignorelist_t *, const char *domname,
265                                const char *devpath);
266
267 /* Actual list of block devices found on last refresh. */
268 struct block_device {
269   virDomainPtr dom; /* domain */
270   char *path;       /* name of block device */
271 };
272
273 /* Actual list of network interfaces found on last refresh. */
274 struct interface_device {
275   virDomainPtr dom; /* domain */
276   char *path;       /* name of interface device */
277   char *address;    /* mac address of interface device */
278   char *number;     /* interface device number */
279 };
280
281 typedef struct domain_s {
282   virDomainPtr ptr;
283   virDomainInfo info;
284 } domain_t;
285
286 struct lv_read_state {
287   /* Actual list of domains found on last refresh. */
288   domain_t *domains;
289   int nr_domains;
290
291   struct block_device *block_devices;
292   int nr_block_devices;
293
294   struct interface_device *interface_devices;
295   int nr_interface_devices;
296 };
297
298 static void free_domains(struct lv_read_state *state);
299 static int add_domain(struct lv_read_state *state, virDomainPtr dom);
300
301 static void free_block_devices(struct lv_read_state *state);
302 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
303                             const char *path);
304
305 static void free_interface_devices(struct lv_read_state *state);
306 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
307                                 const char *path, const char *address,
308                                 unsigned int number);
309
310 #define METADATA_VM_PARTITION_URI "http://ovirt.org/ovirtmap/tag/1.0"
311 #define METADATA_VM_PARTITION_ELEMENT "tag"
312 #define METADATA_VM_PARTITION_PREFIX "ovirtmap"
313
314 #define BUFFER_MAX_LEN 256
315 #define PARTITION_TAG_MAX_LEN 32
316
317 struct lv_read_instance {
318   struct lv_read_state read_state;
319   char tag[PARTITION_TAG_MAX_LEN];
320   size_t id;
321 };
322
323 struct lv_user_data {
324   struct lv_read_instance inst;
325   user_data_t ud;
326 };
327
328 #define NR_INSTANCES_DEFAULT 1
329 #define NR_INSTANCES_MAX 128
330 static int nr_instances = NR_INSTANCES_DEFAULT;
331 static struct lv_user_data lv_read_user_data[NR_INSTANCES_MAX];
332
333 /* HostnameFormat. */
334 #define HF_MAX_FIELDS 3
335
336 enum hf_field { hf_none = 0, hf_hostname, hf_name, hf_uuid };
337
338 static enum hf_field hostname_format[HF_MAX_FIELDS] = {hf_name};
339
340 /* PluginInstanceFormat */
341 #define PLGINST_MAX_FIELDS 2
342
343 enum plginst_field { plginst_none = 0, plginst_name, plginst_uuid };
344
345 static enum plginst_field plugin_instance_format[PLGINST_MAX_FIELDS] = {
346     plginst_none};
347
348 /* BlockDeviceFormat */
349 enum bd_field { target, source };
350
351 /* InterfaceFormat. */
352 enum if_field { if_address, if_name, if_number };
353
354 /* ExtraStats */
355 #define EX_STATS_MAX_FIELDS 15
356 enum ex_stats {
357   ex_stats_none = 0,
358   ex_stats_disk = 1 << 0,
359   ex_stats_pcpu = 1 << 1,
360   ex_stats_cpu_util = 1 << 2,
361   ex_stats_domain_state = 1 << 3,
362 #ifdef HAVE_PERF_STATS
363   ex_stats_perf = 1 << 4,
364 #endif
365   ex_stats_vcpupin = 1 << 5,
366 #ifdef HAVE_DISK_ERR
367   ex_stats_disk_err = 1 << 6,
368 #endif
369 #ifdef HAVE_FS_INFO
370   ex_stats_fs_info = 1 << 7,
371 #endif
372 #ifdef HAVE_JOB_STATS
373   ex_stats_job_stats_completed = 1 << 8,
374   ex_stats_job_stats_background = 1 << 9,
375 #endif
376 };
377
378 static unsigned int extra_stats = ex_stats_none;
379
380 struct ex_stats_item {
381   const char *name;
382   enum ex_stats flag;
383 };
384 static const struct ex_stats_item ex_stats_table[] = {
385     {"disk", ex_stats_disk},
386     {"pcpu", ex_stats_pcpu},
387     {"cpu_util", ex_stats_cpu_util},
388     {"domain_state", ex_stats_domain_state},
389 #ifdef HAVE_PERF_STATS
390     {"perf", ex_stats_perf},
391 #endif
392     {"vcpupin", ex_stats_vcpupin},
393 #ifdef HAVE_DISK_ERR
394     {"disk_err", ex_stats_disk_err},
395 #endif
396 #ifdef HAVE_FS_INFO
397     {"fs_info", ex_stats_fs_info},
398 #endif
399 #ifdef HAVE_JOB_STATS
400     {"job_stats_completed", ex_stats_job_stats_completed},
401     {"job_stats_background", ex_stats_job_stats_background},
402 #endif
403     {NULL, ex_stats_none},
404 };
405
406 /* BlockDeviceFormatBasename */
407 _Bool blockdevice_format_basename = 0;
408 static enum bd_field blockdevice_format = target;
409 static enum if_field interface_format = if_name;
410
411 /* Time that we last refreshed. */
412 static time_t last_refresh = (time_t)0;
413
414 static int refresh_lists(struct lv_read_instance *inst);
415
416 struct lv_block_info {
417   virDomainBlockStatsStruct bi;
418
419   long long rd_total_times;
420   long long wr_total_times;
421
422   long long fl_req;
423   long long fl_total_times;
424 };
425
426 static void init_block_info(struct lv_block_info *binfo) {
427   if (binfo == NULL)
428     return;
429
430   binfo->bi.rd_req = -1;
431   binfo->bi.wr_req = -1;
432   binfo->bi.rd_bytes = -1;
433   binfo->bi.wr_bytes = -1;
434
435   binfo->rd_total_times = -1;
436   binfo->wr_total_times = -1;
437   binfo->fl_req = -1;
438   binfo->fl_total_times = -1;
439 }
440
441 #ifdef HAVE_BLOCK_STATS_FLAGS
442
443 #define GET_BLOCK_INFO_VALUE(NAME, FIELD)                                      \
444   if (!strcmp(param[i].field, NAME)) {                                         \
445     binfo->FIELD = param[i].value.l;                                           \
446     continue;                                                                  \
447   }
448
449 static int get_block_info(struct lv_block_info *binfo,
450                           virTypedParameterPtr param, int nparams) {
451   if (binfo == NULL || param == NULL)
452     return -1;
453
454   for (int i = 0; i < nparams; ++i) {
455     /* ignore type. Everything must be LLONG anyway. */
456     GET_BLOCK_INFO_VALUE("rd_operations", bi.rd_req);
457     GET_BLOCK_INFO_VALUE("wr_operations", bi.wr_req);
458     GET_BLOCK_INFO_VALUE("rd_bytes", bi.rd_bytes);
459     GET_BLOCK_INFO_VALUE("wr_bytes", bi.wr_bytes);
460     GET_BLOCK_INFO_VALUE("rd_total_times", rd_total_times);
461     GET_BLOCK_INFO_VALUE("wr_total_times", wr_total_times);
462     GET_BLOCK_INFO_VALUE("flush_operations", fl_req);
463     GET_BLOCK_INFO_VALUE("flush_total_times", fl_total_times);
464   }
465
466   return 0;
467 }
468
469 #undef GET_BLOCK_INFO_VALUE
470
471 #endif /* HAVE_BLOCK_STATS_FLAGS */
472
473 /* ERROR(...) macro for virterrors. */
474 #define VIRT_ERROR(conn, s)                                                    \
475   do {                                                                         \
476     virErrorPtr err;                                                           \
477     err = (conn) ? virConnGetLastError((conn)) : virGetLastError();            \
478     if (err)                                                                   \
479       ERROR(PLUGIN_NAME " plugin: %s failed: %s", (s), err->message);          \
480   } while (0)
481
482 #ifdef HAVE_CPU_STATS
483 static int get_pcpu_stats(virDomainPtr dom) {
484   int nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0);
485   if (nparams < 0) {
486     VIRT_ERROR(conn, "getting the CPU params count");
487     return -1;
488   }
489
490   virTypedParameterPtr param = calloc(nparams, sizeof(virTypedParameter));
491   if (param == NULL) {
492     ERROR(PLUGIN_NAME " plugin: alloc(%i) for cpu parameters failed.", nparams);
493     return -1;
494   }
495
496   int ret = virDomainGetCPUStats(dom, param, nparams, -1, 1, 0); // total stats.
497   if (ret < 0) {
498     virTypedParamsClear(param, nparams);
499     sfree(param);
500     VIRT_ERROR(conn, "getting the CPU params values");
501     return -1;
502   }
503
504   unsigned long long total_user_cpu_time = 0;
505   unsigned long long total_syst_cpu_time = 0;
506
507   for (int i = 0; i < nparams; ++i) {
508     if (!strcmp(param[i].field, "user_time"))
509       total_user_cpu_time = param[i].value.ul;
510     else if (!strcmp(param[i].field, "system_time"))
511       total_syst_cpu_time = param[i].value.ul;
512   }
513
514   submit_derive2("ps_cputime", total_user_cpu_time, total_syst_cpu_time, dom,
515                  NULL);
516
517   virTypedParamsClear(param, nparams);
518   sfree(param);
519
520   return 0;
521 }
522 #endif /* HAVE_CPU_STATS */
523
524 static void init_value_list(value_list_t *vl, virDomainPtr dom) {
525   int n;
526   const char *name;
527   char uuid[VIR_UUID_STRING_BUFLEN];
528
529   sstrncpy(vl->plugin, PLUGIN_NAME, sizeof(vl->plugin));
530
531   vl->host[0] = '\0';
532
533   /* Construct the hostname field according to HostnameFormat. */
534   for (int i = 0; i < HF_MAX_FIELDS; ++i) {
535     if (hostname_format[i] == hf_none)
536       continue;
537
538     n = DATA_MAX_NAME_LEN - strlen(vl->host) - 2;
539
540     if (i > 0 && n >= 1) {
541       strncat(vl->host, ":", 1);
542       n--;
543     }
544
545     switch (hostname_format[i]) {
546     case hf_none:
547       break;
548     case hf_hostname:
549       strncat(vl->host, hostname_g, n);
550       break;
551     case hf_name:
552       name = virDomainGetName(dom);
553       if (name)
554         strncat(vl->host, name, n);
555       break;
556     case hf_uuid:
557       if (virDomainGetUUIDString(dom, uuid) == 0)
558         strncat(vl->host, uuid, n);
559       break;
560     }
561   }
562
563   vl->host[sizeof(vl->host) - 1] = '\0';
564
565   /* Construct the plugin instance field according to PluginInstanceFormat. */
566   for (int i = 0; i < PLGINST_MAX_FIELDS; ++i) {
567     if (plugin_instance_format[i] == plginst_none)
568       continue;
569
570     n = sizeof(vl->plugin_instance) - strlen(vl->plugin_instance) - 2;
571
572     if (i > 0 && n >= 1) {
573       strncat(vl->plugin_instance, ":", 1);
574       n--;
575     }
576
577     switch (plugin_instance_format[i]) {
578     case plginst_none:
579       break;
580     case plginst_name:
581       name = virDomainGetName(dom);
582       if (name)
583         strncat(vl->plugin_instance, name, n);
584       break;
585     case plginst_uuid:
586       if (virDomainGetUUIDString(dom, uuid) == 0)
587         strncat(vl->plugin_instance, uuid, n);
588       break;
589     }
590   }
591
592   vl->plugin_instance[sizeof(vl->plugin_instance) - 1] = '\0';
593
594 } /* void init_value_list */
595
596 static int init_notif(notification_t *notif, const virDomainPtr domain,
597                       int severity, const char *msg, const char *type,
598                       const char *type_instance) {
599   value_list_t vl = VALUE_LIST_INIT;
600
601   if (!notif) {
602     ERROR(PLUGIN_NAME ": init_notif: NULL pointer");
603     return -1;
604   }
605
606   init_value_list(&vl, domain);
607   notification_init(notif, severity, msg, vl.host, vl.plugin,
608                     vl.plugin_instance, type, type_instance);
609   notif->time = cdtime();
610   return 0;
611 }
612
613 static void submit_notif(const virDomainPtr domain, int severity,
614                          const char *msg, const char *type,
615                          const char *type_instance) {
616   notification_t notif;
617
618   init_notif(&notif, domain, severity, msg, type, type_instance);
619   plugin_dispatch_notification(&notif);
620   if (notif.meta)
621     plugin_notification_meta_free(notif.meta);
622 }
623
624 static void submit(virDomainPtr dom, char const *type,
625                    char const *type_instance, value_t *values,
626                    size_t values_len) {
627   value_list_t vl = VALUE_LIST_INIT;
628   init_value_list(&vl, dom);
629
630   vl.values = values;
631   vl.values_len = values_len;
632
633   sstrncpy(vl.type, type, sizeof(vl.type));
634   if (type_instance != NULL)
635     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
636
637   plugin_dispatch_values(&vl);
638 }
639
640 static void memory_submit(virDomainPtr dom, gauge_t value) {
641   submit(dom, "memory", "total", &(value_t){.gauge = value}, 1);
642 }
643
644 static void memory_stats_submit(gauge_t value, virDomainPtr dom,
645                                 int tag_index) {
646   static const char *tags[] = {"swap_in",        "swap_out", "major_fault",
647                                "minor_fault",    "unused",   "available",
648                                "actual_balloon", "rss",      "usable",
649                                "last_update"};
650
651   if ((tag_index < 0) || (tag_index >= (int)STATIC_ARRAY_SIZE(tags))) {
652     ERROR("virt plugin: Array index out of bounds: tag_index = %d", tag_index);
653     return;
654   }
655
656   submit(dom, "memory", tags[tag_index], &(value_t){.gauge = value}, 1);
657 }
658
659 static void submit_derive2(const char *type, derive_t v0, derive_t v1,
660                            virDomainPtr dom, const char *devname) {
661   value_t values[] = {
662       {.derive = v0}, {.derive = v1},
663   };
664
665   submit(dom, type, devname, values, STATIC_ARRAY_SIZE(values));
666 } /* void submit_derive2 */
667
668 static double cpu_ns_to_percent(unsigned int node_cpus,
669                                 unsigned long long cpu_time_old,
670                                 unsigned long long cpu_time_new) {
671   double percent = 0.0;
672   unsigned long long cpu_time_diff = 0;
673   double time_diff_sec = CDTIME_T_TO_DOUBLE(plugin_get_interval());
674
675   if (node_cpus != 0 && time_diff_sec != 0 && cpu_time_old != 0) {
676     cpu_time_diff = cpu_time_new - cpu_time_old;
677     percent = ((double)(100 * cpu_time_diff)) /
678               (time_diff_sec * node_cpus * NANOSEC_IN_SEC);
679   }
680
681   DEBUG(PLUGIN_NAME ": node_cpus=%u cpu_time_old=%llu cpu_time_new=%llu"
682                     "cpu_time_diff=%llu time_diff_sec=%f percent=%f",
683         node_cpus, cpu_time_old, cpu_time_new, cpu_time_diff, time_diff_sec,
684         percent);
685
686   return percent;
687 }
688
689 static void cpu_submit(const domain_t *dom, unsigned long long cpuTime_new) {
690
691   if (!dom)
692     return;
693
694   if (extra_stats & ex_stats_cpu_util) {
695     /* Computing %CPU requires 2 samples of cpuTime */
696     if (dom->info.cpuTime != 0 && cpuTime_new != 0) {
697
698       submit(dom->ptr, "percent", "virt_cpu_total",
699              &(value_t){.gauge = cpu_ns_to_percent(
700                             nodeinfo.cpus, dom->info.cpuTime, cpuTime_new)},
701              1);
702     }
703   }
704
705   submit(dom->ptr, "virt_cpu_total", NULL, &(value_t){.derive = cpuTime_new},
706          1);
707 }
708
709 static void vcpu_submit(derive_t value, virDomainPtr dom, int vcpu_nr,
710                         const char *type) {
711   char type_instance[DATA_MAX_NAME_LEN];
712
713   snprintf(type_instance, sizeof(type_instance), "%d", vcpu_nr);
714   submit(dom, type, type_instance, &(value_t){.derive = value}, 1);
715 }
716
717 static void disk_submit(struct lv_block_info *binfo, virDomainPtr dom,
718                         const char *dev) {
719   char *dev_copy = strdup(dev);
720   const char *type_instance = dev_copy;
721
722   if (!dev_copy)
723     return;
724
725   if (blockdevice_format_basename && blockdevice_format == source)
726     type_instance = basename(dev_copy);
727
728   if (!type_instance) {
729     sfree(dev_copy);
730     return;
731   }
732
733   char flush_type_instance[DATA_MAX_NAME_LEN];
734   snprintf(flush_type_instance, sizeof(flush_type_instance), "flush-%s",
735            type_instance);
736
737   if ((binfo->bi.rd_req != -1) && (binfo->bi.wr_req != -1))
738     submit_derive2("disk_ops", (derive_t)binfo->bi.rd_req,
739                    (derive_t)binfo->bi.wr_req, dom, type_instance);
740
741   if ((binfo->bi.rd_bytes != -1) && (binfo->bi.wr_bytes != -1))
742     submit_derive2("disk_octets", (derive_t)binfo->bi.rd_bytes,
743                    (derive_t)binfo->bi.wr_bytes, dom, type_instance);
744
745   if (extra_stats & ex_stats_disk) {
746     if ((binfo->rd_total_times != -1) && (binfo->wr_total_times != -1))
747       submit_derive2("disk_time", (derive_t)binfo->rd_total_times,
748                      (derive_t)binfo->wr_total_times, dom, type_instance);
749
750     if (binfo->fl_req != -1)
751       submit(dom, "total_requests", flush_type_instance,
752              &(value_t){.derive = (derive_t)binfo->fl_req}, 1);
753     if (binfo->fl_total_times != -1) {
754       derive_t value = binfo->fl_total_times / 1000; // ns -> ms
755       submit(dom, "total_time_in_ms", flush_type_instance,
756              &(value_t){.derive = value}, 1);
757     }
758   }
759
760   sfree(dev_copy);
761 }
762
763 static unsigned int parse_ex_stats_flags(char **exstats, int numexstats) {
764   unsigned int ex_stats_flags = ex_stats_none;
765   for (int i = 0; i < numexstats; i++) {
766     for (int j = 0; ex_stats_table[j].name != NULL; j++) {
767       if (strcasecmp(exstats[i], ex_stats_table[j].name) == 0) {
768         DEBUG(PLUGIN_NAME " plugin: enabling extra stats for '%s'",
769               ex_stats_table[j].name);
770         ex_stats_flags |= ex_stats_table[j].flag;
771         break;
772       }
773
774       if (ex_stats_table[j + 1].name == NULL) {
775         ERROR(PLUGIN_NAME ": Unmatched ExtraStats option: %s", exstats[i]);
776       }
777     }
778   }
779   return ex_stats_flags;
780 }
781
782 static void domain_state_submit(virDomainPtr dom, int state, int reason) {
783
784   if ((state < 0) || (state >= STATIC_ARRAY_SIZE(domain_states))) {
785     ERROR(PLUGIN_NAME ": Array index out of bounds: state=%d", state);
786     return;
787   }
788
789   char msg[DATA_MAX_NAME_LEN];
790   const char *state_str = domain_states[state];
791 #ifdef HAVE_DOM_REASON
792   if ((reason < 0) || (reason >= STATIC_ARRAY_SIZE(domain_reasons[0]))) {
793     ERROR(PLUGIN_NAME ": Array index out of bounds: reason=%d", reason);
794     return;
795   }
796
797   const char *reason_str = domain_reasons[state][reason];
798   /* Array size for domain reasons is fixed, but different domain states can
799    * have different number of reasons. We need to check if reason was
800    * successfully parsed */
801   if (!reason_str) {
802     ERROR(PLUGIN_NAME ": Invalid reason (%d) for domain state: %s", reason,
803           state_str);
804     return;
805   }
806 #else
807   const char *reason_str = "N/A";
808 #endif
809
810   snprintf(msg, sizeof(msg), "Domain state: %s. Reason: %s", state_str,
811            reason_str);
812
813   int severity;
814   switch (state) {
815   case VIR_DOMAIN_NOSTATE:
816   case VIR_DOMAIN_RUNNING:
817   case VIR_DOMAIN_SHUTDOWN:
818   case VIR_DOMAIN_SHUTOFF:
819     severity = NOTIF_OKAY;
820     break;
821   case VIR_DOMAIN_BLOCKED:
822   case VIR_DOMAIN_PAUSED:
823 #ifdef DOM_STATE_PMSUSPENDED
824   case VIR_DOMAIN_PMSUSPENDED:
825 #endif
826     severity = NOTIF_WARNING;
827     break;
828   case VIR_DOMAIN_CRASHED:
829     severity = NOTIF_FAILURE;
830     break;
831   default:
832     ERROR(PLUGIN_NAME ": Unrecognized domain state (%d)", state);
833     return;
834   }
835   submit_notif(dom, severity, msg, "domain_state", NULL);
836 }
837
838 static int lv_config(const char *key, const char *value) {
839   if (virInitialize() != 0)
840     return 1;
841
842   if (il_domains == NULL)
843     il_domains = ignorelist_create(1);
844   if (il_block_devices == NULL)
845     il_block_devices = ignorelist_create(1);
846   if (il_interface_devices == NULL)
847     il_interface_devices = ignorelist_create(1);
848
849   if (strcasecmp(key, "Connection") == 0) {
850     char *tmp = strdup(value);
851     if (tmp == NULL) {
852       ERROR(PLUGIN_NAME " plugin: Connection strdup failed.");
853       return 1;
854     }
855     sfree(conn_string);
856     conn_string = tmp;
857     return 0;
858   }
859
860   if (strcasecmp(key, "RefreshInterval") == 0) {
861     char *eptr = NULL;
862     interval = strtol(value, &eptr, 10);
863     if (eptr == NULL || *eptr != '\0')
864       return 1;
865     return 0;
866   }
867
868   if (strcasecmp(key, "Domain") == 0) {
869     if (ignorelist_add(il_domains, value))
870       return 1;
871     return 0;
872   }
873   if (strcasecmp(key, "BlockDevice") == 0) {
874     if (ignorelist_add(il_block_devices, value))
875       return 1;
876     return 0;
877   }
878
879   if (strcasecmp(key, "BlockDeviceFormat") == 0) {
880     if (strcasecmp(value, "target") == 0)
881       blockdevice_format = target;
882     else if (strcasecmp(value, "source") == 0)
883       blockdevice_format = source;
884     else {
885       ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s", value);
886       return -1;
887     }
888     return 0;
889   }
890   if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
891     blockdevice_format_basename = IS_TRUE(value);
892     return 0;
893   }
894   if (strcasecmp(key, "InterfaceDevice") == 0) {
895     if (ignorelist_add(il_interface_devices, value))
896       return 1;
897     return 0;
898   }
899
900   if (strcasecmp(key, "IgnoreSelected") == 0) {
901     if (IS_TRUE(value)) {
902       ignorelist_set_invert(il_domains, 0);
903       ignorelist_set_invert(il_block_devices, 0);
904       ignorelist_set_invert(il_interface_devices, 0);
905     } else {
906       ignorelist_set_invert(il_domains, 1);
907       ignorelist_set_invert(il_block_devices, 1);
908       ignorelist_set_invert(il_interface_devices, 1);
909     }
910     return 0;
911   }
912
913   if (strcasecmp(key, "HostnameFormat") == 0) {
914     char *value_copy;
915     char *fields[HF_MAX_FIELDS];
916     int n;
917
918     value_copy = strdup(value);
919     if (value_copy == NULL) {
920       ERROR(PLUGIN_NAME " plugin: strdup failed.");
921       return -1;
922     }
923
924     n = strsplit(value_copy, fields, HF_MAX_FIELDS);
925     if (n < 1) {
926       sfree(value_copy);
927       ERROR(PLUGIN_NAME " plugin: HostnameFormat: no fields");
928       return -1;
929     }
930
931     for (int i = 0; i < n; ++i) {
932       if (strcasecmp(fields[i], "hostname") == 0)
933         hostname_format[i] = hf_hostname;
934       else if (strcasecmp(fields[i], "name") == 0)
935         hostname_format[i] = hf_name;
936       else if (strcasecmp(fields[i], "uuid") == 0)
937         hostname_format[i] = hf_uuid;
938       else {
939         ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
940               fields[i]);
941         sfree(value_copy);
942         return -1;
943       }
944     }
945     sfree(value_copy);
946
947     for (int i = n; i < HF_MAX_FIELDS; ++i)
948       hostname_format[i] = hf_none;
949
950     return 0;
951   }
952
953   if (strcasecmp(key, "PluginInstanceFormat") == 0) {
954     char *value_copy;
955     char *fields[PLGINST_MAX_FIELDS];
956     int n;
957
958     value_copy = strdup(value);
959     if (value_copy == NULL) {
960       ERROR(PLUGIN_NAME " plugin: strdup failed.");
961       return -1;
962     }
963
964     n = strsplit(value_copy, fields, PLGINST_MAX_FIELDS);
965     if (n < 1) {
966       sfree(value_copy);
967       ERROR(PLUGIN_NAME " plugin: PluginInstanceFormat: no fields");
968       return -1;
969     }
970
971     for (int i = 0; i < n; ++i) {
972       if (strcasecmp(fields[i], "none") == 0) {
973         plugin_instance_format[i] = plginst_none;
974         break;
975       } else if (strcasecmp(fields[i], "name") == 0)
976         plugin_instance_format[i] = plginst_name;
977       else if (strcasecmp(fields[i], "uuid") == 0)
978         plugin_instance_format[i] = plginst_uuid;
979       else {
980         ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
981               fields[i]);
982         sfree(value_copy);
983         return -1;
984       }
985     }
986     sfree(value_copy);
987
988     for (int i = n; i < PLGINST_MAX_FIELDS; ++i)
989       plugin_instance_format[i] = plginst_none;
990
991     return 0;
992   }
993
994   if (strcasecmp(key, "InterfaceFormat") == 0) {
995     if (strcasecmp(value, "name") == 0)
996       interface_format = if_name;
997     else if (strcasecmp(value, "address") == 0)
998       interface_format = if_address;
999     else if (strcasecmp(value, "number") == 0)
1000       interface_format = if_number;
1001     else {
1002       ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", value);
1003       return -1;
1004     }
1005     return 0;
1006   }
1007
1008   if (strcasecmp(key, "Instances") == 0) {
1009     char *eptr = NULL;
1010     double val = strtod(value, &eptr);
1011
1012     if (*eptr != '\0') {
1013       ERROR(PLUGIN_NAME " plugin: Invalid value for Instances = '%s'", value);
1014       return 1;
1015     }
1016     if (val <= 0) {
1017       ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
1018       return 1;
1019     }
1020     if (val > NR_INSTANCES_MAX) {
1021       ERROR(PLUGIN_NAME " plugin: Instances=%f > NR_INSTANCES_MAX=%i"
1022                         " use a lower setting or recompile the plugin.",
1023             val, NR_INSTANCES_MAX);
1024       return 1;
1025     }
1026
1027     nr_instances = (int)val;
1028     DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
1029     return 0;
1030   }
1031
1032   if (strcasecmp(key, "ExtraStats") == 0) {
1033     char *localvalue = strdup(value);
1034     if (localvalue != NULL) {
1035       char *exstats[EX_STATS_MAX_FIELDS];
1036       int numexstats =
1037           strsplit(localvalue, exstats, STATIC_ARRAY_SIZE(exstats));
1038       extra_stats = parse_ex_stats_flags(exstats, numexstats);
1039       sfree(localvalue);
1040
1041 #ifdef HAVE_JOB_STATS
1042       if ((extra_stats & ex_stats_job_stats_completed) &&
1043           (extra_stats & ex_stats_job_stats_background)) {
1044         ERROR(PLUGIN_NAME " plugin: Invalid job stats configuration. Only one "
1045                           "type of job statistics can be collected at the same "
1046                           "time");
1047         return 1;
1048       }
1049 #endif
1050     }
1051   }
1052
1053   /* Unrecognised option. */
1054   return -1;
1055 }
1056
1057 static int lv_connect(void) {
1058   if (conn == NULL) {
1059 /* `conn_string == NULL' is acceptable */
1060 #ifdef HAVE_FS_INFO
1061     /* virDomainGetFSInfo requires full read-write access connection */
1062     if (extra_stats & ex_stats_fs_info)
1063       conn = virConnectOpen(conn_string);
1064     else
1065 #endif
1066       conn = virConnectOpenReadOnly(conn_string);
1067     if (conn == NULL) {
1068       c_complain(LOG_ERR, &conn_complain,
1069                  PLUGIN_NAME " plugin: Unable to connect: "
1070                              "virConnectOpen failed.");
1071       return -1;
1072     }
1073     int status = virNodeGetInfo(conn, &nodeinfo);
1074     if (status != 0) {
1075       ERROR(PLUGIN_NAME ": virNodeGetInfo failed");
1076       return -1;
1077     }
1078   }
1079   c_release(LOG_NOTICE, &conn_complain,
1080             PLUGIN_NAME " plugin: Connection established.");
1081   return 0;
1082 }
1083
1084 static void lv_disconnect(void) {
1085   if (conn != NULL)
1086     virConnectClose(conn);
1087   conn = NULL;
1088   WARNING(PLUGIN_NAME " plugin: closed connection to libvirt");
1089 }
1090
1091 static int lv_domain_block_info(virDomainPtr dom, const char *path,
1092                                 struct lv_block_info *binfo) {
1093 #ifdef HAVE_BLOCK_STATS_FLAGS
1094   int nparams = 0;
1095   if (virDomainBlockStatsFlags(dom, path, NULL, &nparams, 0) < 0 ||
1096       nparams <= 0) {
1097     VIRT_ERROR(conn, "getting the disk params count");
1098     return -1;
1099   }
1100
1101   virTypedParameterPtr params = calloc((size_t)nparams, sizeof(*params));
1102   if (params == NULL) {
1103     ERROR("virt plugin: alloc(%i) for block=%s parameters failed.", nparams,
1104           path);
1105     return -1;
1106   }
1107
1108   int rc = -1;
1109   if (virDomainBlockStatsFlags(dom, path, params, &nparams, 0) < 0) {
1110     VIRT_ERROR(conn, "getting the disk params values");
1111   } else {
1112     rc = get_block_info(binfo, params, nparams);
1113   }
1114
1115   virTypedParamsClear(params, nparams);
1116   sfree(params);
1117   return rc;
1118 #else
1119   return virDomainBlockStats(dom, path, &(binfo->bi), sizeof(binfo->bi));
1120 #endif /* HAVE_BLOCK_STATS_FLAGS */
1121 }
1122
1123 #ifdef HAVE_PERF_STATS
1124 static void perf_submit(virDomainStatsRecordPtr stats) {
1125   for (int i = 0; i < stats->nparams; ++i) {
1126     /* Replace '.' with '_' in event field to match other metrics' naming
1127      * convention */
1128     char *c = strchr(stats->params[i].field, '.');
1129     if (c)
1130       *c = '_';
1131     submit(stats->dom, "perf", stats->params[i].field,
1132            &(value_t){.derive = stats->params[i].value.ul}, 1);
1133   }
1134 }
1135
1136 static int get_perf_events(virDomainPtr domain) {
1137   virDomainStatsRecordPtr *stats = NULL;
1138   /* virDomainListGetStats requires a NULL terminated list of domains */
1139   virDomainPtr domain_array[] = {domain, NULL};
1140
1141   int status =
1142       virDomainListGetStats(domain_array, VIR_DOMAIN_STATS_PERF, &stats, 0);
1143   if (status == -1) {
1144     ERROR("virt plugin: virDomainListGetStats failed with status %i.", status);
1145     return status;
1146   }
1147
1148   for (int i = 0; i < status; ++i)
1149     perf_submit(stats[i]);
1150
1151   virDomainStatsRecordListFree(stats);
1152   return 0;
1153 }
1154 #endif /* HAVE_PERF_STATS */
1155
1156 static void vcpu_pin_submit(virDomainPtr dom, int max_cpus, int vcpu,
1157                             unsigned char *cpu_maps, int cpu_map_len) {
1158   for (int cpu = 0; cpu < max_cpus; ++cpu) {
1159     char type_instance[DATA_MAX_NAME_LEN];
1160     _Bool is_set = VIR_CPU_USABLE(cpu_maps, cpu_map_len, vcpu, cpu) ? 1 : 0;
1161
1162     snprintf(type_instance, sizeof(type_instance), "vcpu_%d-cpu_%d", vcpu, cpu);
1163     submit(dom, "cpu_affinity", type_instance, &(value_t){.gauge = is_set}, 1);
1164   }
1165 }
1166
1167 static int get_vcpu_stats(virDomainPtr domain, unsigned short nr_virt_cpu) {
1168   int max_cpus = VIR_NODEINFO_MAXCPUS(nodeinfo);
1169   int cpu_map_len = VIR_CPU_MAPLEN(max_cpus);
1170
1171   virVcpuInfoPtr vinfo = calloc(nr_virt_cpu, sizeof(vinfo[0]));
1172   if (vinfo == NULL) {
1173     ERROR(PLUGIN_NAME " plugin: calloc failed.");
1174     return -1;
1175   }
1176
1177   unsigned char *cpumaps = calloc(nr_virt_cpu, cpu_map_len);
1178   if (cpumaps == NULL) {
1179     ERROR(PLUGIN_NAME " plugin: calloc failed.");
1180     sfree(vinfo);
1181     return -1;
1182   }
1183
1184   int status =
1185       virDomainGetVcpus(domain, vinfo, nr_virt_cpu, cpumaps, cpu_map_len);
1186   if (status < 0) {
1187     ERROR(PLUGIN_NAME " plugin: virDomainGetVcpus failed with status %i.",
1188           status);
1189     sfree(cpumaps);
1190     sfree(vinfo);
1191     return status;
1192   }
1193
1194   for (int i = 0; i < nr_virt_cpu; ++i) {
1195     vcpu_submit(vinfo[i].cpuTime, domain, vinfo[i].number, "virt_vcpu");
1196     if (extra_stats & ex_stats_vcpupin)
1197       vcpu_pin_submit(domain, max_cpus, i, cpumaps, cpu_map_len);
1198   }
1199
1200   sfree(cpumaps);
1201   sfree(vinfo);
1202   return 0;
1203 }
1204
1205 #ifdef HAVE_DOM_REASON
1206 static int get_domain_state(virDomainPtr domain) {
1207   int domain_state = 0;
1208   int domain_reason = 0;
1209
1210   int status = virDomainGetState(domain, &domain_state, &domain_reason, 0);
1211   if (status != 0) {
1212     ERROR(PLUGIN_NAME " plugin: virDomainGetState failed with status %i.",
1213           status);
1214     return status;
1215   }
1216
1217   domain_state_submit(domain, domain_state, domain_reason);
1218   return status;
1219 }
1220 #endif /* HAVE_DOM_REASON */
1221
1222 static int get_memory_stats(virDomainPtr domain) {
1223   virDomainMemoryStatPtr minfo =
1224       calloc(VIR_DOMAIN_MEMORY_STAT_NR, sizeof(virDomainMemoryStatStruct));
1225   if (minfo == NULL) {
1226     ERROR("virt plugin: malloc failed.");
1227     return -1;
1228   }
1229
1230   int mem_stats =
1231       virDomainMemoryStats(domain, minfo, VIR_DOMAIN_MEMORY_STAT_NR, 0);
1232   if (mem_stats < 0) {
1233     ERROR("virt plugin: virDomainMemoryStats failed with mem_stats %i.",
1234           mem_stats);
1235     sfree(minfo);
1236     return mem_stats;
1237   }
1238
1239   for (int i = 0; i < mem_stats; i++)
1240     memory_stats_submit((gauge_t)minfo[i].val * 1024, domain, minfo[i].tag);
1241
1242   sfree(minfo);
1243   return 0;
1244 }
1245
1246 #ifdef HAVE_DISK_ERR
1247 static void disk_err_submit(virDomainPtr domain,
1248                             virDomainDiskErrorPtr disk_err) {
1249   submit(domain, "disk_error", disk_err->disk,
1250          &(value_t){.gauge = disk_err->error}, 1);
1251 }
1252
1253 static int get_disk_err(virDomainPtr domain) {
1254   /* Get preferred size of disk errors array */
1255   int disk_err_count = virDomainGetDiskErrors(domain, NULL, 0, 0);
1256   if (disk_err_count == -1) {
1257     ERROR(PLUGIN_NAME
1258           " plugin: failed to get preferred size of disk errors array");
1259     return -1;
1260   }
1261
1262   DEBUG(PLUGIN_NAME
1263         " plugin: preferred size of disk errors array: %d for domain %s",
1264         disk_err_count, virDomainGetName(domain));
1265   virDomainDiskError disk_err[disk_err_count];
1266
1267   disk_err_count = virDomainGetDiskErrors(domain, disk_err, disk_err_count, 0);
1268   if (disk_err_count == -1) {
1269     ERROR(PLUGIN_NAME " plugin: virDomainGetDiskErrors failed with status %d",
1270           disk_err_count);
1271     return -1;
1272   }
1273
1274   DEBUG(PLUGIN_NAME " plugin: detected %d disk errors in domain %s",
1275         disk_err_count, virDomainGetName(domain));
1276
1277   for (int i = 0; i < disk_err_count; ++i) {
1278     disk_err_submit(domain, &disk_err[i]);
1279     sfree(disk_err[i].disk);
1280   }
1281
1282   return 0;
1283 }
1284 #endif /* HAVE_DISK_ERR */
1285
1286 static int get_block_stats(struct block_device *block_dev) {
1287
1288   if (!block_dev) {
1289     ERROR(PLUGIN_NAME " plugin: get_block_stats NULL pointer");
1290     return -1;
1291   }
1292
1293   struct lv_block_info binfo;
1294   init_block_info(&binfo);
1295
1296   if (lv_domain_block_info(block_dev->dom, block_dev->path, &binfo) < 0) {
1297     ERROR(PLUGIN_NAME " plugin: lv_domain_block_info failed");
1298     return -1;
1299   }
1300
1301   disk_submit(&binfo, block_dev->dom, block_dev->path);
1302   return 0;
1303 }
1304
1305 #ifdef HAVE_FS_INFO
1306
1307 #define NM_ADD_ITEM(_fun, _name, _val)                                         \
1308   do {                                                                         \
1309     ret = _fun(&notif, _name, _val);                                           \
1310     if (ret != 0) {                                                            \
1311       ERROR(PLUGIN_NAME " plugin: failed to add notification metadata");       \
1312       goto cleanup;                                                            \
1313     }                                                                          \
1314   } while (0)
1315
1316 #define NM_ADD_STR_ITEMS(_items, _size)                                        \
1317   do {                                                                         \
1318     for (int _i = 0; _i < _size; ++_i) {                                       \
1319       DEBUG(PLUGIN_NAME                                                        \
1320             " plugin: Adding notification metadata name=%s value=%s",          \
1321             _items[_i].name, _items[_i].value);                                \
1322       NM_ADD_ITEM(plugin_notification_meta_add_string, _items[_i].name,        \
1323                   _items[_i].value);                                           \
1324     }                                                                          \
1325   } while (0)
1326
1327 static int fs_info_notify(virDomainPtr domain, virDomainFSInfoPtr fs_info) {
1328   notification_t notif;
1329   int ret = 0;
1330
1331   /* Local struct, just for the purpose of this function. */
1332   typedef struct nm_str_item_s {
1333     const char *name;
1334     const char *value;
1335   } nm_str_item_t;
1336
1337   nm_str_item_t fs_dev_alias[fs_info->ndevAlias];
1338   nm_str_item_t fs_str_items[] = {
1339       {.name = "mountpoint", .value = fs_info->mountpoint},
1340       {.name = "name", .value = fs_info->name},
1341       {.name = "fstype", .value = fs_info->fstype}};
1342
1343   for (int i = 0; i < fs_info->ndevAlias; ++i) {
1344     fs_dev_alias[i].name = "devAlias";
1345     fs_dev_alias[i].value = fs_info->devAlias[i];
1346   }
1347
1348   init_notif(&notif, domain, NOTIF_OKAY, "File system information",
1349              "file_system", NULL);
1350   NM_ADD_STR_ITEMS(fs_str_items, STATIC_ARRAY_SIZE(fs_str_items));
1351   NM_ADD_ITEM(plugin_notification_meta_add_unsigned_int, "ndevAlias",
1352               fs_info->ndevAlias);
1353   NM_ADD_STR_ITEMS(fs_dev_alias, fs_info->ndevAlias);
1354
1355   plugin_dispatch_notification(&notif);
1356
1357 cleanup:
1358   if (notif.meta)
1359     plugin_notification_meta_free(notif.meta);
1360   return ret;
1361 }
1362
1363 #undef RETURN_ON_ERR
1364 #undef NM_ADD_STR_ITEMS
1365
1366 static int get_fs_info(virDomainPtr domain) {
1367   virDomainFSInfoPtr *fs_info = NULL;
1368   int ret = 0;
1369
1370   int mount_points_cnt = virDomainGetFSInfo(domain, &fs_info, 0);
1371   if (mount_points_cnt == -1) {
1372     ERROR(PLUGIN_NAME " plugin: virDomainGetFSInfo failed: %d",
1373           mount_points_cnt);
1374     return mount_points_cnt;
1375   }
1376
1377   for (int i = 0; i < mount_points_cnt; ++i) {
1378     if (fs_info_notify(domain, fs_info[i]) != 0) {
1379       ERROR(PLUGIN_NAME " plugin: failed to send file system notification "
1380                         "for mount point %s",
1381             fs_info[i]->mountpoint);
1382       ret = -1;
1383     }
1384     virDomainFSInfoFree(fs_info[i]);
1385   }
1386
1387   sfree(fs_info);
1388   return ret;
1389 }
1390
1391 #endif /* HAVE_FS_INFO */
1392
1393 #ifdef HAVE_JOB_STATS
1394 static void job_stats_submit(virDomainPtr domain, virTypedParameterPtr param) {
1395   value_t vl = {0};
1396
1397   if (param->type == VIR_TYPED_PARAM_INT)
1398     vl.derive = param->value.i;
1399   else if (param->type == VIR_TYPED_PARAM_UINT)
1400     vl.derive = param->value.ui;
1401   else if (param->type == VIR_TYPED_PARAM_LLONG)
1402     vl.derive = param->value.l;
1403   else if (param->type == VIR_TYPED_PARAM_ULLONG)
1404     vl.derive = param->value.ul;
1405   else if (param->type == VIR_TYPED_PARAM_DOUBLE)
1406     vl.derive = param->value.d;
1407   else if (param->type == VIR_TYPED_PARAM_BOOLEAN)
1408     vl.derive = param->value.b;
1409   else if (param->type == VIR_TYPED_PARAM_STRING) {
1410     submit_notif(domain, NOTIF_OKAY, param->value.s, "job_stats", param->field);
1411     return;
1412   } else {
1413     ERROR(PLUGIN_NAME " plugin: unrecognized virTypedParameterType");
1414     return;
1415   }
1416
1417   submit(domain, "job_stats", param->field, &vl, 1);
1418 }
1419
1420 static int get_job_stats(virDomainPtr domain) {
1421   int ret = 0;
1422   int job_type = 0;
1423   int nparams = 0;
1424   virTypedParameterPtr params = NULL;
1425   int flags = (extra_stats & ex_stats_job_stats_completed)
1426                   ? VIR_DOMAIN_JOB_STATS_COMPLETED
1427                   : 0;
1428
1429   ret = virDomainGetJobStats(domain, &job_type, &params, &nparams, flags);
1430   if (ret != 0) {
1431     ERROR(PLUGIN_NAME " plugin: virDomainGetJobStats failed: %d", ret);
1432     return ret;
1433   }
1434
1435   DEBUG(PLUGIN_NAME " plugin: job_type=%d nparams=%d", job_type, nparams);
1436
1437   for (int i = 0; i < nparams; ++i) {
1438     DEBUG(PLUGIN_NAME " plugin: param[%d] field=%s type=%d", i, params[i].field,
1439           params[i].type);
1440     job_stats_submit(domain, &params[i]);
1441   }
1442
1443   virTypedParamsFree(params, nparams);
1444   return ret;
1445 }
1446 #endif /* HAVE_JOB_STATS */
1447
1448 static int get_domain_metrics(domain_t *domain) {
1449   if (!domain || !domain->ptr) {
1450     ERROR(PLUGIN_NAME "plugin: get_domain_metrics: NULL pointer");
1451     return -1;
1452   }
1453
1454   virDomainInfo info;
1455   int status = virDomainGetInfo(domain->ptr, &info);
1456   if (status != 0) {
1457     ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
1458           status);
1459     return -1;
1460   }
1461
1462   if (extra_stats & ex_stats_domain_state) {
1463 #ifdef HAVE_DOM_REASON
1464     /* At this point we already know domain's state from virDomainGetInfo call,
1465      * however it doesn't provide a reason for entering particular state.
1466      * We need to get it from virDomainGetState.
1467      */
1468     GET_STATS(get_domain_state, "domain reason", domain->ptr);
1469 #else
1470     /* virDomainGetState is not available. Submit 0, which corresponds to
1471      * unknown reason. */
1472     domain_state_submit(domain->ptr, info.state, 0);
1473 #endif
1474   }
1475
1476   /* Gather remaining stats only for running domains */
1477   if (info.state != VIR_DOMAIN_RUNNING)
1478     return 0;
1479
1480 #ifdef HAVE_CPU_STATS
1481   if (extra_stats & ex_stats_pcpu)
1482     get_pcpu_stats(domain->ptr);
1483 #endif
1484
1485   cpu_submit(domain, info.cpuTime);
1486
1487   memory_submit(domain->ptr, (gauge_t)info.memory * 1024);
1488
1489   GET_STATS(get_vcpu_stats, "vcpu stats", domain->ptr, info.nrVirtCpu);
1490   GET_STATS(get_memory_stats, "memory stats", domain->ptr);
1491
1492 #ifdef HAVE_PERF_STATS
1493   if (extra_stats & ex_stats_perf)
1494     GET_STATS(get_perf_events, "performance monitoring events", domain->ptr);
1495 #endif
1496
1497 #ifdef HAVE_FS_INFO
1498   if (extra_stats & ex_stats_fs_info)
1499     GET_STATS(get_fs_info, "file system info", domain->ptr);
1500 #endif
1501
1502 #ifdef HAVE_DISK_ERR
1503   if (extra_stats & ex_stats_disk_err)
1504     GET_STATS(get_disk_err, "disk errors", domain->ptr);
1505 #endif
1506
1507 #ifdef HAVE_JOB_STATS
1508   if (extra_stats &
1509       (ex_stats_job_stats_completed | ex_stats_job_stats_background))
1510     GET_STATS(get_job_stats, "job stats", domain->ptr);
1511 #endif
1512
1513   /* Update cached virDomainInfo. It has to be done after cpu_submit */
1514   memcpy(&domain->info, &info, sizeof(domain->info));
1515   return 0;
1516 }
1517
1518 static int get_if_dev_stats(struct interface_device *if_dev) {
1519   virDomainInterfaceStatsStruct stats = {0};
1520   char *display_name = NULL;
1521
1522   if (!if_dev) {
1523     ERROR(PLUGIN_NAME " plugin: get_if_dev_stats: NULL pointer");
1524     return -1;
1525   }
1526
1527   switch (interface_format) {
1528   case if_address:
1529     display_name = if_dev->address;
1530     break;
1531   case if_number:
1532     display_name = if_dev->number;
1533     break;
1534   case if_name:
1535   default:
1536     display_name = if_dev->path;
1537   }
1538
1539   if (virDomainInterfaceStats(if_dev->dom, if_dev->path, &stats,
1540                               sizeof(stats)) != 0) {
1541     ERROR(PLUGIN_NAME " plugin: virDomainInterfaceStats failed");
1542     return -1;
1543   }
1544
1545   if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
1546     submit_derive2("if_octets", (derive_t)stats.rx_bytes,
1547                    (derive_t)stats.tx_bytes, if_dev->dom, display_name);
1548
1549   if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
1550     submit_derive2("if_packets", (derive_t)stats.rx_packets,
1551                    (derive_t)stats.tx_packets, if_dev->dom, display_name);
1552
1553   if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
1554     submit_derive2("if_errors", (derive_t)stats.rx_errs,
1555                    (derive_t)stats.tx_errs, if_dev->dom, display_name);
1556
1557   if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
1558     submit_derive2("if_dropped", (derive_t)stats.rx_drop,
1559                    (derive_t)stats.tx_drop, if_dev->dom, display_name);
1560   return 0;
1561 }
1562
1563 static int lv_read(user_data_t *ud) {
1564   time_t t;
1565   struct lv_read_instance *inst = NULL;
1566   struct lv_read_state *state = NULL;
1567
1568   if (ud->data == NULL) {
1569     ERROR(PLUGIN_NAME " plugin: NULL userdata");
1570     return -1;
1571   }
1572
1573   inst = ud->data;
1574   state = &inst->read_state;
1575
1576   if (inst->id == 0) {
1577     if (lv_connect() < 0)
1578       return -1;
1579   }
1580
1581   time(&t);
1582
1583   /* Need to refresh domain or device lists? */
1584   if ((last_refresh == (time_t)0) ||
1585       ((interval > 0) && ((last_refresh + interval) <= t))) {
1586     if (refresh_lists(inst) != 0) {
1587       if (inst->id == 0)
1588         lv_disconnect();
1589       return -1;
1590     }
1591     last_refresh = t;
1592   }
1593
1594 #if 0
1595     for (int i = 0; i < nr_domains; ++i)
1596         fprintf (stderr, "domain %s\n", virDomainGetName (state->domains[i].ptr));
1597     for (int i = 0; i < nr_block_devices; ++i)
1598         fprintf  (stderr, "block device %d %s:%s\n",
1599                   i, virDomainGetName (block_devices[i].dom),
1600                   block_devices[i].path);
1601     for (int i = 0; i < nr_interface_devices; ++i)
1602         fprintf (stderr, "interface device %d %s:%s\n",
1603                  i, virDomainGetName (interface_devices[i].dom),
1604                  interface_devices[i].path);
1605 #endif
1606
1607   /* Get domains' metrics */
1608   for (int i = 0; i < state->nr_domains; ++i) {
1609     int status = get_domain_metrics(&state->domains[i]);
1610     if (status != 0)
1611       ERROR(PLUGIN_NAME " failed to get metrics for domain=%s",
1612             virDomainGetName(state->domains[i].ptr));
1613   }
1614
1615   /* Get block device stats for each domain. */
1616   for (int i = 0; i < state->nr_block_devices; ++i) {
1617     int status = get_block_stats(&state->block_devices[i]);
1618     if (status != 0)
1619       ERROR(PLUGIN_NAME
1620             " failed to get stats for block device (%s) in domain %s",
1621             state->block_devices[i].path,
1622             virDomainGetName(state->block_devices[i].dom));
1623   }
1624
1625   /* Get interface stats for each domain. */
1626   for (int i = 0; i < state->nr_interface_devices; ++i) {
1627     int status = get_if_dev_stats(&state->interface_devices[i]);
1628     if (status != 0)
1629       ERROR(PLUGIN_NAME
1630             " failed to get interface stats for device (%s) in domain %s",
1631             state->interface_devices[i].path,
1632             virDomainGetName(state->interface_devices[i].dom));
1633   }
1634
1635   return 0;
1636 }
1637
1638 static int lv_init_instance(size_t i, plugin_read_cb callback) {
1639   struct lv_user_data *lv_ud = &(lv_read_user_data[i]);
1640   struct lv_read_instance *inst = &(lv_ud->inst);
1641
1642   memset(lv_ud, 0, sizeof(*lv_ud));
1643
1644   snprintf(inst->tag, sizeof(inst->tag), "%s-%zu", PLUGIN_NAME, i);
1645   inst->id = i;
1646
1647   user_data_t *ud = &(lv_ud->ud);
1648   ud->data = inst;
1649   ud->free_func = NULL;
1650
1651   INFO(PLUGIN_NAME " plugin: reader %s initialized", inst->tag);
1652   return plugin_register_complex_read(NULL, inst->tag, callback, 0, ud);
1653 }
1654
1655 static void lv_clean_read_state(struct lv_read_state *state) {
1656   free_block_devices(state);
1657   free_interface_devices(state);
1658   free_domains(state);
1659 }
1660
1661 static void lv_fini_instance(size_t i) {
1662   struct lv_read_instance *inst = &(lv_read_user_data[i].inst);
1663   struct lv_read_state *state = &(inst->read_state);
1664
1665   lv_clean_read_state(state);
1666   INFO(PLUGIN_NAME " plugin: reader %s finalized", inst->tag);
1667 }
1668
1669 static int lv_init(void) {
1670   if (virInitialize() != 0)
1671     return -1;
1672
1673   if (lv_connect() != 0)
1674     return -1;
1675
1676   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
1677
1678   for (int i = 0; i < nr_instances; ++i)
1679     lv_init_instance(i, lv_read);
1680
1681   return 0;
1682 }
1683
1684 /*
1685  * returns 0 on success and <0 on error
1686  */
1687 static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
1688                              char *dom_tag) {
1689   char xpath_str[BUFFER_MAX_LEN] = {'\0'};
1690   xmlXPathObjectPtr xpath_obj = NULL;
1691   xmlNodePtr xml_node = NULL;
1692   int ret = -1;
1693   int err;
1694
1695   err = xmlXPathRegisterNs(xpath_ctx,
1696                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
1697                            (const xmlChar *)METADATA_VM_PARTITION_URI);
1698   if (err) {
1699     ERROR(PLUGIN_NAME " plugin: xmlXpathRegisterNs(%s, %s) failed on domain %s",
1700           METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_URI, dom_name);
1701     goto done;
1702   }
1703
1704   snprintf(xpath_str, sizeof(xpath_str), "/domain/metadata/%s:%s/text()",
1705            METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_ELEMENT);
1706   xpath_obj = xmlXPathEvalExpression((xmlChar *)xpath_str, xpath_ctx);
1707   if (xpath_obj == NULL) {
1708     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed on domain %s",
1709           xpath_str, dom_name);
1710     goto done;
1711   }
1712
1713   if (xpath_obj->type != XPATH_NODESET) {
1714     ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d "
1715                       "(wanted %d) on domain %s",
1716           xpath_str, xpath_obj->type, XPATH_NODESET, dom_name);
1717     goto done;
1718   }
1719
1720   /*
1721    * from now on there is no real error, it's ok if a domain
1722    * doesn't have the metadata partition tag.
1723    */
1724   ret = 0;
1725   if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) {
1726     DEBUG(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i "
1727                       "expected=1 on domain %s",
1728           xpath_str,
1729           (xpath_obj->nodesetval == NULL) ? 0 : xpath_obj->nodesetval->nodeNr,
1730           dom_name);
1731   } else {
1732     xml_node = xpath_obj->nodesetval->nodeTab[0];
1733     sstrncpy(dom_tag, (const char *)xml_node->content, PARTITION_TAG_MAX_LEN);
1734   }
1735
1736 done:
1737   /* deregister to clean up */
1738   err = xmlXPathRegisterNs(xpath_ctx,
1739                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX, NULL);
1740   if (err) {
1741     /* we can't really recover here */
1742     ERROR(PLUGIN_NAME
1743           " plugin: deregistration of namespace %s failed for domain %s",
1744           METADATA_VM_PARTITION_PREFIX, dom_name);
1745   }
1746   if (xpath_obj)
1747     xmlXPathFreeObject(xpath_obj);
1748
1749   return ret;
1750 }
1751
1752 static int is_known_tag(const char *dom_tag) {
1753   for (int i = 0; i < nr_instances; ++i)
1754     if (!strcmp(dom_tag, lv_read_user_data[i].inst.tag))
1755       return 1;
1756   return 0;
1757 }
1758
1759 static int lv_instance_include_domain(struct lv_read_instance *inst,
1760                                       const char *dom_name,
1761                                       const char *dom_tag) {
1762   if ((dom_tag[0] != '\0') && (strcmp(dom_tag, inst->tag) == 0))
1763     return 1;
1764
1765   /* instance#0 will always be there, so it is in charge of extra duties */
1766   if (inst->id == 0) {
1767     if (dom_tag[0] == '\0' || !is_known_tag(dom_tag)) {
1768       DEBUG(PLUGIN_NAME " plugin#%s: refreshing domain %s "
1769                         "with unknown tag '%s'",
1770             inst->tag, dom_name, dom_tag);
1771       return 1;
1772     }
1773   }
1774
1775   return 0;
1776 }
1777
1778 /*
1779   virConnectListAllDomains() appeared in 0.10.2
1780   Note that LIBVIR_CHECK_VERSION appeared a year later, so
1781   in some systems which actually have virConnectListAllDomains()
1782   we can't detect this.
1783  */
1784 #ifdef LIBVIR_CHECK_VERSION
1785 #if LIBVIR_CHECK_VERSION(0, 10, 2)
1786 #define HAVE_LIST_ALL_DOMAINS 1
1787 #endif
1788 #endif
1789
1790 static int refresh_lists(struct lv_read_instance *inst) {
1791   struct lv_read_state *state = &inst->read_state;
1792   int n;
1793
1794   n = virConnectNumOfDomains(conn);
1795   if (n < 0) {
1796     VIRT_ERROR(conn, "reading number of domains");
1797     return -1;
1798   }
1799
1800   lv_clean_read_state(state);
1801
1802   if (n > 0) {
1803 #ifdef HAVE_LIST_ALL_DOMAINS
1804     virDomainPtr *domains;
1805     n = virConnectListAllDomains(conn, &domains,
1806                                  VIR_CONNECT_LIST_DOMAINS_ACTIVE);
1807 #else
1808     int *domids;
1809
1810     /* Get list of domains. */
1811     domids = malloc(sizeof(*domids) * n);
1812     if (domids == NULL) {
1813       ERROR(PLUGIN_NAME " plugin: malloc failed.");
1814       return -1;
1815     }
1816
1817     n = virConnectListDomains(conn, domids, n);
1818 #endif
1819
1820     if (n < 0) {
1821       VIRT_ERROR(conn, "reading list of domains");
1822 #ifndef HAVE_LIST_ALL_DOMAINS
1823       sfree(domids);
1824 #endif
1825       return -1;
1826     }
1827
1828     /* Fetch each domain and add it to the list, unless ignore. */
1829     for (int i = 0; i < n; ++i) {
1830       const char *name;
1831       char *xml = NULL;
1832       xmlDocPtr xml_doc = NULL;
1833       xmlXPathContextPtr xpath_ctx = NULL;
1834       xmlXPathObjectPtr xpath_obj = NULL;
1835       char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
1836       virDomainInfo info;
1837       int status;
1838
1839 #ifdef HAVE_LIST_ALL_DOMAINS
1840       virDomainPtr dom = domains[i];
1841 #else
1842       virDomainPtr dom = NULL;
1843       dom = virDomainLookupByID(conn, domids[i]);
1844       if (dom == NULL) {
1845         VIRT_ERROR(conn, "virDomainLookupByID");
1846         /* Could be that the domain went away -- ignore it anyway. */
1847         continue;
1848       }
1849 #endif
1850
1851       name = virDomainGetName(dom);
1852       if (name == NULL) {
1853         VIRT_ERROR(conn, "virDomainGetName");
1854         goto cont;
1855       }
1856
1857       status = virDomainGetInfo(dom, &info);
1858       if (status != 0) {
1859         ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
1860               status);
1861         continue;
1862       }
1863
1864       if (info.state != VIR_DOMAIN_RUNNING) {
1865         DEBUG(PLUGIN_NAME " plugin: skipping inactive domain %s", name);
1866         continue;
1867       }
1868
1869       if (il_domains && ignorelist_match(il_domains, name) != 0)
1870         goto cont;
1871
1872       /* Get a list of devices for this domain. */
1873       xml = virDomainGetXMLDesc(dom, 0);
1874       if (!xml) {
1875         VIRT_ERROR(conn, "virDomainGetXMLDesc");
1876         goto cont;
1877       }
1878
1879       /* Yuck, XML.  Parse out the devices. */
1880       xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
1881       if (xml_doc == NULL) {
1882         VIRT_ERROR(conn, "xmlReadDoc");
1883         goto cont;
1884       }
1885
1886       xpath_ctx = xmlXPathNewContext(xml_doc);
1887
1888       if (lv_domain_get_tag(xpath_ctx, name, tag) < 0) {
1889         ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
1890         goto cont;
1891       }
1892
1893       if (!lv_instance_include_domain(inst, name, tag))
1894         goto cont;
1895
1896       if (add_domain(state, dom) < 0) {
1897         ERROR(PLUGIN_NAME " plugin: malloc failed.");
1898         goto cont;
1899       }
1900
1901       /* Block devices. */
1902       const char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
1903       if (blockdevice_format == source)
1904         bd_xmlpath = "/domain/devices/disk/source[@dev]";
1905       xpath_obj = xmlXPathEval((const xmlChar *)bd_xmlpath, xpath_ctx);
1906
1907       if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
1908           xpath_obj->nodesetval == NULL)
1909         goto cont;
1910
1911       for (int j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
1912         xmlNodePtr node;
1913         char *path = NULL;
1914
1915         node = xpath_obj->nodesetval->nodeTab[j];
1916         if (!node)
1917           continue;
1918         path = (char *)xmlGetProp(node, (xmlChar *)"dev");
1919         if (!path)
1920           continue;
1921
1922         if (il_block_devices &&
1923             ignore_device_match(il_block_devices, name, path) != 0)
1924           goto cont2;
1925
1926         add_block_device(state, dom, path);
1927       cont2:
1928         if (path)
1929           xmlFree(path);
1930       }
1931       xmlXPathFreeObject(xpath_obj);
1932
1933       /* Network interfaces. */
1934       xpath_obj = xmlXPathEval(
1935           (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
1936       if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
1937           xpath_obj->nodesetval == NULL)
1938         goto cont;
1939
1940       xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
1941
1942       for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
1943         char *path = NULL;
1944         char *address = NULL;
1945         xmlNodePtr xml_interface;
1946
1947         xml_interface = xml_interfaces->nodeTab[j];
1948         if (!xml_interface)
1949           continue;
1950
1951         for (xmlNodePtr child = xml_interface->children; child;
1952              child = child->next) {
1953           if (child->type != XML_ELEMENT_NODE)
1954             continue;
1955
1956           if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
1957             path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
1958             if (!path)
1959               continue;
1960           } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
1961             address = (char *)xmlGetProp(child, (const xmlChar *)"address");
1962             if (!address)
1963               continue;
1964           }
1965         }
1966
1967         if (il_interface_devices &&
1968             (ignore_device_match(il_interface_devices, name, path) != 0 ||
1969              ignore_device_match(il_interface_devices, name, address) != 0))
1970           goto cont3;
1971
1972         add_interface_device(state, dom, path, address, j + 1);
1973       cont3:
1974         if (path)
1975           xmlFree(path);
1976         if (address)
1977           xmlFree(address);
1978       }
1979
1980     cont:
1981       if (xpath_obj)
1982         xmlXPathFreeObject(xpath_obj);
1983       if (xpath_ctx)
1984         xmlXPathFreeContext(xpath_ctx);
1985       if (xml_doc)
1986         xmlFreeDoc(xml_doc);
1987       sfree(xml);
1988     }
1989
1990 #ifdef HAVE_LIST_ALL_DOMAINS
1991     sfree(domains);
1992 #else
1993     sfree(domids);
1994 #endif
1995   }
1996
1997   DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
1998                     " domains=%i block_devices=%i iface_devices=%i",
1999         inst->tag, state->nr_domains, state->nr_block_devices,
2000         state->nr_interface_devices);
2001
2002   return 0;
2003 }
2004
2005 static void free_domains(struct lv_read_state *state) {
2006   if (state->domains) {
2007     for (int i = 0; i < state->nr_domains; ++i)
2008       virDomainFree(state->domains[i].ptr);
2009     sfree(state->domains);
2010   }
2011   state->domains = NULL;
2012   state->nr_domains = 0;
2013 }
2014
2015 static int add_domain(struct lv_read_state *state, virDomainPtr dom) {
2016   domain_t *new_ptr;
2017   int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
2018
2019   if (state->domains)
2020     new_ptr = realloc(state->domains, new_size);
2021   else
2022     new_ptr = malloc(new_size);
2023
2024   if (new_ptr == NULL)
2025     return -1;
2026
2027   state->domains = new_ptr;
2028   state->domains[state->nr_domains].ptr = dom;
2029   memset(&state->domains[state->nr_domains].info, 0,
2030          sizeof(state->domains[state->nr_domains].info));
2031
2032   return state->nr_domains++;
2033 }
2034
2035 static void free_block_devices(struct lv_read_state *state) {
2036   if (state->block_devices) {
2037     for (int i = 0; i < state->nr_block_devices; ++i)
2038       sfree(state->block_devices[i].path);
2039     sfree(state->block_devices);
2040   }
2041   state->block_devices = NULL;
2042   state->nr_block_devices = 0;
2043 }
2044
2045 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
2046                             const char *path) {
2047   struct block_device *new_ptr;
2048   int new_size =
2049       sizeof(state->block_devices[0]) * (state->nr_block_devices + 1);
2050   char *path_copy;
2051
2052   path_copy = strdup(path);
2053   if (!path_copy)
2054     return -1;
2055
2056   if (state->block_devices)
2057     new_ptr = realloc(state->block_devices, new_size);
2058   else
2059     new_ptr = malloc(new_size);
2060
2061   if (new_ptr == NULL) {
2062     sfree(path_copy);
2063     return -1;
2064   }
2065   state->block_devices = new_ptr;
2066   state->block_devices[state->nr_block_devices].dom = dom;
2067   state->block_devices[state->nr_block_devices].path = path_copy;
2068   return state->nr_block_devices++;
2069 }
2070
2071 static void free_interface_devices(struct lv_read_state *state) {
2072   if (state->interface_devices) {
2073     for (int i = 0; i < state->nr_interface_devices; ++i) {
2074       sfree(state->interface_devices[i].path);
2075       sfree(state->interface_devices[i].address);
2076       sfree(state->interface_devices[i].number);
2077     }
2078     sfree(state->interface_devices);
2079   }
2080   state->interface_devices = NULL;
2081   state->nr_interface_devices = 0;
2082 }
2083
2084 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
2085                                 const char *path, const char *address,
2086                                 unsigned int number) {
2087   struct interface_device *new_ptr;
2088   int new_size =
2089       sizeof(state->interface_devices[0]) * (state->nr_interface_devices + 1);
2090   char *path_copy, *address_copy, number_string[15];
2091
2092   if ((path == NULL) || (address == NULL))
2093     return EINVAL;
2094
2095   path_copy = strdup(path);
2096   if (!path_copy)
2097     return -1;
2098
2099   address_copy = strdup(address);
2100   if (!address_copy) {
2101     sfree(path_copy);
2102     return -1;
2103   }
2104
2105   snprintf(number_string, sizeof(number_string), "interface-%u", number);
2106
2107   if (state->interface_devices)
2108     new_ptr = realloc(state->interface_devices, new_size);
2109   else
2110     new_ptr = malloc(new_size);
2111
2112   if (new_ptr == NULL) {
2113     sfree(path_copy);
2114     sfree(address_copy);
2115     return -1;
2116   }
2117   state->interface_devices = new_ptr;
2118   state->interface_devices[state->nr_interface_devices].dom = dom;
2119   state->interface_devices[state->nr_interface_devices].path = path_copy;
2120   state->interface_devices[state->nr_interface_devices].address = address_copy;
2121   state->interface_devices[state->nr_interface_devices].number =
2122       strdup(number_string);
2123   return state->nr_interface_devices++;
2124 }
2125
2126 static int ignore_device_match(ignorelist_t *il, const char *domname,
2127                                const char *devpath) {
2128   char *name;
2129   int n, r;
2130
2131   if ((domname == NULL) || (devpath == NULL))
2132     return 0;
2133
2134   n = strlen(domname) + strlen(devpath) + 2;
2135   name = malloc(n);
2136   if (name == NULL) {
2137     ERROR(PLUGIN_NAME " plugin: malloc failed.");
2138     return 0;
2139   }
2140   snprintf(name, n, "%s:%s", domname, devpath);
2141   r = ignorelist_match(il, name);
2142   sfree(name);
2143   return r;
2144 }
2145
2146 static int lv_shutdown(void) {
2147   for (int i = 0; i < nr_instances; ++i) {
2148     lv_fini_instance(i);
2149   }
2150
2151   lv_disconnect();
2152
2153   ignorelist_free(il_domains);
2154   il_domains = NULL;
2155   ignorelist_free(il_block_devices);
2156   il_block_devices = NULL;
2157   ignorelist_free(il_interface_devices);
2158   il_interface_devices = NULL;
2159
2160   return 0;
2161 }
2162
2163 void module_register(void) {
2164   plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
2165   plugin_register_init(PLUGIN_NAME, lv_init);
2166   plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
2167 }