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

Revision 5736, 11.7 KB (checked in by khali, 5 years ago)

Optimize CGI generation code a bit.

  • 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 int (*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
140static int applyToFeatures(FeatureFN fn, void *data)
141{
142        const sensors_chip_name *chip;
143        int i, j, ret = 0, num = 0;
144
145        for (j = 0; (ret == 0) && (j < sensord_args.numChipNames); ++ j) {
146                i = 0;
147                while ((ret == 0) && ((chip = sensors_get_detected_chips(&sensord_args.chipNames[j], &i)) != NULL)) {
148                        int index0, chipindex = -1;
149
150                        /* Trick: we compare addresses here. We know it works
151                         * because both pointers were returned by
152                         * sensors_get_detected_chips(), so they refer to
153                         * libsensors internal structures, which do not move.
154                         */
155                        for (index0 = 0; knownChips[index0].features; ++index0)
156                                if (knownChips[index0].name == chip) {
157                                        chipindex = index0;
158                                        break;
159                                }
160                        if (chipindex >= 0) {
161                                const ChipDescriptor *descriptor = &knownChips[chipindex];
162                                const FeatureDescriptor *features = descriptor->features;
163
164                                for (index0 = 0; (ret == 0) && (num < MAX_RRD_SENSORS) && features[index0].format; ++index0) {
165                                        const FeatureDescriptor *feature = features + index0;
166                                        const char *rawLabel = feature->feature->name;
167                                        char *label = NULL;
168
169                                        if (!(label = sensors_get_label(chip, feature->feature))) {
170                                                sensorLog(LOG_ERR, "Error getting sensor label: %s/%s", chip->prefix, rawLabel);
171                                                ret = -1;
172                                        } else  {
173                                                rrdCheckLabel(rawLabel, num);
174                                                ret = fn(data,
175                                                         rrdLabels[num],
176                                                         label, feature);
177                                                ++ num;
178                                        }
179                                        if (label)
180                                                free(label);
181                                }
182                        }
183                }
184        }
185        return ret;
186}
187
188struct ds {
189        int num;
190        const char **argv;
191};
192
193static int rrdGetSensors_DS(void *_data, const char *rawLabel,
194                            const char *label,
195                            const FeatureDescriptor *feature)
196{
197        (void) label; /* no warning */
198        if (!feature || feature->rrd) {
199                struct ds *data = (struct ds *) _data;
200                char *ptr = rrdBuff + data->num * RRD_BUFF;
201                const char *min, *max;
202                data->argv[data->num ++] = ptr;
203
204                /* arbitrary sanity limits */
205                switch (feature ? feature->type : DataType_other) {
206                case DataType_voltage:
207                        min="-25";
208                        max="25";
209                        break;
210                case DataType_rpm:
211                        min = "0";
212                        max = "12000";
213                        break;
214                case DataType_temperature:
215                        min = "-100";
216                        max = "250";
217                        break;
218                default:
219                        min = max = "U";
220                        break;
221                }
222
223                /*
224                 * number of seconds downtime during which average be used
225                 * instead of unknown
226                 */
227                sprintf(ptr, "DS:%s:GAUGE:%d:%s:%s", rawLabel, 5 *
228                        sensord_args.rrdTime, min, max);
229        }
230        return 0;
231}
232
233static int rrdGetSensors(const char **argv)
234{
235        int ret = 0;
236        struct ds data = { 0, argv};
237        ret = applyToFeatures(rrdGetSensors_DS, &data);
238        if (!ret && sensord_args.doLoad)
239                ret = rrdGetSensors_DS(&data, LOADAVG, LOAD_AVERAGE, NULL);
240        return ret ? -1 : data.num;
241}
242
243int rrdInit(void)
244{
245        int ret;
246        struct stat sb;
247        char stepBuff[STEP_BUFF], rraBuff[RRA_BUFF];
248        int argc = 4, num;
249        const char *argv[6 + MAX_RRD_SENSORS] = {
250                "sensord", sensord_args.rrdFile, "-s", stepBuff
251        };
252
253        sensorLog(LOG_DEBUG, "sensor RRD init");
254
255        /* Create RRD if it does not exist. */
256        if (stat(sensord_args.rrdFile, &sb)) {
257                if (errno != ENOENT) {
258                        sensorLog(LOG_ERR, "Could not stat rrd file: %s\n",
259                                  sensord_args.rrdFile);
260                        return -1;
261                }
262                sensorLog(LOG_INFO, "Creating round robin database");
263
264                num = rrdGetSensors(argv + argc);
265                if (num < 1) {
266                        sensorLog(LOG_ERR, "Error creating RRD: %s: %s",
267                                  sensord_args.rrdFile, "No sensors detected");
268                        return -1;
269                }
270
271                sprintf(stepBuff, "%d", sensord_args.rrdTime);
272                sprintf(rraBuff, "RRA:%s:%f:%d:%d",
273                        sensord_args.rrdNoAverage ? "LAST" :"AVERAGE",
274                        0.5, 1, 7 * 24 * 60 * 60 / sensord_args.rrdTime);
275
276                argc += num;
277                argv[argc++] = rraBuff;
278                argv[argc] = NULL;
279
280                ret = rrd_create(argc, (char**) argv);
281                if (ret == -1) {
282                        sensorLog(LOG_ERR, "Error creating RRD file: %s: %s",
283                                  sensord_args.rrdFile, rrd_get_error());
284                        return -1;
285                }
286        }
287
288        sensorLog(LOG_DEBUG, "sensor RRD initialized");
289        return 0;
290}
291
292#define RRDCGI "/usr/bin/rrdcgi"
293#define WWWDIR "/sensord"
294
295struct gr {
296        DataType type;
297        const char *h2;
298        const char *image;
299        const char *title;
300        const char *axisTitle;
301        const char *axisDefn;
302        const char *options;
303        int loadAvg;
304};
305
306static int rrdCGI_DEF(void *_data, const char *rawLabel, const char *label,
307                      const FeatureDescriptor *feature)
308{
309        struct gr *data = (struct gr *) _data;
310        (void) label; /* no warning */
311        if (!feature || (feature->rrd && (feature->type == data->type)))
312                printf("\n\tDEF:%s=%s:%s:AVERAGE", rawLabel,
313                       sensord_args.rrdFile, rawLabel);
314        return 0;
315}
316
317/*
318 * Compute an arbitrary color based on the sensor label. This is preferred
319 * over a random value because this guarantees that daily and weekly charts
320 * will use the same colors.
321 */
322static int rrdCGI_color(const char *label)
323{
324        unsigned long color = 0, brightness;
325        const char *c;
326
327        for (c = label; *c; c++) {
328                color = (color << 6) + (color >> (*c & 7));
329                color ^= (*c) * 0x401;
330        }
331        color &= 0xffffff;
332        /* Adjust very light colors */
333        brightness = (color & 0xff) + ((color >> 8) & 0xff) + (color >> 16);
334        if (brightness > 672)
335                color &= 0x7f7f7f;
336        /* Adjust very dark colors */
337        else if (brightness < 96)
338                color |= 0x808080;
339        return color;
340}
341
342static int rrdCGI_LINE(void *_data, const char *rawLabel, const char *label,
343                       const FeatureDescriptor *feature)
344{
345        struct gr *data = (struct gr *) _data;
346        if (!feature || (feature->rrd && (feature->type == data->type)))
347                printf("\n\tLINE2:%s#%.6x:\"%s\"", rawLabel,
348                       rrdCGI_color(label), label);
349        return 0;
350}
351
352static struct gr graphs[] = {
353        {
354                DataType_temperature,
355                "Daily Temperature Summary",
356                "daily-temperature",
357                "Temperature",
358                "Temperature (C)",
359                "HOUR:1:HOUR:3:HOUR:3:0:%b %d %H:00",
360                "-s -1d -l 0",
361                1
362        }, {
363                DataType_rpm,
364                "Daily Fan Speed Summary",
365                "daily-rpm",
366                "Fan Speed",
367                "Speed (RPM)",
368                "HOUR:1:HOUR:3:HOUR:3:0:%b %d %H:00",
369                "-s -1d -l 0 -X 0",
370                0
371        }, {
372                DataType_voltage,
373                "Daily Voltage Summary",
374                "daily-voltage",
375                "Power Supply",
376                "Voltage (V)",
377                "HOUR:1:HOUR:3:HOUR:3:0:%b %d %H:00",
378                "-s -1d --alt-autoscale",
379                0
380        }, {
381                DataType_temperature,
382                "Weekly Temperature Summary",
383                "weekly-temperature",
384                "Temperature",
385                "Temperature (C)",
386                "HOUR:6:DAY:1:DAY:1:86400:%a %b %d",
387                "-s -1w -l 0",
388                1
389        }, {
390                DataType_rpm,
391                "Weekly Fan Speed Summary",
392                "weekly-rpm",
393                "Fan Speed",
394                "Speed (RPM)",
395                "HOUR:6:DAY:1:DAY:1:86400:%a %b %d",
396                "-s -1w -l 0 -X 0",
397                0
398        }, {
399                DataType_voltage,
400                "Weekly Voltage Summary",
401                "weekly-voltage",
402                "Power Supply",
403                "Voltage (V)",
404                "HOUR:6:DAY:1:DAY:1:86400:%a %b %d",
405                "-s -1w --alt-autoscale",
406                0
407        }
408};
409
410int rrdUpdate(void)
411{
412        int ret = rrdChips ();
413
414        if (!ret && sensord_args.doLoad) {
415                FILE *loadavg;
416                if (!(loadavg = fopen("/proc/loadavg", "r"))) {
417                        sensorLog(LOG_ERR,
418                                  "Error opening `/proc/loadavg': %s",
419                                  strerror(errno));
420                        ret = 1;
421                } else {
422                        float value;
423                        if (fscanf(loadavg, "%f", &value) != 1) {
424                                sensorLog(LOG_ERR,
425                                          "Error reading load average");
426                                ret = 2;
427                        } else {
428                                sprintf(rrdBuff + strlen(rrdBuff), ":%f",
429                                        value);
430                        }
431                        fclose(loadavg);
432                }
433        }
434        if (!ret) {
435                const char *argv[] = {
436                        "sensord", sensord_args.rrdFile, rrdBuff, NULL
437                };
438                if ((ret = rrd_update(3, (char **) /* WEAK */ argv))) {
439                        sensorLog(LOG_ERR, "Error updating RRD file: %s: %s",
440                                  sensord_args.rrdFile, rrd_get_error());
441                }
442        }
443        sensorLog(LOG_DEBUG, "sensor rrd updated");
444
445        return ret;
446}
447
448int rrdCGI(void)
449{
450        int ret = 0, i;
451
452        printf("#!" RRDCGI "\n\n<HTML>\n<HEAD>\n<TITLE>sensord</TITLE>\n</HEAD>\n<BODY>\n<H1>sensord</H1>\n");
453        for (i = 0; i < ARRAY_SIZE(graphs); i++) {
454                struct gr *graph = &graphs[i];
455
456                printf("<H2>%s</H2>\n", graph->h2);
457                printf("<P>\n<RRD::GRAPH %s/%s.png\n\t--imginfo '<IMG SRC=" WWWDIR "/%%s WIDTH=%%lu HEIGHT=%%lu>'\n\t-a PNG\n\t-h 200 -w 800\n",
458                       sensord_args.cgiDir, graph->image);
459                printf("\t--lazy\n\t-v '%s'\n\t-t '%s'\n\t-x '%s'\n\t%s",
460                       graph->axisTitle, graph->title, graph->axisDefn,
461                       graph->options);
462                if (!ret)
463                        ret = applyToFeatures(rrdCGI_DEF, graph);
464                if (!ret && sensord_args.doLoad && graph->loadAvg)
465                        ret = rrdCGI_DEF(graph, LOADAVG, LOAD_AVERAGE, NULL);
466                if (!ret)
467                        ret = applyToFeatures(rrdCGI_LINE, graph);
468                if (!ret && sensord_args.doLoad && graph->loadAvg)
469                        ret = rrdCGI_LINE(graph, LOADAVG, LOAD_AVERAGE, NULL);
470                printf (">\n</P>\n");
471        }
472        printf("<p>\n<small><b>sensord</b> by <a href=\"mailto:merlin@merlin.org\">Merlin Hughes</a>, all credit to the <a href=\"http://www.lm-sensors.org/\">lm_sensors</a> crew.</small>\n</p>\n");
473        printf("</BODY>\n</HTML>\n");
474
475        return ret;
476}
Note: See TracBrowser for help on using the browser.