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