Add df plugin
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Dec 2015 18:05:28 +0000 (18:05 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Dec 2015 18:05:28 +0000 (18:05 +0000)
The df plugin collects data about the filesystem and
inode usage.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
configure.ac
src/_collectymodule.c
src/collecty/__init__.py
src/collecty/colours.py [new file with mode: 0644]
src/collecty/constants.py
src/collecty/plugins/__init__.py
src/collecty/plugins/df.py [new file with mode: 0644]
src/collecty/util.py [new file with mode: 0644]

index 7e406b6..70fa8d4 100644 (file)
@@ -75,12 +75,14 @@ collecty_PYTHON = \
        src/collecty/__version__.py \
        src/collecty/bus.py \
        src/collecty/client.py \
+       src/collecty/colours.py \
        src/collecty/constants.py \
        src/collecty/daemon.py \
        src/collecty/errors.py \
        src/collecty/i18n.py \
        src/collecty/locales.py \
-       src/collecty/logger.py
+       src/collecty/logger.py \
+       src/collecty/util.py
 
 collectydir = $(pythondir)/collecty
 
@@ -89,6 +91,7 @@ collectyplugins_PYTHON = \
        src/collecty/plugins/contextswitches.py \
        src/collecty/plugins/conntrack.py \
        src/collecty/plugins/cpufreq.py \
+       src/collecty/plugins/df.py \
        src/collecty/plugins/disk.py \
        src/collecty/plugins/entropy.py \
        src/collecty/plugins/__init__.py \
index 9b540fd..85a07fa 100644 (file)
@@ -59,6 +59,10 @@ AC_PROG_CC_C99
 AC_PROG_CC_C_O
 AC_PROG_GCC_TRADITIONAL
 
+AC_CHECK_HEADERS_ONCE([
+       mntent.h
+])
+
 AC_PATH_PROG([XSLTPROC], [xsltproc])
 
 PKG_CHECK_MODULES([OPING], [liboping])
index d42f8fd..ff00377 100644 (file)
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/hdreg.h>
+#include <mntent.h>
 #include <oping.h>
 #include <sensors/error.h>
 #include <sensors/sensors.h>
@@ -1044,8 +1045,59 @@ static PyObject* _collecty_get_detected_sensors(PyObject* o, PyObject* args) {
        return list;
 }
 
+static int _collecty_mountpoint_is_virtual(const struct mntent* mp) {
+       // Ignore all ramdisks
+       if (mp->mnt_fsname[0] != '/')
+               return 1;
+
+       // Ignore network mounts
+       if (hasmntopt(mp, "_netdev") != NULL)
+               return 1;
+
+       return 0;
+}
+
+static PyObject* _collecty_get_mountpoints() {
+       FILE* fp = setmntent(_PATH_MOUNTED, "r");
+       if (!fp)
+               return NULL;
+
+       PyObject* list = PyList_New(0);
+       int r = 0;
+
+       struct mntent* mountpoint = getmntent(fp);
+       while (mountpoint) {
+               if (!_collecty_mountpoint_is_virtual(mountpoint)) {
+                       // Create a tuple with the information of the mountpoint
+                       PyObject* mp = PyTuple_New(4);
+                       PyTuple_SET_ITEM(mp, 0, PyUnicode_FromString(mountpoint->mnt_fsname));
+                       PyTuple_SET_ITEM(mp, 1, PyUnicode_FromString(mountpoint->mnt_dir));
+                       PyTuple_SET_ITEM(mp, 2, PyUnicode_FromString(mountpoint->mnt_type));
+                       PyTuple_SET_ITEM(mp, 3, PyUnicode_FromString(mountpoint->mnt_opts));
+
+                       // Append the tuple to the list
+                       r = PyList_Append(list, mp);
+                       if (r)
+                               break;
+               }
+
+               // Move on to the next mountpoint
+               mountpoint = getmntent(fp);
+       }
+
+       endmntent(fp);
+
+       if (r) {
+               Py_DECREF(list);
+               return NULL;
+       }
+
+       return list;
+}
+
 static PyMethodDef collecty_module_methods[] = {
        {"get_detected_sensors", (PyCFunction)_collecty_get_detected_sensors, METH_VARARGS, NULL},
+       {"get_mountpoints", (PyCFunction)_collecty_get_mountpoints, METH_NOARGS, NULL},
        {"sensors_cleanup", (PyCFunction)_collecty_sensors_cleanup, METH_NOARGS, NULL},
        {"sensors_init", (PyCFunction)_collecty_sensors_init, METH_NOARGS, NULL},
        {NULL},
index a1a60f0..fe9261c 100644 (file)
@@ -24,3 +24,4 @@ from . import logger
 
 from .client import CollectyClient
 from .daemon import Collecty
+from . import util
diff --git a/src/collecty/colours.py b/src/collecty/colours.py
new file mode 100644 (file)
index 0000000..35dc762
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/python3
+###############################################################################
+#                                                                             #
+# collecty - A system statistics collection daemon for IPFire                 #
+# Copyright (C) 2015 IPFire development team                                  #
+#                                                                             #
+# 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 Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+LIGHT_GREEN = "#00CC33"
+LIGHT_RED   = "#CC0033"
index 34e6b66..e07ce4c 100644 (file)
@@ -19,6 +19,7 @@
 #                                                                             #
 ###############################################################################
 
+from .colours import *
 from .i18n import _
 
 from .__version__ import *
index 19d889a..bc90617 100644 (file)
@@ -25,6 +25,7 @@ from . import base
 from . import contextswitches
 from . import conntrack
 from . import cpufreq
+from . import df
 from . import disk
 from . import entropy
 from . import interface
diff --git a/src/collecty/plugins/df.py b/src/collecty/plugins/df.py
new file mode 100644 (file)
index 0000000..aeac588
--- /dev/null
@@ -0,0 +1,205 @@
+#!/usr/bin/python3
+###############################################################################
+#                                                                             #
+# collecty - A system statistics collection daemon for IPFire                 #
+# Copyright (C) 2012 IPFire development team                                  #
+#                                                                             #
+# 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 Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+from collecty import _collecty
+import os
+
+from ..constants import *
+from .. import util
+from . import base
+
+from ..i18n import _
+
+class GraphTemplateDiskUsage(base.GraphTemplate):
+       name = "disk-usage"
+       lower_limit = 0
+
+       @property
+       def rrd_graph(self):
+               _ = self.locale.translate
+
+               return [
+                       "DEF:used=%(file)s:used:AVERAGE",
+                       "VDEF:used_cur=used,LAST",
+                       "VDEF:used_min=used,MINIMUM",
+                       "VDEF:used_max=used,MAXIMUM",
+
+                       "DEF:free=%(file)s:free:AVERAGE",
+                       "VDEF:free_cur=free,LAST",
+                       "VDEF:free_min=free,MINIMUM",
+                       "VDEF:free_max=free,MAXIMUM",
+
+                       # Calculate the percentage of the currently used
+                       # space since this is helps the user very much to
+                       # judge
+                       "CDEF:percentage_used=100,used,*,used,free,+,/",
+                       "VDEF:percentage_used_now=percentage_used,LAST",
+                       "CDEF:percentage_left=100,percentage_used,-",
+                       "VDEF:percentage_left_now=percentage_left,LAST",
+
+                       # Area for the used space
+                       "AREA:used%s:%s" % (util.lighten(LIGHT_RED, .66), _("Used")),
+                       "GPRINT:percentage_used_now: (%6.2lf%%)",
+                       "GPRINT:used_cur:%12s\:" % _("Current") + " %9.2lf%s",
+                       "GPRINT:used_min:%12s\:" % _("Minimum") + " %9.2lf%s",
+                       "GPRINT:used_max:%12s\:" % _("Maximum") + " %9.2lf%s\\n",
+
+                       # Stacked area of unused space
+                       "AREA:free%s:%s:STACK" % (util.lighten(LIGHT_GREEN, .66), _("Free")),
+                       "GPRINT:percentage_left_now: (%6.2lf%%)",
+                       "GPRINT:free_cur:%12s\:" % _("Current") + " %9.2lf%s",
+                       "GPRINT:free_min:%12s\:" % _("Minimum") + " %9.2lf%s",
+                       "GPRINT:free_max:%12s\:" % _("Maximum") + " %9.2lf%s\\n",
+
+                       # Add contour lines for the areas
+                       "LINE:used%s" % LIGHT_RED,
+                       "LINE:free%s::STACK" % LIGHT_GREEN,
+               ]
+
+       @property
+       def graph_title(self):
+               _ = self.locale.translate
+               return _("Disk Usage of %s") % self.object.mountpoint
+
+       @property
+       def graph_vertical_label(self):
+               _ = self.locale.translate
+               return _("Bytes")
+
+
+class GraphTemplateInodeUsage(base.GraphTemplate):
+       name = "inode-usage"
+       lower_limit = 0
+
+       @property
+       def rrd_graph(self):
+               _ = self.locale.translate
+
+               rrd_graph = [
+                       "DEF:used=%(file)s:inodes_used:AVERAGE",
+                       "VDEF:used_cur=used,LAST",
+                       "VDEF:used_min=used,MINIMUM",
+                       "VDEF:used_max=used,MAXIMUM",
+
+                       "DEF:free=%(file)s:inodes_free:AVERAGE",
+                       "VDEF:free_cur=free,LAST",
+                       "VDEF:free_min=free,MINIMUM",
+                       "VDEF:free_max=free,MAXIMUM",
+
+                       # Calculate the percentage of the currently used
+                       # inodes since this is helps the user very much to
+                       # judge
+                       "CDEF:percentage_used=100,used,*,used,free,+,/",
+                       "VDEF:percentage_used_now=percentage_used,LAST",
+                       "CDEF:percentage_left=100,percentage_used,-",
+                       "VDEF:percentage_left_now=percentage_left,LAST",
+
+                       # Area for the used inodes
+                       "AREA:used%s:%s" % (util.lighten(LIGHT_RED, .66), _("Used")),
+                       "GPRINT:percentage_used_now: (%6.2lf%%)",
+                       "GPRINT:used_cur:%12s\:" % _("Current") + " %9.2lf%s",
+                       "GPRINT:used_min:%12s\:" % _("Minimum") + " %9.2lf%s",
+                       "GPRINT:used_max:%12s\:" % _("Maximum") + " %9.2lf%s\\n",
+
+                       # Stacked area of unused inodes
+                       "AREA:free%s:%s:STACK" % (util.lighten(LIGHT_GREEN, .66), _("Free")),
+                       "GPRINT:percentage_left_now: (%6.2lf%%)",
+                       "GPRINT:free_cur:%12s\:" % _("Current") + " %9.2lf%s",
+                       "GPRINT:free_min:%12s\:" % _("Minimum") + " %9.2lf%s",
+                       "GPRINT:free_max:%12s\:" % _("Maximum") + " %9.2lf%s\\n",
+
+                       # Add contour lines for the areas
+                       "LINE:used%s" % LIGHT_RED,
+                       "LINE:free%s::STACK" % LIGHT_GREEN,
+               ]
+
+               return rrd_graph
+
+       rrd_graph_args = [
+               "--base", "1000", # inodes
+       ]
+
+       @property
+       def graph_title(self):
+               _ = self.locale.translate
+               return _("Inode Usage of %s") % self.object.mountpoint
+
+       @property
+       def graph_vertical_label(self):
+               _ = self.locale.translate
+               return _("Inodes")
+
+
+class DiskUsageObject(base.Object):
+       rrd_schema = [
+               "DS:used:GAUGE:0:U",
+               "DS:free:GAUGE:0:U",
+               "DS:inodes_used:GAUGE:0:U",
+               "DS:inodes_free:GAUGE:0:U",
+       ]
+
+       def __repr__(self):
+               return "<%s %s>" % (self.__class__.__name__, self.mountpoint)
+
+       def init(self, mountpoint):
+               self.mountpoint = mountpoint
+
+       @property
+       def id(self):
+               mountpoint = self.mountpoint
+
+               if mountpoint.startswith("/"):
+                       mountpoint = mountpoint[1:]
+
+               if not mountpoint:
+                       return "root"
+
+               return mountpoint.replace("/", "-")
+
+       def collect(self):
+               stats = os.statvfs(self.mountpoint)
+
+               return (
+                       # used
+                       (stats.f_blocks * stats.f_frsize) - \
+                               (stats.f_bfree * stats.f_bsize),
+                       # free
+                       stats.f_bfree * stats.f_bsize,
+                       # inodes used
+                       stats.f_files - stats.f_ffree,
+                       # inodes free
+                       stats.f_ffree,
+               )
+
+
+class DiskUsagePlugin(base.Plugin):
+       name = "df"
+       description = "Disk Usage Plugin"
+
+       templates = [
+               GraphTemplateDiskUsage,
+               GraphTemplateInodeUsage,
+       ]
+
+       @property
+       def objects(self):
+               for dev, mnt, fs, opts in _collecty.get_mountpoints():
+                       yield DiskUsageObject(self, mnt)
diff --git a/src/collecty/util.py b/src/collecty/util.py
new file mode 100644 (file)
index 0000000..00aa85f
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/python3
+###############################################################################
+#                                                                             #
+# collecty - A system statistics collection daemon for IPFire                 #
+# Copyright (C) 2015 IPFire development team                                  #
+#                                                                             #
+# 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 Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+def __add_colour(colour, amount):
+       colour = colour.strip("#")
+
+       colour = (
+               int(colour[0:2], 16),
+               int(colour[2:4], 16),
+               int(colour[4:6], 16),
+       )
+
+       # Scale the colour
+       colour = (e + amount for e in colour)
+       colour = (max(e, 0) for e in colour)
+       colour = (min(e, 255) for e in colour)
+
+       return "#%02x%02x%02x" % tuple(colour)
+
+def lighten(colour, scale=0.1):
+       """
+               Takes a hexadecimal colour code
+               and brightens the colour.
+       """
+       return __add_colour(colour, 0xff * scale)
+
+def darken(colour, scale=0.1):
+       """
+               Takes a hexadecimal colour code
+               and darkens the colour.
+       """
+       return __add_colour(colour, 0xff * -scale)