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