]> git.ipfire.org Git - collecty.git/blame - src/_collectymodule.c
Add df plugin
[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>
9823dfef 25#include <mntent.h>
63f9f8be 26#include <oping.h>
c389fccf
MT
27#include <sensors/error.h>
28#include <sensors/sensors.h>
30777a6c
MT
29#include <stdbool.h>
30#include <string.h>
31#include <sys/ioctl.h>
63f9f8be 32#include <time.h>
30777a6c
MT
33
34#define MODEL_SIZE 40
35#define SERIAL_SIZE 20
36
63f9f8be
MT
37#define PING_HISTORY_SIZE 1024
38#define PING_DEFAULT_COUNT 10
39#define PING_DEFAULT_TIMEOUT 8
40
30777a6c
MT
41typedef struct {
42 PyObject_HEAD
43 char* path;
44 struct hd_driveid identity;
45 SkDisk* disk;
46} BlockDevice;
47
48static 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
f37913e8 55 Py_TYPE(self)->tp_free((PyObject*)self);
30777a6c
MT
56}
57
58static 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
74static 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
87static 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
100static 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
113static 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
150static PyObject* BlockDevice_get_path(PyObject* self) {
151 BlockDevice* device = (BlockDevice*)self;
152
f37913e8 153 return PyUnicode_FromString(device->path);
30777a6c
MT
154}
155
156static void clean_string(char *s) {
157 for (char* e = s; *e; e++) {
158 if (*e < ' ' || *e >= 127)
159 *e = ' ';
160 }
161}
162
163static 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
187static 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
199static 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
f37913e8 205 return PyUnicode_FromString(model);
30777a6c
MT
206}
207
208static 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
f37913e8 214 return PyUnicode_FromString(serial);
30777a6c
MT
215}
216
217static 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
226static 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
235static 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
251static 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
6f04ea33
MT
264 // Convert the temperature to Kelvin
265 return PyFloat_FromDouble((double)mkelvin / 1000.0);
30777a6c
MT
266}
267
268static 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
274static 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
282static PyTypeObject BlockDeviceType = {
f37913e8 283 PyVarObject_HEAD_INIT(NULL, 0)
30777a6c
MT
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
63f9f8be
MT
323static PyObject* PyExc_PingError;
324static PyObject* PyExc_PingAddHostError;
325
326typedef 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
342static 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
349static 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
360static 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
373static 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
433static 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
446static 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
463static 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
476static 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
486static 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) {
c6e8015b 557 PyErr_Format(PyExc_PingError, "No replies received from %s", self->host);
63f9f8be
MT
558 return NULL;
559 }
560
561 Ping_compute_stats(self);
562
563 Py_RETURN_NONE;
564}
565
566static PyObject* Ping_get_packets_sent(PingObject* self) {
567 return PyLong_FromUnsignedLong(self->stats.packets_sent);
568}
569
570static PyObject* Ping_get_packets_rcvd(PingObject* self) {
571 return PyLong_FromUnsignedLong(self->stats.packets_rcvd);
572}
573
574static PyObject* Ping_get_average(PingObject* self) {
575 return PyFloat_FromDouble(self->stats.average);
576}
577
578static PyObject* Ping_get_stddev(PingObject* self) {
579 return PyFloat_FromDouble(self->stats.stddev);
580}
581
582static PyObject* Ping_get_loss(PingObject* self) {
583 return PyFloat_FromDouble(self->stats.loss);
584}
585
586static 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
595static PyMethodDef Ping_methods[] = {
596 {"ping", (PyCFunction)Ping_ping, METH_VARARGS|METH_KEYWORDS, NULL},
597 {NULL}
598};
599
600static 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
c389fccf
MT
641typedef struct {
642 PyObject_HEAD
643 const sensors_chip_name* chip;
644 const sensors_feature* feature;
645} SensorObject;
646
647static void Sensor_dealloc(SensorObject* self) {
f37913e8 648 Py_TYPE(self)->tp_free((PyObject*)self);
c389fccf
MT
649}
650
651static 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
657static int Sensor_init(SensorObject* self, PyObject* args, PyObject* kwds) {
658 return 0;
659}
660
661static PyObject* Sensor_get_label(SensorObject* self) {
662 char* label = sensors_get_label(self->chip, self->feature);
663
664 if (label) {
f37913e8 665 PyObject* string = PyUnicode_FromString(label);
c389fccf
MT
666 free(label);
667
668 return string;
669 }
670
671 Py_RETURN_NONE;
672}
673
674static 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
f37913e8 683 return PyUnicode_FromString(chip_name);
c389fccf
MT
684}
685
686static 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)
f37913e8 711 return PyUnicode_FromString(type);
c389fccf
MT
712
713 Py_RETURN_NONE;
714}
715
716static 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)
f37913e8 753 return PyUnicode_FromString(type);
c389fccf
MT
754
755 Py_RETURN_NONE;
756}
757
758static 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
770static 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
794static PyObject* Sensor_no_value() {
795 PyErr_Format(PyExc_ValueError, "Value not supported for this sensor type");
796 return NULL;
797}
798
799static 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
826static 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
849static 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
876static 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
899static 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
914static 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
927static PyTypeObject SensorType = {
928 PyObject_HEAD_INIT(NULL)
c389fccf
MT
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
968static 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
984static 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
998static PyObject* _collecty_sensors_cleanup() {
999 sensors_cleanup();
1000 Py_RETURN_NONE;
1001}
1002
1003static 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
9823dfef
MT
1048static 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
1060static 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
30777a6c 1098static PyMethodDef collecty_module_methods[] = {
c389fccf 1099 {"get_detected_sensors", (PyCFunction)_collecty_get_detected_sensors, METH_VARARGS, NULL},
9823dfef 1100 {"get_mountpoints", (PyCFunction)_collecty_get_mountpoints, METH_NOARGS, NULL},
c389fccf
MT
1101 {"sensors_cleanup", (PyCFunction)_collecty_sensors_cleanup, METH_NOARGS, NULL},
1102 {"sensors_init", (PyCFunction)_collecty_sensors_init, METH_NOARGS, NULL},
30777a6c
MT
1103 {NULL},
1104};
1105
f37913e8
MT
1106static 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
1118PyMODINIT_FUNC PyInit__collecty(void) {
30777a6c 1119 if (PyType_Ready(&BlockDeviceType) < 0)
f37913e8 1120 return NULL;
30777a6c 1121
63f9f8be
MT
1122 if (PyType_Ready(&PingType) < 0)
1123 return NULL;
1124
c389fccf 1125 if (PyType_Ready(&SensorType) < 0)
f37913e8 1126 return NULL;
c389fccf 1127
f37913e8 1128 PyObject* m = PyModule_Create(&collecty_module);
30777a6c 1129
f37913e8 1130 Py_INCREF(&BlockDeviceType);
30777a6c 1131 PyModule_AddObject(m, "BlockDevice", (PyObject*)&BlockDeviceType);
f37913e8 1132
63f9f8be
MT
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
f37913e8 1144 Py_INCREF(&SensorType);
c389fccf 1145 PyModule_AddObject(m, "Sensor", (PyObject*)&SensorType);
f37913e8
MT
1146
1147 return m;
30777a6c 1148}