+++ /dev/null
-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
-