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