-check return value
[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         if (GNUNET_OK != GNUNET_BIO_write_close (wh))
249           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", fn);
250         wh = NULL;
251       }
252       else
253         total += size;
254     }
255     GNUNET_free (pos);
256   }
257   if (NULL != wh)
258   {
259     if (GNUNET_OK != GNUNET_BIO_write_close (wh))
260       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", fn);
261     if (total == 0)
262       GNUNET_break (0 == UNLINK (fn));
263     else
264       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
265                   _("Wrote %llu bytes of statistics to `%s'\n"), total, fn);
266   }
267   GNUNET_free_non_null (fn);
268 }
269
270
271 /**
272  * Transmit the given stats value.
273  */
274 static void
275 transmit (struct GNUNET_SERVER_Client *client, const struct StatsEntry *e)
276 {
277   struct GNUNET_STATISTICS_ReplyMessage *m;
278   size_t size;
279
280   size =
281       sizeof (struct GNUNET_STATISTICS_ReplyMessage) + strlen (e->service) + 1 +
282       strlen (e->name) + 1;
283   GNUNET_assert (size < GNUNET_SERVER_MAX_MESSAGE_SIZE);
284   m = GNUNET_malloc (size);
285   m->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
286   m->header.size = htons (size);
287   m->uid = htonl (e->uid);
288   if (e->persistent)
289     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
290   m->value = GNUNET_htonll (e->value);
291   size -= sizeof (struct GNUNET_STATISTICS_ReplyMessage);
292   GNUNET_assert (size ==
293                  GNUNET_STRINGS_buffer_fill ((char *) &m[1], size, 2,
294                                              e->service, e->name));
295 #if DEBUG_STATISTICS
296   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
297               "Transmitting value for `%s:%s' (%d): %llu\n", e->service,
298               e->name, e->persistent, e->value);
299 #endif
300   GNUNET_SERVER_notification_context_unicast (nc, client, &m->header,
301                                               GNUNET_NO);
302   GNUNET_free (m);
303 }
304
305
306 /**
307  * Does this entry match the request?
308  */
309 static int
310 matches (const struct StatsEntry *e, const char *service, const char *name)
311 {
312   return ((0 == strlen (service)) || (0 == strcmp (service, e->service))) &&
313       ((0 == strlen (name)) || (0 == strcmp (name, e->name)));
314 }
315
316
317 static struct ClientEntry *
318 make_client_entry (struct GNUNET_SERVER_Client *client)
319 {
320   struct ClientEntry *ce;
321
322   GNUNET_assert (client != NULL);
323   ce = client_head;
324   while (ce != NULL)
325   {
326     if (ce->client == client)
327       return ce;
328     ce = ce->next;
329   }
330   ce = GNUNET_malloc (sizeof (struct ClientEntry));
331   ce->client = client;
332   GNUNET_SERVER_client_keep (client);
333   GNUNET_CONTAINER_DLL_insert (client_head, client_tail, ce);
334   GNUNET_SERVER_notification_context_add (nc, client);
335   return ce;
336 }
337
338
339 /**
340  * Handle GET-message.
341  *
342  * @param cls closure
343  * @param client identification of the client
344  * @param message the actual message
345  * @return GNUNET_OK to keep the connection open,
346  *         GNUNET_SYSERR to close it (signal serious error)
347  */
348 static void
349 handle_get (void *cls, struct GNUNET_SERVER_Client *client,
350             const struct GNUNET_MessageHeader *message)
351 {
352   struct GNUNET_MessageHeader end;
353   char *service;
354   char *name;
355   struct StatsEntry *pos;
356   size_t size;
357
358   if (client != NULL)
359     make_client_entry (client);
360   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
361   if (size !=
362       GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1], size, 2,
363                                       &service, &name))
364   {
365     GNUNET_break (0);
366     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
367     return;
368   }
369 #if DEBUG_STATISTICS
370   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
371               "Received request for statistics on `%s:%s'\n",
372               strlen (service) ? service : "*", strlen (name) ? name : "*");
373 #endif
374   pos = start;
375   while (pos != NULL)
376   {
377     if (matches (pos, service, name))
378       transmit (client, pos);
379     pos = pos->next;
380   }
381   end.size = htons (sizeof (struct GNUNET_MessageHeader));
382   end.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_END);
383   GNUNET_SERVER_notification_context_unicast (nc, client, &end, GNUNET_NO);
384   GNUNET_SERVER_receive_done (client, GNUNET_OK);
385 }
386
387
388 static void
389 notify_change (struct StatsEntry *se)
390 {
391   struct GNUNET_STATISTICS_WatchValueMessage wvm;
392   struct WatchEntry *pos;
393
394   pos = se->we_head;
395   while (pos != NULL)
396   {
397     if (pos->last_value != se->value)
398     {
399       wvm.header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE);
400       wvm.header.size =
401           htons (sizeof (struct GNUNET_STATISTICS_WatchValueMessage));
402       wvm.flags = htonl (se->persistent ? GNUNET_STATISTICS_PERSIST_BIT : 0);
403       wvm.wid = htonl (pos->wid);
404       wvm.reserved = htonl (0);
405       wvm.value = GNUNET_htonll (se->value);
406       GNUNET_SERVER_notification_context_unicast (nc, pos->client, &wvm.header,
407                                                   GNUNET_NO);
408       pos->last_value = se->value;
409     }
410     pos = pos->next;
411   }
412 }
413
414 /**
415  * Handle SET-message.
416  *
417  * @param cls closure
418  * @param client identification of the client
419  * @param message the actual message
420  */
421 static void
422 handle_set (void *cls, struct GNUNET_SERVER_Client *client,
423             const struct GNUNET_MessageHeader *message)
424 {
425   char *service;
426   char *name;
427   uint16_t msize;
428   uint16_t size;
429   const struct GNUNET_STATISTICS_SetMessage *msg;
430   struct StatsEntry *pos;
431   struct StatsEntry *prev;
432   uint32_t flags;
433   uint64_t value;
434   int64_t delta;
435   int changed;
436
437   if (client != NULL)
438     make_client_entry (client);
439   msize = ntohs (message->size);
440   if (msize < sizeof (struct GNUNET_STATISTICS_SetMessage))
441   {
442     GNUNET_break (0);
443     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
444     return;
445   }
446   size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage);
447   msg = (const struct GNUNET_STATISTICS_SetMessage *) message;
448
449   if (size !=
450       GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1], size, 2, &service,
451                                       &name))
452   {
453     GNUNET_break (0);
454     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
455     return;
456   }
457   flags = ntohl (msg->flags);
458   value = GNUNET_ntohll (msg->value);
459 #if DEBUG_STATISTICS
460   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
461               "Received request to update statistic on `%s:%s' (%u) to/by %llu\n",
462               service, name, (unsigned int) flags, (unsigned long long) value);
463 #endif
464   pos = start;
465   prev = NULL;
466   while (pos != NULL)
467   {
468     if (matches (pos, service, name))
469     {
470       if ((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0)
471       {
472         changed = (pos->value != value);
473         pos->value = value;
474       }
475       else
476       {
477         delta = (int64_t) value;
478         if ((delta < 0) && (pos->value < -delta))
479         {
480           changed = (pos->value != 0);
481           pos->value = 0;
482         }
483         else
484         {
485           changed = (delta != 0);
486           GNUNET_break ((delta <= 0) || (pos->value + delta > pos->value));
487           pos->value += delta;
488         }
489       }
490       pos->msg->value = GNUNET_htonll (pos->value);
491       pos->msg->flags = msg->flags;
492       pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
493       if (prev != NULL)
494       {
495         /* move to front for faster setting next time! */
496         prev->next = pos->next;
497         pos->next = start;
498         start = pos;
499       }
500 #if DEBUG_STATISTICS
501       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502                   "Statistic `%s:%s' updated to value %llu.\n", service, name,
503                   pos->value);
504 #endif
505       if (changed)
506         notify_change (pos);
507       GNUNET_SERVER_receive_done (client, GNUNET_OK);
508       return;
509     }
510     prev = pos;
511     pos = pos->next;
512   }
513   pos = GNUNET_malloc (sizeof (struct StatsEntry) + msize);
514   pos->next = start;
515   if (((flags & GNUNET_STATISTICS_SETFLAG_RELATIVE) == 0) ||
516       (0 < (int64_t) GNUNET_ntohll (msg->value)))
517     pos->value = GNUNET_ntohll (msg->value);
518   pos->uid = uidgen++;
519   pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
520   pos->msg = (void *) &pos[1];
521   memcpy (pos->msg, message, ntohs (message->size));
522   pos->service = (const char *) &pos->msg[1];
523   pos->name = &pos->service[strlen (pos->service) + 1];
524
525   start = pos;
526 #if DEBUG_STATISTICS
527   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
528               "New statistic on `%s:%s' with value %llu created.\n", service,
529               name, pos->value);
530 #endif
531   GNUNET_SERVER_receive_done (client, GNUNET_OK);
532 }
533
534
535 /**
536  * Handle WATCH-message.
537  *
538  * @param cls closure
539  * @param client identification of the client
540  * @param message the actual message
541  */
542 static void
543 handle_watch (void *cls, struct GNUNET_SERVER_Client *client,
544               const struct GNUNET_MessageHeader *message)
545 {
546   char *service;
547   char *name;
548   uint16_t msize;
549   uint16_t size;
550   struct StatsEntry *pos;
551   struct ClientEntry *ce;
552   struct WatchEntry *we;
553   size_t slen;
554
555   ce = make_client_entry (client);
556   msize = ntohs (message->size);
557   if (msize < sizeof (struct GNUNET_MessageHeader))
558   {
559     GNUNET_break (0);
560     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
561     return;
562   }
563   size = msize - sizeof (struct GNUNET_MessageHeader);
564   if (size !=
565       GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1], size, 2,
566                                       &service, &name))
567   {
568     GNUNET_break (0);
569     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
570     return;
571   }
572 #if DEBUG_STATISTICS
573   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
574               "Received request to watch statistic on `%s:%s'\n", service,
575               name);
576 #endif
577   pos = start;
578   while (pos != NULL)
579   {
580     if (matches (pos, service, name))
581       break;
582     pos = pos->next;
583   }
584   if (pos == NULL)
585   {
586     pos =
587         GNUNET_malloc (sizeof (struct StatsEntry) +
588                        sizeof (struct GNUNET_STATISTICS_SetMessage) + size);
589     pos->next = start;
590     pos->uid = uidgen++;
591     pos->msg = (void *) &pos[1];
592     pos->msg->header.size =
593         htons (sizeof (struct GNUNET_STATISTICS_SetMessage) + size);
594     pos->msg->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_SET);
595     pos->service = (const char *) &pos->msg[1];
596     slen = strlen (service) + 1;
597     memcpy ((void *) pos->service, service, slen);
598     pos->name = &pos->service[slen];
599     memcpy ((void *) pos->name, name, strlen (name) + 1);
600     start = pos;
601   }
602   we = GNUNET_malloc (sizeof (struct WatchEntry));
603   we->client = client;
604   GNUNET_SERVER_client_keep (client);
605   we->wid = ce->max_wid++;
606   GNUNET_CONTAINER_DLL_insert (pos->we_head, pos->we_tail, we);
607   if (pos->value != 0)
608     notify_change (pos);
609   GNUNET_SERVER_receive_done (client, GNUNET_OK);
610 }
611
612
613 /**
614  * Task run during shutdown.
615  *
616  * @param cls unused
617  * @param tc unused
618  */
619 static void
620 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
621 {
622   struct ClientEntry *ce;
623   struct WatchEntry *we;
624   struct StatsEntry *se;
625
626   save ();
627   GNUNET_SERVER_notification_context_destroy (nc);
628   nc = NULL;
629   while (NULL != (ce = client_head))
630   {
631     GNUNET_SERVER_client_drop (ce->client);
632     GNUNET_CONTAINER_DLL_remove (client_head, client_tail, ce);
633     GNUNET_free (ce);
634   }
635   while (NULL != (se = start))
636   {
637     start = se->next;
638     while (NULL != (we = se->we_head))
639     {
640       GNUNET_SERVER_client_drop (we->client);
641       GNUNET_CONTAINER_DLL_remove (se->we_head, se->we_tail, we);
642       GNUNET_free (we);
643     }
644     GNUNET_free (se);
645   }
646 }
647
648
649 /**
650  * A client disconnected.  Remove all of its data structure entries.
651  *
652  * @param cls closure, NULL
653  * @param client identification of the client
654  */
655 static void
656 handle_client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
657 {
658   struct ClientEntry *ce;
659   struct WatchEntry *we;
660   struct WatchEntry *wen;
661   struct StatsEntry *se;
662
663   ce = client_head;
664   while (NULL != ce)
665   {
666     if (ce->client == client)
667     {
668       GNUNET_SERVER_client_drop (ce->client);
669       GNUNET_CONTAINER_DLL_remove (client_head, client_tail, ce);
670       GNUNET_free (ce);
671       break;
672     }
673     ce = ce->next;
674   }
675   se = start;
676   while (NULL != se)
677   {
678     wen = se->we_head;
679     while (NULL != (we = wen))
680     {
681       wen = we->next;
682       if (we->client != client)
683         continue;
684       GNUNET_SERVER_client_drop (we->client);
685       GNUNET_CONTAINER_DLL_remove (se->we_head, se->we_tail, we);
686       GNUNET_free (we);
687     }
688     se = se->next;
689   }
690 }
691
692
693 /**
694  * Process statistics requests.
695  *
696  * @param cls closure
697  * @param server the initialized server
698  * @param c configuration to use
699  */
700 static void
701 run (void *cls, struct GNUNET_SERVER_Handle *server,
702      const struct GNUNET_CONFIGURATION_Handle *c)
703 {
704   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
705     {&handle_set, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_SET, 0},
706     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_GET, 0},
707     {&handle_watch, NULL, GNUNET_MESSAGE_TYPE_STATISTICS_WATCH, 0},
708     {NULL, NULL, 0, 0}
709   };
710   cfg = c;
711   GNUNET_SERVER_add_handlers (server, handlers);
712   nc = GNUNET_SERVER_notification_context_create (server, 16);
713   GNUNET_SERVER_disconnect_notify (server, &handle_client_disconnect, NULL);
714   load (server);
715   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
716                                 NULL);
717 }
718
719
720 /**
721  * The main function for the statistics service.
722  *
723  * @param argc number of arguments from the command line
724  * @param argv command line arguments
725  * @return 0 ok, 1 on error
726  */
727 int
728 main (int argc, char *const *argv)
729 {
730   return (GNUNET_OK ==
731           GNUNET_SERVICE_run (argc, argv, "statistics",
732                               GNUNET_SERVICE_OPTION_NONE, &run, NULL)) ? 0 : 1;
733 }
734
735 /* end of gnunet-service-statistics.c */