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