#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
/* 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",
"PluginInstanceFormat",
+ "Instances",
+
NULL};
#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
int nr_interface_devices;
};
-static struct lv_read_state read_state = {
- NULL, 0, NULL, 0, NULL, 0,
-};
-
static void free_domains(struct lv_read_state *state);
static int add_domain(struct lv_read_state *state, virDomainPtr dom);
const char *path, const char *address,
unsigned int number);
+#define METADATA_VM_PARTITION_URI "http://ovirt.org/ovirtmap/tag/1.0"
+#define METADATA_VM_PARTITION_ELEMENT "tag"
+#define METADATA_VM_PARTITION_PREFIX "ovirtmap"
+
+#define BUFFER_MAX_LEN 256
+#define PARTITION_TAG_MAX_LEN 32
+
+struct lv_read_instance {
+ struct lv_read_state read_state;
+ char tag[PARTITION_TAG_MAX_LEN];
+ size_t id;
+};
+
+struct lv_user_data {
+ struct lv_read_instance inst;
+ user_data_t ud;
+};
+
+#define NR_INSTANCES_DEFAULT 1
+#define NR_INSTANCES_MAX 128
+static int nr_instances = NR_INSTANCES_DEFAULT;
+static struct lv_user_data lv_read_user_data[NR_INSTANCES_MAX];
+
/* HostnameFormat. */
#define HF_MAX_FIELDS 3
/* Time that we last refreshed. */
static time_t last_refresh = (time_t)0;
-static int refresh_lists(struct lv_read_state *state);
+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) \
submit(dom, type, devname, values, STATIC_ARRAY_SIZE(values));
} /* void submit_derive2 */
-static int lv_init(void) {
- if (virInitialize() != 0)
- return -1;
- else
- return 0;
+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) {
return 0;
}
+ if (strcasecmp(key, "Instances") == 0) {
+ char *eptr = NULL;
+ double val = strtod(value, &eptr);
+
+ if (*eptr != '\0') {
+ ERROR(PLUGIN_NAME " plugin: Invalid value for Instances = '%s'", value);
+ return 1;
+ }
+ if (val <= 0) {
+ ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
+ return 1;
+ }
+ if (val > NR_INSTANCES_MAX) {
+ ERROR(PLUGIN_NAME " plugin: Instances=%f > NR_INSTANCES_MAX=%i"
+ " use a lower setting or recompile the plugin.",
+ val, NR_INSTANCES_MAX);
+ return 1;
+ }
+
+ nr_instances = (int)val;
+ DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
+ return 0;
+ }
+
/* Unrecognised option. */
return -1;
}
-static int lv_read(void) {
- time_t t;
- struct lv_read_state *state = &read_state;
-
+static int lv_connect(void) {
if (conn == NULL) {
/* `conn_string == NULL' is acceptable. */
conn = virConnectOpenReadOnly(conn_string);
}
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;
+ struct lv_read_state *state = NULL;
+
+ if (ud->data == NULL) {
+ ERROR(PLUGIN_NAME " plugin: NULL userdata");
+ return -1;
+ }
+
+ inst = ud->data;
+ state = &inst->read_state;
+
+ if (inst->id == 0) {
+ if (lv_connect() < 0)
+ return -1;
+ }
time(&t);
/* Need to refresh domain or device lists? */
if ((last_refresh == (time_t)0) ||
((interval > 0) && ((last_refresh + interval) <= t))) {
- if (refresh_lists(state) != 0) {
- if (conn != NULL)
- virConnectClose(conn);
- conn = NULL;
+ if (refresh_lists(inst) != 0) {
+ if (inst->id == 0)
+ lv_disconnect();
return -1;
}
last_refresh = t;
/* 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);
-
- 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);
+ type_instance = strdup(bdev->path);
- 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) */
return 0;
}
-static int refresh_lists(struct lv_read_state *state) {
+static int lv_init_instance(size_t i, plugin_read_cb callback) {
+ struct lv_user_data *lv_ud = &(lv_read_user_data[i]);
+ struct lv_read_instance *inst = &(lv_ud->inst);
+
+ memset(lv_ud, 0, sizeof(*lv_ud));
+
+ ssnprintf(inst->tag, sizeof(inst->tag), "%s-%zu", PLUGIN_NAME, i);
+ inst->id = i;
+
+ user_data_t *ud = &(lv_ud->ud);
+ ud->data = inst;
+ ud->free_func = NULL;
+
+ INFO(PLUGIN_NAME " plugin: reader %s initialized", inst->tag);
+ return plugin_register_complex_read(NULL, inst->tag, callback, 0, ud);
+}
+
+static void lv_clean_read_state(struct lv_read_state *state) {
+ free_block_devices(state);
+ free_interface_devices(state);
+ free_domains(state);
+}
+
+static void lv_fini_instance(size_t i) {
+ struct lv_read_instance *inst = &(lv_read_user_data[i].inst);
+ struct lv_read_state *state = &(inst->read_state);
+
+ lv_clean_read_state(state);
+ INFO(PLUGIN_NAME " plugin: reader %s finalized", inst->tag);
+}
+
+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)
+ lv_init_instance(i, lv_read);
+
+ 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 ret = -1;
+ int err;
+
+ err = xmlXPathRegisterNs(xpath_ctx,
+ (const xmlChar *)METADATA_VM_PARTITION_PREFIX,
+ (const xmlChar *)METADATA_VM_PARTITION_URI);
+ if (err) {
+ ERROR(PLUGIN_NAME " plugin: xmlXpathRegisterNs(%s, %s) failed on domain %s",
+ METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_URI, dom_name);
+ goto done;
+ }
+
+ ssnprintf(xpath_str, sizeof(xpath_str), "/domain/metadata/%s:%s/text()",
+ METADATA_VM_PARTITION_PREFIX, METADATA_VM_PARTITION_ELEMENT);
+ xpath_obj = xmlXPathEvalExpression((xmlChar *)xpath_str, xpath_ctx);
+ if (xpath_obj == NULL) {
+ ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed on domain %s",
+ xpath_str, dom_name);
+ goto done;
+ }
+
+ if (xpath_obj->type != XPATH_NODESET) {
+ ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d "
+ "(wanted %d) on domain %s",
+ xpath_str, xpath_obj->type, XPATH_NODESET, dom_name);
+ goto done;
+ }
+
+ /*
+ * from now on there is no real error, it's ok if a domain
+ * doesn't have the metadata partition tag.
+ */
+ 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",
+ xpath_str,
+ (xpath_obj->nodesetval == NULL) ? 0 : xpath_obj->nodesetval->nodeNr,
+ dom_name);
+ } else {
+ xml_node = xpath_obj->nodesetval->nodeTab[0];
+ sstrncpy(dom_tag, (const char *)xml_node->content, PARTITION_TAG_MAX_LEN);
+ }
+
+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 ret;
+}
+
+static int is_known_tag(const char *dom_tag) {
+ for (int i = 0; i < nr_instances; ++i)
+ if (!strcmp(dom_tag, lv_read_user_data[i].inst.tag))
+ return 1;
+ return 0;
+}
+
+static int lv_instance_include_domain(struct lv_read_instance *inst,
+ const char *dom_name,
+ const char *dom_tag) {
+ if ((dom_tag[0] != '\0') && (strcmp(dom_tag, inst->tag) == 0))
+ return 1;
+
+ /* instance#0 will always be there, so it is in charge of extra duties */
+ if (inst->id == 0) {
+ if (dom_tag[0] == '\0' || !is_known_tag(dom_tag)) {
+ DEBUG(PLUGIN_NAME " plugin#%s: refreshing domain %s "
+ "with unknown tag '%s'",
+ inst->tag, dom_name, dom_tag);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int refresh_lists(struct lv_read_instance *inst) {
+ struct lv_read_state *state = &inst->read_state;
int n;
n = virConnectNumOfDomains(conn);
return -1;
}
+ lv_clean_read_state(state);
+
if (n > 0) {
int *domids;
return -1;
}
- free_block_devices(state);
- free_interface_devices(state);
- free_domains(state);
-
/* Fetch each domain and add it to the list, unless ignore. */
for (int i = 0; i < n; ++i) {
virDomainPtr dom = NULL;
xmlDocPtr xml_doc = NULL;
xmlXPathContextPtr xpath_ctx = NULL;
xmlXPathObjectPtr xpath_obj = NULL;
+ char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
dom = virDomainLookupByID(conn, domids[i]);
if (dom == NULL) {
if (il_domains && ignorelist_match(il_domains, name) != 0)
goto cont;
- if (add_domain(state, dom) < 0) {
- ERROR(PLUGIN_NAME " plugin: malloc failed.");
- goto cont;
- }
-
/* Get a list of devices for this domain. */
xml = virDomainGetXMLDesc(dom, 0);
if (!xml) {
xpath_ctx = xmlXPathNewContext(xml_doc);
+ if (lv_domain_get_tag(xpath_ctx, name, tag) < 0) {
+ ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
+ goto cont;
+ }
+
+ if (!lv_instance_include_domain(inst, name, tag))
+ goto cont;
+
+ if (add_domain(state, dom) < 0) {
+ ERROR(PLUGIN_NAME " plugin: malloc failed.");
+ goto cont;
+ }
+
/* 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)
sfree(domids);
}
+ DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
+ " domains=%i block_devices=%i iface_devices=%i",
+ inst->tag, state->nr_domains, state->nr_block_devices,
+ state->nr_interface_devices);
+
return 0;
}
}
static int lv_shutdown(void) {
- struct lv_read_state *state = &read_state;
- free_block_devices(state);
- free_interface_devices(state);
- free_domains(state);
+ for (int i = 0; i < nr_instances; ++i) {
+ lv_fini_instance(i);
+ }
- if (conn != NULL)
- virConnectClose(conn);
- conn = NULL;
+ lv_disconnect();
ignorelist_free(il_domains);
il_domains = NULL;
void module_register(void) {
plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
plugin_register_init(PLUGIN_NAME, lv_init);
- plugin_register_read(PLUGIN_NAME, lv_read);
plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
}
-
-/*
- * vim: shiftwidth=4 tabstop=8 softtabstop=4 expandtab fdm=marker
- */