From 9823dfeff7e73625977e47f97b2d3a451b306e25 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Sat, 12 Dec 2015 18:05:28 +0000 Subject: [PATCH] Add df plugin The df plugin collects data about the filesystem and inode usage. Signed-off-by: Michael Tremer --- Makefile.am | 5 +- configure.ac | 4 + src/_collectymodule.c | 52 ++++++++ src/collecty/__init__.py | 1 + src/collecty/colours.py | 23 ++++ src/collecty/constants.py | 1 + src/collecty/plugins/__init__.py | 1 + src/collecty/plugins/df.py | 205 +++++++++++++++++++++++++++++++ src/collecty/util.py | 50 ++++++++ 9 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 src/collecty/colours.py create mode 100644 src/collecty/plugins/df.py create mode 100644 src/collecty/util.py diff --git a/Makefile.am b/Makefile.am index 7e406b6..70fa8d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/configure.ac b/configure.ac index 9b540fd..85a07fa 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/src/_collectymodule.c b/src/_collectymodule.c index d42f8fd..ff00377 100644 --- a/src/_collectymodule.c +++ b/src/_collectymodule.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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}, diff --git a/src/collecty/__init__.py b/src/collecty/__init__.py index a1a60f0..fe9261c 100644 --- a/src/collecty/__init__.py +++ b/src/collecty/__init__.py @@ -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 index 0000000..35dc762 --- /dev/null +++ b/src/collecty/colours.py @@ -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 . # +# # +############################################################################### + +LIGHT_GREEN = "#00CC33" +LIGHT_RED = "#CC0033" diff --git a/src/collecty/constants.py b/src/collecty/constants.py index 34e6b66..e07ce4c 100644 --- a/src/collecty/constants.py +++ b/src/collecty/constants.py @@ -19,6 +19,7 @@ # # ############################################################################### +from .colours import * from .i18n import _ from .__version__ import * diff --git a/src/collecty/plugins/__init__.py b/src/collecty/plugins/__init__.py index 19d889a..bc90617 100644 --- a/src/collecty/plugins/__init__.py +++ b/src/collecty/plugins/__init__.py @@ -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 index 0000000..aeac588 --- /dev/null +++ b/src/collecty/plugins/df.py @@ -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 . # +# # +############################################################################### + +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 index 0000000..00aa85f --- /dev/null +++ b/src/collecty/util.py @@ -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 . # +# # +############################################################################### + +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) -- 2.39.2