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