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
_collecty_la_CFLAGS = \
$(AM_CFLAGS) \
$(LIBATASMART_CFLAGS) \
+ $(OPING_CFLAGS) \
$(PYTHON_CFLAGS)
_collecty_la_LDFLAGS = \
_collecty_la_LIBADD = \
$(LIBATASMART_LIBS) \
+ $(OPING_LIBS) \
$(PYTHON_LIBS) \
$(SENSORS_LIBS)
AC_PATH_PROG([XSLTPROC], [xsltproc])
+PKG_CHECK_MODULES([OPING], [liboping])
+
# Python
AM_PATH_PYTHON([3.2])
PKG_CHECK_MODULES([PYTHON], [python-${PYTHON_VERSION}])
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
#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;
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;
if (PyType_Ready(&BlockDeviceType) < 0)
return NULL;
+ if (PyType_Ready(&PingType) < 0)
+ return NULL;
+
if (PyType_Ready(&SensorType) < 0)
return NULL;
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);
+++ /dev/null
-#!/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))
# #
###############################################################################
-import collecty.ping
+import socket
+import collecty._collecty
from . import base
from ..i18n import _
@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):
@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)