virt plugin: Report disk flush information
[collectd.git] / src / virt.c
index 86f83ae..acdc11f 100644 (file)
 /* Plugin name */
 #define PLUGIN_NAME "virt"
 
+#ifdef LIBVIR_CHECK_VERSION
+#if LIBVIR_CHECK_VERSION(0, 9, 5)
+#define HAVE_BLOCK_STATS_FLAGS 1
+#endif
+#endif
+
 static const char *config_keys[] = {"Connection",
 
                                     "RefreshInterval",
@@ -168,6 +174,65 @@ static time_t last_refresh = (time_t)0;
 
 static int refresh_lists(struct lv_read_instance *inst);
 
+struct lv_block_info {
+  virDomainBlockStatsStruct bi;
+
+  long long rd_total_times;
+  long long wr_total_times;
+
+  long long fl_req;
+  long long fl_total_times;
+};
+
+static void init_block_info(struct lv_block_info *binfo) {
+  if (binfo == NULL)
+    return;
+
+  binfo->bi.rd_req = -1;
+  binfo->bi.wr_req = -1;
+  binfo->bi.rd_bytes = -1;
+  binfo->bi.wr_bytes = -1;
+
+  binfo->rd_total_times = -1;
+  binfo->wr_total_times = -1;
+  binfo->fl_req = -1;
+  binfo->fl_total_times = -1;
+}
+
+#ifdef HAVE_BLOCK_STATS_FLAGS
+
+#define GET_BLOCK_INFO_VALUE(NAME, FIELD)                                      \
+  do {                                                                         \
+    if (!strcmp(param[i].field, NAME)) {                                       \
+      binfo->FIELD = param[i].value.l;                                         \
+      continue;                                                                \
+    }                                                                          \
+  } while (0)
+
+static int get_block_info(struct lv_block_info *binfo,
+                          virTypedParameterPtr param, int nparams) {
+  if (binfo == NULL || param == NULL)
+    return -1;
+
+  for (int i = 0; i < nparams; ++i) {
+    /* ignore type. Everything must be LLONG anyway. */
+    GET_BLOCK_INFO_VALUE("rd_operations", bi.rd_req);
+    GET_BLOCK_INFO_VALUE("wr_operations", bi.wr_req);
+    GET_BLOCK_INFO_VALUE("rd_bytes", bi.rd_bytes);
+    GET_BLOCK_INFO_VALUE("wr_bytes", bi.wr_bytes);
+    GET_BLOCK_INFO_VALUE("rd_total_times", rd_total_times);
+    GET_BLOCK_INFO_VALUE("wr_total_times", wr_total_times);
+    GET_BLOCK_INFO_VALUE("flush_operations", fl_req);
+    GET_BLOCK_INFO_VALUE("flush_total_times", fl_total_times);
+  }
+
+  return 0;
+}
+
+#undef GET_BLOCK_INFO_VALUE
+
+#endif /* HAVE_BLOCK_STATS_FLAGS */
+
 /* ERROR(...) macro for virterrors. */
 #define VIRT_ERROR(conn, s)                                                    \
   do {                                                                         \
@@ -306,6 +371,35 @@ static void submit_derive2(const char *type, derive_t v0, derive_t v1,
   submit(dom, type, devname, values, STATIC_ARRAY_SIZE(values));
 } /* void submit_derive2 */
 
+static void disk_submit(struct lv_block_info *binfo, virDomainPtr dom,
+                        const char *type_instance) {
+  char flush_type_instance[DATA_MAX_NAME_LEN];
+
+  ssnprintf(flush_type_instance, sizeof(flush_type_instance), "flush-%s",
+            type_instance);
+
+  if ((binfo->bi.rd_req != -1) && (binfo->bi.wr_req != -1))
+    submit_derive2("disk_ops", (derive_t)binfo->bi.rd_req,
+                   (derive_t)binfo->bi.wr_req, dom, type_instance);
+
+  if ((binfo->bi.rd_bytes != -1) && (binfo->bi.wr_bytes != -1))
+    submit_derive2("disk_octets", (derive_t)binfo->bi.rd_bytes,
+                   (derive_t)binfo->bi.wr_bytes, dom, type_instance);
+
+  if ((binfo->rd_total_times != -1) && (binfo->wr_total_times != -1))
+    submit_derive2("disk_time", (derive_t)binfo->rd_total_times,
+                   (derive_t)binfo->wr_total_times, dom, type_instance);
+
+  if (binfo->fl_req != -1)
+    submit(dom, "total_requests", flush_type_instance,
+           &(value_t){.derive = (derive_t)binfo->fl_req}, 1);
+  if (binfo->fl_total_times != -1) {
+    derive_t value = binfo->fl_total_times / 1000; // ns -> ms
+    submit(dom, "total_time_in_ms", flush_type_instance,
+           &(value_t){.derive = value}, 1);
+  }
+}
+
 static int lv_config(const char *key, const char *value) {
   if (virInitialize() != 0)
     return 1;
@@ -504,6 +598,65 @@ static int lv_config(const char *key, const char *value) {
   return -1;
 }
 
+static int lv_connect(void) {
+  if (conn == NULL) {
+    /* `conn_string == NULL' is acceptable. */
+    conn = virConnectOpenReadOnly(conn_string);
+    if (conn == NULL) {
+      c_complain(LOG_ERR, &conn_complain,
+                 PLUGIN_NAME " plugin: Unable to connect: "
+                             "virConnectOpenReadOnly failed.");
+      return -1;
+    }
+  }
+  c_release(LOG_NOTICE, &conn_complain,
+            PLUGIN_NAME " plugin: Connection established.");
+  return 0;
+}
+
+static void lv_disconnect(void) {
+  if (conn != NULL)
+    virConnectClose(conn);
+  conn = NULL;
+  WARNING(PLUGIN_NAME " plugin: closed connection to libvirt");
+}
+
+static int lv_domain_block_info(virDomainPtr dom, const char *path,
+                                struct lv_block_info *binfo) {
+#ifdef HAVE_BLOCK_STATS_FLAGS
+  virTypedParameterPtr params = NULL;
+  int nparams = 0;
+  int rc = -1;
+  int ret;
+
+  ret = virDomainBlockStatsFlags(dom, path, NULL, &nparams, 0);
+  if (ret < 0 || nparams == 0) {
+    VIRT_ERROR(conn, "getting the disk params count");
+    return -1;
+  }
+
+  params = calloc(nparams, sizeof(virTypedParameter));
+  if (params == NULL) {
+    ERROR("virt plugin: alloc(%i) for block=%s parameters failed.", nparams,
+          path);
+    return -1;
+  }
+  ret = virDomainBlockStatsFlags(dom, path, params, &nparams, 0);
+  if (ret < 0) {
+    VIRT_ERROR(conn, "getting the disk params values");
+    goto done;
+  }
+
+  rc = get_block_info(binfo, params, nparams);
+
+done:
+  virTypedParamsFree(params, nparams);
+  return rc;
+#else
+  return virDomainBlockStats(dom, path, &(binfo->bi), sizeof(binfo->bi));
+#endif /* HAVE_BLOCK_STATS_FLAGS */
+}
+
 static int lv_read(user_data_t *ud) {
   time_t t;
   struct lv_read_instance *inst = NULL;
@@ -517,18 +670,10 @@ static int lv_read(user_data_t *ud) {
   inst = ud->data;
   state = &inst->read_state;
 
-  if (inst->id == 0 && conn == NULL) {
-    /* `conn_string == NULL' is acceptable. */
-    conn = virConnectOpenReadOnly(conn_string);
-    if (conn == NULL) {
-      c_complain(LOG_ERR, &conn_complain,
-                 PLUGIN_NAME " plugin: Unable to connect: "
-                             "virConnectOpenReadOnly failed.");
+  if (inst->id == 0) {
+    if (lv_connect() < 0)
       return -1;
-    }
   }
-  c_release(LOG_NOTICE, &conn_complain,
-            PLUGIN_NAME " plugin: Connection established.");
 
   time(&t);
 
@@ -536,9 +681,8 @@ static int lv_read(user_data_t *ud) {
   if ((last_refresh == (time_t)0) ||
       ((interval > 0) && ((last_refresh + interval) <= t))) {
     if (refresh_lists(inst) != 0) {
-      if (conn != NULL)
-        virConnectClose(conn);
-      conn = NULL;
+      if (inst->id == 0)
+        lv_disconnect();
       return -1;
     }
     last_refresh = t;
@@ -626,27 +770,20 @@ static int lv_read(user_data_t *ud) {
 
   /* Get block device stats for each domain. */
   for (int i = 0; i < state->nr_block_devices; ++i) {
-    struct _virDomainBlockStats stats;
+    struct block_device *bdev = &(state->block_devices[i]);
+    struct lv_block_info binfo;
+    init_block_info(&binfo);
 
-    if (virDomainBlockStats(state->block_devices[i].dom,
-                            state->block_devices[i].path, &stats,
-                            sizeof stats) != 0)
+    if (lv_domain_block_info(bdev->dom, bdev->path, &binfo) < 0)
       continue;
 
     char *type_instance = NULL;
     if (blockdevice_format_basename && blockdevice_format == source)
-      type_instance = strdup(basename(state->block_devices[i].path));
+      type_instance = strdup(basename(bdev->path));
     else
-      type_instance = strdup(state->block_devices[i].path);
+      type_instance = strdup(bdev->path);
 
-    if ((stats.rd_req != -1) && (stats.wr_req != -1))
-      submit_derive2("disk_ops", (derive_t)stats.rd_req, (derive_t)stats.wr_req,
-                     state->block_devices[i].dom, type_instance);
-
-    if ((stats.rd_bytes != -1) && (stats.wr_bytes != -1))
-      submit_derive2("disk_octets", (derive_t)stats.rd_bytes,
-                     (derive_t)stats.wr_bytes, state->block_devices[i].dom,
-                     type_instance);
+    disk_submit(&binfo, bdev->dom, type_instance);
 
     sfree(type_instance);
   } /* for (nr_block_devices) */
@@ -732,6 +869,9 @@ static int lv_init(void) {
   if (virInitialize() != 0)
     return -1;
 
+  if (lv_connect() != 0)
+    return -1;
+
   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
 
   for (int i = 0; i < nr_instances; ++i)
@@ -740,12 +880,16 @@ static int lv_init(void) {
   return 0;
 }
 
+/*
+ * returns 0 on success and <0 on error
+ */
 static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
                              char *dom_tag) {
   char xpath_str[BUFFER_MAX_LEN] = {'\0'};
   xmlXPathObjectPtr xpath_obj = NULL;
   xmlNodePtr xml_node = NULL;
-  int err = -1;
+  int ret = -1;
+  int err;
 
   err = xmlXPathRegisterNs(xpath_ctx,
                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
@@ -776,8 +920,7 @@ static int lv_domain_get_tag(xmlXPathContextPtr xpath_ctx, const char *dom_name,
    * from now on there is no real error, it's ok if a domain
    * doesn't have the metadata partition tag.
    */
-  err = 0;
-
+  ret = 0;
   if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) {
     DEBUG(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i "
                       "expected=1 on domain %s",
@@ -793,11 +936,16 @@ done:
   /* deregister to clean up */
   err = xmlXPathRegisterNs(xpath_ctx,
                            (const xmlChar *)METADATA_VM_PARTITION_PREFIX, NULL);
-
+  if (err) {
+    /* we can't really recover here */
+    ERROR(PLUGIN_NAME
+          " plugin: deregistration of namespace %s failed for domain %s",
+          METADATA_VM_PARTITION_PREFIX, dom_name);
+  }
   if (xpath_obj)
     xmlXPathFreeObject(xpath_obj);
 
-  return err;
+  return ret;
 }
 
 static int is_known_tag(const char *dom_tag) {
@@ -911,10 +1059,10 @@ static int refresh_lists(struct lv_read_instance *inst) {
       }
 
       /* Block devices. */
-      char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
+      const char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
       if (blockdevice_format == source)
         bd_xmlpath = "/domain/devices/disk/source[@dev]";
-      xpath_obj = xmlXPathEval((xmlChar *)bd_xmlpath, xpath_ctx);
+      xpath_obj = xmlXPathEval((const xmlChar *)bd_xmlpath, xpath_ctx);
 
       if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
           xpath_obj->nodesetval == NULL)
@@ -1153,9 +1301,7 @@ static int lv_shutdown(void) {
     lv_fini_instance(i);
   }
 
-  if (conn != NULL)
-    virConnectClose(conn);
-  conn = NULL;
+  lv_disconnect();
 
   ignorelist_free(il_domains);
   il_domains = NULL;