2 This file is part of GNUnet.
3 Copyright (C) 2001, 2002, 2004-2007, 2009, 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-statistics.c
23 * @brief tool to obtain statistics
24 * @author Christian Grothoff
25 * @author Igor Wronsky
28 #include "gnunet_util_lib.h"
29 #include "gnunet_statistics_service.h"
30 #include "statistics.h"
39 * Set to subsystem that we're going to get stats for (or NULL for all).
41 static char *subsystem;
44 * The path of the testbed data.
46 static char *path_testbed;
49 * Set to the specific stat value that we are after (or NULL for all).
54 * Make the value that is being set persistent.
56 static int persistent;
59 * Watch value continuously
69 * @brief Separator string for csv.
71 static char *csv_separator;
76 static char *remote_host;
81 static unsigned long long remote_port;
86 static unsigned long long set_val;
94 * @brief Representation of all (testbed) nodes.
99 * @brief Index of the node in this array.
104 * @brief Configuration handle for this node
106 struct GNUNET_CONFIGURATION_Handle *conf;
109 * Handle for pending GET operation.
111 struct GNUNET_STATISTICS_GetHandle *gh;
114 * @brief Statistics handle nodes.
116 struct GNUNET_STATISTICS_Handle *handle;
118 * @brief Identifier for shutdown task for this node.
120 struct GNUNET_SCHEDULER_Task *shutdown_task;
124 * @brief Number of configurations of all (testbed) nodes.
126 static unsigned num_nodes;
129 * @brief Set of values for a combination of subsystem and name.
134 * @brief Subsystem of the valueset.
139 * @brief Name of the valueset.
149 * @brief Persistence of the values.
155 * @brief Collection of all values (represented with #ValueSet).
157 static struct GNUNET_CONTAINER_MultiHashMap *values;
160 * @brief Number of nodes that have their values ready.
162 static int num_nodes_ready;
165 * @brief Number of nodes that have their values ready.
167 static int num_nodes_ready_shutdown;
170 * @brief Create a new #ValueSet
172 * @param subsystem Subsystem of the valueset.
173 * @param name Name of the valueset.
174 * @param num_values Number of values in valueset - number of peers.
175 * @param is_persistent Persistence status of values.
177 * @return Newly allocated #ValueSet.
179 static struct ValueSet *
180 new_value_set (const char *subsystem,
185 struct ValueSet *value_set;
187 value_set = GNUNET_new (struct ValueSet);
188 value_set->subsystem = GNUNET_strdup (subsystem);
189 value_set->name = GNUNET_strdup (name);
190 value_set->values = GNUNET_new_array (num_values, uint64_t);
191 value_set->is_persistent = persistent;
197 * @brief Print the (collected) values.
199 * Implements #GNUNET_CONTAINER_HashMapIterator.
201 * @param cls Closure - unused
202 * @param key #GNUNET_HashCode key of #GNUNET_CONTAINER_MultiHashMap iterator -
204 * @param value Values represented as #ValueSet.
206 * @return GNUNET_YES - continue iteration.
209 printer (void *cls, const struct GNUNET_HashCode *key, void *value)
211 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
213 struct ValueSet *value_set = value;
215 if (quiet == GNUNET_NO)
217 if (GNUNET_YES == watch)
219 now_str = GNUNET_STRINGS_absolute_time_to_string (now);
221 "%24s%s %s%s%12s%s %s%50s%s%s ",
224 value_set->is_persistent ? "!" : " ",
226 value_set->subsystem,
228 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
230 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
231 (0 == strlen (csv_separator) ? ":" : csv_separator));
236 "%s%s%12s%s %s%50s%s%s ",
237 value_set->is_persistent ? "!" : " ",
239 value_set->subsystem,
241 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
243 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
244 (0 == strlen (csv_separator) ? ":" : csv_separator));
247 for (unsigned i = 0; i < num_nodes; i++)
251 (unsigned long long) value_set->values[i],
254 fprintf (stdout, "\n");
255 GNUNET_free (value_set->subsystem);
256 GNUNET_free (value_set->name);
257 GNUNET_free (value_set->values);
258 GNUNET_free (value_set);
264 * Callback function to process statistic values.
267 * @param subsystem name of subsystem that created the statistic
268 * @param name the name of the datum
269 * @param value the current value
270 * @param is_persistent #GNUNET_YES if the value is persistent, #GNUNET_NO if not
271 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
274 printer_watch (void *cls,
275 const char *subsystem,
280 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
283 if (quiet == GNUNET_NO)
285 if (GNUNET_YES == watch)
287 now_str = GNUNET_STRINGS_absolute_time_to_string (now);
289 "%24s%s %s%s%12s%s %s%50s%s%s %16llu\n",
292 is_persistent ? "!" : " ",
296 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
298 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
299 (0 == strlen (csv_separator) ? ":" : csv_separator),
300 (unsigned long long) value);
305 "%s%s%12s%s %s%50s%s%s %16llu\n",
306 is_persistent ? "!" : " ",
310 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
312 (0 == strlen (csv_separator) ? "" : "\""), /* quotes if csv */
313 (0 == strlen (csv_separator) ? ":" : csv_separator),
314 (unsigned long long) value);
318 fprintf (stdout, "%llu\n", (unsigned long long) value);
325 * @brief Clean all data structures related to given node.
327 * Also clears global structures if we are the last node to clean.
329 * @param cls the index of the node
332 clean_node (void *cls)
334 const unsigned index_node = *(unsigned *) cls;
335 struct GNUNET_STATISTICS_Handle *h;
336 struct GNUNET_STATISTICS_GetHandle *gh;
338 if ((NULL != path_testbed) && /* were issued with -t <testbed-path> option */
339 (NULL != nodes[index_node].conf))
341 GNUNET_CONFIGURATION_destroy (nodes[index_node].conf);
342 nodes[index_node].conf = NULL;
345 h = nodes[index_node].handle;
346 gh = nodes[index_node].gh;
350 GNUNET_STATISTICS_get_cancel (gh);
353 if (GNUNET_YES == watch)
357 GNUNET_STATISTICS_watch_cancel (h,
361 &nodes[index_node].index_node));
366 GNUNET_STATISTICS_destroy (h, GNUNET_NO);
370 num_nodes_ready_shutdown++;
371 if (num_nodes == num_nodes_ready_shutdown)
373 GNUNET_array_grow (nodes, num_nodes, 0);
374 GNUNET_CONTAINER_multihashmap_destroy (values);
380 * @brief Print and shutdown
385 print_finish (void *cls)
387 GNUNET_CONTAINER_multihashmap_iterate (values, printer, NULL);
388 GNUNET_SCHEDULER_shutdown ();
393 * @brief Called once all statistic values are available.
395 * Implements #GNUNET_STATISTICS_Callback
397 * @param cls Closure - The index of the node.
398 * @param succes Whether statistics were obtained successfully.
401 continuation_print (void *cls, int success)
403 const unsigned index_node = *(unsigned *) cls;
405 nodes[index_node].gh = NULL;
406 if (GNUNET_OK != success)
408 if (NULL == remote_host)
409 fprintf (stderr, "%s", _ ("Failed to obtain statistics.\n"));
412 _ ("Failed to obtain statistics from host `%s:%llu'\n"),
417 if (NULL != nodes[index_node].shutdown_task)
419 GNUNET_SCHEDULER_cancel (nodes[index_node].shutdown_task);
420 nodes[index_node].shutdown_task = NULL;
422 GNUNET_SCHEDULER_add_now (clean_node, &nodes[index_node].index_node);
424 if (num_nodes_ready == num_nodes)
426 GNUNET_SCHEDULER_add_now (print_finish, NULL);
432 * Function called last by the statistics code.
435 * @param success #GNUNET_OK if statistics were
436 * successfully obtained, #GNUNET_SYSERR if not.
439 cleanup (void *cls, int success)
441 for (unsigned i = 0; i < num_nodes; i++)
445 if (GNUNET_OK != success)
447 if (NULL == remote_host)
448 fprintf (stderr, "%s", _ ("Failed to obtain statistics.\n"));
451 _ ("Failed to obtain statistics from host `%s:%llu'\n"),
456 GNUNET_SCHEDULER_shutdown ();
461 * @brief Iterate over statistics values and store them in #values.
462 * They will be printed once all are available.
464 * @param cls Cosure - Node index.
465 * @param subsystem Subsystem of the value.
466 * @param name Name of the value.
467 * @param value Value itself.
468 * @param is_persistent Persistence.
470 * @return GNUNET_OK - continue.
473 collector (void *cls,
474 const char *subsystem,
479 const unsigned index_node = *(unsigned *) cls;
480 struct GNUNET_HashCode *key;
481 struct GNUNET_HashCode hc;
483 unsigned len_subsys_name;
484 struct ValueSet *value_set;
486 len_subsys_name = strlen (subsystem) + 3 + strlen (name) + 1;
487 subsys_name = GNUNET_malloc (len_subsys_name);
488 sprintf (subsys_name, "%s---%s", subsystem, name);
490 GNUNET_CRYPTO_hash (subsys_name, len_subsys_name, key);
491 GNUNET_free (subsys_name);
492 if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (values, key))
494 value_set = GNUNET_CONTAINER_multihashmap_get (values, key);
498 value_set = new_value_set (subsystem, name, num_nodes, is_persistent);
500 value_set->values[index_node] = value;
501 GNUNET_assert (GNUNET_YES ==
502 GNUNET_CONTAINER_multihashmap_put (
506 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
512 * Main task that does the actual work.
514 * @param cls closure with our configuration
517 main_task (void *cls)
519 unsigned index_node = *(unsigned *) cls;
520 const struct GNUNET_CONFIGURATION_Handle *cfg = nodes[index_node].conf;
524 if (NULL == subsystem)
526 fprintf (stderr, "%s", _ ("Missing argument: subsystem \n"));
532 fprintf (stderr, "%s", _ ("Missing argument: name\n"));
536 nodes[index_node].handle = GNUNET_STATISTICS_create (subsystem, cfg);
537 if (NULL == nodes[index_node].handle)
542 GNUNET_STATISTICS_set (nodes[index_node].handle,
546 GNUNET_STATISTICS_destroy (nodes[index_node].handle, GNUNET_YES);
547 nodes[index_node].handle = NULL;
550 if (NULL == (nodes[index_node].handle =
551 GNUNET_STATISTICS_create ("gnunet-statistics", cfg)))
556 if (GNUNET_NO == watch)
558 if (NULL == (nodes[index_node].gh =
559 GNUNET_STATISTICS_get (nodes[index_node].handle,
564 &nodes[index_node].index_node)))
565 cleanup (nodes[index_node].handle, GNUNET_SYSERR);
569 if ((NULL == subsystem) || (NULL == name))
571 printf (_ ("No subsystem or name given\n"));
572 GNUNET_STATISTICS_destroy (nodes[index_node].handle, GNUNET_NO);
573 nodes[index_node].handle = NULL;
577 if (GNUNET_OK != GNUNET_STATISTICS_watch (nodes[index_node].handle,
581 &nodes[index_node].index_node))
583 fprintf (stderr, _ ("Failed to initialize watch routine\n"));
584 nodes[index_node].shutdown_task =
585 GNUNET_SCHEDULER_add_now (&clean_node, &nodes[index_node].index_node);
589 nodes[index_node].shutdown_task =
590 GNUNET_SCHEDULER_add_shutdown (&clean_node, &nodes[index_node].index_node);
595 * @brief Iter over content of a node's directory to check for existence of a
598 * Implements #GNUNET_FileNameCallback
600 * @param cls pointer to indicate success
601 * @param filename filename inside the directory of the potential node
603 * @return to continue iteration or not to
606 iter_check_config (void *cls, const char *filename)
608 if (0 == strncmp (GNUNET_STRINGS_get_short_name (filename), "config", 6))
610 /* Found the config - stop iteration successfully */
611 GNUNET_array_grow (nodes, num_nodes, num_nodes + 1);
612 nodes[num_nodes - 1].conf = GNUNET_CONFIGURATION_create ();
613 nodes[num_nodes - 1].index_node = num_nodes - 1;
615 GNUNET_CONFIGURATION_load (nodes[num_nodes - 1].conf, filename))
617 fprintf (stderr, "Failed loading config `%s'\n", filename);
618 return GNUNET_SYSERR;
624 /* Continue iteration */
631 * @brief Iterates over filenames in testbed directory.
633 * Implements #GNUNET_FileNameCallback
635 * Checks if the file is a directory for a testbed node
636 * and counts the nodes.
638 * @param cls counter of nodes
639 * @param filename full path of the file in testbed
641 * @return status whether to continue iteration
644 iter_testbed_path (void *cls, const char *filename)
648 GNUNET_assert (NULL != filename);
649 if (1 == sscanf (GNUNET_STRINGS_get_short_name (filename), "%u", &index_node))
651 if (-1 == GNUNET_DISK_directory_scan (filename, iter_check_config, NULL))
653 /* This is probably no directory for a testbed node
654 * Go on with iteration */
664 * @brief Count the number of nodes running in the testbed
666 * @param path_testbed path to the testbed data
668 * @return number of running nodes
671 discover_testbed_nodes (const char *path_testbed)
676 GNUNET_DISK_directory_scan (path_testbed, iter_testbed_path, NULL);
677 if (-1 == num_dir_entries)
679 fprintf (stderr, "Failure during scanning directory `%s'\n", path_testbed);
687 * Main function that will be run by the scheduler.
690 * @param args remaining command-line arguments
691 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
692 * @param cfg configuration
698 const struct GNUNET_CONFIGURATION_Handle *cfg)
700 struct GNUNET_CONFIGURATION_Handle *c;
702 c = (struct GNUNET_CONFIGURATION_Handle *) cfg;
703 set_value = GNUNET_NO;
704 if (NULL == csv_separator)
708 if (1 != sscanf (args[0], "%llu", &set_val))
710 fprintf (stderr, _ ("Invalid argument `%s'\n"), args[0]);
714 set_value = GNUNET_YES;
716 if (NULL != remote_host)
718 if (0 == remote_port)
720 if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_number (cfg,
726 _ ("A port is required to connect to host `%s'\n"),
731 else if (65535 <= remote_port)
735 "A port has to be between 1 and 65535 to connect to host `%s'\n"),
740 /* Manipulate configuration */
741 GNUNET_CONFIGURATION_set_value_string (c, "statistics", "UNIXPATH", "");
742 GNUNET_CONFIGURATION_set_value_string (c,
746 GNUNET_CONFIGURATION_set_value_number (c,
751 if (NULL == path_testbed)
753 values = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
754 GNUNET_array_grow (nodes, num_nodes, 1);
755 nodes[0].index_node = 0;
757 GNUNET_SCHEDULER_add_now (&main_task, &nodes[0].index_node);
761 if (GNUNET_YES == watch)
764 _ ("Not able to watch testbed nodes (yet - feel free to implement)\n"));
768 values = GNUNET_CONTAINER_multihashmap_create (4, GNUNET_NO);
769 if (-1 == discover_testbed_nodes (path_testbed))
773 /* For each config/node collect statistics */
774 for (unsigned i = 0; i < num_nodes; i++)
776 GNUNET_SCHEDULER_add_now (&main_task, &nodes[i].index_node);
783 * The main function to obtain statistics in GNUnet.
785 * @param argc number of arguments from the command line
786 * @param argv command line arguments
787 * @return 0 ok, 1 on error
790 main (int argc, char *const *argv)
792 struct GNUNET_GETOPT_CommandLineOption options[] =
793 { GNUNET_GETOPT_option_string (
797 gettext_noop ("limit output to statistics for the given NAME"),
800 GNUNET_GETOPT_option_flag ('p',
803 "make the value being set persistent"),
806 GNUNET_GETOPT_option_string ('s',
810 "limit output to the given SUBSYSTEM"),
813 GNUNET_GETOPT_option_string ('S',
816 gettext_noop ("use as csv separator"),
819 GNUNET_GETOPT_option_filename (
823 gettext_noop ("path to the folder containing the testbed data"),
826 GNUNET_GETOPT_option_flag ('q',
829 "just print the statistics value"),
832 GNUNET_GETOPT_option_flag ('w',
834 gettext_noop ("watch value continuously"),
837 GNUNET_GETOPT_option_string ('r',
840 gettext_noop ("connect to remote host"),
843 GNUNET_GETOPT_option_ulong ('o',
846 gettext_noop ("port for remote host"),
849 GNUNET_GETOPT_OPTION_END };
853 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
857 GNUNET_PROGRAM_run (argc,
859 "gnunet-statistics [options [value]]",
861 "Print statistics about GNUnet operations."),
867 GNUNET_free_non_null (remote_host);
868 GNUNET_free ((void *) argv);
873 /* end of gnunet-statistics.c */