Add graphs for IP fragmentation
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Dec 2015 22:26:22 +0000 (22:26 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Dec 2015 22:26:22 +0000 (22:26 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
po/POTFILES.in
src/collecty/colours.py
src/collecty/plugins/__init__.py
src/collecty/plugins/interface.py
src/collecty/plugins/ipfrag.py [new file with mode: 0644]
src/collecty/util.py

index 70fa8d4..8142619 100644 (file)
@@ -97,6 +97,7 @@ collectyplugins_PYTHON = \
        src/collecty/plugins/__init__.py \
        src/collecty/plugins/interface.py \
        src/collecty/plugins/interrupts.py \
+       src/collecty/plugins/ipfrag.py \
        src/collecty/plugins/latency.py \
        src/collecty/plugins/loadavg.py \
        src/collecty/plugins/memory.py \
index 0f3c33e..15ca745 100644 (file)
@@ -9,11 +9,13 @@ src/collecty/plugins/base.py
 src/collecty/plugins/conntrack.py
 src/collecty/plugins/contextswitches.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
 src/collecty/plugins/interface.py
 src/collecty/plugins/interrupts.py
+src/collecty/plugins/ipfrag.py
 src/collecty/plugins/latency.py
 src/collecty/plugins/loadavg.py
 src/collecty/plugins/memory.py
index 35dc762..6aaa207 100644 (file)
 #                                                                             #
 ###############################################################################
 
-LIGHT_GREEN = "#00CC33"
-LIGHT_RED   = "#CC0033"
+BLACK        = "#000000"
+WHITE        = "#FFFFFF"
+
+YELLOW       = "#FFFF33"
+LIGHT_YELLOW = "#FFFF66"
+
+LIGHT_GREEN  = "#00CC33"
+LIGHT_RED    = "#CC0033"
+
+COLOUR_OK    = LIGHT_GREEN
+COLOUR_ERROR = LIGHT_RED
+COLOUR_WARN  = LIGHT_YELLOW
index bc90617..9f583a9 100644 (file)
@@ -30,6 +30,7 @@ from . import disk
 from . import entropy
 from . import interface
 from . import interrupts
+from . import ipfrag
 from . import latency
 from . import loadavg
 from . import processor
index 135a59c..f0dcef5 100644 (file)
 #                                                                             #
 ###############################################################################
 
-
-
 import os
 
 from . import base
+from .. import util
 
 from ..i18n import _
 
-SYS_CLASS_NET = "/sys/class/net"
-
 COLOUR_RX = "B22222"
 COLOUR_RX_AREA = "%sAA" % COLOUR_RX
 COLOUR_TX = "228B22"
@@ -243,7 +240,7 @@ class InterfaceObject(base.Object):
                return self.interface
 
        def collect(self):
-               interface_path = os.path.join(SYS_CLASS_NET, self.interface)
+               interface_path = os.path.join("/sys/class/net", self.interface)
 
                # Check if the interface exists.
                if not os.path.exists(interface_path):
@@ -295,19 +292,7 @@ class InterfacePlugin(base.Plugin):
 
        interval = 30
 
-       def get_interfaces(self):
-               for interface in os.listdir(SYS_CLASS_NET):
-                       # Skip some unwanted interfaces.
-                       if interface == "lo" or interface.startswith("mon."):
-                               continue
-
-                       path = os.path.join(SYS_CLASS_NET, interface)
-                       if not os.path.isdir(path):
-                               continue
-
-                       yield interface
-
        @property
        def objects(self):
-               for interface in self.get_interfaces():
+               for interface in util.get_network_interfaces():
                        yield InterfaceObject(self, interface=interface)
diff --git a/src/collecty/plugins/ipfrag.py b/src/collecty/plugins/ipfrag.py
new file mode 100644 (file)
index 0000000..767276a
--- /dev/null
@@ -0,0 +1,301 @@
+#!/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/>.       #
+#                                                                             #
+###############################################################################
+
+import os
+
+from .. import util
+from . import base
+
+from ..constants import *
+from ..i18n import _
+
+class GraphTemplateIPv6Fragmentation(base.GraphTemplate):
+       name = "ipv6-fragmentation"
+
+       @property
+       def rrd_graph(self):
+               _ = self.locale.translate
+
+               return [
+                       "DEF:frags_oks=%(file)s:ip6_frags_oks:AVERAGE",
+                       "DEF:frags_fails=%(file)s:ip6_frags_fails:AVERAGE",
+                       "DEF:reasm_timeout=%(file)s:ip6_reasm_timeout:AVERAGE",
+                       "DEF:reasm_oks=%(file)s:ip6_reasm_oks:AVERAGE",
+                       "DEF:reasm_fails=%(file)s:ip6_reasm_fails:AVERAGE",
+
+                       "CDEF:reasm_real_fails=reasm_fails,reasm_timeout,-",
+
+                       "VDEF:frags_oks_cur=frags_oks,LAST",
+                       "VDEF:frags_oks_avg=frags_oks,AVERAGE",
+                       "VDEF:frags_oks_max=frags_oks,MAXIMUM",
+                       "VDEF:frags_oks_min=frags_oks,MINIMUM",
+
+                       "VDEF:frags_fails_cur=frags_fails,LAST",
+                       "VDEF:frags_fails_avg=frags_fails,AVERAGE",
+                       "VDEF:frags_fails_max=frags_fails,MAXIMUM",
+                       "VDEF:frags_fails_min=frags_fails,MINIMUM",
+
+                       "VDEF:reasm_oks_cur=reasm_oks,LAST",
+                       "VDEF:reasm_oks_avg=reasm_oks,AVERAGE",
+                       "VDEF:reasm_oks_max=reasm_oks,MAXIMUM",
+                       "VDEF:reasm_oks_min=reasm_oks,MINIMUM",
+
+                       "VDEF:reasm_fails_cur=reasm_real_fails,LAST",
+                       "VDEF:reasm_fails_avg=reasm_real_fails,AVERAGE",
+                       "VDEF:reasm_fails_max=reasm_real_fails,MAXIMUM",
+                       "VDEF:reasm_fails_min=reasm_real_fails,MINIMUM",
+
+                       "VDEF:reasm_timeout_cur=reasm_timeout,LAST",
+                       "VDEF:reasm_timeout_avg=reasm_timeout,AVERAGE",
+                       "VDEF:reasm_timeout_max=reasm_timeout,MAXIMUM",
+                       "VDEF:reasm_timeout_min=reasm_timeout,MINIMUM",
+
+                       # Reassembly
+                       "AREA:reasm_real_fails%s:%-24s" % \
+                               (util.lighten(COLOUR_ERROR), _("Failed Reassemblies")),
+                       "GPRINT:reasm_fails_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:reasm_fails_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:reasm_fails_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:reasm_fails_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "AREA:reasm_timeout%s:%-24s:STACK" % \
+                               (util.lighten(COLOUR_WARN), _("Reassembly Timeouts")),
+                       "GPRINT:reasm_timeout_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:reasm_timeout_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:reasm_timeout_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:reasm_timeout_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "LINE2:reasm_oks%s:%-24s" % (BLACK, _("Successful Reassemblies")),
+                       "GPRINT:reasm_oks_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:reasm_oks_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:reasm_oks_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:reasm_oks_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "COMMENT: \\n", # empty line
+
+                       # Fragmentation
+                       "LINE2:frags_fails%s:%-24s" % (COLOUR_ERROR, _("Failed Fragmentations")),
+                       "GPRINT:frags_fails_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:frags_fails_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:frags_fails_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:frags_fails_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "LINE2:frags_oks%s:%-24s" % (COLOUR_OK, _("Fragmented Packets")),
+                       "GPRINT:frags_oks_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:frags_oks_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:frags_oks_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:frags_oks_min:%s %%5.0lf%%s\\j" % _("Min"),
+               ]
+
+       @property
+       def graph_title(self):
+               _ = self.locale.translate
+
+               if self.object.interface:
+                       return _("IPv6 Fragmentation on %s") % self.object.interface
+
+               return _("IPv6 Fragmentation")
+
+       @property
+       def graph_vertical_label(self):
+               _ = self.locale.translate
+               return _("Packets/s")
+
+       @property
+       def rrd_graph_args(self):
+               return [
+                       "--base", "1000",
+                       "--legend-direction=bottomup",
+               ]
+
+
+class GraphTemplateIPv4Fragmentation(base.GraphTemplate):
+       name = "ipv4-fragmentation"
+
+       @property
+       def rrd_graph(self):
+               _ = self.locale.translate
+
+               return [
+                       "DEF:frags_oks=%(file)s:ip4_frags_oks:AVERAGE",
+                       "DEF:frags_fails=%(file)s:ip4_frags_fails:AVERAGE",
+                       "DEF:reasm_timeout=%(file)s:ip4_reasm_timeout:AVERAGE",
+                       "DEF:reasm_oks=%(file)s:ip4_reasm_oks:AVERAGE",
+                       "DEF:reasm_fails=%(file)s:ip4_reasm_fails:AVERAGE",
+
+                       "CDEF:reasm_real_fails=reasm_fails,reasm_timeout,-",
+
+                       "VDEF:frags_oks_cur=frags_oks,LAST",
+                       "VDEF:frags_oks_avg=frags_oks,AVERAGE",
+                       "VDEF:frags_oks_max=frags_oks,MAXIMUM",
+                       "VDEF:frags_oks_min=frags_oks,MINIMUM",
+
+                       "VDEF:frags_fails_cur=frags_fails,LAST",
+                       "VDEF:frags_fails_avg=frags_fails,AVERAGE",
+                       "VDEF:frags_fails_max=frags_fails,MAXIMUM",
+                       "VDEF:frags_fails_min=frags_fails,MINIMUM",
+
+                       "VDEF:reasm_oks_cur=reasm_oks,LAST",
+                       "VDEF:reasm_oks_avg=reasm_oks,AVERAGE",
+                       "VDEF:reasm_oks_max=reasm_oks,MAXIMUM",
+                       "VDEF:reasm_oks_min=reasm_oks,MINIMUM",
+
+                       "VDEF:reasm_fails_cur=reasm_real_fails,LAST",
+                       "VDEF:reasm_fails_avg=reasm_real_fails,AVERAGE",
+                       "VDEF:reasm_fails_max=reasm_real_fails,MAXIMUM",
+                       "VDEF:reasm_fails_min=reasm_real_fails,MINIMUM",
+
+                       "VDEF:reasm_timeout_cur=reasm_timeout,LAST",
+                       "VDEF:reasm_timeout_avg=reasm_timeout,AVERAGE",
+                       "VDEF:reasm_timeout_max=reasm_timeout,MAXIMUM",
+                       "VDEF:reasm_timeout_min=reasm_timeout,MINIMUM",
+
+                       # Reassembly
+                       "AREA:reasm_real_fails%s:%-24s" % \
+                               (util.lighten(COLOUR_ERROR), _("Failed Reassemblies")),
+                       "GPRINT:reasm_fails_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:reasm_fails_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:reasm_fails_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:reasm_fails_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "AREA:reasm_timeout%s:%-24s:STACK" % \
+                               (util.lighten(COLOUR_WARN), _("Reassembly Timeouts")),
+                       "GPRINT:reasm_timeout_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:reasm_timeout_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:reasm_timeout_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:reasm_timeout_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "LINE2:reasm_oks%s:%-24s" % (BLACK, _("Successful Reassemblies")),
+                       "GPRINT:reasm_oks_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:reasm_oks_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:reasm_oks_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:reasm_oks_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "COMMENT: \\n", # empty line
+
+                       # Fragmentation
+                       "LINE2:frags_fails%s:%-24s" % (COLOUR_ERROR, _("Failed Fragmentations")),
+                       "GPRINT:frags_fails_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:frags_fails_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:frags_fails_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:frags_fails_min:%s %%5.0lf%%s\\j" % _("Min"),
+
+                       "LINE2:frags_oks%s:%-24s" % (COLOUR_OK, _("Fragmented Packets")),
+                       "GPRINT:frags_oks_cur:%s %%5.0lf%%s" % _("Now"),
+                       "GPRINT:frags_oks_avg:%s %%5.0lf%%s" % _("Avg"),
+                       "GPRINT:frags_oks_max:%s %%5.0lf%%s" % _("Max"),
+                       "GPRINT:frags_oks_min:%s %%5.0lf%%s\\j" % _("Min"),
+               ]
+
+       @property
+       def graph_title(self):
+               _ = self.locale.translate
+
+               if self.object.interface:
+                       return _("IPv4 Fragmentation on %s") % self.object.interface
+
+               return _("IPv4 Fragmentation")
+
+       @property
+       def graph_vertical_label(self):
+               _ = self.locale.translate
+               return _("Packets/s")
+
+       @property
+       def rrd_graph_args(self):
+               return [
+                       "--base", "1000",
+                       "--legend-direction=bottomup",
+               ]
+
+
+class IPFragmentationObject(base.Object):
+       rrd_schema = [
+               "DS:ip6_frags_oks:DERIVE:0:U",
+               "DS:ip6_frags_fails:DERIVE:0:U",
+               "DS:ip6_frags_creates:DERIVE:0:U",
+               "DS:ip6_reasm_timeout:DERIVE:0:U",
+               "DS:ip6_reasm_reqds:DERIVE:0:U",
+               "DS:ip6_reasm_oks:DERIVE:0:U",
+               "DS:ip6_reasm_fails:DERIVE:0:U",
+               "DS:ip4_frags_oks:DERIVE:0:U",
+               "DS:ip4_frags_fails:DERIVE:0:U",
+               "DS:ip4_frags_creates:DERIVE:0:U",
+               "DS:ip4_reasm_timeout:DERIVE:0:U",
+               "DS:ip4_reasm_reqds:DERIVE:0:U",
+               "DS:ip4_reasm_oks:DERIVE:0:U",
+               "DS:ip4_reasm_fails:DERIVE:0:U",
+       ]
+
+       def __repr__(self):
+               if self.interface:
+                       return "<%s %s>" % (self.__class__.__name__, self.interface)
+
+               return "<%s>" % self.__class__.__name__
+
+       def init(self, interface=None):
+               self.interface = interface
+
+       @property
+       def id(self):
+               return self.interface or "default"
+
+       def collect(self):
+               p = util.ProcNetSnmpParser(self.interface)
+
+               # Description in RFC2465
+               results = [
+                       p.get("Ip6", "FragOKs"),
+                       p.get("Ip6", "FragFails"),
+                       p.get("Ip6", "FragCreates"),
+                       p.get("Ip6", "ReasmTimeout"),
+                       p.get("Ip6", "ReasmReqds"),
+                       p.get("Ip6", "ReasmOKs"),
+                       p.get("Ip6", "ReasmFails"),
+                       p.get("Ip", "FragOKs"),
+                       p.get("Ip", "FragFails"),
+                       p.get("Ip", "FragCreates"),
+                       p.get("Ip", "ReasmTimeout"),
+                       p.get("Ip", "ReasmReqds"),
+                       p.get("Ip", "ReasmOKs"),
+                       p.get("Ip", "ReasmFails"),
+               ]
+
+               return results
+
+
+class IPFragmentationPlugin(base.Plugin):
+       name = "ip-fragmentation"
+       description = "IP Fragmentation Plugin"
+
+       templates = [
+               GraphTemplateIPv6Fragmentation,
+               GraphTemplateIPv4Fragmentation,
+       ]
+
+       @property
+       def objects(self):
+               # Overall statistics
+               yield IPFragmentationObject(self)
+
+               # Stats per interface
+               for interface in util.get_network_interfaces():
+                       yield IPFragmentationObject(self, interface)
index 00aa85f..fdde126 100644 (file)
 #                                                                             #
 ###############################################################################
 
+import os
+
+import logging
+log = logging.getLogger("collecty.util")
+log.propagate = 1
+
+from .constants import *
+
 def __add_colour(colour, amount):
        colour = colour.strip("#")
 
@@ -48,3 +56,94 @@ def darken(colour, scale=0.1):
                and darkens the colour.
        """
        return __add_colour(colour, 0xff * -scale)
+
+def get_network_interfaces():
+       """
+               Returns all real network interfaces
+       """
+       for interface in os.listdir("/sys/class/net"):
+               # Skip some unwanted interfaces.
+               if interface == "lo" or interface.startswith("mon."):
+                       continue
+
+               path = os.path.join("/sys/class/net", interface)
+               if not os.path.isdir(path):
+                       continue
+
+               yield interface
+
+class ProcNetSnmpParser(object):
+       """
+               This class parses /proc/net/snmp{,6} and allows
+               easy access to the values.
+       """
+       def __init__(self, intf=None):
+               self.intf = intf
+               self._data = {}
+
+               if not self.intf:
+                       self._data.update(self._parse())
+
+               self._data.update(self._parse6())
+
+       def _parse(self):
+               res = {}
+
+               with open("/proc/net/snmp") as f:
+                       keys = {}
+
+                       for line in f.readlines():
+                               line = line.strip()
+
+                               # Stop after an empty line
+                               if not line:
+                                       break
+
+                               type, values = line.split(": ", 1)
+
+                               # Check if the keys are already known
+                               if type in keys:
+                                       values = (int(v) for v in values.split())
+                                       res[type] = dict(zip(keys[type], values))
+
+                               # Otherwise remember the keys
+                               else:
+                                       keys[type] = values.split()
+
+               return res
+
+       def _parse6(self):
+               res = {}
+
+               fn = "/proc/net/snmp6"
+               if self.intf:
+                       fn = os.path.join("/proc/net/dev_snmp6", self.intf)
+
+               with open(fn) as f:
+                       for line in f.readlines():
+                               key, val = line.split()
+
+                               try:
+                                       type, key = key.split("6", 1)
+                               except ValueError:
+                                       continue
+
+                               type += "6"
+                               val = int(val)
+
+                               try:
+                                       res[type][key] = val
+                               except KeyError:
+                                       res[type] = { key : val }
+
+               return res
+
+       def get(self, proto, key):
+               """
+                       Retrieves a value from the internally
+                       parse dictionary read from /proc/net/snmp.
+               """
+               try:
+                       return self._data[proto][key]
+               except KeyError:
+                       pass