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