modules/admin-full: rework realtime stats to start luci-bwc on demand, kill daemon...
[project/luci.git] / modules / admin-full / src / luci-bwc.c
1 /*
2  * luci-bwc - Very simple bandwidth collector cache for LuCI realtime graphs
3  *
4  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdint.h>
23 #include <inttypes.h>
24 #include <fcntl.h>
25 #include <time.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #include <signal.h>
29
30 #include <sys/stat.h>
31 #include <sys/mman.h>
32 #include <arpa/inet.h>
33
34
35 #define STEP_COUNT      60
36 #define STEP_TIME       1
37 #define TIMEOUT         10
38
39 #define PID_PATH        "/var/run/luci-bwc.pid"
40
41 #define DB_PATH         "/var/lib/luci-bwc"
42 #define DB_IF_FILE      DB_PATH "/if/%s"
43 #define DB_CN_FILE      DB_PATH "/connections"
44 #define DB_LD_FILE      DB_PATH "/load"
45
46 #define IF_SCAN_PATTERN \
47         " %[^ :]:%" SCNu64 " %" SCNu64 \
48         " %*d %*d %*d %*d %*d %*d" \
49         " %" SCNu64 " %" SCNu64
50
51 #define LD_SCAN_PATTERN \
52         "%f %f %f"
53
54
55 struct file_map {
56         int fd;
57         int size;
58         char *mmap;
59 };
60
61 struct traffic_entry {
62         uint64_t time;
63         uint64_t rxb;
64         uint64_t rxp;
65         uint64_t txb;
66         uint64_t txp;
67 };
68
69 struct conn_entry {
70         uint64_t time;
71         uint32_t udp;
72         uint32_t tcp;
73         uint32_t other;
74 };
75
76 struct load_entry {
77         uint64_t time;
78         uint16_t load1;
79         uint16_t load5;
80         uint16_t load15;
81 };
82
83
84 static uint64_t htonll(uint64_t value)
85 {
86         int num = 1;
87
88         if (*(char *)&num == 1)
89                 return htonl((uint32_t)(value & 0xFFFFFFFF)) |
90                        htonl((uint32_t)(value >> 32));
91
92         return value;
93 }
94
95 #define ntohll htonll
96
97 static int readpid(void)
98 {
99         int fd;
100         int pid = -1;
101         char buf[9] = { 0 };
102
103         if ((fd = open(PID_PATH, O_RDONLY)) > -1)
104         {
105                 if (read(fd, buf, sizeof(buf)))
106                 {
107                         buf[8] = 0;
108                         pid = atoi(buf);
109                 }
110
111                 close(fd);
112         }
113
114         return pid;
115 }
116
117 static int writepid(void)
118 {
119         int fd;
120         int wlen;
121         char buf[9] = { 0 };
122
123         if ((fd = open(PID_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0600)) > -1)
124         {
125                 wlen = snprintf(buf, sizeof(buf), "%i", getpid());
126                 write(fd, buf, wlen);
127                 close(fd);
128
129                 return 0;
130         }
131
132         return -1;
133 }
134
135 static int timeout = TIMEOUT;
136 static int countdown = -1;
137
138 static void reset_countdown(int sig)
139 {
140         countdown = timeout;
141
142 }
143
144
145 static int init_directory(char *path)
146 {
147         char *p = path;
148
149         for (p = &path[1]; *p; p++)
150         {
151                 if (*p == '/')
152                 {
153                         *p = 0;
154
155                         if (mkdir(path, 0700) && (errno != EEXIST))
156                                 return -1;
157
158                         *p = '/';
159                 }
160         }
161
162         return 0;
163 }
164
165 static int init_file(char *path, int esize)
166 {
167         int i, file;
168         char buf[sizeof(struct traffic_entry)] = { 0 };
169
170         if (init_directory(path))
171                 return -1;
172
173         if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
174         {
175                 for (i = 0; i < STEP_COUNT; i++)
176                 {
177                         if (write(file, buf, esize) < 0)
178                                 break;
179                 }
180
181                 close(file);
182
183                 return 0;
184         }
185
186         return -1;
187 }
188
189 static inline uint64_t timeof(void *entry)
190 {
191         return ((struct traffic_entry *)entry)->time;
192 }
193
194 static int update_file(const char *path, void *entry, int esize)
195 {
196         int rv = -1;
197         int file;
198         char *map;
199
200         if ((file = open(path, O_RDWR)) >= 0)
201         {
202                 map = mmap(NULL, esize * STEP_COUNT, PROT_READ | PROT_WRITE,
203                                    MAP_SHARED | MAP_LOCKED, file, 0);
204
205                 if ((map != NULL) && (map != MAP_FAILED))
206                 {
207                         if (timeof(entry) > timeof(map + esize * (STEP_COUNT-1)))
208                         {
209                                 memmove(map, map + esize, esize * (STEP_COUNT-1));
210                                 memcpy(map + esize * (STEP_COUNT-1), entry, esize);
211                         }
212
213                         munmap(map, esize * STEP_COUNT);
214
215                         rv = 0;
216                 }
217
218                 close(file);
219         }
220
221         return rv;
222 }
223
224 static int mmap_file(const char *path, int esize, struct file_map *m)
225 {
226         m->fd   = -1;
227         m->size = -1;
228         m->mmap = NULL;
229
230         if ((m->fd = open(path, O_RDONLY)) >= 0)
231         {
232                 m->size = STEP_COUNT * esize;
233                 m->mmap = mmap(NULL, m->size, PROT_READ,
234                                            MAP_SHARED | MAP_LOCKED, m->fd, 0);
235
236                 if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
237                         return 0;
238         }
239
240         return -1;
241 }
242
243 static void umap_file(struct file_map *m)
244 {
245         if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
246                 munmap(m->mmap, m->size);
247
248         if (m->fd > -1)
249                 close(m->fd);
250 }
251
252
253 static int update_ifstat(
254         const char *ifname, uint64_t rxb, uint64_t rxp, uint64_t txb, uint64_t txp
255 ) {
256         char path[1024];
257
258         struct stat s;
259         struct traffic_entry e;
260
261         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
262
263         if (stat(path, &s))
264         {
265                 if (init_file(path, sizeof(struct traffic_entry)))
266                 {
267                         fprintf(stderr, "Failed to init %s: %s\n",
268                                         path, strerror(errno));
269
270                         return -1;
271                 }
272         }
273
274         e.time = htonll(time(NULL));
275         e.rxb  = htonll(rxb);
276         e.rxp  = htonll(rxp);
277         e.txb  = htonll(txb);
278         e.txp  = htonll(txp);
279
280         return update_file(path, &e, sizeof(struct traffic_entry));
281 }
282
283 static int update_cnstat(uint32_t udp, uint32_t tcp, uint32_t other)
284 {
285         char path[1024];
286
287         struct stat s;
288         struct conn_entry e;
289
290         snprintf(path, sizeof(path), DB_CN_FILE);
291
292         if (stat(path, &s))
293         {
294                 if (init_file(path, sizeof(struct conn_entry)))
295                 {
296                         fprintf(stderr, "Failed to init %s: %s\n",
297                                         path, strerror(errno));
298
299                         return -1;
300                 }
301         }
302
303         e.time  = htonll(time(NULL));
304         e.udp   = htonl(udp);
305         e.tcp   = htonl(tcp);
306         e.other = htonl(other);
307
308         return update_file(path, &e, sizeof(struct conn_entry));
309 }
310
311 static int update_ldstat(uint16_t load1, uint16_t load5, uint16_t load15)
312 {
313         char path[1024];
314
315         struct stat s;
316         struct load_entry e;
317
318         snprintf(path, sizeof(path), DB_LD_FILE);
319
320         if (stat(path, &s))
321         {
322                 if (init_file(path, sizeof(struct load_entry)))
323                 {
324                         fprintf(stderr, "Failed to init %s: %s\n",
325                                         path, strerror(errno));
326
327                         return -1;
328                 }
329         }
330
331         e.time   = htonll(time(NULL));
332         e.load1  = htons(load1);
333         e.load5  = htons(load5);
334         e.load15 = htons(load15);
335
336         return update_file(path, &e, sizeof(struct load_entry));
337 }
338
339 static int run_daemon(char *progname)
340 {
341         FILE *info;
342         uint64_t rxb, txb, rxp, txp;
343         uint32_t udp, tcp, other;
344         float lf1, lf5, lf15;
345         char line[1024];
346         char ifname[16];
347
348         struct sigaction sa;
349
350         struct stat s;
351         const char *ipc = stat("/proc/net/nf_conntrack", &s)
352                 ? "/proc/net/ip_conntrack" : "/proc/net/nf_conntrack";
353
354         switch (fork())
355         {
356                 case -1:
357                         perror("fork()");
358                         return -1;
359
360                 case 0:
361                         if (chdir("/") < 0)
362                         {
363                                 perror("chdir()");
364                                 exit(1);
365                         }
366
367                         close(0);
368                         close(1);
369                         close(2);
370                         break;
371
372                 default:
373                         return 0;
374         }
375
376         /* setup USR1 signal handler to reset timer */
377         sa.sa_handler = reset_countdown;
378         sa.sa_flags   = SA_RESTART;
379         sigemptyset(&sa.sa_mask);
380         sigaction(SIGUSR1, &sa, NULL);
381
382         /* write pid */
383         if (writepid())
384         {
385                 fprintf(stderr, "Failed to write pid file: %s\n", strerror(errno));
386                 return 1;
387         }
388
389         /* go */
390         for (reset_countdown(0); countdown >= 0; countdown--)
391         {
392                 /* alter progname for ps, top */
393                 sprintf(progname, "luci-bwc %d", countdown);
394
395                 if ((info = fopen("/proc/net/dev", "r")) != NULL)
396                 {
397                         while (fgets(line, sizeof(line), info))
398                         {
399                                 if (strchr(line, '|'))
400                                         continue;
401
402                                 if (sscanf(line, IF_SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp))
403                                 {
404                                         if (strncmp(ifname, "lo", sizeof(ifname)))
405                                                 update_ifstat(ifname, rxb, rxp, txb, txp);
406                                 }
407                         }
408
409                         fclose(info);
410                 }
411
412                 if ((info = fopen(ipc, "r")) != NULL)
413                 {
414                         udp   = 0;
415                         tcp   = 0;
416                         other = 0;
417
418                         while (fgets(line, sizeof(line), info))
419                         {
420                                 if (strstr(line, "TIME_WAIT"))
421                                         continue;
422
423                                 if (sscanf(line, "%*s %*d %s", ifname) || sscanf(line, "%s %*d", ifname))
424                                 {
425                                         if (!strcmp(ifname, "tcp"))
426                                                 tcp++;
427                                         else if (!strcmp(ifname, "udp"))
428                                                 udp++;
429                                         else
430                                                 other++;
431                                 }
432                         }
433
434                         update_cnstat(udp, tcp, other);
435
436                         fclose(info);
437                 }
438
439                 if ((info = fopen("/proc/loadavg", "r")) != NULL)
440                 {
441                         if (fscanf(info, LD_SCAN_PATTERN, &lf1, &lf5, &lf15))
442                         {
443                                 update_ldstat((uint16_t)(lf1  * 100),
444                                                           (uint16_t)(lf5  * 100),
445                                                           (uint16_t)(lf15 * 100));
446                         }
447
448                         fclose(info);
449                 }
450
451                 sleep(STEP_TIME);
452         }
453
454         unlink(PID_PATH);
455
456         return 0;
457 }
458
459 static int check_daemon(char *progname)
460 {
461         int pid;
462
463         if ((pid = readpid()) < 0 || kill(pid, 0) < 0)
464         {
465                 /* daemon ping failed, try to start it up */
466                 if (run_daemon(progname))
467                 {
468                         fprintf(stderr,
469                                 "Failed to ping daemon and unable to start it up: %s\n",
470                                 strerror(errno));
471
472                         return 1;
473                 }
474         }
475         else if (kill(pid, SIGUSR1))
476         {
477                 fprintf(stderr, "Failed to send signal: %s\n", strerror(errno));
478                 return 1;
479         }
480
481         return 0;
482 }
483
484 static int run_dump_ifname(char *progname, const char *ifname)
485 {
486         int i;
487         char path[1024];
488         struct file_map m;
489         struct traffic_entry *e;
490
491         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
492
493         if (check_daemon(progname))
494         {
495                 return 1;
496         }
497
498         if (mmap_file(path, sizeof(struct traffic_entry), &m))
499         {
500                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
501                 return 1;
502         }
503
504         for (i = 0; i < m.size; i += sizeof(struct traffic_entry))
505         {
506                 e = (struct traffic_entry *) &m.mmap[i];
507
508                 if (!e->time)
509                         continue;
510
511                 printf("[ %" PRIu64 ", %" PRIu64 ", %" PRIu64
512                            ", %" PRIu64 ", %" PRIu64 " ]%s\n",
513                         ntohll(e->time),
514                         ntohll(e->rxb), ntohll(e->rxp),
515                         ntohll(e->txb), ntohll(e->txp),
516                         ((i + sizeof(struct traffic_entry)) < m.size) ? "," : "");
517         }
518
519         umap_file(&m);
520
521         return 0;
522 }
523
524 static int run_dump_conns(char *progname)
525 {
526         int i;
527         char path[1024];
528         struct file_map m;
529         struct conn_entry *e;
530
531         snprintf(path, sizeof(path), DB_CN_FILE);
532
533         if (check_daemon(progname))
534         {
535                 return 1;
536         }
537
538         if (mmap_file(path, sizeof(struct conn_entry), &m))
539         {
540                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
541                 return 1;
542         }
543
544         for (i = 0; i < m.size; i += sizeof(struct conn_entry))
545         {
546                 e = (struct conn_entry *) &m.mmap[i];
547
548                 if (!e->time)
549                         continue;
550
551                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
552                         ntohll(e->time), ntohl(e->udp),
553                         ntohl(e->tcp), ntohl(e->other),
554                         ((i + sizeof(struct conn_entry)) < m.size) ? "," : "");
555         }
556
557         umap_file(&m);
558
559         return 0;
560 }
561
562 static int run_dump_load(char *progname)
563 {
564         int i;
565         char path[1024];
566         struct file_map m;
567         struct load_entry *e;
568
569         snprintf(path, sizeof(path), DB_LD_FILE);
570
571         if (check_daemon(progname))
572         {
573                 return 1;
574         }
575
576         if (mmap_file(path, sizeof(struct load_entry), &m))
577         {
578                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
579                 return 1;
580         }
581
582         for (i = 0; i < m.size; i += sizeof(struct load_entry))
583         {
584                 e = (struct load_entry *) &m.mmap[i];
585
586                 if (!e->time)
587                         continue;
588
589                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
590                         ntohll(e->time),
591                         ntohs(e->load1), ntohs(e->load5), ntohs(e->load15),
592                         ((i + sizeof(struct load_entry)) < m.size) ? "," : "");
593         }
594
595         umap_file(&m);
596
597         return 0;
598 }
599
600
601 int main(int argc, char *argv[])
602 {
603         int opt;
604
605         while ((opt = getopt(argc, argv, "t:i:cl")) > -1)
606         {
607                 switch (opt)
608                 {
609                         case 't':
610                                 timeout = atoi(optarg);
611                                 break;
612
613                         case 'i':
614                                 if (optarg)
615                                         return run_dump_ifname(argv[0], optarg);
616                                 break;
617
618                         case 'c':
619                                 return run_dump_conns(argv[0]);
620
621                         case 'l':
622                                 return run_dump_load(argv[0]);
623
624                         default:
625                                 break;
626                 }
627         }
628
629         fprintf(stderr,
630                 "Usage:\n"
631                 "       %s [-t timeout] -i ifname\n"
632                 "       %s [-t timeout] -c\n"
633                 "       %s [-t timeout] -l\n",
634                         argv[0], argv[0], argv[0]
635         );
636
637         return 1;
638 }