]>
Commit | Line | Data |
---|---|---|
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 <atasmart.h> | |
22 | #include <errno.h> | |
23 | #include <fcntl.h> | |
24 | #include <linux/hdreg.h> | |
25 | #include <mntent.h> | |
26 | #include <oping.h> | |
27 | #include <sensors/error.h> | |
28 | #include <sensors/sensors.h> | |
29 | #include <stdbool.h> | |
30 | #include <string.h> | |
31 | #include <sys/ioctl.h> | |
32 | #include <time.h> | |
33 | ||
34 | #define MODEL_SIZE 40 | |
35 | #define SERIAL_SIZE 20 | |
36 | ||
37 | #define PING_HISTORY_SIZE 1024 | |
38 | #define PING_DEFAULT_COUNT 10 | |
39 | #define PING_DEFAULT_TIMEOUT 8 | |
40 | ||
41 | typedef struct { | |
42 | PyObject_HEAD | |
43 | char* path; | |
44 | struct hd_driveid identity; | |
45 | SkDisk* disk; | |
46 | } BlockDevice; | |
47 | ||
48 | static void BlockDevice_dealloc(BlockDevice* self) { | |
49 | if (self->disk) | |
50 | sk_disk_free(self->disk); | |
51 | ||
52 | if (self->path) | |
53 | free(self->path); | |
54 | ||
55 | Py_TYPE(self)->tp_free((PyObject*)self); | |
56 | } | |
57 | ||
58 | static int BlockDevice_get_identity(BlockDevice* device) { | |
59 | int fd; | |
60 | ||
61 | if ((fd = open(device->path, O_RDONLY | O_NONBLOCK)) < 0) { | |
62 | return 1; | |
63 | } | |
64 | ||
65 | int r = ioctl(fd, HDIO_GET_IDENTITY, &device->identity); | |
66 | close(fd); | |
67 | ||
68 | if (r) | |
69 | return 1; | |
70 | ||
71 | return 0; | |
72 | } | |
73 | ||
74 | static int BlockDevice_smart_is_available(BlockDevice* device) { | |
75 | SkBool available = FALSE; | |
76 | ||
77 | int r = sk_disk_smart_is_available(device->disk, &available); | |
78 | if (r) | |
79 | return -1; | |
80 | ||
81 | if (available) | |
82 | return 0; | |
83 | ||
84 | return 1; | |
85 | } | |
86 | ||
87 | static int BlockDevice_check_sleep_mode(BlockDevice* device) { | |
88 | SkBool awake = FALSE; | |
89 | ||
90 | int r = sk_disk_check_sleep_mode(device->disk, &awake); | |
91 | if (r) | |
92 | return -1; | |
93 | ||
94 | if (awake) | |
95 | return 0; | |
96 | ||
97 | return 1; | |
98 | } | |
99 | ||
100 | static PyObject * BlockDevice_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { | |
101 | BlockDevice* self = (BlockDevice*)type->tp_alloc(type, 0); | |
102 | ||
103 | if (self) { | |
104 | self->path = NULL; | |
105 | ||
106 | // libatasmart | |
107 | self->disk = NULL; | |
108 | } | |
109 | ||
110 | return (PyObject *)self; | |
111 | } | |
112 | ||
113 | static int BlockDevice_init(BlockDevice* self, PyObject* args, PyObject* kwds) { | |
114 | const char* path = NULL; | |
115 | ||
116 | if (!PyArg_ParseTuple(args, "s", &path)) | |
117 | return -1; | |
118 | ||
119 | self->path = strdup(path); | |
120 | ||
121 | int r = BlockDevice_get_identity(self); | |
122 | if (r) { | |
123 | PyErr_Format(PyExc_OSError, "Could not open block device: %s", path); | |
124 | return -1; | |
125 | } | |
126 | ||
127 | r = sk_disk_open(path, &self->disk); | |
128 | if (r == 0) { | |
129 | if (BlockDevice_smart_is_available(self) == 0) { | |
130 | if (BlockDevice_check_sleep_mode(self) == 0) { | |
131 | r = sk_disk_smart_read_data(self->disk); | |
132 | if (r) { | |
133 | PyErr_Format(PyExc_OSError, "Could not open block device %s: %s", path, | |
134 | strerror(errno)); | |
135 | return -1; | |
136 | } | |
137 | } | |
138 | } | |
139 | } else { | |
140 | PyErr_Format(PyExc_OSError, "Could not open block device %s: %s", path, | |
141 | strerror(errno)); | |
142 | return -1; | |
143 | } | |
144 | ||
145 | //sk_disk_identify_is_available | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | static PyObject* BlockDevice_get_path(PyObject* self) { | |
151 | BlockDevice* device = (BlockDevice*)self; | |
152 | ||
153 | return PyUnicode_FromString(device->path); | |
154 | } | |
155 | ||
156 | static void clean_string(char *s) { | |
157 | for (char* e = s; *e; e++) { | |
158 | if (*e < ' ' || *e >= 127) | |
159 | *e = ' '; | |
160 | } | |
161 | } | |
162 | ||
163 | static void drop_spaces(char *s) { | |
164 | char *d = s; | |
165 | bool prev_space = false; | |
166 | ||
167 | s += strspn(s, " "); | |
168 | ||
169 | for (; *s; s++) { | |
170 | if (prev_space) { | |
171 | if (*s != ' ') { | |
172 | prev_space = false; | |
173 | *(d++) = ' '; | |
174 | *(d++) = *s; | |
175 | } | |
176 | } else { | |
177 | if (*s == ' ') | |
178 | prev_space = true; | |
179 | else | |
180 | *(d++) = *s; | |
181 | } | |
182 | } | |
183 | ||
184 | *d = 0; | |
185 | } | |
186 | ||
187 | static void copy_string(char* d, const char* s, size_t n) { | |
188 | // Copy the source buffer to the destination buffer up to n | |
189 | memcpy(d, s, n); | |
190 | ||
191 | // Terminate the destination buffer with NULL | |
192 | d[n] = '\0'; | |
193 | ||
194 | // Clean up the string from non-printable characters | |
195 | clean_string(d); | |
196 | drop_spaces(d); | |
197 | } | |
198 | ||
199 | static PyObject* BlockDevice_get_model(PyObject* self) { | |
200 | BlockDevice* device = (BlockDevice*)self; | |
201 | ||
202 | char model[MODEL_SIZE + 1]; | |
203 | copy_string(model, device->identity.model, sizeof(model)); | |
204 | ||
205 | return PyUnicode_FromString(model); | |
206 | } | |
207 | ||
208 | static PyObject* BlockDevice_get_serial(PyObject* self) { | |
209 | BlockDevice* device = (BlockDevice*)self; | |
210 | ||
211 | char serial[SERIAL_SIZE + 1]; | |
212 | copy_string(serial, device->identity.serial_no, sizeof(serial)); | |
213 | ||
214 | return PyUnicode_FromString(serial); | |
215 | } | |
216 | ||
217 | static PyObject* BlockDevice_is_smart_supported(PyObject* self) { | |
218 | BlockDevice* device = (BlockDevice*)self; | |
219 | ||
220 | if (BlockDevice_smart_is_available(device) == 0) | |
221 | Py_RETURN_TRUE; | |
222 | ||
223 | Py_RETURN_FALSE; | |
224 | } | |
225 | ||
226 | static PyObject* BlockDevice_is_awake(PyObject* self) { | |
227 | BlockDevice* device = (BlockDevice*)self; | |
228 | ||
229 | if (BlockDevice_check_sleep_mode(device) == 0) | |
230 | Py_RETURN_TRUE; | |
231 | ||
232 | Py_RETURN_FALSE; | |
233 | } | |
234 | ||
235 | static PyObject* BlockDevice_get_bad_sectors(PyObject* self) { | |
236 | BlockDevice* device = (BlockDevice*)self; | |
237 | ||
238 | if (BlockDevice_smart_is_available(device)) { | |
239 | PyErr_Format(PyExc_OSError, "Device does not support SMART"); | |
240 | return NULL; | |
241 | } | |
242 | ||
243 | uint64_t bad_sectors; | |
244 | int r = sk_disk_smart_get_bad(device->disk, &bad_sectors); | |
245 | if (r) | |
246 | return NULL; | |
247 | ||
248 | return PyLong_FromUnsignedLongLong((unsigned long long)bad_sectors); | |
249 | } | |
250 | ||
251 | static PyObject* BlockDevice_get_temperature(PyObject* self) { | |
252 | BlockDevice* device = (BlockDevice*)self; | |
253 | ||
254 | if (BlockDevice_smart_is_available(device)) { | |
255 | PyErr_Format(PyExc_OSError, "Device does not support SMART"); | |
256 | return NULL; | |
257 | } | |
258 | ||
259 | uint64_t mkelvin; | |
260 | int r = sk_disk_smart_get_temperature(device->disk, &mkelvin); | |
261 | if (r) | |
262 | return NULL; | |
263 | ||
264 | // Convert the temperature to Kelvin | |
265 | return PyFloat_FromDouble((double)mkelvin / 1000.0); | |
266 | } | |
267 | ||
268 | static PyGetSetDef BlockDevice_getsetters[] = { | |
269 | {"path", (getter)BlockDevice_get_path, NULL, NULL, NULL}, | |
270 | {"model", (getter)BlockDevice_get_model, NULL, NULL, NULL}, | |
271 | {"serial", (getter)BlockDevice_get_serial, NULL, NULL, NULL}, | |
272 | }; | |
273 | ||
274 | static PyMethodDef BlockDevice_methods[] = { | |
275 | {"get_bad_sectors", (PyCFunction)BlockDevice_get_bad_sectors, METH_NOARGS, NULL}, | |
276 | {"get_temperature", (PyCFunction)BlockDevice_get_temperature, METH_NOARGS, NULL}, | |
277 | {"is_smart_supported", (PyCFunction)BlockDevice_is_smart_supported, METH_NOARGS, NULL}, | |
278 | {"is_awake", (PyCFunction)BlockDevice_is_awake, METH_NOARGS, NULL}, | |
279 | {NULL} | |
280 | }; | |
281 | ||
282 | static PyTypeObject BlockDeviceType = { | |
283 | PyVarObject_HEAD_INIT(NULL, 0) | |
284 | "_collecty.BlockDevice", /*tp_name*/ | |
285 | sizeof(BlockDevice), /*tp_basicsize*/ | |
286 | 0, /*tp_itemsize*/ | |
287 | (destructor)BlockDevice_dealloc, /*tp_dealloc*/ | |
288 | 0, /*tp_print*/ | |
289 | 0, /*tp_getattr*/ | |
290 | 0, /*tp_setattr*/ | |
291 | 0, /*tp_compare*/ | |
292 | 0, /*tp_repr*/ | |
293 | 0, /*tp_as_number*/ | |
294 | 0, /*tp_as_sequence*/ | |
295 | 0, /*tp_as_mapping*/ | |
296 | 0, /*tp_hash */ | |
297 | 0, /*tp_call*/ | |
298 | 0, /*tp_str*/ | |
299 | 0, /*tp_getattro*/ | |
300 | 0, /*tp_setattro*/ | |
301 | 0, /*tp_as_buffer*/ | |
302 | Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ | |
303 | "BlockDevice objects", /* tp_doc */ | |
304 | 0, /* tp_traverse */ | |
305 | 0, /* tp_clear */ | |
306 | 0, /* tp_richcompare */ | |
307 | 0, /* tp_weaklistoffset */ | |
308 | 0, /* tp_iter */ | |
309 | 0, /* tp_iternext */ | |
310 | BlockDevice_methods, /* tp_methods */ | |
311 | 0, /* tp_members */ | |
312 | BlockDevice_getsetters, /* tp_getset */ | |
313 | 0, /* tp_base */ | |
314 | 0, /* tp_dict */ | |
315 | 0, /* tp_descr_get */ | |
316 | 0, /* tp_descr_set */ | |
317 | 0, /* tp_dictoffset */ | |
318 | (initproc)BlockDevice_init, /* tp_init */ | |
319 | 0, /* tp_alloc */ | |
320 | BlockDevice_new, /* tp_new */ | |
321 | }; | |
322 | ||
323 | static PyObject* PyExc_PingError; | |
324 | static PyObject* PyExc_PingAddHostError; | |
325 | ||
326 | typedef struct { | |
327 | PyObject_HEAD | |
328 | pingobj_t* ping; | |
329 | const char* host; | |
330 | struct { | |
331 | double history[PING_HISTORY_SIZE]; | |
332 | size_t history_index; | |
333 | size_t history_size; | |
334 | size_t packets_sent; | |
335 | size_t packets_rcvd; | |
336 | double average; | |
337 | double stddev; | |
338 | double loss; | |
339 | } stats; | |
340 | } PingObject; | |
341 | ||
342 | static void Ping_dealloc(PingObject* self) { | |
343 | if (self->ping) | |
344 | ping_destroy(self->ping); | |
345 | ||
346 | Py_TYPE(self)->tp_free((PyObject*)self); | |
347 | } | |
348 | ||
349 | static void Ping_init_stats(PingObject* self) { | |
350 | self->stats.history_index = 0; | |
351 | self->stats.history_size = 0; | |
352 | self->stats.packets_sent = 0; | |
353 | self->stats.packets_rcvd = 0; | |
354 | ||
355 | self->stats.average = 0.0; | |
356 | self->stats.stddev = 0.0; | |
357 | self->stats.loss = 0.0; | |
358 | } | |
359 | ||
360 | static PyObject* Ping_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { | |
361 | PingObject* self = (PingObject*)type->tp_alloc(type, 0); | |
362 | ||
363 | if (self) { | |
364 | self->ping = NULL; | |
365 | self->host = NULL; | |
366 | ||
367 | Ping_init_stats(self); | |
368 | } | |
369 | ||
370 | return (PyObject*)self; | |
371 | } | |
372 | ||
373 | static int Ping_init(PingObject* self, PyObject* args, PyObject* kwds) { | |
374 | char* kwlist[] = {"host", "family", "timeout", "ttl", NULL}; | |
375 | int family = PING_DEF_AF; | |
376 | double timeout = PING_DEFAULT_TIMEOUT; | |
377 | int ttl = PING_DEF_TTL; | |
378 | ||
379 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|idi", kwlist, &self->host, | |
380 | &family, &timeout, &ttl)) | |
381 | return -1; | |
382 | ||
383 | if (family != AF_UNSPEC && family != AF_INET6 && family != AF_INET) { | |
384 | PyErr_Format(PyExc_ValueError, "Family must be AF_UNSPEC, AF_INET6, or AF_INET"); | |
385 | return -1; | |
386 | } | |
387 | ||
388 | if (timeout < 0) { | |
389 | PyErr_Format(PyExc_ValueError, "Timeout must be greater than zero"); | |
390 | return -1; | |
391 | } | |
392 | ||
393 | if (ttl < 1 || ttl > 255) { | |
394 | PyErr_Format(PyExc_ValueError, "TTL must be between 1 and 255"); | |
395 | return -1; | |
396 | } | |
397 | ||
398 | self->ping = ping_construct(); | |
399 | if (!self->ping) { | |
400 | return -1; | |
401 | } | |
402 | ||
403 | // Set options | |
404 | int r; | |
405 | ||
406 | r = ping_setopt(self->ping, PING_OPT_AF, &family); | |
407 | if (r) { | |
408 | PyErr_Format(PyExc_RuntimeError, "Could not set address family: %s", | |
409 | ping_get_error(self->ping)); | |
410 | return -1; | |
411 | } | |
412 | ||
413 | if (timeout > 0) { | |
414 | r = ping_setopt(self->ping, PING_OPT_TIMEOUT, &timeout); | |
415 | ||
416 | if (r) { | |
417 | PyErr_Format(PyExc_RuntimeError, "Could not set timeout: %s", | |
418 | ping_get_error(self->ping)); | |
419 | return -1; | |
420 | } | |
421 | } | |
422 | ||
423 | r = ping_setopt(self->ping, PING_OPT_TTL, &ttl); | |
424 | if (r) { | |
425 | PyErr_Format(PyExc_RuntimeError, "Could not set TTL: %s", | |
426 | ping_get_error(self->ping)); | |
427 | return -1; | |
428 | } | |
429 | ||
430 | return 0; | |
431 | } | |
432 | ||
433 | static double Ping_compute_average(PingObject* self) { | |
434 | assert(self->stats.packets_rcvd > 0); | |
435 | ||
436 | double total_latency = 0.0; | |
437 | ||
438 | for (int i = 0; i < self->stats.history_size; i++) { | |
439 | if (self->stats.history[i] > 0) | |
440 | total_latency += self->stats.history[i]; | |
441 | } | |
442 | ||
443 | return total_latency / self->stats.packets_rcvd; | |
444 | } | |
445 | ||
446 | static double Ping_compute_stddev(PingObject* self, double mean) { | |
447 | assert(self->stats.packets_rcvd > 0); | |
448 | ||
449 | double deviation = 0.0; | |
450 | ||
451 | for (int i = 0; i < self->stats.history_size; i++) { | |
452 | if (self->stats.history[i] > 0) { | |
453 | deviation += pow(self->stats.history[i] - mean, 2); | |
454 | } | |
455 | } | |
456 | ||
457 | // Normalise | |
458 | deviation /= self->stats.packets_rcvd; | |
459 | ||
460 | return sqrt(deviation); | |
461 | } | |
462 | ||
463 | static void Ping_compute_stats(PingObject* self) { | |
464 | // Compute the average latency | |
465 | self->stats.average = Ping_compute_average(self); | |
466 | ||
467 | // Compute the standard deviation | |
468 | self->stats.stddev = Ping_compute_stddev(self, self->stats.average); | |
469 | ||
470 | // Compute lost packets | |
471 | self->stats.loss = 1.0; | |
472 | self->stats.loss -= (double)self->stats.packets_rcvd \ | |
473 | / (double)self->stats.packets_sent; | |
474 | } | |
475 | ||
476 | static double time_elapsed(struct timeval* t0) { | |
477 | struct timeval now; | |
478 | gettimeofday(&now, NULL); | |
479 | ||
480 | double r = now.tv_sec - t0->tv_sec; | |
481 | r += ((double)now.tv_usec / 1000000) - ((double)t0->tv_usec / 1000000); | |
482 | ||
483 | return r; | |
484 | } | |
485 | ||
486 | static PyObject* Ping_ping(PingObject* self, PyObject* args, PyObject* kwds) { | |
487 | char* kwlist[] = {"count", "deadline", NULL}; | |
488 | size_t count = PING_DEFAULT_COUNT; | |
489 | double deadline = 0; | |
490 | ||
491 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Id", kwlist, &count, &deadline)) | |
492 | return NULL; | |
493 | ||
494 | int r = ping_host_add(self->ping, self->host); | |
495 | if (r) { | |
496 | PyErr_Format(PyExc_PingAddHostError, "Could not add host %s: %s", | |
497 | self->host, ping_get_error(self->ping)); | |
498 | return NULL; | |
499 | } | |
500 | ||
501 | // Reset all collected statistics in case ping() is called more than once. | |
502 | Ping_init_stats(self); | |
503 | ||
504 | // Save start time | |
505 | struct timeval time_start; | |
506 | r = gettimeofday(&time_start, NULL); | |
507 | if (r) { | |
508 | PyErr_Format(PyExc_RuntimeError, "Could not determine start time"); | |
509 | return NULL; | |
510 | } | |
511 | ||
512 | // Do the pinging | |
513 | while (count--) { | |
514 | self->stats.packets_sent++; | |
515 | ||
516 | Py_BEGIN_ALLOW_THREADS | |
517 | r = ping_send(self->ping); | |
518 | Py_END_ALLOW_THREADS | |
519 | ||
520 | // Count recieved packets | |
521 | if (r >= 0) { | |
522 | self->stats.packets_rcvd += r; | |
523 | ||
524 | // Raise any errors | |
525 | } else { | |
526 | PyErr_Format(PyExc_RuntimeError, "Error executing ping_send(): %s", | |
527 | ping_get_error(self->ping)); | |
528 | return NULL; | |
529 | } | |
530 | ||
531 | // Extract all data | |
532 | pingobj_iter_t* iter = ping_iterator_get(self->ping); | |
533 | ||
534 | double* latency = &self->stats.history[self->stats.history_index]; | |
535 | size_t buffer_size = sizeof(latency); | |
536 | ping_iterator_get_info(iter, PING_INFO_LATENCY, latency, &buffer_size); | |
537 | ||
538 | // Increase the history pointer | |
539 | self->stats.history_index++; | |
540 | self->stats.history_index %= sizeof(self->stats.history); | |
541 | ||
542 | // Increase the history size | |
543 | if (self->stats.history_size < sizeof(self->stats.history)) | |
544 | self->stats.history_size++; | |
545 | ||
546 | // Check if the deadline is due | |
547 | if (deadline > 0) { | |
548 | double elapsed_time = time_elapsed(&time_start); | |
549 | ||
550 | // If we have run longer than the deadline is, we end the main loop | |
551 | if (elapsed_time >= deadline) | |
552 | break; | |
553 | } | |
554 | } | |
555 | ||
556 | if (self->stats.packets_rcvd == 0) { | |
557 | PyErr_Format(PyExc_PingError, "No replies received from %s", self->host); | |
558 | return NULL; | |
559 | } | |
560 | ||
561 | Ping_compute_stats(self); | |
562 | ||
563 | Py_RETURN_NONE; | |
564 | } | |
565 | ||
566 | static PyObject* Ping_get_packets_sent(PingObject* self) { | |
567 | return PyLong_FromUnsignedLong(self->stats.packets_sent); | |
568 | } | |
569 | ||
570 | static PyObject* Ping_get_packets_rcvd(PingObject* self) { | |
571 | return PyLong_FromUnsignedLong(self->stats.packets_rcvd); | |
572 | } | |
573 | ||
574 | static PyObject* Ping_get_average(PingObject* self) { | |
575 | return PyFloat_FromDouble(self->stats.average); | |
576 | } | |
577 | ||
578 | static PyObject* Ping_get_stddev(PingObject* self) { | |
579 | return PyFloat_FromDouble(self->stats.stddev); | |
580 | } | |
581 | ||
582 | static PyObject* Ping_get_loss(PingObject* self) { | |
583 | return PyFloat_FromDouble(self->stats.loss); | |
584 | } | |
585 | ||
586 | static PyGetSetDef Ping_getsetters[] = { | |
587 | {"average", (getter)Ping_get_average, NULL, NULL, NULL}, | |
588 | {"loss", (getter)Ping_get_loss, NULL, NULL, NULL}, | |
589 | {"stddev", (getter)Ping_get_stddev, NULL, NULL, NULL}, | |
590 | {"packets_sent", (getter)Ping_get_packets_sent, NULL, NULL, NULL}, | |
591 | {"packets_rcvd", (getter)Ping_get_packets_rcvd, NULL, NULL, NULL}, | |
592 | {NULL} | |
593 | }; | |
594 | ||
595 | static PyMethodDef Ping_methods[] = { | |
596 | {"ping", (PyCFunction)Ping_ping, METH_VARARGS|METH_KEYWORDS, NULL}, | |
597 | {NULL} | |
598 | }; | |
599 | ||
600 | static PyTypeObject PingType = { | |
601 | PyVarObject_HEAD_INIT(NULL, 0) | |
602 | "_collecty.Ping", /*tp_name*/ | |
603 | sizeof(PingObject), /*tp_basicsize*/ | |
604 | 0, /*tp_itemsize*/ | |
605 | (destructor)Ping_dealloc, /*tp_dealloc*/ | |
606 | 0, /*tp_print*/ | |
607 | 0, /*tp_getattr*/ | |
608 | 0, /*tp_setattr*/ | |
609 | 0, /*tp_compare*/ | |
610 | 0, /*tp_repr*/ | |
611 | 0, /*tp_as_number*/ | |
612 | 0, /*tp_as_sequence*/ | |
613 | 0, /*tp_as_mapping*/ | |
614 | 0, /*tp_hash */ | |
615 | 0, /*tp_call*/ | |
616 | 0, /*tp_str*/ | |
617 | 0, /*tp_getattro*/ | |
618 | 0, /*tp_setattro*/ | |
619 | 0, /*tp_as_buffer*/ | |
620 | Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ | |
621 | "Ping object", /* tp_doc */ | |
622 | 0, /* tp_traverse */ | |
623 | 0, /* tp_clear */ | |
624 | 0, /* tp_richcompare */ | |
625 | 0, /* tp_weaklistoffset */ | |
626 | 0, /* tp_iter */ | |
627 | 0, /* tp_iternext */ | |
628 | Ping_methods, /* tp_methods */ | |
629 | 0, /* tp_members */ | |
630 | Ping_getsetters, /* tp_getset */ | |
631 | 0, /* tp_base */ | |
632 | 0, /* tp_dict */ | |
633 | 0, /* tp_descr_get */ | |
634 | 0, /* tp_descr_set */ | |
635 | 0, /* tp_dictoffset */ | |
636 | (initproc)Ping_init, /* tp_init */ | |
637 | 0, /* tp_alloc */ | |
638 | Ping_new, /* tp_new */ | |
639 | }; | |
640 | ||
641 | typedef struct { | |
642 | PyObject_HEAD | |
643 | const sensors_chip_name* chip; | |
644 | const sensors_feature* feature; | |
645 | } SensorObject; | |
646 | ||
647 | static void Sensor_dealloc(SensorObject* self) { | |
648 | Py_TYPE(self)->tp_free((PyObject*)self); | |
649 | } | |
650 | ||
651 | static PyObject* Sensor_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { | |
652 | SensorObject* self = (SensorObject*)type->tp_alloc(type, 0); | |
653 | ||
654 | return (PyObject *)self; | |
655 | } | |
656 | ||
657 | static int Sensor_init(SensorObject* self, PyObject* args, PyObject* kwds) { | |
658 | return 0; | |
659 | } | |
660 | ||
661 | static PyObject* Sensor_get_label(SensorObject* self) { | |
662 | char* label = sensors_get_label(self->chip, self->feature); | |
663 | ||
664 | if (label) { | |
665 | PyObject* string = PyUnicode_FromString(label); | |
666 | free(label); | |
667 | ||
668 | return string; | |
669 | } | |
670 | ||
671 | Py_RETURN_NONE; | |
672 | } | |
673 | ||
674 | static PyObject* Sensor_get_name(SensorObject* self) { | |
675 | char chip_name[512]; | |
676 | ||
677 | int r = sensors_snprintf_chip_name(chip_name, sizeof(chip_name), self->chip); | |
678 | if (r < 0) { | |
679 | PyErr_Format(PyExc_RuntimeError, "Could not print chip name"); | |
680 | return NULL; | |
681 | } | |
682 | ||
683 | return PyUnicode_FromString(chip_name); | |
684 | } | |
685 | ||
686 | static PyObject* Sensor_get_type(SensorObject* self) { | |
687 | const char* type = NULL; | |
688 | ||
689 | switch (self->feature->type) { | |
690 | case SENSORS_FEATURE_IN: | |
691 | type = "voltage"; | |
692 | break; | |
693 | ||
694 | case SENSORS_FEATURE_FAN: | |
695 | type = "fan"; | |
696 | break; | |
697 | ||
698 | case SENSORS_FEATURE_TEMP: | |
699 | type = "temperature"; | |
700 | break; | |
701 | ||
702 | case SENSORS_FEATURE_POWER: | |
703 | type = "power"; | |
704 | break; | |
705 | ||
706 | default: | |
707 | break; | |
708 | } | |
709 | ||
710 | if (type) | |
711 | return PyUnicode_FromString(type); | |
712 | ||
713 | Py_RETURN_NONE; | |
714 | } | |
715 | ||
716 | static PyObject* Sensor_get_bus(SensorObject* self) { | |
717 | const char* type = NULL; | |
718 | ||
719 | switch (self->chip->bus.type) { | |
720 | case SENSORS_BUS_TYPE_I2C: | |
721 | type = "i2c"; | |
722 | break; | |
723 | ||
724 | case SENSORS_BUS_TYPE_ISA: | |
725 | type = "isa"; | |
726 | break; | |
727 | ||
728 | case SENSORS_BUS_TYPE_PCI: | |
729 | type = "pci"; | |
730 | break; | |
731 | ||
732 | case SENSORS_BUS_TYPE_SPI: | |
733 | type = "spi"; | |
734 | break; | |
735 | ||
736 | case SENSORS_BUS_TYPE_VIRTUAL: | |
737 | type = "virtual"; | |
738 | break; | |
739 | ||
740 | case SENSORS_BUS_TYPE_ACPI: | |
741 | type = "acpi"; | |
742 | break; | |
743 | ||
744 | case SENSORS_BUS_TYPE_HID: | |
745 | type = "hid"; | |
746 | break; | |
747 | ||
748 | default: | |
749 | break; | |
750 | } | |
751 | ||
752 | if (type) | |
753 | return PyUnicode_FromString(type); | |
754 | ||
755 | Py_RETURN_NONE; | |
756 | } | |
757 | ||
758 | static const sensors_subfeature* Sensor_get_subfeature(SensorObject* sensor, sensors_subfeature_type type) { | |
759 | const sensors_subfeature* subfeature; | |
760 | int subfeature_num = 0; | |
761 | ||
762 | while ((subfeature = sensors_get_all_subfeatures(sensor->chip, sensor->feature, &subfeature_num))) { | |
763 | if (subfeature->type == type) | |
764 | break; | |
765 | } | |
766 | ||
767 | return subfeature; | |
768 | } | |
769 | ||
770 | static PyObject* Sensor_return_value(SensorObject* sensor, sensors_subfeature_type subfeature_type) { | |
771 | double value; | |
772 | ||
773 | const sensors_subfeature* subfeature = Sensor_get_subfeature(sensor, subfeature_type); | |
774 | if (!subfeature) { | |
775 | PyErr_Format(PyExc_AttributeError, "Could not find sensor of requested type"); | |
776 | return NULL; | |
777 | } | |
778 | ||
779 | // Fetch value from the sensor | |
780 | int r = sensors_get_value(sensor->chip, subfeature->number, &value); | |
781 | if (r < 0) { | |
782 | PyErr_Format(PyExc_ValueError, "Error retrieving value from sensor: %s", | |
783 | sensors_strerror(errno)); | |
784 | return NULL; | |
785 | } | |
786 | ||
787 | // Convert all temperature values from Celcius to Kelvon | |
788 | if (sensor->feature->type == SENSORS_FEATURE_TEMP) | |
789 | value += 273.15; | |
790 | ||
791 | return PyFloat_FromDouble(value); | |
792 | } | |
793 | ||
794 | static PyObject* Sensor_no_value() { | |
795 | PyErr_Format(PyExc_ValueError, "Value not supported for this sensor type"); | |
796 | return NULL; | |
797 | } | |
798 | ||
799 | static PyObject* Sensor_get_value(SensorObject* self) { | |
800 | sensors_subfeature_type subfeature_type; | |
801 | ||
802 | switch (self->feature->type) { | |
803 | case SENSORS_FEATURE_IN: | |
804 | subfeature_type = SENSORS_SUBFEATURE_IN_INPUT; | |
805 | break; | |
806 | ||
807 | case SENSORS_FEATURE_FAN: | |
808 | subfeature_type = SENSORS_SUBFEATURE_FAN_INPUT; | |
809 | break; | |
810 | ||
811 | case SENSORS_FEATURE_TEMP: | |
812 | subfeature_type = SENSORS_SUBFEATURE_TEMP_INPUT; | |
813 | break; | |
814 | ||
815 | case SENSORS_FEATURE_POWER: | |
816 | subfeature_type = SENSORS_SUBFEATURE_POWER_INPUT; | |
817 | break; | |
818 | ||
819 | default: | |
820 | return Sensor_no_value(); | |
821 | } | |
822 | ||
823 | return Sensor_return_value(self, subfeature_type); | |
824 | } | |
825 | ||
826 | static PyObject* Sensor_get_critical(SensorObject* self) { | |
827 | sensors_subfeature_type subfeature_type; | |
828 | ||
829 | switch (self->feature->type) { | |
830 | case SENSORS_FEATURE_IN: | |
831 | subfeature_type = SENSORS_SUBFEATURE_IN_CRIT; | |
832 | break; | |
833 | ||
834 | case SENSORS_FEATURE_TEMP: | |
835 | subfeature_type = SENSORS_SUBFEATURE_TEMP_CRIT; | |
836 | break; | |
837 | ||
838 | case SENSORS_FEATURE_POWER: | |
839 | subfeature_type = SENSORS_SUBFEATURE_POWER_CRIT; | |
840 | break; | |
841 | ||
842 | default: | |
843 | return Sensor_no_value(); | |
844 | } | |
845 | ||
846 | return Sensor_return_value(self, subfeature_type); | |
847 | } | |
848 | ||
849 | static PyObject* Sensor_get_maximum(SensorObject* self) { | |
850 | sensors_subfeature_type subfeature_type; | |
851 | ||
852 | switch (self->feature->type) { | |
853 | case SENSORS_FEATURE_IN: | |
854 | subfeature_type = SENSORS_SUBFEATURE_IN_MAX; | |
855 | break; | |
856 | ||
857 | case SENSORS_FEATURE_FAN: | |
858 | subfeature_type = SENSORS_SUBFEATURE_FAN_MAX; | |
859 | break; | |
860 | ||
861 | case SENSORS_FEATURE_TEMP: | |
862 | subfeature_type = SENSORS_SUBFEATURE_TEMP_MAX; | |
863 | break; | |
864 | ||
865 | case SENSORS_FEATURE_POWER: | |
866 | subfeature_type = SENSORS_SUBFEATURE_POWER_MAX; | |
867 | break; | |
868 | ||
869 | default: | |
870 | return Sensor_no_value(); | |
871 | } | |
872 | ||
873 | return Sensor_return_value(self, subfeature_type); | |
874 | } | |
875 | ||
876 | static PyObject* Sensor_get_minimum(SensorObject* self) { | |
877 | sensors_subfeature_type subfeature_type; | |
878 | ||
879 | switch (self->feature->type) { | |
880 | case SENSORS_FEATURE_IN: | |
881 | subfeature_type = SENSORS_SUBFEATURE_IN_MIN; | |
882 | break; | |
883 | ||
884 | case SENSORS_FEATURE_FAN: | |
885 | subfeature_type = SENSORS_SUBFEATURE_FAN_MIN; | |
886 | break; | |
887 | ||
888 | case SENSORS_FEATURE_TEMP: | |
889 | subfeature_type = SENSORS_SUBFEATURE_TEMP_MIN; | |
890 | break; | |
891 | ||
892 | default: | |
893 | return Sensor_no_value(); | |
894 | } | |
895 | ||
896 | return Sensor_return_value(self, subfeature_type); | |
897 | } | |
898 | ||
899 | static PyObject* Sensor_get_high(SensorObject* self) { | |
900 | sensors_subfeature_type subfeature_type; | |
901 | ||
902 | switch (self->feature->type) { | |
903 | case SENSORS_FEATURE_TEMP: | |
904 | subfeature_type = SENSORS_SUBFEATURE_TEMP_MAX; | |
905 | break; | |
906 | ||
907 | default: | |
908 | return Sensor_no_value(); | |
909 | } | |
910 | ||
911 | return Sensor_return_value(self, subfeature_type); | |
912 | } | |
913 | ||
914 | static PyGetSetDef Sensor_getsetters[] = { | |
915 | {"bus", (getter)Sensor_get_bus, NULL, NULL, NULL}, | |
916 | {"critical", (getter)Sensor_get_critical, NULL, NULL, NULL}, | |
917 | {"high", (getter)Sensor_get_high, NULL, NULL, NULL}, | |
918 | {"label", (getter)Sensor_get_label, NULL, NULL, NULL}, | |
919 | {"maximum", (getter)Sensor_get_maximum, NULL, NULL, NULL}, | |
920 | {"minumum", (getter)Sensor_get_minimum, NULL, NULL, NULL}, | |
921 | {"name", (getter)Sensor_get_name, NULL, NULL, NULL}, | |
922 | {"type", (getter)Sensor_get_type, NULL, NULL, NULL}, | |
923 | {"value", (getter)Sensor_get_value, NULL, NULL, NULL}, | |
924 | {NULL}, | |
925 | }; | |
926 | ||
927 | static PyTypeObject SensorType = { | |
928 | PyObject_HEAD_INIT(NULL) | |
929 | "_collecty.Sensor", /*tp_name*/ | |
930 | sizeof(SensorObject), /*tp_basicsize*/ | |
931 | 0, /*tp_itemsize*/ | |
932 | (destructor)Sensor_dealloc, /*tp_dealloc*/ | |
933 | 0, /*tp_print*/ | |
934 | 0, /*tp_getattr*/ | |
935 | 0, /*tp_setattr*/ | |
936 | 0, /*tp_compare*/ | |
937 | 0, /*tp_repr*/ | |
938 | 0, /*tp_as_number*/ | |
939 | 0, /*tp_as_sequence*/ | |
940 | 0, /*tp_as_mapping*/ | |
941 | 0, /*tp_hash */ | |
942 | 0, /*tp_call*/ | |
943 | 0, /*tp_str*/ | |
944 | 0, /*tp_getattro*/ | |
945 | 0, /*tp_setattro*/ | |
946 | 0, /*tp_as_buffer*/ | |
947 | Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ | |
948 | "Sensor objects", /* tp_doc */ | |
949 | 0, /* tp_traverse */ | |
950 | 0, /* tp_clear */ | |
951 | 0, /* tp_richcompare */ | |
952 | 0, /* tp_weaklistoffset */ | |
953 | 0, /* tp_iter */ | |
954 | 0, /* tp_iternext */ | |
955 | 0, /* tp_methods */ | |
956 | 0, /* tp_members */ | |
957 | Sensor_getsetters, /* tp_getset */ | |
958 | 0, /* tp_base */ | |
959 | 0, /* tp_dict */ | |
960 | 0, /* tp_descr_get */ | |
961 | 0, /* tp_descr_set */ | |
962 | 0, /* tp_dictoffset */ | |
963 | (initproc)Sensor_init, /* tp_init */ | |
964 | 0, /* tp_alloc */ | |
965 | Sensor_new, /* tp_new */ | |
966 | }; | |
967 | ||
968 | static SensorObject* make_sensor_object(const sensors_chip_name* chip, const sensors_feature* feature) { | |
969 | SensorObject* sensor = PyObject_New(SensorObject, &SensorType); | |
970 | if (!sensor) | |
971 | return NULL; | |
972 | ||
973 | if (!PyObject_Init((PyObject*)sensor, &SensorType)) { | |
974 | Py_DECREF(sensor); | |
975 | return NULL; | |
976 | } | |
977 | ||
978 | sensor->chip = chip; | |
979 | sensor->feature = feature; | |
980 | ||
981 | return sensor; | |
982 | } | |
983 | ||
984 | static PyObject* _collecty_sensors_init() { | |
985 | // Clean up everything first in case sensors_init was called earlier | |
986 | sensors_cleanup(); | |
987 | ||
988 | int r = sensors_init(NULL); | |
989 | if (r) { | |
990 | PyErr_Format(PyExc_OSError, "Could not initialise sensors: %s", | |
991 | sensors_strerror(errno)); | |
992 | return NULL; | |
993 | } | |
994 | ||
995 | Py_RETURN_NONE; | |
996 | } | |
997 | ||
998 | static PyObject* _collecty_sensors_cleanup() { | |
999 | sensors_cleanup(); | |
1000 | Py_RETURN_NONE; | |
1001 | } | |
1002 | ||
1003 | static PyObject* _collecty_get_detected_sensors(PyObject* o, PyObject* args) { | |
1004 | const char* name = NULL; | |
1005 | sensors_chip_name chip_name; | |
1006 | ||
1007 | if (!PyArg_ParseTuple(args, "|z", &name)) | |
1008 | return NULL; | |
1009 | ||
1010 | if (name) { | |
1011 | int r = sensors_parse_chip_name(name, &chip_name); | |
1012 | if (r < 0) { | |
1013 | PyErr_Format(PyExc_ValueError, "Could not parse chip name: %s", name); | |
1014 | return NULL; | |
1015 | } | |
1016 | } | |
1017 | ||
1018 | PyObject* list = PyList_New(0); | |
1019 | ||
1020 | const sensors_chip_name* chip; | |
1021 | int chip_num = 0; | |
1022 | ||
1023 | while ((chip = sensors_get_detected_chips((name) ? &chip_name : NULL, &chip_num))) { | |
1024 | const sensors_feature* feature; | |
1025 | int feature_num = 0; | |
1026 | ||
1027 | while ((feature = sensors_get_features(chip, &feature_num))) { | |
1028 | // Skip sensors we do not want to support | |
1029 | switch (feature->type) { | |
1030 | case SENSORS_FEATURE_IN: | |
1031 | case SENSORS_FEATURE_FAN: | |
1032 | case SENSORS_FEATURE_TEMP: | |
1033 | case SENSORS_FEATURE_POWER: | |
1034 | break; | |
1035 | ||
1036 | default: | |
1037 | continue; | |
1038 | } | |
1039 | ||
1040 | SensorObject* sensor = make_sensor_object(chip, feature); | |
1041 | PyList_Append(list, (PyObject*)sensor); | |
1042 | } | |
1043 | } | |
1044 | ||
1045 | return list; | |
1046 | } | |
1047 | ||
1048 | static int _collecty_mountpoint_is_virtual(const struct mntent* mp) { | |
1049 | // Ignore all ramdisks | |
1050 | if (mp->mnt_fsname[0] != '/') | |
1051 | return 1; | |
1052 | ||
1053 | // Ignore network mounts | |
1054 | if (hasmntopt(mp, "_netdev") != NULL) | |
1055 | return 1; | |
1056 | ||
1057 | return 0; | |
1058 | } | |
1059 | ||
1060 | static PyObject* _collecty_get_mountpoints() { | |
1061 | FILE* fp = setmntent(_PATH_MOUNTED, "r"); | |
1062 | if (!fp) | |
1063 | return NULL; | |
1064 | ||
1065 | PyObject* list = PyList_New(0); | |
1066 | int r = 0; | |
1067 | ||
1068 | struct mntent* mountpoint = getmntent(fp); | |
1069 | while (mountpoint) { | |
1070 | if (!_collecty_mountpoint_is_virtual(mountpoint)) { | |
1071 | // Create a tuple with the information of the mountpoint | |
1072 | PyObject* mp = PyTuple_New(4); | |
1073 | PyTuple_SET_ITEM(mp, 0, PyUnicode_FromString(mountpoint->mnt_fsname)); | |
1074 | PyTuple_SET_ITEM(mp, 1, PyUnicode_FromString(mountpoint->mnt_dir)); | |
1075 | PyTuple_SET_ITEM(mp, 2, PyUnicode_FromString(mountpoint->mnt_type)); | |
1076 | PyTuple_SET_ITEM(mp, 3, PyUnicode_FromString(mountpoint->mnt_opts)); | |
1077 | ||
1078 | // Append the tuple to the list | |
1079 | r = PyList_Append(list, mp); | |
1080 | if (r) | |
1081 | break; | |
1082 | } | |
1083 | ||
1084 | // Move on to the next mountpoint | |
1085 | mountpoint = getmntent(fp); | |
1086 | } | |
1087 | ||
1088 | endmntent(fp); | |
1089 | ||
1090 | if (r) { | |
1091 | Py_DECREF(list); | |
1092 | return NULL; | |
1093 | } | |
1094 | ||
1095 | return list; | |
1096 | } | |
1097 | ||
1098 | static PyMethodDef collecty_module_methods[] = { | |
1099 | {"get_detected_sensors", (PyCFunction)_collecty_get_detected_sensors, METH_VARARGS, NULL}, | |
1100 | {"get_mountpoints", (PyCFunction)_collecty_get_mountpoints, METH_NOARGS, NULL}, | |
1101 | {"sensors_cleanup", (PyCFunction)_collecty_sensors_cleanup, METH_NOARGS, NULL}, | |
1102 | {"sensors_init", (PyCFunction)_collecty_sensors_init, METH_NOARGS, NULL}, | |
1103 | {NULL}, | |
1104 | }; | |
1105 | ||
1106 | static struct PyModuleDef collecty_module = { | |
1107 | PyModuleDef_HEAD_INIT, | |
1108 | "_collecty", /* m_name */ | |
1109 | "_collecty module", /* m_doc */ | |
1110 | -1, /* m_size */ | |
1111 | collecty_module_methods, /* m_methods */ | |
1112 | NULL, /* m_reload */ | |
1113 | NULL, /* m_traverse */ | |
1114 | NULL, /* m_clear */ | |
1115 | NULL, /* m_free */ | |
1116 | }; | |
1117 | ||
1118 | PyMODINIT_FUNC PyInit__collecty(void) { | |
1119 | if (PyType_Ready(&BlockDeviceType) < 0) | |
1120 | return NULL; | |
1121 | ||
1122 | if (PyType_Ready(&PingType) < 0) | |
1123 | return NULL; | |
1124 | ||
1125 | if (PyType_Ready(&SensorType) < 0) | |
1126 | return NULL; | |
1127 | ||
1128 | PyObject* m = PyModule_Create(&collecty_module); | |
1129 | ||
1130 | Py_INCREF(&BlockDeviceType); | |
1131 | PyModule_AddObject(m, "BlockDevice", (PyObject*)&BlockDeviceType); | |
1132 | ||
1133 | Py_INCREF(&PingType); | |
1134 | PyModule_AddObject(m, "Ping", (PyObject*)&PingType); | |
1135 | ||
1136 | PyExc_PingError = PyErr_NewException("_collecty.PingError", NULL, NULL); | |
1137 | Py_INCREF(PyExc_PingError); | |
1138 | PyModule_AddObject(m, "PingError", PyExc_PingError); | |
1139 | ||
1140 | PyExc_PingAddHostError = PyErr_NewException("_collecty.PingAddHostError", NULL, NULL); | |
1141 | Py_INCREF(PyExc_PingAddHostError); | |
1142 | PyModule_AddObject(m, "PingAddHostError", PyExc_PingAddHostError); | |
1143 | ||
1144 | Py_INCREF(&SensorType); | |
1145 | PyModule_AddObject(m, "Sensor", (PyObject*)&SensorType); | |
1146 | ||
1147 | return m; | |
1148 | } |