9352ce8498d3f8f85eba2b3bacda7f488a51fdfa
[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 #include <dlfcn.h>
35
36
37 #define STEP_COUNT      60
38 #define STEP_TIME       1
39 #define TIMEOUT         10
40
41 #define PID_PATH        "/var/run/luci-bwc.pid"
42
43 #define DB_PATH         "/var/lib/luci-bwc"
44 #define DB_IF_FILE      DB_PATH "/if/%s"
45 #define DB_RD_FILE      DB_PATH "/radio/%s"
46 #define DB_CN_FILE      DB_PATH "/connections"
47 #define DB_LD_FILE      DB_PATH "/load"
48
49 #define IF_SCAN_PATTERN \
50         " %[^ :]:%" SCNu64 " %" SCNu64 \
51         " %*d %*d %*d %*d %*d %*d" \
52         " %" SCNu64 " %" SCNu64
53
54 #define LD_SCAN_PATTERN \
55         "%f %f %f"
56
57
58 struct file_map {
59         int fd;
60         int size;
61         char *mmap;
62 };
63
64 struct traffic_entry {
65         uint64_t time;
66         uint64_t rxb;
67         uint64_t rxp;
68         uint64_t txb;
69         uint64_t txp;
70 };
71
72 struct conn_entry {
73         uint64_t time;
74         uint32_t udp;
75         uint32_t tcp;
76         uint32_t other;
77 };
78
79 struct load_entry {
80         uint64_t time;
81         uint16_t load1;
82         uint16_t load5;
83         uint16_t load15;
84 };
85
86 struct radio_entry {
87         uint64_t time;
88         uint16_t rate;
89         uint8_t  rssi;
90         uint8_t  noise;
91 };
92
93
94 static uint64_t htonll(uint64_t value)
95 {
96         int num = 1;
97
98         if (*(char *)&num == 1)
99                 return htonl((uint32_t)(value & 0xFFFFFFFF)) |
100                        htonl((uint32_t)(value >> 32));
101
102         return value;
103 }
104
105 #define ntohll htonll
106
107 static int readpid(void)
108 {
109         int fd;
110         int pid = -1;
111         char buf[9] = { 0 };
112
113         if ((fd = open(PID_PATH, O_RDONLY)) > -1)
114         {
115                 if (read(fd, buf, sizeof(buf)))
116                 {
117                         buf[8] = 0;
118                         pid = atoi(buf);
119                 }
120
121                 close(fd);
122         }
123
124         return pid;
125 }
126
127 static int writepid(void)
128 {
129         int fd;
130         int wlen;
131         char buf[9] = { 0 };
132
133         if ((fd = open(PID_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0600)) > -1)
134         {
135                 wlen = snprintf(buf, sizeof(buf), "%i", getpid());
136                 write(fd, buf, wlen);
137                 close(fd);
138
139                 return 0;
140         }
141
142         return -1;
143 }
144
145 static int timeout = TIMEOUT;
146 static int countdown = -1;
147
148 static void reset_countdown(int sig)
149 {
150         countdown = timeout;
151
152 }
153
154
155 static char *progname;
156 static int prognamelen;
157
158 static int (*iw_get_rate)(const char *, int *) = NULL;
159 static int (*iw_get_rssi)(const char *, int *) = NULL;
160 static int (*iw_get_noise)(const char *, int *) = NULL;
161
162
163 static int init_directory(char *path)
164 {
165         char *p = path;
166
167         for (p = &path[1]; *p; p++)
168         {
169                 if (*p == '/')
170                 {
171                         *p = 0;
172
173                         if (mkdir(path, 0700) && (errno != EEXIST))
174                                 return -1;
175
176                         *p = '/';
177                 }
178         }
179
180         return 0;
181 }
182
183 static int init_file(char *path, int esize)
184 {
185         int i, file;
186         char buf[sizeof(struct traffic_entry)] = { 0 };
187
188         if (init_directory(path))
189                 return -1;
190
191         if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
192         {
193                 for (i = 0; i < STEP_COUNT; i++)
194                 {
195                         if (write(file, buf, esize) < 0)
196                                 break;
197                 }
198
199                 close(file);
200
201                 return 0;
202         }
203
204         return -1;
205 }
206
207 static inline uint64_t timeof(void *entry)
208 {
209         return ((struct traffic_entry *)entry)->time;
210 }
211
212 static int update_file(const char *path, void *entry, int esize)
213 {
214         int rv = -1;
215         int file;
216         char *map;
217
218         if ((file = open(path, O_RDWR)) >= 0)
219         {
220                 map = mmap(NULL, esize * STEP_COUNT, PROT_READ | PROT_WRITE,
221                                    MAP_SHARED | MAP_LOCKED, file, 0);
222
223                 if ((map != NULL) && (map != MAP_FAILED))
224                 {
225                         if (timeof(entry) > timeof(map + esize * (STEP_COUNT-1)))
226                         {
227                                 memmove(map, map + esize, esize * (STEP_COUNT-1));
228                                 memcpy(map + esize * (STEP_COUNT-1), entry, esize);
229                         }
230
231                         munmap(map, esize * STEP_COUNT);
232
233                         rv = 0;
234                 }
235
236                 close(file);
237         }
238
239         return rv;
240 }
241
242 static int mmap_file(const char *path, int esize, struct file_map *m)
243 {
244         m->fd   = -1;
245         m->size = -1;
246         m->mmap = NULL;
247
248         if ((m->fd = open(path, O_RDONLY)) >= 0)
249         {
250                 m->size = STEP_COUNT * esize;
251                 m->mmap = mmap(NULL, m->size, PROT_READ,
252                                            MAP_SHARED | MAP_LOCKED, m->fd, 0);
253
254                 if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
255                         return 0;
256         }
257
258         return -1;
259 }
260
261 static void umap_file(struct file_map *m)
262 {
263         if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
264                 munmap(m->mmap, m->size);
265
266         if (m->fd > -1)
267                 close(m->fd);
268 }
269
270 static void * iwinfo_open(void)
271 {
272         return dlopen("/usr/lib/lua/iwinfo.so", RTLD_LAZY);
273 }
274
275 static int iwinfo_update(
276         void *iw, const char *ifname, uint16_t *rate, uint8_t *rssi, uint8_t *noise
277 ) {
278         int (*probe)(const char *);
279         int val;
280
281         if (!iw_get_rate)
282         {
283                 if ((probe = dlsym(iw, "nl80211_probe")) != NULL && probe(ifname))
284                 {
285                         iw_get_rate  = dlsym(iw, "nl80211_get_bitrate");
286                         iw_get_rssi  = dlsym(iw, "nl80211_get_signal");
287                         iw_get_noise = dlsym(iw, "nl80211_get_noise");
288                 }
289                 else if ((probe = dlsym(iw, "madwifi_probe")) != NULL && probe(ifname))
290                 {
291                         iw_get_rate  = dlsym(iw, "madwifi_get_bitrate");
292                         iw_get_rssi  = dlsym(iw, "madwifi_get_signal");
293                         iw_get_noise = dlsym(iw, "madwifi_get_noise");
294                 }
295                 else if ((probe = dlsym(iw, "wl_probe")) != NULL && probe(ifname))
296                 {
297                         iw_get_rate  = dlsym(iw, "wl_get_bitrate");
298                         iw_get_rssi  = dlsym(iw, "wl_get_signal");
299                         iw_get_noise = dlsym(iw, "wl_get_noise");
300                 }
301                 else
302                 {
303                         return 0;
304                 }
305         }
306
307         *rate = (iw_get_rate && !iw_get_rate(ifname, &val)) ? val : 0;
308         *rssi = (iw_get_rssi && !iw_get_rssi(ifname, &val)) ? val : 0;
309         *noise = (iw_get_noise && !iw_get_noise(ifname, &val)) ? val : 0;
310
311         return 1;
312 }
313
314 static void iwinfo_close(void *iw)
315 {
316         void (*do_close)(void);
317
318         if ((do_close = dlsym(iw, "nl80211_close")) != NULL) do_close();
319         if ((do_close = dlsym(iw, "madwifi_close")) != NULL) do_close();
320         if ((do_close = dlsym(iw, "wl_close"))      != NULL) do_close();
321         if ((do_close = dlsym(iw, "wext_close"))    != NULL) do_close();
322         if ((do_close = dlsym(iw, "iwinfo_close"))  != NULL) do_close();
323
324         dlclose(iw);
325 }
326
327
328 static int update_ifstat(
329         const char *ifname, uint64_t rxb, uint64_t rxp, uint64_t txb, uint64_t txp
330 ) {
331         char path[1024];
332
333         struct stat s;
334         struct traffic_entry e;
335
336         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
337
338         if (stat(path, &s))
339         {
340                 if (init_file(path, sizeof(struct traffic_entry)))
341                 {
342                         fprintf(stderr, "Failed to init %s: %s\n",
343                                         path, strerror(errno));
344
345                         return -1;
346                 }
347         }
348
349         e.time = htonll(time(NULL));
350         e.rxb  = htonll(rxb);
351         e.rxp  = htonll(rxp);
352         e.txb  = htonll(txb);
353         e.txp  = htonll(txp);
354
355         return update_file(path, &e, sizeof(struct traffic_entry));
356 }
357
358 static int update_radiostat(
359         const char *ifname, uint16_t rate, uint8_t rssi, uint8_t noise
360 ) {
361         char path[1024];
362
363         struct stat s;
364         struct radio_entry e;
365
366         snprintf(path, sizeof(path), DB_RD_FILE, ifname);
367
368         if (stat(path, &s))
369         {
370                 if (init_file(path, sizeof(struct radio_entry)))
371                 {
372                         fprintf(stderr, "Failed to init %s: %s\n",
373                                         path, strerror(errno));
374
375                         return -1;
376                 }
377         }
378
379         e.time  = htonll(time(NULL));
380         e.rate  = htons(rate);
381         e.rssi  = rssi;
382         e.noise = noise;
383
384         return update_file(path, &e, sizeof(struct radio_entry));
385 }
386
387 static int update_cnstat(uint32_t udp, uint32_t tcp, uint32_t other)
388 {
389         char path[1024];
390
391         struct stat s;
392         struct conn_entry e;
393
394         snprintf(path, sizeof(path), DB_CN_FILE);
395
396         if (stat(path, &s))
397         {
398                 if (init_file(path, sizeof(struct conn_entry)))
399                 {
400                         fprintf(stderr, "Failed to init %s: %s\n",
401                                         path, strerror(errno));
402
403                         return -1;
404                 }
405         }
406
407         e.time  = htonll(time(NULL));
408         e.udp   = htonl(udp);
409         e.tcp   = htonl(tcp);
410         e.other = htonl(other);
411
412         return update_file(path, &e, sizeof(struct conn_entry));
413 }
414
415 static int update_ldstat(uint16_t load1, uint16_t load5, uint16_t load15)
416 {
417         char path[1024];
418
419         struct stat s;
420         struct load_entry e;
421
422         snprintf(path, sizeof(path), DB_LD_FILE);
423
424         if (stat(path, &s))
425         {
426                 if (init_file(path, sizeof(struct load_entry)))
427                 {
428                         fprintf(stderr, "Failed to init %s: %s\n",
429                                         path, strerror(errno));
430
431                         return -1;
432                 }
433         }
434
435         e.time   = htonll(time(NULL));
436         e.load1  = htons(load1);
437         e.load5  = htons(load5);
438         e.load15 = htons(load15);
439
440         return update_file(path, &e, sizeof(struct load_entry));
441 }
442
443 static int run_daemon(void)
444 {
445         FILE *info;
446         uint64_t rxb, txb, rxp, txp;
447         uint32_t udp, tcp, other;
448         uint16_t rate;
449         uint8_t rssi, noise;
450         float lf1, lf5, lf15;
451         char line[1024];
452         char ifname[16];
453         int i;
454         void *iw;
455         struct sigaction sa;
456
457         struct stat s;
458         const char *ipc = stat("/proc/net/nf_conntrack", &s)
459                 ? "/proc/net/ip_conntrack" : "/proc/net/nf_conntrack";
460
461         switch (fork())
462         {
463                 case -1:
464                         perror("fork()");
465                         return -1;
466
467                 case 0:
468                         if (chdir("/") < 0)
469                         {
470                                 perror("chdir()");
471                                 exit(1);
472                         }
473
474                         close(0);
475                         close(1);
476                         close(2);
477                         break;
478
479                 default:
480                         return 0;
481         }
482
483         /* setup USR1 signal handler to reset timer */
484         sa.sa_handler = reset_countdown;
485         sa.sa_flags   = SA_RESTART;
486         sigemptyset(&sa.sa_mask);
487         sigaction(SIGUSR1, &sa, NULL);
488
489         /* write pid */
490         if (writepid())
491         {
492                 fprintf(stderr, "Failed to write pid file: %s\n", strerror(errno));
493                 return 1;
494         }
495
496         /* initialize iwinfo */
497         iw = iwinfo_open();
498
499         /* go */
500         for (reset_countdown(0); countdown >= 0; countdown--)
501         {
502                 /* alter progname for ps, top */
503                 memset(progname, 0, prognamelen);
504                 snprintf(progname, prognamelen, "luci-bwc %d", countdown);
505
506                 if ((info = fopen("/proc/net/dev", "r")) != NULL)
507                 {
508                         while (fgets(line, sizeof(line), info))
509                         {
510                                 if (strchr(line, '|'))
511                                         continue;
512
513                                 if (sscanf(line, IF_SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp))
514                                 {
515                                         if (strncmp(ifname, "lo", sizeof(ifname)))
516                                                 update_ifstat(ifname, rxb, rxp, txb, txp);
517                                 }
518                         }
519
520                         fclose(info);
521                 }
522
523                 if (iw)
524                 {
525                         for (i = 0; i < 5; i++)
526                         {
527 #define iwinfo_checkif(pattern) \
528                                 do {                                                      \
529                                         snprintf(ifname, sizeof(ifname), pattern, i);         \
530                                         if (iwinfo_update(iw, ifname, &rate, &rssi, &noise))  \
531                                         {                                                     \
532                                                 update_radiostat(ifname, rate, rssi, noise);      \
533                                                 continue;                                         \
534                                         }                                                     \
535                                 } while(0)
536
537                                 iwinfo_checkif("wlan%d");
538                                 iwinfo_checkif("ath%d");
539                                 iwinfo_checkif("wl%d");
540                         }
541                 }
542
543                 if ((info = fopen(ipc, "r")) != NULL)
544                 {
545                         udp   = 0;
546                         tcp   = 0;
547                         other = 0;
548
549                         while (fgets(line, sizeof(line), info))
550                         {
551                                 if (strstr(line, "TIME_WAIT"))
552                                         continue;
553
554                                 if (sscanf(line, "%*s %*d %s", ifname) || sscanf(line, "%s %*d", ifname))
555                                 {
556                                         if (!strcmp(ifname, "tcp"))
557                                                 tcp++;
558                                         else if (!strcmp(ifname, "udp"))
559                                                 udp++;
560                                         else
561                                                 other++;
562                                 }
563                         }
564
565                         update_cnstat(udp, tcp, other);
566
567                         fclose(info);
568                 }
569
570                 if ((info = fopen("/proc/loadavg", "r")) != NULL)
571                 {
572                         if (fscanf(info, LD_SCAN_PATTERN, &lf1, &lf5, &lf15))
573                         {
574                                 update_ldstat((uint16_t)(lf1  * 100),
575                                                           (uint16_t)(lf5  * 100),
576                                                           (uint16_t)(lf15 * 100));
577                         }
578
579                         fclose(info);
580                 }
581
582                 sleep(STEP_TIME);
583         }
584
585         unlink(PID_PATH);
586
587         if (iw)
588                 iwinfo_close(iw);
589
590         return 0;
591 }
592
593 static void check_daemon(void)
594 {
595         int pid;
596
597         if ((pid = readpid()) < 0 || kill(pid, 0) < 0)
598         {
599                 /* daemon ping failed, try to start it up */
600                 if (run_daemon())
601                 {
602                         fprintf(stderr,
603                                 "Failed to ping daemon and unable to start it up: %s\n",
604                                 strerror(errno));
605
606                         exit(1);
607                 }
608         }
609         else if (kill(pid, SIGUSR1))
610         {
611                 fprintf(stderr, "Failed to send signal: %s\n", strerror(errno));
612                 exit(2);
613         }
614 }
615
616 static int run_dump_ifname(const char *ifname)
617 {
618         int i;
619         char path[1024];
620         struct file_map m;
621         struct traffic_entry *e;
622
623         check_daemon();
624         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
625
626         if (mmap_file(path, sizeof(struct traffic_entry), &m))
627         {
628                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
629                 return 1;
630         }
631
632         for (i = 0; i < m.size; i += sizeof(struct traffic_entry))
633         {
634                 e = (struct traffic_entry *) &m.mmap[i];
635
636                 if (!e->time)
637                         continue;
638
639                 printf("[ %" PRIu64 ", %" PRIu64 ", %" PRIu64
640                            ", %" PRIu64 ", %" PRIu64 " ]%s\n",
641                         ntohll(e->time),
642                         ntohll(e->rxb), ntohll(e->rxp),
643                         ntohll(e->txb), ntohll(e->txp),
644                         ((i + sizeof(struct traffic_entry)) < m.size) ? "," : "");
645         }
646
647         umap_file(&m);
648
649         return 0;
650 }
651
652 static int run_dump_radio(const char *ifname)
653 {
654         int i;
655         char path[1024];
656         struct file_map m;
657         struct radio_entry *e;
658
659         check_daemon();
660         snprintf(path, sizeof(path), DB_RD_FILE, ifname);
661
662         if (mmap_file(path, sizeof(struct radio_entry), &m))
663         {
664                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
665                 return 1;
666         }
667
668         for (i = 0; i < m.size; i += sizeof(struct radio_entry))
669         {
670                 e = (struct radio_entry *) &m.mmap[i];
671
672                 if (!e->time)
673                         continue;
674
675                 printf("[ %" PRIu64 ", %d, %d, %d ]%s\n",
676                         ntohll(e->time),
677                         e->rate, e->rssi, e->noise,
678                         ((i + sizeof(struct radio_entry)) < m.size) ? "," : "");
679         }
680
681         umap_file(&m);
682
683         return 0;
684 }
685
686 static int run_dump_conns(void)
687 {
688         int i;
689         char path[1024];
690         struct file_map m;
691         struct conn_entry *e;
692
693         check_daemon();
694         snprintf(path, sizeof(path), DB_CN_FILE);
695
696         if (mmap_file(path, sizeof(struct conn_entry), &m))
697         {
698                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
699                 return 1;
700         }
701
702         for (i = 0; i < m.size; i += sizeof(struct conn_entry))
703         {
704                 e = (struct conn_entry *) &m.mmap[i];
705
706                 if (!e->time)
707                         continue;
708
709                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
710                         ntohll(e->time), ntohl(e->udp),
711                         ntohl(e->tcp), ntohl(e->other),
712                         ((i + sizeof(struct conn_entry)) < m.size) ? "," : "");
713         }
714
715         umap_file(&m);
716
717         return 0;
718 }
719
720 static int run_dump_load(void)
721 {
722         int i;
723         char path[1024];
724         struct file_map m;
725         struct load_entry *e;
726
727         check_daemon();
728         snprintf(path, sizeof(path), DB_LD_FILE);
729
730         if (mmap_file(path, sizeof(struct load_entry), &m))
731         {
732                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
733                 return 1;
734         }
735
736         for (i = 0; i < m.size; i += sizeof(struct load_entry))
737         {
738                 e = (struct load_entry *) &m.mmap[i];
739
740                 if (!e->time)
741                         continue;
742
743                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
744                         ntohll(e->time),
745                         ntohs(e->load1), ntohs(e->load5), ntohs(e->load15),
746                         ((i + sizeof(struct load_entry)) < m.size) ? "," : "");
747         }
748
749         umap_file(&m);
750
751         return 0;
752 }
753
754
755 int main(int argc, char *argv[])
756 {
757         int opt;
758
759         progname = argv[0];
760         prognamelen = -1;
761
762         for (opt = 0; opt < argc; opt++)
763                 prognamelen += 1 + strlen(argv[opt]);
764
765         while ((opt = getopt(argc, argv, "t:i:r:cl")) > -1)
766         {
767                 switch (opt)
768                 {
769                         case 't':
770                                 timeout = atoi(optarg);
771                                 break;
772
773                         case 'i':
774                                 if (optarg)
775                                         return run_dump_ifname(optarg);
776                                 break;
777
778                         case 'r':
779                                 if (optarg)
780                                         return run_dump_radio(optarg);
781                                 break;
782
783                         case 'c':
784                                 return run_dump_conns();
785
786                         case 'l':
787                                 return run_dump_load();
788
789                         default:
790                                 break;
791                 }
792         }
793
794         fprintf(stderr,
795                 "Usage:\n"
796                 "       %s [-t timeout] -i ifname\n"
797                 "       %s [-t timeout] -r radiodev\n"
798                 "       %s [-t timeout] -c\n"
799                 "       %s [-t timeout] -l\n",
800                         argv[0], argv[0], argv[0], argv[0]
801         );
802
803         return 1;
804 }