lvm: Report used/free space for thin pools and thick snapshots
authorBenjamin Gilbert <bgilbert@cs.cmu.edu>
Wed, 16 Apr 2014 19:00:11 +0000 (15:00 -0400)
committerBenjamin Gilbert <bgilbert@cs.cmu.edu>
Wed, 16 Apr 2014 19:36:44 +0000 (15:36 -0400)
Thin pools and traditional snapshot volumes fill up as I/O is performed
to their associated thin volumes or origins.  When they run out of free
space, things break, so it's useful to monitor their disk utilization.

Add new plugin instances for thin pool data LVs, thin pool metadata LVs,
and thick snapshot LVs, each with "used" and "free" type instances.

AUTHORS
src/lvm.c

diff --git a/AUTHORS b/AUTHORS
index 31d132f..fa55cc0 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -47,6 +47,9 @@ AurĂ©lien Reynaud <collectd at wattapower.net>
  - LPAR plugin.
  - Various fixes for AIX, HP-UX and Solaris.
 
+Benjamin Gilbert <bgilbert at cs.cmu.edu>
+ - Improvements to the LVM plugin.
+
 Bert Vermeulen <bert at biot.com>
  - sigrok plugin
 
index 1677f05..97d8017 100644 (file)
--- a/src/lvm.c
+++ b/src/lvm.c
@@ -1,6 +1,7 @@
 /**
  * collectd - src/lvm.c
  * Copyright (C) 2013       Chad Malfait
+ * Copyright (C) 2014       Carnegie Mellon University
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -17,6 +18,7 @@
  *
  * Authors:
  *   Chad Malfait <malfaitc at yahoo.com>
+ *   Benjamin Gilbert <bgilbert at cs.cmu.edu>
  **/
 
 #include <lvm2app.h>
 #include "common.h"
 #include "plugin.h"
 
+#define NO_VALUE UINT64_MAX
+#define PERCENT_SCALE_FACTOR 1e-8
+
+static uint64_t get_lv_property_int(lv_t lv, char const *property)
+{
+    lvm_property_value_t v;
+
+    v = lvm_lv_get_property(lv, property);
+    if (!v.is_valid || !v.is_integer)
+        return NO_VALUE;
+    /* May be NO_VALUE if @property does not apply to this LV */
+    return v.value.integer;
+}
+
+static char const *get_lv_property_string(lv_t lv, char const *property)
+{
+    lvm_property_value_t v;
+
+    v = lvm_lv_get_property(lv, property);
+    if (!v.is_valid || !v.is_string)
+        return NULL;
+    return v.value.string;
+}
+
 static void lvm_submit (char const *plugin_instance, char const *type_instance,
         uint64_t ivalue)
 {
@@ -45,26 +71,76 @@ static void lvm_submit (char const *plugin_instance, char const *type_instance,
     plugin_dispatch_values (&vl);
 }
 
+static void report_lv_utilization(lv_t lv, char const *vg_name,
+        char const *lv_name, uint64_t lv_size,
+        char const *used_percent_property)
+{
+    uint64_t used_percent_unscaled;
+    uint64_t used_bytes;
+    char plugin_instance[DATA_MAX_NAME_LEN];
+
+    used_percent_unscaled = get_lv_property_int(lv, used_percent_property);
+    if (used_percent_unscaled == NO_VALUE)
+        return;
+    used_bytes = lv_size * (used_percent_unscaled * PERCENT_SCALE_FACTOR);
+
+    ssnprintf(plugin_instance, sizeof(plugin_instance), "%s-%s",
+            vg_name, lv_name);
+    lvm_submit(plugin_instance, "used", used_bytes);
+    lvm_submit(plugin_instance, "free", lv_size - used_bytes);
+}
+
+static void report_thin_pool_utilization(lv_t lv, char const *vg_name,
+        uint64_t lv_size)
+{
+    char const *data_lv;
+    char const *metadata_lv;
+    uint64_t metadata_size;
+
+    data_lv = get_lv_property_string(lv, "data_lv");
+    metadata_lv = get_lv_property_string(lv, "metadata_lv");
+    metadata_size = get_lv_property_int(lv, "lv_metadata_size");
+    if (data_lv == NULL || metadata_lv == NULL || metadata_size == NO_VALUE)
+        return;
+
+    report_lv_utilization(lv, vg_name, data_lv, lv_size, "data_percent");
+    report_lv_utilization(lv, vg_name, metadata_lv, metadata_size,
+            "metadata_percent");
+}
+
 static void vg_read(vg_t vg, char const *vg_name)
 {
     struct dm_list *lvs;
     struct lvm_lv_list *lvl;
+    char const *name;
     char const *attrs;
+    uint64_t size;
 
     lvm_submit (vg_name, "free", lvm_vg_get_free_size(vg));
 
     lvs = lvm_vg_list_lvs(vg);
     dm_list_iterate_items(lvl, lvs) {
+        name = lvm_lv_get_name(lvl->lv);
         attrs = lvm_lv_get_attr(lvl->lv);
-        if (attrs == NULL)
+        size = lvm_lv_get_size(lvl->lv);
+        if (name == NULL || attrs == NULL || size == NO_VALUE)
             continue;
+
         /* Condition on volume type.  We want the reported sizes in the
            volume group to sum to the size of the volume group, so we ignore
            virtual volumes.  */
         switch (attrs[0]) {
+            case 's':
+            case 'S':
+                /* Snapshot.  Also report used/free space. */
+                report_lv_utilization(lvl->lv, vg_name, name, size,
+                        "data_percent");
+                break;
             case 't':
                 /* Thin pool virtual volume.  We report the underlying data
-                   and metadata volumes, not this one.  Ignore. */
+                   and metadata volumes, not this one.  Report used/free
+                   space, then ignore. */
+                report_thin_pool_utilization(lvl->lv, vg_name, size);
                 continue;
             case 'v':
                 /* Virtual volume.  Ignore. */
@@ -73,7 +149,7 @@ static void vg_read(vg_t vg, char const *vg_name)
                 /* Thin volume or thin snapshot.  Ignore. */
                 continue;
         }
-        lvm_submit(vg_name, lvm_lv_get_name(lvl->lv), lvm_lv_get_size(lvl->lv));
+        lvm_submit(vg_name, name, size);
     }
 }