modules/admin-full: simplify and fix progname change
[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 char *progname;
146 static int prognamelen;
147
148
149 static int init_directory(char *path)
150 {
151         char *p = path;
152
153         for (p = &path[1]; *p; p++)
154         {
155                 if (*p == '/')
156                 {
157                         *p = 0;
158
159                         if (mkdir(path, 0700) && (errno != EEXIST))
160                                 return -1;
161
162                         *p = '/';
163                 }
164         }
165
166         return 0;
167 }
168
169 static int init_file(char *path, int esize)
170 {
171         int i, file;
172         char buf[sizeof(struct traffic_entry)] = { 0 };
173
174         if (init_directory(path))
175                 return -1;
176
177         if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
178         {
179                 for (i = 0; i < STEP_COUNT; i++)
180                 {
181                         if (write(file, buf, esize) < 0)
182                                 break;
183                 }
184
185                 close(file);
186
187                 return 0;
188         }
189
190         return -1;
191 }
192
193 static inline uint64_t timeof(void *entry)
194 {
195         return ((struct traffic_entry *)entry)->time;
196 }
197
198 static int update_file(const char *path, void *entry, int esize)
199 {
200         int rv = -1;
201         int file;
202         char *map;
203
204         if ((file = open(path, O_RDWR)) >= 0)
205         {
206                 map = mmap(NULL, esize * STEP_COUNT, PROT_READ | PROT_WRITE,
207                                    MAP_SHARED | MAP_LOCKED, file, 0);
208
209                 if ((map != NULL) && (map != MAP_FAILED))
210                 {
211                         if (timeof(entry) > timeof(map + esize * (STEP_COUNT-1)))
212                         {
213                                 memmove(map, map + esize, esize * (STEP_COUNT-1));
214                                 memcpy(map + esize * (STEP_COUNT-1), entry, esize);
215                         }
216
217                         munmap(map, esize * STEP_COUNT);
218
219                         rv = 0;
220                 }
221
222                 close(file);
223         }
224
225         return rv;
226 }
227
228 static int mmap_file(const char *path, int esize, struct file_map *m)
229 {
230         m->fd   = -1;
231         m->size = -1;
232         m->mmap = NULL;
233
234         if ((m->fd = open(path, O_RDONLY)) >= 0)
235         {
236                 m->size = STEP_COUNT * esize;
237                 m->mmap = mmap(NULL, m->size, PROT_READ,
238                                            MAP_SHARED | MAP_LOCKED, m->fd, 0);
239
240                 if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
241                         return 0;
242         }
243
244         return -1;
245 }
246
247 static void umap_file(struct file_map *m)
248 {
249         if ((m->mmap != NULL) && (m->mmap != MAP_FAILED))
250                 munmap(m->mmap, m->size);
251
252         if (m->fd > -1)
253                 close(m->fd);
254 }
255
256
257 static int update_ifstat(
258         const char *ifname, uint64_t rxb, uint64_t rxp, uint64_t txb, uint64_t txp
259 ) {
260         char path[1024];
261
262         struct stat s;
263         struct traffic_entry e;
264
265         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
266
267         if (stat(path, &s))
268         {
269                 if (init_file(path, sizeof(struct traffic_entry)))
270                 {
271                         fprintf(stderr, "Failed to init %s: %s\n",
272                                         path, strerror(errno));
273
274                         return -1;
275                 }
276         }
277
278         e.time = htonll(time(NULL));
279         e.rxb  = htonll(rxb);
280         e.rxp  = htonll(rxp);
281         e.txb  = htonll(txb);
282         e.txp  = htonll(txp);
283
284         return update_file(path, &e, sizeof(struct traffic_entry));
285 }
286
287 static int update_cnstat(uint32_t udp, uint32_t tcp, uint32_t other)
288 {
289         char path[1024];
290
291         struct stat s;
292         struct conn_entry e;
293
294         snprintf(path, sizeof(path), DB_CN_FILE);
295
296         if (stat(path, &s))
297         {
298                 if (init_file(path, sizeof(struct conn_entry)))
299                 {
300                         fprintf(stderr, "Failed to init %s: %s\n",
301                                         path, strerror(errno));
302
303                         return -1;
304                 }
305         }
306
307         e.time  = htonll(time(NULL));
308         e.udp   = htonl(udp);
309         e.tcp   = htonl(tcp);
310         e.other = htonl(other);
311
312         return update_file(path, &e, sizeof(struct conn_entry));
313 }
314
315 static int update_ldstat(uint16_t load1, uint16_t load5, uint16_t load15)
316 {
317         char path[1024];
318
319         struct stat s;
320         struct load_entry e;
321
322         snprintf(path, sizeof(path), DB_LD_FILE);
323
324         if (stat(path, &s))
325         {
326                 if (init_file(path, sizeof(struct load_entry)))
327                 {
328                         fprintf(stderr, "Failed to init %s: %s\n",
329                                         path, strerror(errno));
330
331                         return -1;
332                 }
333         }
334
335         e.time   = htonll(time(NULL));
336         e.load1  = htons(load1);
337         e.load5  = htons(load5);
338         e.load15 = htons(load15);
339
340         return update_file(path, &e, sizeof(struct load_entry));
341 }
342
343 static int run_daemon(void)
344 {
345         FILE *info;
346         uint64_t rxb, txb, rxp, txp;
347         uint32_t udp, tcp, other;
348         float lf1, lf5, lf15;
349         char line[1024];
350         char ifname[16];
351
352         struct sigaction sa;
353
354         struct stat s;
355         const char *ipc = stat("/proc/net/nf_conntrack", &s)
356                 ? "/proc/net/ip_conntrack" : "/proc/net/nf_conntrack";
357
358         switch (fork())
359         {
360                 case -1:
361                         perror("fork()");
362                         return -1;
363
364                 case 0:
365                         if (chdir("/") < 0)
366                         {
367                                 perror("chdir()");
368                                 exit(1);
369                         }
370
371                         close(0);
372                         close(1);
373                         close(2);
374                         break;
375
376                 default:
377                         return 0;
378         }
379
380         /* setup USR1 signal handler to reset timer */
381         sa.sa_handler = reset_countdown;
382         sa.sa_flags   = SA_RESTART;
383         sigemptyset(&sa.sa_mask);
384         sigaction(SIGUSR1, &sa, NULL);
385
386         /* write pid */
387         if (writepid())
388         {
389                 fprintf(stderr, "Failed to write pid file: %s\n", strerror(errno));
390                 return 1;
391         }
392
393         /* go */
394         for (reset_countdown(0); countdown >= 0; countdown--)
395         {
396                 /* alter progname for ps, top */
397                 memset(progname, 0, prognamelen);
398                 snprintf(progname, prognamelen, "luci-bwc %d", countdown);
399
400                 if ((info = fopen("/proc/net/dev", "r")) != NULL)
401                 {
402                         while (fgets(line, sizeof(line), info))
403                         {
404                                 if (strchr(line, '|'))
405                                         continue;
406
407                                 if (sscanf(line, IF_SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp))
408                                 {
409                                         if (strncmp(ifname, "lo", sizeof(ifname)))
410                                                 update_ifstat(ifname, rxb, rxp, txb, txp);
411                                 }
412                         }
413
414                         fclose(info);
415                 }
416
417                 if ((info = fopen(ipc, "r")) != NULL)
418                 {
419                         udp   = 0;
420                         tcp   = 0;
421                         other = 0;
422
423                         while (fgets(line, sizeof(line), info))
424                         {
425                                 if (strstr(line, "TIME_WAIT"))
426                                         continue;
427
428                                 if (sscanf(line, "%*s %*d %s", ifname) || sscanf(line, "%s %*d", ifname))
429                                 {
430                                         if (!strcmp(ifname, "tcp"))
431                                                 tcp++;
432                                         else if (!strcmp(ifname, "udp"))
433                                                 udp++;
434                                         else
435                                                 other++;
436                                 }
437                         }
438
439                         update_cnstat(udp, tcp, other);
440
441                         fclose(info);
442                 }
443
444                 if ((info = fopen("/proc/loadavg", "r")) != NULL)
445                 {
446                         if (fscanf(info, LD_SCAN_PATTERN, &lf1, &lf5, &lf15))
447                         {
448                                 update_ldstat((uint16_t)(lf1  * 100),
449                                                           (uint16_t)(lf5  * 100),
450                                                           (uint16_t)(lf15 * 100));
451                         }
452
453                         fclose(info);
454                 }
455
456                 sleep(STEP_TIME);
457         }
458
459         unlink(PID_PATH);
460
461         return 0;
462 }
463
464 static void check_daemon(void)
465 {
466         int pid;
467
468         if ((pid = readpid()) < 0 || kill(pid, 0) < 0)
469         {
470                 /* daemon ping failed, try to start it up */
471                 if (run_daemon())
472                 {
473                         fprintf(stderr,
474                                 "Failed to ping daemon and unable to start it up: %s\n",
475                                 strerror(errno));
476
477                         exit(1);
478                 }
479         }
480         else if (kill(pid, SIGUSR1))
481         {
482                 fprintf(stderr, "Failed to send signal: %s\n", strerror(errno));
483                 exit(2);
484         }
485 }
486
487 static int run_dump_ifname(const char *ifname)
488 {
489         int i;
490         char path[1024];
491         struct file_map m;
492         struct traffic_entry *e;
493
494         check_daemon();
495         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
496
497         if (mmap_file(path, sizeof(struct traffic_entry), &m))
498         {
499                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
500                 return 1;
501         }
502
503         for (i = 0; i < m.size; i += sizeof(struct traffic_entry))
504         {
505                 e = (struct traffic_entry *) &m.mmap[i];
506
507                 if (!e->time)
508                         continue;
509
510                 printf("[ %" PRIu64 ", %" PRIu64 ", %" PRIu64
511                            ", %" PRIu64 ", %" PRIu64 " ]%s\n",
512                         ntohll(e->time),
513                         ntohll(e->rxb), ntohll(e->rxp),
514                         ntohll(e->txb), ntohll(e->txp),
515                         ((i + sizeof(struct traffic_entry)) < m.size) ? "," : "");
516         }
517
518         umap_file(&m);
519
520         return 0;
521 }
522
523 static int run_dump_conns(void)
524 {
525         int i;
526         char path[1024];
527         struct file_map m;
528         struct conn_entry *e;
529
530         check_daemon();
531         snprintf(path, sizeof(path), DB_CN_FILE);
532
533         if (mmap_file(path, sizeof(struct conn_entry), &m))
534         {
535                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
536                 return 1;
537         }
538
539         for (i = 0; i < m.size; i += sizeof(struct conn_entry))
540         {
541                 e = (struct conn_entry *) &m.mmap[i];
542
543                 if (!e->time)
544                         continue;
545
546                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
547                         ntohll(e->time), ntohl(e->udp),
548                         ntohl(e->tcp), ntohl(e->other),
549                         ((i + sizeof(struct conn_entry)) < m.size) ? "," : "");
550         }
551
552         umap_file(&m);
553
554         return 0;
555 }
556
557 static int run_dump_load(void)
558 {
559         int i;
560         char path[1024];
561         struct file_map m;
562         struct load_entry *e;
563
564         check_daemon();
565         snprintf(path, sizeof(path), DB_LD_FILE);
566
567         if (mmap_file(path, sizeof(struct load_entry), &m))
568         {
569                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
570                 return 1;
571         }
572
573         for (i = 0; i < m.size; i += sizeof(struct load_entry))
574         {
575                 e = (struct load_entry *) &m.mmap[i];
576
577                 if (!e->time)
578                         continue;
579
580                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
581                         ntohll(e->time),
582                         ntohs(e->load1), ntohs(e->load5), ntohs(e->load15),
583                         ((i + sizeof(struct load_entry)) < m.size) ? "," : "");
584         }
585
586         umap_file(&m);
587
588         return 0;
589 }
590
591
592 int main(int argc, char *argv[])
593 {
594         int opt;
595
596         progname = argv[0];
597         prognamelen = -1;
598
599         for (opt = 0; opt < argc; opt++)
600                 prognamelen += 1 + strlen(argv[opt]);
601
602         while ((opt = getopt(argc, argv, "t:i:cl")) > -1)
603         {
604                 switch (opt)
605                 {
606                         case 't':
607                                 timeout = atoi(optarg);
608                                 break;
609
610                         case 'i':
611                                 if (optarg)
612                                         return run_dump_ifname(optarg);
613                                 break;
614
615                         case 'c':
616                                 return run_dump_conns();
617
618                         case 'l':
619                                 return run_dump_load();
620
621                         default:
622                                 break;
623                 }
624         }
625
626         fprintf(stderr,
627                 "Usage:\n"
628                 "       %s [-t timeout] -i ifname\n"
629                 "       %s [-t timeout] -c\n"
630                 "       %s [-t timeout] -l\n",
631                         argv[0], argv[0], argv[0]
632         );
633
634         return 1;
635 }