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