3 * Copyright (C) 2015 IPFire Team (www.ipfire.org)
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include "_collectymodule.h"
27 static PyGetSetDef Ping_getsetters
[] = {
28 {"average", (getter
)Ping_get_average
, NULL
, NULL
, NULL
},
29 {"loss", (getter
)Ping_get_loss
, NULL
, NULL
, NULL
},
30 {"stddev", (getter
)Ping_get_stddev
, NULL
, NULL
, NULL
},
31 {"packets_sent", (getter
)Ping_get_packets_sent
, NULL
, NULL
, NULL
},
32 {"packets_rcvd", (getter
)Ping_get_packets_rcvd
, NULL
, NULL
, NULL
},
36 static PyMethodDef Ping_methods
[] = {
37 {"ping", (PyCFunction
)Ping_ping
, METH_VARARGS
|METH_KEYWORDS
, NULL
},
41 PyTypeObject PingType
= {
42 PyVarObject_HEAD_INIT(NULL
, 0)
43 "_collecty.Ping", /*tp_name*/
44 sizeof(PingObject
), /*tp_basicsize*/
46 (destructor
)Ping_dealloc
, /*tp_dealloc*/
61 Py_TPFLAGS_DEFAULT
|Py_TPFLAGS_BASETYPE
, /*tp_flags*/
62 "Ping object", /* tp_doc */
65 0, /* tp_richcompare */
66 0, /* tp_weaklistoffset */
69 Ping_methods
, /* tp_methods */
71 Ping_getsetters
, /* tp_getset */
76 0, /* tp_dictoffset */
77 (initproc
)Ping_init
, /* tp_init */
79 Ping_new
, /* tp_new */
82 void Ping_dealloc(PingObject
* self
) {
84 ping_destroy(self
->ping
);
86 Py_TYPE(self
)->tp_free((PyObject
*)self
);
89 void Ping_init_stats(PingObject
* self
) {
90 self
->stats
.history_index
= 0;
91 self
->stats
.history_size
= 0;
92 self
->stats
.packets_sent
= 0;
93 self
->stats
.packets_rcvd
= 0;
95 self
->stats
.average
= 0.0;
96 self
->stats
.stddev
= 0.0;
97 self
->stats
.loss
= 0.0;
100 PyObject
* Ping_new(PyTypeObject
* type
, PyObject
* args
, PyObject
* kwds
) {
101 PingObject
* self
= (PingObject
*)type
->tp_alloc(type
, 0);
107 Ping_init_stats(self
);
110 return (PyObject
*)self
;
113 int Ping_init(PingObject
* self
, PyObject
* args
, PyObject
* kwds
) {
114 char* kwlist
[] = {"host", "family", "timeout", "ttl", NULL
};
115 int family
= PING_DEF_AF
;
116 double timeout
= PING_DEFAULT_TIMEOUT
;
117 int ttl
= PING_DEF_TTL
;
119 if (!PyArg_ParseTupleAndKeywords(args
, kwds
, "s|idi", kwlist
, &self
->host
,
120 &family
, &timeout
, &ttl
))
123 if (family
!= AF_UNSPEC
&& family
!= AF_INET6
&& family
!= AF_INET
) {
124 PyErr_Format(PyExc_ValueError
, "Family must be AF_UNSPEC, AF_INET6, or AF_INET");
129 PyErr_Format(PyExc_ValueError
, "Timeout must be greater than zero");
133 if (ttl
< 1 || ttl
> 255) {
134 PyErr_Format(PyExc_ValueError
, "TTL must be between 1 and 255");
138 self
->ping
= ping_construct();
146 r
= ping_setopt(self
->ping
, PING_OPT_AF
, &family
);
148 PyErr_Format(PyExc_RuntimeError
, "Could not set address family: %s",
149 ping_get_error(self
->ping
));
154 r
= ping_setopt(self
->ping
, PING_OPT_TIMEOUT
, &timeout
);
157 PyErr_Format(PyExc_RuntimeError
, "Could not set timeout: %s",
158 ping_get_error(self
->ping
));
163 r
= ping_setopt(self
->ping
, PING_OPT_TTL
, &ttl
);
165 PyErr_Format(PyExc_RuntimeError
, "Could not set TTL: %s",
166 ping_get_error(self
->ping
));
173 double Ping_compute_average(PingObject
* self
) {
174 assert(self
->stats
.packets_rcvd
> 0);
176 double total_latency
= 0.0;
178 for (int i
= 0; i
< self
->stats
.history_size
; i
++) {
179 if (self
->stats
.history
[i
] > 0)
180 total_latency
+= self
->stats
.history
[i
];
183 return total_latency
/ self
->stats
.packets_rcvd
;
186 double Ping_compute_stddev(PingObject
* self
, double mean
) {
187 assert(self
->stats
.packets_rcvd
> 0);
189 double deviation
= 0.0;
191 for (int i
= 0; i
< self
->stats
.history_size
; i
++) {
192 if (self
->stats
.history
[i
] > 0) {
193 deviation
+= pow(self
->stats
.history
[i
] - mean
, 2);
198 deviation
/= self
->stats
.packets_rcvd
;
200 return sqrt(deviation
);
203 static void Ping_compute_stats(PingObject
* self
) {
204 // Compute the average latency
205 self
->stats
.average
= Ping_compute_average(self
);
207 // Compute the standard deviation
208 self
->stats
.stddev
= Ping_compute_stddev(self
, self
->stats
.average
);
210 // Compute lost packets
211 self
->stats
.loss
= 1.0;
212 self
->stats
.loss
-= (double)self
->stats
.packets_rcvd \
213 / (double)self
->stats
.packets_sent
;
216 static double time_elapsed(struct timeval
* t0
) {
218 gettimeofday(&now
, NULL
);
220 double r
= now
.tv_sec
- t0
->tv_sec
;
221 r
+= ((double)now
.tv_usec
/ 1000000) - ((double)t0
->tv_usec
/ 1000000);
226 PyObject
* Ping_ping(PingObject
* self
, PyObject
* args
, PyObject
* kwds
) {
227 char* kwlist
[] = {"count", "deadline", NULL
};
228 size_t count
= PING_DEFAULT_COUNT
;
231 if (!PyArg_ParseTupleAndKeywords(args
, kwds
, "|Id", kwlist
, &count
, &deadline
))
234 int r
= ping_host_add(self
->ping
, self
->host
);
236 PyErr_Format(PyExc_PingAddHostError
, "Could not add host %s: %s",
237 self
->host
, ping_get_error(self
->ping
));
241 // Reset all collected statistics in case ping() is called more than once.
242 Ping_init_stats(self
);
245 struct timeval time_start
;
246 r
= gettimeofday(&time_start
, NULL
);
248 PyErr_Format(PyExc_RuntimeError
, "Could not determine start time");
254 self
->stats
.packets_sent
++;
256 Py_BEGIN_ALLOW_THREADS
257 r
= ping_send(self
->ping
);
260 // Count recieved packets
262 self
->stats
.packets_rcvd
+= r
;
266 PyErr_Format(PyExc_RuntimeError
, "Error executing ping_send(): %s",
267 ping_get_error(self
->ping
));
272 pingobj_iter_t
* iter
= ping_iterator_get(self
->ping
);
274 double* latency
= &self
->stats
.history
[self
->stats
.history_index
];
275 size_t buffer_size
= sizeof(latency
);
276 ping_iterator_get_info(iter
, PING_INFO_LATENCY
, latency
, &buffer_size
);
278 // Increase the history pointer
279 self
->stats
.history_index
++;
280 self
->stats
.history_index
%= sizeof(self
->stats
.history
);
282 // Increase the history size
283 if (self
->stats
.history_size
< sizeof(self
->stats
.history
))
284 self
->stats
.history_size
++;
286 // Check if the deadline is due
288 double elapsed_time
= time_elapsed(&time_start
);
290 // If we have run longer than the deadline is, we end the main loop
291 if (elapsed_time
>= deadline
)
296 if (self
->stats
.packets_rcvd
== 0) {
297 PyErr_Format(PyExc_PingNoReplyError
, "No replies received from %s", self
->host
);
301 Ping_compute_stats(self
);
306 PyObject
* Ping_get_packets_sent(PingObject
* self
) {
307 return PyLong_FromUnsignedLong(self
->stats
.packets_sent
);
310 PyObject
* Ping_get_packets_rcvd(PingObject
* self
) {
311 return PyLong_FromUnsignedLong(self
->stats
.packets_rcvd
);
314 PyObject
* Ping_get_average(PingObject
* self
) {
315 return PyFloat_FromDouble(self
->stats
.average
);
318 PyObject
* Ping_get_stddev(PingObject
* self
) {
319 return PyFloat_FromDouble(self
->stats
.stddev
);
322 PyObject
* Ping_get_loss(PingObject
* self
) {
323 return PyFloat_FromDouble(self
->stats
.loss
);