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