2 This file is part of GNUnet.
3 Copyright (C) 2009, 2010, 2012, 2014, 2016 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
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.
17 * @file statistics/gnunet-service-statistics.c
18 * @brief program that tracks statistics
19 * @author Christian Grothoff
22 #include "gnunet_bio_lib.h"
23 #include "gnunet_container_lib.h"
24 #include "gnunet_disk_lib.h"
25 #include "gnunet_getopt_lib.h"
26 #include "gnunet_protocols.h"
27 #include "gnunet_service_lib.h"
28 #include "gnunet_statistics_service.h"
29 #include "gnunet_strings_lib.h"
30 #include "gnunet_time_lib.h"
31 #include "statistics.h"
40 * Watch entries are kept in a linked list.
42 struct WatchEntry *next;
45 * Watch entries are kept in a linked list.
47 struct WatchEntry *prev;
50 * For which client is this watch entry?
52 struct ClientEntry *ce;
55 * Last value we communicated to the client for this watch entry.
60 * Unique watch number for this client and this watched value.
66 * #GNUNET_NO : last_value is n/a, #GNUNET_YES: last_value is valid
74 * We keep the statistics organized by subsystem for faster
75 * lookup during SET operations.
77 struct SubsystemEntry;
81 * Entry in the statistics list.
86 * This is a linked list.
88 struct StatsEntry *next;
91 * This is a linked list.
93 struct StatsEntry *prev;
96 * Subsystem this entry belongs to.
98 struct SubsystemEntry *subsystem;
101 * Name for the value stored by this entry, allocated at the end of
107 * Watch context for changes to this value, or NULL for none.
109 struct WatchEntry *we_head;
112 * Watch context for changes to this value, or NULL for none.
114 struct WatchEntry *we_tail;
127 * Is this value persistent?
133 * #GNUNET_NO: value is n/a, #GNUNET_YES: value is valid
141 * We keep the statistics organized by subsystem for faster
142 * lookup during SET operations.
144 struct SubsystemEntry
147 * Subsystems are kept in a DLL.
149 struct SubsystemEntry *next;
152 * Subsystems are kept in a DLL.
154 struct SubsystemEntry *prev;
157 * Head of list of values kept for this subsystem.
159 struct StatsEntry *stat_head;
162 * Tail of list of values kept for this subsystem.
164 struct StatsEntry *stat_tail;
167 * Name of the subsystem this entry is for, allocated at
168 * the end of this struct, do not free().
181 * Corresponding server handle.
183 struct GNUNET_SERVICE_Client *client;
186 * Corresponding message queue.
188 struct GNUNET_MQ_Handle *mq;
191 * Which subsystem is this client writing to (SET/UPDATE)?
193 struct SubsystemEntry *subsystem;
196 * Maximum watch ID used by this client so far.
206 static const struct GNUNET_CONFIGURATION_Handle *cfg;
209 * Head of linked list of subsystems with active statistics.
211 static struct SubsystemEntry *sub_head;
214 * Tail of linked list of subsystems with active statistics.
216 static struct SubsystemEntry *sub_tail;
219 * Number of connected clients.
221 static unsigned int client_count;
224 * Our notification context.
226 static struct GNUNET_NotificationContext *nc;
229 * Counter used to generate unique values.
231 static uint32_t uidgen;
234 * Set to #GNUNET_YES if we are shutting down as soon as possible.
236 static int in_shutdown;
240 * Write persistent statistics to disk.
245 struct SubsystemEntry *se;
246 struct StatsEntry *pos;
248 struct GNUNET_BIO_WriteHandle *wh;
250 unsigned long long total;
253 struct GNUNET_STATISTICS_SetMessage *msg;
256 GNUNET_CONFIGURATION_get_value_filename (cfg,
261 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
266 (void) GNUNET_DISK_directory_create_for_file (fn);
267 wh = GNUNET_BIO_write_open (fn);
269 while (NULL != (se = sub_head))
271 GNUNET_CONTAINER_DLL_remove (sub_head,
274 slen = strlen (se->service) + 1;
275 while (NULL != (pos = se->stat_head))
277 GNUNET_CONTAINER_DLL_remove (se->stat_head,
280 if ( (pos->persistent) &&
283 nlen = strlen (pos->name) + 1;
284 size = sizeof (struct GNUNET_STATISTICS_SetMessage) + nlen + slen;
285 GNUNET_assert (size < UINT16_MAX);
286 msg = GNUNET_malloc (size);
288 msg->header.size = htons ((uint16_t) size);
289 msg->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_SET);
290 GNUNET_assert (nlen + slen ==
291 GNUNET_STRINGS_buffer_fill ((char *) &msg[1],
296 msg->flags = htonl (pos->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
297 msg->value = GNUNET_htonll (pos->value);
298 if (GNUNET_OK != GNUNET_BIO_write (wh,
302 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
305 if (GNUNET_OK != GNUNET_BIO_write_close (wh))
306 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
324 GNUNET_BIO_write_close (wh))
325 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
332 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
333 _("Wrote %llu bytes of statistics to `%s'\n"),
337 GNUNET_free_non_null (fn);
342 * Transmit the given stats value.
344 * @param client receiver of the value
345 * @param e value to transmit
348 transmit (struct ClientEntry *ce,
349 const struct StatsEntry *e)
351 struct GNUNET_MQ_Envelope *env;
352 struct GNUNET_STATISTICS_ReplyMessage *m;
355 size = strlen (e->subsystem->service) + 1 +
356 strlen (e->name) + 1;
357 GNUNET_assert (size < GNUNET_MAX_MESSAGE_SIZE);
358 env = GNUNET_MQ_msg_extra (m,
360 GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
361 m->uid = htonl (e->uid);
363 m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
364 m->value = GNUNET_htonll (e->value);
365 GNUNET_assert (size ==
366 GNUNET_STRINGS_buffer_fill ((char *) &m[1],
369 e->subsystem->service,
371 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
372 "Transmitting value for `%s:%s' (%d): %llu\n",
373 e->subsystem->service,
376 (unsigned long long) e->value);
377 GNUNET_MQ_send (ce->mq,
383 * Callback called when a client connects to the service.
385 * @param cls closure for the service
386 * @param c the new client that connected to the service
387 * @param mq the message queue used to send messages to the client
391 client_connect_cb (void *cls,
392 struct GNUNET_SERVICE_Client *c,
393 struct GNUNET_MQ_Handle *mq)
395 struct ClientEntry *ce;
397 ce = GNUNET_new (struct ClientEntry);
401 GNUNET_notification_context_add (nc,
408 * Check integrity of GET-message.
410 * @param cls identification of the client
411 * @param message the actual message
412 * @return #GNUNET_OK if @a message is well-formed
415 check_get (void *cls,
416 const struct GNUNET_MessageHeader *message)
422 size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
424 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
431 return GNUNET_SYSERR;
438 * Handle GET-message.
440 * @param cls identification of the client
441 * @param message the actual message
444 handle_get (void *cls,
445 const struct GNUNET_MessageHeader *message)
447 struct ClientEntry *ce = cls;
448 struct GNUNET_MQ_Envelope *env;
449 struct GNUNET_MessageHeader *end;
454 struct SubsystemEntry *se;
455 struct StatsEntry *pos;
458 size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
459 GNUNET_assert (size ==
460 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
465 slen = strlen (service);
466 nlen = strlen (name);
467 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
468 "Received request for statistics on `%s:%s'\n",
469 slen ? service : "*",
471 for (se = sub_head; NULL != se; se = se->next)
473 if (! ( (0 == slen) ||
474 (0 == strcmp (service, se->service))) )
476 for (pos = se->stat_head; NULL != pos; pos = pos->next)
478 if (! ( (0 == nlen) ||
486 env = GNUNET_MQ_msg (end,
487 GNUNET_MESSAGE_TYPE_STATISTICS_END);
488 GNUNET_MQ_send (ce->mq,
490 GNUNET_SERVICE_client_continue (ce->client);
495 * Notify all clients listening about a change to a value.
497 * @param se value that changed
500 notify_change (struct StatsEntry *se)
502 struct GNUNET_MQ_Envelope *env;
503 struct GNUNET_STATISTICS_WatchValueMessage *wvm;
504 struct WatchEntry *pos;
506 for (pos = se->we_head; NULL != pos; pos = pos->next)
508 if (GNUNET_YES == pos->last_value_set)
510 if (pos->last_value == se->value)
515 pos->last_value_set = GNUNET_YES;
517 env = GNUNET_MQ_msg (wvm,
518 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE);
519 wvm->flags = htonl (se->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
520 wvm->wid = htonl (pos->wid);
521 wvm->reserved = htonl (0);
522 wvm->value = GNUNET_htonll (se->value);
523 GNUNET_MQ_send (pos->ce->mq,
525 pos->last_value = se->value;
531 * Find the subsystem entry of the given name for the specified client.
533 * @param ce client looking for the subsystem, may contain a hint
534 * to find the entry faster, can be NULL
535 * @param service name of the subsystem to look for
536 * @return subsystem entry, never NULL (subsystem entry is created if necessary)
538 static struct SubsystemEntry *
539 find_subsystem_entry (struct ClientEntry *ce,
543 struct SubsystemEntry *se;
550 (0 != strcmp (service,
553 for (se = sub_head; NULL != se; se = se->next)
554 if (0 == strcmp (service,
562 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
563 "Allocating new subsystem entry `%s'\n",
565 slen = strlen (service) + 1;
566 se = GNUNET_malloc (sizeof (struct SubsystemEntry) +
568 GNUNET_memcpy (&se[1],
571 se->service = (const char *) &se[1];
572 GNUNET_CONTAINER_DLL_insert (sub_head,
582 * Find the statistics entry of the given subsystem.
584 * @param subsystem subsystem to look in
585 * @param name name of the entry to look for
586 * @return statistis entry, or NULL if not found
588 static struct StatsEntry *
589 find_stat_entry (struct SubsystemEntry *se,
592 struct StatsEntry *pos;
594 for (pos = se->stat_head; NULL != pos; pos = pos->next)
595 if (0 == strcmp (name, pos->name))
602 * Check format of SET-message.
604 * @param cls the `struct ClientEntry`
605 * @param message the actual message
606 * @return #GNUNET_OK if message is well-formed
609 check_set (void *cls,
610 const struct GNUNET_STATISTICS_SetMessage *msg)
616 msize = ntohs (msg->header.size) - sizeof (*msg);
618 GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
625 return GNUNET_SYSERR;
632 * Handle SET-message.
634 * @param cls the `struct ClientEntry`
635 * @param message the actual message
638 handle_set (void *cls,
639 const struct GNUNET_STATISTICS_SetMessage *msg)
641 struct ClientEntry *ce = cls;
647 struct SubsystemEntry *se;
648 struct StatsEntry *pos;
655 msize = ntohs (msg->header.size);
656 size = msize - sizeof (struct GNUNET_STATISTICS_SetMessage);
657 GNUNET_assert (size ==
658 GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
663 se = find_subsystem_entry (ce,
665 flags = ntohl (msg->flags);
666 value = GNUNET_ntohll (msg->value);
667 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
668 "Received request to update statistic on `%s:%s' (%u) to/by %llu\n",
671 (unsigned int) flags,
672 (unsigned long long) value);
673 pos = find_stat_entry (se,
678 if (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE))
680 changed = (pos->value != value);
685 delta = (int64_t) value;
686 if ((delta < 0) && (pos->value < -delta))
688 changed = (0 != pos->value);
693 changed = (0 != delta);
694 GNUNET_break ( (delta <= 0) ||
695 (pos->value + delta > pos->value) );
699 if (GNUNET_NO == pos->set)
701 pos->set = GNUNET_YES;
704 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
705 if (pos != se->stat_head)
707 /* move to front for faster setting next time! */
708 GNUNET_CONTAINER_DLL_remove (se->stat_head,
711 GNUNET_CONTAINER_DLL_insert (se->stat_head,
715 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
716 "Statistic `%s:%s' updated to value %llu (%d).\n",
719 (unsigned long long) pos->value,
724 GNUNET_SERVICE_client_continue (ce->client);
727 /* not found, create a new entry */
728 nlen = strlen (name) + 1;
729 pos = GNUNET_malloc (sizeof (struct StatsEntry) + nlen);
730 GNUNET_memcpy (&pos[1],
733 pos->name = (const char *) &pos[1];
735 if ( (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE)) ||
736 (0 < (int64_t) GNUNET_ntohll (msg->value)) )
738 pos->value = GNUNET_ntohll (msg->value);
739 pos->set = GNUNET_YES;
743 pos->set = GNUNET_NO;
746 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
747 GNUNET_CONTAINER_DLL_insert (se->stat_head,
750 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
751 "New statistic on `%s:%s' with value %llu created.\n",
754 (unsigned long long) pos->value);
756 GNUNET_SERVICE_client_continue (ce->client);
761 * Check integrity of WATCH-message.
763 * @param cls the `struct ClientEntry *`
764 * @param message the actual message
765 * @return #GNUNET_OK if message is well-formed
768 check_watch (void *cls,
769 const struct GNUNET_MessageHeader *message)
775 size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
777 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
784 return GNUNET_SYSERR;
791 * Handle WATCH-message.
793 * @param cls the `struct ClientEntry *`
794 * @param message the actual message
797 handle_watch (void *cls,
798 const struct GNUNET_MessageHeader *message)
800 struct ClientEntry *ce = cls;
805 struct SubsystemEntry *se;
806 struct StatsEntry *pos;
807 struct WatchEntry *we;
812 GNUNET_SERVICE_client_drop (ce->client);
815 GNUNET_SERVICE_client_mark_monitor (ce->client);
816 msize = ntohs (message->size);
817 size = msize - sizeof (struct GNUNET_MessageHeader);
818 GNUNET_assert (size ==
819 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
824 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
825 "Received request to watch statistic on `%s:%s'\n",
828 se = find_subsystem_entry (ce,
830 pos = find_stat_entry (se,
834 nlen = strlen (name) + 1;
835 pos = GNUNET_malloc (sizeof (struct StatsEntry) +
837 GNUNET_memcpy (&pos[1],
840 pos->name = (const char *) &pos[1];
842 GNUNET_CONTAINER_DLL_insert (se->stat_head,
846 pos->set = GNUNET_NO;
847 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
848 "New statistic on `%s:%s' with value %llu created.\n",
851 (unsigned long long) pos->value);
853 we = GNUNET_new (struct WatchEntry);
855 we->last_value_set = GNUNET_NO;
856 we->wid = ce->max_wid++;
857 GNUNET_CONTAINER_DLL_insert (pos->we_head,
862 GNUNET_SERVICE_client_continue (ce->client);
867 * Handle DISCONNECT-message. Sync to disk and send
868 * back a #GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM
871 * @param cls the `struct ClientEntry *`
872 * @param message the actual message
875 handle_disconnect (void *cls,
876 const struct GNUNET_MessageHeader *message)
878 struct ClientEntry *ce = cls;
879 struct GNUNET_MQ_Envelope *env;
880 struct GNUNET_MessageHeader *msg;
882 env = GNUNET_MQ_msg (msg,
883 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM);
884 GNUNET_MQ_send (ce->mq,
886 GNUNET_SERVICE_client_continue (ce->client);
891 * Actually perform the shutdown.
896 struct WatchEntry *we;
897 struct StatsEntry *pos;
898 struct SubsystemEntry *se;
903 GNUNET_notification_context_destroy (nc);
905 GNUNET_assert (0 == client_count);
906 while (NULL != (se = sub_head))
908 GNUNET_CONTAINER_DLL_remove (sub_head,
911 while (NULL != (pos = se->stat_head))
913 GNUNET_CONTAINER_DLL_remove (se->stat_head,
916 while (NULL != (we = pos->we_head))
919 GNUNET_CONTAINER_DLL_remove (pos->we_head,
932 * Task run during shutdown.
937 shutdown_task (void *cls)
939 in_shutdown = GNUNET_YES;
940 if (0 != client_count)
947 * A client disconnected. Remove all of its data structure entries.
949 * @param cls closure, NULL
950 * @param client identification of the client
951 * @param app_cls the `struct ClientEntry *`
954 client_disconnect_cb (void *cls,
955 struct GNUNET_SERVICE_Client *client,
958 struct ClientEntry *ce = app_cls;
959 struct WatchEntry *we;
960 struct WatchEntry *wen;
961 struct StatsEntry *pos;
962 struct SubsystemEntry *se;
965 for (se = sub_head; NULL != se; se = se->next)
967 for (pos = se->stat_head; NULL != pos; pos = pos->next)
970 while (NULL != (we = wen))
975 GNUNET_CONTAINER_DLL_remove (pos->we_head,
983 if ( (0 == client_count) &&
984 (GNUNET_YES == in_shutdown) )
990 * We've read a `struct GNUNET_STATISTICS_SetMessage *` from
991 * disk. Check that it is well-formed, and if so pass it to
992 * the handler for set messages.
995 * @param message the message found on disk
996 * @return #GNUNET_OK on success,
997 * #GNUNET_NO to stop further processing (no error)
998 * #GNUNET_SYSERR to stop further processing with error
1001 inject_message (void *cls,
1002 const struct GNUNET_MessageHeader *message)
1004 uint16_t msize = ntohs (message->size);
1005 const struct GNUNET_STATISTICS_SetMessage *sm;
1007 sm = (const struct GNUNET_STATISTICS_SetMessage *) message;
1008 if ( (sizeof (struct GNUNET_STATISTICS_SetMessage) > msize) ||
1014 return GNUNET_SYSERR;
1023 * Load persistent values from disk. Disk format is exactly the same
1024 * format that we also use for setting the values over the network.
1030 struct GNUNET_BIO_ReadHandle *rh;
1033 struct GNUNET_MessageStreamTokenizer *mst;
1036 GNUNET_CONFIGURATION_get_value_filename (cfg,
1041 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1047 GNUNET_DISK_file_size (fn,
1056 buf = GNUNET_malloc (fsize);
1057 rh = GNUNET_BIO_read_open (fn);
1065 GNUNET_BIO_read (rh,
1070 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
1073 GNUNET_break (GNUNET_OK ==
1074 GNUNET_BIO_read_close (rh,
1080 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1081 _("Loading %llu bytes of statistics from `%s'\n"),
1082 (unsigned long long) fsize,
1084 mst = GNUNET_MST_create (&inject_message,
1086 GNUNET_break (GNUNET_OK ==
1087 GNUNET_MST_from_buffer (mst,
1092 GNUNET_MST_destroy (mst);
1094 GNUNET_break (GNUNET_OK ==
1095 GNUNET_BIO_read_close (rh,
1102 * Process statistics requests.
1104 * @param cls closure
1105 * @param c configuration to use
1106 * @param service the initialized service
1110 const struct GNUNET_CONFIGURATION_Handle *c,
1111 struct GNUNET_SERVICE_Handle *service)
1114 nc = GNUNET_notification_context_create (16);
1116 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1122 * Define "main" method using service macro.
1126 GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
1129 &client_disconnect_cb,
1131 GNUNET_MQ_hd_var_size (set,
1132 GNUNET_MESSAGE_TYPE_STATISTICS_SET,
1133 struct GNUNET_STATISTICS_SetMessage,
1135 GNUNET_MQ_hd_var_size (get,
1136 GNUNET_MESSAGE_TYPE_STATISTICS_GET,
1137 struct GNUNET_MessageHeader,
1139 GNUNET_MQ_hd_var_size (watch,
1140 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH,
1141 struct GNUNET_MessageHeader,
1143 GNUNET_MQ_hd_fixed_size (disconnect,
1144 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT,
1145 struct GNUNET_MessageHeader,
1147 GNUNET_MQ_handler_end ());
1150 #if defined(LINUX) && defined(__GLIBC__)
1154 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
1156 void __attribute__ ((constructor))
1157 GNUNET_STATISTICS_memory_init ()
1159 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
1160 mallopt (M_TOP_PAD, 1 * 1024);
1166 /* end of gnunet-service-statistics.c */