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