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"
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
76 * We keep the statistics organized by subsystem for faster
77 * lookup during SET operations.
79 struct SubsystemEntry;
83 * Entry in the statistics list.
87 * This is a linked list.
89 struct StatsEntry *next;
92 * This is a linked list.
94 struct StatsEntry *prev;
97 * Subsystem this entry belongs to.
99 struct SubsystemEntry *subsystem;
102 * Name for the value stored by this entry, allocated at the end of
108 * Watch context for changes to this value, or NULL for none.
110 struct WatchEntry *we_head;
113 * Watch context for changes to this value, or NULL for none.
115 struct WatchEntry *we_tail;
128 * Is this value persistent?
134 * #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 {
146 * Subsystems are kept in a DLL.
148 struct SubsystemEntry *next;
151 * Subsystems are kept in a DLL.
153 struct SubsystemEntry *prev;
156 * Head of list of values kept for this subsystem.
158 struct StatsEntry *stat_head;
161 * Tail of list of values kept for this subsystem.
163 struct StatsEntry *stat_tail;
166 * Name of the subsystem this entry is for, allocated at
167 * the end of this struct, do not free().
178 * Corresponding server handle.
180 struct GNUNET_SERVICE_Client *client;
183 * Corresponding message queue.
185 struct GNUNET_MQ_Handle *mq;
188 * Which subsystem is this client writing to (SET/UPDATE)?
190 struct SubsystemEntry *subsystem;
193 * Maximum watch ID used by this client so far.
202 static const struct GNUNET_CONFIGURATION_Handle *cfg;
205 * Head of linked list of subsystems with active statistics.
207 static struct SubsystemEntry *sub_head;
210 * Tail of linked list of subsystems with active statistics.
212 static struct SubsystemEntry *sub_tail;
215 * Number of connected clients.
217 static unsigned int client_count;
220 * Our notification context.
222 static struct GNUNET_NotificationContext *nc;
225 * Counter used to generate unique values.
227 static uint32_t uidgen;
230 * Set to #GNUNET_YES if we are shutting down as soon as possible.
232 static int in_shutdown;
236 * Write persistent statistics to disk.
241 struct SubsystemEntry *se;
242 struct StatsEntry *pos;
244 struct GNUNET_BIO_WriteHandle *wh;
246 unsigned long long total;
249 struct GNUNET_STATISTICS_SetMessage *msg;
251 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename(cfg,
256 GNUNET_log_config_missing(GNUNET_ERROR_TYPE_ERROR,
261 (void)GNUNET_DISK_directory_create_for_file(fn);
262 wh = GNUNET_BIO_write_open(fn);
264 while (NULL != (se = sub_head))
266 GNUNET_CONTAINER_DLL_remove(sub_head, sub_tail, se);
267 slen = strlen(se->service) + 1;
268 while (NULL != (pos = se->stat_head))
270 GNUNET_CONTAINER_DLL_remove(se->stat_head, se->stat_tail, pos);
271 if ((pos->persistent) && (NULL != wh))
273 nlen = strlen(pos->name) + 1;
274 size = sizeof(struct GNUNET_STATISTICS_SetMessage) + nlen + slen;
275 GNUNET_assert(size < UINT16_MAX);
276 msg = GNUNET_malloc(size);
278 msg->header.size = htons((uint16_t)size);
279 msg->header.type = htons(GNUNET_MESSAGE_TYPE_STATISTICS_SET);
280 GNUNET_assert(nlen + slen ==
281 GNUNET_STRINGS_buffer_fill((char *)&msg[1],
287 htonl(pos->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
288 msg->value = GNUNET_htonll(pos->value);
289 if (GNUNET_OK != GNUNET_BIO_write(wh, msg, size))
291 GNUNET_log_strerror_file(GNUNET_ERROR_TYPE_WARNING, "write", fn);
292 if (GNUNET_OK != GNUNET_BIO_write_close(wh))
293 GNUNET_log_strerror_file(GNUNET_ERROR_TYPE_WARNING, "close", fn);
308 if (GNUNET_OK != GNUNET_BIO_write_close(wh))
309 GNUNET_log_strerror_file(GNUNET_ERROR_TYPE_WARNING, "close", fn);
311 GNUNET_break(0 == unlink(fn));
313 GNUNET_log(GNUNET_ERROR_TYPE_INFO,
314 _("Wrote %llu bytes of statistics to `%s'\n"),
318 GNUNET_free_non_null(fn);
323 * Transmit the given stats value.
325 * @param client receiver of the value
326 * @param e value to transmit
329 transmit(struct ClientEntry *ce, const struct StatsEntry *e)
331 struct GNUNET_MQ_Envelope *env;
332 struct GNUNET_STATISTICS_ReplyMessage *m;
335 size = strlen(e->subsystem->service) + 1 + strlen(e->name) + 1;
336 GNUNET_assert(size < GNUNET_MAX_MESSAGE_SIZE);
337 env = GNUNET_MQ_msg_extra(m, size, GNUNET_MESSAGE_TYPE_STATISTICS_VALUE);
338 m->uid = htonl(e->uid);
340 m->uid |= htonl(GNUNET_STATISTICS_PERSIST_BIT);
341 m->value = GNUNET_htonll(e->value);
342 GNUNET_assert(size == GNUNET_STRINGS_buffer_fill((char *)&m[1],
345 e->subsystem->service,
347 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
348 "Transmitting value for `%s:%s' (%d): %llu\n",
349 e->subsystem->service,
352 (unsigned long long)e->value);
353 GNUNET_MQ_send(ce->mq, env);
358 * Callback called when a client connects to the service.
360 * @param cls closure for the service
361 * @param c the new client that connected to the service
362 * @param mq the message queue used to send messages to the client
366 client_connect_cb(void *cls,
367 struct GNUNET_SERVICE_Client *c,
368 struct GNUNET_MQ_Handle *mq)
370 struct ClientEntry *ce;
372 ce = GNUNET_new(struct ClientEntry);
376 GNUNET_notification_context_add(nc, mq);
382 * Check integrity of GET-message.
384 * @param cls identification of the client
385 * @param message the actual message
386 * @return #GNUNET_OK if @a message is well-formed
389 check_get(void *cls, const struct GNUNET_MessageHeader *message)
395 size = ntohs(message->size) - sizeof(struct GNUNET_MessageHeader);
396 if (size != GNUNET_STRINGS_buffer_tokenize((const char *)&message[1],
403 return GNUNET_SYSERR;
410 * Handle GET-message.
412 * @param cls identification of the client
413 * @param message the actual message
416 handle_get(void *cls, const struct GNUNET_MessageHeader *message)
418 struct ClientEntry *ce = cls;
419 struct GNUNET_MQ_Envelope *env;
420 struct GNUNET_MessageHeader *end;
425 struct SubsystemEntry *se;
426 struct StatsEntry *pos;
429 size = ntohs(message->size) - sizeof(struct GNUNET_MessageHeader);
430 GNUNET_assert(size ==
431 GNUNET_STRINGS_buffer_tokenize((const char *)&message[1],
436 slen = strlen(service);
438 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
439 "Received request for statistics on `%s:%s'\n",
440 slen ? service : "*",
442 for (se = sub_head; NULL != se; se = se->next)
444 if (!((0 == slen) || (0 == strcmp(service, se->service))))
446 for (pos = se->stat_head; NULL != pos; pos = pos->next)
448 if (!((0 == nlen) || (0 == strcmp(name, pos->name))))
453 env = GNUNET_MQ_msg(end, GNUNET_MESSAGE_TYPE_STATISTICS_END);
454 GNUNET_MQ_send(ce->mq, env);
455 GNUNET_SERVICE_client_continue(ce->client);
460 * Notify all clients listening about a change to a value.
462 * @param se value that changed
465 notify_change(struct StatsEntry *se)
467 struct GNUNET_MQ_Envelope *env;
468 struct GNUNET_STATISTICS_WatchValueMessage *wvm;
469 struct WatchEntry *pos;
471 for (pos = se->we_head; NULL != pos; pos = pos->next)
473 if (GNUNET_YES == pos->last_value_set)
475 if (pos->last_value == se->value)
480 pos->last_value_set = GNUNET_YES;
482 env = GNUNET_MQ_msg(wvm, GNUNET_MESSAGE_TYPE_STATISTICS_WATCH_VALUE);
484 htonl(se->persistent ? GNUNET_STATISTICS_SETFLAG_PERSISTENT : 0);
485 wvm->wid = htonl(pos->wid);
486 wvm->reserved = htonl(0);
487 wvm->value = GNUNET_htonll(se->value);
488 GNUNET_MQ_send(pos->ce->mq, env);
489 pos->last_value = se->value;
495 * Find the subsystem entry of the given name for the specified client.
497 * @param ce client looking for the subsystem, may contain a hint
498 * to find the entry faster, can be NULL
499 * @param service name of the subsystem to look for
500 * @return subsystem entry, never NULL (subsystem entry is created if necessary)
502 static struct SubsystemEntry *
503 find_subsystem_entry(struct ClientEntry *ce, const char *service)
506 struct SubsystemEntry *se;
512 if ((NULL == se) || (0 != strcmp(service, se->service)))
514 for (se = sub_head; NULL != se; se = se->next)
515 if (0 == strcmp(service, se->service))
522 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
523 "Allocating new subsystem entry `%s'\n",
525 slen = strlen(service) + 1;
526 se = GNUNET_malloc(sizeof(struct SubsystemEntry) + slen);
527 GNUNET_memcpy(&se[1], service, slen);
528 se->service = (const char *)&se[1];
529 GNUNET_CONTAINER_DLL_insert(sub_head, sub_tail, se);
537 * Find the statistics entry of the given subsystem.
539 * @param subsystem subsystem to look in
540 * @param name name of the entry to look for
541 * @return statistis entry, or NULL if not found
543 static struct StatsEntry *
544 find_stat_entry(struct SubsystemEntry *se, const char *name)
546 struct StatsEntry *pos;
548 for (pos = se->stat_head; NULL != pos; pos = pos->next)
549 if (0 == strcmp(name, pos->name))
556 * Check format of SET-message.
558 * @param cls the `struct ClientEntry`
559 * @param message the actual message
560 * @return #GNUNET_OK if message is well-formed
563 check_set(void *cls, const struct GNUNET_STATISTICS_SetMessage *msg)
569 msize = ntohs(msg->header.size) - sizeof(*msg);
570 if (msize != GNUNET_STRINGS_buffer_tokenize((const char *)&msg[1],
577 return GNUNET_SYSERR;
584 * Handle SET-message.
586 * @param cls the `struct ClientEntry`
587 * @param message the actual message
590 handle_set(void *cls, const struct GNUNET_STATISTICS_SetMessage *msg)
592 struct ClientEntry *ce = cls;
598 struct SubsystemEntry *se;
599 struct StatsEntry *pos;
606 msize = ntohs(msg->header.size);
607 size = msize - sizeof(struct GNUNET_STATISTICS_SetMessage);
608 GNUNET_assert(size == GNUNET_STRINGS_buffer_tokenize((const char *)&msg[1],
613 se = find_subsystem_entry(ce, service);
614 flags = ntohl(msg->flags);
615 value = GNUNET_ntohll(msg->value);
616 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
617 "Received request to update statistic on `%s:%s' (%u) to/by %llu\n",
621 (unsigned long long)value);
622 pos = find_stat_entry(se, name);
626 if (0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE))
628 changed = (pos->value != value);
633 delta = (int64_t)value;
634 if ((delta < 0) && (pos->value < -delta))
636 changed = (0 != pos->value);
641 changed = (0 != delta);
642 GNUNET_break((delta <= 0) || (pos->value + delta > pos->value));
646 if (GNUNET_NO == pos->set)
648 pos->set = GNUNET_YES;
651 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
652 if (pos != se->stat_head)
654 /* move to front for faster setting next time! */
655 GNUNET_CONTAINER_DLL_remove(se->stat_head, se->stat_tail, pos);
656 GNUNET_CONTAINER_DLL_insert(se->stat_head, se->stat_tail, pos);
658 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
659 "Statistic `%s:%s' updated to value %llu (%d).\n",
662 (unsigned long long)pos->value,
664 if ((changed) || (1 == initial_set))
666 GNUNET_SERVICE_client_continue(ce->client);
669 /* not found, create a new entry */
670 nlen = strlen(name) + 1;
671 pos = GNUNET_malloc(sizeof(struct StatsEntry) + nlen);
672 GNUNET_memcpy(&pos[1], name, nlen);
673 pos->name = (const char *)&pos[1];
675 if ((0 == (flags & GNUNET_STATISTICS_SETFLAG_RELATIVE)) ||
676 (0 < (int64_t)GNUNET_ntohll(msg->value)))
678 pos->value = GNUNET_ntohll(msg->value);
679 pos->set = GNUNET_YES;
683 pos->set = GNUNET_NO;
686 pos->persistent = (0 != (flags & GNUNET_STATISTICS_SETFLAG_PERSISTENT));
687 GNUNET_CONTAINER_DLL_insert(se->stat_head, se->stat_tail, pos);
688 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
689 "New statistic on `%s:%s' with value %llu created.\n",
692 (unsigned long long)pos->value);
694 GNUNET_SERVICE_client_continue(ce->client);
699 * Check integrity of WATCH-message.
701 * @param cls the `struct ClientEntry *`
702 * @param message the actual message
703 * @return #GNUNET_OK if message is well-formed
706 check_watch(void *cls, const struct GNUNET_MessageHeader *message)
712 size = ntohs(message->size) - sizeof(struct GNUNET_MessageHeader);
713 if (size != GNUNET_STRINGS_buffer_tokenize((const char *)&message[1],
720 return GNUNET_SYSERR;
727 * Handle WATCH-message.
729 * @param cls the `struct ClientEntry *`
730 * @param message the actual message
733 handle_watch(void *cls, const struct GNUNET_MessageHeader *message)
735 struct ClientEntry *ce = cls;
740 struct SubsystemEntry *se;
741 struct StatsEntry *pos;
742 struct WatchEntry *we;
747 GNUNET_SERVICE_client_drop(ce->client);
750 GNUNET_SERVICE_client_mark_monitor(ce->client);
751 msize = ntohs(message->size);
752 size = msize - sizeof(struct GNUNET_MessageHeader);
753 GNUNET_assert(size ==
754 GNUNET_STRINGS_buffer_tokenize((const char *)&message[1],
759 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
760 "Received request to watch statistic on `%s:%s'\n",
763 se = find_subsystem_entry(ce, service);
764 pos = find_stat_entry(se, name);
767 nlen = strlen(name) + 1;
768 pos = GNUNET_malloc(sizeof(struct StatsEntry) + nlen);
769 GNUNET_memcpy(&pos[1], name, nlen);
770 pos->name = (const char *)&pos[1];
772 GNUNET_CONTAINER_DLL_insert(se->stat_head, se->stat_tail, pos);
774 pos->set = GNUNET_NO;
775 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
776 "New statistic on `%s:%s' with value %llu created.\n",
779 (unsigned long long)pos->value);
781 we = GNUNET_new(struct WatchEntry);
783 we->last_value_set = GNUNET_NO;
784 we->wid = ce->max_wid++;
785 GNUNET_CONTAINER_DLL_insert(pos->we_head, pos->we_tail, we);
788 GNUNET_SERVICE_client_continue(ce->client);
793 * Handle DISCONNECT-message. Sync to disk and send
794 * back a #GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM
797 * @param cls the `struct ClientEntry *`
798 * @param message the actual message
801 handle_disconnect(void *cls, const struct GNUNET_MessageHeader *message)
803 struct ClientEntry *ce = cls;
804 struct GNUNET_MQ_Envelope *env;
805 struct GNUNET_MessageHeader *msg;
807 env = GNUNET_MQ_msg(msg, GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT_CONFIRM);
808 GNUNET_MQ_send(ce->mq, env);
809 GNUNET_SERVICE_client_continue(ce->client);
814 * Actually perform the shutdown.
819 struct WatchEntry *we;
820 struct StatsEntry *pos;
821 struct SubsystemEntry *se;
826 GNUNET_notification_context_destroy(nc);
828 GNUNET_assert(0 == client_count);
829 while (NULL != (se = sub_head))
831 GNUNET_CONTAINER_DLL_remove(sub_head, sub_tail, se);
832 while (NULL != (pos = se->stat_head))
834 GNUNET_CONTAINER_DLL_remove(se->stat_head, se->stat_tail, pos);
835 while (NULL != (we = pos->we_head))
838 GNUNET_CONTAINER_DLL_remove(pos->we_head, pos->we_tail, we);
849 * Task run during shutdown.
854 shutdown_task(void *cls)
856 in_shutdown = GNUNET_YES;
857 if (0 != client_count)
864 * A client disconnected. Remove all of its data structure entries.
866 * @param cls closure, NULL
867 * @param client identification of the client
868 * @param app_cls the `struct ClientEntry *`
871 client_disconnect_cb(void *cls,
872 struct GNUNET_SERVICE_Client *client,
875 struct ClientEntry *ce = app_cls;
876 struct WatchEntry *we;
877 struct WatchEntry *wen;
878 struct StatsEntry *pos;
879 struct SubsystemEntry *se;
882 for (se = sub_head; NULL != se; se = se->next)
884 for (pos = se->stat_head; NULL != pos; pos = pos->next)
887 while (NULL != (we = wen))
892 GNUNET_CONTAINER_DLL_remove(pos->we_head, pos->we_tail, we);
898 if ((0 == client_count) && (GNUNET_YES == in_shutdown))
904 * We've read a `struct GNUNET_STATISTICS_SetMessage *` from
905 * disk. Check that it is well-formed, and if so pass it to
906 * the handler for set messages.
909 * @param message the message found on disk
910 * @return #GNUNET_OK on success,
911 * #GNUNET_NO to stop further processing (no error)
912 * #GNUNET_SYSERR to stop further processing with error
915 inject_message(void *cls, const struct GNUNET_MessageHeader *message)
917 uint16_t msize = ntohs(message->size);
918 const struct GNUNET_STATISTICS_SetMessage *sm;
920 sm = (const struct GNUNET_STATISTICS_SetMessage *)message;
921 if ((sizeof(struct GNUNET_STATISTICS_SetMessage) > msize) ||
922 (GNUNET_OK != check_set(NULL, sm)))
925 return GNUNET_SYSERR;
927 handle_set(NULL, sm);
933 * Load persistent values from disk. Disk format is exactly the same
934 * format that we also use for setting the values over the network.
940 struct GNUNET_BIO_ReadHandle *rh;
943 struct GNUNET_MessageStreamTokenizer *mst;
945 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename(cfg,
950 GNUNET_log_config_missing(GNUNET_ERROR_TYPE_ERROR,
956 GNUNET_DISK_file_size(fn, &fsize, GNUNET_NO, GNUNET_YES)) ||
962 buf = GNUNET_malloc(fsize);
963 rh = GNUNET_BIO_read_open(fn);
970 if (GNUNET_OK != GNUNET_BIO_read(rh, fn, buf, fsize))
972 GNUNET_log_strerror_file(GNUNET_ERROR_TYPE_WARNING, "read", fn);
973 GNUNET_break(GNUNET_OK == GNUNET_BIO_read_close(rh, NULL));
978 GNUNET_log(GNUNET_ERROR_TYPE_INFO,
979 _("Loading %llu bytes of statistics from `%s'\n"),
980 (unsigned long long)fsize,
982 mst = GNUNET_MST_create(&inject_message, NULL);
985 GNUNET_MST_from_buffer(mst, buf, (size_t)fsize, GNUNET_YES, GNUNET_NO));
986 GNUNET_MST_destroy(mst);
988 GNUNET_break(GNUNET_OK == GNUNET_BIO_read_close(rh, NULL));
994 * Process statistics requests.
997 * @param c configuration to use
998 * @param service the initialized service
1002 const struct GNUNET_CONFIGURATION_Handle *c,
1003 struct GNUNET_SERVICE_Handle *service)
1006 nc = GNUNET_notification_context_create(16);
1008 GNUNET_SCHEDULER_add_shutdown(&shutdown_task, NULL);
1013 * Define "main" method using service macro.
1015 GNUNET_SERVICE_MAIN(
1017 GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
1020 &client_disconnect_cb,
1022 GNUNET_MQ_hd_var_size(set,
1023 GNUNET_MESSAGE_TYPE_STATISTICS_SET,
1024 struct GNUNET_STATISTICS_SetMessage,
1026 GNUNET_MQ_hd_var_size(get,
1027 GNUNET_MESSAGE_TYPE_STATISTICS_GET,
1028 struct GNUNET_MessageHeader,
1030 GNUNET_MQ_hd_var_size(watch,
1031 GNUNET_MESSAGE_TYPE_STATISTICS_WATCH,
1032 struct GNUNET_MessageHeader,
1034 GNUNET_MQ_hd_fixed_size(disconnect,
1035 GNUNET_MESSAGE_TYPE_STATISTICS_DISCONNECT,
1036 struct GNUNET_MessageHeader,
1038 GNUNET_MQ_handler_end());
1041 #if defined(LINUX) && defined(__GLIBC__)
1045 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
1047 void __attribute__ ((constructor)) GNUNET_STATISTICS_memory_init()
1049 mallopt(M_TRIM_THRESHOLD, 4 * 1024);
1050 mallopt(M_TOP_PAD, 1 * 1024);
1056 /* end of gnunet-service-statistics.c */