aef6d8ec0d0a15362d6658e7ccd021096d768627
[darkstat.git] / 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 "graph_db.h"
21 #include "now.h"
22 #include "opt.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", /*path_depth=*/0, /*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: */