]> git.ipfire.org Git - people/pmueller/ipfire-3.x.git/commitdiff
collecty: Update to version 004
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 15 Dec 2015 14:46:25 +0000 (14:46 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 15 Dec 2015 14:46:25 +0000 (14:46 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
collecty/collecty.nm
collecty/patches/0001-plugins-Automatically-replace-None-by-NaN.patch [deleted file]
collecty/patches/0002-latency-Rewrite-latency-module.patch [deleted file]

index 72ed7b105e72983ea5584d69ba4ad1307a25172c..2b30bd5e8e28299752a2dd4b080f695a342e53d3 100644 (file)
@@ -4,8 +4,8 @@
 ###############################################################################
 
 name       = collecty
-version    = 003
-release    = 4
+version    = 004
+release    = 1
 
 maintainer = Michael Tremer <michael.tremer@ipfire.org>
 groups     = System/Monitoring
diff --git a/collecty/patches/0001-plugins-Automatically-replace-None-by-NaN.patch b/collecty/patches/0001-plugins-Automatically-replace-None-by-NaN.patch
deleted file mode 100644 (file)
index 772c65d..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-From a9af411f0703eac939e0df5d5f75b46d35f531bc Mon Sep 17 00:00:00 2001
-From: Michael Tremer <michael.tremer@ipfire.org>
-Date: Mon, 29 Jun 2015 20:44:18 +0000
-Subject: [PATCH 1/2] plugins: Automatically replace None by NaN
-
-rrdtool uses NaN to represent no value. Python uses None.
-This patch automatically translates from None to NaN.
-
-Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
----
- src/collecty/plugins/base.py | 22 ++++++++++++++++++++--
- 1 file changed, 20 insertions(+), 2 deletions(-)
-
-diff --git a/src/collecty/plugins/base.py b/src/collecty/plugins/base.py
-index bed461f..cf9c3b4 100644
---- a/src/collecty/plugins/base.py
-+++ b/src/collecty/plugins/base.py
-@@ -147,8 +147,7 @@ class Plugin(object, metaclass=PluginRegistration):
-                       try:
-                               result = o.collect()
--                              if isinstance(result, tuple) or isinstance(result, list):
--                                      result = ":".join(("%s" % e for e in result))
-+                              result = self._format_result(result)
-                       except:
-                               self.log.warning(_("Unhandled exception in %s.collect()") % o, exc_info=True)
-                               continue
-@@ -170,6 +169,25 @@ class Plugin(object, metaclass=PluginRegistration):
-               if delay >= 60:
-                       self.log.warning(_("A worker thread was stalled for %.4fs") % delay)
-+      @staticmethod
-+      def _format_result(result):
-+              if not isinstance(result, tuple) and not isinstance(result, list):
-+                      return result
-+
-+              # Replace all Nones by NaN
-+              s = []
-+
-+              for e in result:
-+                      if e is None:
-+                              e = "NaN"
-+
-+                      # Format as string
-+                      e = "%s" % e
-+
-+                      s.append(e)
-+
-+              return ":".join(s)
-+
-       def get_object(self, id):
-               for object in self.objects:
-                       if not object.id == id:
--- 
-1.8.1
-
diff --git a/collecty/patches/0002-latency-Rewrite-latency-module.patch b/collecty/patches/0002-latency-Rewrite-latency-module.patch
deleted file mode 100644 (file)
index 837502b..0000000
+++ /dev/null
@@ -1,995 +0,0 @@
-From 63f9f8beed445a80dcb492570b105c5b50e65a59 Mon Sep 17 00:00:00 2001
-From: Michael Tremer <michael.tremer@ipfire.org>
-Date: Mon, 29 Jun 2015 20:49:02 +0000
-Subject: [PATCH 2/2] latency: Rewrite latency module
-
-This patch replaces the builtin python implementation
-that pinged hosts by a Python C module that uses liboping.
-
-liboping is able to ping IPv6 hosts as well and should
-implement the ICMP protocol more appropriately.
-
-The graph has been extended so that hosts will have a
-line for latency over IPv6 and IPv4 if available and
-the packet loss is merged from both protocols, too.
-
-Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
----
- Makefile.am                     |   5 +-
- configure.ac                    |   2 +
- po/POTFILES.in                  |   1 -
- src/_collectymodule.c           | 338 ++++++++++++++++++++++++++++++++++++++++
- src/collecty/ping.py            | 324 --------------------------------------
- src/collecty/plugins/latency.py | 151 +++++++++++-------
- 6 files changed, 437 insertions(+), 384 deletions(-)
- delete mode 100644 src/collecty/ping.py
-
-diff --git a/Makefile.am b/Makefile.am
-index fe00da7..0b7e299 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -79,8 +79,7 @@ collecty_PYTHON = \
-       src/collecty/daemon.py \
-       src/collecty/errors.py \
-       src/collecty/i18n.py \
--      src/collecty/logger.py \
--      src/collecty/ping.py
-+      src/collecty/logger.py
- collectydir = $(pythondir)/collecty
-@@ -109,6 +108,7 @@ _collecty_la_SOURCES = \
- _collecty_la_CFLAGS = \
-       $(AM_CFLAGS) \
-       $(LIBATASMART_CFLAGS) \
-+      $(OPING_CFLAGS) \
-       $(PYTHON_CFLAGS)
- _collecty_la_LDFLAGS = \
-@@ -119,6 +119,7 @@ _collecty_la_LDFLAGS = \
- _collecty_la_LIBADD = \
-       $(LIBATASMART_LIBS) \
-+      $(OPING_LIBS) \
-       $(PYTHON_LIBS) \
-       $(SENSORS_LIBS)
-diff --git a/configure.ac b/configure.ac
-index 59250ca..9b540fd 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -61,6 +61,8 @@ AC_PROG_GCC_TRADITIONAL
- AC_PATH_PROG([XSLTPROC], [xsltproc])
-+PKG_CHECK_MODULES([OPING], [liboping])
-+
- # Python
- AM_PATH_PYTHON([3.2])
- PKG_CHECK_MODULES([PYTHON], [python-${PYTHON_VERSION}])
-diff --git a/po/POTFILES.in b/po/POTFILES.in
-index f6aebb3..a96f7b2 100644
---- a/po/POTFILES.in
-+++ b/po/POTFILES.in
-@@ -5,7 +5,6 @@ src/collecty/daemon.py
- src/collecty/errors.py
- src/collecty/i18n.py
- src/collecty/__init__.py
--src/collecty/ping.py
- src/collecty/plugins/base.py
- src/collecty/plugins/conntrack.py
- src/collecty/plugins/cpu.py
-diff --git a/src/_collectymodule.c b/src/_collectymodule.c
-index 422c27d..c13ca69 100644
---- a/src/_collectymodule.c
-+++ b/src/_collectymodule.c
-@@ -22,15 +22,21 @@
- #include <errno.h>
- #include <fcntl.h>
- #include <linux/hdreg.h>
-+#include <oping.h>
- #include <sensors/error.h>
- #include <sensors/sensors.h>
- #include <stdbool.h>
- #include <string.h>
- #include <sys/ioctl.h>
-+#include <time.h>
- #define MODEL_SIZE  40
- #define SERIAL_SIZE 20
-+#define PING_HISTORY_SIZE 1024
-+#define PING_DEFAULT_COUNT 10
-+#define PING_DEFAULT_TIMEOUT 8
-+
- typedef struct {
-       PyObject_HEAD
-       char* path;
-@@ -313,6 +319,324 @@ static PyTypeObject BlockDeviceType = {
-       BlockDevice_new,                    /* tp_new */
- };
-+static PyObject* PyExc_PingError;
-+static PyObject* PyExc_PingAddHostError;
-+
-+typedef struct {
-+      PyObject_HEAD
-+      pingobj_t* ping;
-+      const char* host;
-+      struct {
-+              double history[PING_HISTORY_SIZE];
-+              size_t history_index;
-+              size_t history_size;
-+              size_t packets_sent;
-+              size_t packets_rcvd;
-+              double average;
-+              double stddev;
-+              double loss;
-+      } stats;
-+} PingObject;
-+
-+static void Ping_dealloc(PingObject* self) {
-+      if (self->ping)
-+              ping_destroy(self->ping);
-+
-+      Py_TYPE(self)->tp_free((PyObject*)self);
-+}
-+
-+static void Ping_init_stats(PingObject* self) {
-+      self->stats.history_index = 0;
-+      self->stats.history_size = 0;
-+      self->stats.packets_sent = 0;
-+      self->stats.packets_rcvd = 0;
-+
-+      self->stats.average = 0.0;
-+      self->stats.stddev = 0.0;
-+      self->stats.loss = 0.0;
-+}
-+
-+static PyObject* Ping_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
-+      PingObject* self = (PingObject*)type->tp_alloc(type, 0);
-+
-+      if (self) {
-+              self->ping = NULL;
-+              self->host = NULL;
-+
-+              Ping_init_stats(self);
-+      }
-+
-+      return (PyObject*)self;
-+}
-+
-+static int Ping_init(PingObject* self, PyObject* args, PyObject* kwds) {
-+      char* kwlist[] = {"host", "family", "timeout", "ttl", NULL};
-+      int family = PING_DEF_AF;
-+      double timeout = PING_DEFAULT_TIMEOUT;
-+      int ttl = PING_DEF_TTL;
-+
-+      if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|idi", kwlist, &self->host,
-+                      &family, &timeout, &ttl))
-+              return -1;
-+
-+      if (family != AF_UNSPEC && family != AF_INET6 && family != AF_INET) {
-+              PyErr_Format(PyExc_ValueError, "Family must be AF_UNSPEC, AF_INET6, or AF_INET");
-+              return -1;
-+      }
-+
-+      if (timeout < 0) {
-+              PyErr_Format(PyExc_ValueError, "Timeout must be greater than zero");
-+              return -1;
-+      }
-+
-+      if (ttl < 1 || ttl > 255) {
-+              PyErr_Format(PyExc_ValueError, "TTL must be between 1 and 255");
-+              return -1;
-+      }
-+
-+      self->ping = ping_construct();
-+      if (!self->ping) {
-+              return -1;
-+      }
-+
-+      // Set options
-+      int r;
-+
-+      r = ping_setopt(self->ping, PING_OPT_AF, &family);
-+      if (r) {
-+              PyErr_Format(PyExc_RuntimeError, "Could not set address family: %s",
-+                      ping_get_error(self->ping));
-+              return -1;
-+      }
-+
-+      if (timeout > 0) {
-+              r = ping_setopt(self->ping, PING_OPT_TIMEOUT, &timeout);
-+
-+              if (r) {
-+                      PyErr_Format(PyExc_RuntimeError, "Could not set timeout: %s",
-+                              ping_get_error(self->ping));
-+                      return -1;
-+              }
-+      }
-+
-+      r = ping_setopt(self->ping, PING_OPT_TTL, &ttl);
-+      if (r) {
-+              PyErr_Format(PyExc_RuntimeError, "Could not set TTL: %s",
-+                      ping_get_error(self->ping));
-+              return -1;
-+      }
-+
-+      return 0;
-+}
-+
-+static double Ping_compute_average(PingObject* self) {
-+      assert(self->stats.packets_rcvd > 0);
-+
-+      double total_latency = 0.0;
-+
-+      for (int i = 0; i < self->stats.history_size; i++) {
-+              if (self->stats.history[i] > 0)
-+                      total_latency += self->stats.history[i];
-+      }
-+
-+      return total_latency / self->stats.packets_rcvd;
-+}
-+
-+static double Ping_compute_stddev(PingObject* self, double mean) {
-+      assert(self->stats.packets_rcvd > 0);
-+
-+      double deviation = 0.0;
-+
-+      for (int i = 0; i < self->stats.history_size; i++) {
-+              if (self->stats.history[i] > 0) {
-+                      deviation += pow(self->stats.history[i] - mean, 2);
-+              }
-+      }
-+
-+      // Normalise
-+      deviation /= self->stats.packets_rcvd;
-+
-+      return sqrt(deviation);
-+}
-+
-+static void Ping_compute_stats(PingObject* self) {
-+      // Compute the average latency
-+      self->stats.average = Ping_compute_average(self);
-+
-+      // Compute the standard deviation
-+      self->stats.stddev = Ping_compute_stddev(self, self->stats.average);
-+
-+      // Compute lost packets
-+      self->stats.loss = 1.0;
-+      self->stats.loss -= (double)self->stats.packets_rcvd \
-+              / (double)self->stats.packets_sent;
-+}
-+
-+static double time_elapsed(struct timeval* t0) {
-+      struct timeval now;
-+      gettimeofday(&now, NULL);
-+
-+      double r = now.tv_sec - t0->tv_sec;
-+      r += ((double)now.tv_usec / 1000000) - ((double)t0->tv_usec / 1000000);
-+
-+      return r;
-+}
-+
-+static PyObject* Ping_ping(PingObject* self, PyObject* args, PyObject* kwds) {
-+      char* kwlist[] = {"count", "deadline", NULL};
-+      size_t count = PING_DEFAULT_COUNT;
-+      double deadline = 0;
-+
-+      if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Id", kwlist, &count, &deadline))
-+              return NULL;
-+
-+      int r = ping_host_add(self->ping, self->host);
-+      if (r) {
-+              PyErr_Format(PyExc_PingAddHostError, "Could not add host %s: %s",
-+                      self->host, ping_get_error(self->ping));
-+              return NULL;
-+      }
-+
-+      // Reset all collected statistics in case ping() is called more than once.
-+      Ping_init_stats(self);
-+
-+      // Save start time
-+      struct timeval time_start;
-+      r = gettimeofday(&time_start, NULL);
-+      if (r) {
-+              PyErr_Format(PyExc_RuntimeError, "Could not determine start time");
-+              return NULL;
-+      }
-+
-+      // Do the pinging
-+      while (count--) {
-+              self->stats.packets_sent++;
-+
-+              Py_BEGIN_ALLOW_THREADS
-+              r = ping_send(self->ping);
-+              Py_END_ALLOW_THREADS
-+
-+              // Count recieved packets
-+              if (r >= 0) {
-+                      self->stats.packets_rcvd += r;
-+
-+              // Raise any errors
-+              } else {
-+                      PyErr_Format(PyExc_RuntimeError, "Error executing ping_send(): %s",
-+                              ping_get_error(self->ping));
-+                      return NULL;
-+              }
-+
-+              // Extract all data
-+              pingobj_iter_t* iter = ping_iterator_get(self->ping);
-+
-+              double* latency = &self->stats.history[self->stats.history_index];
-+              size_t buffer_size = sizeof(latency);
-+              ping_iterator_get_info(iter, PING_INFO_LATENCY, latency, &buffer_size);
-+
-+              // Increase the history pointer
-+              self->stats.history_index++;
-+              self->stats.history_index %= sizeof(self->stats.history);
-+
-+              // Increase the history size
-+              if (self->stats.history_size < sizeof(self->stats.history))
-+                      self->stats.history_size++;
-+
-+              // Check if the deadline is due
-+              if (deadline > 0) {
-+                      double elapsed_time = time_elapsed(&time_start);
-+
-+                      // If we have run longer than the deadline is, we end the main loop
-+                      if (elapsed_time >= deadline)
-+                              break;
-+              }
-+      }
-+
-+      if (self->stats.packets_rcvd == 0) {
-+              PyErr_Format(PyExc_PingError, "No replies received");
-+              return NULL;
-+      }
-+
-+      Ping_compute_stats(self);
-+
-+      Py_RETURN_NONE;
-+}
-+
-+static PyObject* Ping_get_packets_sent(PingObject* self) {
-+      return PyLong_FromUnsignedLong(self->stats.packets_sent);
-+}
-+
-+static PyObject* Ping_get_packets_rcvd(PingObject* self) {
-+      return PyLong_FromUnsignedLong(self->stats.packets_rcvd);
-+}
-+
-+static PyObject* Ping_get_average(PingObject* self) {
-+      return PyFloat_FromDouble(self->stats.average);
-+}
-+
-+static PyObject* Ping_get_stddev(PingObject* self) {
-+      return PyFloat_FromDouble(self->stats.stddev);
-+}
-+
-+static PyObject* Ping_get_loss(PingObject* self) {
-+      return PyFloat_FromDouble(self->stats.loss);
-+}
-+
-+static PyGetSetDef Ping_getsetters[] = {
-+      {"average", (getter)Ping_get_average, NULL, NULL, NULL},
-+      {"loss", (getter)Ping_get_loss, NULL, NULL, NULL},
-+      {"stddev", (getter)Ping_get_stddev, NULL, NULL, NULL},
-+      {"packets_sent", (getter)Ping_get_packets_sent, NULL, NULL, NULL},
-+      {"packets_rcvd", (getter)Ping_get_packets_rcvd, NULL, NULL, NULL},
-+      {NULL}
-+};
-+
-+static PyMethodDef Ping_methods[] = {
-+      {"ping", (PyCFunction)Ping_ping, METH_VARARGS|METH_KEYWORDS, NULL},
-+      {NULL}
-+};
-+
-+static PyTypeObject PingType = {
-+      PyVarObject_HEAD_INIT(NULL, 0)
-+      "_collecty.Ping",                   /*tp_name*/
-+      sizeof(PingObject),                 /*tp_basicsize*/
-+      0,                                  /*tp_itemsize*/
-+      (destructor)Ping_dealloc,           /*tp_dealloc*/
-+      0,                                  /*tp_print*/
-+      0,                                  /*tp_getattr*/
-+      0,                                  /*tp_setattr*/
-+      0,                                  /*tp_compare*/
-+      0,                                  /*tp_repr*/
-+      0,                                  /*tp_as_number*/
-+      0,                                  /*tp_as_sequence*/
-+      0,                                  /*tp_as_mapping*/
-+      0,                                  /*tp_hash */
-+      0,                                  /*tp_call*/
-+      0,                                  /*tp_str*/
-+      0,                                  /*tp_getattro*/
-+      0,                                  /*tp_setattro*/
-+      0,                                  /*tp_as_buffer*/
-+      Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
-+      "Ping object",                      /* tp_doc */
-+      0,                                          /* tp_traverse */
-+      0,                                          /* tp_clear */
-+      0,                                          /* tp_richcompare */
-+      0,                                          /* tp_weaklistoffset */
-+      0,                                          /* tp_iter */
-+      0,                                          /* tp_iternext */
-+      Ping_methods,                       /* tp_methods */
-+      0,                                  /* tp_members */
-+      Ping_getsetters,                    /* tp_getset */
-+      0,                                  /* tp_base */
-+      0,                                  /* tp_dict */
-+      0,                                  /* tp_descr_get */
-+      0,                                  /* tp_descr_set */
-+      0,                                  /* tp_dictoffset */
-+      (initproc)Ping_init,                /* tp_init */
-+      0,                                  /* tp_alloc */
-+      Ping_new,                           /* tp_new */
-+};
-+
- typedef struct {
-       PyObject_HEAD
-       const sensors_chip_name* chip;
-@@ -743,6 +1067,9 @@ PyMODINIT_FUNC PyInit__collecty(void) {
-       if (PyType_Ready(&BlockDeviceType) < 0)
-               return NULL;
-+      if (PyType_Ready(&PingType) < 0)
-+              return NULL;
-+
-       if (PyType_Ready(&SensorType) < 0)
-               return NULL;
-@@ -751,6 +1078,17 @@ PyMODINIT_FUNC PyInit__collecty(void) {
-       Py_INCREF(&BlockDeviceType);
-       PyModule_AddObject(m, "BlockDevice", (PyObject*)&BlockDeviceType);
-+      Py_INCREF(&PingType);
-+      PyModule_AddObject(m, "Ping", (PyObject*)&PingType);
-+
-+      PyExc_PingError = PyErr_NewException("_collecty.PingError", NULL, NULL);
-+      Py_INCREF(PyExc_PingError);
-+      PyModule_AddObject(m, "PingError", PyExc_PingError);
-+
-+      PyExc_PingAddHostError = PyErr_NewException("_collecty.PingAddHostError", NULL, NULL);
-+      Py_INCREF(PyExc_PingAddHostError);
-+      PyModule_AddObject(m, "PingAddHostError", PyExc_PingAddHostError);
-+
-       Py_INCREF(&SensorType);
-       PyModule_AddObject(m, "Sensor", (PyObject*)&SensorType);
-diff --git a/src/collecty/ping.py b/src/collecty/ping.py
-deleted file mode 100644
-index e2d7970..0000000
---- a/src/collecty/ping.py
-+++ /dev/null
-@@ -1,324 +0,0 @@
--#!/usr/bin/python3
--
--import array
--import math
--import os
--import random
--import select
--import socket
--import struct
--import sys
--import time
--
--ICMP_TYPE_ECHO_REPLY = 0
--ICMP_TYPE_ECHO_REQUEST = 8
--ICMP_MAX_RECV = 2048
--
--MAX_SLEEP = 1000
--
--class PingError(Exception):
--      msg = None
--
--
--class PingResolveError(PingError):
--      msg = "Could not resolve hostname"
--
--
--class Ping(object):
--      def __init__(self, destination, timeout=1000, packet_size=56):
--              self.destination = self._resolve(destination)
--              self.timeout = timeout
--              self.packet_size = packet_size
--
--              self.id = os.getpid() & 0xffff # XXX ? Is this a good idea?
--
--              self.seq_number = 0
--
--              # Number of sent packets.
--              self.send_count = 0
--
--              # Save the delay of all responses.
--              self.times = []
--
--      def run(self, count=None, deadline=None):
--              while True:
--                      delay = self.do()
--
--                      self.seq_number += 1
--
--                      if count and self.seq_number >= count:
--                              break
--
--                      if deadline and self.total_time >= deadline:
--                              break
--
--                      if delay == None:
--                              delay = 0
--
--                      if MAX_SLEEP > delay:
--                              time.sleep((MAX_SLEEP - delay) / 1000)
--
--      def do(self):
--              s = None
--              try:
--                      # Open a socket for ICMP communication.
--                      s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
--
--                      # Send one packet.
--                      send_time = self.send_icmp_echo_request(s)
--
--                      # Increase number of sent packets (even if it could not be sent).
--                      self.send_count += 1
--
--                      # If the packet could not be sent, we may stop here.
--                      if send_time is None:
--                              return
--
--                      # Wait for the reply.
--                      receive_time, packet_size, ip, ip_header, icmp_header = self.receive_icmp_echo_reply(s)
--
--              finally:
--                      # Close the socket.
--                      if s:
--                              s.close()
--
--              # If a packet has been received...
--              if receive_time:
--                      delay = (receive_time - send_time) * 1000
--                      self.times.append(delay)
--
--                      return delay
--
--      def send_icmp_echo_request(self, s):
--              # Header is type (8), code (8), checksum (16), id (16), sequence (16)
--              checksum = 0
--
--              # Create a header with checksum == 0.
--              header = struct.pack("!BBHHH", ICMP_TYPE_ECHO_REQUEST, 0,
--                      checksum, self.id, self.seq_number)
--
--              # Get some bytes for padding.
--              padding = os.urandom(self.packet_size)
--
--              # Calculate the checksum for header + padding data.
--              checksum = self._calculate_checksum(header + padding)
--
--              # Rebuild the header with the new checksum.
--              header = struct.pack("!BBHHH", ICMP_TYPE_ECHO_REQUEST, 0,
--                      checksum, self.id, self.seq_number)
--
--              # Build the packet.
--              packet = header + padding
--
--              # Save the time when the packet has been sent.
--              send_time = time.time()
--
--              # Send the packet.
--              try:
--                      s.sendto(packet, (self.destination, 0))
--              except socket.error as e:
--                      if e.errno == 1: # Operation not permitted
--                              # The packet could not be sent, probably because of
--                              # wrong firewall settings.
--                              return
--
--              return send_time
--
--      def receive_icmp_echo_reply(self, s):
--              timeout = self.timeout / 1000.0
--
--              # Wait until the reply packet arrived or until we hit timeout.
--              while True:
--                      select_start = time.time()
--
--                      inputready, outputready, exceptready = select.select([s], [], [], timeout)
--                      select_duration = (time.time() - select_start)
--
--                      if inputready == []: # Timeout
--                              return None, 0, 0, 0, 0
--
--                      # Save the time when the packet has been received.
--                      receive_time = time.time()
--
--                      # Read the packet from the socket.
--                      packet_data, address = s.recvfrom(ICMP_MAX_RECV)
--
--                      # Parse the ICMP header.
--                      icmp_header = self._header2dict(
--                              ["type", "code", "checksum", "packet_id", "seq_number"],
--                              "!BBHHH", packet_data[20:28]
--                      )
--
--                      # This is the reply to our packet if the ID matches.
--                      if icmp_header["packet_id"] == self.id:
--                              # Parse the IP header.
--                              ip_header = self._header2dict(
--                                      ["version", "type", "length", "id", "flags",
--                                      "ttl", "protocol", "checksum", "src_ip", "dst_ip"],
--                                      "!BBHHHBBHII", packet_data[:20]
--                              )
--
--                              packet_size = len(packet_data) - 28
--                              ip = socket.inet_ntoa(struct.pack("!I", ip_header["src_ip"]))
--
--                              return receive_time, packet_size, ip, ip_header, icmp_header
--
--                      # Check if the timeout has already been hit.
--                      timeout = timeout - select_duration
--                      if timeout <= 0:
--                              return None, 0, 0, 0, 0
--
--      def _header2dict(self, names, struct_format, data):
--              """
--                      Unpack tghe raw received IP and ICMP header informations to a dict
--              """
--              unpacked_data = struct.unpack(struct_format, data)
--              return dict(list(zip(names, unpacked_data)))
--
--      def _calculate_checksum(self, source_string):
--              if len(source_string) % 2:
--                      source_string += "\x00"
--
--              converted = array.array("H", source_string)
--              if sys.byteorder == "big":
--                      converted.byteswap()
--
--              val = sum(converted)
--
--              # Truncate val to 32 bits (a variance from ping.c, which uses signed
--              # integers, but overflow is unlinkely in ping).
--              val &= 0xffffffff
--
--              # Add high 16 bits to low 16 bits.
--              val = (val >> 16) + (val & 0xffff)
--
--              # Add carry from above (if any).
--              val += (val >> 16)
--
--              # Invert and truncate to 16 bits.
--              answer = ~val & 0xffff
--
--              return socket.htons(answer)
--
--      def _resolve(self, host):
--              """
--                      Resolve host.
--              """
--              if self._is_valid_ipv4_address(host):
--                      return host
--
--              try:
--                      return socket.gethostbyname(host)
--              except socket.gaierror as e:
--                      if e.errno == -3:
--                              raise PingResolveError
--
--                      raise
--
--      def _is_valid_ipv4_address(self, addr):
--              """
--                      Check addr to be a valid IPv4 address.
--              """
--              parts = addr.split(".")
--
--              if not len(parts) == 4:
--                      return False
--
--              for part in parts:
--                      try:
--                              number = int(part)
--                      except ValueError:
--                              return False
--
--                      if number > 255:
--                              return False
--
--              return True
--
--      @property
--      def receive_count(self):
--              """
--                      The number of received packets.
--              """
--              return len(self.times)
--
--      @property
--      def total_time(self):
--              """
--                      The total time of all roundtrips.
--              """
--              try:
--                      return sum(self.times)
--              except ValueError:
--                      return
--
--      @property
--      def min_time(self):
--              """
--                      The smallest roundtrip time.
--              """
--              try:
--                      return min(self.times)
--              except ValueError:
--                      return
--
--      @property
--      def max_time(self):
--              """
--                      The biggest roundtrip time.
--              """
--              try:
--                      return max(self.times)
--              except ValueError:
--                      return
--
--      @property
--      def avg_time(self):
--              """
--                      Calculate the average response time.
--              """
--              try:
--                      return self.total_time / self.receive_count
--              except ZeroDivisionError:
--                      return
--
--      @property
--      def variance(self):
--              """
--                      Calculate the variance of all roundtrips.
--              """
--              if self.avg_time is None:
--                      return
--
--              var = 0
--
--              for t in self.times:
--                      var += (t - self.avg_time) ** 2
--
--              var /= self.receive_count
--              return var
--
--      @property
--      def stddev(self):
--              """
--                      Standard deviation of all roundtrips.
--              """
--              return math.sqrt(self.variance)
--
--      @property
--      def loss(self):
--              """
--                      Outputs the percentage of dropped packets.
--              """
--              dropped = self.send_count - self.receive_count
--
--              return dropped / self.send_count
--
--
--if __name__ == "__main__":
--      p = Ping("ping.ipfire.org")
--      p.run(count=5)
--
--      print("Min/Avg/Max/Stddev: %.2f/%.2f/%.2f/%.2f" % \
--              (p.min_time, p.avg_time, p.max_time, p.stddev))
--      print("Sent/Recv/Loss: %d/%d/%.2f" % (p.send_count, p.receive_count, p.loss))
-diff --git a/src/collecty/plugins/latency.py b/src/collecty/plugins/latency.py
-index df67102..a219240 100644
---- a/src/collecty/plugins/latency.py
-+++ b/src/collecty/plugins/latency.py
-@@ -19,8 +19,9 @@
- #                                                                             #
- ###############################################################################
--import collecty.ping
-+import socket
-+import collecty._collecty
- from . import base
- from ..i18n import _
-@@ -37,86 +38,124 @@ class GraphTemplateLatency(base.GraphTemplate):
-       @property
-       def rrd_graph(self):
-               return [
--                      "DEF:latency=%(file)s:latency:AVERAGE",
--                      "DEF:latency_loss=%(file)s:latency_loss:AVERAGE",
--                      "DEF:latency_stddev=%(file)s:latency_stddev:AVERAGE",
--
--                      # Compute loss in percentage.
--                      "CDEF:latency_ploss=latency_loss,100,*",
--
--                      # Compute standard deviation.
--                      "CDEF:stddev1=latency,latency_stddev,+",
--                      "CDEF:stddev2=latency,latency_stddev,-",
--
--                      "CDEF:l005=latency_ploss,0,5,LIMIT,UN,UNKN,INF,IF",
--                      "CDEF:l010=latency_ploss,5,10,LIMIT,UN,UNKN,INF,IF",
--                      "CDEF:l025=latency_ploss,10,25,LIMIT,UN,UNKN,INF,IF",
--                      "CDEF:l050=latency_ploss,25,50,LIMIT,UN,UNKN,INF,IF",
--                      "CDEF:l100=latency_ploss,50,100,LIMIT,UN,UNKN,INF,IF",
--
-+                      "DEF:latency6=%(file)s:latency6:AVERAGE",
-+                      "DEF:loss6=%(file)s:loss6:AVERAGE",
-+                      "DEF:stddev6=%(file)s:stddev6:AVERAGE",
-+
-+                      "DEF:latency4=%(file)s:latency4:AVERAGE",
-+                      "DEF:loss4=%(file)s:loss4:AVERAGE",
-+                      "DEF:stddev4=%(file)s:stddev4:AVERAGE",
-+
-+                      # Compute the biggest loss and convert into percentage
-+                      "CDEF:ploss=loss6,loss4,MAX,100,*",
-+
-+                      # Compute standard deviation
-+                      "CDEF:stddevarea6=stddev6,2,*",
-+                      "CDEF:spacer6=latency6,stddev6,-",
-+                      "CDEF:stddevarea4=stddev4,2,*",
-+                      "CDEF:spacer4=latency4,stddev4,-",
-+
-+                      "CDEF:l005=ploss,0,5,LIMIT,UN,UNKN,INF,IF",
-+                      "CDEF:l010=ploss,5,10,LIMIT,UN,UNKN,INF,IF",
-+                      "CDEF:l025=ploss,10,25,LIMIT,UN,UNKN,INF,IF",
-+                      "CDEF:l050=ploss,25,50,LIMIT,UN,UNKN,INF,IF",
-+                      "CDEF:l100=ploss,50,100,LIMIT,UN,UNKN,INF,IF",
-+
-+                      "VDEF:latency6min=latency6,MINIMUM",
-+                      "VDEF:latency6max=latency6,MAXIMUM",
-+                      "VDEF:latency6avg=latency6,AVERAGE",
-+                      "VDEF:latency4min=latency4,MINIMUM",
-+                      "VDEF:latency4max=latency4,MAXIMUM",
-+                      "VDEF:latency4avg=latency4,AVERAGE",
-+
-+                      "LINE1:latency6avg#00ff0066:%s" % _("Average latency (IPv6)"),
-+                      "LINE1:latency4avg#ff000066:%s\\r" % _("Average latency (IPv4)"),
-+
-+                      "COMMENT:%s" % _("Packet Loss"),
-                       "AREA:l005#ffffff:%s" % _("0-5%%"),
--                      "AREA:l010#000000:%s" % _("5-10%%"),
--                      "AREA:l025#ff0000:%s" % _("10-25%%"),
--                      "AREA:l050#00ff00:%s" % _("25-50%%"),
--                      "AREA:l100#0000ff:%s" % _("50-100%%") + "\\n",
--
--                      "LINE1:stddev1#00660088",
--                      "LINE1:stddev2#00660088",
--
--                      "LINE3:latency#ff0000:%s" % _("Latency"),
--                      "VDEF:latencymin=latency,MINIMUM",
--                      "VDEF:latencymax=latency,MAXIMUM",
--                      "VDEF:latencyavg=latency,AVERAGE",
--                      "GPRINT:latencymax:%12s\:" % _("Maximum") + " %6.2lf",
--                      "GPRINT:latencymin:%12s\:" % _("Minimum") + " %6.2lf",
--                      "GPRINT:latencyavg:%12s\:" % _("Average") + " %6.2lf\\n",
--
--                      "LINE1:latencyavg#000000:%s" % _("Average latency"),
-+                      "AREA:l010#cccccc:%s" % _("5-10%%"),
-+                      "AREA:l025#999999:%s" % _("10-25%%"),
-+                      "AREA:l050#666666:%s" % _("25-50%%"),
-+                      "AREA:l100#333333:%s" % _("50-100%%") + "\\r",
-+
-+                      "COMMENT: \\n", # empty line
-+
-+                      "AREA:spacer4",
-+                      "AREA:stddevarea4#ff000033:STACK",
-+                      "LINE2:latency4#ff0000:%s" % _("Latency (IPv4)"),
-+                      "GPRINT:latency4max:%12s\:" % _("Maximum") + " %6.2lf",
-+                      "GPRINT:latency4min:%12s\:" % _("Minimum") + " %6.2lf",
-+                      "GPRINT:latency4avg:%12s\:" % _("Average") + " %6.2lf\\n",
-+
-+                      "AREA:spacer6",
-+                      "AREA:stddevarea6#00ff0033:STACK",
-+                      "LINE2:latency6#00ff00:%s" % _("Latency (IPv6)"),
-+                      "GPRINT:latency6max:%12s\:" % _("Maximum") + " %6.2lf",
-+                      "GPRINT:latency6min:%12s\:" % _("Minimum") + " %6.2lf",
-+                      "GPRINT:latency6avg:%12s\:" % _("Average") + " %6.2lf\\n",
-               ]
-       @property
-       def graph_title(self):
--              return _("Latency to %(host)s")
-+              return _("Latency to %s") % self.object.hostname
-       @property
-       def graph_vertical_label(self):
-               return _("Milliseconds")
-+      @property
-+      def rrd_graph_args(self):
-+              return [
-+                      "--legend-direction=bottomup",
-+              ]
-+
- class LatencyObject(base.Object):
-       rrd_schema = [
--              "DS:latency:GAUGE:0:U",
--              "DS:latency_loss:GAUGE:0:100",
--              "DS:latency_stddev:GAUGE:0:U",
-+              "DS:latency6:GAUGE:0:U",
-+              "DS:stddev6:GAUGE:0:U",
-+              "DS:loss6:GAUGE:0:100",
-+              "DS:latency4:GAUGE:0:U",
-+              "DS:stddev4:GAUGE:0:U",
-+              "DS:loss4:GAUGE:0:100",
-       ]
-       def __repr__(self):
-               return "<%s %s>" % (self.__class__.__name__, self.hostname)
--      def init(self, hostname, deadline=None):
-+      def init(self, hostname):
-               self.hostname = hostname
--              self.deadline = deadline
-       @property
-       def id(self):
-               return self.hostname
-       def collect(self):
--              # Send up to five ICMP echo requests.
--              try:
--                      ping = collecty.ping.Ping(destination=self.hostname, timeout=20000)
--                      ping.run(count=5, deadline=self.deadline)
-+              result = []
-+
-+              for family in (socket.AF_INET6, socket.AF_INET):
-+                      try:
-+                              p = collecty._collecty.Ping(self.hostname, family=family)
-+                              p.ping(count=5, deadline=10)
-+
-+                              result += (p.average, p.stddev, p.loss)
-+
-+                      except collecty._collecty.PingAddHostError as e:
-+                              self.log.debug(_("Could not add host %(host)s for family %(family)s") \
-+                                      % { "host" : self.hostname, "family" : family })
--              except collecty.ping.PingError as e:
--                      self.log.warning(_("Could not run latency check for %(host)s: %(msg)s") \
--                              % { "host" : self.hostname, "msg" : e.msg })
--                      return
-+                              # No data available
-+                              result += (None, None, None)
-+                              continue
--              return (
--                      "%.10f" % ping.avg_time,
--                      "%.10f" % ping.loss,
--                      "%.10f" % ping.stddev,
--              )
-+                      except collecty._collecty.PingError as e:
-+                              self.log.warning(_("Could not run latency check for %(host)s: %(msg)s") \
-+                                      % { "host" : self.hostname, "msg" : e })
-+
-+                              # A hundred percent loss
-+                              result += (None, None, 1)
-+
-+              return result
- class LatencyPlugin(base.Plugin):
-@@ -127,7 +166,5 @@ class LatencyPlugin(base.Plugin):
-       @property
-       def objects(self):
--              deadline = self.interval / len(PING_HOSTS)
--
-               for hostname in PING_HOSTS:
--                      yield LatencyObject(self, hostname, deadline=deadline)
-+                      yield LatencyObject(self, hostname)
--- 
-1.8.1
-