root/lm-sensors/trunk/prog/sensord/rrd.c

Revision 5835, 12.0 KB (checked in by khali, 2 years ago)

Fix support of multiple chips. Regression in 3.1.2, faulty commit is
r5792. Fixes ticket #2377.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*
2 * sensord
3 *
4 * A daemon that periodically logs sensor information to syslog.
5 *
6 * Copyright (c) 1999-2002 Merlin Hughes <merlin@merlin.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301 USA.
22 */
23
24/*
25 * RRD is the Round Robin Database
26 *
27 * Get this package from:
28 *   http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/
29 *
30 * For compilation you need the development libraries;
31 * for execution you need the runtime libraries; for
32 * Web-based graph access you need the binary rrdtool.
33 */
34
35#include <errno.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <syslog.h>
40#include <unistd.h>
41#include <sys/stat.h>
42#include <sys/types.h>
43
44#include <rrd.h>
45
46#include "args.h"
47#include "sensord.h"
48
49#define DO_READ 0
50#define DO_SCAN 1
51#define DO_SET 2
52#define DO_RRD 3
53
54/* one integer */
55#define STEP_BUFF 64
56/* RRA:AVERAGE:0.5:1:12345 */
57#define RRA_BUFF 256
58/* weak: max sensors for RRD .. TODO: fix */
59#define MAX_RRD_SENSORS 256
60/* weak: max raw label length .. TODO: fix */
61#define RAW_LABEL_LENGTH 32
62/* DS:label:GAUGE:900:U:U | :3000 .. TODO: fix */
63#define RRD_BUFF 64
64
65char rrdBuff[MAX_RRD_SENSORS * RRD_BUFF + 1];
66static char rrdLabels[MAX_RRD_SENSORS][RAW_LABEL_LENGTH + 1];
67
68#define LOADAVG "loadavg"
69#define LOAD_AVERAGE "Load Average"
70
71typedef void (*FeatureFN) (void *data, const char *rawLabel, const char *label,
72                           const FeatureDescriptor *feature);
73
74static char rrdNextChar(char c)
75{
76        if (c == '9') {
77                return 'A';
78        } else if (c == 'Z') {
79                return 'a';
80        } else if (c == 'z') {
81                return 0;
82        } else {
83                return c + 1;
84        }
85}
86
87static void rrdCheckLabel(const char *rawLabel, int index0)
88{
89        char *buffer = rrdLabels[index0];
90        int i, j, okay;
91
92        i = 0;
93        /* contrain raw label to [A-Za-z0-9_] */
94        while ((i < RAW_LABEL_LENGTH) && rawLabel[i]) {
95                char c = rawLabel[i];
96                if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))
97                    || ((c >= '0') && (c <= '9')) || (c == '_')) {
98                        buffer[i] = c;
99                } else {
100                        buffer[i] = '_';
101                }
102                ++i;
103        }
104        buffer[i] = '\0';
105
106        j = 0;
107        okay = (i > 0);
108
109        /* locate duplicates */
110        while (okay && (j < index0))
111                okay = strcmp(rrdLabels[j++], buffer);
112
113        /* uniquify duplicate labels with _? or _?? */
114        while (!okay) {
115                if (!buffer[i]) {
116                        if (i > RAW_LABEL_LENGTH - 3)
117                                i = RAW_LABEL_LENGTH - 3;
118                        buffer[i] = '_';
119                        buffer[i + 1] = '0';
120                        buffer[i + 2] = '\0';
121                } else if (!buffer[i + 2]) {
122                        if (!(buffer[i + 1] = rrdNextChar(buffer[i + 1]))) {
123                                buffer[i + 1] = '0';
124                                buffer[i + 2] = '0';
125                                buffer[i + 3] = '\0';
126                        }
127                } else {
128                        if (!(buffer[i + 2] = rrdNextChar(buffer[i + 2]))) {
129                                buffer[i + 1] = rrdNextChar(buffer[i + 1]);
130                                buffer[i + 2] = '0';
131                        }
132                }
133                j = 0;
134                okay = 1;
135                while (okay && (j < index0))
136                        okay = strcmp(rrdLabels[j ++], buffer);
137        }
138}
139
140/* Returns the number of features processed, or -1 on error */
141static int _applyToFeatures(FeatureFN fn, void *data,
142                            const sensors_chip_name *chip,
143                            const ChipDescriptor *desc,
144                            int labelOffset)
145{
146        int i;
147        const FeatureDescriptor *features = desc->features;
148        const FeatureDescriptor *feature;
149        const char *rawLabel;
150        char *label;
151
152        for (i = 0; labelOffset + i < MAX_RRD_SENSORS && features[i].format; ++i) {
153                feature = features + i;
154                rawLabel = feature->feature->name;
155
156                label = sensors_get_label(chip, feature->feature);
157                if (!label) {
158                        sensorLog(LOG_ERR, "Error getting sensor label: %s/%s",
159                                  chip->prefix, rawLabel);
160                        return -1;
161                }
162
163                rrdCheckLabel(rawLabel, labelOffset + i);
164                fn(data, rrdLabels[labelOffset + i], label, feature);
165                free(label);
166        }
167        return i;
168}
169
170static ChipDescriptor *lookup_known_chips(const sensors_chip_name *chip)
171{
172        int i;
173
174        /* Trick: we compare addresses here. We know it works
175         * because both pointers were returned by
176         * sensors_get_detected_chips(), so they refer to
177         * libsensors internal structures, which do not move.
178         */
179        for (i = 0; knownChips[i].features; i++) {
180                if (knownChips[i].name == chip) {
181                        return &knownChips[i];
182                }
183        }
184        return NULL;
185}
186
187static int applyToFeatures(FeatureFN fn, void *data)
188{
189        int i, i_detected, ret, labelOffset = 0;
190        const sensors_chip_name *chip, *chip_arg;
191        ChipDescriptor *desc;
192
193        for (i = 0; i < sensord_args.numChipNames; i++) {
194                chip_arg = &sensord_args.chipNames[i];
195                i_detected = 0;
196                while ((chip = sensors_get_detected_chips(chip_arg,
197                                                          &i_detected))) {
198                        desc = lookup_known_chips(chip);
199                        if (!desc)
200                                continue;
201
202                        ret = _applyToFeatures(fn, data, chip, desc, labelOffset);
203                        if (ret < 0)
204                                return ret;
205                        labelOffset += ret;
206                }
207        }
208        return 0;
209}
210
211struct ds {
212        int num;
213        const char **argv;
214};
215
216static void rrdGetSensors_DS(void *_data, const char *rawLabel,
217                             const char *label,
218                             const FeatureDescriptor *feature)
219{
220        (void) label; /* no warning */
221        if (!feature || feature->rrd) {
222                struct ds *data = _data;
223                char *ptr = rrdBuff + data->num * RRD_BUFF;
224                const char *min, *max;
225                data->argv[data->num ++] = ptr;
226
227                /* arbitrary sanity limits */
228                switch (feature ? feature->type : DataType_other) {
229                case DataType_voltage:
230                        min="-25";
231                        max="25";
232                        break;
233                case DataType_rpm:
234                        min = "0";
235                        max = "12000";
236                        break;
237                case DataType_temperature:
238                        min = "-100";
239                        max = "250";
240                        break;
241                default:
242                        min = max = "U";
243                        break;
244                }
245
246                /*
247                 * number of seconds downtime during which average be used
248                 * instead of unknown
249                 */
250                sprintf(ptr, "DS:%s:GAUGE:%d:%s:%s", rawLabel, 5 *
251                        sensord_args.rrdTime, min, max);
252        }
253}
254
255static int rrdGetSensors(const char **argv)
256{
257        int ret = 0;
258        struct ds data = { 0, argv};
259        ret = applyToFeatures(rrdGetSensors_DS, &data);
260        if (!ret && sensord_args.doLoad)
261                rrdGetSensors_DS(&data, LOADAVG, LOAD_AVERAGE, NULL);
262        return ret ? -1 : data.num;
263}
264
265int rrdInit(void)
266{
267        int ret;
268        struct stat sb;
269        char stepBuff[STEP_BUFF], rraBuff[RRA_BUFF];
270        int argc = 4, num;
271        const char *argv[6 + MAX_RRD_SENSORS] = {
272                "sensord", sensord_args.rrdFile, "-s", stepBuff
273        };
274
275        sensorLog(LOG_DEBUG, "sensor RRD init");
276
277        /* Create RRD if it does not exist. */
278        if (stat(sensord_args.rrdFile, &sb)) {
279                if (errno != ENOENT) {
280                        sensorLog(LOG_ERR, "Could not stat rrd file: %s\n",
281                                  sensord_args.rrdFile);
282                        return -1;
283                }
284                sensorLog(LOG_INFO, "Creating round robin database");
285
286                num = rrdGetSensors(argv + argc);
287                if (num < 1) {
288                        sensorLog(LOG_ERR, "Error creating RRD: %s: %s",
289                                  sensord_args.rrdFile, "No sensors detected");
290                        return -1;
291                }
292
293                sprintf(stepBuff, "%d", sensord_args.rrdTime);
294                sprintf(rraBuff, "RRA:%s:%f:%d:%d",
295                        sensord_args.rrdNoAverage ? "LAST" :"AVERAGE",
296                        0.5, 1, 7 * 24 * 60 * 60 / sensord_args.rrdTime);
297
298                argc += num;
299                argv[argc++] = rraBuff;
300                argv[argc] = NULL;
301
302                ret = rrd_create(argc, (char**) argv);
303                if (ret == -1) {
304                        sensorLog(LOG_ERR, "Error creating RRD file: %s: %s",
305                                  sensord_args.rrdFile, rrd_get_error());
306                        return -1;
307                }
308        }
309
310        sensorLog(LOG_DEBUG, "sensor RRD initialized");
311        return 0;
312}
313
314#define RRDCGI "/usr/bin/rrdcgi"
315#define WWWDIR "/sensord"
316
317struct gr {
318        DataType type;
319        const char *h2;
320        const char *image;
321        const char *title;
322        const char *axisTitle;
323        const char *axisDefn;
324        const char *options;
325        int loadAvg;
326};
327
328static void rrdCGI_DEF(void *_data, const char *rawLabel, const char *label,
329                       const FeatureDescriptor *feature)
330{
331        struct gr *data = _data;
332        (void) label; /* no warning */
333        if (!feature || (feature->rrd && (feature->type == data->type)))
334                printf("\n\tDEF:%s=%s:%s:AVERAGE", rawLabel,
335                       sensord_args.rrdFile, rawLabel);
336}
337
338/*
339 * Compute an arbitrary color based on the sensor label. This is preferred
340 * over a random value because this guarantees that daily and weekly charts
341 * will use the same colors.
342 */
343static int rrdCGI_color(const char *label)
344{
345        unsigned long color = 0, brightness;
346        const char *c;
347
348        for (c = label; *c; c++) {
349                color = (color << 6) + (color >> (*c & 7));
350                color ^= (*c) * 0x401;
351        }
352        color &= 0xffffff;
353        /* Adjust very light colors */
354        brightness = (color & 0xff) + ((color >> 8) & 0xff) + (color >> 16);
355        if (brightness > 672)
356                color &= 0x7f7f7f;
357        /* Adjust very dark colors */
358        else if (brightness < 96)
359                color |= 0x808080;
360        return color;
361}
362
363static void rrdCGI_LINE(void *_data, const char *rawLabel, const char *label,
364                        const FeatureDescriptor *feature)
365{
366        struct gr *data = _data;
367        if (!feature || (feature->rrd && (feature->type == data->type)))
368                printf("\n\tLINE2:%s#%.6x:\"%s\"", rawLabel,
369                       rrdCGI_color(label), label);
370}
371
372static struct gr graphs[] = {
373        {
374                DataType_temperature,
375                "Daily Temperature Summary",
376                "daily-temperature",
377                "Temperature",
378                "Temperature (C)",
379                "HOUR:1:HOUR:3:HOUR:3:0:%b %d %H:00",
380                "-s -1d -l 0",
381                1
382        }, {
383                DataType_rpm,
384                "Daily Fan Speed Summary",
385                "daily-rpm",
386                "Fan Speed",
387                "Speed (RPM)",
388                "HOUR:1:HOUR:3:HOUR:3:0:%b %d %H:00",
389                "-s -1d -l 0 -X 0",
390                0
391        }, {
392                DataType_voltage,
393                "Daily Voltage Summary",
394                "daily-voltage",
395                "Power Supply",
396                "Voltage (V)",
397                "HOUR:1:HOUR:3:HOUR:3:0:%b %d %H:00",
398                "-s -1d --alt-autoscale",
399                0
400        }, {
401                DataType_temperature,
402                "Weekly Temperature Summary",
403                "weekly-temperature",
404                "Temperature",
405                "Temperature (C)",
406                "HOUR:6:DAY:1:DAY:1:86400:%a %b %d",
407                "-s -1w -l 0",
408                1
409        }, {
410                DataType_rpm,
411                "Weekly Fan Speed Summary",
412                "weekly-rpm",
413                "Fan Speed",
414                "Speed (RPM)",
415                "HOUR:6:DAY:1:DAY:1:86400:%a %b %d",
416                "-s -1w -l 0 -X 0",
417                0
418        }, {
419                DataType_voltage,
420                "Weekly Voltage Summary",
421                "weekly-voltage",
422                "Power Supply",
423                "Voltage (V)",
424                "HOUR:6:DAY:1:DAY:1:86400:%a %b %d",
425                "-s -1w --alt-autoscale",
426                0
427        }
428};
429
430int rrdUpdate(void)
431{
432        int ret = rrdChips ();
433
434        if (!ret && sensord_args.doLoad) {
435                FILE *loadavg;
436                if (!(loadavg = fopen("/proc/loadavg", "r"))) {
437                        sensorLog(LOG_ERR,
438                                  "Error opening `/proc/loadavg': %s",
439                                  strerror(errno));
440                        ret = 1;
441                } else {
442                        float value;
443                        if (fscanf(loadavg, "%f", &value) != 1) {
444                                sensorLog(LOG_ERR,
445                                          "Error reading load average");
446                                ret = 2;
447                        } else {
448                                sprintf(rrdBuff + strlen(rrdBuff), ":%f",
449                                        value);
450                        }
451                        fclose(loadavg);
452                }
453        }
454        if (!ret) {
455                const char *argv[] = {
456                        "sensord", sensord_args.rrdFile, rrdBuff, NULL
457                };
458                if ((ret = rrd_update(3, (char **) /* WEAK */ argv))) {
459                        sensorLog(LOG_ERR, "Error updating RRD file: %s: %s",
460                                  sensord_args.rrdFile, rrd_get_error());
461                }
462        }
463        sensorLog(LOG_DEBUG, "sensor rrd updated");
464
465        return ret;
466}
467
468int rrdCGI(void)
469{
470        int ret = 0, i;
471
472        printf("#!" RRDCGI "\n\n<html>\n"
473               "<head>\n<title>sensord</title>\n</head>\n"
474               "<body>\n<h1>sensord</h1>\n");
475
476        for (i = 0; i < ARRAY_SIZE(graphs); i++) {
477                struct gr *graph = &graphs[i];
478
479                printf("<h2>%s</h2>\n", graph->h2);
480                printf("<p>\n<RRD::GRAPH %s/%s.png\n\t--imginfo '"
481                       "<img src=" WWWDIR "/%%s width=%%lu height=%%lu>'"
482                       "\n\t-a PNG\n\t-h 200 -w 800\n",
483                       sensord_args.cgiDir, graph->image);
484
485                printf("\t--lazy\n\t-v '%s'\n\t-t '%s'\n\t-x '%s'\n\t%s",
486                       graph->axisTitle, graph->title, graph->axisDefn,
487                       graph->options);
488                if (!ret)
489                        ret = applyToFeatures(rrdCGI_DEF, graph);
490                if (!ret && sensord_args.doLoad && graph->loadAvg)
491                        rrdCGI_DEF(graph, LOADAVG, LOAD_AVERAGE, NULL);
492                if (!ret)
493                        ret = applyToFeatures(rrdCGI_LINE, graph);
494                if (!ret && sensord_args.doLoad && graph->loadAvg)
495                        rrdCGI_LINE(graph, LOADAVG, LOAD_AVERAGE, NULL);
496                printf (">\n</p>\n");
497        }
498        printf("<p>\n<small><b>sensord</b> by "
499               "<a href=\"mailto:merlin@merlin.org\">Merlin Hughes</a>"
500               ", all credit to the "
501               "<a href=\"http://www.lm-sensors.org/\">lm_sensors</a> "
502               "crew.</small>\n</p>\n");
503
504        printf("</body>\n</html>\n");
505
506        return ret;
507}
Note: See TracBrowser for help on using the browser.