From: Michael Tremer Date: Sat, 12 Dec 2015 22:26:22 +0000 (+0000) Subject: Add graphs for IP fragmentation X-Git-Tag: 004~2 X-Git-Url: http://git.ipfire.org/?p=collecty.git;a=commitdiff_plain;h=29455c5f73dbed2c753d1503abef35d7a88f3171 Add graphs for IP fragmentation Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 70fa8d4..8142619 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/po/POTFILES.in b/po/POTFILES.in index 0f3c33e..15ca745 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -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 diff --git a/src/collecty/colours.py b/src/collecty/colours.py index 35dc762..6aaa207 100644 --- a/src/collecty/colours.py +++ b/src/collecty/colours.py @@ -19,5 +19,15 @@ # # ############################################################################### -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 diff --git a/src/collecty/plugins/__init__.py b/src/collecty/plugins/__init__.py index bc90617..9f583a9 100644 --- a/src/collecty/plugins/__init__.py +++ b/src/collecty/plugins/__init__.py @@ -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 diff --git a/src/collecty/plugins/interface.py b/src/collecty/plugins/interface.py index 135a59c..f0dcef5 100644 --- a/src/collecty/plugins/interface.py +++ b/src/collecty/plugins/interface.py @@ -19,16 +19,13 @@ # # ############################################################################### - - 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 index 0000000..767276a --- /dev/null +++ b/src/collecty/plugins/ipfrag.py @@ -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 . # +# # +############################################################################### + +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) diff --git a/src/collecty/util.py b/src/collecty/util.py index 00aa85f..fdde126 100644 --- a/src/collecty/util.py +++ b/src/collecty/util.py @@ -19,6 +19,14 @@ # # ############################################################################### +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