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 }