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