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