modules/admin-full: implement load graph
[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
29 #include <sys/stat.h>
30 #include <sys/mman.h>
31 #include <arpa/inet.h>
32
33
34 #define STEP_COUNT      60
35 #define STEP_TIME       1
36
37 #define DB_PATH         "/var/lib/luci-bwc"
38 #define DB_IF_FILE      DB_PATH "/if/%s"
39 #define DB_LD_FILE      DB_PATH "/load"
40
41 #define IF_SCAN_PATTERN \
42         " %[^ :]:%" SCNu64 " %" SCNu64 \
43         " %*d %*d %*d %*d %*d %*d" \
44         " %" SCNu64 " %" SCNu64
45
46 #define LD_SCAN_PATTERN \
47         "%f %f %f"
48
49
50 struct traffic_entry {
51         uint64_t time;
52         uint64_t rxb;
53         uint64_t rxp;
54         uint64_t txb;
55         uint64_t txp;
56 };
57
58 struct load_entry {
59         uint64_t time;
60         uint16_t load1;
61         uint16_t load5;
62         uint16_t load15;
63 };
64
65
66 static uint64_t htonll(uint64_t value)
67 {
68         int num = 1;
69
70         if (*(char *)&num == 1)
71                 return htonl((uint32_t)(value & 0xFFFFFFFF)) |
72                        htonl((uint32_t)(value >> 32));
73
74         return value;
75 }
76
77 #define ntohll htonll
78
79
80 static int init_directory(char *path)
81 {
82         char *p = path;
83
84         for (p = &path[1]; *p; p++)
85         {
86                 if (*p == '/')
87                 {
88                         *p = 0;
89
90                         if (mkdir(path, 0700) && (errno != EEXIST))
91                                 return -1;
92
93                         *p = '/';
94                 }
95         }
96
97         return 0;
98 }
99
100 static int update_file(const char *path, void *entry, int esize)
101 {
102         int rv = -1;
103         int file;
104         char *map;
105
106         if ((file = open(path, O_RDWR)) >= 0)
107         {
108                 map = mmap(NULL, esize * STEP_COUNT, PROT_READ | PROT_WRITE,
109                                    MAP_SHARED | MAP_LOCKED, file, 0);
110
111                 if ((map != NULL) && (map != MAP_FAILED))
112                 {
113                         memmove(map, map + esize, esize * (STEP_COUNT-1));
114                         memcpy(map + esize * (STEP_COUNT-1), entry, esize);
115
116                         munmap(map, esize * STEP_COUNT);
117
118                         rv = 0;
119                 }
120
121                 close(file);
122         }
123
124         return rv;
125 }
126
127
128 static int init_ifstat(const char *ifname)
129 {
130         int i, file;
131         char path[1024];
132         struct traffic_entry e = { 0 };
133
134         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
135
136         if (init_directory(path))
137                 return -1;
138
139         if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
140         {
141                 for (i = 0; i < STEP_COUNT; i++)
142                 {
143                         if (write(file, &e, sizeof(struct traffic_entry)) < 0)
144                                 break;
145                 }
146
147                 close(file);
148
149                 return 0;
150         }
151
152         return -1;
153 }
154
155 static int update_ifstat(
156         const char *ifname, uint64_t rxb, uint64_t rxp, uint64_t txb, uint64_t txp
157 ) {
158         char path[1024];
159
160         struct stat s;
161         struct traffic_entry e;
162
163         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
164
165         if (stat(path, &s))
166         {
167                 if (init_ifstat(ifname))
168                 {
169                         fprintf(stderr, "Failed to init %s: %s\n",
170                                         path, strerror(errno));
171
172                         return -1;
173                 }
174         }
175
176         e.time = htonll(time(NULL));
177         e.rxb  = htonll(rxb);
178         e.rxp  = htonll(rxp);
179         e.txb  = htonll(txb);
180         e.txp  = htonll(txp);
181
182         return update_file(path, &e, sizeof(struct traffic_entry));
183 }
184
185 static int init_ldstat(void)
186 {
187         int i, file;
188         char path[1024];
189         struct load_entry e = { 0 };
190
191         snprintf(path, sizeof(path), DB_LD_FILE);
192
193         if (init_directory(path))
194                 return -1;
195
196         if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
197         {
198                 for (i = 0; i < STEP_COUNT; i++)
199                 {
200                         if (write(file, &e, sizeof(struct load_entry)) < 0)
201                                 break;
202                 }
203
204                 close(file);
205
206                 return 0;
207         }
208
209         return -1;
210 }
211
212 static int update_ldstat(uint16_t load1, uint16_t load5, uint16_t load15)
213 {
214         char path[1024];
215
216         struct stat s;
217         struct load_entry e;
218
219         snprintf(path, sizeof(path), DB_LD_FILE);
220
221         if (stat(path, &s))
222         {
223                 if (init_ldstat())
224                 {
225                         fprintf(stderr, "Failed to init %s: %s\n",
226                                         path, strerror(errno));
227
228                         return -1;
229                 }
230         }
231
232         e.time   = htonll(time(NULL));
233         e.load1  = htons(load1);
234         e.load5  = htons(load5);
235         e.load15 = htons(load15);
236
237         return update_file(path, &e, sizeof(struct load_entry));
238 }
239
240 static int run_daemon(int nofork)
241 {
242         FILE *info;
243         uint64_t rxb, txb, rxp, txp;
244         float lf1, lf5, lf15;
245         char line[1024];
246         char ifname[16];
247
248
249         if (!nofork)
250         {
251                 switch (fork())
252                 {
253                         case -1:
254                                 perror("fork()");
255                                 return -1;
256
257                         case 0:
258                                 if (chdir("/") < 0)
259                                 {
260                                         perror("chdir()");
261                                         exit(1);
262                                 }
263
264                                 close(0);
265                                 close(1);
266                                 close(2);
267                                 break;
268
269                         default:
270                                 exit(0);
271                 }
272         }
273
274
275         /* go */
276         while (1)
277         {
278                 if ((info = fopen("/proc/net/dev", "r")) != NULL)
279                 {
280                         while (fgets(line, sizeof(line), info))
281                         {
282                                 if (strchr(line, '|'))
283                                         continue;
284
285                                 if (sscanf(line, IF_SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp))
286                                 {
287                                         if (strncmp(ifname, "lo", sizeof(ifname)))
288                                                 update_ifstat(ifname, rxb, rxp, txb, txp);
289                                 }
290                         }
291
292                         fclose(info);
293                 }
294
295                 if ((info = fopen("/proc/loadavg", "r")) != NULL)
296                 {
297                         if (fscanf(info, LD_SCAN_PATTERN, &lf1, &lf5, &lf15))
298                         {
299                                 update_ldstat((uint16_t)(lf1  * 100),
300                                                           (uint16_t)(lf5  * 100),
301                                                           (uint16_t)(lf15 * 100));
302                         }
303
304                         fclose(info);
305                 }
306
307                 sleep(STEP_TIME);
308         }
309 }
310
311 static int run_dump_ifname(const char *ifname)
312 {
313         int rv = 1;
314
315         int i, file;
316         int entrysize = sizeof(struct traffic_entry);
317         int mapsize = STEP_COUNT * entrysize;
318
319         char path[1024];
320         char *map;
321
322         struct traffic_entry *e;
323
324         snprintf(path, sizeof(path), DB_IF_FILE, ifname);
325
326         if ((file = open(path, O_RDONLY)) >= 0)
327         {
328                 map = mmap(NULL, mapsize, PROT_READ, MAP_SHARED | MAP_LOCKED, file, 0);
329
330                 if ((map != NULL) && (map != MAP_FAILED))
331                 {
332                         for (i = 0; i < mapsize; i += entrysize)
333                         {
334                                 e = (struct traffic_entry *) &map[i];
335
336                                 if (!e->time)
337                                         continue;
338
339                                 printf("[ %" PRIu64 ", %" PRIu64 ", %" PRIu64
340                                            ", %" PRIu64 ", %" PRIu64 " ]%s\n",
341                                         ntohll(e->time),
342                                         ntohll(e->rxb), ntohll(e->rxp),
343                                         ntohll(e->txb), ntohll(e->txp),
344                                         ((i + entrysize) < mapsize) ? "," : "");
345                         }
346
347                         munmap(map, mapsize);
348                         rv = 0;
349                 }
350
351                 close(file);
352         }
353         else
354         {
355                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
356         }
357
358         return rv;
359 }
360
361 static int run_dump_load(void)
362 {
363         int rv = 1;
364
365         int i, file;
366         int entrysize = sizeof(struct load_entry);
367         int mapsize = STEP_COUNT * entrysize;
368
369         char path[1024];
370         char *map;
371
372         struct load_entry *e;
373
374         snprintf(path, sizeof(path), DB_LD_FILE);
375
376         if ((file = open(path, O_RDONLY)) >= 0)
377         {
378                 map = mmap(NULL, mapsize, PROT_READ, MAP_SHARED | MAP_LOCKED, file, 0);
379
380                 if ((map != NULL) && (map != MAP_FAILED))
381                 {
382                         for (i = 0; i < mapsize; i += entrysize)
383                         {
384                                 e = (struct load_entry *) &map[i];
385
386                                 if (!e->time)
387                                         continue;
388
389                                 printf("[ %" PRIu64 ", %u, %u, %u ]%s\n",
390                                         ntohll(e->time),
391                                         ntohs(e->load1), ntohs(e->load5), ntohs(e->load15),
392                                         ((i + entrysize) < mapsize) ? "," : "");
393                         }
394
395                         munmap(map, mapsize);
396                         rv = 0;
397                 }
398
399                 close(file);
400         }
401         else
402         {
403                 fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
404         }
405
406         return rv;
407 }
408
409
410 int main(int argc, char *argv[])
411 {
412         int opt;
413         int daemon = 0;
414         int nofork = 0;
415         int iprint = 0;
416         int lprint = 0;
417         char *ifname = NULL;
418
419         while ((opt = getopt(argc, argv, "dfi:l")) > -1)
420         {
421                 switch (opt)
422                 {
423                         case 'd':
424                                 daemon = 1;
425                                 break;
426
427                         case 'f':
428                                 nofork = 1;
429                                 break;
430
431                         case 'i':
432                                 iprint = 1;
433                                 ifname = optarg;
434                                 break;
435
436                         case 'l':
437                                 lprint = 1;
438                                 break;
439
440                         default:
441                                 break;
442                 }
443         }
444
445         if (daemon)
446                 return run_daemon(nofork);
447
448         else if (iprint && ifname)
449                 return run_dump_ifname(ifname);
450
451         else if (lprint)
452                 return run_dump_load();
453
454         else
455                 fprintf(stderr,
456                         "Usage:\n"
457                         "       %s -d [-f]\n"
458                         "       %s -i ifname\n"
459                         "       %s -l\n",
460                                 argv[0], argv[0], argv[0]
461                 );
462
463         return 1;
464 }