From 5fc517c0e5f99cfc46dbb9cc05927fe873017445 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Tue, 15 Dec 2015 14:46:25 +0000 Subject: [PATCH] collecty: Update to version 004 Signed-off-by: Michael Tremer --- collecty/collecty.nm | 4 +- ...ns-Automatically-replace-None-by-NaN.patch | 56 - .../0002-latency-Rewrite-latency-module.patch | 995 ------------------ 3 files changed, 2 insertions(+), 1053 deletions(-) delete mode 100644 collecty/patches/0001-plugins-Automatically-replace-None-by-NaN.patch delete mode 100644 collecty/patches/0002-latency-Rewrite-latency-module.patch diff --git a/collecty/collecty.nm b/collecty/collecty.nm index 72ed7b105..2b30bd5e8 100644 --- a/collecty/collecty.nm +++ b/collecty/collecty.nm @@ -4,8 +4,8 @@ ############################################################################### name = collecty -version = 003 -release = 4 +version = 004 +release = 1 maintainer = Michael Tremer 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 index 772c65d5b..000000000 --- a/collecty/patches/0001-plugins-Automatically-replace-None-by-NaN.patch +++ /dev/null @@ -1,56 +0,0 @@ -From a9af411f0703eac939e0df5d5f75b46d35f531bc Mon Sep 17 00:00:00 2001 -From: Michael Tremer -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 ---- - 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 index 837502bdb..000000000 --- a/collecty/patches/0002-latency-Rewrite-latency-module.patch +++ /dev/null @@ -1,995 +0,0 @@ -From 63f9f8beed445a80dcb492570b105c5b50e65a59 Mon Sep 17 00:00:00 2001 -From: Michael Tremer -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 ---- - 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 - #include - #include -+#include - #include - #include - #include - #include - #include -+#include - - #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 -