#include <errno.h>
#include <fcntl.h>
#include <linux/hdreg.h>
+#include <sensors/error.h>
+#include <sensors/sensors.h>
#include <stdbool.h>
#include <string.h>
#include <sys/ioctl.h>
BlockDevice_new, /* tp_new */
};
+typedef struct {
+ PyObject_HEAD
+ const sensors_chip_name* chip;
+ const sensors_feature* feature;
+} SensorObject;
+
+static void Sensor_dealloc(SensorObject* self) {
+ self->ob_type->tp_free((PyObject*)self);
+}
+
+static PyObject* Sensor_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
+ SensorObject* self = (SensorObject*)type->tp_alloc(type, 0);
+
+ return (PyObject *)self;
+}
+
+static int Sensor_init(SensorObject* self, PyObject* args, PyObject* kwds) {
+ return 0;
+}
+
+static PyObject* Sensor_get_label(SensorObject* self) {
+ char* label = sensors_get_label(self->chip, self->feature);
+
+ if (label) {
+ PyObject* string = PyString_FromString(label);
+ free(label);
+
+ return string;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject* Sensor_get_name(SensorObject* self) {
+ char chip_name[512];
+
+ int r = sensors_snprintf_chip_name(chip_name, sizeof(chip_name), self->chip);
+ if (r < 0) {
+ PyErr_Format(PyExc_RuntimeError, "Could not print chip name");
+ return NULL;
+ }
+
+ return PyString_FromString(chip_name);
+}
+
+static PyObject* Sensor_get_type(SensorObject* self) {
+ const char* type = NULL;
+
+ switch (self->feature->type) {
+ case SENSORS_FEATURE_IN:
+ type = "voltage";
+ break;
+
+ case SENSORS_FEATURE_FAN:
+ type = "fan";
+ break;
+
+ case SENSORS_FEATURE_TEMP:
+ type = "temperature";
+ break;
+
+ case SENSORS_FEATURE_POWER:
+ type = "power";
+ break;
+
+ default:
+ break;
+ }
+
+ if (type)
+ return PyString_FromString(type);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject* Sensor_get_bus(SensorObject* self) {
+ const char* type = NULL;
+
+ switch (self->chip->bus.type) {
+ case SENSORS_BUS_TYPE_I2C:
+ type = "i2c";
+ break;
+
+ case SENSORS_BUS_TYPE_ISA:
+ type = "isa";
+ break;
+
+ case SENSORS_BUS_TYPE_PCI:
+ type = "pci";
+ break;
+
+ case SENSORS_BUS_TYPE_SPI:
+ type = "spi";
+ break;
+
+ case SENSORS_BUS_TYPE_VIRTUAL:
+ type = "virtual";
+ break;
+
+ case SENSORS_BUS_TYPE_ACPI:
+ type = "acpi";
+ break;
+
+ case SENSORS_BUS_TYPE_HID:
+ type = "hid";
+ break;
+
+ default:
+ break;
+ }
+
+ if (type)
+ return PyString_FromString(type);
+
+ Py_RETURN_NONE;
+}
+
+static const sensors_subfeature* Sensor_get_subfeature(SensorObject* sensor, sensors_subfeature_type type) {
+ const sensors_subfeature* subfeature;
+ int subfeature_num = 0;
+
+ while ((subfeature = sensors_get_all_subfeatures(sensor->chip, sensor->feature, &subfeature_num))) {
+ if (subfeature->type == type)
+ break;
+ }
+
+ return subfeature;
+}
+
+static PyObject* Sensor_return_value(SensorObject* sensor, sensors_subfeature_type subfeature_type) {
+ double value;
+
+ const sensors_subfeature* subfeature = Sensor_get_subfeature(sensor, subfeature_type);
+ if (!subfeature) {
+ PyErr_Format(PyExc_AttributeError, "Could not find sensor of requested type");
+ return NULL;
+ }
+
+ // Fetch value from the sensor
+ int r = sensors_get_value(sensor->chip, subfeature->number, &value);
+ if (r < 0) {
+ PyErr_Format(PyExc_ValueError, "Error retrieving value from sensor: %s",
+ sensors_strerror(errno));
+ return NULL;
+ }
+
+ // Convert all temperature values from Celcius to Kelvon
+ if (sensor->feature->type == SENSORS_FEATURE_TEMP)
+ value += 273.15;
+
+ return PyFloat_FromDouble(value);
+}
+
+static PyObject* Sensor_no_value() {
+ PyErr_Format(PyExc_ValueError, "Value not supported for this sensor type");
+ return NULL;
+}
+
+static PyObject* Sensor_get_value(SensorObject* self) {
+ sensors_subfeature_type subfeature_type;
+
+ switch (self->feature->type) {
+ case SENSORS_FEATURE_IN:
+ subfeature_type = SENSORS_SUBFEATURE_IN_INPUT;
+ break;
+
+ case SENSORS_FEATURE_FAN:
+ subfeature_type = SENSORS_SUBFEATURE_FAN_INPUT;
+ break;
+
+ case SENSORS_FEATURE_TEMP:
+ subfeature_type = SENSORS_SUBFEATURE_TEMP_INPUT;
+ break;
+
+ case SENSORS_FEATURE_POWER:
+ subfeature_type = SENSORS_SUBFEATURE_POWER_INPUT;
+ break;
+
+ default:
+ return Sensor_no_value();
+ }
+
+ return Sensor_return_value(self, subfeature_type);
+}
+
+static PyObject* Sensor_get_critical(SensorObject* self) {
+ sensors_subfeature_type subfeature_type;
+
+ switch (self->feature->type) {
+ case SENSORS_FEATURE_IN:
+ subfeature_type = SENSORS_SUBFEATURE_IN_CRIT;
+ break;
+
+ case SENSORS_FEATURE_TEMP:
+ subfeature_type = SENSORS_SUBFEATURE_TEMP_CRIT;
+ break;
+
+ case SENSORS_FEATURE_POWER:
+ subfeature_type = SENSORS_SUBFEATURE_POWER_CRIT;
+ break;
+
+ default:
+ return Sensor_no_value();
+ }
+
+ return Sensor_return_value(self, subfeature_type);
+}
+
+static PyObject* Sensor_get_maximum(SensorObject* self) {
+ sensors_subfeature_type subfeature_type;
+
+ switch (self->feature->type) {
+ case SENSORS_FEATURE_IN:
+ subfeature_type = SENSORS_SUBFEATURE_IN_MAX;
+ break;
+
+ case SENSORS_FEATURE_FAN:
+ subfeature_type = SENSORS_SUBFEATURE_FAN_MAX;
+ break;
+
+ case SENSORS_FEATURE_TEMP:
+ subfeature_type = SENSORS_SUBFEATURE_TEMP_MAX;
+ break;
+
+ case SENSORS_FEATURE_POWER:
+ subfeature_type = SENSORS_SUBFEATURE_POWER_MAX;
+ break;
+
+ default:
+ return Sensor_no_value();
+ }
+
+ return Sensor_return_value(self, subfeature_type);
+}
+
+static PyObject* Sensor_get_minimum(SensorObject* self) {
+ sensors_subfeature_type subfeature_type;
+
+ switch (self->feature->type) {
+ case SENSORS_FEATURE_IN:
+ subfeature_type = SENSORS_SUBFEATURE_IN_MIN;
+ break;
+
+ case SENSORS_FEATURE_FAN:
+ subfeature_type = SENSORS_SUBFEATURE_FAN_MIN;
+ break;
+
+ case SENSORS_FEATURE_TEMP:
+ subfeature_type = SENSORS_SUBFEATURE_TEMP_MIN;
+ break;
+
+ default:
+ return Sensor_no_value();
+ }
+
+ return Sensor_return_value(self, subfeature_type);
+}
+
+static PyObject* Sensor_get_high(SensorObject* self) {
+ sensors_subfeature_type subfeature_type;
+
+ switch (self->feature->type) {
+ case SENSORS_FEATURE_TEMP:
+ subfeature_type = SENSORS_SUBFEATURE_TEMP_MAX;
+ break;
+
+ default:
+ return Sensor_no_value();
+ }
+
+ return Sensor_return_value(self, subfeature_type);
+}
+
+static PyGetSetDef Sensor_getsetters[] = {
+ {"bus", (getter)Sensor_get_bus, NULL, NULL, NULL},
+ {"critical", (getter)Sensor_get_critical, NULL, NULL, NULL},
+ {"high", (getter)Sensor_get_high, NULL, NULL, NULL},
+ {"label", (getter)Sensor_get_label, NULL, NULL, NULL},
+ {"maximum", (getter)Sensor_get_maximum, NULL, NULL, NULL},
+ {"minumum", (getter)Sensor_get_minimum, NULL, NULL, NULL},
+ {"name", (getter)Sensor_get_name, NULL, NULL, NULL},
+ {"type", (getter)Sensor_get_type, NULL, NULL, NULL},
+ {"value", (getter)Sensor_get_value, NULL, NULL, NULL},
+ {NULL},
+};
+
+static PyTypeObject SensorType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size*/
+ "_collecty.Sensor", /*tp_name*/
+ sizeof(SensorObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)Sensor_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ "Sensor objects", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ Sensor_getsetters, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)Sensor_init, /* tp_init */
+ 0, /* tp_alloc */
+ Sensor_new, /* tp_new */
+};
+
+static SensorObject* make_sensor_object(const sensors_chip_name* chip, const sensors_feature* feature) {
+ SensorObject* sensor = PyObject_New(SensorObject, &SensorType);
+ if (!sensor)
+ return NULL;
+
+ if (!PyObject_Init((PyObject*)sensor, &SensorType)) {
+ Py_DECREF(sensor);
+ return NULL;
+ }
+
+ sensor->chip = chip;
+ sensor->feature = feature;
+
+ return sensor;
+}
+
+static PyObject* _collecty_sensors_init() {
+ // Clean up everything first in case sensors_init was called earlier
+ sensors_cleanup();
+
+ int r = sensors_init(NULL);
+ if (r) {
+ PyErr_Format(PyExc_OSError, "Could not initialise sensors: %s",
+ sensors_strerror(errno));
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject* _collecty_sensors_cleanup() {
+ sensors_cleanup();
+ Py_RETURN_NONE;
+}
+
+static PyObject* _collecty_get_detected_sensors(PyObject* o, PyObject* args) {
+ const char* name = NULL;
+ sensors_chip_name chip_name;
+
+ if (!PyArg_ParseTuple(args, "|z", &name))
+ return NULL;
+
+ if (name) {
+ int r = sensors_parse_chip_name(name, &chip_name);
+ if (r < 0) {
+ PyErr_Format(PyExc_ValueError, "Could not parse chip name: %s", name);
+ return NULL;
+ }
+ }
+
+ PyObject* list = PyList_New(0);
+
+ const sensors_chip_name* chip;
+ int chip_num = 0;
+
+ while ((chip = sensors_get_detected_chips((name) ? &chip_name : NULL, &chip_num))) {
+ const sensors_feature* feature;
+ int feature_num = 0;
+
+ while ((feature = sensors_get_features(chip, &feature_num))) {
+ // Skip sensors we do not want to support
+ switch (feature->type) {
+ case SENSORS_FEATURE_IN:
+ case SENSORS_FEATURE_FAN:
+ case SENSORS_FEATURE_TEMP:
+ case SENSORS_FEATURE_POWER:
+ break;
+
+ default:
+ continue;
+ }
+
+ SensorObject* sensor = make_sensor_object(chip, feature);
+ PyList_Append(list, (PyObject*)sensor);
+ }
+ }
+
+ return list;
+}
+
static PyMethodDef collecty_module_methods[] = {
+ {"get_detected_sensors", (PyCFunction)_collecty_get_detected_sensors, METH_VARARGS, NULL},
+ {"sensors_cleanup", (PyCFunction)_collecty_sensors_cleanup, METH_NOARGS, NULL},
+ {"sensors_init", (PyCFunction)_collecty_sensors_init, METH_NOARGS, NULL},
{NULL},
};
if (PyType_Ready(&BlockDeviceType) < 0)
return;
+ if (PyType_Ready(&SensorType) < 0)
+ return;
+
PyObject* m = Py_InitModule("_collecty", collecty_module_methods);
PyModule_AddObject(m, "BlockDevice", (PyObject*)&BlockDeviceType);
+ PyModule_AddObject(m, "Sensor", (PyObject*)&SensorType);
}
--- /dev/null
+#!/usr/bin/python
+# encoding: utf-8
+###############################################################################
+# #
+# collecty - A system statistics collection daemon for IPFire #
+# Copyright (C) 2015 IPFire development team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+from collecty import _collecty
+import os
+import re
+
+import base
+
+from ..i18n import _
+
+class GraphTemplateSensorsTemperature(base.GraphTemplate):
+ name = "sensors-temperature"
+
+ rrd_graph = [
+ "DEF:value_kelvin=%(file)s:value:AVERAGE",
+ "DEF:critical_kelvin=%(file)s:critical:AVERAGE",
+ "DEF:high_kelvin=%(file)s:high:AVERAGE",
+ "DEF:low_kelvin=%(file)s:low:AVERAGE",
+
+ # Convert everything to celsius
+ "CDEF:value=value_kelvin,273.15,-",
+ "CDEF:critical=critical_kelvin,273.15,-",
+ "CDEF:high=high_kelvin,273.15,-",
+ "CDEF:low=low_kelvin,273.15,-",
+
+ # Change colour when the value gets above high
+ "CDEF:value_high=value,high,GT,value,UNKN,IF",
+ "CDEF:value_normal=value,high,GT,UNKN,value,IF",
+
+ "VDEF:value_cur=value,LAST",
+ "VDEF:value_avg=value,AVERAGE",
+ "VDEF:value_max=value,MAXIMUM",
+ "VDEF:value_min=value,MINIMUM",
+
+ # Get data points for the threshold lines
+ "VDEF:critical_line=critical,MINIMUM",
+ "VDEF:low_line=low,MAXIMUM",
+
+ # Draw the temperature value
+ "LINE3:value_high#ff0000",
+ "LINE2:value_normal#00ff00:%-15s" % _("Temperature"),
+
+ # Draw the legend
+ "GPRINT:value_cur:%%10.2lf °C\l",
+ "GPRINT:value_avg: %-15s %%6.2lf °C\l" % _("Average"),
+ "GPRINT:value_max: %-15s %%6.2lf °C\l" % _("Maximum"),
+ "GPRINT:value_min: %-15s %%6.2lf °C\l" % _("Minimum"),
+
+ # Empty line
+ "COMMENT: \\n",
+
+ # Draw boundary lines
+ "COMMENT:%s\:" % _("Temperature Thresholds"),
+ "HRULE:critical_line#000000:%-15s" % _("Critical"),
+ "GPRINT:critical_line:%%6.2lf °C\\r",
+ "HRULE:low_line#0000ff:%-15s" % _("Low"),
+ "GPRINT:low_line:%%6.2lf °C\\r",
+ ]
+
+ @property
+ def graph_title(self):
+ return _("Temperature (%s)") % self.object.sensor.name
+
+ @property
+ def graph_vertical_label(self):
+ return _("° Celsius")
+
+
+class GraphTemplateSensorsProcessorTemperature(base.GraphTemplate):
+ name = "processor-temperature"
+
+ core_colours = {
+ "core0" : "#ff000033",
+ "core1" : "#0000ff33",
+ "core2" : "#00ff0033",
+ "core3" : "#0000ff33",
+ }
+
+ def get_temperature_sensors(self):
+ return self.plugin.get_detected_sensor_objects("coretemp-*")
+
+ def get_object_table(self):
+ objects_table = {}
+
+ counter = 0
+ for object in self.get_temperature_sensors():
+ objects_table["core%s" % counter] = object
+ counter += 1
+
+ return objects_table
+
+ @property
+ def rrd_graph(self):
+ rrd_graph = []
+
+ cores = sorted(self.object_table.keys())
+
+ for core in cores:
+ i = {
+ "core" : core,
+ }
+
+ core_graph = [
+ "DEF:value_kelvin_%(core)s=%%(%(core)s)s:value:AVERAGE",
+ "DEF:critical_kelvin_%(core)s=%%(%(core)s)s:critical:AVERAGE",
+ "DEF:high_kelvin_%(core)s=%%(%(core)s)s:high:AVERAGE",
+
+ # Convert everything to celsius
+ "CDEF:value_%(core)s=value_kelvin_%(core)s,273.15,-",
+ "CDEF:critical_%(core)s=critical_kelvin_%(core)s,273.15,-",
+ "CDEF:high_%(core)s=high_kelvin_%(core)s,273.15,-",
+ ]
+
+ rrd_graph += [line % i for line in core_graph]
+
+ all_core_values = ("value_%s" % c for c in cores)
+ all_core_highs = ("high_%s" % c for c in cores)
+
+ rrd_graph += [
+ # Compute the temperature of the processor
+ # by taking the average of all cores
+ "CDEF:value=%s,%s,AVG" % (",".join(all_core_values), len(cores)),
+ "CDEF:high=%s,MIN" % ",".join(all_core_highs),
+
+ # Change colour when the value gets above high
+ "CDEF:value_high=value,high,GT,value,UNKN,IF",
+ "CDEF:value_normal=value,high,GT,UNKN,value,IF",
+
+ "VDEF:value_avg=value,AVERAGE",
+ "VDEF:value_max=value,MAXIMUM",
+ "VDEF:value_min=value,MINIMUM",
+
+ "LINE3:value_high#FF0000",
+ "LINE3:value_normal#000000:%-15s\l" % _("Temperature"),
+
+ "GPRINT:value_avg: %-15s %%6.2lf °C\l" % _("Average"),
+ "GPRINT:value_max: %-15s %%6.2lf °C\l" % _("Maximum"),
+ "GPRINT:value_min: %-15s %%6.2lf °C\l" % _("Minimum"),
+ ]
+
+ for core in cores:
+ object = self.object_table.get(core)
+
+ i = {
+ "colour" : self.core_colours.get(core, "#000000"),
+ "core" : core,
+ "label" : object.sensor.label,
+ }
+
+ core_graph = [
+ # TODO these lines were supposed to be dashed, but that
+ # didn't really work here
+ "LINE2:value_%(core)s%(colour)s:%(label)-10s",
+ ]
+
+ rrd_graph += [line % i for line in core_graph]
+
+ # Draw the critical line
+ rrd_graph += [
+ "VDEF:critical_line=critical_core0,MINIMUM",
+ "HRULE:critical_line#000000:%-15s" % _("Critical"),
+ "GPRINT:critical_line:%%6.2lf °C\\r",
+ ]
+
+ return rrd_graph
+
+ @property
+ def graph_title(self):
+ return _("Processor")
+
+ @property
+ def graph_vertical_label(self):
+ return _("Temperature")
+
+
+class SensorBaseObject(base.Object):
+ def init(self, sensor):
+ self.sensor = sensor
+
+ def __repr__(self):
+ return "<%s %s (%s)>" % (self.__class__.__name__, self.sensor.name, self.sensor.label)
+
+ @property
+ def id(self):
+ return "-".join((self.sensor.name, self.sensor.label))
+
+ @property
+ def type(self):
+ return self.sensor.type
+
+
+class SensorTemperatureObject(SensorBaseObject):
+ rrd_schema = [
+ "DS:value:GAUGE:0:U",
+ "DS:critical:GAUGE:0:U",
+ "DS:low:GAUGE:0:U",
+ "DS:high:GAUGE:0:U",
+ ]
+
+ def collect(self):
+ assert self.type == "temperature"
+
+ return (
+ self.sensor.value,
+ self.critical,
+ self.low,
+ self.high,
+ )
+
+ @property
+ def critical(self):
+ try:
+ return self.sensor.critical
+ except AttributeError:
+ return "NaN"
+
+ @property
+ def low(self):
+ try:
+ return self.sensor.minimum
+ except AttributeError:
+ return "NaN"
+
+ @property
+ def high(self):
+ try:
+ return self.sensor.high
+ except AttributeError:
+ return "NaN"
+
+
+class SensorVoltageObject(SensorBaseObject):
+ rrd_schema = [
+ "DS:value:GAUGE:0:U",
+ "DS:minimum:GAUGE:0:U",
+ "DS:maximum:GAUGE:0:U",
+ ]
+
+ def collect(self):
+ assert self.type == "voltage"
+
+ return (
+ self.sensor.value,
+ self.minimum,
+ self.maximum,
+ )
+
+ @property
+ def minimum(self):
+ try:
+ return self.sensor.minimum
+ except AttributeError:
+ return "NaN"
+
+ @property
+ def maximum(self):
+ try:
+ return self.sensor.maximum
+ except AttributeError:
+ return "NaN"
+
+
+class SensorFanObject(SensorBaseObject):
+ rrd_schema = [
+ "DS:value:GAUGE:0:U",
+ "DS:minimum:GAUGE:0:U",
+ "DS:maximum:GAUGE:0:U",
+ ]
+
+ def collect(self):
+ assert self.type == "fan"
+
+ return (
+ self.sensor.value,
+ self.minimum,
+ self.maximum,
+ )
+
+ @property
+ def mimimum(self):
+ try:
+ return self.sensor.minimum
+ except AttributeError:
+ return "NaN"
+
+ @property
+ def maximum(self):
+ try:
+ return self.sensor.maximum
+ except AttributeError:
+ return "NaN"
+
+
+class SensorsPlugin(base.Plugin):
+ name = "sensors"
+ description = "Sensors Plugin"
+
+ templates = [
+ GraphTemplateSensorsProcessorTemperature,
+ GraphTemplateSensorsTemperature,
+ ]
+
+ def init(self):
+ # Initialise the sensors library.
+ _collecty.sensors_init()
+
+ def __del__(self):
+ _collecty.sensors_cleanup()
+
+ @property
+ def objects(self):
+ return self.get_detected_sensor_objects()
+
+ def get_detected_sensor_objects(self, what=None):
+ for sensor in _collecty.get_detected_sensors(what):
+ if sensor.type == "temperature":
+ yield SensorTemperatureObject(self, sensor)
+
+ elif sensor.type == "voltage":
+ yield SensorVoltageObject(self, sensor)
+
+ elif sensor.type == "fan":
+ yield SensorFanObject(self, sensor)