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