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 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.
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.
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/>.
20 * @file statistics/gnunet-service-statistics.c
21 * @brief program that tracks statistics
22 * @author Christian Grothoff
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"
43 * Watch entries are kept in a linked list.
45 struct WatchEntry *next;
48 * Watch entries are kept in a linked list.
50 struct WatchEntry *prev;
53 * For which client is this watch entry?
55 struct ClientEntry *ce;
58 * Last value we communicated to the client for this watch entry.
63 * Unique watch number for this client and this watched value.
69 * #GNUNET_NO : last_value is n/a, #GNUNET_YES: last_value is valid
77 * We keep the statistics organized by subsystem for faster
78 * lookup during SET operations.
80 struct SubsystemEntry;
84 * Entry in the statistics list.
89 * This is a linked list.
91 struct StatsEntry *next;
94 * This is a linked list.
96 struct StatsEntry *prev;
99 * Subsystem this entry belongs to.
101 struct SubsystemEntry *subsystem;
104 * Name for the value stored by this entry, allocated at the end of
110 * Watch context for changes to this value, or NULL for none.
112 struct WatchEntry *we_head;
115 * Watch context for changes to this value, or NULL for none.
117 struct WatchEntry *we_tail;
130 * Is this value persistent?
136 * #GNUNET_NO: value is n/a, #GNUNET_YES: value is valid
144 * We keep the statistics organized by subsystem for faster
145 * lookup during SET operations.
147 struct SubsystemEntry
150 * Subsystems are kept in a DLL.
152 struct SubsystemEntry *next;
155 * Subsystems are kept in a DLL.
157 struct SubsystemEntry *prev;
160 * Head of list of values kept for this subsystem.
162 struct StatsEntry *stat_head;
165 * Tail of list of values kept for this subsystem.
167 struct StatsEntry *stat_tail;
170 * Name of the subsystem this entry is for, allocated at
171 * the end of this struct, do not free().
184 * Corresponding server handle.
186 struct GNUNET_SERVICE_Client *client;
189 * Corresponding message queue.
191 struct GNUNET_MQ_Handle *mq;
194 * Which subsystem is this client writing to (SET/UPDATE)?
196 struct SubsystemEntry *subsystem;
199 * Maximum watch ID used by this client so far.
209 static const struct GNUNET_CONFIGURATION_Handle *cfg;
212 * Head of linked list of subsystems with active statistics.
214 static struct SubsystemEntry *sub_head;
217 * Tail of linked list of subsystems with active statistics.
219 static struct SubsystemEntry *sub_tail;
222 * Number of connected clients.
224 static unsigned int client_count;
227 * Our notification context.
229 static struct GNUNET_NotificationContext *nc;
232 * Counter used to generate unique values.
234 static uint32_t uidgen;
237 * Set to #GNUNET_YES if we are shutting down as soon as possible.
239 static int in_shutdown;
243 * Write persistent statistics to disk.
248 struct SubsystemEntry *se;
249 struct StatsEntry *pos;
251 struct GNUNET_BIO_WriteHandle *wh;
253 unsigned long long total;
256 struct GNUNET_STATISTICS_SetMessage *msg;
259 GNUNET_CONFIGURATION_get_value_filename (cfg,
264 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
269 (void) GNUNET_DISK_directory_create_for_file (fn);
270 wh = GNUNET_BIO_write_open (fn);
272 while (NULL != (se = sub_head))
274 GNUNET_CONTAINER_DLL_remove (sub_head,
277 slen = strlen (se->service) + 1;
278 while (NULL != (pos = se->stat_head))
280 GNUNET_CONTAINER_DLL_remove (se->stat_head,
283 if ( (pos->persistent) &&
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);
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],
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,
305 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
308 if (GNUNET_OK != GNUNET_BIO_write_close (wh))
309 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
327 GNUNET_BIO_write_close (wh))
328 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
335 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
336 _("Wrote %llu bytes of statistics to `%s'\n"),
340 GNUNET_free_non_null (fn);
345 * Transmit the given stats value.
347 * @param client receiver of the value
348 * @param e value to transmit
351 transmit (struct ClientEntry *ce,
352 const struct StatsEntry *e)
354 struct GNUNET_MQ_Envelope *env;
355 struct GNUNET_STATISTICS_ReplyMessage *m;
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,
363 GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
364 m->uid = htonl (e->uid);
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],
372 e->subsystem->service,
374 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
375 "Transmitting value for `%s:%s' (%d): %llu\n",
376 e->subsystem->service,
379 (unsigned long long) e->value);
380 GNUNET_MQ_send (ce->mq,
386 * Callback called when a client connects to the service.
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
394 client_connect_cb (void *cls,
395 struct GNUNET_SERVICE_Client *c,
396 struct GNUNET_MQ_Handle *mq)
398 struct ClientEntry *ce;
400 ce = GNUNET_new (struct ClientEntry);
404 GNUNET_notification_context_add (nc,
411 * Check integrity of GET-message.
413 * @param cls identification of the client
414 * @param message the actual message
415 * @return #GNUNET_OK if @a message is well-formed
418 check_get (void *cls,
419 const struct GNUNET_MessageHeader *message)
425 size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
427 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
434 return GNUNET_SYSERR;
441 * Handle GET-message.
443 * @param cls identification of the client
444 * @param message the actual message
447 handle_get (void *cls,
448 const struct GNUNET_MessageHeader *message)
450 struct ClientEntry *ce = cls;
451 struct GNUNET_MQ_Envelope *env;
452 struct GNUNET_MessageHeader *end;
457 struct SubsystemEntry *se;
458 struct StatsEntry *pos;
461 size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
462 GNUNET_assert (size ==
463 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
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 : "*",
474 for (se = sub_head; NULL != se; se = se->next)
476 if (! ( (0 == slen) ||
477 (0 == strcmp (service, se->service))) )
479 for (pos = se->stat_head; NULL != pos; pos = pos->next)
481 if (! ( (0 == nlen) ||
489 env = GNUNET_MQ_msg (end,
490 GNUNET_MESSAGE_TYPE_STATISTICS_END);
491 GNUNET_MQ_send (ce->mq,
493 GNUNET_SERVICE_client_continue (ce->client);
498 * Notify all clients listening about a change to a value.
500 * @param se value that changed
503 notify_change (struct StatsEntry *se)
505 struct GNUNET_MQ_Envelope *env;
506 struct GNUNET_STATISTICS_WatchValueMessage *wvm;
507 struct WatchEntry *pos;
509 for (pos = se->we_head; NULL != pos; pos = pos->next)
511 if (GNUNET_YES == pos->last_value_set)
513 if (pos->last_value == se->value)
518 pos->last_value_set = GNUNET_YES;
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,
528 pos->last_value = se->value;
534 * Find the subsystem entry of the given name for the specified client.
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)
541 static struct SubsystemEntry *
542 find_subsystem_entry (struct ClientEntry *ce,
546 struct SubsystemEntry *se;
553 (0 != strcmp (service,
556 for (se = sub_head; NULL != se; se = se->next)
557 if (0 == strcmp (service,
565 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
566 "Allocating new subsystem entry `%s'\n",
568 slen = strlen (service) + 1;
569 se = GNUNET_malloc (sizeof (struct SubsystemEntry) +
571 GNUNET_memcpy (&se[1],
574 se->service = (const char *) &se[1];
575 GNUNET_CONTAINER_DLL_insert (sub_head,
585 * Find the statistics entry of the given subsystem.
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
591 static struct StatsEntry *
592 find_stat_entry (struct SubsystemEntry *se,
595 struct StatsEntry *pos;
597 for (pos = se->stat_head; NULL != pos; pos = pos->next)
598 if (0 == strcmp (name, pos->name))
605 * Check format of SET-message.
607 * @param cls the `struct ClientEntry`
608 * @param message the actual message
609 * @return #GNUNET_OK if message is well-formed
612 check_set (void *cls,
613 const struct GNUNET_STATISTICS_SetMessage *msg)
619 msize = ntohs (msg->header.size) - sizeof (*msg);
621 GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
628 return GNUNET_SYSERR;
635 * Handle SET-message.
637 * @param cls the `struct ClientEntry`
638 * @param message the actual message
641 handle_set (void *cls,
642 const struct GNUNET_STATISTICS_SetMessage *msg)
644 struct ClientEntry *ce = cls;
650 struct SubsystemEntry *se;
651 struct StatsEntry *pos;
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],
666 se = find_subsystem_entry (ce,
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",
674 (unsigned int) flags,
675 (unsigned long long) value);
676 pos = find_stat_entry (se,
681 if (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE))
683 changed = (pos->value != value);
688 delta = (int64_t) value;
689 if ((delta < 0) && (pos->value < -delta))
691 changed = (0 != pos->value);
696 changed = (0 != delta);
697 GNUNET_break ( (delta <= 0) ||
698 (pos->value + delta > pos->value) );
702 if (GNUNET_NO == pos->set)
704 pos->set = GNUNET_YES;
707 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
708 if (pos != se->stat_head)
710 /* move to front for faster setting next time! */
711 GNUNET_CONTAINER_DLL_remove (se->stat_head,
714 GNUNET_CONTAINER_DLL_insert (se->stat_head,
718 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
719 "Statistic `%s:%s' updated to value %llu (%d).\n",
722 (unsigned long long) pos->value,
727 GNUNET_SERVICE_client_continue (ce->client);
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],
736 pos->name = (const char *) &pos[1];
738 if ( (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE)) ||
739 (0 < (int64_t) GNUNET_ntohll (msg->value)) )
741 pos->value = GNUNET_ntohll (msg->value);
742 pos->set = GNUNET_YES;
746 pos->set = GNUNET_NO;
749 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
750 GNUNET_CONTAINER_DLL_insert (se->stat_head,
753 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
754 "New statistic on `%s:%s' with value %llu created.\n",
757 (unsigned long long) pos->value);
759 GNUNET_SERVICE_client_continue (ce->client);
764 * Check integrity of WATCH-message.
766 * @param cls the `struct ClientEntry *`
767 * @param message the actual message
768 * @return #GNUNET_OK if message is well-formed
771 check_watch (void *cls,
772 const struct GNUNET_MessageHeader *message)
778 size = ntohs (message->size) - sizeof (struct GNUNET_MessageHeader);
780 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
787 return GNUNET_SYSERR;
794 * Handle WATCH-message.
796 * @param cls the `struct ClientEntry *`
797 * @param message the actual message
800 handle_watch (void *cls,
801 const struct GNUNET_MessageHeader *message)
803 struct ClientEntry *ce = cls;
808 struct SubsystemEntry *se;
809 struct StatsEntry *pos;
810 struct WatchEntry *we;
815 GNUNET_SERVICE_client_drop (ce->client);
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],
827 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
828 "Received request to watch statistic on `%s:%s'\n",
831 se = find_subsystem_entry (ce,
833 pos = find_stat_entry (se,
837 nlen = strlen (name) + 1;
838 pos = GNUNET_malloc (sizeof (struct StatsEntry) +
840 GNUNET_memcpy (&pos[1],
843 pos->name = (const char *) &pos[1];
845 GNUNET_CONTAINER_DLL_insert (se->stat_head,
849 pos->set = GNUNET_NO;
850 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
851 "New statistic on `%s:%s' with value %llu created.\n",
854 (unsigned long long) pos->value);
856 we = GNUNET_new (struct WatchEntry);
858 we->last_value_set = GNUNET_NO;
859 we->wid = ce->max_wid++;
860 GNUNET_CONTAINER_DLL_insert (pos->we_head,
865 GNUNET_SERVICE_client_continue (ce->client);
870 * Handle DISCONNECT-message. Sync to disk and send
871 * back a #GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM
874 * @param cls the `struct ClientEntry *`
875 * @param message the actual message
878 handle_disconnect (void *cls,
879 const struct GNUNET_MessageHeader *message)
881 struct ClientEntry *ce = cls;
882 struct GNUNET_MQ_Envelope *env;
883 struct GNUNET_MessageHeader *msg;
885 env = GNUNET_MQ_msg (msg,
886 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM);
887 GNUNET_MQ_send (ce->mq,
889 GNUNET_SERVICE_client_continue (ce->client);
894 * Actually perform the shutdown.
899 struct WatchEntry *we;
900 struct StatsEntry *pos;
901 struct SubsystemEntry *se;
906 GNUNET_notification_context_destroy (nc);
908 GNUNET_assert (0 == client_count);
909 while (NULL != (se = sub_head))
911 GNUNET_CONTAINER_DLL_remove (sub_head,
914 while (NULL != (pos = se->stat_head))
916 GNUNET_CONTAINER_DLL_remove (se->stat_head,
919 while (NULL != (we = pos->we_head))
922 GNUNET_CONTAINER_DLL_remove (pos->we_head,
935 * Task run during shutdown.
940 shutdown_task (void *cls)
942 in_shutdown = GNUNET_YES;
943 if (0 != client_count)
950 * A client disconnected. Remove all of its data structure entries.
952 * @param cls closure, NULL
953 * @param client identification of the client
954 * @param app_cls the `struct ClientEntry *`
957 client_disconnect_cb (void *cls,
958 struct GNUNET_SERVICE_Client *client,
961 struct ClientEntry *ce = app_cls;
962 struct WatchEntry *we;
963 struct WatchEntry *wen;
964 struct StatsEntry *pos;
965 struct SubsystemEntry *se;
968 for (se = sub_head; NULL != se; se = se->next)
970 for (pos = se->stat_head; NULL != pos; pos = pos->next)
973 while (NULL != (we = wen))
978 GNUNET_CONTAINER_DLL_remove (pos->we_head,
986 if ( (0 == client_count) &&
987 (GNUNET_YES == in_shutdown) )
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.
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
1004 inject_message (void *cls,
1005 const struct GNUNET_MessageHeader *message)
1007 uint16_t msize = ntohs (message->size);
1008 const struct GNUNET_STATISTICS_SetMessage *sm;
1010 sm = (const struct GNUNET_STATISTICS_SetMessage *) message;
1011 if ( (sizeof (struct GNUNET_STATISTICS_SetMessage) > msize) ||
1017 return GNUNET_SYSERR;
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.
1033 struct GNUNET_BIO_ReadHandle *rh;
1036 struct GNUNET_MessageStreamTokenizer *mst;
1039 GNUNET_CONFIGURATION_get_value_filename (cfg,
1044 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1050 GNUNET_DISK_file_size (fn,
1059 buf = GNUNET_malloc (fsize);
1060 rh = GNUNET_BIO_read_open (fn);
1068 GNUNET_BIO_read (rh,
1073 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
1076 GNUNET_break (GNUNET_OK ==
1077 GNUNET_BIO_read_close (rh,
1083 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1084 _("Loading %llu bytes of statistics from `%s'\n"),
1085 (unsigned long long) fsize,
1087 mst = GNUNET_MST_create (&inject_message,
1089 GNUNET_break (GNUNET_OK ==
1090 GNUNET_MST_from_buffer (mst,
1095 GNUNET_MST_destroy (mst);
1097 GNUNET_break (GNUNET_OK ==
1098 GNUNET_BIO_read_close (rh,
1105 * Process statistics requests.
1107 * @param cls closure
1108 * @param c configuration to use
1109 * @param service the initialized service
1113 const struct GNUNET_CONFIGURATION_Handle *c,
1114 struct GNUNET_SERVICE_Handle *service)
1117 nc = GNUNET_notification_context_create (16);
1119 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1125 * Define "main" method using service macro.
1129 GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
1132 &client_disconnect_cb,
1134 GNUNET_MQ_hd_var_size (set,
1135 GNUNET_MESSAGE_TYPE_STATISTICS_SET,
1136 struct GNUNET_STATISTICS_SetMessage,
1138 GNUNET_MQ_hd_var_size (get,
1139 GNUNET_MESSAGE_TYPE_STATISTICS_GET,
1140 struct GNUNET_MessageHeader,
1142 GNUNET_MQ_hd_var_size (watch,
1143 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH,
1144 struct GNUNET_MessageHeader,
1146 GNUNET_MQ_hd_fixed_size (disconnect,
1147 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT,
1148 struct GNUNET_MessageHeader,
1150 GNUNET_MQ_handler_end ());
1153 #if defined(LINUX) && defined(__GLIBC__)
1157 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
1159 void __attribute__ ((constructor))
1160 GNUNET_STATISTICS_memory_init ()
1162 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
1163 mallopt (M_TOP_PAD, 1 * 1024);
1169 /* end of gnunet-service-statistics.c */