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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file statistics/gnunet-service-statistics.c
23 * @brief program that tracks statistics
24 * @author Christian Grothoff
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"
44 * Watch entries are kept in a linked list.
46 struct WatchEntry *next;
49 * Watch entries are kept in a linked list.
51 struct WatchEntry *prev;
54 * For which client is this watch entry?
56 struct ClientEntry *ce;
59 * Last value we communicated to the client for this watch entry.
64 * Unique watch number for this client and this watched value.
70 * #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
143 * We keep the statistics organized by subsystem for faster
144 * lookup during SET operations.
146 struct SubsystemEntry
149 * Subsystems are kept in a DLL.
151 struct SubsystemEntry *next;
154 * Subsystems are kept in a DLL.
156 struct SubsystemEntry *prev;
159 * Head of list of values kept for this subsystem.
161 struct StatsEntry *stat_head;
164 * Tail of list of values kept for this subsystem.
166 struct StatsEntry *stat_tail;
169 * Name of the subsystem this entry is for, allocated at
170 * the end of this struct, do not free().
182 * Corresponding server handle.
184 struct GNUNET_SERVICE_Client *client;
187 * Corresponding message queue.
189 struct GNUNET_MQ_Handle *mq;
192 * Which subsystem is this client writing to (SET/UPDATE)?
194 struct SubsystemEntry *subsystem;
197 * 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;
255 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg,
260 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
265 (void) GNUNET_DISK_directory_create_for_file (fn);
266 wh = GNUNET_BIO_write_open (fn);
268 while (NULL != (se = sub_head))
270 GNUNET_CONTAINER_DLL_remove (sub_head, sub_tail, se);
271 slen = strlen (se->service) + 1;
272 while (NULL != (pos = se->stat_head))
274 GNUNET_CONTAINER_DLL_remove (se->stat_head, se->stat_tail, pos);
275 if ((pos->persistent) && (NULL != wh))
277 nlen = strlen (pos->name) + 1;
278 size = sizeof(struct GNUNET_STATISTICS_SetMessage) + nlen + slen;
279 GNUNET_assert (size < UINT16_MAX);
280 msg = GNUNET_malloc (size);
282 msg->header.size = htons ((uint16_t) size);
283 msg->header.type = htons (GNUNET_MESSAGE_TYPE_STATISTICS_SET);
284 GNUNET_assert (nlen + slen ==
285 GNUNET_STRINGS_buffer_fill ((char *) &msg[1],
291 htonl (pos->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
292 msg->value = GNUNET_htonll (pos->value);
293 if (GNUNET_OK != GNUNET_BIO_write (wh, msg, size))
295 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
296 if (GNUNET_OK != GNUNET_BIO_write_close (wh))
297 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", fn);
312 if (GNUNET_OK != GNUNET_BIO_write_close (wh))
313 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", fn);
315 GNUNET_break (0 == unlink (fn));
317 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
318 _ ("Wrote %llu bytes of statistics to `%s'\n"),
322 GNUNET_free_non_null (fn);
327 * Transmit the given stats value.
329 * @param client receiver of the value
330 * @param e value to transmit
333 transmit (struct ClientEntry *ce, const struct StatsEntry *e)
335 struct GNUNET_MQ_Envelope *env;
336 struct GNUNET_STATISTICS_ReplyMessage *m;
339 size = strlen (e->subsystem->service) + 1 + strlen (e->name) + 1;
340 GNUNET_assert (size < GNUNET_MAX_MESSAGE_SIZE);
341 env = GNUNET_MQ_msg_extra (m, size, GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
342 m->uid = htonl (e->uid);
344 m->uid |= htonl (GNUNET_STATISTICS_PERSIST_BIT);
345 m->value = GNUNET_htonll (e->value);
346 GNUNET_assert (size == GNUNET_STRINGS_buffer_fill ((char *) &m[1],
349 e->subsystem->service,
351 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
352 "Transmitting value for `%s:%s' (%d): %llu\n",
353 e->subsystem->service,
356 (unsigned long long) e->value);
357 GNUNET_MQ_send (ce->mq, env);
362 * Callback called when a client connects to the service.
364 * @param cls closure for the service
365 * @param c the new client that connected to the service
366 * @param mq the message queue used to send messages to the client
370 client_connect_cb (void *cls,
371 struct GNUNET_SERVICE_Client *c,
372 struct GNUNET_MQ_Handle *mq)
374 struct ClientEntry *ce;
376 ce = GNUNET_new (struct ClientEntry);
380 GNUNET_notification_context_add (nc, mq);
386 * Check integrity of GET-message.
388 * @param cls identification of the client
389 * @param message the actual message
390 * @return #GNUNET_OK if @a message is well-formed
393 check_get (void *cls, const struct GNUNET_MessageHeader *message)
399 size = ntohs (message->size) - sizeof(struct GNUNET_MessageHeader);
400 if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
407 return GNUNET_SYSERR;
414 * Handle GET-message.
416 * @param cls identification of the client
417 * @param message the actual message
420 handle_get (void *cls, const struct GNUNET_MessageHeader *message)
422 struct ClientEntry *ce = cls;
423 struct GNUNET_MQ_Envelope *env;
424 struct GNUNET_MessageHeader *end;
429 struct SubsystemEntry *se;
430 struct StatsEntry *pos;
433 size = ntohs (message->size) - sizeof(struct GNUNET_MessageHeader);
434 GNUNET_assert (size ==
435 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
440 slen = strlen (service);
441 nlen = strlen (name);
442 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
443 "Received request for statistics on `%s:%s'\n",
444 slen ? service : "*",
446 for (se = sub_head; NULL != se; se = se->next)
448 if (! ((0 == slen) || (0 == strcmp (service, se->service))))
450 for (pos = se->stat_head; NULL != pos; pos = pos->next)
452 if (! ((0 == nlen) || (0 == strcmp (name, pos->name))))
457 env = GNUNET_MQ_msg (end, GNUNET_MESSAGE_TYPE_STATISTICS_END);
458 GNUNET_MQ_send (ce->mq, env);
459 GNUNET_SERVICE_client_continue (ce->client);
464 * Notify all clients listening about a change to a value.
466 * @param se value that changed
469 notify_change (struct StatsEntry *se)
471 struct GNUNET_MQ_Envelope *env;
472 struct GNUNET_STATISTICS_WatchValueMessage *wvm;
473 struct WatchEntry *pos;
475 for (pos = se->we_head; NULL != pos; pos = pos->next)
477 if (GNUNET_YES == pos->last_value_set)
479 if (pos->last_value == se->value)
484 pos->last_value_set = GNUNET_YES;
486 env = GNUNET_MQ_msg (wvm, GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE);
488 htonl (se->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
489 wvm->wid = htonl (pos->wid);
490 wvm->reserved = htonl (0);
491 wvm->value = GNUNET_htonll (se->value);
492 GNUNET_MQ_send (pos->ce->mq, env);
493 pos->last_value = se->value;
499 * Find the subsystem entry of the given name for the specified client.
501 * @param ce client looking for the subsystem, may contain a hint
502 * to find the entry faster, can be NULL
503 * @param service name of the subsystem to look for
504 * @return subsystem entry, never NULL (subsystem entry is created if necessary)
506 static struct SubsystemEntry *
507 find_subsystem_entry (struct ClientEntry *ce, const char *service)
510 struct SubsystemEntry *se;
516 if ((NULL == se) || (0 != strcmp (service, se->service)))
518 for (se = sub_head; NULL != se; se = se->next)
519 if (0 == strcmp (service, se->service))
526 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
527 "Allocating new subsystem entry `%s'\n",
529 slen = strlen (service) + 1;
530 se = GNUNET_malloc (sizeof(struct SubsystemEntry) + slen);
531 GNUNET_memcpy (&se[1], service, slen);
532 se->service = (const char *) &se[1];
533 GNUNET_CONTAINER_DLL_insert (sub_head, sub_tail, se);
541 * Find the statistics entry of the given subsystem.
543 * @param subsystem subsystem to look in
544 * @param name name of the entry to look for
545 * @return statistis entry, or NULL if not found
547 static struct StatsEntry *
548 find_stat_entry (struct SubsystemEntry *se, const char *name)
550 struct StatsEntry *pos;
552 for (pos = se->stat_head; NULL != pos; pos = pos->next)
553 if (0 == strcmp (name, pos->name))
560 * Check format of SET-message.
562 * @param cls the `struct ClientEntry`
563 * @param message the actual message
564 * @return #GNUNET_OK if message is well-formed
567 check_set (void *cls, const struct GNUNET_STATISTICS_SetMessage *msg)
573 msize = ntohs (msg->header.size) - sizeof(*msg);
574 if (msize != GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
581 return GNUNET_SYSERR;
588 * Handle SET-message.
590 * @param cls the `struct ClientEntry`
591 * @param message the actual message
594 handle_set (void *cls, const struct GNUNET_STATISTICS_SetMessage *msg)
596 struct ClientEntry *ce = cls;
602 struct SubsystemEntry *se;
603 struct StatsEntry *pos;
610 msize = ntohs (msg->header.size);
611 size = msize - sizeof(struct GNUNET_STATISTICS_SetMessage);
612 GNUNET_assert (size == GNUNET_STRINGS_buffer_tokenize ((const char *) &msg[1],
617 se = find_subsystem_entry (ce, service);
618 flags = ntohl (msg->flags);
619 value = GNUNET_ntohll (msg->value);
620 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
621 "Received request to update statistic on `%s:%s' (%u) to/by %llu\n",
624 (unsigned int) flags,
625 (unsigned long long) value);
626 pos = find_stat_entry (se, name);
630 if (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE))
632 changed = (pos->value != value);
637 delta = (int64_t) value;
638 if ((delta < 0) && (pos->value < -delta))
640 changed = (0 != pos->value);
645 changed = (0 != delta);
646 GNUNET_break ((delta <= 0) || (pos->value + delta > pos->value));
650 if (GNUNET_NO == pos->set)
652 pos->set = GNUNET_YES;
655 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
656 if (pos != se->stat_head)
658 /* move to front for faster setting next time! */
659 GNUNET_CONTAINER_DLL_remove (se->stat_head, se->stat_tail, pos);
660 GNUNET_CONTAINER_DLL_insert (se->stat_head, se->stat_tail, pos);
662 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
663 "Statistic `%s:%s' updated to value %llu (%d).\n",
666 (unsigned long long) pos->value,
668 if ((changed) || (1 == initial_set))
670 GNUNET_SERVICE_client_continue (ce->client);
673 /* not found, create a new entry */
674 nlen = strlen (name) + 1;
675 pos = GNUNET_malloc (sizeof(struct StatsEntry) + nlen);
676 GNUNET_memcpy (&pos[1], name, nlen);
677 pos->name = (const char *) &pos[1];
679 if ((0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE)) ||
680 (0 < (int64_t) GNUNET_ntohll (msg->value)))
682 pos->value = GNUNET_ntohll (msg->value);
683 pos->set = GNUNET_YES;
687 pos->set = GNUNET_NO;
690 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
691 GNUNET_CONTAINER_DLL_insert (se->stat_head, se->stat_tail, pos);
692 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
693 "New statistic on `%s:%s' with value %llu created.\n",
696 (unsigned long long) pos->value);
698 GNUNET_SERVICE_client_continue (ce->client);
703 * Check integrity of WATCH-message.
705 * @param cls the `struct ClientEntry *`
706 * @param message the actual message
707 * @return #GNUNET_OK if message is well-formed
710 check_watch (void *cls, const struct GNUNET_MessageHeader *message)
716 size = ntohs (message->size) - sizeof(struct GNUNET_MessageHeader);
717 if (size != GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
724 return GNUNET_SYSERR;
731 * Handle WATCH-message.
733 * @param cls the `struct ClientEntry *`
734 * @param message the actual message
737 handle_watch (void *cls, const struct GNUNET_MessageHeader *message)
739 struct ClientEntry *ce = cls;
744 struct SubsystemEntry *se;
745 struct StatsEntry *pos;
746 struct WatchEntry *we;
751 GNUNET_SERVICE_client_drop (ce->client);
754 GNUNET_SERVICE_client_mark_monitor (ce->client);
755 msize = ntohs (message->size);
756 size = msize - sizeof(struct GNUNET_MessageHeader);
757 GNUNET_assert (size ==
758 GNUNET_STRINGS_buffer_tokenize ((const char *) &message[1],
763 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
764 "Received request to watch statistic on `%s:%s'\n",
767 se = find_subsystem_entry (ce, service);
768 pos = find_stat_entry (se, name);
771 nlen = strlen (name) + 1;
772 pos = GNUNET_malloc (sizeof(struct StatsEntry) + nlen);
773 GNUNET_memcpy (&pos[1], name, nlen);
774 pos->name = (const char *) &pos[1];
776 GNUNET_CONTAINER_DLL_insert (se->stat_head, se->stat_tail, pos);
778 pos->set = GNUNET_NO;
779 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
780 "New statistic on `%s:%s' with value %llu created.\n",
783 (unsigned long long) pos->value);
785 we = GNUNET_new (struct WatchEntry);
787 we->last_value_set = GNUNET_NO;
788 we->wid = ce->max_wid++;
789 GNUNET_CONTAINER_DLL_insert (pos->we_head, pos->we_tail, we);
792 GNUNET_SERVICE_client_continue (ce->client);
797 * Handle DISCONNECT-message. Sync to disk and send
798 * back a #GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM
801 * @param cls the `struct ClientEntry *`
802 * @param message the actual message
805 handle_disconnect (void *cls, const struct GNUNET_MessageHeader *message)
807 struct ClientEntry *ce = cls;
808 struct GNUNET_MQ_Envelope *env;
809 struct GNUNET_MessageHeader *msg;
811 env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM);
812 GNUNET_MQ_send (ce->mq, env);
813 GNUNET_SERVICE_client_continue (ce->client);
818 * Actually perform the shutdown.
823 struct WatchEntry *we;
824 struct StatsEntry *pos;
825 struct SubsystemEntry *se;
830 GNUNET_notification_context_destroy (nc);
832 GNUNET_assert (0 == client_count);
833 while (NULL != (se = sub_head))
835 GNUNET_CONTAINER_DLL_remove (sub_head, sub_tail, se);
836 while (NULL != (pos = se->stat_head))
838 GNUNET_CONTAINER_DLL_remove (se->stat_head, se->stat_tail, pos);
839 while (NULL != (we = pos->we_head))
842 GNUNET_CONTAINER_DLL_remove (pos->we_head, pos->we_tail, we);
853 * Task run during shutdown.
858 shutdown_task (void *cls)
860 in_shutdown = GNUNET_YES;
861 if (0 != client_count)
868 * A client disconnected. Remove all of its data structure entries.
870 * @param cls closure, NULL
871 * @param client identification of the client
872 * @param app_cls the `struct ClientEntry *`
875 client_disconnect_cb (void *cls,
876 struct GNUNET_SERVICE_Client *client,
879 struct ClientEntry *ce = app_cls;
880 struct WatchEntry *we;
881 struct WatchEntry *wen;
882 struct StatsEntry *pos;
883 struct SubsystemEntry *se;
886 for (se = sub_head; NULL != se; se = se->next)
888 for (pos = se->stat_head; NULL != pos; pos = pos->next)
891 while (NULL != (we = wen))
896 GNUNET_CONTAINER_DLL_remove (pos->we_head, pos->we_tail, we);
902 if ((0 == client_count) && (GNUNET_YES == in_shutdown))
908 * We've read a `struct GNUNET_STATISTICS_SetMessage *` from
909 * disk. Check that it is well-formed, and if so pass it to
910 * the handler for set messages.
913 * @param message the message found on disk
914 * @return #GNUNET_OK on success,
915 * #GNUNET_NO to stop further processing (no error)
916 * #GNUNET_SYSERR to stop further processing with error
919 inject_message (void *cls, const struct GNUNET_MessageHeader *message)
921 uint16_t msize = ntohs (message->size);
922 const struct GNUNET_STATISTICS_SetMessage *sm;
924 sm = (const struct GNUNET_STATISTICS_SetMessage *) message;
925 if ((sizeof(struct GNUNET_STATISTICS_SetMessage) > msize) ||
926 (GNUNET_OK != check_set (NULL, sm)))
929 return GNUNET_SYSERR;
931 handle_set (NULL, sm);
937 * Load persistent values from disk. Disk format is exactly the same
938 * format that we also use for setting the values over the network.
944 struct GNUNET_BIO_ReadHandle *rh;
947 struct GNUNET_MessageStreamTokenizer *mst;
949 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg,
954 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
960 GNUNET_DISK_file_size (fn, &fsize, GNUNET_NO, GNUNET_YES)) ||
966 buf = GNUNET_malloc (fsize);
967 rh = GNUNET_BIO_read_open (fn);
974 if (GNUNET_OK != GNUNET_BIO_read (rh, fn, buf, fsize))
976 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "read", fn);
977 GNUNET_break (GNUNET_OK == GNUNET_BIO_read_close (rh, NULL));
982 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
983 _ ("Loading %llu bytes of statistics from `%s'\n"),
984 (unsigned long long) fsize,
986 mst = GNUNET_MST_create (&inject_message, NULL);
989 GNUNET_MST_from_buffer (mst, buf, (size_t) fsize, GNUNET_YES, GNUNET_NO));
990 GNUNET_MST_destroy (mst);
992 GNUNET_break (GNUNET_OK == GNUNET_BIO_read_close (rh, NULL));
998 * Process statistics requests.
1000 * @param cls closure
1001 * @param c configuration to use
1002 * @param service the initialized service
1006 const struct GNUNET_CONFIGURATION_Handle *c,
1007 struct GNUNET_SERVICE_Handle *service)
1010 nc = GNUNET_notification_context_create (16);
1012 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
1017 * Define "main" method using service macro.
1019 GNUNET_SERVICE_MAIN (
1021 GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
1024 &client_disconnect_cb,
1026 GNUNET_MQ_hd_var_size (set,
1027 GNUNET_MESSAGE_TYPE_STATISTICS_SET,
1028 struct GNUNET_STATISTICS_SetMessage,
1030 GNUNET_MQ_hd_var_size (get,
1031 GNUNET_MESSAGE_TYPE_STATISTICS_GET,
1032 struct GNUNET_MessageHeader,
1034 GNUNET_MQ_hd_var_size (watch,
1035 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH,
1036 struct GNUNET_MessageHeader,
1038 GNUNET_MQ_hd_fixed_size (disconnect,
1039 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT,
1040 struct GNUNET_MessageHeader,
1042 GNUNET_MQ_handler_end ());
1045 #if defined(__linux__) && defined(__GLIBC__)
1049 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
1051 void __attribute__ ((constructor))
1052 GNUNET_STATISTICS_memory_init ()
1054 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
1055 mallopt (M_TOP_PAD, 1 * 1024);
1063 /* end of gnunet-service-statistics.c */