]>
Commit | Line | Data |
---|---|---|
86d23308 MT |
1 | /* |
2 | * collecty | |
3 | * Copyright (C) 2015 IPFire Team (www.ipfire.org) | |
4 | * | |
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. | |
9 | * | |
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. | |
14 | * | |
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/>. | |
17 | */ | |
18 | ||
19 | #include <Python.h> | |
20 | ||
21 | #include <errno.h> | |
22 | #include <oping.h> | |
23 | #include <time.h> | |
24 | ||
25 | #include "_collectymodule.h" | |
26 | ||
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}, | |
33 | {NULL} | |
34 | }; | |
35 | ||
36 | static PyMethodDef Ping_methods[] = { | |
37 | {"ping", (PyCFunction)Ping_ping, METH_VARARGS|METH_KEYWORDS, NULL}, | |
38 | {NULL} | |
39 | }; | |
40 | ||
41 | PyTypeObject PingType = { | |
42 | PyVarObject_HEAD_INIT(NULL, 0) | |
43 | "_collecty.Ping", /*tp_name*/ | |
44 | sizeof(PingObject), /*tp_basicsize*/ | |
45 | 0, /*tp_itemsize*/ | |
46 | (destructor)Ping_dealloc, /*tp_dealloc*/ | |
47 | 0, /*tp_print*/ | |
48 | 0, /*tp_getattr*/ | |
49 | 0, /*tp_setattr*/ | |
50 | 0, /*tp_compare*/ | |
51 | 0, /*tp_repr*/ | |
52 | 0, /*tp_as_number*/ | |
53 | 0, /*tp_as_sequence*/ | |
54 | 0, /*tp_as_mapping*/ | |
55 | 0, /*tp_hash */ | |
56 | 0, /*tp_call*/ | |
57 | 0, /*tp_str*/ | |
58 | 0, /*tp_getattro*/ | |
59 | 0, /*tp_setattro*/ | |
60 | 0, /*tp_as_buffer*/ | |
61 | Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ | |
62 | "Ping object", /* tp_doc */ | |
63 | 0, /* tp_traverse */ | |
64 | 0, /* tp_clear */ | |
65 | 0, /* tp_richcompare */ | |
66 | 0, /* tp_weaklistoffset */ | |
67 | 0, /* tp_iter */ | |
68 | 0, /* tp_iternext */ | |
69 | Ping_methods, /* tp_methods */ | |
70 | 0, /* tp_members */ | |
71 | Ping_getsetters, /* tp_getset */ | |
72 | 0, /* tp_base */ | |
73 | 0, /* tp_dict */ | |
74 | 0, /* tp_descr_get */ | |
75 | 0, /* tp_descr_set */ | |
76 | 0, /* tp_dictoffset */ | |
77 | (initproc)Ping_init, /* tp_init */ | |
78 | 0, /* tp_alloc */ | |
79 | Ping_new, /* tp_new */ | |
80 | }; | |
81 | ||
82 | void Ping_dealloc(PingObject* self) { | |
83 | if (self->ping) | |
84 | ping_destroy(self->ping); | |
85 | ||
86 | Py_TYPE(self)->tp_free((PyObject*)self); | |
87 | } | |
88 | ||
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; | |
94 | ||
95 | self->stats.average = 0.0; | |
96 | self->stats.stddev = 0.0; | |
97 | self->stats.loss = 0.0; | |
98 | } | |
99 | ||
100 | PyObject* Ping_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { | |
101 | PingObject* self = (PingObject*)type->tp_alloc(type, 0); | |
102 | ||
103 | if (self) { | |
104 | self->ping = NULL; | |
105 | self->host = NULL; | |
106 | ||
107 | Ping_init_stats(self); | |
108 | } | |
109 | ||
110 | return (PyObject*)self; | |
111 | } | |
112 | ||
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; | |
118 | ||
119 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|idi", kwlist, &self->host, | |
120 | &family, &timeout, &ttl)) | |
121 | return -1; | |
122 | ||
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"); | |
125 | return -1; | |
126 | } | |
127 | ||
128 | if (timeout < 0) { | |
129 | PyErr_Format(PyExc_ValueError, "Timeout must be greater than zero"); | |
130 | return -1; | |
131 | } | |
132 | ||
133 | if (ttl < 1 || ttl > 255) { | |
134 | PyErr_Format(PyExc_ValueError, "TTL must be between 1 and 255"); | |
135 | return -1; | |
136 | } | |
137 | ||
138 | self->ping = ping_construct(); | |
139 | if (!self->ping) { | |
140 | return -1; | |
141 | } | |
142 | ||
143 | // Set options | |
144 | int r; | |
145 | ||
146 | r = ping_setopt(self->ping, PING_OPT_AF, &family); | |
147 | if (r) { | |
148 | PyErr_Format(PyExc_RuntimeError, "Could not set address family: %s", | |
149 | ping_get_error(self->ping)); | |
150 | return -1; | |
151 | } | |
152 | ||
153 | if (timeout > 0) { | |
154 | r = ping_setopt(self->ping, PING_OPT_TIMEOUT, &timeout); | |
155 | ||
156 | if (r) { | |
157 | PyErr_Format(PyExc_RuntimeError, "Could not set timeout: %s", | |
158 | ping_get_error(self->ping)); | |
159 | return -1; | |
160 | } | |
161 | } | |
162 | ||
163 | r = ping_setopt(self->ping, PING_OPT_TTL, &ttl); | |
164 | if (r) { | |
165 | PyErr_Format(PyExc_RuntimeError, "Could not set TTL: %s", | |
166 | ping_get_error(self->ping)); | |
167 | return -1; | |
168 | } | |
169 | ||
170 | return 0; | |
171 | } | |
172 | ||
173 | double Ping_compute_average(PingObject* self) { | |
174 | assert(self->stats.packets_rcvd > 0); | |
175 | ||
176 | double total_latency = 0.0; | |
177 | ||
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]; | |
181 | } | |
182 | ||
183 | return total_latency / self->stats.packets_rcvd; | |
184 | } | |
185 | ||
186 | double Ping_compute_stddev(PingObject* self, double mean) { | |
187 | assert(self->stats.packets_rcvd > 0); | |
188 | ||
189 | double deviation = 0.0; | |
190 | ||
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); | |
194 | } | |
195 | } | |
196 | ||
197 | // Normalise | |
198 | deviation /= self->stats.packets_rcvd; | |
199 | ||
200 | return sqrt(deviation); | |
201 | } | |
202 | ||
203 | static void Ping_compute_stats(PingObject* self) { | |
204 | // Compute the average latency | |
205 | self->stats.average = Ping_compute_average(self); | |
206 | ||
207 | // Compute the standard deviation | |
208 | self->stats.stddev = Ping_compute_stddev(self, self->stats.average); | |
209 | ||
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; | |
214 | } | |
215 | ||
216 | static double time_elapsed(struct timeval* t0) { | |
217 | struct timeval now; | |
218 | gettimeofday(&now, NULL); | |
219 | ||
220 | double r = now.tv_sec - t0->tv_sec; | |
221 | r += ((double)now.tv_usec / 1000000) - ((double)t0->tv_usec / 1000000); | |
222 | ||
223 | return r; | |
224 | } | |
225 | ||
226 | PyObject* Ping_ping(PingObject* self, PyObject* args, PyObject* kwds) { | |
227 | char* kwlist[] = {"count", "deadline", NULL}; | |
228 | size_t count = PING_DEFAULT_COUNT; | |
229 | double deadline = 0; | |
230 | ||
231 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Id", kwlist, &count, &deadline)) | |
232 | return NULL; | |
233 | ||
234 | int r = ping_host_add(self->ping, self->host); | |
235 | if (r) { | |
236 | PyErr_Format(PyExc_PingAddHostError, "Could not add host %s: %s", | |
237 | self->host, ping_get_error(self->ping)); | |
238 | return NULL; | |
239 | } | |
240 | ||
241 | // Reset all collected statistics in case ping() is called more than once. | |
242 | Ping_init_stats(self); | |
243 | ||
244 | // Save start time | |
245 | struct timeval time_start; | |
246 | r = gettimeofday(&time_start, NULL); | |
247 | if (r) { | |
248 | PyErr_Format(PyExc_RuntimeError, "Could not determine start time"); | |
249 | return NULL; | |
250 | } | |
251 | ||
252 | // Do the pinging | |
253 | while (count--) { | |
254 | self->stats.packets_sent++; | |
255 | ||
256 | Py_BEGIN_ALLOW_THREADS | |
257 | r = ping_send(self->ping); | |
258 | Py_END_ALLOW_THREADS | |
259 | ||
260 | // Count recieved packets | |
261 | if (r >= 0) { | |
262 | self->stats.packets_rcvd += r; | |
263 | ||
264 | // Raise any errors | |
265 | } else { | |
266 | PyErr_Format(PyExc_RuntimeError, "Error executing ping_send(): %s", | |
267 | ping_get_error(self->ping)); | |
268 | return NULL; | |
269 | } | |
270 | ||
271 | // Extract all data | |
272 | pingobj_iter_t* iter = ping_iterator_get(self->ping); | |
273 | ||
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); | |
277 | ||
278 | // Increase the history pointer | |
279 | self->stats.history_index++; | |
280 | self->stats.history_index %= sizeof(self->stats.history); | |
281 | ||
282 | // Increase the history size | |
283 | if (self->stats.history_size < sizeof(self->stats.history)) | |
284 | self->stats.history_size++; | |
285 | ||
286 | // Check if the deadline is due | |
287 | if (deadline > 0) { | |
288 | double elapsed_time = time_elapsed(&time_start); | |
289 | ||
290 | // If we have run longer than the deadline is, we end the main loop | |
291 | if (elapsed_time >= deadline) | |
292 | break; | |
293 | } | |
294 | } | |
295 | ||
296 | if (self->stats.packets_rcvd == 0) { | |
c9991a7a | 297 | PyErr_Format(PyExc_PingNoReplyError, "No replies received from %s", self->host); |
86d23308 MT |
298 | return NULL; |
299 | } | |
300 | ||
301 | Ping_compute_stats(self); | |
302 | ||
303 | Py_RETURN_NONE; | |
304 | } | |
305 | ||
306 | PyObject* Ping_get_packets_sent(PingObject* self) { | |
307 | return PyLong_FromUnsignedLong(self->stats.packets_sent); | |
308 | } | |
309 | ||
310 | PyObject* Ping_get_packets_rcvd(PingObject* self) { | |
311 | return PyLong_FromUnsignedLong(self->stats.packets_rcvd); | |
312 | } | |
313 | ||
314 | PyObject* Ping_get_average(PingObject* self) { | |
315 | return PyFloat_FromDouble(self->stats.average); | |
316 | } | |
317 | ||
318 | PyObject* Ping_get_stddev(PingObject* self) { | |
319 | return PyFloat_FromDouble(self->stats.stddev); | |
320 | } | |
321 | ||
322 | PyObject* Ping_get_loss(PingObject* self) { | |
323 | return PyFloat_FromDouble(self->stats.loss); | |
324 | } |