ec3492710a0236aef366dd387b78388c4a6f6863
[oweals/gnunet.git] / src / statistics / gnunet-service-statistics.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009, 2010, 2012 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file statistics/gnunet-service-statistics.c
23  * @brief program that tracks statistics
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_bio_lib.h"
28 #include "gnunet_container_lib.h"
29 #include "gnunet_disk_lib.h"
30 #include "gnunet_getopt_lib.h"
31 #include "gnunet_protocols.h"
32 #include "gnunet_service_lib.h"
33 #include "gnunet_statistics_service.h"
34 #include "gnunet_strings_lib.h"
35 #include "gnunet_time_lib.h"
36 #include "statistics.h"
37
38 /**
39  * Watch entry.
40  */
41 struct WatchEntry
42 {
43
44   struct WatchEntry *next;
45
46   struct WatchEntry *prev;
47
48   struct GNUNET_SERVER_Client *client;
49
50   uint64_t last_value;
51
52   uint32_t wid;
53
54 };
55
56
57 /**
58  * Client entry.
59  */
60 struct ClientEntry
61 {
62
63   struct ClientEntry *next;
64
65   struct ClientEntry *prev;
66
67   struct GNUNET_SERVER_Client *client;
68
69   uint32_t max_wid;
70
71 };
72
73 /**
74  * Entry in the statistics list.
75  */
76 struct StatsEntry
77 {
78   /**
79    * This is a linked list.
80    */
81   struct StatsEntry *next;
82
83   /**
84    * Name of the service, points into the
85    * middle of msg.
86    */
87   const char *service;
88
89   /**
90    * Name for the value, points into
91    * the middle of msg.
92    */
93   const char *name;
94
95   /**
96    * Message that can be used to set this value,
97    * stored at the end of the memory used by
98    * this struct.
99    */
100   struct GNUNET_STATISTICS_SetMessage *msg;
101
102   /**
103    * Watch context for changes to this
104    * value, or NULL for none.
105    */
106   struct WatchEntry *we_head;
107
108   /**
109    * Watch context for changes to this
110    * value, or NULL for none.
111    */
112   struct WatchEntry *we_tail;
113
114   /**
115    * Our value.
116    */
117   uint64_t value;
118
119   /**
120    * Unique ID.
121    */
122   uint32_t uid;
123
124   /**
125    * Is this value persistent?
126    */
127   int persistent;
128
129 };
130
131 /**
132  * Our configuration.
133  */
134 static const struct GNUNET_CONFIGURATION_Handle *cfg;
135
136 /**
137  * Linked list of our active statistics.
138  */
139 static struct StatsEntry *start;
140
141 static struct ClientEntry *client_head;
142
143 static struct ClientEntry *client_tail;
144
145 /**
146  * Our notification context.
147  */
148 static struct GNUNET_SERVER_NotificationContext *nc;
149
150 /**
151  * Counter used to generate unique values.
152  */
153 static uint32_t uidgen;
154
155
156 static void
157 inject_message (void *cls, void *client, const struct GNUNET_MessageHeader *msg)
158 {
159   struct GNUNET_SERVER_Handle *server = cls;
160
161   GNUNET_break (GNUNET_OK == GNUNET_SERVER_inject (server, NULL, msg));
162 }
163
164
165 /**
166  * Load persistent values from disk.  Disk format is
167  * exactly the same format that we also use for
168  * setting the values over the network.
169  *
170  * @param server handle to the server context
171  */
172 static void
173 load (struct GNUNET_SERVER_Handle *server)
174 {
175   char *fn;
176   struct GNUNET_BIO_ReadHandle *rh;
177   struct stat sb;
178   char *buf;
179   struct GNUNET_SERVER_MessageStreamTokenizer *mst;
180   char *emsg;
181
182   fn = GNUNET_DISK_get_home_filename (cfg, "statistics", "statistics.data",
183                                       NULL);
184   if (fn == NULL)
185     return;
186   if ((0 != stat (fn, &sb)) || (sb.st_size == 0))
187   {
188     GNUNET_free (fn);
189     return;
190   }
191   buf = GNUNET_malloc (sb.st_size);
192   rh = GNUNET_BIO_read_open (fn);
193   if (!rh)
194   {
195     GNUNET_free (fn);
196     return;
197   }
198   if (GNUNET_OK != GNUNET_BIO_read (rh, fn, buf, sb.st_size))
199   {
200     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "read", fn);
201     GNUNET_break (GNUNET_OK == GNUNET_BIO_read_close (rh, &emsg));
202     GNUNET_free_non_null (emsg);
203     GNUNET_free (fn);
204     return;
205   }
206   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
207               _("Loading %llu bytes of statistics from `%s'\n"),
208               (unsigned long long) sb.st_size, fn);
209   mst = GNUNET_SERVER_mst_create (&inject_message, server);
210   GNUNET_break (GNUNET_OK ==
211                 GNUNET_SERVER_mst_receive (mst, NULL, buf, sb.st_size,
212                                            GNUNET_YES, GNUNET_NO));
213   GNUNET_SERVER_mst_destroy (mst);
214   GNUNET_free (buf);
215   GNUNET_break (GNUNET_OK == GNUNET_BIO_read_close (rh, &emsg));
216   GNUNET_free_non_null (emsg);
217   GNUNET_free (fn);
218 }
219
220 /**
221  * Write persistent statistics to disk.
222  */
223 static void
224 save ()
225 {
226   struct StatsEntry *pos;
227   char *fn;
228   struct GNUNET_BIO_WriteHandle *wh;
229   
230   uint16_t size;
231   unsigned long long total;
232
233   wh = NULL;
234   fn = GNUNET_DISK_get_home_filename (cfg, "statistics", "statistics.data",
235                                       NULL);
236   if (fn != NULL)
237     wh = GNUNET_BIO_write_open (fn);
238   total = 0;
239   while (NULL != (pos = start))
240   {
241     start = pos->next;
242     if ((pos->persistent) && (NULL != wh))
243     {
244       size = htons (pos->msg->header.size);
245       if (GNUNET_OK != GNUNET_BIO_write (wh, pos->msg, size))
246       {
247         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
248         GNUNET_BIO_write_close (wh);
249         wh = NULL;
250       }
251       else
252         total += size;
253     }
254     GNUNET_free (pos);
255   }
256   if (NULL != wh)
257   {
258     GNUNET_BIO_write_close (wh);
259     if (total == 0)
260       GNUNET_break (0 == UNLINK (fn));
261     else
262       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
263                   _("Wrote %llu bytes of statistics to `%s'\n"), total, fn);
264   }
265   GNUNET_free_non_null (fn);
266 }
267
268
269 /**
270  * Transmit the given stats value.
271  */
272 static void
273 transmit (struct GNUNET_SERVER_Client *client, const struct StatsEntry *e)
274 {
275   struct GNUNET_STATISTICS_ReplyMessage *m;
276   size_t size;
277
278   size =
279       sizeof (struct GNUNET_STATISTICS_ReplyMessage) + strlen (e->service) + 1 +
280       strlen (e->name) + 1;
281   GNUNET_assert (size < GNUNET_SERVER_MAX_MESSAGE_SIZE);
282   m = GNUNET_malloc (size);
283   m->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
284   m->header.size = htons (size);
285   m->uid = htonl (e->uid);
286   if (e->persistent)
287     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
288   m->value = GNUNET_htonll (e->value);
289   size -= sizeof (struct GNUNET_STATISTICS_ReplyMessage);
290   GNUNET_assert (size ==
291                  GNUNET_STRINGS_buffer_fill ((char *) &m[1], size, 2,
292                                              e->service, e->name));
293 #if DEBUG_STATISTICS
294   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
295               "Transmitting value for `%s:%s' (%d): %llu\n", e->service,
296               e->name, e->persistent, e->value);
297 #endif
298   GNUNET_SERVER_notification_context_unicast (nc, client, &m->header,
299                                               GNUNET_NO);
300   GNUNET_free (m);
301 }
302
303
304 /**
305  * Does this entry match the request?
306  */
307 static int
308 matches (const struct StatsEntry *e, const char *service, const char *name)
309 {
310   return ((0 == strlen (service)) || (0 == strcmp (service, e->service))) &&
311       ((0 == strlen (name)) || (0 == strcmp (name, e->name)));
312 }
313
314
315 static struct ClientEntry *
316 make_client_entry (struct GNUNET_SERVER_Client *client)
317 {
318   struct ClientEntry *ce;
319
320   GNUNET_assert (client != NULL);
321   ce = client_head;
322   while (ce != NULL)
323   {
324     if (ce->client == client)
325       return ce;
326     ce = ce->next;
327   }
328   ce = GNUNET_malloc (sizeof (struct ClientEntry));
329   ce->client = client;
330   GNUNET_SERVER_client_keep (client);
331   GNUNET_CONTAINER_DLL_insert (client_head, client_tail, ce);
332   GNUNET_SERVER_notification_context_add (nc, client);
333   return ce;
334 }
335
336
337 /**
338  * Handle GET-message.
339  *
340  * @param cls closure
341  * @param client identification of the client
342  * @param message the actual message
343  * @return GNUNET_OK to keep the connection open,
344  *         GNUNET_SYSERR to close it (signal serious error)
345  */
346 static void
347 handle_get (void *cls, struct GNUNET_SERVER_Client *client,
348             const struct GNUNET_MessageHeader *message)
349 {
350   struct GNUNET_MessageHeader end;
351   char *service;
352   char *name;
353   struct StatsEntry *pos;
354   size_t size;
355
356   if (client != NULL)
357     make_client_entry (client);
358   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
359   if (size !=
360       GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1], size, 2,
361                                       &service, &name))
362   {
363     GNUNET_break (0);
364     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
365     return;
366   }
367 #if DEBUG_STATISTICS
368   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
369               "Received request for statistics on `%s:%s'\n",
370               strlen (service) ? service : "*", strlen (name) ? name : "*");
371 #endif
372   pos = start;
373   while (pos != NULL)
374   {
375     if (matches (pos, service, name))
376       transmit (client, pos);
377     pos = pos->next;
378   }
379   end.size = htons (sizeof (struct GNUNET_MessageHeader));
380   end.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_END);
381   GNUNET_SERVER_notification_context_unicast (nc, client, &end, GNUNET_NO);
382   GNUNET_SERVER_receive_done (client, GNUNET_OK);
383 }
384
385
386 static void
387 notify_change (struct StatsEntry *se)
388 {
389   struct GNUNET_STATISTICS_WatchValueMessage wvm;
390   struct WatchEntry *pos;
391
392   pos = se->we_head;
393   while (pos != NULL)
394   {
395     if (pos->last_value != se->value)
396     {
397       wvm.header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE);
398       wvm.header.size =
399           htons (sizeof (struct GNUNET_STATISTICS_WatchValueMessage));
400       wvm.flags = htonl (se->persistent ? GNUNET_STATISTICS_PERSIST_BIT : 0);
401       wvm.wid = htonl (pos->wid);
402       wvm.reserved = htonl (0);
403       wvm.value = GNUNET_htonll (se->value);
404       GNUNET_SERVER_notification_context_unicast (nc, pos->client, &wvm.header,
405                                                   GNUNET_NO);
406       pos->last_value = se->value;
407     }
408     pos = pos->next;
409   }
410 }
411
412 /**
413  * Handle SET-message.
414  *
415  * @param cls closure
416  * @param client identification of the client
417  * @param message the actual message
418  */
419 static void
420 handle_set (void *cls, struct GNUNET_SERVER_Client *client,
421             const struct GNUNET_MessageHeader *message)
422 {
423   char *service;
424   char *name;
425   uint16_t msize;
426   uint16_t size;
427   const struct GNUNET_STATISTICS_SetMessage *msg;
428   struct StatsEntry *pos;
429   struct StatsEntry *prev;
430   uint32_t flags;
431   uint64_t value;
432   int64_t delta;
433   int changed;
434
435   if (client != NULL)
436     make_client_entry (client);
437   msize = ntohs (message->size);
438   if (msize < sizeof (struct GNUNET_STATISTICS_SetMessage))
439   {
440     GNUNET_break (0);
441     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
442     return;
443   }
444   size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage);
445   msg = (const struct GNUNET_STATISTICS_SetMessage *) message;
446
447   if (size !=
448       GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1], size, 2, &service,
449                                       &name))
450   {
451     GNUNET_break (0);
452     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
453     return;
454   }
455   flags = ntohl (msg->flags);
456   value = GNUNET_ntohll (msg->value);
457 #if DEBUG_STATISTICS
458   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
459               "Received request to update statistic on `%s:%s' (%u) to/by %llu\n",
460               service, name, (unsigned int) flags, (unsigned long long) value);
461 #endif
462   pos = start;
463   prev = NULL;
464   while (pos != NULL)
465   {
466     if (matches (pos, service, name))
467     {
468       if ((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0)
469       {
470         changed = (pos->value != value);
471         pos->value = value;
472       }
473       else
474       {
475         delta = (int64_t) value;
476         if ((delta < 0) && (pos->value < -delta))
477         {
478           changed = (pos->value != 0);
479           pos->value = 0;
480         }
481         else
482         {
483           changed = (delta != 0);
484           GNUNET_break ((delta <= 0) || (pos->value + delta > pos->value));
485           pos->value += delta;
486         }
487       }
488       pos->msg->value = GNUNET_htonll (pos->value);
489       pos->msg->flags = msg->flags;
490       pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
491       if (prev != NULL)
492       {
493         /* move to front for faster setting next time! */
494         prev->next = pos->next;
495         pos->next = start;
496         start = pos;
497       }
498 #if DEBUG_STATISTICS
499       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
500                   "Statistic `%s:%s' updated to value %llu.\n", service, name,
501                   pos->value);
502 #endif
503       if (changed)
504         notify_change (pos);
505       GNUNET_SERVER_receive_done (client, GNUNET_OK);
506       return;
507     }
508     prev = pos;
509     pos = pos->next;
510   }
511   pos = GNUNET_malloc (sizeof (struct StatsEntry) + msize);
512   pos->next = start;
513   if (((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0) ||
514       (0 < (int64_t) GNUNET_ntohll (msg->value)))
515     pos->value = GNUNET_ntohll (msg->value);
516   pos->uid = uidgen++;
517   pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
518   pos->msg = (void *) &pos[1];
519   memcpy (pos->msg, message, ntohs (message->size));
520   pos->service = (const char *) &pos->msg[1];
521   pos->name = &pos->service[strlen (pos->service) + 1];
522
523   start = pos;
524 #if DEBUG_STATISTICS
525   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
526               "New statistic on `%s:%s' with value %llu created.\n", service,
527               name, pos->value);
528 #endif
529   GNUNET_SERVER_receive_done (client, GNUNET_OK);
530 }
531
532
533 /**
534  * Handle WATCH-message.
535  *
536  * @param cls closure
537  * @param client identification of the client
538  * @param message the actual message
539  */
540 static void
541 handle_watch (void *cls, struct GNUNET_SERVER_Client *client,
542               const struct GNUNET_MessageHeader *message)
543 {
544   char *service;
545   char *name;
546   uint16_t msize;
547   uint16_t size;
548   struct StatsEntry *pos;
549   struct ClientEntry *ce;
550   struct WatchEntry *we;
551   size_t slen;
552
553   ce = make_client_entry (client);
554   msize = ntohs (message->size);
555   if (msize < sizeof (struct GNUNET_MessageHeader))
556   {
557     GNUNET_break (0);
558     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
559     return;
560   }
561   size = msize - sizeof (struct GNUNET_MessageHeader);
562   if (size !=
563       GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1], size, 2,
564                                       &service, &name))
565   {
566     GNUNET_break (0);
567     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
568     return;
569   }
570 #if DEBUG_STATISTICS
571   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
572               "Received request to watch statistic on `%s:%s'\n", service,
573               name);
574 #endif
575   pos = start;
576   while (pos != NULL)
577   {
578     if (matches (pos, service, name))
579       break;
580     pos = pos->next;
581   }
582   if (pos == NULL)
583   {
584     pos =
585         GNUNET_malloc (sizeof (struct StatsEntry) +
586                        sizeof (struct GNUNET_STATISTICS_SetMessage) + size);
587     pos->next = start;
588     pos->uid = uidgen++;
589     pos->msg = (void *) &pos[1];
590     pos->msg->header.size =
591         htons (sizeof (struct GNUNET_STATISTICS_SetMessage) + size);
592     pos->msg->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_SET);
593     pos->service = (const char *) &pos->msg[1];
594     slen = strlen (service) + 1;
595     memcpy ((void *) pos->service, service, slen);
596     pos->name = &pos->service[slen];
597     memcpy ((void *) pos->name, name, strlen (name) + 1);
598     start = pos;
599   }
600   we = GNUNET_malloc (sizeof (struct WatchEntry));
601   we->client = client;
602   GNUNET_SERVER_client_keep (client);
603   we->wid = ce->max_wid++;
604   GNUNET_CONTAINER_DLL_insert (pos->we_head, pos->we_tail, we);
605   if (pos->value != 0)
606     notify_change (pos);
607   GNUNET_SERVER_receive_done (client, GNUNET_OK);
608 }
609
610
611 /**
612  * Task run during shutdown.
613  *
614  * @param cls unused
615  * @param tc unused
616  */
617 static void
618 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
619 {
620   struct ClientEntry *ce;
621   struct WatchEntry *we;
622   struct StatsEntry *se;
623
624   save ();
625   GNUNET_SERVER_notification_context_destroy (nc);
626   nc = NULL;
627   while (NULL != (ce = client_head))
628   {
629     GNUNET_SERVER_client_drop (ce->client);
630     GNUNET_CONTAINER_DLL_remove (client_head, client_tail, ce);
631     GNUNET_free (ce);
632   }
633   while (NULL != (se = start))
634   {
635     start = se->next;
636     while (NULL != (we = se->we_head))
637     {
638       GNUNET_SERVER_client_drop (we->client);
639       GNUNET_CONTAINER_DLL_remove (se->we_head, se->we_tail, we);
640       GNUNET_free (we);
641     }
642     GNUNET_free (se);
643   }
644 }
645
646
647 /**
648  * A client disconnected.  Remove all of its data structure entries.
649  *
650  * @param cls closure, NULL
651  * @param client identification of the client
652  */
653 static void
654 handle_client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
655 {
656   struct ClientEntry *ce;
657   struct WatchEntry *we;
658   struct WatchEntry *wen;
659   struct StatsEntry *se;
660
661   ce = client_head;
662   while (NULL != ce)
663   {
664     if (ce->client == client)
665     {
666       GNUNET_SERVER_client_drop (ce->client);
667       GNUNET_CONTAINER_DLL_remove (client_head, client_tail, ce);
668       GNUNET_free (ce);
669       break;
670     }
671     ce = ce->next;
672   }
673   se = start;
674   while (NULL != se)
675   {
676     wen = se->we_head;
677     while (NULL != (we = wen))
678     {
679       wen = we->next;
680       if (we->client != client)
681         continue;
682       GNUNET_SERVER_client_drop (we->client);
683       GNUNET_CONTAINER_DLL_remove (se->we_head, se->we_tail, we);
684       GNUNET_free (we);
685     }
686     se = se->next;
687   }
688 }
689
690
691 /**
692  * Process statistics requests.
693  *
694  * @param cls closure
695  * @param server the initialized server
696  * @param c configuration to use
697  */
698 static void
699 run (void *cls, struct GNUNET_SERVER_Handle *server,
700      const struct GNUNET_CONFIGURATION_Handle *c)
701 {
702   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
703     {&handle_set, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_SET, 0},
704     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_GET, 0},
705     {&handle_watch, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_WATCH, 0},
706     {NULL, NULL, 0, 0}
707   };
708   cfg = c;
709   GNUNET_SERVER_add_handlers (server, handlers);
710   nc = GNUNET_SERVER_notification_context_create (server, 16);
711   GNUNET_SERVER_disconnect_notify (server, &handle_client_disconnect, NULL);
712   load (server);
713   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
714                                 NULL);
715 }
716
717
718 /**
719  * The main function for the statistics service.
720  *
721  * @param argc number of arguments from the command line
722  * @param argv command line arguments
723  * @return 0 ok, 1 on error
724  */
725 int
726 main (int argc, char *const *argv)
727 {
728   return (GNUNET_OK ==
729           GNUNET_SERVICE_run (argc, argv, "statistics",
730                               GNUNET_SERVICE_OPTION_NONE, &run, NULL)) ? 0 : 1;
731 }
732
733 /* end of gnunet-service-statistics.c */