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