@cryptotaxi247 / netdata-1 / commits / 99c1ebbee

added gzip compression and chunked http responses support

Costa Tsaousis (ktsaou) committed Apr 5, 2014 at 09:02 UTC 99c1ebbeecf83c1a529f9334f3d72a6644b300a4
3 files changed +3458 -3233
netdata.c
+3453 -3223
@@ -1,3223 +1,3453 @@
1 -// enable strcasestr()
2 -#define _GNU_SOURCE
3 -
4 -#include <stdio.h>
5 -#include <stdlib.h>
6 -#include <string.h>
7 -#include <time.h>
8 -#include <unistd.h>
9 -#include <pwd.h>
10 -#include <sys/types.h>
11 -#include <sys/time.h>
12 -
13 -#include <sys/socket.h>
14 -#include <sys/select.h>
15 -#include <sys/resource.h>
16 -#include <sys/stat.h>
17 -#include <sys/sendfile.h>
18 -
19 -#include <arpa/inet.h>
20 -#include <netinet/in.h>
21 -#include <netinet/tcp.h>
22 -
23 -#include <errno.h>
24 -#include <stdarg.h>
25 -#include <locale.h>
26 -#include <signal.h>
27 -#include <ctype.h>
28 -#include <time.h>
29 -#include <fcntl.h>
30 -#include <syslog.h>
31 -
32 -#include <pthread.h>
33 -
34 -#define RRD_DIMENSION_ABSOLUTE 0
35 -#define RRD_DIMENSION_INCREMENTAL 1
36 -#define RRD_DIMENSION_PCENT_OVER_TOTAL 2
37 -
38 -#define RRD_TYPE_NET "net"
39 -#define RRD_TYPE_NET_LEN strlen(RRD_TYPE_NET)
40 -
41 -#define RRD_TYPE_TC "tc"
42 -#define RRD_TYPE_TC_LEN strlen(RRD_TYPE_TC)
43 -
44 -#define RRD_TYPE_DISK "disk"
45 -#define RRD_TYPE_DISK_LEN strlen(RRD_TYPE_DISK)
46 -
47 -#define RRD_TYPE_NET_SNMP "ipv4"
48 -#define RRD_TYPE_NET_SNMP_LEN strlen(RRD_TYPE_NET_SNMP)
49 -
50 -#define RRD_TYPE_NET_STAT_CONNTRACK "conntrack"
51 -#define RRD_TYPE_NET_STAT_CONNTRACK_LEN strlen(RRD_TYPE_NET_STAT_CONNTRACK)
52 -
53 -#define RRD_TYPE_NET_IPVS "ipvs"
54 -#define RRD_TYPE_NET_IPVS_LEN strlen(RRD_TYPE_NET_IPVS)
55 -
56 -#define RRD_TYPE_STAT "cpu"
57 -#define RRD_TYPE_STAT_LEN strlen(RRD_TYPE_STAT)
58 -
59 -#define WEB_PATH_FILE "file"
60 -#define WEB_PATH_DATA "data"
61 -#define WEB_PATH_GRAPH "graph"
62 -
63 -// internal defaults
64 -#define UPDATE_EVERY 1
65 -#define UPDATE_EVERY_MAX 3600
66 -#define LISTEN_PORT 19999
67 -#define HISTORY 3600
68 -#define HISTORY_MAX (86400*10)
69 -#define SAVE_PATH "/tmp"
70 -
71 -#define D_WEB_BUFFER 0x00000001
72 -#define D_WEB_CLIENT 0x00000002
73 -#define D_LISTENER 0x00000004
74 -#define D_WEB_DATA 0x00000008
75 -#define D_OPTIONS 0x00000010
76 -#define D_PROCNETDEV_LOOP 0x00000020
77 -#define D_RRD_STATS 0x00000040
78 -#define D_WEB_CLIENT_ACCESS 0x00000080
79 -#define D_TC_LOOP 0x00000100
80 -
81 -#define CT_APPLICATION_JSON 1
82 -#define CT_TEXT_PLAIN 2
83 -#define CT_TEXT_HTML 3
84 -#define CT_APPLICATION_X_JAVASCRIPT 4
85 -#define CT_TEXT_CSS 5
86 -#define CT_TEXT_XML 6
87 -#define CT_APPLICATION_XML 7
88 -#define CT_TEXT_XSL 8
89 -#define CT_APPLICATION_OCTET_STREAM 9
90 -#define CT_APPLICATION_X_FONT_TRUETYPE 10
91 -#define CT_APPLICATION_X_FONT_OPENTYPE 11
92 -#define CT_APPLICATION_FONT_WOFF 12
93 -#define CT_APPLICATION_VND_MS_FONTOBJ 13
94 -#define CT_IMAGE_SVG_XML 14
95 -
96 -// configuration
97 -#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS)
98 -//#define DEBUG 0xffffffff
99 -//#define DEBUG (0)
100 -
101 -#define EXIT_FAILURE 1
102 -#define LISTEN_BACKLOG 100
103 -
104 -#define MAX_SOCKET_DATA_LENGTH (256 * 1024)
105 -#define SOCKET_DATA_LENGTH_INCREASE_STEP 65536
106 -
107 -#define MAX_HTTP_HEADER_SIZE 16384
108 -
109 -#define MAX_PROC_NET_DEV_LINE 4096
110 -#define MAX_PROC_NET_DEV_IFACE_NAME 1024
111 -
112 -#define MAX_PROC_DISKSTATS_LINE 4096
113 -#define MAX_PROC_DISKSTATS_DISK_NAME 1024
114 -
115 -#define MAX_PROC_NET_SNMP_LINE 4096
116 -#define MAX_PROC_NET_SNMP_NAME 1024
117 -
118 -#define MAX_PROC_NET_STAT_CONNTRACK_LINE 4096
119 -#define MAX_PROC_NET_STAT_CONNTRACK_NAME 1024
120 -
121 -#define MAX_PROC_NET_IPVS_LINE 4096
122 -#define MAX_PROC_NET_IPVS_NAME 1024
123 -
124 -#define MAX_PROC_STAT_LINE 4096
125 -#define MAX_PROC_STAT_NAME 1024
126 -
127 -
128 -int silent = 0;
129 -int save_history = HISTORY;
130 -int update_every = UPDATE_EVERY;
131 -int listen_port = LISTEN_PORT;
132 -
133 -
134 -// ----------------------------------------------------------------------------
135 -// helpers
136 -
137 -unsigned long long usecdiff(struct timeval *now, struct timeval *last) {
138 - return ((((now->tv_sec * 1000000L) + now->tv_usec) - ((last->tv_sec * 1000000L) + last->tv_usec)));
139 -}
140 -
141 -// ----------------------------------------------------------------------------
142 -// LOG
143 -
144 -void log_date()
145 -{
146 - char outstr[200];
147 - time_t t;
148 - struct tm *tmp;
149 -
150 - t = time(NULL);
151 - tmp = localtime(&t);
152 -
153 - if (tmp == NULL) return;
154 - if (strftime(outstr, sizeof(outstr), "%y-%m-%d %H:%M:%S", tmp) == 0) return;
155 -
156 - fprintf(stderr, "%s: ", outstr);
157 -}
158 -
159 -#define debug(args...) debug_int(__FILE__, __FUNCTION__, __LINE__, ##args)
160 -
161 -void debug_int( const char *file, const char *function, const unsigned long line, unsigned long type, const char *fmt, ... )
162 -{
163 - if(silent) return;
164 -
165 - va_list args;
166 -
167 - if(DEBUG & type) {
168 - log_date();
169 - va_start( args, fmt );
170 - fprintf(stderr, "DEBUG (%04lu@%-15.15s): ", line, function);
171 - vfprintf( stderr, fmt, args );
172 - va_end( args );
173 - fprintf(stderr, "\n");
174 - }
175 -}
176 -
177 -#define error(args...) error_int(__FILE__, __FUNCTION__, __LINE__, ##args)
178 -
179 -void error_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... )
180 -{
181 - va_list args;
182 -
183 - if(!silent) {
184 - log_date();
185 -
186 - va_start( args, fmt );
187 - fprintf(stderr, "ERROR (%04lu@%-15.15s): ", line, function);
188 - vfprintf( stderr, fmt, args );
189 - va_end( args );
190 -
191 - if(errno) {
192 - fprintf(stderr, " (errno %d, %s)\n", errno, strerror(errno));
193 - errno = 0;
194 - }
195 - else fprintf(stderr, "\n");
196 - }
197 -
198 - va_start( args, fmt );
199 - vsyslog(LOG_ERR, fmt, args );
200 - va_end( args );
201 -}
202 -
203 -#define fatal(args...) fatal_int(__FILE__, __FUNCTION__, __LINE__, ##args)
204 -
205 -void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... )
206 -{
207 - va_list args;
208 -
209 - if(!silent) {
210 - log_date();
211 -
212 - va_start( args, fmt );
213 - fprintf(stderr, "FATAL (%04lu@%-15.15s): ", line, function);
214 - vfprintf( stderr, fmt, args );
215 - va_end( args );
216 -
217 - perror(" # ");
218 - fprintf(stderr, "\n");
219 - }
220 -
221 - va_start( args, fmt );
222 - vsyslog(LOG_CRIT, fmt, args );
223 - va_end( args );
224 -
225 - exit(EXIT_FAILURE);
226 -}
227 -
228 -
229 -// ----------------------------------------------------------------------------
230 -// URL encode / decode
231 -// code from: http://www.geekhideout.com/urlcode.shtml
232 -
233 -/* Converts a hex character to its integer value */
234 -char from_hex(char ch) {
235 - return (char)(isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10);
236 -}
237 -
238 -/* Converts an integer value to its hex character*/
239 -char to_hex(char code) {
240 - static char hex[] = "0123456789abcdef";
241 - return hex[code & 15];
242 -}
243 -
244 -/* Returns a url-encoded version of str */
245 -/* IMPORTANT: be sure to free() the returned string after use */
246 -char *url_encode(char *str) {
247 - char *pstr = str,
248 - *buf = malloc(strlen(str) * 3 + 1),
249 - *pbuf = buf;
250 -
251 - while (*pstr) {
252 - if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
253 - *pbuf++ = *pstr;
254 -
255 - else if (*pstr == ' ')
256 - *pbuf++ = '+';
257 -
258 - else
259 - *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
260 -
261 - pstr++;
262 - }
263 -
264 - *pbuf = '\0';
265 -
266 - return buf;
267 -}
268 -
269 -/* Returns a url-decoded version of str */
270 -/* IMPORTANT: be sure to free() the returned string after use */
271 -char *url_decode(char *str) {
272 - char *pstr = str,
273 - *buf = malloc(strlen(str) + 1),
274 - *pbuf = buf;
275 -
276 - if(!buf) fatal("Cannot allocate memory.");
277 -
278 - while (*pstr) {
279 - if (*pstr == '%') {
280 - if (pstr[1] && pstr[2]) {
281 - *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
282 - pstr += 2;
283 - }
284 - }
285 - else if (*pstr == '+')
286 - *pbuf++ = ' ';
287 -
288 - else
289 - *pbuf++ = *pstr;
290 -
291 - pstr++;
292 - }
293 -
294 - *pbuf = '\0';
295 -
296 - return buf;
297 -}
298 -
299 -
300 -// ----------------------------------------------------------------------------
301 -// socket
302 -
303 -int create_listen_socket(int port)
304 -{
305 - int sock=-1;
306 - int sockopt=1;
307 - struct sockaddr_in name;
308 -
309 - debug(D_LISTENER, "Creating new listening socket on port %d", port);
310 -
311 - sock = socket(AF_INET, SOCK_STREAM, 0);
312 - if (sock < 0)
313 - fatal("socket() failed, errno=%d", errno);
314 -
315 - /* avoid "address already in use" */
316 - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&sockopt, sizeof(sockopt));
317 -
318 - memset(&name, 0, sizeof(struct sockaddr_in));
319 - name.sin_family = AF_INET;
320 - name.sin_port = htons (port);
321 - name.sin_addr.s_addr = htonl (INADDR_ANY);
322 - if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
323 - fatal("bind() failed, errno=%d", errno);
324 -
325 - if (listen(sock, LISTEN_BACKLOG) < 0)
326 - fatal("listen() failed, errno=%d", errno);
327 -
328 - debug(D_LISTENER, "Listening Port %d created", port);
329 - return sock;
330 -}
331 -
332 -
333 -// ----------------------------------------------------------------------------
334 -// RRD STATS
335 -
336 -#define RRD_STATS_NAME_MAX 1024
337 -
338 -struct rrd_dimension {
339 - char id[RRD_STATS_NAME_MAX + 1];
340 - char name[RRD_STATS_NAME_MAX + 1];
341 - size_t bytes;
342 - size_t entries;
343 -
344 - int type;
345 - int hidden; // if set to non zero, this dimension will not be sent to the client
346 -
347 - long multiplier;
348 - long divisor;
349 -
350 - void *values;
351 -
352 - time_t last_updated;
353 -
354 - struct rrd_dimension *next;
355 -};
356 -typedef struct rrd_dimension RRD_DIMENSION;
357 -
358 -struct rrd_stats {
359 - pthread_mutex_t mutex;
360 -
361 - char id[RRD_STATS_NAME_MAX + 1];
362 - char name[RRD_STATS_NAME_MAX + 1];
363 - char title[RRD_STATS_NAME_MAX + 1];
364 - char vtitle[RRD_STATS_NAME_MAX + 1];
365 -
366 - char usertitle[RRD_STATS_NAME_MAX + 1];
367 - char userpriority[RRD_STATS_NAME_MAX + 1];
368 -
369 - char envtitle[RRD_STATS_NAME_MAX + 1];
370 - char envpriority[RRD_STATS_NAME_MAX + 1];
371 -
372 - char type[RRD_STATS_NAME_MAX + 1];
373 -
374 - char hostname[RRD_STATS_NAME_MAX + 1];
375 -
376 - size_t entries;
377 - size_t current_entry;
378 - // size_t last_entry;
379 -
380 - time_t last_updated;
381 -
382 - struct timeval *times;
383 - RRD_DIMENSION *dimensions;
384 - struct rrd_stats *next;
385 -};
386 -typedef struct rrd_stats RRD_STATS;
387 -
388 -RRD_STATS *root = NULL;
389 -pthread_mutex_t root_mutex = PTHREAD_MUTEX_INITIALIZER;
390 -
391 -RRD_STATS *rrd_stats_create(const char *id, const char *name, unsigned long entries, const char *title, const char *vtitle, const char *type)
392 -{
393 - RRD_STATS *st = NULL;
394 - char *p;
395 -
396 - debug(D_RRD_STATS, "Creating RRD_STATS for '%s'.", name);
397 -
398 - st = calloc(1, sizeof(RRD_STATS));
399 - if(!st) return NULL;
400 -
401 - st->times = calloc(entries, sizeof(struct timeval));
402 - if(!st->times) {
403 - error("Cannot allocate %lu entries of %lu bytes each for RRD_STATS.", st->entries, sizeof(struct timeval));
404 - free(st);
405 - return NULL;
406 - }
407 -
408 - strncpy(st->id, id, RRD_STATS_NAME_MAX);
409 - st->id[RRD_STATS_NAME_MAX] = '\0';
410 -
411 - strncpy(st->name, name, RRD_STATS_NAME_MAX);
412 - st->name[RRD_STATS_NAME_MAX] = '\0';
413 - while((p = strchr(st->name, '/'))) *p = '_';
414 - while((p = strchr(st->name, '?'))) *p = '_';
415 - while((p = strchr(st->name, '&'))) *p = '_';
416 -
417 - strncpy(st->title, title, RRD_STATS_NAME_MAX);
418 - st->title[RRD_STATS_NAME_MAX] = '\0';
419 -
420 - strncpy(st->vtitle, vtitle, RRD_STATS_NAME_MAX);
421 - st->vtitle[RRD_STATS_NAME_MAX] = '\0';
422 -
423 - strncpy(st->type, type, RRD_STATS_NAME_MAX);
424 - st->type[RRD_STATS_NAME_MAX] = '\0';
425 -
426 - // check if there is a name for it in the environment
427 - sprintf(st->envtitle, "NETDATA_TITLE_%s", st->id);
428 - while((p = strchr(st->envtitle, '/'))) *p = '_';
429 - while((p = strchr(st->envtitle, '.'))) *p = '_';
430 - while((p = strchr(st->envtitle, '-'))) *p = '_';
431 - p = getenv(st->envtitle);
432 -
433 - if(p) strncpy(st->usertitle, p, RRD_STATS_NAME_MAX);
434 - else strncpy(st->usertitle, st->name, RRD_STATS_NAME_MAX);
435 - st->usertitle[RRD_STATS_NAME_MAX] = '\0';
436 -
437 - sprintf(st->envpriority, "NETDATA_PRIORITY_%s", st->id);
438 - while((p = strchr(st->envpriority, '/'))) *p = '_';
439 - while((p = strchr(st->envpriority, '.'))) *p = '_';
440 - while((p = strchr(st->envpriority, '-'))) *p = '_';
441 - p = getenv(st->envpriority);
442 -
443 - if(p) strncpy(st->userpriority, p, RRD_STATS_NAME_MAX);
444 - else strncpy(st->userpriority, st->name, RRD_STATS_NAME_MAX);
445 - st->userpriority[RRD_STATS_NAME_MAX] = '\0';
446 -
447 - if(gethostname(st->hostname, RRD_STATS_NAME_MAX) == -1)
448 - error("Cannot get hostname.");
449 -
450 - st->entries = entries;
451 - st->current_entry = 0;
452 - // st->last_entry = 0;
453 - st->dimensions = NULL;
454 - st->last_updated = time(NULL);
455 -
456 - pthread_mutex_init(&st->mutex, NULL);
457 - pthread_mutex_lock(&st->mutex);
458 - pthread_mutex_lock(&root_mutex);
459 -
460 - st->next = root;
461 - root = st;
462 -
463 - pthread_mutex_unlock(&root_mutex);
464 - // leave st->mutex locked
465 -
466 - return(st);
467 -}
468 -
469 -RRD_DIMENSION *rrd_stats_dimension_add(RRD_STATS *st, const char *id, const char *name, size_t bytes, long multiplier, long divisor, int type)
470 -{
471 - RRD_DIMENSION *rd = NULL;
472 -
473 - debug(D_RRD_STATS, "Adding dimension '%s' (%s) to RRD_STATS '%s' (%s).", name, id, st->name, st->id);
474 -
475 - rd = calloc(1, sizeof(RRD_DIMENSION));
476 - if(!rd) return NULL;
477 -
478 - rd->bytes = bytes;
479 - rd->entries = st->entries;
480 - rd->multiplier = multiplier;
481 - rd->divisor = divisor;
482 - rd->type = type;
483 - rd->values = calloc(rd->entries, rd->bytes);
484 - if(!rd->values) {
485 - error("Cannot allocate %lu entries of %lu bytes each for RRD_DIMENSION.", rd->entries, rd->bytes);
486 - free(rd);
487 - return NULL;
488 - }
489 -
490 - strncpy(rd->id, id, RRD_STATS_NAME_MAX);
491 - rd->id[RRD_STATS_NAME_MAX] = '\0';
492 -
493 - strncpy(rd->name, name, RRD_STATS_NAME_MAX);
494 - rd->name[RRD_STATS_NAME_MAX] = '\0';
495 -
496 - rd->next = st->dimensions;
497 - st->dimensions = rd;
498 -
499 - return(rd);
500 -}
501 -
502 -void rrd_stats_dimension_free(RRD_DIMENSION *rd)
503 -{
504 - if(rd->next) rrd_stats_dimension_free(rd->next);
505 - debug(D_RRD_STATS, "Removing dimension '%s'.", rd->name);
506 - free(rd->values);
507 - free(rd);
508 -}
509 -
510 -RRD_STATS *rrd_stats_find(const char *id)
511 -{
512 - pthread_mutex_lock(&root_mutex);
513 -
514 - RRD_STATS *st = root;
515 -
516 - for ( ; st ; st = st->next ) {
517 - if(strcmp(st->id, id) == 0) break;
518 - }
519 -
520 - pthread_mutex_unlock(&root_mutex);
521 - return(st);
522 -}
523 -
524 -RRD_STATS *rrd_stats_find_byname(const char *name)
525 -{
526 - pthread_mutex_lock(&root_mutex);
527 -
528 - RRD_STATS *st = root;
529 -
530 - for ( ; st ; st = st->next ) {
531 - if(strcmp(st->name, name) == 0) break;
532 - }
533 -
534 - pthread_mutex_unlock(&root_mutex);
535 - return(st);
536 -}
537 -
538 -RRD_DIMENSION *rrd_stats_dimension_find(RRD_STATS *st, const char *id)
539 -{
540 - RRD_DIMENSION *rd = st->dimensions;
541 -
542 - for ( ; rd ; rd = rd->next ) {
543 - if(strcmp(rd->id, id) == 0) break;
544 - }
545 -
546 - return(rd);
547 -}
548 -
549 -void rrd_stats_next(RRD_STATS *st)
550 -{
551 - struct timeval *now;
552 -
553 - pthread_mutex_lock(&st->mutex);
554 -
555 - // st->current_entry should never be outside the array
556 - // or, the parallel threads may end up crashing
557 - // st->last_entry = st->current_entry;
558 - st->current_entry = ((st->current_entry + 1) >= st->entries) ? 0 : st->current_entry + 1;
559 -
560 - now = &st->times[st->current_entry];
561 - gettimeofday(now, NULL);
562 -
563 - st->last_updated = now->tv_sec;
564 -
565 - // leave mutex locked
566 -}
567 -
568 -void rrd_stats_done(RRD_STATS *st)
569 -{
570 - RRD_DIMENSION *rd, *last;
571 -
572 - // find if there are any obsolete dimensions (not updated recently)
573 - for( rd = st->dimensions, last = NULL ; rd ; ) {
574 - if((rd->last_updated + (10 * update_every)) < st->last_updated) { // remove it only it is not updated in 10 seconds
575 - debug(D_RRD_STATS, "Removing obsolete dimension '%s' (%s) of '%s' (%s).", rd->name, rd->id, st->name, st->id);
576 -
577 - if(!last) {
578 - st->dimensions = rd->next;
579 - rd->next = NULL;
580 - rrd_stats_dimension_free(rd);
581 - rd = st->dimensions;
582 - continue;
583 - }
584 - else {
585 - last->next = rd->next;
586 - rd->next = NULL;
587 - rrd_stats_dimension_free(rd);
588 - rd = last->next;
589 - continue;
590 - }
591 - }
592 -
593 - last = rd;
594 - rd = rd->next;
595 - }
596 -
597 - pthread_mutex_unlock(&st->mutex);
598 -}
599 -
600 -int rrd_stats_dimension_hide(RRD_STATS *st, const char *id)
601 -{
602 - RRD_DIMENSION *rd = rrd_stats_dimension_find(st, id);
603 - if(!rd) {
604 - error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id);
605 - return 1;
606 - }
607 -
608 - rd->hidden = 1;
609 - return 0;
610 -}
611 -
612 -
613 -int rrd_stats_dimension_set(RRD_STATS *st, const char *id, void *data)
614 -{
615 - RRD_DIMENSION *rd = rrd_stats_dimension_find(st, id);
616 - if(!rd) {
617 - error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id);
618 - return 1;
619 - }
620 -
621 - rd->last_updated = st->last_updated;
622 -
623 - if(rd->bytes == sizeof(long long)) {
624 - long long *dimension = rd->values, *value = data;
625 -
626 - dimension[st->current_entry] = (*value);
627 - }
628 - else if(rd->bytes == sizeof(long)) {
629 - long *dimension = rd->values, *value = data;
630 -
631 - dimension[st->current_entry] = (*value);
632 - }
633 - else if(rd->bytes == sizeof(int)) {
634 - int *dimension = rd->values, *value = data;
635 -
636 - dimension[st->current_entry] = (*value);
637 - }
638 - else if(rd->bytes == sizeof(char)) {
639 - char *dimension = rd->values, *value = data;
640 -
641 - dimension[st->current_entry] = (*value);
642 - }
643 - else fatal("I don't know how to handle data of length %d bytes.", rd->bytes);
644 -
645 - return 0;
646 -}
647 -
648 -#define GROUP_AVERAGE 0
649 -#define GROUP_MAX 1
650 -
651 -size_t rrd_stats_one_json(RRD_STATS *st, char *options, char *buffer, size_t len)
652 -{
653 - size_t i = 0;
654 - if(i + 200 > len) return(0);
655 -
656 - i += sprintf(&buffer[i], "\t\t{\n");
657 - i += sprintf(&buffer[i], "\t\t\t\"id\" : \"%s\",\n", st->id);
658 - i += sprintf(&buffer[i], "\t\t\t\"name\" : \"%s\",\n", st->name);
659 - i += sprintf(&buffer[i], "\t\t\t\"type\" : \"%s\",\n", st->type);
660 - i += sprintf(&buffer[i], "\t\t\t\"title\" : \"%s %s\",\n", st->title, st->name);
661 - i += sprintf(&buffer[i], "\t\t\t\"usertitle\" : \"%s %s (%s)\",\n", st->title, st->usertitle, st->id);
662 - i += sprintf(&buffer[i], "\t\t\t\"userpriority\" : \"%s\",\n", st->userpriority);
663 - i += sprintf(&buffer[i], "\t\t\t\"envtitle\" : \"%s\",\n", st->envtitle);
664 - i += sprintf(&buffer[i], "\t\t\t\"envpriority\" : \"%s\",\n", st->envpriority);
665 - i += sprintf(&buffer[i], "\t\t\t\"hostname\" : \"%s\",\n", st->hostname);
666 - i += sprintf(&buffer[i], "\t\t\t\"vtitle\" : \"%s\",\n", st->vtitle);
667 - i += sprintf(&buffer[i], "\t\t\t\"url\" : \"/data/%s/%s\",\n", st->name, options?options:"");
668 - i += sprintf(&buffer[i], "\t\t\t\"entries\" : %ld,\n", st->entries);
669 - i += sprintf(&buffer[i], "\t\t\t\"current\" : %ld,\n", st->current_entry);
670 - i += sprintf(&buffer[i], "\t\t\t\"update_every\" : %d,\n", update_every);
671 - i += sprintf(&buffer[i], "\t\t\t\"last_updated\" : %lu,\n", st->last_updated);
672 - i += sprintf(&buffer[i], "\t\t\t\"last_updated_secs_ago\" : %lu\n", time(NULL) - st->last_updated);
673 - i += sprintf(&buffer[i], "\t\t}");
674 -
675 - return(i);
676 -}
677 -
678 -#define RRD_GRAPH_JSON_HEADER "{\n\t\"charts\": [\n"
679 -#define RRD_GRAPH_JSON_FOOTER "\n\t]\n}\n"
680 -
681 -size_t rrd_stats_graph_json(RRD_STATS *st, char * options, char *buffer, size_t len)
682 -{
683 - size_t i = 0;
684 - i += sprintf(&buffer[i], RRD_GRAPH_JSON_HEADER);
685 - i += rrd_stats_one_json(st, options, &buffer[i], len - i);
686 - i += sprintf(&buffer[i], RRD_GRAPH_JSON_FOOTER);
687 - return(i);
688 -}
689 -
690 -size_t rrd_stats_all_json(char *buffer, size_t len)
691 -{
692 - size_t i = 0, c;
693 - RRD_STATS *st;
694 -
695 - i += sprintf(&buffer[i], RRD_GRAPH_JSON_HEADER);
696 -
697 - for(st = root, c = 0; st ; st = st->next, c++) {
698 - if(c) i += sprintf(&buffer[i], "%s", ",\n");
699 - i += rrd_stats_one_json(st, NULL, &buffer[i], len - i);
700 - }
701 -
702 - i += sprintf(&buffer[i], RRD_GRAPH_JSON_FOOTER);
703 - return(i);
704 -}
705 -
706 -long long rrd_stats_dimension_get(RRD_DIMENSION *rd, size_t position)
707 -{
708 - if(rd->bytes == sizeof(long long)) {
709 - long long *dimension = rd->values;
710 - return(dimension[position]);
711 - }
712 - else if(rd->bytes == sizeof(long)) {
713 - long *dimension = rd->values;
714 - return(dimension[position]);
715 - }
716 - else if(rd->bytes == sizeof(int)) {
717 - int *dimension = rd->values;
718 - return(dimension[position]);
719 - }
720 - else if(rd->bytes == sizeof(char)) {
721 - char *dimension = rd->values;
722 - return(dimension[position]);
723 - }
724 - else fatal("Cannot produce JSON for size %d bytes dimension.", rd->bytes);
725 -
726 - return(0);
727 -}
728 -
729 -size_t rrd_stats_json(RRD_STATS *st, char *b, size_t length, size_t entries_to_show, size_t group_count, int group_method)
730 -{
731 - pthread_mutex_lock(&st->mutex);
732 -
733 - // check the options
734 - if(entries_to_show <= 0) entries_to_show = 1;
735 - if(group_count <= 0) group_count = 1;
736 - if(group_count > st->entries / 20) group_count = st->entries / 20;
737 -
738 - size_t i = 0; // the bytes of JSON output we have generated so far
739 - size_t printed = 0; // the lines of JSON data we have generated so far
740 -
741 - size_t current_entry = st->current_entry;
742 - size_t t, lt; // t = the current entry, lt = the lest entry of data
743 - long count = st->entries; // count down of the entries examined so far
744 - long pad = 0; // align the entries when grouping values together
745 -
746 - RRD_DIMENSION *rd;
747 - size_t c = 0; // counter for dimension loops
748 - size_t dimensions = 0; // the total number of dimensions present
749 -
750 - unsigned long long usec = 0;// usec between the entries
751 - char dtm[201]; // temp variable for storing dates
752 -
753 - int we_need_totals = 0; // if set, we should calculate totals for all dimensions
754 -
755 - // find how many dimensions we have
756 - for( rd = st->dimensions ; rd ; rd = rd->next) {
757 - dimensions++;
758 - if(rd->type == RRD_DIMENSION_PCENT_OVER_TOTAL) we_need_totals++;
759 - }
760 -
761 - if(!dimensions) {
762 - pthread_mutex_unlock(&st->mutex);
763 - return sprintf(b, "No dimensions yet.");
764 - }
765 -
766 - // temporary storage to keep track of group values and counts
767 - long double group_values[dimensions];
768 - int group_counts[dimensions];
769 - for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++)
770 - group_values[c] = group_counts[c] = 0;
771 -
772 - i += sprintf(&b[i], "{\n \"cols\":\n [\n");
773 - i += sprintf(&b[i], " {\"id\":\"\",\"label\":\"time\",\"pattern\":\"\",\"type\":\"datetime\"}");
774 -
775 - for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++)
776 - if(!rd->hidden)
777 - i += sprintf(&b[i], ",\n {\"id\":\"\",\"label\":\"%s\",\"pattern\":\"\",\"type\":\"number\"}", rd->name);
778 -
779 - i += sprintf(&b[i], " ],\n \"rows\":\n [\n");
780 -
781 - // to allow grouping on the same values, we need a pad
782 - pad = current_entry % group_count;
783 -
784 - // make sure current_entry is within limits
785 - if(current_entry < 0 || current_entry >= st->entries) current_entry = 0;
786 -
787 - // find the old entry of the round-robin
788 - t = current_entry + 1;
789 - if(t >= st->entries) t = 0;
790 - lt = t;
791 -
792 - // find the current entry
793 - t++;
794 - if(t >= st->entries) t = 0;
795 -
796 - // the loop in dimension data
797 - count -= 2;
798 - for ( ; t != current_entry && count >= 0 ; lt = t++, count--) {
799 - if(t >= st->entries) t = 0;
800 -
801 - // if the last is empty, loop again
802 - if(!st->times[lt].tv_sec) continue;
803 -
804 - // check if we may exceed the buffer provided
805 - if((length - i) < 1024) break;
806 -
807 - // prefer the most recent last entries
808 - if(((count-pad) / group_count) > entries_to_show) continue;
809 -
810 -
811 - // ok. we will use this entry!
812 - // find how much usec since the previous entry
813 -
814 - usec = usecdiff(&st->times[t], &st->times[lt]);
815 -
816 - if(((count-pad) % group_count) == 0) {
817 - if(printed >= entries_to_show) break;
818 -
819 - // generate the local date time
820 - struct tm *tm = localtime(&st->times[t].tv_sec);
821 - if(!tm) { error("localtime() failed."); continue; }
822 -
823 - sprintf(dtm, "\"Date(%d, %d, %d, %d, %d, %d, %d)\"", tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(st->times[t].tv_usec / 1000)); // datetime
824 - // strftime(dtm, 200, "\"Date(%Y, %m, %d, %H, %M, %S)\"", tm); // datetime
825 - // strftime(dtm, 200, "[%H, %M, %S, 0]", tm); // timeofday
826 - i += sprintf(&b[i], "%s {\"c\":[{\"v\":%s}", printed?"]},\n":"", dtm);
827 -
828 - printed++;
829 - }
830 -
831 - // if we need a PCENT_OVER_TOTAL, calculate the totals for the current and the last
832 - long double total = 0, oldtotal = 0;
833 - if(we_need_totals) {
834 - for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
835 - total += (long double)rrd_stats_dimension_get(rd, t);
836 - oldtotal += (long double)rrd_stats_dimension_get(rd, lt);
837 - }
838 - }
839 -
840 - for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
841 - //long long oldvalue, value; // temp variable for storing data values
842 - long double oldvalue, value; // temp variable for storing data values
843 -
844 - value = (long double)rrd_stats_dimension_get(rd, t);
845 - oldvalue = (long double)rrd_stats_dimension_get(rd, lt);
846 -
847 - switch(rd->type) {
848 - case RRD_DIMENSION_PCENT_OVER_TOTAL:
849 - value = (long double)100 * (value - oldvalue) / (total - oldtotal);
850 - break;
851 -
852 - case RRD_DIMENSION_INCREMENTAL:
853 - if(oldvalue > value) value = 0; // detect overflows and resets
854 - else value -= oldvalue;
855 - value = value * (long double)1000000L / (long double)usec;
856 - break;
857 -
858 - default:
859 - break;
860 - }
861 -
862 - value = value * (long double)rd->multiplier / (long double)rd->divisor;
863 -
864 - group_counts[c]++;
865 - switch(group_method) {
866 - case GROUP_MAX:
867 - if(abs(value) > abs(group_values[c])) group_values[c] = value;
868 - break;
869 -
870 - default:
871 - case GROUP_AVERAGE:
872 - group_values[c] += value;
873 - if(((count - pad) % group_count) == 0) group_values[c] /= (long double)group_counts[c];
874 - break;
875 - }
876 -
877 - if(((count-pad) % group_count) == 0) {
878 - if(!rd->hidden)
879 - i += sprintf(&b[i], ",{\"v\":%0.1Lf}", group_values[c]);
880 - //i += sprintf(&b[i], ",{\"v\":%lld}", group_values[c]);
881 -
882 - group_values[c] = group_counts[c] = 0;
883 - }
884 - }
885 - }
886 - if(printed) i += sprintf(&b[i], "]}");
887 - i += sprintf(&b[i], "\n ]\n}\n");
888 -
889 - pthread_mutex_unlock(&st->mutex);
890 - return(i);
891 -}
892 -
893 -// ----------------------------------------------------------------------------
894 -// listener (web server)
895 -
896 -struct web_buffer {
897 - size_t size; // allocation size of buffer
898 - size_t bytes; // current data length in buffer
899 - size_t sent; // current data length sent to output
900 - char *buffer; // the buffer
901 - int contenttype;
902 - size_t rbytes; // if non-zero, the excepted size of ifd
903 - time_t date; // the date this content has been generated
904 -};
905 -
906 -struct web_buffer *web_buffer_create(size_t size)
907 -{
908 - struct web_buffer *b;
909 -
910 - debug(D_WEB_BUFFER, "Creating new web buffer of size %d.", size);
911 -
912 - b = calloc(1, sizeof(struct web_buffer));
913 - if(!b) {
914 - error("Cannot allocate a web_buffer.");
915 - return NULL;
916 - }
917 -
918 - b->buffer = calloc(1, size);
919 - if(!b->buffer) {
920 - error("Cannot allocate a buffer of size %u.", size);
921 - free(b);
922 - return NULL;
923 - }
924 - b->size = size;
925 - b->contenttype = CT_TEXT_PLAIN;
926 - return(b);
927 -}
928 -
929 -void web_buffer_free(struct web_buffer *b)
930 -{
931 - debug(D_WEB_BUFFER, "Freeing web buffer of size %d.", b->size);
932 -
933 - if(b->buffer) free(b->buffer);
934 - free(b);
935 -}
936 -
937 -void web_buffer_increase(struct web_buffer *b, size_t free_size_required)
938 -{
939 - size_t left = b->size - b->bytes;
940 -
941 - if(left >= free_size_required) return;
942 - size_t increase = free_size_required - left;
943 -
944 - debug(D_WEB_BUFFER, "Increasing data buffer from size %d to %d.", b->size, b->size + increase);
945 -
946 - b->buffer = realloc(b->buffer, b->size + free_size_required - left);
947 - if(!b->buffer) fatal("Failed to increase data buffer from size %d to %d.", b->size, b->size + increase);
948 -
949 - b->size += increase;
950 -}
951 -
952 -#define WEB_CLIENT_MODE_NORMAL 0
953 -#define WEB_CLIENT_MODE_FILECOPY 1
954 -
955 -struct web_client {
956 - unsigned long long id;
957 - char client_ip[101];
958 -
959 - int mode;
960 - int keepalive;
961 -
962 - struct sockaddr_in clientaddr;
963 -
964 - pthread_t thread; // the thread servicing this client
965 - int obsolete; // if set to 1, the listener will remove this client
966 -
967 - int ifd;
968 - int ofd;
969 -
970 - struct web_buffer *data;
971 -
972 - int wait_receive;
973 - int wait_send;
974 -
975 - char response_header[MAX_HTTP_HEADER_SIZE+1];
976 -
977 - struct web_client *prev;
978 - struct web_client *next;
979 -} *web_clients = NULL;
980 -
981 -unsigned long long web_clients_count = 0;
982 -
983 -struct web_client *web_client_create(int listener)
984 -{
985 - struct web_client *w;
986 - socklen_t addrlen;
987 -
988 - w = calloc(1, sizeof(struct web_client));
989 - if(!w) {
990 - error("Cannot allocate new web_client memory.");
991 - return NULL;
992 - }
993 -
994 - w->id = ++web_clients_count;
995 - w->mode = WEB_CLIENT_MODE_NORMAL;
996 -
997 - addrlen = sizeof(w->clientaddr);
998 - w->ifd = accept(listener, (struct sockaddr *)&w->clientaddr, &addrlen);
999 - if (w->ifd == -1) {
1000 - error("%llu: Cannot accept new incoming connection.", w->id);
1001 - free(w);
1002 - return NULL;
1003 - }
1004 - w->ofd = w->ifd;
1005 -
1006 - strncpy(w->client_ip, inet_ntoa(w->clientaddr.sin_addr), 100);
1007 - w->client_ip[100] = '\0';
1008 -
1009 - syslog(LOG_NOTICE, "%llu: New client from %s.", w->id, w->client_ip);
1010 - debug(D_WEB_CLIENT_ACCESS, "%llu: New web client from %s on socket %d.", w->id, w->client_ip, w->ifd);
1011 -
1012 - {
1013 - int flag = 1;
1014 - if(setsockopt(w->ifd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int)) != 0) error("%llu: Cannot set SO_KEEPALIVE on socket.", w->id);
1015 - }
1016 -
1017 - w->data = web_buffer_create(MAX_SOCKET_DATA_LENGTH);
1018 - if(!w->data) {
1019 - close(w->ifd);
1020 - free(w);
1021 - return NULL;
1022 - }
1023 -
1024 - w->wait_receive = 1;
1025 -
1026 - if(web_clients) web_clients->prev = w;
1027 - w->next = web_clients;
1028 - web_clients = w;
1029 -
1030 - return(w);
1031 -}
1032 -
1033 -struct web_client *web_client_free(struct web_client *w)
1034 -{
1035 - struct web_client *n = w->next;
1036 -
1037 - debug(D_WEB_CLIENT_ACCESS, "%llu: Closing web client from %s.", w->id, inet_ntoa(w->clientaddr.sin_addr));
1038 -
1039 - if(w->prev) w->prev->next = w->next;
1040 - if(w->next) w->next->prev = w->prev;
1041 -
1042 - if(w == web_clients) web_clients = w->next;
1043 -
1044 - if(w->data) web_buffer_free(w->data);
1045 - close(w->ifd);
1046 - if(w->ofd != w->ifd) close(w->ofd);
1047 - free(w);
1048 -
1049 - return(n);
1050 -}
1051 -
1052 -int mysendfile(struct web_client *w, char *filename)
1053 -{
1054 - debug(D_WEB_CLIENT, "%llu: Looking for file '%s'...", w->id, filename);
1055 -
1056 - // skip leading slashes
1057 - while (filename[0] == '/') filename = &filename[1];
1058 -
1059 - // if the filename contain known paths, skip them
1060 - if(strncmp(filename, WEB_PATH_DATA "/", strlen(WEB_PATH_DATA) + 1) == 0) filename = &filename[strlen(WEB_PATH_DATA) + 1];
1061 - else if(strncmp(filename, WEB_PATH_GRAPH "/", strlen(WEB_PATH_GRAPH) + 1) == 0) filename = &filename[strlen(WEB_PATH_GRAPH) + 1];
1062 - else if(strncmp(filename, WEB_PATH_FILE "/", strlen(WEB_PATH_FILE) + 1) == 0) filename = &filename[strlen(WEB_PATH_FILE) + 1];
1063 -
1064 - // if the filename contains a / or a .., refuse to serve it
1065 - if(strstr(filename, "/") != 0 || strstr(filename, "..") != 0) {
1066 - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename);
1067 - w->data->bytes = sprintf(w->data->buffer, "File '%s' cannot be served. Filenames cannot contain / or ..", filename);
1068 - return 400;
1069 - }
1070 -
1071 - // access the file in web/*
1072 - char webfilename[FILENAME_MAX + 1];
1073 - strcpy(webfilename, "web/");
1074 - strncpy(&webfilename[4], filename, FILENAME_MAX - 4);
1075 - webfilename[FILENAME_MAX] = '\0';
1076 -
1077 - // check if the file exists
1078 - struct stat stat;
1079 - if(lstat(webfilename, &stat) != 0) {
1080 - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not found.", w->id, filename);
1081 - w->data->bytes = sprintf(w->data->buffer, "File '%s' does not exist, or is not accessible.", filename);
1082 - return 404;
1083 - }
1084 -
1085 - // check if the file is owned by us
1086 - if(stat.st_uid != getuid() && stat.st_uid != geteuid()) {
1087 - error("%llu: File '%s' is owned by user %d (I run as user %d). Access Denied.", w->id, filename, stat.st_uid, getuid());
1088 - w->data->bytes = sprintf(w->data->buffer, "Access to file '%s' is not permitted.", filename);
1089 - return 403;
1090 - }
1091 -
1092 - // open the file
1093 - w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY);
1094 - if(w->ifd == -1) {
1095 - w->ifd = w->ofd;
1096 -
1097 - if(errno == EBUSY || errno == EAGAIN) {
1098 - error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, filename);
1099 - sprintf(w->response_header, "Location: /" WEB_PATH_FILE "/%s\r\n", filename);
1100 - w->data->bytes = sprintf(w->data->buffer, "The file '%s' is currently busy. Please try again later.", filename);
1101 - return 307;
1102 - }
1103 - else {
1104 - error("%llu: Cannot open file '%s'.", w->id, filename);
1105 - w->data->bytes = sprintf(w->data->buffer, "Cannot open file '%s'.", filename);
1106 - return 404;
1107 - }
1108 - }
1109 -
1110 - syslog(LOG_NOTICE, "%llu: Sending file '%s' to client %s.", w->id, filename, w->client_ip);
1111 -
1112 - // pick a Content-Type for the file
1113 - if(strstr(filename, ".html") != NULL) w->data->contenttype = CT_TEXT_HTML;
1114 - else if(strstr(filename, ".js") != NULL) w->data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
1115 - else if(strstr(filename, ".css") != NULL) w->data->contenttype = CT_TEXT_CSS;
1116 - else if(strstr(filename, ".xml") != NULL) w->data->contenttype = CT_TEXT_XML;
1117 - else if(strstr(filename, ".xsl") != NULL) w->data->contenttype = CT_TEXT_XSL;
1118 - else if(strstr(filename, ".txt") != NULL) w->data->contenttype = CT_TEXT_PLAIN;
1119 - else if(strstr(filename, ".svg") != NULL) w->data->contenttype = CT_IMAGE_SVG_XML;
1120 - else if(strstr(filename, ".ttf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_TRUETYPE;
1121 - else if(strstr(filename, ".otf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_OPENTYPE;
1122 - else if(strstr(filename, ".woff") != NULL) w->data->contenttype = CT_APPLICATION_FONT_WOFF;
1123 - else if(strstr(filename, ".eot") != NULL) w->data->contenttype = CT_APPLICATION_VND_MS_FONTOBJ;
1124 - else w->data->contenttype = CT_APPLICATION_OCTET_STREAM;
1125 -
1126 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, filename, stat.st_size, w->ifd, w->ofd);
1127 -
1128 - w->mode = WEB_CLIENT_MODE_FILECOPY;
1129 - w->wait_receive = 1;
1130 - w->wait_send = 0;
1131 - w->data->bytes = 0;
1132 - w->data->buffer[0] = '\0';
1133 - w->data->rbytes = stat.st_size;
1134 - w->data->date = stat.st_mtim.tv_sec;
1135 -
1136 - return 200;
1137 -}
1138 -
1139 -char *mystrsep(char **ptr, char *s)
1140 -{
1141 - char *p = "";
1142 - while ( !p[0] && *ptr ) p = strsep(ptr, s);
1143 - return(p);
1144 -}
1145 -
1146 -void web_client_reset(struct web_client *w)
1147 -{
1148 - if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1149 - close(w->ifd);
1150 - w->ifd = w->ofd;
1151 - }
1152 -
1153 - w->data->contenttype = CT_TEXT_PLAIN;
1154 - w->mode = WEB_CLIENT_MODE_NORMAL;
1155 -
1156 - w->data->rbytes = 0;
1157 - w->data->bytes = 0;
1158 - w->data->sent = 0;
1159 -
1160 - w->response_header[0] = '\0';
1161 - w->data->buffer[0] = '\0';
1162 -
1163 - w->wait_receive = 1;
1164 - w->wait_send = 0;
1165 -}
1166 -
1167 -void web_client_process(struct web_client *w)
1168 -{
1169 - int code = 500;
1170 - int bytes;
1171 -
1172 - // check if we have an empty line (end of HTTP header)
1173 - if(strstr(w->data->buffer, "\r\n\r\n")) {
1174 - debug(D_WEB_DATA, "%llu: Processing data buffer of %d bytes: '%s'.", w->id, w->data->bytes, w->data->buffer);
1175 -
1176 - // check if the client requested keep-alive HTTP
1177 - if(strcasestr(w->data->buffer, "Connection: keep-alive") == 0) w->keepalive = 0;
1178 - else w->keepalive = 1;
1179 -
1180 - char *buf = w->data->buffer;
1181 - char *tok = strsep(&buf, " \r\n");
1182 - char *url = NULL;
1183 -
1184 - if(buf && strcmp(tok, "GET") == 0) {
1185 - tok = strsep(&buf, " \r\n");
1186 - url = url_decode(tok);
1187 - debug(D_WEB_CLIENT, "%llu: Processing HTTP GET on url '%s'.", w->id, url);
1188 - }
1189 - else if (buf && strcmp(tok, "POST") == 0) {
1190 - w->keepalive = 0;
1191 - tok = strsep(&buf, " \r\n");
1192 - url = url_decode(tok);
1193 -
1194 - debug(D_WEB_CLIENT, "%llu: I don't know how to handle POST with form data. Assuming it is a GET on url '%s'.", w->id, url);
1195 - }
1196 -
1197 - if(url) {
1198 - tok = mystrsep(&url, "/?&");
1199 -
1200 - debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
1201 -
1202 - if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
1203 - // the client is requesting rrd data
1204 -
1205 - // get the name of the data to show
1206 - tok = mystrsep(&url, "/?&");
1207 - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1208 -
1209 - // do we have such a data set?
1210 - RRD_STATS *st = rrd_stats_find_byname(tok);
1211 - if(!st) st = rrd_stats_find(tok);
1212 - if(!st) {
1213 - // we don't have it
1214 - // try to send a file with that name
1215 - code = mysendfile(w, tok);
1216 - }
1217 - else {
1218 - // we have it
1219 - debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok);
1220 -
1221 - // how many entries does the client want?
1222 - size_t lines = save_history;
1223 - size_t group_count = 1;
1224 - int group_method = GROUP_AVERAGE;
1225 -
1226 - if(url) {
1227 - // parse the lines required
1228 - tok = mystrsep(&url, "/?&");
1229 - if(tok) lines = atoi(tok);
1230 - if(lines < 5) lines = save_history;
1231 - }
1232 - if(url) {
1233 - // parse the group count required
1234 - tok = mystrsep(&url, "/?&");
1235 - if(tok) group_count = atoi(tok);
1236 - if(group_count < 1) group_count = 1;
1237 - if(group_count > save_history / 20) group_count = save_history / 20;
1238 - }
1239 - if(url) {
1240 - // parse the grouping method required
1241 - tok = mystrsep(&url, "/?&");
1242 - if(strcmp(tok, "max") == 0) group_method = GROUP_MAX;
1243 - else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE;
1244 - else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok);
1245 - }
1246 -
1247 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending RRD data '%s' (id %s, %d lines, %d group_count, %d group_method).", w->id, st->name, st->id, lines, group_count, group_method);
1248 -
1249 - code = 200;
1250 - w->data->contenttype = CT_APPLICATION_JSON;
1251 - w->data->bytes = rrd_stats_json(st, w->data->buffer, w->data->size, lines, group_count, group_method);
1252 - }
1253 - }
1254 - else if(strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph"
1255 - // the client is requesting an rrd graph
1256 -
1257 - // get the name of the data to show
1258 - tok = mystrsep(&url, "/?&");
1259 - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1260 -
1261 - // do we have such a data set?
1262 - RRD_STATS *st = rrd_stats_find_byname(tok);
1263 - if(!st) {
1264 - // we don't have it
1265 - // try to send a file with that name
1266 - code = mysendfile(w, tok);
1267 - }
1268 - else {
1269 - code = 200;
1270 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name);
1271 - w->data->contenttype = CT_APPLICATION_JSON;
1272 - w->data->bytes = rrd_stats_graph_json(st, url, w->data->buffer, w->data->size);
1273 - }
1274 - }
1275 - else if(strcmp(tok, "mirror") == 0) {
1276 - code = 200;
1277 -
1278 - debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
1279 -
1280 - // just leave the buffer as is
1281 - // it will be copied back to the client
1282 - }
1283 - else if(strcmp(tok, "list") == 0) {
1284 - code = 200;
1285 -
1286 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
1287 -
1288 - w->data->bytes = 0;
1289 - RRD_STATS *st = root;
1290 -
1291 - for ( ; st ; st = st->next )
1292 - w->data->bytes += sprintf(&w->data->buffer[w->data->bytes], "%s\n", st->name);
1293 - }
1294 - else if(strcmp(tok, "envlist") == 0) {
1295 - code = 200;
1296 -
1297 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending envlist of RRD_STATS...", w->id);
1298 -
1299 - w->data->bytes = 0;
1300 - RRD_STATS *st = root;
1301 -
1302 - for ( ; st ; st = st->next ) {
1303 - w->data->bytes += sprintf(&w->data->buffer[w->data->bytes], "%s=%s\n", st->envtitle, st->usertitle);
1304 - w->data->bytes += sprintf(&w->data->buffer[w->data->bytes], "%s=%s\n", st->envpriority, st->userpriority);
1305 - }
1306 - }
1307 - else if(strcmp(tok, "all.json") == 0) {
1308 - code = 200;
1309 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending all.json of RRD_STATS...", w->id);
1310 - w->data->contenttype = CT_APPLICATION_JSON;
1311 - w->data->bytes = rrd_stats_all_json(w->data->buffer, w->data->size);
1312 - }
1313 - else if(strcmp(tok, WEB_PATH_FILE) == 0) { // "file"
1314 - tok = mystrsep(&url, "/?&");
1315 - if(tok && *tok) code = mysendfile(w, tok);
1316 - else {
1317 - code = 400;
1318 - strcpy(w->data->buffer, "You have to give a filename to get.\r\n");
1319 - w->data->bytes = strlen(w->data->buffer);
1320 - }
1321 - }
1322 - else if(!tok[0]) {
1323 - code = mysendfile(w, "index.html");
1324 - }
1325 - else {
1326 - code = mysendfile(w, tok);
1327 - }
1328 - }
1329 - else {
1330 - if(buf) debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, buf);
1331 -
1332 - code = 500;
1333 - strcpy(w->data->buffer, "I don't understand you...\r\n");
1334 - w->data->bytes = strlen(w->data->buffer);
1335 - }
1336 - }
1337 - else if(w->data->bytes > 8192) {
1338 - debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big.", w->id);
1339 -
1340 - code = 400;
1341 - strcpy(w->data->buffer, "Received request is too big.\r\n");
1342 - w->data->bytes = strlen(w->data->buffer);
1343 - }
1344 - else {
1345 - // wait for more data
1346 - return;
1347 - }
1348 -
1349 - w->data->date = time(NULL);
1350 - w->wait_receive = 0;
1351 - w->data->sent = 0;
1352 -
1353 - // prepare the HTTP response header
1354 - debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code);
1355 -
1356 - char *content_type_string = "";
1357 - switch(w->data->contenttype) {
1358 - case CT_TEXT_HTML:
1359 - content_type_string = "text/html";
1360 - break;
1361 -
1362 - case CT_APPLICATION_XML:
1363 - content_type_string = "application/xml";
1364 - break;
1365 -
1366 - case CT_APPLICATION_JSON:
1367 - content_type_string = "application/json";
1368 - break;
1369 -
1370 - case CT_APPLICATION_X_JAVASCRIPT:
1371 - content_type_string = "application/x-javascript";
1372 - break;
1373 -
1374 - case CT_TEXT_CSS:
1375 - content_type_string = "text/css";
1376 - break;
1377 -
1378 - case CT_TEXT_XML:
1379 - content_type_string = "text/xml";
1380 - break;
1381 -
1382 - case CT_TEXT_XSL:
1383 - content_type_string = "text/xsl";
1384 - break;
1385 -
1386 - case CT_APPLICATION_OCTET_STREAM:
1387 - content_type_string = "application/octet-stream";
1388 - break;
1389 -
1390 - case CT_IMAGE_SVG_XML:
1391 - content_type_string = "image/svg+xml";
1392 - break;
1393 -
1394 - case CT_APPLICATION_X_FONT_TRUETYPE:
1395 - content_type_string = "application/x-font-truetype";
1396 - break;
1397 -
1398 - case CT_APPLICATION_X_FONT_OPENTYPE:
1399 - content_type_string = "application/x-font-opentype";
1400 - break;
1401 -
1402 - case CT_APPLICATION_FONT_WOFF:
1403 - content_type_string = "application/font-woff";
1404 - break;
1405 -
1406 - case CT_APPLICATION_VND_MS_FONTOBJ:
1407 - content_type_string = "application/vnd.ms-fontobject";
1408 - break;
1409 -
1410 - default:
1411 - case CT_TEXT_PLAIN:
1412 - content_type_string = "text/plain";
1413 - break;
1414 - }
1415 -
1416 - char *code_msg = "";
1417 - switch(code) {
1418 - case 200:
1419 - code_msg = "OK";
1420 - break;
1421 -
1422 - case 307:
1423 - code_msg = "Temporary Redirect";
1424 - break;
1425 -
1426 - case 400:
1427 - code_msg = "Bad Request";
1428 - break;
1429 -
1430 - case 403:
1431 - code_msg = "Forbidden";
1432 - break;
1433 -
1434 - case 404:
1435 - code_msg = "Not Found";
1436 - break;
1437 -
1438 - default:
1439 - code_msg = "Internal Server Error";
1440 - break;
1441 - }
1442 -
1443 - char date[100];
1444 - struct tm tm = *gmtime(&w->data->date);
1445 - strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", &tm);
1446 -
1447 - char custom_header[MAX_HTTP_HEADER_SIZE + 1] = "";
1448 - if(w->response_header[0])
1449 - strcpy(custom_header, w->response_header);
1450 -
1451 - size_t headerlen = 0;
1452 - headerlen += sprintf(&w->response_header[headerlen],
1453 - "HTTP/1.1 %d %s\r\n"
1454 - "Connection: %s\r\n"
1455 - "Server: Data Collector HTTP Server\r\n"
1456 - "Content-Type: %s\r\n"
1457 - "Access-Control-Allow-Origin: *\r\n"
1458 - "Date: %s\r\n"
1459 - , code, code_msg
1460 - , w->keepalive?"keep-alive":"close"
1461 - , content_type_string
1462 - , date
1463 - );
1464 -
1465 - if(custom_header[0])
1466 - headerlen += sprintf(&w->response_header[headerlen], "%s", custom_header);
1467 -
1468 - if(w->mode == WEB_CLIENT_MODE_NORMAL) {
1469 - headerlen += sprintf(&w->response_header[headerlen],
1470 - "Expires: %s\r\n"
1471 - "Cache-Control: private\r\n"
1472 - , date
1473 - );
1474 - }
1475 -
1476 - // if we know the content length, put it
1477 - if(w->data->bytes || w->data->rbytes)
1478 - headerlen += sprintf(&w->response_header[headerlen],
1479 - "Content-Length: %ld\r\n"
1480 - , w->data->bytes?w->data->bytes:w->data->rbytes
1481 - );
1482 - else w->keepalive = 0; // content-length is required for keep-alive
1483 -
1484 - headerlen += sprintf(&w->response_header[headerlen], "\r\n");
1485 -
1486 - // disable TCP_NODELAY, to buffer the header
1487 - int flag = 0;
1488 - if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to disable TCP_NODELAY on socket.", w->id);
1489 -
1490 - // sent the HTTP header
1491 - debug(D_WEB_CLIENT, "%llu: Sending response HTTP header of size %d.", w->id, headerlen);
1492 -
1493 - bytes = send(w->ofd, w->response_header, headerlen, 0);
1494 - if(bytes != headerlen)
1495 - error("%llu: HTTP Header failed to be sent (I sent %d bytes but the system sent %d bytes).", w->id, headerlen, bytes);
1496 -
1497 - // enable TCP_NODELAY, to send all data immediately at the next send()
1498 - flag = 1;
1499 - if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to enable TCP_NODELAY on socket.", w->id);
1500 -
1501 - // enable sending immediately if we have data
1502 - if(w->data->bytes) w->wait_send = 1;
1503 - else w->wait_send = 0;
1504 -
1505 - // pretty logging
1506 - switch(w->mode) {
1507 - case WEB_CLIENT_MODE_NORMAL:
1508 - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%d bytes) to client.", w->id, w->data->bytes);
1509 - break;
1510 -
1511 - case WEB_CLIENT_MODE_FILECOPY:
1512 - if(w->data->rbytes) {
1513 - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %d bytes to client.", w->id, w->data->rbytes);
1514 -
1515 - // utilize the kernel sendfile() for copying the file to the socket.
1516 - // this block of code can be commented, without anything missing.
1517 - // when it is commented, the program will copy the data using async I/O.
1518 - {
1519 - ssize_t len = sendfile(w->ofd, w->ifd, NULL, w->data->rbytes);
1520 - if(len != w->data->rbytes) error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->data->rbytes, len);
1521 - else web_client_reset(w);
1522 - }
1523 - }
1524 - else
1525 - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id);
1526 - break;
1527 -
1528 - default:
1529 - fatal("%llu: Unknown client mode %d.", w->id, w->mode);
1530 - break;
1531 - }
1532 -}
1533 -
1534 -ssize_t web_client_send(struct web_client *w)
1535 -{
1536 - ssize_t bytes;
1537 -
1538 - if(w->data->bytes - w->data->sent == 0) {
1539 - // there is nothing to send
1540 -
1541 - debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
1542 -
1543 - // there can be two cases for this
1544 - // A. we have done everything
1545 - // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
1546 -
1547 - if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->data->rbytes && w->data->rbytes > w->data->bytes) {
1548 - // we have to wait, more data will come
1549 - debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
1550 - w->wait_send = 0;
1551 - return(0);
1552 - }
1553 -
1554 - if(w->keepalive == 0) {
1555 - debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->data->sent);
1556 - errno = 0;
1557 - return(-1);
1558 - }
1559 -
1560 - web_client_reset(w);
1561 - debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id);
1562 - return(0);
1563 - }
1564 -
1565 - bytes = send(w->ofd, &w->data->buffer[w->data->sent], w->data->bytes - w->data->sent, MSG_DONTWAIT);
1566 - if(bytes > 0) {
1567 - w->data->sent += bytes;
1568 - debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
1569 - }
1570 - else if(bytes == 0) debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
1571 -
1572 - return(bytes);
1573 -}
1574 -
1575 -ssize_t web_client_receive(struct web_client *w)
1576 -{
1577 - // do we have any space for more data?
1578 - web_buffer_increase(w->data, SOCKET_DATA_LENGTH_INCREASE_STEP);
1579 -
1580 - ssize_t left = w->data->size - w->data->bytes;
1581 - ssize_t bytes;
1582 -
1583 - if(w->mode == WEB_CLIENT_MODE_FILECOPY)
1584 - bytes = read(w->ifd, &w->data->buffer[w->data->bytes], (left-1));
1585 - else
1586 - bytes = recv(w->ifd, &w->data->buffer[w->data->bytes], left-1, MSG_DONTWAIT);
1587 -
1588 - if(bytes > 0) {
1589 - int old = w->data->bytes;
1590 - w->data->bytes += bytes;
1591 - w->data->buffer[w->data->bytes] = '\0';
1592 -
1593 - debug(D_WEB_CLIENT, "%llu: Received %d bytes.", w->id, bytes);
1594 - debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->data->buffer[old]);
1595 -
1596 - if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1597 - w->wait_send = 1;
1598 - if(w->data->rbytes && w->data->bytes >= w->data->rbytes) w->wait_receive = 0;
1599 - }
1600 - }
1601 - else if(bytes == 0) {
1602 - debug(D_WEB_CLIENT, "%llu: Out of input data.", w->id);
1603 -
1604 - // if we cannot read, it means we have an error on input.
1605 - // if however, we are copying a file from ifd to ofd, we should not return an error.
1606 - // in this case, the error should be generated when the file has been sent to the client.
1607 -
1608 - if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1609 - // we are copying data fron ifd to ofd
1610 - // let it finish copying...
1611 - w->wait_receive = 0;
1612 - debug(D_WEB_CLIENT, "%llu: Disabling input.", w->id);
1613 - }
1614 - else {
1615 - bytes = -1;
1616 - errno = 0;
1617 - }
1618 - }
1619 -
1620 - return(bytes);
1621 -}
1622 -
1623 -
1624 -// --------------------------------------------------------------------------------------
1625 -// the thread of a single client
1626 -
1627 -// 1. waits for input and output, using async I/O
1628 -// 2. it processes HTTP requests
1629 -// 3. it generates HTTP responses
1630 -// 4. it copies data from input to output if mode is FILECOPY
1631 -
1632 -void *new_client(void *ptr)
1633 -{
1634 - struct timeval tv;
1635 - struct web_client *w = ptr;
1636 - int retval;
1637 - fd_set ifds, ofds, efds;
1638 - int fdmax = 0;
1639 -
1640 - for(;;) {
1641 - FD_ZERO (&ifds);
1642 - FD_ZERO (&ofds);
1643 - FD_ZERO (&efds);
1644 -
1645 - FD_SET(w->ifd, &efds);
1646 - if(w->ifd != w->ofd) FD_SET(w->ofd, &efds);
1647 - if (w->wait_receive) {
1648 - debug(D_WEB_CLIENT, "%llu: Waiting for input (fd %d).", w->id, w->ifd);
1649 - FD_SET(w->ifd, &ifds);
1650 - if(w->ifd > fdmax) fdmax = w->ifd;
1651 - }
1652 - if (w->wait_send) {
1653 - debug(D_WEB_CLIENT, "%llu: Waiting for output (fd %d).", w->id, w->ofd);
1654 - FD_SET(w->ofd, &ofds);
1655 - if(w->ofd > fdmax) fdmax = w->ofd;
1656 - }
1657 -
1658 - tv.tv_sec = 30;
1659 - tv.tv_usec = 0;
1660 -
1661 - debug(D_WEB_CLIENT, "%llu: Waiting...", w->id);
1662 - retval = select(fdmax+1, &ifds, &ofds, &efds, &tv);
1663 -
1664 - if(retval == -1) {
1665 - error("%llu: LISTENER: select() failed.", w->id);
1666 - continue;
1667 - }
1668 - else if(!retval) {
1669 - // timeout
1670 - w->obsolete = 1;
1671 - return NULL;
1672 - }
1673 -
1674 - if(FD_ISSET(w->ifd, &efds)) {
1675 - debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on input socket (%s).", w->id, strerror(errno));
1676 - w->obsolete = 1;
1677 - return NULL;
1678 - }
1679 -
1680 - if(FD_ISSET(w->ofd, &efds)) {
1681 - debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on output socket (%s).", w->id, strerror(errno));
1682 - w->obsolete = 1;
1683 - return NULL;
1684 - }
1685 -
1686 - if(w->wait_send && FD_ISSET(w->ofd, &ofds)) {
1687 - if(web_client_send(w) < 0) {
1688 - debug(D_WEB_CLIENT, "%llu: Closing client (input: %s).", w->id, strerror(errno));
1689 - errno = 0;
1690 - w->obsolete = 1;
1691 - return NULL;
1692 - }
1693 - }
1694 -
1695 - if(w->wait_receive && FD_ISSET(w->ifd, &ifds)) {
1696 - if(web_client_receive(w) < 0) {
1697 - debug(D_WEB_CLIENT, "%llu: Closing client (output: %s).", w->id, strerror(errno));
1698 - errno = 0;
1699 - w->obsolete = 1;
1700 - return NULL;
1701 - }
1702 -
1703 - if(w->mode == WEB_CLIENT_MODE_NORMAL) web_client_process(w);
1704 - }
1705 - }
1706 - debug(D_WEB_CLIENT, "%llu: done...", w->id);
1707 -
1708 - return NULL;
1709 -}
1710 -
1711 -// --------------------------------------------------------------------------------------
1712 -// the main socket listener
1713 -
1714 -// 1. it accepts new incoming requests on our port
1715 -// 2. creates a new web_client for each connection received
1716 -// 3. spawns a new pthread to serve the client (this is optimal for keep-alive clients)
1717 -// 4. cleans up old web_clients that their pthreads have been exited
1718 -
1719 -void *socket_listen_main(void *ptr)
1720 -{
1721 - struct web_client *w;
1722 - struct timeval tv;
1723 - int retval;
1724 -
1725 - int listener = create_listen_socket(listen_port);
1726 - if(listener == -1) fatal("LISTENER: Cannot create listening socket on port 19999.");
1727 -
1728 - fd_set ifds, ofds, efds;
1729 - int fdmax = listener;
1730 -
1731 - FD_ZERO (&ifds);
1732 - FD_ZERO (&ofds);
1733 - FD_ZERO (&efds);
1734 -
1735 - for(;;) {
1736 - tv.tv_sec = 1;
1737 - tv.tv_usec = 0;
1738 -
1739 - FD_SET(listener, &ifds);
1740 - FD_SET(listener, &efds);
1741 -
1742 - // debug(D_WEB_CLIENT, "LISTENER: Waiting...");
1743 - retval = select(fdmax+1, &ifds, &ofds, &efds, &tv);
1744 -
1745 - if(retval == -1) {
1746 - error("LISTENER: select() failed.");
1747 - continue;
1748 - }
1749 - else if(retval) {
1750 - // check for new incoming connections
1751 - if(FD_ISSET(listener, &ifds)) {
1752 - w = web_client_create(listener);
1753 -
1754 - if(pthread_create(&w->thread, NULL, new_client, w) != 0)
1755 - error("%llu: failed to create new thread.");
1756 -
1757 - if(pthread_detach(w->thread) != 0)
1758 - error("%llu: Cannot request detach of newly created thread.", w->id);
1759 - }
1760 - else debug(D_WEB_CLIENT, "LISTENER: select() didn't do anything.");
1761 - }
1762 - else debug(D_WEB_CLIENT, "LISTENER: select() timeout.");
1763 -
1764 - // cleanup unused clients
1765 - for(w = web_clients; w ; w = w?w->next:NULL) {
1766 - if(w->obsolete) {
1767 - debug(D_WEB_CLIENT, "%llu: Removing client.", w->id);
1768 - w = web_client_free(w);
1769 - }
1770 - }
1771 - }
1772 -
1773 - error("LISTENER: exit!");
1774 -
1775 - close(listener);
1776 - exit(2);
1777 -
1778 - return NULL;
1779 -}
1780 -
1781 -// ----------------------------------------------------------------------------
1782 -// /proc/net/dev processor
1783 -
1784 -int do_proc_net_dev() {
1785 - char buffer[MAX_PROC_NET_DEV_LINE+1] = "";
1786 - char name[MAX_PROC_NET_DEV_IFACE_NAME + 1] = RRD_TYPE_NET ".";
1787 - unsigned long long rbytes, rpackets, rerrors, rdrops, rfifo, rframe, rcompressed, rmulticast;
1788 - unsigned long long tbytes, tpackets, terrors, tdrops, tfifo, tcollisions, tcarrier, tcompressed;
1789 -
1790 - int r;
1791 - char *p;
1792 -
1793 - FILE *fp = fopen("/proc/net/dev", "r");
1794 - if(!fp) {
1795 - error("Cannot read /proc/net/dev.");
1796 - return 1;
1797 - }
1798 -
1799 - // skip the first two lines
1800 - p = fgets(buffer, MAX_PROC_NET_DEV_LINE, fp);
1801 - p = fgets(buffer, MAX_PROC_NET_DEV_LINE, fp);
1802 -
1803 - // read the rest of the lines
1804 - for(;1;) {
1805 - char *c;
1806 - p = fgets(buffer, MAX_PROC_NET_DEV_LINE, fp);
1807 - if(!p) break;
1808 -
1809 - c = strchr(buffer, ':');
1810 - if(c) *c = '\t';
1811 -
1812 - // if(DEBUG) printf("%s\n", buffer);
1813 - r = sscanf(buffer, "%s\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\t%llu\n",
1814 - &name[RRD_TYPE_NET_LEN + 1],
1815 - &rbytes, &rpackets, &rerrors, &rdrops, &rfifo, &rframe, &rcompressed, &rmulticast,
1816 - &tbytes, &tpackets, &terrors, &tdrops, &tfifo, &tcollisions, &tcarrier, &tcompressed);
1817 - if(r == EOF) break;
1818 - if(r != 17) error("Cannot read /proc/net/dev line. Expected 17 params, read %d.", r);
1819 - else {
1820 - RRD_STATS *st = rrd_stats_find(name);
1821 -
1822 - if(!st) {
1823 - st = rrd_stats_create(name, name, save_history, "Network usage for ", "Bandwidth in kilobits/s", RRD_TYPE_NET);
1824 - if(!st) {
1825 - error("Cannot create RRD_STATS for interface %s.", name);
1826 - continue;
1827 - }
1828 -
1829 - if(!rrd_stats_dimension_add(st, "sent", "sent", sizeof(unsigned long long), -8, 1024, RRD_DIMENSION_INCREMENTAL))
1830 - error("Cannot add RRD_STATS dimension %s.", "sent");
1831 -
1832 - if(!rrd_stats_dimension_add(st, "received", "received", sizeof(unsigned long long), 8, 1024, RRD_DIMENSION_INCREMENTAL))
1833 - error("Cannot add RRD_STATS dimension %s.", "received");
1834 -
1835 - }
1836 - else rrd_stats_next(st);
1837 -
1838 - rrd_stats_dimension_set(st, "received", &rbytes);
1839 - rrd_stats_dimension_set(st, "sent", &tbytes);
1840 - rrd_stats_done(st);
1841 - }
1842 - }
1843 -
1844 - fclose(fp);
1845 - return 0;
1846 -}
1847 -
1848 -// ----------------------------------------------------------------------------
1849 -// /proc/diskstats processor
1850 -
1851 -int do_proc_diskstats() {
1852 - char buffer[MAX_PROC_DISKSTATS_LINE+1] = "";
1853 - char name[MAX_PROC_DISKSTATS_DISK_NAME + 1] = RRD_TYPE_DISK ".";
1854 - // 1 2 3 4 5 6 7 8 9 10 11
1855 - unsigned long long major, minor, reads, reads_merged, readsectors, readms, writes, writes_merged, writesectors, writems, currentios, iosms, wiosms;
1856 -
1857 - int r;
1858 - char *p;
1859 -
1860 - FILE *fp = fopen("/proc/diskstats", "r");
1861 - if(!fp) {
1862 - error("Cannot read /proc/diskstats.");
1863 - return 1;
1864 - }
1865 -
1866 - for(;1;) {
1867 - p = fgets(buffer, MAX_PROC_DISKSTATS_LINE, fp);
1868 - if(!p) break;
1869 -
1870 - // if(DEBUG) printf("%s\n", buffer);
1871 - r = sscanf(buffer, "%llu %llu %s %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
1872 - &major, &minor, &name[RRD_TYPE_DISK_LEN + 1],
1873 - &reads, &reads_merged, &readsectors, &readms, &writes, &writes_merged, &writesectors, &writems, &currentios, &iosms, &wiosms
1874 - );
1875 - if(r == EOF) break;
1876 - if(r != 14) error("Cannot read /proc/diskstats line. Expected 14 params, read %d.", r);
1877 - else {
1878 - switch(major) {
1879 - case 3: // ide
1880 - case 13: // 8bit ide
1881 - case 22: // ide
1882 - case 33: // ide
1883 - case 34: // ide
1884 - case 56: // ide
1885 - case 57: // ide
1886 - case 88: // ide
1887 - case 89: // ide
1888 - case 90: // ide
1889 - case 91: // ide
1890 - if(minor % 64) continue; // partitions
1891 - break;
1892 -
1893 - case 160: // raid
1894 - case 161: // raid
1895 - if(minor % 32) continue; // partitions
1896 - break;
1897 -
1898 - case 8: // scsi disks
1899 - case 65: // scsi disks
1900 - case 66: // scsi disks
1901 - case 67: // scsi disks
1902 - case 68: // scsi disks
1903 - case 69: // scsi disks
1904 - case 70: // scsi disks
1905 - case 71: // scsi disks
1906 - case 72: // scsi disks
1907 - case 73: // scsi disks
1908 - case 74: // scsi disks
1909 - case 75: // scsi disks
1910 - case 76: // scsi disks
1911 - case 77: // scsi disks
1912 - case 78: // scsi disks
1913 - case 79: // scsi disks
1914 - case 80: // i2o
1915 - case 81: // i2o
1916 - case 82: // i2o
1917 - case 83: // i2o
1918 - case 84: // i2o
1919 - case 85: // i2o
1920 - case 86: // i2o
1921 - case 87: // i2o
1922 - case 101: // hyperdisk
1923 - case 102: // compressed
1924 - case 104: // scsi
1925 - case 105: // scsi
1926 - case 106: // scsi
1927 - case 107: // scsi
1928 - case 108: // scsi
1929 - case 109: // scsi
1930 - case 110: // scsi
1931 - case 111: // scsi
1932 - case 114: // bios raid
1933 - case 116: // ram board
1934 - case 128: // scsi
1935 - case 129: // scsi
1936 - case 130: // scsi
1937 - case 131: // scsi
1938 - case 132: // scsi
1939 - case 133: // scsi
1940 - case 134: // scsi
1941 - case 135: // scsi
1942 - case 153: // raid
1943 - case 202: // xen
1944 - case 256: // flash
1945 - case 257: // flash
1946 - if(minor % 16) continue; // partitions
1947 - break;
1948 -
1949 - case 9: // MDs
1950 - case 43: // network block
1951 - case 144: // nfs
1952 - case 145: // nfs
1953 - case 146: // nfs
1954 - case 199: // veritas
1955 - case 201: // veritas
1956 - break;
1957 -
1958 - case 48: // RAID
1959 - case 49: // RAID
1960 - case 50: // RAID
1961 - case 51: // RAID
1962 - case 52: // RAID
1963 - case 53: // RAID
1964 - case 54: // RAID
1965 - case 55: // RAID
1966 - case 112: // RAID
1967 - case 136: // RAID
1968 - case 137: // RAID
1969 - case 138: // RAID
1970 - case 139: // RAID
1971 - case 140: // RAID
1972 - case 141: // RAID
1973 - case 142: // RAID
1974 - case 143: // RAID
1975 - case 179: // MMC
1976 - case 180: // USB
1977 - if(minor % 8) continue; // partitions
1978 - break;
1979 -
1980 - default:
1981 - continue;
1982 - }
1983 -
1984 - RRD_STATS *st = rrd_stats_find(name);
1985 -
1986 - if(!st) {
1987 - char ssfilename[FILENAME_MAX + 1];
1988 - int sector_size = 512;
1989 -
1990 - sprintf(ssfilename, "/sys/block/%s/queue/hw_sector_size", &name[RRD_TYPE_DISK_LEN + 1]);
1991 - FILE *fpss = fopen(ssfilename, "r");
1992 - if(fpss) {
1993 - char ssbuffer[1025];
1994 - char *tmp = fgets(ssbuffer, 1024, fpss);
1995 -
1996 - if(tmp) {
1997 - sector_size = atoi(tmp);
1998 - if(sector_size <= 0) {
1999 - error("Invalid sector size %d for device %s in %s. Assuming 512.", sector_size, name, ssfilename);
2000 - sector_size = 512;
2001 - }
2002 - }
2003 - else error("Cannot read data for sector size for device %s from %s. Assuming 512.", name, ssfilename);
2004 -
2005 - fclose(fpss);
2006 - }
2007 - else error("Cannot read sector size for device %s from %s. Assuming 512.", name, ssfilename);
2008 -
2009 - st = rrd_stats_create(name, name, save_history, "Disk usage for ", "I/O in kilobytes/s", RRD_TYPE_DISK);
2010 - if(!st) {
2011 - error("Cannot create RRD_STATS for disk %s.", name);
2012 - continue;
2013 - }
2014 -
2015 - if(!rrd_stats_dimension_add(st, "writes", "writes", sizeof(unsigned long long), sector_size * -1, 1024, RRD_DIMENSION_INCREMENTAL))
2016 - error("Cannot add RRD_STATS dimension %s.", "writes");
2017 -
2018 - if(!rrd_stats_dimension_add(st, "reads", "reads", sizeof(unsigned long long), sector_size, 1024, RRD_DIMENSION_INCREMENTAL))
2019 - error("Cannot add RRD_STATS dimension %s.", "reads");
2020 -
2021 - }
2022 - else rrd_stats_next(st);
2023 -
2024 - rrd_stats_dimension_set(st, "reads", &readsectors);
2025 - rrd_stats_dimension_set(st, "writes", &writesectors);
2026 - rrd_stats_done(st);
2027 - }
2028 - }
2029 -
2030 - fclose(fp);
2031 - return 0;
2032 -}
2033 -
2034 -// ----------------------------------------------------------------------------
2035 -// /proc/net/snmp processor
2036 -
2037 -int do_proc_net_snmp() {
2038 - char buffer[MAX_PROC_NET_SNMP_LINE+1] = "";
2039 -
2040 - FILE *fp = fopen("/proc/net/snmp", "r");
2041 - if(!fp) {
2042 - error("Cannot read /proc/net/snmp.");
2043 - return 1;
2044 - }
2045 -
2046 - for(;1;) {
2047 - char *p = fgets(buffer, MAX_PROC_NET_SNMP_LINE, fp);
2048 - if(!p) break;
2049 -
2050 - if(strncmp(p, "Ip: ", 4) == 0) {
2051 - // skip the header line, read the data
2052 - p = fgets(buffer, MAX_PROC_NET_SNMP_LINE, fp);
2053 - if(!p) break;
2054 -
2055 - if(strncmp(p, "Ip: ", 4) != 0) {
2056 - error("Cannot read IP line from /proc/net/snmp.");
2057 - break;
2058 - }
2059 -
2060 - // see also http://net-snmp.sourceforge.net/docs/mibs/ip.html
2061 - unsigned long long Forwarding, DefaultTTL, InReceives, InHdrErrors, InAddrErrors, ForwDatagrams, InUnknownProtos, InDiscards, InDelivers,
2062 - OutRequests, OutDiscards, OutNoRoutes, ReasmTimeout, ReasmReqds, ReasmOKs, ReasmFails, FragOKs, FragFails, FragCreates;
2063 -
2064 - int r = sscanf(&buffer[4], "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
2065 - &Forwarding, &DefaultTTL, &InReceives, &InHdrErrors, &InAddrErrors, &ForwDatagrams, &InUnknownProtos, &InDiscards, &InDelivers,
2066 - &OutRequests, &OutDiscards, &OutNoRoutes, &ReasmTimeout, &ReasmReqds, &ReasmOKs, &ReasmFails, &FragOKs, &FragFails, &FragCreates);
2067 -
2068 - if(r == EOF) break;
2069 - if(r != 19) error("Cannot read /proc/net/snmp IP line. Expected 19 params, read %d.", r);
2070 -
2071 - RRD_STATS *st = rrd_stats_find(RRD_TYPE_NET_SNMP ".packets");
2072 - if(!st) {
2073 - st = rrd_stats_create(RRD_TYPE_NET_SNMP ".packets", RRD_TYPE_NET_SNMP ".packets", save_history, "IPv4 Packets", "Packets/s", RRD_TYPE_NET_SNMP);
2074 - if(!st) {
2075 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_SNMP ".packets");
2076 - continue;
2077 - }
2078 -
2079 - if(!rrd_stats_dimension_add(st, "forwarded", "forwarded", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2080 - error("Cannot add RRD_STATS dimension %s.", "forwarded");
2081 -
2082 - if(!rrd_stats_dimension_add(st, "sent", "sent", sizeof(unsigned long long), -1, 1, RRD_DIMENSION_INCREMENTAL))
2083 - error("Cannot add RRD_STATS dimension %s.", "sent");
2084 -
2085 - if(!rrd_stats_dimension_add(st, "received", "received", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2086 - error("Cannot add RRD_STATS dimension %s.", "received");
2087 -
2088 - }
2089 - else rrd_stats_next(st);
2090 -
2091 - rrd_stats_dimension_set(st, "sent", &OutRequests);
2092 - rrd_stats_dimension_set(st, "received", &InReceives);
2093 - rrd_stats_dimension_set(st, "forwarded", &ForwDatagrams);
2094 - rrd_stats_done(st);
2095 - }
2096 - else if(strncmp(p, "Tcp: ", 5) == 0) {
2097 - // skip the header line, read the data
2098 - p = fgets(buffer, MAX_PROC_NET_SNMP_LINE, fp);
2099 - if(!p) break;
2100 -
2101 - if(strncmp(p, "Tcp: ", 5) != 0) {
2102 - error("Cannot read TCP line from /proc/net/snmp.");
2103 - break;
2104 - }
2105 -
2106 - unsigned long long RtoAlgorithm, RtoMin, RtoMax, MaxConn, ActiveOpens, PassiveOpens, AttemptFails, EstabResets, CurrEstab, InSegs, OutSegs, RetransSegs, InErrs, OutRsts;
2107 -
2108 - int r = sscanf(&buffer[5], "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
2109 - &RtoAlgorithm, &RtoMin, &RtoMax, &MaxConn, &ActiveOpens, &PassiveOpens, &AttemptFails, &EstabResets, &CurrEstab, &InSegs, &OutSegs, &RetransSegs, &InErrs, &OutRsts);
2110 -
2111 - if(r == EOF) break;
2112 - if(r != 14) error("Cannot read /proc/net/snmp TCP line. Expected 14 params, read %d.", r);
2113 -
2114 - // see http://net-snmp.sourceforge.net/docs/mibs/tcp.html
2115 - RRD_STATS *st = rrd_stats_find(RRD_TYPE_NET_SNMP ".tcpsock");
2116 - if(!st) {
2117 - st = rrd_stats_create(RRD_TYPE_NET_SNMP ".tcpsock", RRD_TYPE_NET_SNMP ".tcpsock", save_history, "IPv4 TCP Connections", "tcp connections", RRD_TYPE_NET_SNMP);
2118 - if(!st) {
2119 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_SNMP ".tcpsock");
2120 - continue;
2121 - }
2122 -
2123 - if(!rrd_stats_dimension_add(st, "connections", "connections", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_ABSOLUTE))
2124 - error("Cannot add RRD_STATS dimension %s.", "connections");
2125 - }
2126 - else rrd_stats_next(st);
2127 -
2128 - rrd_stats_dimension_set(st, "connections", &CurrEstab);
2129 - rrd_stats_done(st);
2130 -
2131 - st = rrd_stats_find(RRD_TYPE_NET_SNMP ".tcppackets");
2132 - if(!st) {
2133 - st = rrd_stats_create(RRD_TYPE_NET_SNMP ".tcppackets", RRD_TYPE_NET_SNMP ".tcppackets", save_history, "IPv4 TCP Packets", "Packets/s", RRD_TYPE_NET_SNMP);
2134 - if(!st) {
2135 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_SNMP ".tcppackets");
2136 - continue;
2137 - }
2138 -
2139 - if(!rrd_stats_dimension_add(st, "sent", "sent", sizeof(unsigned long long), -1, 1, RRD_DIMENSION_INCREMENTAL))
2140 - error("Cannot add RRD_STATS dimension %s.", "sent");
2141 -
2142 - if(!rrd_stats_dimension_add(st, "received", "received", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2143 - error("Cannot add RRD_STATS dimension %s.", "received");
2144 - }
2145 - else rrd_stats_next(st);
2146 -
2147 - rrd_stats_dimension_set(st, "received", &InSegs);
2148 - rrd_stats_dimension_set(st, "sent", &OutSegs);
2149 - rrd_stats_done(st);
2150 - }
2151 - else if(strncmp(p, "Udp: ", 5) == 0) {
2152 - // skip the header line, read the data
2153 - p = fgets(buffer, MAX_PROC_NET_SNMP_LINE, fp);
2154 - if(!p) break;
2155 -
2156 - if(strncmp(p, "Udp: ", 5) != 0) {
2157 - error("Cannot read UDP line from /proc/net/snmp.");
2158 - break;
2159 - }
2160 -
2161 - unsigned long long InDatagrams, NoPorts, InErrors, OutDatagrams, RcvbufErrors, SndbufErrors;
2162 -
2163 - int r = sscanf(&buffer[5], "%llu %llu %llu %llu %llu %llu\n",
2164 - &InDatagrams, &NoPorts, &InErrors, &OutDatagrams, &RcvbufErrors, &SndbufErrors);
2165 -
2166 - if(r == EOF) break;
2167 - if(r != 6) error("Cannot read /proc/net/snmp UDP line. Expected 6 params, read %d.", r);
2168 -
2169 - // see http://net-snmp.sourceforge.net/docs/mibs/udp.html
2170 - RRD_STATS *st = rrd_stats_find(RRD_TYPE_NET_SNMP ".udppackets");
2171 - if(!st) {
2172 - st = rrd_stats_create(RRD_TYPE_NET_SNMP ".udppackets", RRD_TYPE_NET_SNMP ".udppackets", save_history, "IPv4 UDP Packets", "Packets/s", RRD_TYPE_NET_SNMP);
2173 - if(!st) {
2174 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_SNMP ".udppackets");
2175 - continue;
2176 - }
2177 -
2178 - if(!rrd_stats_dimension_add(st, "sent", "sent", sizeof(unsigned long long), -1, 1, RRD_DIMENSION_INCREMENTAL))
2179 - error("Cannot add RRD_STATS dimension %s.", "sent");
2180 -
2181 - if(!rrd_stats_dimension_add(st, "received", "received", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2182 - error("Cannot add RRD_STATS dimension %s.", "received");
2183 - }
2184 - else rrd_stats_next(st);
2185 -
2186 - rrd_stats_dimension_set(st, "received", &InDatagrams);
2187 - rrd_stats_dimension_set(st, "sent", &OutDatagrams);
2188 - rrd_stats_done(st);
2189 - }
2190 - }
2191 -
2192 - fclose(fp);
2193 - return 0;
2194 -}
2195 -
2196 -// ----------------------------------------------------------------------------
2197 -// /proc/net/netstat processor
2198 -
2199 -int do_proc_net_netstat() {
2200 - char buffer[MAX_PROC_NET_SNMP_LINE+1] = "";
2201 -
2202 - FILE *fp = fopen("/proc/net/netstat", "r");
2203 - if(!fp) {
2204 - error("Cannot read /proc/net/netstat.");
2205 - return 1;
2206 - }
2207 -
2208 - for(;1;) {
2209 - char *p = fgets(buffer, MAX_PROC_NET_SNMP_LINE, fp);
2210 - if(!p) break;
2211 -
2212 - if(strncmp(p, "IpExt: ", 7) == 0) {
2213 - // skip the header line, read the data
2214 - p = fgets(buffer, MAX_PROC_NET_SNMP_LINE, fp);
2215 - if(!p) break;
2216 -
2217 - if(strncmp(p, "IpExt: ", 7) != 0) {
2218 - error("Cannot read IpExt line from /proc/net/netstat.");
2219 - break;
2220 - }
2221 -
2222 - unsigned long long InNoRoutes, InTruncatedPkts, InMcastPkts, OutMcastPkts, InBcastPkts, OutBcastPkts, InOctets, OutOctets, InMcastOctets, OutMcastOctets, InBcastOctets, OutBcastOctets;
2223 -
2224 - int r = sscanf(&buffer[7], "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
2225 - &InNoRoutes, &InTruncatedPkts, &InMcastPkts, &OutMcastPkts, &InBcastPkts, &OutBcastPkts, &InOctets, &OutOctets, &InMcastOctets, &OutMcastOctets, &InBcastOctets, &OutBcastOctets);
2226 -
2227 - if(r == EOF) break;
2228 - if(r != 12) error("Cannot read /proc/net/netstat IpExt line. Expected 12 params, read %d.", r);
2229 -
2230 - RRD_STATS *st = rrd_stats_find(RRD_TYPE_NET_SNMP ".net");
2231 - if(!st) {
2232 - st = rrd_stats_create(RRD_TYPE_NET_SNMP ".net", RRD_TYPE_NET_SNMP ".net", save_history, "All IPv4 Bandwidth", "bandwidth in kilobits/s", RRD_TYPE_NET_SNMP);
2233 - if(!st) {
2234 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_SNMP ".net");
2235 - continue;
2236 - }
2237 -
2238 - if(!rrd_stats_dimension_add(st, "sent", "sent", sizeof(unsigned long long), -8, 1024, RRD_DIMENSION_INCREMENTAL))
2239 - error("Cannot add RRD_STATS dimension %s.", "sent");
2240 -
2241 - if(!rrd_stats_dimension_add(st, "received", "received", sizeof(unsigned long long), 8, 1024, RRD_DIMENSION_INCREMENTAL))
2242 - error("Cannot add RRD_STATS dimension %s.", "received");
2243 -
2244 - }
2245 - else rrd_stats_next(st);
2246 -
2247 - rrd_stats_dimension_set(st, "sent", &OutOctets);
2248 - rrd_stats_dimension_set(st, "received", &InOctets);
2249 - rrd_stats_done(st);
2250 - }
2251 - }
2252 -
2253 - fclose(fp);
2254 - return 0;
2255 -}
2256 -
2257 -// ----------------------------------------------------------------------------
2258 -// /proc/net/stat/nf_conntrack processor
2259 -
2260 -int do_proc_net_stat_conntrack() {
2261 - char buffer[MAX_PROC_NET_STAT_CONNTRACK_LINE+1] = "";
2262 -
2263 - FILE *fp = fopen("/proc/net/stat/nf_conntrack", "r");
2264 - if(!fp) {
2265 - error("Cannot read /proc/net/stat/nf_conntrack.");
2266 - return 1;
2267 - }
2268 -
2269 - // read the discard the header
2270 - char *p = fgets(buffer, MAX_PROC_NET_STAT_CONNTRACK_LINE, fp);
2271 -
2272 - unsigned long long aentries = 0, asearched = 0, afound = 0, anew = 0, ainvalid = 0, aignore = 0, adelete = 0, adelete_list = 0,
2273 - ainsert = 0, ainsert_failed = 0, adrop = 0, aearly_drop = 0, aicmp_error = 0, aexpect_new = 0, aexpect_create = 0, aexpect_delete = 0, asearch_restart = 0;
2274 -
2275 - for(;1;) {
2276 - p = fgets(buffer, MAX_PROC_NET_STAT_CONNTRACK_LINE, fp);
2277 - if(!p) break;
2278 -
2279 - unsigned long long tentries, tsearched, tfound, tnew, tinvalid, tignore, tdelete, tdelete_list, tinsert, tinsert_failed, tdrop, tearly_drop, ticmp_error, texpect_new, texpect_create, texpect_delete, tsearch_restart;
2280 -
2281 - int r = sscanf(buffer, "%llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx\n",
2282 - &tentries, &tsearched, &tfound, &tnew, &tinvalid, &tignore, &tdelete, &tdelete_list, &tinsert, &tinsert_failed, &tdrop, &tearly_drop, &ticmp_error, &texpect_new, &texpect_create, &texpect_delete, &tsearch_restart);
2283 -
2284 - if(r == EOF) break;
2285 - if(r != 17) error("Cannot read /proc/net/stat/nf_conntrack. Expected 17 params, read %d.", r);
2286 -
2287 - if(!aentries) aentries = tentries;
2288 -
2289 - // sum all the cpus together
2290 - asearched += tsearched;
2291 - afound += tfound;
2292 - anew += tnew;
2293 - ainvalid += tinvalid;
2294 - aignore += tignore;
2295 - adelete += tdelete;
2296 - adelete_list += tdelete_list;
2297 - ainsert += tinsert;
2298 - ainsert_failed += tinsert_failed;
2299 - adrop += tdrop;
2300 - aearly_drop += tearly_drop;
2301 - aicmp_error += ticmp_error;
2302 - aexpect_new += texpect_new;
2303 - aexpect_create += texpect_create;
2304 - aexpect_delete += texpect_delete;
2305 - asearch_restart += tsearch_restart;
2306 - }
2307 - fclose(fp);
2308 -
2309 - RRD_STATS *st = rrd_stats_find(RRD_TYPE_NET_STAT_CONNTRACK ".sockets");
2310 - if(!st) {
2311 - st = rrd_stats_create(RRD_TYPE_NET_STAT_CONNTRACK ".sockets", RRD_TYPE_NET_STAT_CONNTRACK ".sockets", save_history, "Netfilter Connections", "netfilter connections", RRD_TYPE_NET_STAT_CONNTRACK);
2312 - if(!st) {
2313 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_STAT_CONNTRACK ".sockets");
2314 - return 1;
2315 - }
2316 -
2317 - if(!rrd_stats_dimension_add(st, "connections", "connections", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_ABSOLUTE))
2318 - error("Cannot add RRD_STATS dimension %s.", "connections");
2319 - }
2320 - else rrd_stats_next(st);
2321 -
2322 - rrd_stats_dimension_set(st, "connections", &aentries);
2323 - rrd_stats_done(st);
2324 -
2325 - st = rrd_stats_find(RRD_TYPE_NET_STAT_CONNTRACK ".new");
2326 - if(!st) {
2327 - st = rrd_stats_create(RRD_TYPE_NET_STAT_CONNTRACK ".new", RRD_TYPE_NET_STAT_CONNTRACK ".new", save_history, "Netfilter New Connections", "connections/s", RRD_TYPE_NET_STAT_CONNTRACK);
2328 - if(!st) {
2329 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_STAT_CONNTRACK ".new");
2330 - return 1;
2331 - }
2332 -
2333 - if(!rrd_stats_dimension_add(st, "dropped", "dropped", sizeof(unsigned long long), -1, 1, RRD_DIMENSION_INCREMENTAL))
2334 - error("Cannot add RRD_STATS dimension %s.", "dropped");
2335 -
2336 - if(!rrd_stats_dimension_add(st, "new", "new", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2337 - error("Cannot add RRD_STATS dimension %s.", "new");
2338 - }
2339 - else rrd_stats_next(st);
2340 -
2341 - rrd_stats_dimension_set(st, "new", &anew);
2342 - rrd_stats_dimension_set(st, "dropped", &adrop);
2343 - rrd_stats_done(st);
2344 -
2345 - st = rrd_stats_find(RRD_TYPE_NET_STAT_CONNTRACK ".changes");
2346 - if(!st) {
2347 - st = rrd_stats_create(RRD_TYPE_NET_STAT_CONNTRACK ".changes", RRD_TYPE_NET_STAT_CONNTRACK ".changes", save_history, "Netfilter Connection Changes", "connections/s", RRD_TYPE_NET_STAT_CONNTRACK);
2348 - if(!st) {
2349 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_STAT_CONNTRACK ".changes");
2350 - return 1;
2351 - }
2352 -
2353 - if(!rrd_stats_dimension_add(st, "deleted", "deleted", sizeof(unsigned long long), -1, 1, RRD_DIMENSION_INCREMENTAL))
2354 - error("Cannot add RRD_STATS dimension %s.", "deleted");
2355 -
2356 - if(!rrd_stats_dimension_add(st, "inserted", "inserted", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2357 - error("Cannot add RRD_STATS dimension %s.", "inserted");
2358 - }
2359 - else rrd_stats_next(st);
2360 -
2361 - rrd_stats_dimension_set(st, "inserted", &ainsert);
2362 - rrd_stats_dimension_set(st, "deleted", &adelete);
2363 - rrd_stats_done(st);
2364 -
2365 - return 0;
2366 -}
2367 -
2368 -// ----------------------------------------------------------------------------
2369 -// /proc/net/ip_vs_stats processor
2370 -
2371 -int do_proc_net_ip_vs_stats() {
2372 - char buffer[MAX_PROC_NET_IPVS_LINE+1] = "";
2373 -
2374 - FILE *fp = fopen("/proc/net/ip_vs_stats", "r");
2375 - if(!fp) {
2376 - error("Cannot read /proc/net/ip_vs_stats.");
2377 - return 1;
2378 - }
2379 -
2380 - // read the discard the 2 header lines
2381 - char *p = fgets(buffer, MAX_PROC_NET_IPVS_LINE, fp);
2382 - if(!p) {
2383 - error("Cannot read /proc/net/ip_vs_stats.");
2384 - return 1;
2385 - }
2386 -
2387 - p = fgets(buffer, MAX_PROC_NET_IPVS_LINE, fp);
2388 - if(!p) {
2389 - error("Cannot read /proc/net/ip_vs_stats.");
2390 - return 1;
2391 - }
2392 -
2393 - p = fgets(buffer, MAX_PROC_NET_IPVS_LINE, fp);
2394 - if(!p) {
2395 - error("Cannot read /proc/net/ip_vs_stats.");
2396 - return 1;
2397 - }
2398 -
2399 - unsigned long long entries, InPackets, OutPackets, InBytes, OutBytes;
2400 -
2401 - int r = sscanf(buffer, "%llx %llx %llx %llx %llx\n", &entries, &InPackets, &OutPackets, &InBytes, &OutBytes);
2402 -
2403 - if(r == EOF) {
2404 - error("Cannot read /proc/net/ip_vs_stats.");
2405 - return 1;
2406 - }
2407 - if(r != 5) error("Cannot read /proc/net/ip_vs_stats. Expected 5 params, read %d.", r);
2408 -
2409 - fclose(fp);
2410 -
2411 - RRD_STATS *st = rrd_stats_find(RRD_TYPE_NET_IPVS ".sockets");
2412 - if(!st) {
2413 - st = rrd_stats_create(RRD_TYPE_NET_IPVS ".sockets", RRD_TYPE_NET_IPVS ".sockets", save_history, "IPVS New Connections", "new connections/s", RRD_TYPE_NET_IPVS);
2414 - if(!st) {
2415 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_IPVS ".sockets");
2416 - return 1;
2417 - }
2418 -
2419 - if(!rrd_stats_dimension_add(st, "connections", "connections", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2420 - error("Cannot add RRD_STATS dimension %s.", "connections");
2421 - }
2422 - else rrd_stats_next(st);
2423 -
2424 - rrd_stats_dimension_set(st, "connections", &entries);
2425 - rrd_stats_done(st);
2426 -
2427 - st = rrd_stats_find(RRD_TYPE_NET_IPVS ".packets");
2428 - if(!st) {
2429 - st = rrd_stats_create(RRD_TYPE_NET_IPVS ".packets", RRD_TYPE_NET_IPVS ".packets", save_history, "IPVS Packets", "Packets/s", RRD_TYPE_NET_IPVS);
2430 - if(!st) {
2431 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_IPVS ".packets");
2432 - return 1;
2433 - }
2434 -
2435 - if(!rrd_stats_dimension_add(st, "sent", "sent", sizeof(unsigned long long), -1, 1, RRD_DIMENSION_INCREMENTAL))
2436 - error("Cannot add RRD_STATS dimension %s.", "sent");
2437 -
2438 - if(!rrd_stats_dimension_add(st, "received", "received", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2439 - error("Cannot add RRD_STATS dimension %s.", "received");
2440 - }
2441 - else rrd_stats_next(st);
2442 -
2443 - rrd_stats_dimension_set(st, "received", &InPackets);
2444 - rrd_stats_dimension_set(st, "sent", &OutPackets);
2445 - rrd_stats_done(st);
2446 -
2447 - st = rrd_stats_find(RRD_TYPE_NET_IPVS ".net");
2448 - if(!st) {
2449 - st = rrd_stats_create(RRD_TYPE_NET_IPVS ".net", RRD_TYPE_NET_IPVS ".net", save_history, "IPVS Bandwidth", "bandwidth in kilobits/s", RRD_TYPE_NET_IPVS);
2450 - if(!st) {
2451 - error("Cannot create RRD_STATS for %s.", RRD_TYPE_NET_IPVS ".net");
2452 - return 1;
2453 - }
2454 -
2455 - if(!rrd_stats_dimension_add(st, "sent", "sent", sizeof(unsigned long long), -8, 1024, RRD_DIMENSION_INCREMENTAL))
2456 - error("Cannot add RRD_STATS dimension %s.", "sent");
2457 -
2458 - if(!rrd_stats_dimension_add(st, "received", "received", sizeof(unsigned long long), 8, 1024, RRD_DIMENSION_INCREMENTAL))
2459 - error("Cannot add RRD_STATS dimension %s.", "received");
2460 - }
2461 - else rrd_stats_next(st);
2462 -
2463 - rrd_stats_dimension_set(st, "received", &InBytes);
2464 - rrd_stats_dimension_set(st, "sent", &OutBytes);
2465 - rrd_stats_done(st);
2466 -
2467 - return 0;
2468 -}
2469 -
2470 -int do_proc_stat() {
2471 - char buffer[MAX_PROC_STAT_LINE+1] = "";
2472 -
2473 - FILE *fp = fopen("/proc/stat", "r");
2474 - if(!fp) {
2475 - error("Cannot read /proc/stat.");
2476 - return 1;
2477 - }
2478 -
2479 - for(;1;) {
2480 - char *p = fgets(buffer, MAX_PROC_STAT_LINE, fp);
2481 - if(!p) break;
2482 -
2483 - if(strncmp(p, "cpu", 3) == 0) {
2484 - char id[MAX_PROC_STAT_NAME + 1] = RRD_TYPE_STAT ".";
2485 - unsigned long long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
2486 - char *name = &id[RRD_TYPE_STAT_LEN + 1];
2487 -
2488 - int r = sscanf(buffer, "%s %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
2489 - name, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice);
2490 -
2491 - if(r == EOF) break;
2492 - if(r != 11) error("Cannot read /proc/stat cpu line. Expected 11 params, read %d.", r);
2493 -
2494 - RRD_STATS *st = rrd_stats_find(id);
2495 - if(!st) {
2496 - st = rrd_stats_create(id, id, save_history, "Time for ", "percentage", RRD_TYPE_STAT);
2497 - if(!st) {
2498 - error("Cannot create RRD_STATS for %s.", id);
2499 - continue;
2500 - }
2501 -
2502 - if(strcmp(id, "cpu.cpu") == 0) strcpy(st->usertitle, "all CPUs");
2503 - else strcpy(st->usertitle, name);
2504 -
2505 - long multiplier = 1;
2506 - long divisor = 1; // sysconf(_SC_CLK_TCK);
2507 -
2508 - if(!rrd_stats_dimension_add(st, "iowait", "iowait", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2509 - error("Cannot add RRD_STATS dimension %s.", "iowait");
2510 -
2511 - if(!rrd_stats_dimension_add(st, "nice", "nice", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2512 - error("Cannot add RRD_STATS dimension %s.", "nice");
2513 -
2514 - if(!rrd_stats_dimension_add(st, "system", "system", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2515 - error("Cannot add RRD_STATS dimension %s.", "system");
2516 -
2517 - if(!rrd_stats_dimension_add(st, "user", "user", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2518 - error("Cannot add RRD_STATS dimension %s.", "user");
2519 -
2520 - if(!rrd_stats_dimension_add(st, "idle", "idle", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2521 - error("Cannot add RRD_STATS dimension %s.", "idle");
2522 - else rrd_stats_dimension_hide(st, "idle");
2523 -
2524 - if(!rrd_stats_dimension_add(st, "irq", "irq", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2525 - error("Cannot add RRD_STATS dimension %s.", "irq");
2526 -
2527 - if(!rrd_stats_dimension_add(st, "softirq", "softirq", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2528 - error("Cannot add RRD_STATS dimension %s.", "softirq");
2529 -
2530 - if(!rrd_stats_dimension_add(st, "steal", "steal", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2531 - error("Cannot add RRD_STATS dimension %s.", "steal");
2532 -
2533 - if(!rrd_stats_dimension_add(st, "guest", "guest", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2534 - error("Cannot add RRD_STATS dimension %s.", "guest");
2535 -
2536 - if(!rrd_stats_dimension_add(st, "guest_nice", "guest_nice", sizeof(unsigned long long), multiplier, divisor, RRD_DIMENSION_PCENT_OVER_TOTAL))
2537 - error("Cannot add RRD_STATS dimension %s.", "guest_nice");
2538 -
2539 - }
2540 - else rrd_stats_next(st);
2541 -
2542 - rrd_stats_dimension_set(st, "user", &user);
2543 - rrd_stats_dimension_set(st, "nice", &nice);
2544 - rrd_stats_dimension_set(st, "system", &system);
2545 - rrd_stats_dimension_set(st, "idle", &idle);
2546 - rrd_stats_dimension_set(st, "iowait", &iowait);
2547 - rrd_stats_dimension_set(st, "irq", &irq);
2548 - rrd_stats_dimension_set(st, "softirq", &softirq);
2549 - rrd_stats_dimension_set(st, "steal", &steal);
2550 - rrd_stats_dimension_set(st, "guest", &guest);
2551 - rrd_stats_dimension_set(st, "guest_nice", &guest_nice);
2552 - rrd_stats_done(st);
2553 - }
2554 - else if(strncmp(p, "intr ", 5) == 0) {
2555 - char id[MAX_PROC_STAT_NAME + 1] = RRD_TYPE_STAT ".";
2556 - char *name = &id[RRD_TYPE_STAT_LEN + 1];
2557 -
2558 - unsigned long long value;
2559 -
2560 - int r = sscanf(buffer, "%s %llu ", name, &value);
2561 - if(r == EOF) break;
2562 - if(r != 2) error("Cannot read /proc/stat intr line. Expected 2 params, read %d.", r);
2563 -
2564 - RRD_STATS *st = rrd_stats_find(id);
2565 - if(!st) {
2566 - st = rrd_stats_create(id, id, save_history, "CPU Interrupts", "interrupts/s", RRD_TYPE_STAT);
2567 - if(!st) {
2568 - error("Cannot create RRD_STATS for %s.", id);
2569 - continue;
2570 - }
2571 -
2572 - if(!rrd_stats_dimension_add(st, "interrupts", "interrupts", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2573 - error("Cannot add RRD_STATS dimension %s.", "interrupts");
2574 -
2575 - }
2576 - else rrd_stats_next(st);
2577 -
2578 - rrd_stats_dimension_set(st, "interrupts", &value);
2579 - rrd_stats_done(st);
2580 - }
2581 - else if(strncmp(p, "ctxt ", 5) == 0) {
2582 - char id[MAX_PROC_STAT_NAME + 1] = RRD_TYPE_STAT ".";
2583 - char *name = &id[RRD_TYPE_STAT_LEN + 1];
2584 -
2585 - unsigned long long value;
2586 -
2587 - int r = sscanf(buffer, "%s %llu ", name, &value);
2588 - if(r == EOF) break;
2589 - if(r != 2) error("Cannot read /proc/stat ctxt line. Expected 2 params, read %d.", r);
2590 -
2591 - RRD_STATS *st = rrd_stats_find(id);
2592 - if(!st) {
2593 - st = rrd_stats_create(id, id, save_history, "CPU Context Switches", "context switches/s", RRD_TYPE_STAT);
2594 - if(!st) {
2595 - error("Cannot create RRD_STATS for %s.", id);
2596 - continue;
2597 - }
2598 -
2599 - if(!rrd_stats_dimension_add(st, "switches", "switches", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2600 - error("Cannot add RRD_STATS dimension %s.", "switches");
2601 -
2602 - }
2603 - else rrd_stats_next(st);
2604 -
2605 - rrd_stats_dimension_set(st, "switches", &value);
2606 - rrd_stats_done(st);
2607 - }
2608 - else if(strncmp(p, "processes ", 10) == 0) {
2609 - char id[MAX_PROC_STAT_NAME + 1] = RRD_TYPE_STAT ".";
2610 - char *name = &id[RRD_TYPE_STAT_LEN + 1];
2611 -
2612 - unsigned long long value;
2613 -
2614 - int r = sscanf(buffer, "%s %llu ", name, &value);
2615 - if(r == EOF) break;
2616 - if(r != 2) error("Cannot read /proc/stat processes line. Expected 2 params, read %d.", r);
2617 -
2618 - RRD_STATS *st = rrd_stats_find(id);
2619 - if(!st) {
2620 - st = rrd_stats_create(id, id, save_history, "Started Processes", "processes/s", RRD_TYPE_STAT);
2621 - if(!st) {
2622 - error("Cannot create RRD_STATS for %s.", id);
2623 - continue;
2624 - }
2625 -
2626 - if(!rrd_stats_dimension_add(st, "started", "started", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_INCREMENTAL))
2627 - error("Cannot add RRD_STATS dimension %s.", "started");
2628 -
2629 - }
2630 - else rrd_stats_next(st);
2631 -
2632 - rrd_stats_dimension_set(st, "started", &value);
2633 - rrd_stats_done(st);
2634 - }
2635 - else if(strncmp(p, "procs_running ", 14) == 0) {
2636 - char id[MAX_PROC_STAT_NAME + 1] = RRD_TYPE_STAT ".";
2637 - char *name = &id[RRD_TYPE_STAT_LEN + 1];
2638 -
2639 - unsigned long long value;
2640 -
2641 - int r = sscanf(buffer, "%s %llu ", name, &value);
2642 - if(r == EOF) break;
2643 - if(r != 2) error("Cannot read /proc/stat processes line. Expected 2 params, read %d.", r);
2644 -
2645 - RRD_STATS *st = rrd_stats_find(id);
2646 - if(!st) {
2647 - st = rrd_stats_create(id, id, save_history, "CPU Running Processes", "processes running", RRD_TYPE_STAT);
2648 - if(!st) {
2649 - error("Cannot create RRD_STATS for %s.", id);
2650 - continue;
2651 - }
2652 -
2653 - if(!rrd_stats_dimension_add(st, "running", "running", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_ABSOLUTE))
2654 - error("Cannot add RRD_STATS dimension %s.", "running");
2655 -
2656 - }
2657 - else rrd_stats_next(st);
2658 -
2659 - rrd_stats_dimension_set(st, "running", &value);
2660 - rrd_stats_done(st);
2661 - }
2662 - }
2663 -
2664 - fclose(fp);
2665 - return 0;
2666 -}
2667 -
2668 -
2669 -// ----------------------------------------------------------------------------
2670 -// /proc processor
2671 -
2672 -void *proc_main(void *ptr)
2673 -{
2674 - struct timeval last, now, tmp;
2675 -
2676 - gettimeofday(&last, NULL);
2677 - last.tv_sec -= update_every;
2678 -
2679 - // when ZERO, attempt to do it
2680 - int vdo_proc_net_dev = 0;
2681 - int vdo_proc_diskstats = 0;
2682 - int vdo_proc_net_snmp = 0;
2683 - int vdo_proc_net_netstat = 0;
2684 - int vdo_proc_net_stat_conntrack = 0;
2685 - int vdo_proc_net_ip_vs_stats = 0;
2686 - int vdo_proc_stat = 0;
2687 -
2688 - for(;1;) {
2689 - unsigned long long usec, susec;
2690 - gettimeofday(&now, NULL);
2691 -
2692 - // calculate the time it took for a full loop
2693 - usec = usecdiff(&now, &last);
2694 - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: Last full loop took %llu usec.", usec);
2695 -
2696 - // BEGIN -- the job to be done
2697 - if(!vdo_proc_net_dev) vdo_proc_net_dev = do_proc_net_dev(usec);
2698 - if(!vdo_proc_diskstats) vdo_proc_diskstats = do_proc_diskstats(usec);
2699 - if(!vdo_proc_net_snmp) vdo_proc_net_snmp = do_proc_net_snmp(usec);
2700 - if(!vdo_proc_net_netstat) vdo_proc_net_netstat = do_proc_net_netstat(usec);
2701 - if(!vdo_proc_net_stat_conntrack) vdo_proc_net_stat_conntrack = do_proc_net_stat_conntrack(usec);
2702 - if(!vdo_proc_net_ip_vs_stats) vdo_proc_net_ip_vs_stats = do_proc_net_ip_vs_stats(usec);
2703 - if(!vdo_proc_stat) vdo_proc_stat = do_proc_stat(usec);
2704 - // END -- the job is done
2705 -
2706 - // find the time to sleep in order to wait exactly update_every seconds
2707 - gettimeofday(&tmp, NULL);
2708 - usec = usecdiff(&tmp, &now);
2709 - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: This loop's work took %llu usec.", usec);
2710 -
2711 - if(usec < (update_every * 1000000)) susec = (update_every * 1000000) - usec;
2712 - else susec = 0;
2713 -
2714 - // make sure we will wait at least 100ms
2715 - if(susec < 100000) susec = 100000;
2716 -
2717 - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: Sleeping for %llu usec.", susec);
2718 - usleep(susec);
2719 -
2720 - // copy now to last
2721 - last.tv_sec = now.tv_sec;
2722 - last.tv_usec = now.tv_usec;
2723 - }
2724 -
2725 - return NULL;
2726 -}
2727 -
2728 -// ----------------------------------------------------------------------------
2729 -// /sbin/tc processor
2730 -// this requires the script tc-all.sh
2731 -
2732 -#define PIPE_READ 0
2733 -#define PIPE_WRITE 1
2734 -#define TC_LINE_MAX 1024
2735 -
2736 -struct tc_class {
2737 - char id[RRD_STATS_NAME_MAX + 1];
2738 - char name[RRD_STATS_NAME_MAX + 1];
2739 -
2740 - char parentid[RRD_STATS_NAME_MAX + 1];
2741 -
2742 - int hasparent;
2743 - int isleaf;
2744 - unsigned long long bytes;
2745 -
2746 - struct tc_class *next;
2747 -};
2748 -
2749 -struct tc_device {
2750 - char id[RRD_STATS_NAME_MAX + 1];
2751 - char name[RRD_STATS_NAME_MAX + 1];
2752 -
2753 - struct tc_class *classes;
2754 -};
2755 -
2756 -void tc_device_commit(struct tc_device *d)
2757 -{
2758 - // we only need to add leaf classes
2759 - struct tc_class *c, *x;
2760 - for ( c = d->classes ; c ; c = c->next) {
2761 - c->isleaf = 1;
2762 -
2763 - for ( x = d->classes ; x ; x = x->next) {
2764 - if(strcmp(c->id, x->parentid) == 0) {
2765 - c->isleaf = 0;
2766 - x->hasparent = 1;
2767 - }
2768 - }
2769 - }
2770 -
2771 - // debugging:
2772 - // for ( c = d->classes ; c ; c = c->next) {
2773 - // if(c->isleaf && c->hasparent) debug(D_TC_LOOP, "Device %s, class %s, OK", d->name, c->id);
2774 - // else debug(D_TC_LOOP, "Device %s, class %s, IGNORE (isleaf: %d, hasparent: %d, parent: %s)", d->name, c->id, c->isleaf, c->hasparent, c->parentid);
2775 - // }
2776 -
2777 - for ( c = d->classes ; c ; c = c->next) {
2778 - if(c->isleaf && c->hasparent) break;
2779 - }
2780 - if(!c) {
2781 - debug(D_TC_LOOP, "Ignoring TC device '%s'. No leaf classes.", d->name);
2782 - return;
2783 - }
2784 -
2785 - debug(D_TC_LOOP, "Committing TC device '%s'", d->name);
2786 -
2787 - RRD_STATS *st = rrd_stats_find(d->id);
2788 - if(!st) {
2789 - st = rrd_stats_create(d->id, d->name, save_history, "Class usage for ", "Bandwidth in kilobits/s", RRD_TYPE_TC);
2790 - if(!st) {
2791 - error("Cannot create RRD_STATS for interface %s.", d->name);
2792 - return;
2793 - }
2794 -
2795 - for ( c = d->classes ; c ; c = c->next) {
2796 - if(c->isleaf && c->hasparent) {
2797 - if(!rrd_stats_dimension_add(st, c->id, c->name, sizeof(unsigned long long), 8, 1024, RRD_DIMENSION_INCREMENTAL))
2798 - error("Cannot add RRD_STATS dimension %s.", c->name);
2799 - }
2800 - }
2801 - }
2802 - else rrd_stats_next(st);
2803 -
2804 - for ( c = d->classes ; c ; c = c->next) {
2805 - if(c->isleaf && c->hasparent)
2806 - if(rrd_stats_dimension_set(st, c->id, &c->bytes) != 0) {
2807 -
2808 - // new class, we have to add it
2809 - if(!rrd_stats_dimension_add(st, c->id, c->name, sizeof(unsigned long long), 8, 1024, RRD_DIMENSION_INCREMENTAL))
2810 - error("Cannot add RRD_STATS dimension %s.", c->name);
2811 - else rrd_stats_dimension_set(st, c->id, &c->bytes);
2812 - }
2813 -
2814 - // check if the class changed name
2815 - RRD_DIMENSION *rd;
2816 - for(rd = st->dimensions ; rd ; rd = rd->next) {
2817 - if(strcmp(rd->id, c->id) == 0) { strcpy(rd->name, c->name); break; }
2818 - }
2819 - }
2820 - rrd_stats_done(st);
2821 -}
2822 -
2823 -void tc_device_set_class_name(struct tc_device *d, char *id, char *name)
2824 -{
2825 - struct tc_class *c;
2826 - for ( c = d->classes ; c ; c = c->next) {
2827 - if(strcmp(c->id, id) == 0) {
2828 - strncpy(c->name, name, RRD_STATS_NAME_MAX);
2829 - c->name[RRD_STATS_NAME_MAX] = '\0';
2830 - break;
2831 - }
2832 - }
2833 -}
2834 -
2835 -void tc_device_set_device_name(struct tc_device *d, char *name)
2836 -{
2837 - strcpy(d->name, RRD_TYPE_TC ".");
2838 - strncpy(&d->name[RRD_TYPE_TC_LEN + 1], name, RRD_STATS_NAME_MAX - 3);
2839 - d->name[RRD_STATS_NAME_MAX] = '\0';
2840 -}
2841 -
2842 -struct tc_device *tc_device_create(char *name)
2843 -{
2844 - struct tc_device *d;
2845 -
2846 - d = calloc(1, sizeof(struct tc_device));
2847 - if(!d) return NULL;
2848 -
2849 - strcpy(d->name, RRD_TYPE_TC ".");
2850 - strncpy(&d->name[RRD_TYPE_TC_LEN + 1], name, RRD_STATS_NAME_MAX - 3);
2851 - d->name[RRD_STATS_NAME_MAX] = '\0';
2852 -
2853 - strcpy(d->id, d->name);
2854 -
2855 - return(d);
2856 -}
2857 -
2858 -struct tc_class *tc_class_add(struct tc_device *n, char *id, char *parentid)
2859 -{
2860 - struct tc_class *c;
2861 -
2862 - c = calloc(1, sizeof(struct tc_class));
2863 - if(!c) return NULL;
2864 -
2865 - c->next = n->classes;
2866 - n->classes = c;
2867 -
2868 - strncpy(c->id, id, RRD_STATS_NAME_MAX);
2869 - strcpy(c->name, c->id);
2870 - if(parentid) strncpy(c->parentid, parentid, RRD_STATS_NAME_MAX);
2871 -
2872 - return(c);
2873 -}
2874 -
2875 -void tc_class_free(struct tc_class *c)
2876 -{
2877 - if(c->next) tc_class_free(c->next);
2878 - free(c);
2879 -}
2880 -
2881 -void tc_device_free(struct tc_device *n)
2882 -{
2883 - if(n->classes) tc_class_free(n->classes);
2884 - free(n);
2885 -}
2886 -
2887 -pid_t tc_child_pid = 0;
2888 -void *tc_main(void *ptr)
2889 -{
2890 - char buffer[TC_LINE_MAX+1] = "";
2891 -
2892 - for(;1;) {
2893 - FILE *fp;
2894 - struct tc_device *device = NULL;
2895 - struct tc_class *class = NULL;
2896 -
2897 - sprintf(buffer, "exec ./tc-all.sh %d", update_every);
2898 - fp = popen(buffer, "r");
2899 -
2900 - while(fgets(buffer, TC_LINE_MAX, fp) != NULL) {
2901 - buffer[TC_LINE_MAX] = '\0';
2902 - char *b = buffer, *p;
2903 - // debug(D_TC_LOOP, "TC: read '%s'", buffer);
2904 -
2905 - p = strsep(&b, " \n");
2906 - while (p && (*p == ' ' || *p == '\0')) p = strsep(&b, " \n");
2907 - if(!p) continue;
2908 -
2909 - if(strcmp(p, "END") == 0) {
2910 - if(device) {
2911 - tc_device_commit(device);
2912 - tc_device_free(device);
2913 - device = NULL;
2914 - class = NULL;
2915 - }
2916 - }
2917 - else if(strcmp(p, "BEGIN") == 0) {
2918 - if(device) tc_device_free(device);
2919 -
2920 - p = strsep(&b, " \n");
2921 - if(p && *p) device = tc_device_create(p);
2922 - }
2923 - else if(device && (strcmp(p, "class") == 0)) {
2924 - p = strsep(&b, " \n"); // the class: htb, fq_codel, etc
2925 - char *id = strsep(&b, " \n"); // the class major:minor
2926 - char *parent = strsep(&b, " \n"); // 'parent' or 'root'
2927 - char *parentid = strsep(&b, " \n"); // the parent's id
2928 -
2929 - if(id && *id
2930 - && parent && *parent
2931 - && parentid && *parentid
2932 - && (
2933 - (strcmp(parent, "parent") == 0 && parentid && *parentid)
2934 - || strcmp(parent, "root") == 0
2935 - )) {
2936 - if(strcmp(parent, "root") == 0) parentid = NULL;
2937 -
2938 - class = tc_class_add(device, id, parentid);
2939 - }
2940 - }
2941 - else if(device && class && (strcmp(p, "Sent") == 0)) {
2942 - p = strsep(&b, " \n");
2943 - if(p && *p) class->bytes = atoll(p);
2944 - }
2945 - else if(device && (strcmp(p, "SETDEVICENAME") == 0)) {
2946 - char *name = strsep(&b, " |\n");
2947 - if(name && *name) tc_device_set_device_name(device, name);
2948 - }
2949 - else if(device && (strcmp(p, "SETCLASSNAME") == 0)) {
2950 - char *name = strsep(&b, " |\n");
2951 - char *path = strsep(&b, " |\n");
2952 - char *id = strsep(&b, " |\n");
2953 - char *qdisc = strsep(&b, " |\n");
2954 - if(id && *id && path && *path) tc_device_set_class_name(device, id, path);
2955 -
2956 - // prevent unused variables warning
2957 - if(qdisc) qdisc = NULL;
2958 - if(name) name = NULL;
2959 - }
2960 - else if((strcmp(p, "MYPID") == 0)) {
2961 - char *id = strsep(&b, " \n");
2962 - tc_child_pid = atol(id);
2963 - debug(D_TC_LOOP, "Child PID is %d.", tc_child_pid);
2964 - }
2965 - }
2966 - pclose(fp);
2967 -
2968 - sleep(1);
2969 - }
2970 -
2971 - return NULL;
2972 -}
2973 -
2974 -// ----------------------------------------------------------------------------
2975 -// cpu jitter calculation
2976 -
2977 -#define CPU_JITTER_SLEEP_TIME_MS 20
2978 -
2979 -void *cpujitter_main(void *ptr)
2980 -{
2981 -
2982 - struct timeval before, after;
2983 -
2984 - while(1) {
2985 - unsigned long long usec, susec = 0;
2986 -
2987 - while(susec < (update_every * 1000000L)) {
2988 -
2989 - gettimeofday(&before, NULL);
2990 - usleep(CPU_JITTER_SLEEP_TIME_MS * 1000);
2991 - gettimeofday(&after, NULL);
2992 -
2993 - // calculate the time it took for a full loop
2994 - usec = usecdiff(&after, &before);
2995 - susec += usec;
2996 - }
2997 - usec -= (CPU_JITTER_SLEEP_TIME_MS * 1000);
2998 -
2999 - RRD_STATS *st = rrd_stats_find(RRD_TYPE_STAT ".jitter");
3000 - if(!st) {
3001 - st = rrd_stats_create(RRD_TYPE_STAT ".jitter", RRD_TYPE_STAT ".jitter", save_history, "CPU Jitter", "microseconds lost", RRD_TYPE_STAT);
3002 - if(!st) {
3003 - error("Cannot create RRD_STATS for interface %s.", RRD_TYPE_STAT ".jitter");
3004 - continue;
3005 - }
3006 -
3007 - if(!rrd_stats_dimension_add(st, "jitter", "jitter", sizeof(unsigned long long), 1, 1, RRD_DIMENSION_ABSOLUTE))
3008 - error("Cannot add RRD_STATS dimension %s.", "jitter");
3009 -
3010 - }
3011 - else rrd_stats_next(st);
3012 -
3013 - rrd_stats_dimension_set(st, "jitter", &usec);
3014 - rrd_stats_done(st);
3015 - }
3016 -
3017 - return NULL;
3018 -}
3019 -
3020 -
3021 -
3022 -void bye(void)
3023 -{
3024 - error("bye...");
3025 - if(tc_child_pid) kill(tc_child_pid, SIGTERM);
3026 - tc_child_pid = 0;
3027 -}
3028 -
3029 -void sig_handler(int signo)
3030 -{
3031 - switch(signo) {
3032 - case SIGTERM:
3033 - case SIGQUIT:
3034 - case SIGINT:
3035 - case SIGHUP:
3036 - case SIGSEGV:
3037 - error("Signaled cleanup (signal %d).", signo);
3038 - if(tc_child_pid) kill(tc_child_pid, SIGTERM);
3039 - tc_child_pid = 0;
3040 - exit(1);
3041 - break;
3042 -
3043 - default:
3044 - error("Signal %d received. Ignoring it.", signo);
3045 - break;
3046 - }
3047 -}
3048 -
3049 -int become_user(const char *username)
3050 -{
3051 - struct passwd *pw = getpwnam(username);
3052 - if(!pw) {
3053 - fprintf(stderr, "User %s is not present. Error: %s\n", username, strerror(errno));
3054 - return -1;
3055 - }
3056 - if(setgid(pw->pw_gid) != 0) {
3057 - fprintf(stderr, "Cannot switch to user's %s group (gid: %d). Error: %s\n", username, pw->pw_gid, strerror(errno));
3058 - return -1;
3059 - }
3060 - if(setegid(pw->pw_gid) != 0) {
3061 - fprintf(stderr, "Cannot effectively switch to user's %s group (gid: %d). Error: %s\n", username, pw->pw_gid, strerror(errno));
3062 - return -1;
3063 - }
3064 - if(setuid(pw->pw_uid) != 0) {
3065 - fprintf(stderr, "Cannot switch to user %s (uid: %d). Error: %s\n", username, pw->pw_uid, strerror(errno));
3066 - return -1;
3067 - }
3068 - if(seteuid(pw->pw_uid) != 0) {
3069 - fprintf(stderr, "Cannot effectively switch to user %s (uid: %d). Error: %s\n", username, pw->pw_uid, strerror(errno));
3070 - return -1;
3071 - }
3072 -
3073 - return(0);
3074 -}
3075 -
3076 -void become_daemon()
3077 -{
3078 - int i = fork();
3079 - if(i == -1) {
3080 - perror("cannot fork");
3081 - exit(1);
3082 - }
3083 - if(i != 0) {
3084 - exit(0); // the parent
3085 - }
3086 -
3087 - // become session leader
3088 - if (setsid() < 0)
3089 - exit(2);
3090 -
3091 - signal(SIGCHLD, SIG_IGN);
3092 - signal(SIGHUP, SIG_IGN);
3093 - signal(SIGWINCH, SIG_IGN);
3094 -
3095 - // fork() again
3096 - i = fork();
3097 - if(i == -1) {
3098 - perror("cannot fork");
3099 - exit(1);
3100 - }
3101 - if(i != 0) {
3102 - exit(0); // the parent
3103 - }
3104 -
3105 - // Set new file permissions
3106 - umask(0);
3107 -
3108 - // close all files
3109 - for(i = sysconf(_SC_OPEN_MAX); i > 0; i--)
3110 - close(i);
3111 -
3112 - silent = 1;
3113 -}
3114 -
3115 -int main(int argc, char **argv)
3116 -{
3117 - int i, daemon = 0;
3118 -
3119 - // parse the arguments
3120 - for(i = 1; i < argc ; i++) {
3121 - if(strcmp(argv[i], "-l") == 0 && (i+1) < argc) {
3122 - save_history = atoi(argv[i+1]);
3123 - if(save_history < 5 || save_history > HISTORY_MAX) {
3124 - fprintf(stderr, "Invalid save lines %d given. Defaulting to %d.\n", save_history, HISTORY);
3125 - save_history = HISTORY;
3126 - }
3127 - else {
3128 - debug(D_OPTIONS, "save lines set to %d.", save_history);
3129 - }
3130 - i++;
3131 - }
3132 - else if(strcmp(argv[i], "-u") == 0 && (i+1) < argc) {
3133 - if(become_user(argv[i+1]) != 0) {
3134 - fprintf(stderr, "Cannot become user %s.\n", argv[i+1]);
3135 - exit(1);
3136 - }
3137 - else {
3138 - debug(D_OPTIONS, "Successfully became user %s.", argv[i+1]);
3139 - }
3140 - i++;
3141 - }
3142 - else if(strcmp(argv[i], "-t") == 0 && (i+1) < argc) {
3143 - update_every = atoi(argv[i+1]);
3144 - if(update_every < 1 || update_every > 600) {
3145 - fprintf(stderr, "Invalid update timer %d given. Defaulting to %d.\n", update_every, UPDATE_EVERY_MAX);
3146 - update_every = UPDATE_EVERY;
3147 - }
3148 - else {
3149 - debug(D_OPTIONS, "update timer set to %d.", update_every);
3150 - }
3151 - i++;
3152 - }
3153 - else if(strcmp(argv[i], "-p") == 0 && (i+1) < argc) {
3154 - listen_port = atoi(argv[i+1]);
3155 - if(listen_port < 1 || listen_port > 65535) {
3156 - fprintf(stderr, "Invalid listen port %d given. Defaulting to %d.\n", listen_port, LISTEN_PORT);
3157 - listen_port = LISTEN_PORT;
3158 - }
3159 - else {
3160 - debug(D_OPTIONS, "update timer set to %d.", update_every);
3161 - }
3162 - i++;
3163 - }
3164 - else if(strcmp(argv[i], "-d") == 0) {
3165 - daemon = 1;
3166 - debug(D_OPTIONS, "Enabled daemon mode.");
3167 - }
3168 - else {
3169 - fprintf(stderr, "Cannot understand option '%s'.\n", argv[i]);
3170 - fprintf(stderr, "\nUSAGE: %s [-d] [-l LINES_TO_SAVE] [-u UPDATE_TIMER] [-p LISTEN_PORT].\n\n", argv[0]);
3171 - fprintf(stderr, " -d enable daemon mode (run in background).\n");
3172 - fprintf(stderr, " -l LINES_TO_SAVE can be from 5 to %d lines in JSON data. Default: %d.\n", HISTORY_MAX, HISTORY);
3173 - fprintf(stderr, " -t UPDATE_TIMER can be from 1 to %d seconds. Default: %d.\n", UPDATE_EVERY_MAX, UPDATE_EVERY);
3174 - fprintf(stderr, " -p LISTEN_PORT can be from 1 to %d. Default: %d.\n", 65535, LISTEN_PORT);
3175 - fprintf(stderr, " -u USERNAME can be any system username to run as. Default: none.\n");
3176 - exit(1);
3177 - }
3178 - }
3179 -
3180 - // never become a problem
3181 - if(nice(20) == -1) {
3182 - fprintf(stderr, "Cannot lower my CPU priority. Error: %s.\n", strerror(errno));
3183 - }
3184 -
3185 - if(sizeof(long long) >= sizeof(long double)) {
3186 - fprintf(stderr, "\n\nWARNING:\nThis system does not support [long double] variables properly.\nArithmetic overflows and rounding errors may occur.\n\n");
3187 - }
3188 -
3189 - if(daemon) become_daemon();
3190 -
3191 - // open syslog
3192 - openlog("netdata", LOG_PID, LOG_DAEMON);
3193 - syslog(LOG_NOTICE, "netdata started.");
3194 -
3195 - // make sure we cleanup correctly
3196 - atexit(bye);
3197 -
3198 - // catch all signals
3199 - for (i = 1 ; i < 65 ;i++) if(i != SIGSEGV) signal(i, sig_handler);
3200 -
3201 -
3202 - pthread_t p_proc, p_tc, p_jitter;
3203 - int r_proc, r_tc, r_jitter;
3204 -
3205 - // spawn a child to collect data
3206 - r_proc = pthread_create(&p_proc, NULL, proc_main, NULL);
3207 - r_tc = pthread_create(&p_tc, NULL, tc_main, NULL);
3208 - r_jitter = pthread_create(&p_jitter, NULL, cpujitter_main, NULL);
3209 -
3210 - // the main process - the web server listener
3211 - //sleep(1);
3212 - socket_listen_main(NULL);
3213 -
3214 - // wait for the childs to finish
3215 - pthread_join(p_tc, NULL);
3216 - pthread_join(p_proc, NULL);
3217 -
3218 - printf("TC thread returns: %d\n", r_tc);
3219 - printf("PROC NET DEV thread returns: %d\n", r_proc);
3220 - printf("CPU JITTER thread returns: %d\n", r_jitter);
3221 -
3222 - exit(0);
3223 -}
1 +// enable strcasestr()
2 +#define _GNU_SOURCE
3 +
4 +#include <stdio.h>
5 +#include <stdlib.h>
6 +#include <string.h>
7 +#include <time.h>
8 +#include <unistd.h>
9 +#include <pwd.h>
10 +#include <sys/types.h>
11 +#include <sys/time.h>
12 +
13 +#include <sys/socket.h>
14 +#include <sys/select.h>
15 +#include <sys/resource.h>
16 +#include <sys/stat.h>
17 +#include <sys/sendfile.h>
18 +
19 +#include <arpa/inet.h>
20 +#include <netinet/in.h>
21 +#include <netinet/tcp.h>
22 +
23 +#include <errno.h>
24 +#include <stdarg.h>
25 +#include <locale.h>
26 +#include <signal.h>
27 +#include <ctype.h>
28 +#include <time.h>
29 +#include <fcntl.h>
30 +#include <syslog.h>
31 +
32 +#include <pthread.h>
33 +#include <zlib.h>
34 +
35 +#define RRD_DIMENSION_ABSOLUTE 0
36 +#define RRD_DIMENSION_INCREMENTAL 1
37 +#define RRD_DIMENSION_PCENT_OVER_TOTAL 2
38 +
39 +#define RRD_TYPE_NET "net"
40 +#define RRD_TYPE_NET_LEN strlen(RRD_TYPE_NET)
41 +
42 +#define RRD_TYPE_TC "tc"
43 +#define RRD_TYPE_TC_LEN strlen(RRD_TYPE_TC)
44 +
45 +#define RRD_TYPE_DISK "disk"
46 +#define RRD_TYPE_DISK_LEN strlen(RRD_TYPE_DISK)
47 +
48 +#define RRD_TYPE_NET_SNMP "ipv4"
49 +#define RRD_TYPE_NET_SNMP_LEN strlen(RRD_TYPE_NET_SNMP)
50 +
51 +#define RRD_TYPE_NET_STAT_CONNTRACK "conntrack"
52 +#define RRD_TYPE_NET_STAT_CONNTRACK_LEN strlen(RRD_TYPE_NET_STAT_CONNTRACK)
53 +
54 +#define RRD_TYPE_NET_IPVS "ipvs"
55 +#define RRD_TYPE_NET_IPVS_LEN strlen(RRD_TYPE_NET_IPVS)
56 +
57 +#define RRD_TYPE_STAT "cpu"
58 +#define RRD_TYPE_STAT_LEN strlen(RRD_TYPE_STAT)
59 +
60 +#define WEB_PATH_FILE "file"
61 +#define WEB_PATH_DATA "data"
62 +#define WEB_PATH_GRAPH "graph"
63 +
64 +// internal defaults
65 +#define UPDATE_EVERY 1
66 +#define UPDATE_EVERY_MAX 3600
67 +#define LISTEN_PORT 19999
68 +#define HISTORY 3600
69 +#define HISTORY_MAX (86400*10)
70 +#define SAVE_PATH "/tmp"
71 +
72 +#define D_WEB_BUFFER 0x00000001
73 +#define D_WEB_CLIENT 0x00000002
74 +#define D_LISTENER 0x00000004
75 +#define D_WEB_DATA 0x00000008
76 +#define D_OPTIONS 0x00000010
77 +#define D_PROCNETDEV_LOOP 0x00000020
78 +#define D_RRD_STATS 0x00000040
79 +#define D_WEB_CLIENT_ACCESS 0x00000080
80 +#define D_TC_LOOP 0x00000100
81 +#define D_DEFLATE 0x00000200
82 +
83 +#define CT_APPLICATION_JSON 1
84 +#define CT_TEXT_PLAIN 2
85 +#define CT_TEXT_HTML 3
86 +#define CT_APPLICATION_X_JAVASCRIPT 4
87 +#define CT_TEXT_CSS 5
88 +#define CT_TEXT_XML 6
89 +#define CT_APPLICATION_XML 7
90 +#define CT_TEXT_XSL 8
91 +#define CT_APPLICATION_OCTET_STREAM 9
92 +#define CT_APPLICATION_X_FONT_TRUETYPE 10
93 +#define CT_APPLICATION_X_FONT_OPENTYPE 11
94 +#define CT_APPLICATION_FONT_WOFF 12
95 +#define CT_APPLICATION_VND_MS_FONTOBJ 13
96 +#define CT_IMAGE_SVG_XML 14
97 +
98 +// configuration
99 +#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS)
100 +//#define DEBUG 0xffffffff
101 +//#define DEBUG (0)
102 +
103 +#define EXIT_FAILURE 1
104 +#define LISTEN_BACKLOG 100
105 +
106 +#define MAX_SOCKET_DATA_LENGTH (256 * 1024)
107 +#define SOCKET_DATA_LENGTH_INCREASE_STEP 65536
108 +#define ZLIB_CHUNK 16384
109 +
110 +#define MAX_HTTP_HEADER_SIZE 16384
111 +
112 +#define MAX_PROC_NET_DEV_LINE 4096
113 +#define MAX_PROC_NET_DEV_IFACE_NAME 1024
114 +
115 +#define MAX_PROC_DISKSTATS_LINE 4096
116 +#define MAX_PROC_DISKSTATS_DISK_NAME 1024
117 +
118 +#define MAX_PROC_NET_SNMP_LINE 4096
119 +#define MAX_PROC_NET_SNMP_NAME 1024
120 +
121 +#define MAX_PROC_NET_STAT_CONNTRACK_LINE 4096
122 +#define MAX_PROC_NET_STAT_CONNTRACK_NAME 1024
123 +
124 +#define MAX_PROC_NET_IPVS_LINE 4096
125 +#define MAX_PROC_NET_IPVS_NAME 1024
126 +
127 +#define MAX_PROC_STAT_LINE 4096
128 +#define MAX_PROC_STAT_NAME 1024
129 +
130 +
131 +int silent = 0;
132 +int save_history = HISTORY;
133 +int update_every = UPDATE_EVERY;
134 +int listen_port = LISTEN_PORT;
135 +
136 +
137 +// ----------------------------------------------------------------------------
138 +// helpers
139 +
140 +unsigned long long usecdiff(struct timeval *now, struct timeval *last) {
141 + return ((((now->tv_sec * 1000000L) + now->tv_usec) - ((last->tv_sec * 1000000L) + last->tv_usec)));
142 +}
143 +
144 +// ----------------------------------------------------------------------------
145 +// LOG
146 +
147 +void log_date()
148 +{
149 + char outstr[200];
150 + time_t t;
151 + struct tm *tmp;
152 +
153 + t = time(NULL);
154 + tmp = localtime(&t);
155 +
156 + if (tmp == NULL) return;
157 + if (strftime(outstr, sizeof(outstr), "%y-%m-%d %H:%M:%S", tmp) == 0) return;
158 +
159 + fprintf(stderr, "%s: ", outstr);
160 +}
161 +
162 +#define debug(args...) debug_int(__FILE__, __FUNCTION__, __LINE__, ##args)
163 +
164 +void debug_int( const char *file, const char *function, const unsigned long line, unsigned long type, const char *fmt, ... )
165 +{
166 + if(silent) return;
167 +
168 + va_list args;
169 +
170 + if(DEBUG & type) {
171 + log_date();
172 + va_start( args, fmt );
173 + fprintf(stderr, "DEBUG (%04lu@%-15.15s): ", line, function);
174 + vfprintf( stderr, fmt, args );
175 + va_end( args );
176 + fprintf(stderr, "\n");
177 + }
178 +}
179 +
180 +#define error(args...) error_int(__FILE__, __FUNCTION__, __LINE__, ##args)
181 +
182 +void error_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... )
183 +{
184 + va_list args;
185 +
186 + if(!silent) {
187 + log_date();
188 +
189 + va_start( args, fmt );
190 + fprintf(stderr, "ERROR (%04lu@%-15.15s): ", line, function);
191 + vfprintf( stderr, fmt, args );
192 + va_end( args );
193 +
194 + if(errno) {
195 + fprintf(stderr, " (errno %d, %s)\n", errno, strerror(errno));
196 + errno = 0;
197 + }
198 + else fprintf(stderr, "\n");
199 + }
200 +
201 + va_start( args, fmt );
202 + vsyslog(LOG_ERR, fmt, args );
203 + va_end( args );
204 +}
205 +
206 +#define fatal(args...) fatal_int(__FILE__, __FUNCTION__, __LINE__, ##args)
207 +
208 +void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... )
209 +{
210 + va_list args;
211 +
212 + if(!silent) {
213 + log_date();
214 +
215 + va_start( args, fmt );
216 + fprintf(stderr, "FATAL (%04lu@%-15.15s): ", line, function);
217 + vfprintf( stderr, fmt, args );
218 + va_end( args );
219 +
220 + perror(" # ");
221 + fprintf(stderr, "\n");
222 + }
223 +
224 + va_start( args, fmt );
225 + vsyslog(LOG_CRIT, fmt, args );
226 + va_end( args );
227 +
228 + exit(EXIT_FAILURE);
229 +}
230 +
231 +
232 +// ----------------------------------------------------------------------------
233 +// URL encode / decode
234 +// code from: http://www.geekhideout.com/urlcode.shtml
235 +
236 +/* Converts a hex character to its integer value */
237 +char from_hex(char ch) {
238 + return (char)(isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10);
239 +}
240 +
241 +/* Converts an integer value to its hex character*/
242 +char to_hex(char code) {
243 + static char hex[] = "0123456789abcdef";
244 + return hex[code & 15];
245 +}
246 +
247 +/* Returns a url-encoded version of str */
248 +/* IMPORTANT: be sure to free() the returned string after use */
249 +char *url_encode(char *str) {
250 + char *pstr = str,
251 + *buf = malloc(strlen(str) * 3 + 1),
252 + *pbuf = buf;
253 +
254 + while (*pstr) {
255 + if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
256 + *pbuf++ = *pstr;
257 +
258 + else if (*pstr == ' ')
259 + *pbuf++ = '+';
260 +
261 + else
262 + *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
263 +
264 + pstr++;
265 + }
266 +
267 + *pbuf = '\0';
268 +
269 + return buf;
270 +}
271 +
272 +/* Returns a url-decoded version of str */
273 +/* IMPORTANT: be sure to free() the returned string after use */
274 +char *url_decode(char *str) {
275 + char *pstr = str,
276 + *buf = malloc(strlen(str) + 1),
277 + *pbuf = buf;
278 +
279 + if(!buf) fatal("Cannot allocate memory.");
280 +
281 + while (*pstr) {
282 + if (*pstr == '%') {
283 + if (pstr[1] && pstr[2]) {
284 + *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
285 + pstr += 2;
286 + }
287 + }
288 + else if (*pstr == '+')
289 + *pbuf++ = ' ';
290 +
291 + else
292 + *pbuf++ = *pstr;
293 +
294 + pstr++;
295 + }
296 +
297 + *pbuf = '\0';
298 +
299 + return buf;
300 +}
301 +
302 +
303 +// ----------------------------------------------------------------------------
304 +// socket
305 +
306 +int create_listen_socket(int port)
307 +{
308 + int sock=-1;
309 + int sockopt=1;
310 + struct sockaddr_in name;
311 +
312 + debug(D_LISTENER, "Creating new listening socket on port %d", port);
313 +
314 + sock = socket(AF_INET, SOCK_STREAM, 0);
315 + if (sock < 0)
316 + fatal("socket() failed, errno=%d", errno);
317 +
318 + /* avoid "address already in use" */
319 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&sockopt, sizeof(sockopt));
320 +
321 + memset(&name, 0, sizeof(struct sockaddr_in));
322 + name.sin_family = AF_INET;
323 + name.sin_port = htons (port);
324 + name.sin_addr.s_addr = htonl (INADDR_ANY);
325 + if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
326 + fatal("bind() failed, errno=%d", errno);
327 +
328 + if (listen(sock, LISTEN_BACKLOG) < 0)
329 + fatal("listen() failed, errno=%d", errno);
330 +
331 + debug(D_LISTENER, "Listening Port %d created", port);
332 + return sock;
333 +}
334 +
335 +
336 +// ----------------------------------------------------------------------------
337 +// RRD STATS
338 +
339 +#define RRD_STATS_NAME_MAX 1024
340 +
341 +struct rrd_dimension {
342 + char id[RRD_STATS_NAME_MAX + 1];
343 + char name[RRD_STATS_NAME_MAX + 1];
344 + size_t bytes;
345 + size_t entries;
346 +
347 + int type;
348 + int hidden; // if set to non zero, this dimension will not be sent to the client
349 +
350 + long multiplier;
351 + long divisor;
352 +
353 + void *values;
354 +
355 + time_t last_updated;
356 +
357 + struct rrd_dimension *next;
358 +};
359 +typedef struct rrd_dimension RRD_DIMENSION;
360 +
361 +struct rrd_stats {
362 + pthread_mutex_t mutex;
363 +
364 + char id[RRD_STATS_NAME_MAX + 1];
365 + char name[RRD_STATS_NAME_MAX + 1];
366 + char title[RRD_STATS_NAME_MAX + 1];
367 + char vtitle[RRD_STATS_NAME_MAX + 1];
368 +
369 + char usertitle[RRD_STATS_NAME_MAX + 1];
370 + char userpriority[RRD_STATS_NAME_MAX + 1];
371 +
372 + char envtitle[RRD_STATS_NAME_MAX + 1];
373 + char envpriority[RRD_STATS_NAME_MAX + 1];
374 +
375 + char type[RRD_STATS_NAME_MAX + 1];
376 +
377 + char hostname[RRD_STATS_NAME_MAX + 1];
378 +
379 + size_t entries;
380 + size_t current_entry;
381 + // size_t last_entry;
382 +
383 + time_t last_updated;
384 +
385 + struct timeval *times;
386 + RRD_DIMENSION *dimensions;
387 + struct rrd_stats *next;
388 +};
389 +typedef struct rrd_stats RRD_STATS;
390 +
391 +RRD_STATS *root = NULL;
392 +pthread_mutex_t root_mutex = PTHREAD_MUTEX_INITIALIZER;
393 +
394 +RRD_STATS *rrd_stats_create(const char *id, const char *name, unsigned long entries, const char *title, const char *vtitle, const char *type)
395 +{
396 + RRD_STATS *st = NULL;
397 + char *p;
398 +
399 + debug(D_RRD_STATS, "Creating RRD_STATS for '%s'.", name);
400 +
401 + st = calloc(1, sizeof(RRD_STATS));
402 + if(!st) return NULL;
403 +
404 + st->times = calloc(entries, sizeof(struct timeval));
405 + if(!st->times) {
406 + error("Cannot allocate %lu entries of %lu bytes each for RRD_STATS.", st->entries, sizeof(struct timeval));
407 + free(st);
408 + return NULL;
409 + }
410 +
411 + strncpy(st->id, id, RRD_STATS_NAME_MAX);
412 + st->id[RRD_STATS_NAME_MAX] = '\0';
413 +
414 + strncpy(st->name, name, RRD_STATS_NAME_MAX);
415 + st->name[RRD_STATS_NAME_MAX] = '\0';
416 + while((p = strchr(st->name, '/'))) *p = '_';
417 + while((p = strchr(st->name, '?'))) *p = '_';
418 + while((p = strchr(st->name, '&'))) *p = '_';
419 +
420 + strncpy(st->title, title, RRD_STATS_NAME_MAX);
421 + st->title[RRD_STATS_NAME_MAX] = '\0';
422 +
423 + strncpy(st->vtitle, vtitle, RRD_STATS_NAME_MAX);
424 + st->vtitle[RRD_STATS_NAME_MAX] = '\0';
425 +
426 + strncpy(st->type, type, RRD_STATS_NAME_MAX);
427 + st->type[RRD_STATS_NAME_MAX] = '\0';
428 +
429 + // check if there is a name for it in the environment
430 + sprintf(st->envtitle, "NETDATA_TITLE_%s", st->id);
431 + while((p = strchr(st->envtitle, '/'))) *p = '_';
432 + while((p = strchr(st->envtitle, '.'))) *p = '_';
433 + while((p = strchr(st->envtitle, '-'))) *p = '_';
434 + p = getenv(st->envtitle);
435 +
436 + if(p) strncpy(st->usertitle, p, RRD_STATS_NAME_MAX);
437 + else strncpy(st->usertitle, st->name, RRD_STATS_NAME_MAX);
438 + st->usertitle[RRD_STATS_NAME_MAX] = '\0';
439 +
440 + sprintf(st->envpriority, "NETDATA_PRIORITY_%s", st->id);
441 + while((p = strchr(st->envpriority, '/'))) *p = '_';
442 + while((p = strchr(st->envpriority, '.'))) *p = '_';
443 + while((p = strchr(st->envpriority, '-'))) *p = '_';
444 + p = getenv(st->envpriority);
445 +
446 + if(p) strncpy(st->userpriority, p, RRD_STATS_NAME_MAX);
447 + else strncpy(st->userpriority, st->name, RRD_STATS_NAME_MAX);
448 + st->userpriority[RRD_STATS_NAME_MAX] = '\0';
449 +
450 + if(gethostname(st->hostname, RRD_STATS_NAME_MAX) == -1)
451 + error("Cannot get hostname.");
452 +
453 + st->entries = entries;
454 + st->current_entry = 0;
455 + // st->last_entry = 0;
456 + st->dimensions = NULL;
457 + st->last_updated = time(NULL);
458 +
459 + pthread_mutex_init(&st->mutex, NULL);
460 + pthread_mutex_lock(&st->mutex);
461 + pthread_mutex_lock(&root_mutex);
462 +
463 + st->next = root;
464 + root = st;
465 +
466 + pthread_mutex_unlock(&root_mutex);
467 + // leave st->mutex locked
468 +
469 + return(st);
470 +}
471 +
472 +RRD_DIMENSION *rrd_stats_dimension_add(RRD_STATS *st, const char *id, const char *name, size_t bytes, long multiplier, long divisor, int type)
473 +{
474 + RRD_DIMENSION *rd = NULL;
475 +
476 + debug(D_RRD_STATS, "Adding dimension '%s' (%s) to RRD_STATS '%s' (%s).", name, id, st->name, st->id);
477 +
478 + rd = calloc(1, sizeof(RRD_DIMENSION));
479 + if(!rd) return NULL;
480 +
481 + rd->bytes = bytes;
482 + rd->entries = st->entries;
483 + rd->multiplier = multiplier;
484 + rd->divisor = divisor;
485 + rd->type = type;
486 + rd->values = calloc(rd->entries, rd->bytes);
487 + if(!rd->values) {
488 + error("Cannot allocate %lu entries of %lu bytes each for RRD_DIMENSION.", rd->entries, rd->bytes);
489 + free(rd);
490 + return NULL;
491 + }
492 +
493 + strncpy(rd->id, id, RRD_STATS_NAME_MAX);
494 + rd->id[RRD_STATS_NAME_MAX] = '\0';
495 +
496 + strncpy(rd->name, name, RRD_STATS_NAME_MAX);
497 + rd->name[RRD_STATS_NAME_MAX] = '\0';
498 +
499 + rd->next = st->dimensions;
500 + st->dimensions = rd;
501 +
502 + return(rd);
503 +}
504 +
505 +void rrd_stats_dimension_free(RRD_DIMENSION *rd)
506 +{
507 + if(rd->next) rrd_stats_dimension_free(rd->next);
508 + debug(D_RRD_STATS, "Removing dimension '%s'.", rd->name);
509 + free(rd->values);
510 + free(rd);
511 +}
512 +
513 +RRD_STATS *rrd_stats_find(const char *id)
514 +{
515 + pthread_mutex_lock(&root_mutex);
516 +
517 + RRD_STATS *st = root;
518 +
519 + for ( ; st ; st = st->next ) {
520 + if(strcmp(st->id, id) == 0) break;
521 + }
522 +
523 + pthread_mutex_unlock(&root_mutex);
524 + return(st);
525 +}
526 +
527 +RRD_STATS *rrd_stats_find_byname(const char *name)
528 +{
529 + pthread_mutex_lock(&root_mutex);
530 +
531 + RRD_STATS *st = root;
532 +
533 + for ( ; st ; st = st->next ) {
534 + if(strcmp(st->name, name) == 0) break;
535 + }
536 +
537 + pthread_mutex_unlock(&root_mutex);
538 + return(st);
539 +}
540 +
541 +RRD_DIMENSION *rrd_stats_dimension_find(RRD_STATS *st, const char *id)
542 +{
543 + RRD_DIMENSION *rd = st->dimensions;
544 +
545 + for ( ; rd ; rd = rd->next ) {
546 + if(strcmp(rd->id, id) == 0) break;
547 + }
548 +
549 + return(rd);
550 +}
551 +
552 +void rrd_stats_next(RRD_STATS *st)
553 +{
554 + struct timeval *now;
555 +
556 + pthread_mutex_lock(&st->mutex);
557 +
558 + // st->current_entry should never be outside the array
559 + // or, the parallel threads may end up crashing
560 + // st->last_entry = st->current_entry;
561 + st->current_entry = ((st->current_entry + 1) >= st->entries) ? 0 : st->current_entry + 1;
562 +
563 + now = &st->times[st->current_entry];
564 + gettimeofday(now, NULL);
565 +
566 + st->last_updated = now->tv_sec;
567 +
568 + // leave mutex locked
569 +}
570 +
571 +void rrd_stats_done(RRD_STATS *st)
572 +{
573 + RRD_DIMENSION *rd, *last;
574 +
575 + // find if there are any obsolete dimensions (not updated recently)
576 + for( rd = st->dimensions, last = NULL ; rd ; ) {
577 + if((rd->last_updated + (10 * update_every)) < st->last_updated) { // remove it only it is not updated in 10 seconds
578 + debug(D_RRD_STATS, "Removing obsolete dimension '%s' (%s) of '%s' (%s).", rd->name, rd->id, st->name, st->id);
579 +
580 + if(!last) {
581 + st->dimensions = rd->next;
582 + rd->next = NULL;
583 + rrd_stats_dimension_free(rd);
584 + rd = st->dimensions;
585 + continue;
586 + }
587 + else {
588 + last->next = rd->next;
589 + rd->next = NULL;
590 + rrd_stats_dimension_free(rd);
591 + rd = last->next;
592 + continue;
593 + }
594 + }
595 +
596 + last = rd;
597 + rd = rd->next;
598 + }
599 +
600 + pthread_mutex_unlock(&st->mutex);
601 +}
602 +
603 +int rrd_stats_dimension_hide(RRD_STATS *st, const char *id)
604 +{
605 + RRD_DIMENSION *rd = rrd_stats_dimension_find(st, id);
606 + if(!rd) {
607 + error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id);
608 + return 1;
609 + }
610 +
611 + rd->hidden = 1;
612 + return 0;
613 +}
614 +
615 +
616 +int rrd_stats_dimension_set(RRD_STATS *st, const char *id, void *data)
617 +{
618 + RRD_DIMENSION *rd = rrd_stats_dimension_find(st, id);
619 + if(!rd) {
620 + error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id);
621 + return 1;
622 + }
623 +
624 + rd->last_updated = st->last_updated;
625 +
626 + if(rd->bytes == sizeof(long long)) {
627 + long long *dimension = rd->values, *value = data;
628 +
629 + dimension[st->current_entry] = (*value);
630 + }
631 + else if(rd->bytes == sizeof(long)) {
632 + long *dimension = rd->values, *value = data;
633 +
634 + dimension[st->current_entry] = (*value);
635 + }
636 + else if(rd->bytes == sizeof(int)) {
637 + int *dimension = rd->values, *value = data;
638 +
639 + dimension[st->current_entry] = (*value);
640 + }
641 + else if(rd->bytes == sizeof(char)) {
642 + char *dimension = rd->values, *value = data;
643 +
644 + dimension[st->current_entry] = (*value);
645 + }
646 + else fatal("I don't know how to handle data of length %d bytes.", rd->bytes);
647 +
648 + return 0;
649 +}
650 +
651 +#define GROUP_AVERAGE 0
652 +#define GROUP_MAX 1
653 +
654 +size_t rrd_stats_one_json(RRD_STATS *st, char *options, char *buffer, size_t len)
655 +{
656 + size_t i = 0;
657 + if(i + 200 > len) return(0);
658 +
659 + i += sprintf(&buffer[i], "\t\t{\n");
660 + i += sprintf(&buffer[i], "\t\t\t\"id\" : \"%s\",\n", st->id);
661 + i += sprintf(&buffer[i], "\t\t\t\"name\" : \"%s\",\n", st->name);
662 + i += sprintf(&buffer[i], "\t\t\t\"type\" : \"%s\",\n", st->type);
663 + i += sprintf(&buffer[i], "\t\t\t\"title\" : \"%s %s\",\n", st->title, st->name);
664 + i += sprintf(&buffer[i], "\t\t\t\"usertitle\" : \"%s %s (%s)\",\n", st->title, st->usertitle, st->id);
665 + i += sprintf(&buffer[i], "\t\t\t\"userpriority\" : \"%s\",\n", st->userpriority);
666 + i += sprintf(&buffer[i], "\t\t\t\"envtitle\" : \"%s\",\n", st->envtitle);
667 + i += sprintf(&buffer[i], "\t\t\t\"envpriority\" : \"%s\",\n", st->envpriority);
668 + i += sprintf(&buffer[i], "\t\t\t\"hostname\" : \"%s\",\n", st->hostname);
669 + i += sprintf(&buffer[i], "\t\t\t\"vtitle\" : \"%s\",\n", st->vtitle);
670 + i += sprintf(&buffer[i], "\t\t\t\"url\" : \"/data/%s/%s\",\n", st->name, options?options:"");
671 + i += sprintf(&buffer[i], "\t\t\t\"entries\" : %ld,\n", st->entries);
672 + i += sprintf(&buffer[i], "\t\t\t\"current\" : %ld,\n", st->current_entry);
673 + i += sprintf(&buffer[i], "\t\t\t\"update_every\" : %d,\n", update_every);
674 + i += sprintf(&buffer[i], "\t\t\t\"last_updated\" : %lu,\n", st->last_updated);
675 + i += sprintf(&buffer[i], "\t\t\t\"last_updated_secs_ago\" : %lu\n", time(NULL) - st->last_updated);
676 + i += sprintf(&buffer[i], "\t\t}");
677 +
678 + return(i);
679 +}
680 +
681 +#define RRD_GRAPH_JSON_HEADER "{\n\t\"charts\": [\n"
682 +#define RRD_GRAPH_JSON_FOOTER "\n\t]\n}\n"
683 +
684 +size_t rrd_stats_graph_json(RRD_STATS *st, char * options, char *buffer, size_t len)
685 +{
686 + size_t i = 0;
687 + i += sprintf(&buffer[i], RRD_GRAPH_JSON_HEADER);
688 + i += rrd_stats_one_json(st, options, &buffer[i], len - i);
689 + i += sprintf(&buffer[i], RRD_GRAPH_JSON_FOOTER);
690 + return(i);
691 +}
692 +
693 +size_t rrd_stats_all_json(char *buffer, size_t len)
694 +{
695 + size_t i = 0, c;
696 + RRD_STATS *st;
697 +
698 + i += sprintf(&buffer[i], RRD_GRAPH_JSON_HEADER);
699 +
700 + for(st = root, c = 0; st ; st = st->next, c++) {
701 + if(c) i += sprintf(&buffer[i], "%s", ",\n");
702 + i += rrd_stats_one_json(st, NULL, &buffer[i], len - i);
703 + }
704 +
705 + i += sprintf(&buffer[i], RRD_GRAPH_JSON_FOOTER);
706 + return(i);
707 +}
708 +
709 +long long rrd_stats_dimension_get(RRD_DIMENSION *rd, size_t position)
710 +{
711 + if(rd->bytes == sizeof(long long)) {
712 + long long *dimension = rd->values;
713 + return(dimension[position]);
714 + }
715 + else if(rd->bytes == sizeof(long)) {
716 + long *dimension = rd->values;
717 + return(dimension[position]);
718 + }
719 + else if(rd->bytes == sizeof(int)) {
720 + int *dimension = rd->values;
721 + return(dimension[position]);
722 + }
723 + else if(rd->bytes == sizeof(char)) {
724 + char *dimension = rd->values;
725 + return(dimension[position]);
726 + }
727 + else fatal("Cannot produce JSON for size %d bytes dimension.", rd->bytes);
728 +
729 + return(0);
730 +}
731 +
732 +size_t rrd_stats_json(RRD_STATS *st, char *b, size_t length, size_t entries_to_show, size_t group_count, int group_method)
733 +{
734 + pthread_mutex_lock(&st->mutex);
735 +
736 + // check the options
737 + if(entries_to_show <= 0) entries_to_show = 1;
738 + if(group_count <= 0) group_count = 1;
739 + if(group_count > st->entries / 20) group_count = st->entries / 20;
740 +
741 + size_t i = 0; // the bytes of JSON output we have generated so far
742 + size_t printed = 0; // the lines of JSON data we have generated so far
743 +
744 + size_t current_entry = st->current_entry;
745 + size_t t, lt; // t = the current entry, lt = the lest entry of data
746 + long count = st->entries; // count down of the entries examined so far
747 + long pad = 0; // align the entries when grouping values together
748 +
749 + RRD_DIMENSION *rd;
750 + size_t c = 0; // counter for dimension loops
751 + size_t dimensions = 0; // the total number of dimensions present
752 +
753 + unsigned long long usec = 0;// usec between the entries
754 + char dtm[201]; // temp variable for storing dates
755 +
756 + int we_need_totals = 0; // if set, we should calculate totals for all dimensions
757 +
758 + // find how many dimensions we have
759 + for( rd = st->dimensions ; rd ; rd = rd->next) {
760 + dimensions++;
761 + if(rd->type == RRD_DIMENSION_PCENT_OVER_TOTAL) we_need_totals++;
762 + }
763 +
764 + if(!dimensions) {
765 + pthread_mutex_unlock(&st->mutex);
766 + return sprintf(b, "No dimensions yet.");
767 + }
768 +
769 + // temporary storage to keep track of group values and counts
770 + long double group_values[dimensions];
771 + int group_counts[dimensions];
772 + for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++)
773 + group_values[c] = group_counts[c] = 0;
774 +
775 + i += sprintf(&b[i], "{\n \"cols\":\n [\n");
776 + i += sprintf(&b[i], " {\"id\":\"\",\"label\":\"time\",\"pattern\":\"\",\"type\":\"datetime\"}");
777 +
778 + for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++)
779 + if(!rd->hidden)
780 + i += sprintf(&b[i], ",\n {\"id\":\"\",\"label\":\"%s\",\"pattern\":\"\",\"type\":\"number\"}", rd->name);
781 +
782 + i += sprintf(&b[i], " ],\n \"rows\":\n [\n");
783 +
784 + // to allow grouping on the same values, we need a pad
785 + pad = current_entry % group_count;
786 +
787 + // make sure current_entry is within limits
788 + if(current_entry < 0 || current_entry >= st->entries) current_entry = 0;
789 +
790 + // find the old entry of the round-robin
791 + t = current_entry + 1;
792 + if(t >= st->entries) t = 0;
793 + lt = t;
794 +
795 + // find the current entry
796 + t++;
797 + if(t >= st->entries) t = 0;
798 +
799 + // the loop in dimension data
800 + count -= 2;
801 + for ( ; t != current_entry && count >= 0 ; lt = t++, count--) {
802 + if(t >= st->entries) t = 0;
803 +
804 + // if the last is empty, loop again
805 + if(!st->times[lt].tv_sec) continue;
806 +
807 + // check if we may exceed the buffer provided
808 + if((length - i) < 1024) break;
809 +
810 + // prefer the most recent last entries
811 + if(((count-pad) / group_count) > entries_to_show) continue;
812 +
813 +
814 + // ok. we will use this entry!
815 + // find how much usec since the previous entry
816 +
817 + usec = usecdiff(&st->times[t], &st->times[lt]);
818 +
819 + if(((count-pad) % group_count) == 0) {
820 + if(printed >= entries_to_show) break;
821 +
822 + // generate the local date time
823 + struct tm *tm = localtime(&st->times[t].tv_sec);
824 + if(!tm) { error("localtime() failed."); continue; }
825 +
826 + sprintf(dtm, "\"Date(%d, %d, %d, %d, %d, %d, %d)\"", tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(st->times[t].tv_usec / 1000)); // datetime
827 + // strftime(dtm, 200, "\"Date(%Y, %m, %d, %H, %M, %S)\"", tm); // datetime
828 + // strftime(dtm, 200, "[%H, %M, %S, 0]", tm); // timeofday
829 + i += sprintf(&b[i], "%s {\"c\":[{\"v\":%s}", printed?"]},\n":"", dtm);
830 +
831 + printed++;
832 + }
833 +
834 + // if we need a PCENT_OVER_TOTAL, calculate the totals for the current and the last
835 + long double total = 0, oldtotal = 0;
836 + if(we_need_totals) {
837 + for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
838 + total += (long double)rrd_stats_dimension_get(rd, t);
839 + oldtotal += (long double)rrd_stats_dimension_get(rd, lt);
840 + }
841 + }
842 +
843 + for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) {
844 + //long long oldvalue, value; // temp variable for storing data values
845 + long double oldvalue, value; // temp variable for storing data values
846 +
847 + value = (long double)rrd_stats_dimension_get(rd, t);
848 + oldvalue = (long double)rrd_stats_dimension_get(rd, lt);
849 +
850 + switch(rd->type) {
851 + case RRD_DIMENSION_PCENT_OVER_TOTAL:
852 + value = (long double)100 * (value - oldvalue) / (total - oldtotal);
853 + break;
854 +
855 + case RRD_DIMENSION_INCREMENTAL:
856 + if(oldvalue > value) value = 0; // detect overflows and resets
857 + else value -= oldvalue;
858 + value = value * (long double)1000000L / (long double)usec;
859 + break;
860 +
861 + default:
862 + break;
863 + }
864 +
865 + value = value * (long double)rd->multiplier / (long double)rd->divisor;
866 +
867 + group_counts[c]++;
868 + switch(group_method) {
869 + case GROUP_MAX:
870 + if(abs(value) > abs(group_values[c])) group_values[c] = value;
871 + break;
872 +
873 + default:
874 + case GROUP_AVERAGE:
875 + group_values[c] += value;
876 + if(((count - pad) % group_count) == 0) group_values[c] /= (long double)group_counts[c];
877 + break;
878 + }
879 +
880 + if(((count-pad) % group_count) == 0) {
881 + if(!rd->hidden)
882 + i += sprintf(&b[i], ",{\"v\":%0.1Lf}", group_values[c]);
883 + //i += sprintf(&b[i], ",{\"v\":%lld}", group_values[c]);
884 +
885 + group_values[c] = group_counts[c] = 0;
886 + }
887 + }
888 + }
889 + if(printed) i += sprintf(&b[i], "]}");
890 + i += sprintf(&b[i], "\n ]\n}\n");
891 +
892 + pthread_mutex_unlock(&st->mutex);
893 + return(i);
894 +}
895 +
896 +// ----------------------------------------------------------------------------
897 +// listener (web server)
898 +
899 +struct web_buffer {
900 + size_t size; // allocation size of buffer
901 + size_t bytes; // current data length in buffer
902 + size_t sent; // current data length sent to output
903 + char *buffer; // the buffer
904 + int contenttype;
905 + size_t rbytes; // if non-zero, the excepted size of ifd
906 + time_t date; // the date this content has been generated
907 +};
908 +
909 +struct web_buffer *web_buffer_create(size_t size)
910 +{
911 + struct web_buffer *b;
912 +
913 + debug(D_WEB_BUFFER, "Creating new web buffer of size %d.", size);
914 +
915 + b = calloc(1, sizeof(struct web_buffer));
916 + if(!b) {
917 + error("Cannot allocate a web_buffer.");
918 + return NULL;
919 + }
920 +
921 + b->buffer = calloc(1, size);
922 + if(!b->buffer) {
923 + error("Cannot allocate a buffer of size %u.", size);
924 + free(b);
925 + return NULL;
926 + }
927 + b->size = size;
928 + b->contenttype = CT_TEXT_PLAIN;
929 + return(b);
930 +}
931 +
932 +void web_buffer_free(struct web_buffer *b)
933 +{
934 + debug(D_WEB_BUFFER, "Freeing web buffer of size %d.", b->size);
935 +
936 + if(b->buffer) free(b->buffer);
937 + free(b);
938 +}
939 +
940 +void web_buffer_increase(struct web_buffer *b, size_t free_size_required)
941 +{
942 + size_t left = b->size - b->bytes;
943 +
944 + if(left >= free_size_required) return;
945 + size_t increase = free_size_required - left;
946 +
947 + debug(D_WEB_BUFFER, "Increasing data buffer from size %d to %d.", b->size, b->size + increase);
948 +
949 + b->buffer = realloc(b->buffer, b->size + free_size_required - left);
950 + if(!b->buffer) fatal("Failed to increase data buffer from size %d to %d.", b->size, b->size + increase);
951 +
952 + b->size += increase;
953 +}
954 +
955 +#define WEB_CLIENT_MODE_NORMAL 0
956 +#define WEB_CLIENT_MODE_FILECOPY 1
957 +
958 +struct web_client {
959 + unsigned long long id;
960 + char client_ip[101];
961 +
962 + int mode;
963 + int keepalive;
964 +
965 + struct sockaddr_in clientaddr;
966 +
967 + pthread_t thread; // the thread servicing this client
968 + int obsolete; // if set to 1, the listener will remove this client
969 +
970 + int ifd;
971 + int ofd;
972 +
973 + struct web_buffer *data;
974 +
975 + int zoutput; // if set to 1, web_client_send() will send compressed data
976 + z_stream zstream; // zlib stream for sending compressed output to client
977 + Bytef zbuffer[ZLIB_CHUNK]; // temporary buffer for storing compressed output
978 + size_t zsent; // the compressed bytes we have sent to the client
979 + size_t zhave; // the compressed bytes that we have to send
980 + int zinitialized;
981 +
982 + int wait_receive;
983 + int wait_send;
984 +
985 + char response_header[MAX_HTTP_HEADER_SIZE+1];
986 +
987 + struct web_client *prev;
988 + struct web_client *next;
989 +} *web_clients = NULL;
990 +
991 +unsigned long long web_clients_count = 0;
992 +
993 +struct web_client *web_client_create(int listener)
994 +{
995 + struct web_client *w;
996 + socklen_t addrlen;
997 +
998 + w = calloc(1, sizeof(struct web_client));
999 + if(!w) {
1000 + error("Cannot allocate new web_client memory.");
1001 + return NULL;
1002 + }
1003 +
1004 + w->id = ++web_clients_count;
1005 + w->mode = WEB_CLIENT_MODE_NORMAL;
1006 +
1007 + addrlen = sizeof(w->clientaddr);
1008 + w->ifd = accept(listener, (struct sockaddr *)&w->clientaddr, &addrlen);
1009 + if (w->ifd == -1) {
1010 + error("%llu: Cannot accept new incoming connection.", w->id);
1011 + free(w);
1012 + return NULL;
1013 + }
1014 + w->ofd = w->ifd;
1015 +
1016 + strncpy(w->client_ip, inet_ntoa(w->clientaddr.sin_addr), 100);
1017 + w->client_ip[100] = '\0';
1018 +
1019 + syslog(LOG_NOTICE, "%llu: New client from %s.", w->id, w->client_ip);
1020 + debug(D_WEB_CLIENT_ACCESS, "%llu: New web client from %s on socket %d.", w->id, w->client_ip, w->ifd);
1021 +
1022 + {
1023 + int flag = 1;
1024 + if(setsockopt(w->ifd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int)) != 0) error("%llu: Cannot set SO_KEEPALIVE on socket.", w->id);
1025 + }
1026 +
1027 + w->data = web_buffer_create(MAX_SOCKET_DATA_LENGTH);
1028 + if(!w->data) {
1029 + close(w->ifd);
1030 + free(w);
1031 + return NULL;
1032 + }
1033 +
1034 + w->wait_receive = 1;
1035 +
1036 + if(web_clients) web_clients->prev = w;
1037 + w->next = web_clients;
1038 + web_clients = w;
1039 +
1040 + return(w);
1041 +}
1042 +
1043 +struct web_client *web_client_free(struct web_client *w)
1044 +{
1045 + struct web_client *n = w->next;
1046 +
1047 + debug(D_WEB_CLIENT_ACCESS, "%llu: Closing web client from %s.", w->id, inet_ntoa(w->clientaddr.sin_addr));
1048 +
1049 + if(w->prev) w->prev->next = w->next;
1050 + if(w->next) w->next->prev = w->prev;
1051 +
1052 + if(w == web_clients) web_clients = w->next;
1053 +
1054 + if(w->data) web_buffer_free(w->data);
1055 + close(w->ifd);
1056 + if(w->ofd != w->ifd) close(w->ofd);
1057 + free(w);
1058 +
1059 + return(n);
1060 +}
1061 +
1062 +int mysendfile(struct web_client *w, char *filename)
1063 +{
1064 + debug(D_WEB_CLIENT, "%llu: Looking for file '%s'...", w->id, filename);
1065 +
1066 + // skip leading slashes
1067 + while (filename[0] == '/') filename = &filename[1];
1068 +
1069 + // if the filename contain known paths, skip them
1070 + if(strncmp(filename, WEB_PATH_DATA "/", strlen(WEB_PATH_DATA) + 1) == 0) filename = &filename[strlen(WEB_PATH_DATA) + 1];
1071 + else if(strncmp(filename, WEB_PATH_GRAPH "/", strlen(WEB_PATH_GRAPH) + 1) == 0) filename = &filename[strlen(WEB_PATH_GRAPH) + 1];
1072 + else if(strncmp(filename, WEB_PATH_FILE "/", strlen(WEB_PATH_FILE) + 1) == 0) filename = &filename[strlen(WEB_PATH_FILE) + 1];
1073 +
1074 + // if the filename contains a / or a .., refuse to serve it
1075 + if(strstr(filename, "/") != 0 || strstr(filename, "..") != 0) {
1076 + debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename);
1077 + w->data->bytes = sprintf(w->data->buffer, "File '%s' cannot be served. Filenames cannot contain / or ..", filename);
1078 + return 400;
1079 + }
1080 +
1081 + // access the file in web/*
1082 + char webfilename[FILENAME_MAX + 1];
1083 + strcpy(webfilename, "web/");
1084 + strncpy(&webfilename[4], filename, FILENAME_MAX - 4);
1085 + webfilename[FILENAME_MAX] = '\0';
1086 +
1087 + // check if the file exists
1088 + struct stat stat;
1089 + if(lstat(webfilename, &stat) != 0) {
1090 + debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not found.", w->id, filename);
1091 + w->data->bytes = sprintf(w->data->buffer, "File '%s' does not exist, or is not accessible.", filename);
1092 + return 404;
1093 + }
1094 +
1095 + // check if the file is owned by us
1096 + if(stat.st_uid != getuid() && stat.st_uid != geteuid()) {
1097 + error("%llu: File '%s' is owned by user %d (I run as user %d). Access Denied.", w->id, filename, stat.st_uid, getuid());
1098 + w->data->bytes = sprintf(w->data->buffer, "Access to file '%s' is not permitted.", filename);
1099 + return 403;
1100 + }
1101 +
1102 + // open the file
1103 + w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY);
1104 + if(w->ifd == -1) {
1105 + w->ifd = w->ofd;
1106 +
1107 + if(errno == EBUSY || errno == EAGAIN) {
1108 + error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, filename);
1109 + sprintf(w->response_header, "Location: /" WEB_PATH_FILE "/%s\r\n", filename);
1110 + w->data->bytes = sprintf(w->data->buffer, "The file '%s' is currently busy. Please try again later.", filename);
1111 + return 307;
1112 + }
1113 + else {
1114 + error("%llu: Cannot open file '%s'.", w->id, filename);
1115 + w->data->bytes = sprintf(w->data->buffer, "Cannot open file '%s'.", filename);
1116 + return 404;
1117 + }
1118 + }
1119 +
1120 + syslog(LOG_NOTICE, "%llu: Sending file '%s' to client %s.", w->id, filename, w->client_ip);
1121 +
1122 + // pick a Content-Type for the file
1123 + if(strstr(filename, ".html") != NULL) w->data->contenttype = CT_TEXT_HTML;
1124 + else if(strstr(filename, ".js") != NULL) w->data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
1125 + else if(strstr(filename, ".css") != NULL) w->data->contenttype = CT_TEXT_CSS;
1126 + else if(strstr(filename, ".xml") != NULL) w->data->contenttype = CT_TEXT_XML;
1127 + else if(strstr(filename, ".xsl") != NULL) w->data->contenttype = CT_TEXT_XSL;
1128 + else if(strstr(filename, ".txt") != NULL) w->data->contenttype = CT_TEXT_PLAIN;
1129 + else if(strstr(filename, ".svg") != NULL) w->data->contenttype = CT_IMAGE_SVG_XML;
1130 + else if(strstr(filename, ".ttf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_TRUETYPE;
1131 + else if(strstr(filename, ".otf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_OPENTYPE;
1132 + else if(strstr(filename, ".woff") != NULL) w->data->contenttype = CT_APPLICATION_FONT_WOFF;
1133 + else if(strstr(filename, ".eot") != NULL) w->data->contenttype = CT_APPLICATION_VND_MS_FONTOBJ;
1134 + else w->data->contenttype = CT_APPLICATION_OCTET_STREAM;
1135 +
1136 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, filename, stat.st_size, w->ifd, w->ofd);
1137 +
1138 + w->mode = WEB_CLIENT_MODE_FILECOPY;
1139 + w->wait_receive = 1;
1140 + w->wait_send = 0;
1141 + w->data->bytes = 0;
1142 + w->data->buffer[0] = '\0';
1143 + w->data->rbytes = stat.st_size;
1144 + w->data->date = stat.st_mtim.tv_sec;
1145 +
1146 + return 200;
1147 +}
1148 +
1149 +char *mystrsep(char **ptr, char *s)
1150 +{
1151 + char *p = "";
1152 + while ( !p[0] && *ptr ) p = strsep(ptr, s);
1153 + return(p);
1154 +}
1155 +
1156 +void web_client_reset(struct web_client *w)
1157 +{
1158 + debug(D_WEB_CLIENT, "%llu: Reseting client.", w->id);
1159 +
1160 + if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1161 + debug(D_WEB_CLIENT, "%llu: Closing filecopy input file.", w->id);
1162 + close(w->ifd);
1163 + w->ifd = w->ofd;
1164 + }
1165 +
1166 + w->data->contenttype = CT_TEXT_PLAIN;
1167 + w->mode = WEB_CLIENT_MODE_NORMAL;
1168 +
1169 + w->data->rbytes = 0;
1170 + w->data->bytes = 0;
1171 + w->data->sent = 0;
1172 +
1173 + w->response_header[0] = '\0';
1174 + w->data->buffer[0] = '\0';
1175 +
1176 + w->wait_receive = 1;
1177 + w->wait_send = 0;
1178 +
1179 + // if we had enabled compression, release it
1180 + if(w->zinitialized) {
1181 + debug(D_DEFLATE, "%llu: Reseting compression.", w->id);
1182 + deflateEnd(&w->zstream);
1183 + w->zoutput = 0;
1184 + w->zsent = 0;
1185 + w->zhave = 0;
1186 + w->zstream.avail_in = 0;
1187 + w->zstream.avail_out = 0;
1188 + w->zstream.total_in = 0;
1189 + w->zstream.total_out = 0;
1190 + w->zinitialized = 0;
1191 + }
1192 +}
1193 +
1194 +void web_client_enable_deflate(struct web_client *w) {
1195 + if(w->zinitialized == 1) {
1196 + error("%llu: Compression has already be initialized for this client.", w->id);
1197 + return;
1198 + }
1199 +
1200 + if(w->data->sent) {
1201 + error("%llu: Cannot enable compression in the middle of a conversation.", w->id);
1202 + return;
1203 + }
1204 +
1205 + w->zstream.zalloc = Z_NULL;
1206 + w->zstream.zfree = Z_NULL;
1207 + w->zstream.opaque = Z_NULL;
1208 +
1209 + w->zstream.next_in = (Bytef *)w->data->buffer;
1210 + w->zstream.avail_in = 0;
1211 + w->zstream.total_in = 0;
1212 +
1213 + w->zstream.next_out = w->zbuffer;
1214 + w->zstream.avail_out = 0;
1215 + w->zstream.total_out = 0;
1216 +
1217 + w->zstream.zalloc = Z_NULL;
1218 + w->zstream.zfree = Z_NULL;
1219 + w->zstream.opaque = Z_NULL;
1220 +
1221 +// if(deflateInit(&w->zstream, Z_DEFAULT_COMPRESSION) != Z_OK) {
1222 +// error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
1223 +// return;
1224 +// }
1225 +
1226 + // Select GZIP compression: windowbits = 15 + 16 = 31
1227 + if(deflateInit2(&w->zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
1228 + error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
1229 + return;
1230 + }
1231 +
1232 + w->zsent = 0;
1233 + w->zoutput = 1;
1234 + w->zinitialized = 1;
1235 +
1236 + debug(D_DEFLATE, "%llu: Initialized compression.", w->id);
1237 +}
1238 +
1239 +void web_client_process(struct web_client *w)
1240 +{
1241 + int code = 500;
1242 + int bytes;
1243 +
1244 + w->wait_receive = 0;
1245 +
1246 + // check if we have an empty line (end of HTTP header)
1247 + if(strstr(w->data->buffer, "\r\n\r\n")) {
1248 + debug(D_WEB_DATA, "%llu: Processing data buffer of %d bytes: '%s'.", w->id, w->data->bytes, w->data->buffer);
1249 +
1250 + // check if the client requested keep-alive HTTP
1251 + if(strcasestr(w->data->buffer, "Connection: keep-alive")) w->keepalive = 1;
1252 + else w->keepalive = 0;
1253 +
1254 + // check if the client accepts deflate
1255 + if(strstr(w->data->buffer, "gzip"))
1256 + web_client_enable_deflate(w);
1257 +
1258 + char *buf = w->data->buffer;
1259 + char *tok = strsep(&buf, " \r\n");
1260 + char *url = NULL;
1261 +
1262 + if(buf && strcmp(tok, "GET") == 0) {
1263 + tok = strsep(&buf, " \r\n");
1264 + url = url_decode(tok);
1265 + debug(D_WEB_CLIENT, "%llu: Processing HTTP GET on url '%s'.", w->id, url);
1266 + }
1267 + else if (buf && strcmp(tok, "POST") == 0) {
1268 + w->keepalive = 0;
1269 + tok = strsep(&buf, " \r\n");
1270 + url = url_decode(tok);
1271 +
1272 + debug(D_WEB_CLIENT, "%llu: I don't know how to handle POST with form data. Assuming it is a GET on url '%s'.", w->id, url);
1273 + }
1274 +
1275 + if(url) {
1276 + tok = mystrsep(&url, "/?&");
1277 +
1278 + debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
1279 +
1280 + if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
1281 + // the client is requesting rrd data
1282 +
1283 + // get the name of the data to show
1284 + tok = mystrsep(&url, "/?&");
1285 + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1286 +
1287 + // do we have such a data set?
1288 + RRD_STATS *st = rrd_stats_find_byname(tok);
1289 + if(!st) st = rrd_stats_find(tok);
1290 + if(!st) {
1291 + // we don't have it
1292 + // try to send a file with that name
1293 + code = mysendfile(w, tok);
1294 + }
1295 + else {
1296 + // we have it
1297 + debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok);
1298 +
1299 + // how many entries does the client want?
1300 + size_t lines = save_history;
1301 + size_t group_count = 1;
1302 + int group_method = GROUP_AVERAGE;
1303 +
1304 + if(url) {
1305 + // parse the lines required
1306 + tok = mystrsep(&url, "/?&");
1307 + if(tok) lines = atoi(tok);
1308 + if(lines < 5) lines = save_history;
1309 + }
1310 + if(url) {
1311 + // parse the group count required
1312 + tok = mystrsep(&url, "/?&");
1313 + if(tok) group_count = atoi(tok);
1314 + if(group_count < 1) group_count = 1;
1315 + if(group_count > save_history / 20) group_count = save_history / 20;
1316 + }
1317 + if(url) {
1318 + // parse the grouping method required
1319 + tok = mystrsep(&url, "/?&");
1320 + if(strcmp(tok, "max") == 0) group_method = GROUP_MAX;
1321 + else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE;
1322 + else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok);
1323 + }
1324 +
1325 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending RRD data '%s' (id %s, %d lines, %d group_count, %d group_method).", w->id, st->name, st->id, lines, group_count, group_method);
1326 +
1327 + code = 200;
1328 + w->data->contenttype = CT_APPLICATION_JSON;
1329 + w->data->bytes = rrd_stats_json(st, w->data->buffer, w->data->size, lines, group_count, group_method);
1330 + }
1331 + }
1332 + else if(strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph"
1333 + // the client is requesting an rrd graph
1334 +
1335 + // get the name of the data to show
1336 + tok = mystrsep(&url, "/?&");
1337 + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1338 +
1339 + // do we have such a data set?
1340 + RRD_STATS *st = rrd_stats_find_byname(tok);
1341 + if(!st) {
1342 + // we don't have it
1343 + // try to send a file with that name
1344 + code = mysendfile(w, tok);
1345 + }
1346 + else {
1347 + code = 200;
1348 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name);
1349 + w->data->contenttype = CT_APPLICATION_JSON;
1350 + w->data->bytes = rrd_stats_graph_json(st, url, w->data->buffer, w->data->size);
1351 + }
1352 + }
1353 + else if(strcmp(tok, "mirror") == 0) {
1354 + code = 200;
1355 +
1356 + debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
1357 +
1358 + // just leave the buffer as is
1359 + // it will be copied back to the client
1360 + }
1361 + else if(strcmp(tok, "list") == 0) {
1362 + code = 200;
1363 +
1364 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
1365 +
1366 + w->data->bytes = 0;
1367 + RRD_STATS *st = root;
1368 +
1369 + for ( ; st ; st = st->next )
1370 + w->data->bytes += sprintf(&w->data->buffer[w->data->bytes], "%s\n", st->name);
1371 + }
1372 + else if(strcmp(tok, "envlist") == 0) {
1373 + code = 200;
1374 +
1375 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending envlist of RRD_STATS...", w->id);
1376 +
1377 + w->data->bytes = 0;
1378 + RRD_STATS *st = root;
1379 +
1380 + for ( ; st ; st = st->next ) {
1381 + w->data->bytes += sprintf(&w->data->buffer[w->data->bytes], "%s=%s\n", st->envtitle, st->usertitle);
1382 + w->data->bytes += sprintf(&w->data->buffer[w->data->bytes], "%s=%s\n", st->envpriority, st->userpriority);
1383 + }
1384 + }
1385 + else if(strcmp(tok, "all.json") == 0) {
1386 + code = 200;
1387 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending all.json of RRD_STATS...", w->id);
1388 + w->data->contenttype = CT_APPLICATION_JSON;
1389 + w->data->bytes = rrd_stats_all_json(w->data->buffer, w->data->size);
1390 + }
1391 + else if(strcmp(tok, WEB_PATH_FILE) == 0) { // "file"
1392 + tok = mystrsep(&url, "/?&");
1393 + if(tok && *tok) code = mysendfile(w, tok);
1394 + else {
1395 + code = 400;
1396 + strcpy(w->data->buffer, "You have to give a filename to get.\r\n");
1397 + w->data->bytes = strlen(w->data->buffer);
1398 + }
1399 + }
1400 + else if(!tok[0]) {
1401 + code = mysendfile(w, "index.html");
1402 + }
1403 + else {
1404 + code = mysendfile(w, tok);
1405 + }
1406 + }
1407 + else {
1408 + if(buf) debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, buf);
1409 +
1410 + code = 500;
1411 + strcpy(w->data->buffer, "I don't understand you...\r\n");
1412 + w->data->bytes = strlen(w->data->buffer);
1413 + }
1414 + }
1415 + else if(w->data->bytes > 8192) {
1416 + debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big.", w->id);
1417 +
1418 + code = 400;
1419 + strcpy(w->data->buffer, "Received request is too big.\r\n");
1420 + w->data->bytes = strlen(w->data->buffer);
1421 + }
1422 + else {
1423 + // wait for more data
1424 + w->wait_receive = 1;
1425 + return;
1426 + }
1427 +
1428 + w->data->date = time(NULL);
1429 + w->data->sent = 0;
1430 +
1431 + // prepare the HTTP response header
1432 + debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code);
1433 +
1434 + char *content_type_string = "";
1435 + switch(w->data->contenttype) {
1436 + case CT_TEXT_HTML:
1437 + content_type_string = "text/html";
1438 + break;
1439 +
1440 + case CT_APPLICATION_XML:
1441 + content_type_string = "application/xml";
1442 + break;
1443 +
1444 + case CT_APPLICATION_JSON:
1445 + content_type_string = "application/json";
1446 + break;
1447 +
1448 + case CT_APPLICATION_X_JAVASCRIPT:
1449 + content_type_string = "application/x-javascript";
1450 + break;
1451 +
1452 + case CT_TEXT_CSS:
1453 + content_type_string = "text/css";
1454 + break;
1455 +
1456 + case CT_TEXT_XML:
1457 + content_type_string = "text/xml";
1458 + break;
1459 +
1460 + case CT_TEXT_XSL:
1461 + content_type_string = "text/xsl";
1462 + break;
1463 +
1464 + case CT_APPLICATION_OCTET_STREAM:
1465 + content_type_string = "application/octet-stream";
1466 + break;
1467 +
1468 + case CT_IMAGE_SVG_XML:
1469 + content_type_string = "image/svg+xml";
1470 + break;
1471 +
1472 + case CT_APPLICATION_X_FONT_TRUETYPE:
1473 + content_type_string = "application/x-font-truetype";
1474 + break;
1475 +
1476 + case CT_APPLICATION_X_FONT_OPENTYPE:
1477 + content_type_string = "application/x-font-opentype";
1478 + break;
1479 +
1480 + case CT_APPLICATION_FONT_WOFF:
1481 + content_type_string = "application/font-woff";
1482 + break;
1483 +
1484 + case CT_APPLICATION_VND_MS_FONTOBJ:
1485 + content_type_string = "application/vnd.ms-fontobject";
1486 + break;
1487 +
1488 + default:
1489 + case CT_TEXT_PLAIN:
1490 + content_type_string = "text/plain";
1491 + break;
1492 + }
1493 +
1494 + char *code_msg = "";
1495 + switch(code) {
1496 + case 200:
1497 + code_msg = "OK";
1498 + break;
1499 +
1500 + case 307:
1501 + code_msg = "Temporary Redirect";
1502 + break;
1503 +
1504 + case 400:
1505 + code_msg = "Bad Request";
1506 + break;
1507 +
1508 + case 403:
1509 + code_msg = "Forbidden";
1510 + break;
1511 +
1512 + case 404:
1513 + code_msg = "Not Found";
1514 + break;
1515 +
1516 + default:
1517 + code_msg = "Internal Server Error";
1518 + break;
1519 + }
1520 +
1521 + char date[100];
1522 + struct tm tm = *gmtime(&w->data->date);
1523 + strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", &tm);
1524 +
1525 + char custom_header[MAX_HTTP_HEADER_SIZE + 1] = "";
1526 + if(w->response_header[0])
1527 + strcpy(custom_header, w->response_header);
1528 +
1529 + size_t headerlen = 0;
1530 + headerlen += sprintf(&w->response_header[headerlen],
1531 + "HTTP/1.1 %d %s\r\n"
1532 + "Connection: %s\r\n"
1533 + "Server: Data Collector HTTP Server\r\n"
1534 + "Content-Type: %s\r\n"
1535 + "Access-Control-Allow-Origin: *\r\n"
1536 + "Date: %s\r\n"
1537 + , code, code_msg
1538 + , w->keepalive?"keep-alive":"close"
1539 + , content_type_string
1540 + , date
1541 + );
1542 +
1543 + if(custom_header[0])
1544 + headerlen += sprintf(&w->response_header[headerlen], "%s", custom_header);
1545 +
1546 + if(w->mode == WEB_CLIENT_MODE_NORMAL) {
1547 + headerlen += sprintf(&w->response_header[headerlen],
1548 + "Expires: %s\r\n"
1549 + "Cache-Control: no-cache\r\n"
1550 + , date
1551 + );
1552 + }
1553 + else {
1554 + headerlen += sprintf(&w->response_header[headerlen],
1555 + "Cache-Control: public\r\n"
1556 + );
1557 + }
1558 +
1559 + // if we know the content length, put it
1560 + if(!w->zoutput && (w->data->bytes || w->data->rbytes))
1561 + headerlen += sprintf(&w->response_header[headerlen],
1562 + "Content-Length: %ld\r\n"
1563 + , w->data->bytes?w->data->bytes:w->data->rbytes
1564 + );
1565 + else if(!w->zoutput)
1566 + w->keepalive = 0; // content-length is required for keep-alive
1567 +
1568 + if(w->zoutput) {
1569 + headerlen += sprintf(&w->response_header[headerlen],
1570 + "Content-Encoding: gzip\r\n"
1571 + "Transfer-Encoding: chunked\r\n"
1572 + );
1573 + }
1574 +
1575 + headerlen += sprintf(&w->response_header[headerlen], "\r\n");
1576 +
1577 + // disable TCP_NODELAY, to buffer the header
1578 + int flag = 0;
1579 + if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to disable TCP_NODELAY on socket.", w->id);
1580 +
1581 + // sent the HTTP header
1582 + debug(D_WEB_CLIENT, "%llu: Sending response HTTP header of size %d: '%s'", w->id, headerlen, w->response_header);
1583 +
1584 + bytes = send(w->ofd, w->response_header, headerlen, 0);
1585 + if(bytes != headerlen)
1586 + error("%llu: HTTP Header failed to be sent (I sent %d bytes but the system sent %d bytes).", w->id, headerlen, bytes);
1587 +
1588 + // enable TCP_NODELAY, to send all data immediately at the next send()
1589 + flag = 1;
1590 + if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to enable TCP_NODELAY on socket.", w->id);
1591 +
1592 + // enable sending immediately if we have data
1593 + if(w->data->bytes) w->wait_send = 1;
1594 + else w->wait_send = 0;
1595 +
1596 + // pretty logging
1597 + switch(w->mode) {
1598 + case WEB_CLIENT_MODE_NORMAL:
1599 + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%d bytes) to client.", w->id, w->data->bytes);
1600 + break;
1601 +
1602 + case WEB_CLIENT_MODE_FILECOPY:
1603 + if(w->data->rbytes) {
1604 + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %d bytes to client.", w->id, w->data->rbytes);
1605 + w->wait_receive = 1;
1606 +
1607 + /*
1608 + // utilize the kernel sendfile() for copying the file to the socket.
1609 + // this block of code can be commented, without anything missing.
1610 + // when it is commented, the program will copy the data using async I/O.
1611 + {
1612 + ssize_t len = sendfile(w->ofd, w->ifd, NULL, w->data->rbytes);
1613 + if(len != w->data->rbytes) error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->data->rbytes, len);
1614 + else web_client_reset(w);
1615 + }
1616 + */
1617 + }
1618 + else
1619 + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id);
1620 + break;
1621 +
1622 + default:
1623 + fatal("%llu: Unknown client mode %d.", w->id, w->mode);
1624 + break;
1625 + }
1626 +}
1627 +
1628 +void web_client_send_chunk_header(struct web_client *w, int len)
1629 +{
1630 + debug(D_DEFLATE, "%llu: OPEN CHUNK of %d bytes (hex: %x).", w->id, len, len);
1631 + char buf[1024];
1632 + sprintf(buf, "%X\r\n", len);
1633 + int bytes = send(w->ofd, buf, strlen(buf), MSG_DONTWAIT);
1634 +
1635 + if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk header %d bytes.", w->id, bytes);
1636 + else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk header to the client.", w->id);
1637 + else debug(D_DEFLATE, "%llu: Failed to send chunk header to client.", w->id);
1638 +}
1639 +
1640 +void web_client_send_chunk_close(struct web_client *w)
1641 +{
1642 + debug(D_DEFLATE, "%llu: CLOSE CHUNK.", w->id);
1643 +
1644 + int bytes = send(w->ofd, "\r\n", 2, MSG_DONTWAIT);
1645 +
1646 + if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
1647 + else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id);
1648 + else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client.", w->id);
1649 +}
1650 +
1651 +void web_client_send_chunk_finalize(struct web_client *w)
1652 +{
1653 + debug(D_DEFLATE, "%llu: FINALIZE CHUNK.", w->id);
1654 +
1655 + int bytes = send(w->ofd, "\r\n0\r\n\r\n", 7, MSG_DONTWAIT);
1656 +
1657 + if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
1658 + else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id);
1659 + else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client.", w->id);
1660 +}
1661 +
1662 +ssize_t web_client_send_deflate(struct web_client *w)
1663 +{
1664 + ssize_t bytes;
1665 +
1666 + // when using compression,
1667 + // w->data->sent is the amount of bytes passed through compression
1668 +
1669 + debug(D_DEFLATE, "%llu: TEST w->data->bytes = %d, w->data->sent = %d, w->zhave = %d, w->zsent = %d, w->zstream.avail_in = %d, w->zstream.avail_out = %d, w->zstream.total_in = %d, w->zstream.total_out = %d.", w->id, w->data->bytes, w->data->sent, w->zhave, w->zsent, w->zstream.avail_in, w->zstream.avail_out, w->zstream.total_in, w->zstream.total_out);
1670 +
1671 + if(w->data->bytes - w->data->sent == 0 && w->zstream.avail_in == 0 && w->zhave == w->zsent && w->zstream.avail_out != 0) {
1672 + // there is nothing to send
1673 +
1674 + debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
1675 +
1676 + // finalize the chunk
1677 + if(w->data->sent != 0)
1678 + web_client_send_chunk_finalize(w);
1679 +
1680 + // there can be two cases for this
1681 + // A. we have done everything
1682 + // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
1683 +
1684 + if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->data->rbytes && w->data->rbytes > w->data->bytes) {
1685 + // we have to wait, more data will come
1686 + debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
1687 + w->wait_send = 0;
1688 + return(0);
1689 + }
1690 +
1691 + if(w->keepalive == 0) {
1692 + debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->data->sent);
1693 + errno = 0;
1694 + return(-1);
1695 + }
1696 +
1697 + // reset the client
1698 + web_client_reset(w);
1699 + debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id);
1700 + return(0);
1701 + }
1702 +
1703 + if(w->zstream.avail_out == 0 && w->zhave == w->zsent) {
1704 + // compress more input data
1705 +
1706 + // close the previous open chunk
1707 + if(w->data->sent != 0) web_client_send_chunk_close(w);
1708 +
1709 + debug(D_DEFLATE, "%llu: Compressing %d bytes starting from %d.", w->id, (w->data->bytes - w->data->sent), w->data->sent);
1710 +
1711 + // give the compressor all the data not passed through the compressor yet
1712 + if(w->data->bytes > w->data->sent) {
1713 + w->zstream.next_in = (Bytef *)&w->data->buffer[w->data->sent];
1714 + w->zstream.avail_in = (w->data->bytes - w->data->sent);
1715 + }
1716 +
1717 + // reset the compressor output buffer
1718 + w->zstream.next_out = w->zbuffer;
1719 + w->zstream.avail_out = ZLIB_CHUNK;
1720 +
1721 + // ask for FINISH if we have all the input
1722 + int flush = Z_SYNC_FLUSH;
1723 + if(w->mode == WEB_CLIENT_MODE_NORMAL
1724 + || (w->mode == WEB_CLIENT_MODE_FILECOPY && w->data->bytes == w->data->rbytes)) {
1725 + flush = Z_FINISH;
1726 + debug(D_DEFLATE, "%llu: Requesting Z_FINISH.", w->id);
1727 + }
1728 + else {
1729 + debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id);
1730 + }
1731 +
1732 + // compress
1733 + if(deflate(&w->zstream, flush) == Z_STREAM_ERROR) {
1734 + error("%llu: Compression failed. Closing down client.", w->id);
1735 + web_client_reset(w);
1736 + return(-1);
1737 + }
1738 +
1739 + w->zhave = ZLIB_CHUNK - w->zstream.avail_out;
1740 + w->zsent = 0;
1741 +
1742 + // keep track of the bytes passed through the compressor
1743 + w->data->sent = w->data->bytes;
1744 +
1745 + debug(D_DEFLATE, "%llu: Compression produced %d bytes.", w->id, w->zhave);
1746 +
1747 + // open a new chunk
1748 + web_client_send_chunk_header(w, w->zhave);
1749 + }
1750 +
1751 + bytes = send(w->ofd, &w->zbuffer[w->zsent], w->zhave - w->zsent, MSG_DONTWAIT);
1752 + if(bytes > 0) {
1753 + w->zsent += bytes;
1754 + debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
1755 + }
1756 + else if(bytes == 0) debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id);
1757 + else debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id);
1758 +
1759 + return(bytes);
1760 +}
1761 +
1762 +ssize_t web_client_send(struct web_client *w)
1763 +{
1764 + if(w->zoutput) return web_client_send_deflate(w);
1765 +
1766 + ssize_t bytes;
1767 +
1768 + if(w->data->bytes - w->data->sent == 0) {
1769 + // there is nothing to send
1770 +
1771 + debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
1772 +
1773 + // there can be two cases for this
1774 + // A. we have done everything
1775 + // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
1776 +

This file is too large to show in full.

netdata.start
+2 -2
@@ -39,9 +39,9 @@ echo "Compiling netdata"
39 if [ $NETDATA_CONFIG_DEBUG -eq 1 ]
40 then
41 ulimit -c unlimited
42 - gcc -Wall -ggdb -o netdata netdata.c -lpthread || exit 1
42 + gcc -Wall -ggdb -o netdata netdata.c -lpthread -lz || exit 1
43 else
44 - gcc -Wall -O3 -o netdata netdata.c -lpthread || exit 1
44 + gcc -Wall -O3 -o netdata netdata.c -lpthread -lz || exit 1
45 fi
46
47 # exporting NETDATA variables to netdata
web/index.html
+3 -8
@@ -168,19 +168,14 @@
168
169 width = thumbWidth();
170 $.each(mycharts, function(i, c) {
171 - var now = new Date().getTime();
172 -
171 if(c.enabled && c.chartOptions.width != width) {
172 if(c.chart) c.chart.clearChart();
173 c.chart = null;
176 - c.last_updated = now + (c.update_every * 1000);
174 c.chartOptions.width = width;
175 showChartIsLoading(c.div, c.chartOptions.width, c.chartOptions.height);
176 c.last_updated = 0;
177 }
178 });
182 -
183 - chartsRefresh();
179 }
180
181 // load the charts from the server
@@ -389,7 +384,7 @@
384 <tr><td align="center">
385 <div class="btn-group">
386 <form id="mainchartform">
392 - <div class="btn-group" data-toggle="buttons">
387 + <div class="btn-group btn-group-xs" data-toggle="buttons">
388 <label class="btn btn-primary" data-toggle="tooltip" title="show the MAX point among all points grouped">
389 <input type="radio" name="groupmethod" id="groupmax" onChange="setMainChartGroupMethod('max');">
390 <span class="glyphicon glyphicon-signal"></span>
@@ -399,7 +394,7 @@
394 <span class="glyphicon glyphicon-align-justify"></span>
395 </label>
396 </div>
402 - <div class="btn-group" data-toggle="buttons" >
397 + <div class="btn-group btn-group-xs" data-toggle="buttons" >
398 <label class="btn btn-primary" data-toggle="tooltip" title="do not group points, show the raw data">
399 <input type="radio" name="group" id="group1" onChange="setMainChartGroup(1);">1
400 </label>
@@ -430,7 +425,7 @@
425 <input type="radio" name="group" id="group60" onChange="setMainChartGroup(60);">60
426 </label>
427 </div>
433 - <div class="btn-group" data-toggle="buttons" >
428 + <div class="btn-group btn-group-xs" data-toggle="buttons" >
429 <label class="btn btn-primary" data-toggle="tooltip" title="NORMAL chart size">
430 <input type="radio" name="chartmax" id="chartnormal" onChange="setMainChartMax('normal');">
431 <span class="glyphicon glyphicon-resize-small"></span>