]>
Commit | Line | Data |
---|---|---|
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 |
40 | typedef struct { |
41 | PyObject_HEAD | |
42 | char* path; | |
43 | struct hd_driveid identity; | |
44 | SkDisk* disk; | |
45 | } BlockDevice; | |
46 | ||
47 | static 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 | ||
57 | static 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 | ||
73 | static 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 | ||
86 | static 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 | ||
99 | static 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 | ||
112 | static 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 | ||
149 | static PyObject* BlockDevice_get_path(PyObject* self) { | |
150 | BlockDevice* device = (BlockDevice*)self; | |
151 | ||
f37913e8 | 152 | return PyUnicode_FromString(device->path); |
30777a6c MT |
153 | } |
154 | ||
155 | static void clean_string(char *s) { | |
156 | for (char* e = s; *e; e++) { | |
157 | if (*e < ' ' || *e >= 127) | |
158 | *e = ' '; | |
159 | } | |
160 | } | |
161 | ||
162 | static 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 | ||
186 | static 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 | ||
198 | static 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 | ||
207 | static 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 | ||
216 | static 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 | ||
225 | static 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 | ||
234 | static 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 | ||
250 | static 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 | ||
267 | static 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 | ||
273 | static 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 | ||
281 | static 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 |
322 | static PyObject* PyExc_PingError; |
323 | static PyObject* PyExc_PingAddHostError; | |
324 | ||
325 | typedef 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 | ||
341 | static 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 | ||
348 | static 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 | ||
359 | static 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 | ||
372 | static 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 | ||
432 | static 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 | ||
445 | static 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 | ||
462 | static 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 | ||
475 | static 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 | ||
485 | static 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 | ||
565 | static PyObject* Ping_get_packets_sent(PingObject* self) { | |
566 | return PyLong_FromUnsignedLong(self->stats.packets_sent); | |
567 | } | |
568 | ||
569 | static PyObject* Ping_get_packets_rcvd(PingObject* self) { | |
570 | return PyLong_FromUnsignedLong(self->stats.packets_rcvd); | |
571 | } | |
572 | ||
573 | static PyObject* Ping_get_average(PingObject* self) { | |
574 | return PyFloat_FromDouble(self->stats.average); | |
575 | } | |
576 | ||
577 | static PyObject* Ping_get_stddev(PingObject* self) { | |
578 | return PyFloat_FromDouble(self->stats.stddev); | |
579 | } | |
580 | ||
581 | static PyObject* Ping_get_loss(PingObject* self) { | |
582 | return PyFloat_FromDouble(self->stats.loss); | |
583 | } | |
584 | ||
585 | static 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 | ||
594 | static PyMethodDef Ping_methods[] = { | |
595 | {"ping", (PyCFunction)Ping_ping, METH_VARARGS|METH_KEYWORDS, NULL}, | |
596 | {NULL} | |
597 | }; | |
598 | ||
599 | static 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 |
640 | typedef struct { |
641 | PyObject_HEAD | |
642 | const sensors_chip_name* chip; | |
643 | const sensors_feature* feature; | |
644 | } SensorObject; | |
645 | ||
646 | static void Sensor_dealloc(SensorObject* self) { | |
f37913e8 | 647 | Py_TYPE(self)->tp_free((PyObject*)self); |
c389fccf MT |
648 | } |
649 | ||
650 | static 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 | ||
656 | static int Sensor_init(SensorObject* self, PyObject* args, PyObject* kwds) { | |
657 | return 0; | |
658 | } | |
659 | ||
660 | static 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 | ||
673 | static 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 | ||
685 | static 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 | ||
715 | static 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 | ||
757 | static 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 | ||
769 | static 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 | ||
793 | static PyObject* Sensor_no_value() { | |
794 | PyErr_Format(PyExc_ValueError, "Value not supported for this sensor type"); | |
795 | return NULL; | |
796 | } | |
797 | ||
798 | static 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 | ||
825 | static 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 | ||
848 | static 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 | ||
875 | static 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 | ||
898 | static 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 | ||
913 | static 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 | ||
926 | static 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 | ||
967 | static 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 | ||
983 | static 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 | ||
997 | static PyObject* _collecty_sensors_cleanup() { | |
998 | sensors_cleanup(); | |
999 | Py_RETURN_NONE; | |
1000 | } | |
1001 | ||
1002 | static 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 | 1047 | static 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 |
1054 | static 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 | ||
1066 | PyMODINIT_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 | } |