[changes from birch]
[darkstat] / graph_db.c
1 /* darkstat 3
2 * copyright (c) 2006-2008 Emil Mikulic.
3 *
4 * graph_db.c: round robin database for graph data
5 *
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
8 */
9
10 #include <sys/types.h>
11 #include <netinet/in.h> /* for in_addr_t (db.h needs it) */
12
13 #include "cap.h"
14 #include "conv.h"
15 #include "darkstat.h"
16 #include "db.h"
17 #include "acct.h"
18 #include "err.h"
19 #include "str.h"
20 #include "html.h" /* FIXME: should be pushed into a .c file? */
21 #include "graph_db.h"
22 #include "now.h"
23
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h> /* for memcpy() */
27
28 #define GRAPH_WIDTH "320"
29 #define GRAPH_HEIGHT "200"
30
31 extern const char *interface;
32
33 struct graph {
34 uint64_t *in, *out;
35 unsigned int offset; /* i.e. seconds start at 0, days start at 1 */
36 unsigned int pos, num_bars;
37 const char *unit;
38 unsigned int bar_secs; /* one bar represents <n> seconds */
39 uint64_t max_val; /* if non-zero forces graph to command line speed */
40 };
41
42 static struct graph
43 graph_secs = {NULL, NULL, 0, 0, 60, "seconds", 1},
44 graph_mins = {NULL, NULL, 0, 0, 60, "minutes", 60},
45 graph_hrs = {NULL, NULL, 0, 0, 24, "hours", 3600},
46 graph_days = {NULL, NULL, 1, 0, 31, "days", 86400};
47
48 static struct graph *graph_db[] = {
49 &graph_secs, &graph_mins, &graph_hrs, &graph_days
50 };
51
52 static unsigned int graph_db_size = sizeof(graph_db)/sizeof(*graph_db);
53
54 static time_t start_time, last_time;
55
56 void
57 graph_init(unsigned long graph_max_val)
58 {
59 unsigned int i;
60 for (i=0; i<graph_db_size; i++) {
61 graph_db[i]->in = xmalloc(sizeof(uint64_t) * graph_db[i]->num_bars);
62 graph_db[i]->out = xmalloc(sizeof(uint64_t) * graph_db[i]->num_bars);
63 graph_db[i]->max_val = (uint64_t) graph_max_val;
64 }
65 start_time = time(NULL);
66 graph_reset();
67 }
68
69 static void
70 zero_graph(struct graph *g)
71 {
72 memset(g->in, 0, sizeof(uint64_t) * g->num_bars);
73 memset(g->out, 0, sizeof(uint64_t) * g->num_bars);
74 }
75
76 void
77 graph_reset(void)
78 {
79 unsigned int i;
80 for (i=0; i<graph_db_size; i++)
81 zero_graph(graph_db[i]);
82 last_time = 0;
83 }
84
85 void
86 graph_free(void)
87 {
88 unsigned int i;
89 for (i=0; i<graph_db_size; i++) {
90 free(graph_db[i]->in);
91 free(graph_db[i]->out);
92 }
93 }
94
95 void
96 graph_acct(uint64_t amount, enum graph_dir dir)
97 {
98 unsigned int i;
99 for (i=0; i<graph_db_size; i++)
100 switch (dir) {
101 case GRAPH_IN: graph_db[i]->in[ graph_db[i]->pos ] += amount; break;
102 case GRAPH_OUT: graph_db[i]->out[ graph_db[i]->pos ] += amount; break;
103 default: errx(1, "unknown graph_dir in graph_acct: %d", dir);
104 }
105 }
106
107 /* Advance a graph: advance the pos, zeroing out bars as we move. */
108 static void
109 advance(struct graph *g, const unsigned int pos)
110 {
111 if (g->pos == pos)
112 return; /* didn't need to advance */
113 do {
114 g->pos = (g->pos + 1) % g->num_bars;
115 g->in[g->pos] = g->out[g->pos] = 0;
116 } while (g->pos != pos);
117 }
118
119 /* Rotate a graph: rotate all bars so that the bar at the current pos is moved
120 * to the newly given pos. This is non-destructive. */
121 static void
122 rotate(struct graph *g, const unsigned int pos)
123 {
124 uint64_t *tmp;
125 unsigned int i, ofs;
126 size_t size;
127
128 if (pos == g->pos)
129 return; /* nothing to rotate */
130
131 size = sizeof(*tmp) * g->num_bars;
132 tmp = xmalloc(size);
133 ofs = g->num_bars + pos - g->pos;
134
135 for (i=0; i<g->num_bars; i++)
136 tmp[ (i+ofs) % g->num_bars ] = g->in[i];
137 memcpy(g->in, tmp, size);
138
139 for (i=0; i<g->num_bars; i++)
140 tmp[ (i+ofs) % g->num_bars ] = g->out[i];
141 memcpy(g->out, tmp, size);
142
143 free(tmp);
144 assert(pos == ( (g->pos + ofs) % g->num_bars ));
145 g->pos = pos;
146 }
147
148 static void
149 graph_resync(const time_t new_time)
150 {
151 struct tm *tm;
152 /*
153 * If time went backwards, we assume that real time is continuous and that
154 * the time adjustment should only affect display. i.e., if we have:
155 *
156 * second 15: 12 bytes
157 * second 16: 345 bytes
158 * second 17: <-- current pos
159 *
160 * and time goes backwards to second 8, we will shift the graph around to
161 * get:
162 *
163 * second 6: 12 bytes
164 * second 7: 345 bytes
165 * second 8: <-- current pos
166 *
167 * Note that we don't make any corrections for time being stepped forward.
168 * We rely on graph advancement to happen at the correct real time to
169 * account for, for example, bandwidth used per day.
170 */
171 assert(new_time < last_time);
172
173 tm = localtime(&new_time);
174 if (tm->tm_sec == 60)
175 tm->tm_sec = 59; /* mis-handle leap seconds */
176
177 rotate(&graph_secs, tm->tm_sec);
178 rotate(&graph_mins, tm->tm_min);
179 rotate(&graph_hrs, tm->tm_hour);
180 rotate(&graph_days, tm->tm_mday - 1);
181
182 last_time = new_time;
183 }
184
185 void
186 graph_rotate(void)
187 {
188 time_t t, td;
189 struct tm *tm;
190 unsigned int i;
191
192 t = now;
193
194 if (last_time == 0) {
195 verbosef("first rotate");
196 last_time = t;
197 tm = localtime(&t);
198 if (tm->tm_sec == 60)
199 tm->tm_sec = 59; /* mis-handle leap seconds */
200
201 graph_secs.pos = tm->tm_sec;
202 graph_mins.pos = tm->tm_min;
203 graph_hrs.pos = tm->tm_hour;
204 graph_days.pos = tm->tm_mday - 1;
205 return;
206 }
207
208 if (t == last_time)
209 return; /* superfluous rotate */
210
211 if (t < last_time) {
212 verbosef("time went backwards! (from %u to %u, offset is %d)",
213 (unsigned int)last_time, (unsigned int)t, (int)(t - last_time));
214 graph_resync(t);
215 return;
216 }
217
218 /* else, normal rotation */
219 td = t - last_time;
220 last_time = t;
221 tm = localtime(&t);
222 if (tm->tm_sec == 60)
223 tm->tm_sec = 59; /* mis-handle leap seconds */
224
225 /* zero out graphs which have been completely rotated through */
226 for (i=0; i<graph_db_size; i++)
227 if (td >= (int)(graph_db[i]->num_bars * graph_db[i]->bar_secs))
228 zero_graph(graph_db[i]);
229
230 /* advance the current position, zeroing up to it */
231 advance(&graph_secs, tm->tm_sec);
232 advance(&graph_mins, tm->tm_min);
233 advance(&graph_hrs, tm->tm_hour);
234 advance(&graph_days, tm->tm_mday - 1);
235 }
236
237 /* ---------------------------------------------------------------------------
238 * Database Import: Grab graphs from a file provided by the caller.
239 *
240 * This function will retrieve the data sans the header. We expect the caller
241 * to have validated the header of the segment, and left the file position at
242 * the start of the data.
243 */
244 int
245 graph_import(const int fd)
246 {
247 uint64_t last;
248 unsigned int i, j;
249
250 if (!read64(fd, &last)) return 0;
251 last_time = (time_t)last;
252
253 for (i=0; i<graph_db_size; i++) {
254 unsigned char num_bars, pos;
255 unsigned int filepos = xtell(fd);
256
257 if (!read8(fd, &num_bars)) return 0;
258 if (!read8(fd, &pos)) return 0;
259
260 verbosef("at file pos %u, importing graph with %u bars",
261 filepos, (unsigned int)num_bars);
262
263 if (pos >= num_bars) {
264 warn("pos is %u, should be < num_bars which is %u",
265 (unsigned int)pos, (unsigned int)num_bars);
266 return 0;
267 }
268
269 if (graph_db[i]->num_bars != num_bars) {
270 warn("num_bars is %u, expecting %u",
271 (unsigned int)num_bars, graph_db[i]->num_bars);
272 return 0;
273 }
274
275 graph_db[i]->pos = pos;
276 for (j=0; j<num_bars; j++) {
277 if (!read64(fd, &(graph_db[i]->in[j]))) return 0;
278 if (!read64(fd, &(graph_db[i]->out[j]))) return 0;
279 }
280 }
281
282 return 1;
283 }
284
285 /* ---------------------------------------------------------------------------
286 * Database Export: Dump hosts_db into a file provided by the caller.
287 * The caller is responsible for writing out the header first.
288 */
289 int
290 graph_export(const int fd)
291 {
292 unsigned int i, j;
293
294 if (!write64(fd, (uint64_t)last_time)) return 0;
295 for (i=0; i<graph_db_size; i++) {
296 if (!write8(fd, graph_db[i]->num_bars)) return 0;
297 if (!write8(fd, graph_db[i]->pos)) return 0;
298
299 for (j=0; j<graph_db[i]->num_bars; j++) {
300 if (!write64(fd, graph_db[i]->in[j])) return 0;
301 if (!write64(fd, graph_db[i]->out[j])) return 0;
302 }
303 }
304 return 1;
305 }
306
307 /* ---------------------------------------------------------------------------
308 * Web interface: front page!
309 */
310 struct str *
311 html_front_page(void)
312 {
313 struct str *buf, *rf;
314 unsigned int i;
315 char start_when[100];
316
317 buf = str_make();
318 str_append(buf, html_header_1);
319 str_appendf(buf, "<title>" PACKAGE_STRING " : graphs (%s)</title>\n",
320 interface);
321 str_append(buf, "<script src=\"graph.js\" type=\"text/javascript\">"
322 "</script>\n");
323 str_append(buf, html_header_2);
324 str_appendf(buf, "<h2 class=\"pageheader\">Graphs (%s)</h2>\n", interface);
325
326 str_append(buf, "<p>\n");
327
328 str_append(buf, "<b>Running for</b> <span id=\"rf\">");
329 rf = length_of_time(now - start_time);
330 /* FIXME: use a more monotonic clock perhaps? */
331 str_appendstr(buf, rf);
332 str_free(rf);
333 str_append(buf, "</span>");
334
335 if (strftime(start_when, sizeof(start_when),
336 "%Y-%m-%d %H:%M:%S %Z%z", localtime(&start_time)) != 0)
337 str_appendf(buf, "<b>, since</b> %s", start_when);
338
339 str_appendf(buf,"<b>.</b><br/>\n"
340 "<b>Total</b> <span id=\"tb\">%'qu</span> <b>bytes, "
341 "in</b> <span id=\"tp\">%'qu</span> <b>packets.</b> "
342 "(<span id=\"pc\">%'u</span> <b>captured,</b> "
343 "<span id=\"pd\">%'u</span> <b>dropped)</b><br/>\n"
344 "</p>\n",
345 total_bytes,
346 total_packets,
347 pkts_recv, pkts_drop);
348
349 str_append(buf,
350 "<div id=\"graphs\">\n"
351 "Graphs require JavaScript.\n"
352 "<script type=\"text/javascript\">\n"
353 "//<![CDATA[\n"
354 "var graph_width = " GRAPH_WIDTH ";\n"
355 "var graph_height = " GRAPH_HEIGHT ";\n"
356 "var bar_gap = 1;\n"
357 "var graphs_uri = \"/graphs.xml\";\n"
358 "var graphs = [\n"
359 );
360
361 for (i=0; i<graph_db_size; i++)
362 str_appendf(buf,
363 " { id:\"g%u\", "
364 "name:\"%s\", "
365 "title:\"last %u %s\", "
366 "bar_secs:%u, "
367 "max_val:%qu"
368 " }%s\n",
369 i, graph_db[i]->unit, graph_db[i]->num_bars, graph_db[i]->unit,
370 graph_db[i]->bar_secs, (graph_db[i]->max_val * (uint64_t)graph_db[i]->bar_secs), (i < graph_db_size-1) ? "," : "");
371 /* trailing comma breaks on IE, makes the array one element longer */
372
373 str_append(buf,
374 "];\n"
375 "window.onload = graphs_init;\n"
376 "//]]>\n"
377 "</script>\n"
378 "</div>\n"
379 );
380
381 str_append(buf, html_footer);
382 return (buf);
383 }
384
385 /* ---------------------------------------------------------------------------
386 * Web interface: graphs.xml
387 */
388 struct str *
389 xml_graphs(void)
390 {
391 unsigned int i, j;
392 struct str *buf = str_make(), *rf;
393
394 str_appendf(buf, "<graphs tp=\"%qu\" tb=\"%qu\" pc=\"%u\" pd=\"%u\" rf=\"",
395 total_packets, total_bytes, pkts_recv, pkts_drop);
396 rf = length_of_time(now - start_time);
397 str_appendstr(buf, rf);
398 str_free(rf);
399 str_append(buf, "\">\n");
400
401 for (i=0; i<graph_db_size; i++) {
402 const struct graph *g = graph_db[i];
403
404 str_appendf(buf, "<%s>\n", g->unit);
405 j = g->pos;
406 do {
407 j = (j + 1) % g->num_bars;
408 /* <element pos="" in="" out=""/> */
409 str_appendf(buf, "<e p=\"%u\" i=\"%qu\" o=\"%qu\"/>\n",
410 g->offset + j, g->in[j], g->out[j]);
411 } while (j != g->pos);
412 str_appendf(buf, "</%s>\n", g->unit);
413 }
414 str_append(buf, "</graphs>\n");
415 return (buf);
416 }
417
418 /* vim:set ts=3 sw=3 tw=78 expandtab: */