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