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