first batch of license fixes (boring)
[oweals/gnunet.git] / src / statistics / gnunet-service-statistics.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009, 2010, 2012, 2014, 2016 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14 */
15
16 /**
17  * @file statistics/gnunet-service-statistics.c
18  * @brief program that tracks statistics
19  * @author Christian Grothoff
20  */
21 #include "platform.h"
22 #include "gnunet_bio_lib.h"
23 #include "gnunet_container_lib.h"
24 #include "gnunet_disk_lib.h"
25 #include "gnunet_getopt_lib.h"
26 #include "gnunet_protocols.h"
27 #include "gnunet_service_lib.h"
28 #include "gnunet_statistics_service.h"
29 #include "gnunet_strings_lib.h"
30 #include "gnunet_time_lib.h"
31 #include "statistics.h"
32
33 /**
34  * Watch entry.
35  */
36 struct WatchEntry
37 {
38
39   /**
40    * Watch entries are kept in a linked list.
41    */
42   struct WatchEntry *next;
43
44   /**
45    * Watch entries are kept in a linked list.
46    */
47   struct WatchEntry *prev;
48
49   /**
50    * For which client is this watch entry?
51    */
52   struct ClientEntry *ce;
53
54   /**
55    * Last value we communicated to the client for this watch entry.
56    */
57   uint64_t last_value;
58
59   /**
60    * Unique watch number for this client and this watched value.
61    */
62   uint32_t wid;
63
64   /**
65    * Is last_value valid
66    * #GNUNET_NO : last_value is n/a, #GNUNET_YES: last_value is valid
67    */
68   int last_value_set;
69
70 };
71
72
73 /**
74  * We keep the statistics organized by subsystem for faster
75  * lookup during SET operations.
76  */
77 struct SubsystemEntry;
78
79
80 /**
81  * Entry in the statistics list.
82  */
83 struct StatsEntry
84 {
85   /**
86    * This is a linked list.
87    */
88   struct StatsEntry *next;
89
90   /**
91    * This is a linked list.
92    */
93   struct StatsEntry *prev;
94
95   /**
96    * Subsystem this entry belongs to.
97    */
98   struct SubsystemEntry *subsystem;
99
100   /**
101    * Name for the value stored by this entry, allocated at the end of
102    * this struct.
103    */
104   const char *name;
105
106   /**
107    * Watch context for changes to this value, or NULL for none.
108    */
109   struct WatchEntry *we_head;
110
111   /**
112    * Watch context for changes to this 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    * Is this value set?
133    * #GNUNET_NO: value is n/a, #GNUNET_YES: value is valid
134    */
135   int set;
136
137 };
138
139
140 /**
141  * We keep the statistics organized by subsystem for faster
142  * lookup during SET operations.
143  */
144 struct SubsystemEntry
145 {
146   /**
147    * Subsystems are kept in a DLL.
148    */
149   struct SubsystemEntry *next;
150
151   /**
152    * Subsystems are kept in a DLL.
153    */
154   struct SubsystemEntry *prev;
155
156   /**
157    * Head of list of values kept for this subsystem.
158    */
159   struct StatsEntry *stat_head;
160
161   /**
162    * Tail of list of values kept for this subsystem.
163    */
164   struct StatsEntry *stat_tail;
165
166   /**
167    * Name of the subsystem this entry is for, allocated at
168    * the end of this struct, do not free().
169    */
170   const char *service;
171
172 };
173
174
175 /**
176  * Client entry.
177  */
178 struct ClientEntry
179 {
180   /**
181    * Corresponding server handle.
182    */
183   struct GNUNET_SERVICE_Client *client;
184
185   /**
186    * Corresponding message queue.
187    */
188   struct GNUNET_MQ_Handle *mq;
189
190   /**
191    * Which subsystem is this client writing to (SET/UPDATE)?
192    */
193   struct SubsystemEntry *subsystem;
194
195   /**
196    * Maximum watch ID used by this client so far.
197    */
198   uint32_t max_wid;
199
200 };
201
202
203 /**
204  * Our configuration.
205  */
206 static const struct GNUNET_CONFIGURATION_Handle *cfg;
207
208 /**
209  * Head of linked list of subsystems with active statistics.
210  */
211 static struct SubsystemEntry *sub_head;
212
213 /**
214  * Tail of linked list of subsystems with active statistics.
215  */
216 static struct SubsystemEntry *sub_tail;
217
218 /**
219  * Number of connected clients.
220  */
221 static unsigned int client_count;
222
223 /**
224  * Our notification context.
225  */
226 static struct GNUNET_NotificationContext *nc;
227
228 /**
229  * Counter used to generate unique values.
230  */
231 static uint32_t uidgen;
232
233 /**
234  * Set to #GNUNET_YES if we are shutting down as soon as possible.
235  */
236 static int in_shutdown;
237
238
239 /**
240  * Write persistent statistics to disk.
241  */
242 static void
243 save ()
244 {
245   struct SubsystemEntry *se;
246   struct StatsEntry *pos;
247   char *fn;
248   struct GNUNET_BIO_WriteHandle *wh;
249   uint16_t size;
250   unsigned long long total;
251   size_t nlen;
252   size_t slen;
253   struct GNUNET_STATISTICS_SetMessage *msg;
254
255   if (GNUNET_OK !=
256       GNUNET_CONFIGURATION_get_value_filename (cfg,
257                                                "STATISTICS",
258                                                "DATABASE",
259                                                &fn))
260   {
261     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
262                                "STATISTICS",
263                                "DATABASE");
264     return;
265   }
266   (void) GNUNET_DISK_directory_create_for_file (fn);
267   wh = GNUNET_BIO_write_open (fn);
268   total = 0;
269   while (NULL != (se = sub_head))
270   {
271     GNUNET_CONTAINER_DLL_remove (sub_head,
272                                  sub_tail,
273                                  se);
274     slen = strlen (se->service) + 1;
275     while (NULL != (pos = se->stat_head))
276     {
277       GNUNET_CONTAINER_DLL_remove (se->stat_head,
278                                    se->stat_tail,
279                                    pos);
280       if ( (pos->persistent) &&
281            (NULL != wh) )
282       {
283         nlen = strlen (pos->name) + 1;
284         size = sizeof (struct GNUNET_STATISTICS_SetMessage) + nlen + slen;
285         GNUNET_assert (size < UINT16_MAX);
286         msg = GNUNET_malloc (size);
287
288         msg->header.size = htons ((uint16_t) size);
289         msg->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_SET);
290         GNUNET_assert (nlen + slen ==
291                        GNUNET_STRINGS_buffer_fill ((char *) &msg[1],
292                                                    nlen + slen,
293                                                    2,
294                                                    se->service,
295                                                    pos->name));
296         msg->flags = htonl (pos->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
297         msg->value = GNUNET_htonll (pos->value);
298         if (GNUNET_OK != GNUNET_BIO_write (wh,
299                                            msg,
300                                            size))
301         {
302           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
303                                     "write",
304                                     fn);
305           if (GNUNET_OK != GNUNET_BIO_write_close (wh))
306             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
307                                       "close",
308                                       fn);
309           wh = NULL;
310         }
311         else
312         {
313           total += size;
314         }
315         GNUNET_free (msg);
316       }
317       GNUNET_free (pos);
318     }
319     GNUNET_free (se);
320   }
321   if (NULL != wh)
322   {
323     if (GNUNET_OK !=
324         GNUNET_BIO_write_close (wh))
325       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
326                                 "close",
327                                 fn);
328     if (0 == total)
329       GNUNET_break (0 ==
330                     UNLINK (fn));
331     else
332       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
333                   _("Wrote %llu bytes of statistics to `%s'\n"),
334                   total,
335                   fn);
336   }
337   GNUNET_free_non_null (fn);
338 }
339
340
341 /**
342  * Transmit the given stats value.
343  *
344  * @param client receiver of the value
345  * @param e value to transmit
346  */
347 static void
348 transmit (struct ClientEntry *ce,
349           const struct StatsEntry *e)
350 {
351   struct GNUNET_MQ_Envelope *env;
352   struct GNUNET_STATISTICS_ReplyMessage *m;
353   size_t size;
354
355   size = strlen (e->subsystem->service) + 1 +
356     strlen (e->name) + 1;
357   GNUNET_assert (size < GNUNET_MAX_MESSAGE_SIZE);
358   env = GNUNET_MQ_msg_extra (m,
359                              size,
360                              GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
361   m->uid = htonl (e->uid);
362   if (e->persistent)
363     m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
364   m->value = GNUNET_htonll (e->value);
365   GNUNET_assert (size ==
366                  GNUNET_STRINGS_buffer_fill ((char *) &m[1],
367                                              size,
368                                              2,
369                                              e->subsystem->service,
370                                              e->name));
371   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
372               "Transmitting value for `%s:%s' (%d): %llu\n",
373               e->subsystem->service,
374               e->name,
375               e->persistent,
376               (unsigned long long) e->value);
377   GNUNET_MQ_send (ce->mq,
378                   env);
379 }
380
381
382 /**
383  * Callback called when a client connects to the service.
384  *
385  * @param cls closure for the service
386  * @param c the new client that connected to the service
387  * @param mq the message queue used to send messages to the client
388  * @return @a c
389  */
390 static void *
391 client_connect_cb (void *cls,
392                    struct GNUNET_SERVICE_Client *c,
393                    struct GNUNET_MQ_Handle *mq)
394 {
395   struct ClientEntry *ce;
396
397   ce = GNUNET_new (struct ClientEntry);
398   ce->client = c;
399   ce->mq = mq;
400   client_count++;
401   GNUNET_notification_context_add (nc,
402                                    mq);
403   return ce;
404 }
405
406
407 /**
408  * Check integrity of GET-message.
409  *
410  * @param cls identification of the client
411  * @param message the actual message
412  * @return #GNUNET_OK if @a message is well-formed
413  */
414 static int
415 check_get (void *cls,
416            const struct GNUNET_MessageHeader *message)
417 {
418   const char *service;
419   const char *name;
420   size_t size;
421
422   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
423   if (size !=
424       GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
425                                       size,
426                                       2,
427                                       &service,
428                                       &name))
429   {
430     GNUNET_break (0);
431     return GNUNET_SYSERR;
432   }
433   return GNUNET_OK;
434 }
435
436
437 /**
438  * Handle GET-message.
439  *
440  * @param cls identification of the client
441  * @param message the actual message
442  */
443 static void
444 handle_get (void *cls,
445             const struct GNUNET_MessageHeader *message)
446 {
447   struct ClientEntry *ce = cls;
448   struct GNUNET_MQ_Envelope *env;
449   struct GNUNET_MessageHeader *end;
450   const char *service;
451   const char *name;
452   size_t slen;
453   size_t nlen;
454   struct SubsystemEntry *se;
455   struct StatsEntry *pos;
456   size_t size;
457
458   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
459   GNUNET_assert (size ==
460                  GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
461                                                  size,
462                                                  2,
463                                                  &service,
464                                                  &name));
465   slen = strlen (service);
466   nlen = strlen (name);
467   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
468               "Received request for statistics on `%s:%s'\n",
469               slen ? service : "*",
470               nlen ? name : "*");
471   for (se = sub_head; NULL != se; se = se->next)
472   {
473     if (! ( (0 == slen) ||
474             (0 == strcmp (service, se->service))) )
475       continue;
476     for (pos = se->stat_head; NULL != pos; pos = pos->next)
477     {
478       if  (! ( (0 == nlen) ||
479                (0 == strcmp (name,
480                              pos->name))) )
481         continue;
482       transmit (ce,
483                 pos);
484     }
485   }
486   env = GNUNET_MQ_msg (end,
487                        GNUNET_MESSAGE_TYPE_STATISTICS_END);
488   GNUNET_MQ_send (ce->mq,
489                   env);
490   GNUNET_SERVICE_client_continue (ce->client);
491 }
492
493
494 /**
495  * Notify all clients listening about a change to a value.
496  *
497  * @param se value that changed
498  */
499 static void
500 notify_change (struct StatsEntry *se)
501 {
502   struct GNUNET_MQ_Envelope *env;
503   struct GNUNET_STATISTICS_WatchValueMessage *wvm;
504   struct WatchEntry *pos;
505
506   for (pos = se->we_head; NULL != pos; pos = pos->next)
507   {
508     if (GNUNET_YES == pos->last_value_set)
509     {
510       if (pos->last_value == se->value)
511         continue;
512     }
513     else
514     {
515       pos->last_value_set = GNUNET_YES;
516     }
517     env = GNUNET_MQ_msg (wvm,
518                          GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE);
519     wvm->flags = htonl (se->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
520     wvm->wid = htonl (pos->wid);
521     wvm->reserved = htonl (0);
522     wvm->value = GNUNET_htonll (se->value);
523     GNUNET_MQ_send (pos->ce->mq,
524                     env);
525     pos->last_value = se->value;
526   }
527 }
528
529
530 /**
531  * Find the subsystem entry of the given name for the specified client.
532  *
533  * @param ce client looking for the subsystem, may contain a hint
534  *           to find the entry faster, can be NULL
535  * @param service name of the subsystem to look for
536  * @return subsystem entry, never NULL (subsystem entry is created if necessary)
537  */
538 static struct SubsystemEntry *
539 find_subsystem_entry (struct ClientEntry *ce,
540                       const char *service)
541 {
542   size_t slen;
543   struct SubsystemEntry *se;
544
545   if (NULL != ce)
546     se = ce->subsystem;
547   else
548     se = NULL;
549   if ( (NULL == se) ||
550        (0 != strcmp (service,
551                      se->service)) )
552   {
553     for (se = sub_head; NULL != se; se = se->next)
554       if (0 == strcmp (service,
555                        se->service))
556         break;
557     if (NULL != ce)
558       ce->subsystem = se;
559   }
560   if (NULL != se)
561     return se;
562   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
563               "Allocating new subsystem entry `%s'\n",
564               service);
565   slen = strlen (service) + 1;
566   se = GNUNET_malloc (sizeof (struct SubsystemEntry) +
567                       slen);
568   GNUNET_memcpy (&se[1],
569           service,
570           slen);
571   se->service = (const char *) &se[1];
572   GNUNET_CONTAINER_DLL_insert (sub_head,
573                                sub_tail,
574                                se);
575   if (NULL != ce)
576     ce->subsystem = se;
577   return se;
578 }
579
580
581 /**
582  * Find the statistics entry of the given subsystem.
583  *
584  * @param subsystem subsystem to look in
585  * @param name name of the entry to look for
586  * @return statistis entry, or NULL if not found
587  */
588 static struct StatsEntry *
589 find_stat_entry (struct SubsystemEntry *se,
590                  const char *name)
591 {
592   struct StatsEntry *pos;
593
594   for (pos = se->stat_head; NULL != pos; pos = pos->next)
595     if  (0 == strcmp (name, pos->name))
596       return pos;
597   return NULL;
598 }
599
600
601 /**
602  * Check format of SET-message.
603  *
604  * @param cls the `struct ClientEntry`
605  * @param message the actual message
606  * @return #GNUNET_OK if message is well-formed
607  */
608 static int
609 check_set (void *cls,
610            const struct GNUNET_STATISTICS_SetMessage *msg)
611 {
612   const char *service;
613   const char *name;
614   size_t msize;
615
616   msize = ntohs (msg->header.size) - sizeof (*msg);
617   if (msize !=
618       GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
619                                       msize,
620                                       2,
621                                       &service,
622                                       &name))
623   {
624     GNUNET_break (0);
625     return GNUNET_SYSERR;
626   }
627   return GNUNET_OK;
628 }
629
630
631 /**
632  * Handle SET-message.
633  *
634  * @param cls the `struct ClientEntry`
635  * @param message the actual message
636  */
637 static void
638 handle_set (void *cls,
639             const struct GNUNET_STATISTICS_SetMessage *msg)
640 {
641   struct ClientEntry *ce = cls;
642   const char *service;
643   const char *name;
644   size_t nlen;
645   uint16_t msize;
646   uint16_t size;
647   struct SubsystemEntry *se;
648   struct StatsEntry *pos;
649   uint32_t flags;
650   uint64_t value;
651   int64_t delta;
652   int changed;
653   int initial_set;
654
655   msize = ntohs (msg->header.size);
656   size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage);
657   GNUNET_assert (size ==
658                  GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
659                                                  size,
660                                                  2,
661                                                  &service,
662                                                  &name));
663   se = find_subsystem_entry (ce,
664                              service);
665   flags = ntohl (msg->flags);
666   value = GNUNET_ntohll (msg->value);
667   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
668               "Received request to update statistic on `%s:%s' (%u) to/by %llu\n",
669               service,
670               name,
671               (unsigned int) flags,
672               (unsigned long long) value);
673   pos = find_stat_entry (se,
674                          name);
675   if (NULL != pos)
676   {
677     initial_set = 0;
678     if (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE))
679     {
680       changed = (pos->value != value);
681       pos->value = value;
682     }
683     else
684     {
685       delta = (int64_t) value;
686       if ((delta < 0) && (pos->value < -delta))
687       {
688         changed = (0 != pos->value);
689         pos->value = 0;
690       }
691       else
692       {
693         changed = (0 != delta);
694         GNUNET_break ( (delta <= 0) ||
695                        (pos->value + delta > pos->value) );
696         pos->value += delta;
697       }
698     }
699     if (GNUNET_NO == pos->set)
700     {
701       pos->set = GNUNET_YES;
702       initial_set = 1;
703     }
704     pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
705     if (pos != se->stat_head)
706     {
707       /* move to front for faster setting next time! */
708       GNUNET_CONTAINER_DLL_remove (se->stat_head,
709                                    se->stat_tail,
710                                    pos);
711       GNUNET_CONTAINER_DLL_insert (se->stat_head,
712                                    se->stat_tail,
713                                    pos);
714     }
715     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
716                 "Statistic `%s:%s' updated to value %llu (%d).\n",
717                 service,
718                 name,
719                 (unsigned long long) pos->value,
720                 pos->persistent);
721     if ( (changed) ||
722          (1 == initial_set) )
723       notify_change (pos);
724     GNUNET_SERVICE_client_continue (ce->client);
725     return;
726   }
727   /* not found, create a new entry */
728   nlen = strlen (name) + 1;
729   pos = GNUNET_malloc (sizeof (struct StatsEntry) + nlen);
730   GNUNET_memcpy (&pos[1],
731                  name,
732                  nlen);
733   pos->name = (const char *) &pos[1];
734   pos->subsystem = se;
735   if ( (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE)) ||
736        (0 < (int64_t) GNUNET_ntohll (msg->value)) )
737   {
738     pos->value = GNUNET_ntohll (msg->value);
739     pos->set = GNUNET_YES;
740   }
741   else
742   {
743     pos->set = GNUNET_NO;
744   }
745   pos->uid = uidgen++;
746   pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
747   GNUNET_CONTAINER_DLL_insert (se->stat_head,
748                                se->stat_tail,
749                                pos);
750   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
751               "New statistic on `%s:%s' with value %llu created.\n",
752               service,
753               name,
754               (unsigned long long) pos->value);
755   if (NULL != ce)
756     GNUNET_SERVICE_client_continue (ce->client);
757 }
758
759
760 /**
761  * Check integrity of WATCH-message.
762  *
763  * @param cls the `struct ClientEntry *`
764  * @param message the actual message
765  * @return #GNUNET_OK if message is well-formed
766  */
767 static int
768 check_watch (void *cls,
769              const struct GNUNET_MessageHeader *message)
770 {
771   size_t size;
772   const char *service;
773   const char *name;
774
775   size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
776   if (size !=
777       GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
778                                       size,
779                                       2,
780                                       &service,
781                                       &name))
782   {
783     GNUNET_break (0);
784     return GNUNET_SYSERR;
785   }
786   return GNUNET_OK;
787 }
788
789
790 /**
791  * Handle WATCH-message.
792  *
793  * @param cls the `struct ClientEntry *`
794  * @param message the actual message
795  */
796 static void
797 handle_watch (void *cls,
798               const struct GNUNET_MessageHeader *message)
799 {
800   struct ClientEntry *ce = cls;
801   const char *service;
802   const char *name;
803   uint16_t msize;
804   uint16_t size;
805   struct SubsystemEntry *se;
806   struct StatsEntry *pos;
807   struct WatchEntry *we;
808   size_t nlen;
809
810   if (NULL == nc)
811   {
812     GNUNET_SERVICE_client_drop (ce->client);
813     return;
814   }
815   GNUNET_SERVICE_client_mark_monitor (ce->client);
816   msize = ntohs (message->size);
817   size = msize - sizeof (struct GNUNET_MessageHeader);
818   GNUNET_assert (size ==
819                  GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
820                                                  size,
821                                                  2,
822                                                  &service,
823                                                  &name));
824   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
825               "Received request to watch statistic on `%s:%s'\n",
826               service,
827               name);
828   se = find_subsystem_entry (ce,
829                              service);
830   pos = find_stat_entry (se,
831                          name);
832   if (NULL == pos)
833   {
834     nlen = strlen (name) + 1;
835     pos = GNUNET_malloc (sizeof (struct StatsEntry) +
836                          nlen);
837     GNUNET_memcpy (&pos[1],
838                    name,
839                    nlen);
840     pos->name = (const char *) &pos[1];
841     pos->subsystem = se;
842     GNUNET_CONTAINER_DLL_insert (se->stat_head,
843                                  se->stat_tail,
844                                  pos);
845     pos->uid = uidgen++;
846     pos->set = GNUNET_NO;
847     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
848                 "New statistic on `%s:%s' with value %llu created.\n",
849                 service,
850                 name,
851                 (unsigned long long) pos->value);
852   }
853   we = GNUNET_new (struct WatchEntry);
854   we->ce = ce;
855   we->last_value_set = GNUNET_NO;
856   we->wid = ce->max_wid++;
857   GNUNET_CONTAINER_DLL_insert (pos->we_head,
858                                pos->we_tail,
859                                we);
860   if (0 != pos->value)
861     notify_change (pos);
862   GNUNET_SERVICE_client_continue (ce->client);
863 }
864
865
866 /**
867  * Handle DISCONNECT-message.  Sync to disk and send
868  * back a #GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM
869  * message.
870  *
871  * @param cls the `struct ClientEntry *`
872  * @param message the actual message
873  */
874 static void
875 handle_disconnect (void *cls,
876                    const struct GNUNET_MessageHeader *message)
877 {
878   struct ClientEntry *ce = cls;
879   struct GNUNET_MQ_Envelope *env;
880   struct GNUNET_MessageHeader *msg;
881
882   env = GNUNET_MQ_msg (msg,
883                        GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM);
884   GNUNET_MQ_send (ce->mq,
885                   env);
886   GNUNET_SERVICE_client_continue (ce->client);
887 }
888
889
890 /**
891  * Actually perform the shutdown.
892  */
893 static void
894 do_shutdown ()
895 {
896   struct WatchEntry *we;
897   struct StatsEntry *pos;
898   struct SubsystemEntry *se;
899
900   if (NULL == nc)
901     return;
902   save ();
903   GNUNET_notification_context_destroy (nc);
904   nc = NULL;
905   GNUNET_assert (0 == client_count);
906   while (NULL != (se = sub_head))
907   {
908     GNUNET_CONTAINER_DLL_remove (sub_head,
909                                  sub_tail,
910                                  se);
911     while (NULL != (pos = se->stat_head))
912     {
913       GNUNET_CONTAINER_DLL_remove (se->stat_head,
914                                    se->stat_tail,
915                                    pos);
916       while (NULL != (we = pos->we_head))
917       {
918         GNUNET_break (0);
919         GNUNET_CONTAINER_DLL_remove (pos->we_head,
920                                      pos->we_tail,
921                                      we);
922         GNUNET_free (we);
923       }
924       GNUNET_free (pos);
925     }
926     GNUNET_free (se);
927   }
928 }
929
930
931 /**
932  * Task run during shutdown.
933  *
934  * @param cls unused
935  */
936 static void
937 shutdown_task (void *cls)
938 {
939   in_shutdown = GNUNET_YES;
940   if (0 != client_count)
941     return;
942   do_shutdown ();
943 }
944
945
946 /**
947  * A client disconnected.  Remove all of its data structure entries.
948  *
949  * @param cls closure, NULL
950  * @param client identification of the client
951  * @param app_cls the `struct ClientEntry *`
952  */
953 static void
954 client_disconnect_cb (void *cls,
955                       struct GNUNET_SERVICE_Client *client,
956                       void *app_cls)
957 {
958   struct ClientEntry *ce = app_cls;
959   struct WatchEntry *we;
960   struct WatchEntry *wen;
961   struct StatsEntry *pos;
962   struct SubsystemEntry *se;
963
964   client_count--;
965   for (se = sub_head; NULL != se; se = se->next)
966   {
967     for (pos = se->stat_head; NULL != pos; pos = pos->next)
968     {
969       wen = pos->we_head;
970       while (NULL != (we = wen))
971       {
972         wen = we->next;
973         if (we->ce != ce)
974           continue;
975         GNUNET_CONTAINER_DLL_remove (pos->we_head,
976                                      pos->we_tail,
977                                      we);
978         GNUNET_free (we);
979       }
980     }
981   }
982   GNUNET_free (ce);
983   if ( (0 == client_count) &&
984        (GNUNET_YES == in_shutdown) )
985     do_shutdown ();
986 }
987
988
989 /**
990  * We've read a `struct GNUNET_STATISTICS_SetMessage *` from
991  * disk. Check that it is well-formed, and if so pass it to
992  * the handler for set messages.
993  *
994  * @param cls NULL
995  * @param message the message found on disk
996  * @return #GNUNET_OK on success,
997  *    #GNUNET_NO to stop further processing (no error)
998  *    #GNUNET_SYSERR to stop further processing with error
999  */
1000 static int
1001 inject_message (void *cls,
1002                 const struct GNUNET_MessageHeader *message)
1003 {
1004   uint16_t msize = ntohs (message->size);
1005   const struct GNUNET_STATISTICS_SetMessage *sm;
1006
1007   sm = (const struct GNUNET_STATISTICS_SetMessage *) message;
1008   if ( (sizeof (struct GNUNET_STATISTICS_SetMessage) > msize) ||
1009        (GNUNET_OK !=
1010         check_set (NULL,
1011                    sm)) )
1012   {
1013     GNUNET_break (0);
1014     return GNUNET_SYSERR;
1015   }
1016   handle_set (NULL,
1017               sm);
1018   return GNUNET_OK;
1019 }
1020
1021
1022 /**
1023  * Load persistent values from disk.  Disk format is exactly the same
1024  * format that we also use for setting the values over the network.
1025  */
1026 static void
1027 load ()
1028 {
1029   char *fn;
1030   struct GNUNET_BIO_ReadHandle *rh;
1031   uint64_t fsize;
1032   char *buf;
1033   struct GNUNET_MessageStreamTokenizer *mst;
1034
1035   if (GNUNET_OK !=
1036       GNUNET_CONFIGURATION_get_value_filename (cfg,
1037                                                "STATISTICS",
1038                                                "DATABASE",
1039                                                &fn))
1040   {
1041     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1042                                "STATISTICS",
1043                                "DATABASE");
1044     return;
1045   }
1046   if ( (GNUNET_OK !=
1047         GNUNET_DISK_file_size (fn,
1048                                &fsize,
1049                                GNUNET_NO,
1050                                GNUNET_YES)) ||
1051        (0 == fsize) )
1052   {
1053     GNUNET_free (fn);
1054     return;
1055   }
1056   buf = GNUNET_malloc (fsize);
1057   rh = GNUNET_BIO_read_open (fn);
1058   if (! rh)
1059   {
1060     GNUNET_free (buf);
1061     GNUNET_free (fn);
1062     return;
1063   }
1064   if (GNUNET_OK !=
1065       GNUNET_BIO_read (rh,
1066                        fn,
1067                        buf,
1068                        fsize))
1069   {
1070     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
1071                               "read",
1072                               fn);
1073     GNUNET_break (GNUNET_OK ==
1074                   GNUNET_BIO_read_close (rh,
1075                                          NULL));
1076     GNUNET_free (buf);
1077     GNUNET_free (fn);
1078     return;
1079   }
1080   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1081               _("Loading %llu bytes of statistics from `%s'\n"),
1082               (unsigned long long) fsize,
1083               fn);
1084   mst = GNUNET_MST_create (&inject_message,
1085                            NULL);
1086   GNUNET_break (GNUNET_OK ==
1087                 GNUNET_MST_from_buffer (mst,
1088                                         buf,
1089                                         (size_t) fsize,
1090                                         GNUNET_YES,
1091                                         GNUNET_NO));
1092   GNUNET_MST_destroy (mst);
1093   GNUNET_free (buf);
1094   GNUNET_break (GNUNET_OK ==
1095                 GNUNET_BIO_read_close (rh,
1096                                        NULL));
1097   GNUNET_free (fn);
1098 }
1099
1100
1101 /**
1102  * Process statistics requests.
1103  *
1104  * @param cls closure
1105  * @param c configuration to use
1106  * @param service the initialized service
1107  */
1108 static void
1109 run (void *cls,
1110      const struct GNUNET_CONFIGURATION_Handle *c,
1111      struct GNUNET_SERVICE_Handle *service)
1112 {
1113   cfg = c;
1114   nc = GNUNET_notification_context_create (16);
1115   load ();
1116   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1117                                  NULL);
1118 }
1119
1120
1121 /**
1122  * Define "main" method using service macro.
1123  */
1124 GNUNET_SERVICE_MAIN
1125 ("statistics",
1126  GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
1127  &run,
1128  &client_connect_cb,
1129  &client_disconnect_cb,
1130  NULL,
1131  GNUNET_MQ_hd_var_size (set,
1132                         GNUNET_MESSAGE_TYPE_STATISTICS_SET,
1133                         struct GNUNET_STATISTICS_SetMessage,
1134                         NULL),
1135  GNUNET_MQ_hd_var_size (get,
1136                         GNUNET_MESSAGE_TYPE_STATISTICS_GET,
1137                         struct GNUNET_MessageHeader,
1138                         NULL),
1139  GNUNET_MQ_hd_var_size (watch,
1140                         GNUNET_MESSAGE_TYPE_STATISTICS_WATCH,
1141                         struct GNUNET_MessageHeader,
1142                         NULL),
1143  GNUNET_MQ_hd_fixed_size (disconnect,
1144                           GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT,
1145                           struct GNUNET_MessageHeader,
1146                           NULL),
1147  GNUNET_MQ_handler_end ());
1148
1149
1150 #if defined(LINUX) && defined(__GLIBC__)
1151 #include <malloc.h>
1152
1153 /**
1154  * MINIMIZE heap size (way below 128k) since this process doesn't need much.
1155  */
1156 void __attribute__ ((constructor))
1157 GNUNET_STATISTICS_memory_init ()
1158 {
1159   mallopt (M_TRIM_THRESHOLD, 4 * 1024);
1160   mallopt (M_TOP_PAD, 1 * 1024);
1161   malloc_trim (0);
1162 }
1163 #endif
1164
1165
1166 /* end of gnunet-service-statistics.c */