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