Merge branch 'master' of gnunet.org:gnunet
[oweals/gnunet.git] / src / statistics / gnunet-statistics.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2001, 2002, 2004-2007, 2009, 2016 GNUnet e.V.
4
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.
9
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.
14     
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/>.
17 */
18
19 /**
20  * @file statistics/gnunet-statistics.c
21  * @brief tool to obtain statistics
22  * @author Christian Grothoff
23  * @author Igor Wronsky
24  */
25 #include "platform.h"
26 #include "gnunet_util_lib.h"
27 #include "gnunet_statistics_service.h"
28 #include "statistics.h"
29
30
31 /**
32  * Final status code.
33  */
34 static int ret;
35
36 /**
37  * Set to subsystem that we're going to get stats for (or NULL for all).
38  */
39 static char *subsystem;
40
41 /**
42  * The path of the testbed data.
43  */
44 static char *path_testbed;
45
46 /**
47  * Set to the specific stat value that we are after (or NULL for all).
48  */
49 static char *name;
50
51 /**
52  * Make the value that is being set persistent.
53  */
54 static int persistent;
55
56 /**
57  * Watch value continuously
58  */
59 static int watch;
60
61 /**
62  * Quiet mode
63  */
64 static int quiet;
65
66 /**
67  * @brief Separator string for csv.
68  */
69 static char *csv_separator;
70
71 /**
72  * Remote host
73  */
74 static char *remote_host;
75
76 /**
77  * Remote host's port
78  */
79 static unsigned long long remote_port;
80
81 /**
82  * Value to set
83  */
84 static unsigned long long set_val;
85
86 /**
87  * Set operation
88  */
89 static int set_value;
90
91 /**
92  * @brief Representation of all (testbed) nodes.
93  */
94 static struct Node {
95   /**
96    * @brief Index of the node in this array.
97    */
98   unsigned index_node;
99
100   /**
101    * @brief Configuration handle for this node
102    */
103   struct GNUNET_CONFIGURATION_Handle *conf;
104
105   /**
106    * Handle for pending GET operation.
107    */
108   struct GNUNET_STATISTICS_GetHandle *gh;
109
110   /**
111    * @brief Statistics handle nodes.
112    */
113   struct GNUNET_STATISTICS_Handle *handle;
114   /**
115    * @brief Identifier for shutdown task for this node.
116    */
117   struct GNUNET_SCHEDULER_Task *shutdown_task;
118 } *nodes;
119
120 /**
121  * @brief Number of configurations of all (testbed) nodes.
122  */
123 static unsigned num_nodes;
124
125 /**
126  * @brief Set of values for a combination of subsystem and name.
127  */
128 struct ValueSet
129 {
130   /**
131    * @brief Subsystem of the valueset.
132    */
133   char *subsystem;
134
135   /**
136    * @brief Name of the valueset.
137    */
138   char *name;
139
140   /**
141    * @brief The values.
142    */
143   uint64_t *values;
144
145   /**
146    * @brief Persistence of the values.
147    */
148   int is_persistent;
149 };
150
151 /**
152  * @brief Collection of all values (represented with #ValueSet).
153  */
154 static struct GNUNET_CONTAINER_MultiHashMap *values;
155
156 /**
157  * @brief Number of nodes that have their values ready.
158  */
159 static int num_nodes_ready;
160
161 /**
162  * @brief Create a new #ValueSet
163  *
164  * @param subsystem Subsystem of the valueset.
165  * @param name Name of the valueset.
166  * @param num_values Number of values in valueset - number of peers.
167  * @param is_persistent Persistence status of values.
168  *
169  * @return Newly allocated #ValueSet.
170  */
171 static struct ValueSet *
172 new_value_set (const char *subsystem,
173                const char *name,
174                unsigned num_values,
175                int is_persistent)
176 {
177   struct ValueSet *value_set;
178
179   value_set = GNUNET_new (struct ValueSet);
180   value_set->subsystem = GNUNET_strdup (subsystem);
181   value_set->name = GNUNET_strdup (name);
182   value_set->values = GNUNET_new_array (num_values, uint64_t);
183   value_set->is_persistent = persistent;
184   return value_set;
185 }
186
187 /**
188  * @brief Print the (collected) values.
189  *
190  * Implements #GNUNET_CONTAINER_HashMapIterator.
191  *
192  * @param cls Closure - unused
193  * @param key #GNUNET_HashCode key of #GNUNET_CONTAINER_MultiHashMap iterator -
194  *        unused
195  * @param value Values represented as #ValueSet.
196  *
197  * @return GNUNET_YES - continue iteration.
198  */
199 static int
200 printer (void *cls,
201          const struct GNUNET_HashCode *key,
202          void *value)
203 {
204   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get();
205   const char *now_str;
206   struct ValueSet *value_set = value;
207
208   if (quiet == GNUNET_NO)
209   {
210     if (GNUNET_YES == watch)
211     {
212       now_str = GNUNET_STRINGS_absolute_time_to_string (now);
213       FPRINTF (stdout,
214                "%24s%s %s%s%12s%s %50s%s ",
215                now_str,
216                csv_separator,
217                value_set->is_persistent ? "!" : " ",
218                csv_separator,
219                value_set->subsystem,
220                csv_separator,
221                      _(value_set->name),
222                (0 == strlen (csv_separator) ? ":": csv_separator));
223     }
224     else
225     {
226       FPRINTF (stdout,
227                "%s%s%12s%s %50s%s ",
228                value_set->is_persistent ? "!" : " ",
229                csv_separator,
230                value_set->subsystem,
231                csv_separator,
232                _(value_set->name),
233                (0 == strlen (csv_separator) ? ":": csv_separator));
234     }
235   }
236   for (unsigned i = 0; i < num_nodes; i++)
237   {
238     FPRINTF (stdout,
239             "%16llu%s",
240             (unsigned long long) value_set->values[i],
241             csv_separator);
242   }
243   FPRINTF (stdout, "\n");
244   GNUNET_free (value_set->subsystem);
245   GNUNET_free (value_set->name);
246   GNUNET_free (value_set->values);
247   GNUNET_free (value_set);
248   return GNUNET_YES;
249 }
250
251 /**
252  * @brief Called once all statistic values are available.
253  *
254  * Implements #GNUNET_STATISTICS_Callback
255  *
256  * @param cls Closure - The index of the node.
257  * @param succes Whether statistics were obtained successfully.
258  */
259 static void
260 continuation_print (void *cls,
261                     int success)
262 {
263   const unsigned index_node = *(unsigned *) cls;
264
265   nodes[index_node].gh = NULL;
266   if (GNUNET_OK != success)
267   {
268     if (NULL == remote_host)
269       FPRINTF (stderr,
270                "%s",
271                _("Failed to obtain statistics.\n"));
272     else
273       FPRINTF (stderr,
274                _("Failed to obtain statistics from host `%s:%llu'\n"),
275                remote_host,
276                remote_port);
277     ret = 1;
278   }
279   num_nodes_ready++;
280   if (num_nodes_ready == num_nodes)
281   {
282     GNUNET_CONTAINER_multihashmap_iterate (values, printer, NULL);
283     GNUNET_SCHEDULER_shutdown();
284   }
285 }
286
287 /**
288  * Callback function to process statistic values.
289  *
290  * @param cls closure
291  * @param subsystem name of subsystem that created the statistic
292  * @param name the name of the datum
293  * @param value the current value
294  * @param is_persistent #GNUNET_YES if the value is persistent, #GNUNET_NO if not
295  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
296  */
297 static int
298 printer_watch (void *cls,
299                const char *subsystem,
300                const char *name,
301                uint64_t value,
302                int is_persistent)
303 {
304   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get();
305   const char *now_str;
306
307   if (quiet == GNUNET_NO)
308   {
309     if (GNUNET_YES == watch)
310     {
311       now_str = GNUNET_STRINGS_absolute_time_to_string (now);
312       FPRINTF (stdout,
313                "%24s%s %s%s%12s%s %50s%s %16llu\n",
314                now_str,
315                csv_separator,
316                is_persistent ? "!" : " ",
317                csv_separator,
318                subsystem,
319                csv_separator,
320                _(name),
321                (0 == strlen (csv_separator) ? ":": csv_separator),
322                (unsigned long long) value);
323     }
324     else
325     {
326       FPRINTF (stdout,
327                "%s%s%12s%s %50s%s %16llu\n",
328                is_persistent ? "!" : " ",
329                csv_separator,
330                subsystem,
331                csv_separator,
332                _(name),
333                (0 == strlen (csv_separator) ? ":": csv_separator),
334                (unsigned long long) value);
335     }
336   }
337   else
338     FPRINTF (stdout,
339              "%llu\n",
340              (unsigned long long) value);
341
342   return GNUNET_OK;
343 }
344
345 /**
346  * Function called last by the statistics code.
347  *
348  * @param cls closure
349  * @param success #GNUNET_OK if statistics were
350  *        successfully obtained, #GNUNET_SYSERR if not.
351  */
352 static void
353 cleanup (void *cls,
354          int success)
355 {
356   for (unsigned i = 0; i < num_nodes; i++)
357   {
358     nodes[i].gh = NULL;
359   }
360   if (GNUNET_OK != success)
361   {
362     if (NULL == remote_host)
363       FPRINTF (stderr,
364                "%s",
365                _("Failed to obtain statistics.\n"));
366     else
367       FPRINTF (stderr,
368                _("Failed to obtain statistics from host `%s:%llu'\n"),
369                remote_host,
370                remote_port);
371     ret = 1;
372   }
373   GNUNET_SCHEDULER_shutdown ();
374 }
375
376 /**
377  * @brief Iterate over statistics values and store them in #values.
378  * They will be printed once all are available.
379  *
380  * @param cls Cosure - Node index.
381  * @param subsystem Subsystem of the value.
382  * @param name Name of the value.
383  * @param value Value itself.
384  * @param is_persistent Persistence.
385  *
386  * @return GNUNET_OK - continue.
387  */
388 static int
389 collector (void *cls,
390            const char *subsystem,
391            const char *name,
392            uint64_t value,
393            int is_persistent)
394 {
395   const unsigned index_node = *(unsigned *) cls;
396   struct GNUNET_HashCode *key;
397   struct GNUNET_HashCode hc;
398   char *subsys_name;
399   unsigned len_subsys_name;
400   struct ValueSet *value_set;
401
402   len_subsys_name = strlen (subsystem) + 3 + strlen (name) + 1;
403   subsys_name = GNUNET_malloc (len_subsys_name);
404   SPRINTF (subsys_name, "%s---%s", subsystem, name);
405   key = &hc;
406   GNUNET_CRYPTO_hash (subsys_name, len_subsys_name, key);
407   GNUNET_free (subsys_name);
408   if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (values, key))
409   {
410     // get
411     value_set = GNUNET_CONTAINER_multihashmap_get (values, key);
412   }
413   else
414   {
415     // new
416     value_set = new_value_set (subsystem, name, num_nodes, is_persistent);
417   }
418   // write
419   value_set->values[index_node] = value;
420   // put
421   GNUNET_CONTAINER_multihashmap_put (values, key, value_set,
422       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
423   return GNUNET_OK;
424 }
425
426 /**
427  * Function run on shutdown to clean up.
428  *
429  * @param cls the statistics handle
430  */
431 static void
432 shutdown_task (void *cls)
433 {
434   const unsigned index_node = *(unsigned *) cls;
435   struct GNUNET_STATISTICS_Handle *h;
436   struct GNUNET_STATISTICS_GetHandle *gh;
437
438   nodes[index_node].shutdown_task = NULL;
439   if ( (NULL != path_testbed) &&
440        (NULL != nodes[index_node].conf) )
441   {
442     GNUNET_CONFIGURATION_destroy (nodes[index_node].conf);
443     nodes[index_node].conf = NULL;
444   }
445
446   h = nodes[index_node].handle;
447   gh = nodes[index_node].gh;
448   if (NULL == h)
449   {
450     num_nodes_ready--;
451     if (0 == num_nodes_ready)
452     {
453       GNUNET_array_grow (nodes, num_nodes, 0);
454       GNUNET_CONTAINER_multihashmap_destroy (values);
455     }
456     return;
457   }
458   if (NULL != gh)
459   {
460     GNUNET_STATISTICS_get_cancel (gh);
461     gh = NULL;
462   }
463   if ( (GNUNET_YES == watch) &&
464        (NULL != subsystem) &&
465        (NULL != name) )
466     GNUNET_assert (GNUNET_OK ==
467        GNUNET_STATISTICS_watch_cancel (h,
468                                                    subsystem,
469                                                    name,
470                                                    &printer_watch,
471                &nodes[index_node].index_node));
472   GNUNET_STATISTICS_destroy (h,
473                              GNUNET_NO);
474   h = NULL;
475
476   num_nodes_ready--;
477   if (0 == num_nodes_ready)
478   {
479     GNUNET_array_grow (nodes, num_nodes, 0);
480     GNUNET_CONTAINER_multihashmap_destroy (values);
481   }
482 }
483
484
485 /**
486  * Main task that does the actual work.
487  *
488  * @param cls closure with our configuration
489  */
490 static void
491 main_task (void *cls)
492 {
493   unsigned index_node = *(unsigned *) cls;
494   const struct GNUNET_CONFIGURATION_Handle *cfg = nodes[index_node].conf;
495
496   if (set_value)
497   {
498     if (NULL == subsystem)
499     {
500       FPRINTF (stderr,
501                "%s",
502                _("Missing argument: subsystem \n"));
503       ret = 1;
504       return;
505     }
506     if (NULL == name)
507     {
508       FPRINTF (stderr,
509                "%s",
510                _("Missing argument: name\n"));
511       ret = 1;
512       return;
513     }
514     nodes[index_node].handle = GNUNET_STATISTICS_create (subsystem,
515                                   cfg);
516     if (NULL == nodes[index_node].handle)
517     {
518       ret = 1;
519       return;
520     }
521     GNUNET_STATISTICS_set (nodes[index_node].handle,
522                            name,
523                            (uint64_t) set_val,
524                            persistent);
525     GNUNET_STATISTICS_destroy (nodes[index_node].handle,
526                                GNUNET_YES);
527     nodes[index_node].handle = NULL;
528     return;
529   }
530   if (NULL == (nodes[index_node].handle = GNUNET_STATISTICS_create ("gnunet-statistics",
531                                              cfg)))
532   {
533     ret = 1;
534     return;
535   }
536   if (GNUNET_NO == watch)
537   {
538     if (NULL ==
539         (nodes[index_node].gh = GNUNET_STATISTICS_get (nodes[index_node].handle,
540                                                        subsystem,
541                                                        name,
542                                                        &continuation_print,
543                                                        &collector,
544                                      &nodes[index_node].index_node)) )
545       cleanup (nodes[index_node].handle,
546                GNUNET_SYSERR);
547   }
548   else
549   {
550     if ( (NULL == subsystem) ||
551          (NULL == name) )
552     {
553       printf (_("No subsystem or name given\n"));
554       GNUNET_STATISTICS_destroy (nodes[index_node].handle,
555                                  GNUNET_NO);
556       nodes[index_node].handle = NULL;
557       ret = 1;
558       return;
559     }
560     if (GNUNET_OK !=
561         GNUNET_STATISTICS_watch (nodes[index_node].handle,
562                                  subsystem,
563                                  name,
564                                  &printer_watch,
565                                  &nodes[index_node].index_node))
566     {
567       fprintf (stderr,
568                _("Failed to initialize watch routine\n"));
569       nodes[index_node].shutdown_task =
570         GNUNET_SCHEDULER_add_now (&shutdown_task,
571                                   &nodes[index_node].index_node);
572       return;
573     }
574   }
575   nodes[index_node].shutdown_task =
576     GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
577                                    &nodes[index_node].index_node);
578 }
579
580 /**
581  * @brief Iter over content of a node's directory to check for existence of a
582  * config file.
583  *
584  * Implements #GNUNET_FileNameCallback
585  *
586  * @param cls pointer to indicate success
587  * @param filename filename inside the directory of the potential node
588  *
589  * @return to continue iteration or not to
590  */
591 static int
592 iter_check_config (void *cls,
593                    const char *filename)
594 {
595   if (0 == strncmp (GNUNET_STRINGS_get_short_name (filename), "config", 6))
596   {
597     /* Found the config - stop iteration successfully */
598     GNUNET_array_grow (nodes, num_nodes, num_nodes+1);
599     nodes[num_nodes-1].conf = GNUNET_CONFIGURATION_create();
600     nodes[num_nodes-1].index_node = num_nodes-1;
601     if (GNUNET_OK != GNUNET_CONFIGURATION_load (nodes[num_nodes-1].conf, filename))
602     {
603       FPRINTF (stderr, "Failed loading config `%s'\n", filename);
604       return GNUNET_SYSERR;
605     }
606     return GNUNET_NO;
607   }
608   else
609   {
610     /* Continue iteration */
611     return GNUNET_OK;
612   }
613 }
614
615 /**
616  * @brief Iterates over filenames in testbed directory.
617  *
618  * Implements #GNUNET_FileNameCallback
619  *
620  * Checks if the file is a directory for a testbed node
621  * and counts the nodes.
622  *
623  * @param cls counter of nodes
624  * @param filename full path of the file in testbed
625  *
626  * @return status whether to continue iteration
627  */
628 static int
629 iter_testbed_path (void *cls,
630                    const char *filename)
631 {
632   unsigned index_node;
633
634   GNUNET_assert (NULL != filename);
635   if (1 == SSCANF (GNUNET_STRINGS_get_short_name (filename),
636                   "%u",
637                   &index_node))
638   {
639     if (-1 == GNUNET_DISK_directory_scan (filename,
640                                           iter_check_config,
641                                           NULL))
642     {
643       /* This is probably no directory for a testbed node
644        * Go on with iteration */
645       return GNUNET_OK;
646     }
647     return GNUNET_OK;
648   }
649   return GNUNET_OK;
650 }
651
652 /**
653  * @brief Count the number of nodes running in the testbed
654  *
655  * @param path_testbed path to the testbed data
656  *
657  * @return number of running nodes
658  */
659 static int
660 discover_testbed_nodes (const char *path_testbed)
661 {
662   int num_dir_entries;
663
664   num_dir_entries = GNUNET_DISK_directory_scan (path_testbed,
665                                                 iter_testbed_path,
666                                                 NULL);
667   if (-1 == num_dir_entries)
668   {
669     FPRINTF (stderr,
670             "Failure during scanning directory `%s'\n",
671             path_testbed);
672     return -1;
673   }
674   return 0;
675 }
676
677 /**
678  * Main function that will be run by the scheduler.
679  *
680  * @param cls closure
681  * @param args remaining command-line arguments
682  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
683  * @param cfg configuration
684  */
685 static void
686 run (void *cls,
687      char *const *args,
688      const char *cfgfile,
689      const struct GNUNET_CONFIGURATION_Handle *cfg)
690 {
691   struct GNUNET_CONFIGURATION_Handle *c;
692
693   c = (struct GNUNET_CONFIGURATION_Handle *) cfg;
694   set_value = GNUNET_NO;
695   if (NULL == csv_separator) csv_separator = "";
696   if (NULL != args[0])
697   {
698     if (1 != SSCANF (args[0],
699                      "%llu",
700                      &set_val))
701     {
702       FPRINTF (stderr,
703                _("Invalid argument `%s'\n"),
704                args[0]);
705       ret = 1;
706       return;
707     }
708     set_value = GNUNET_YES;
709   }
710   if (NULL != remote_host)
711   {
712     if (0 == remote_port)
713     {
714       if (GNUNET_SYSERR ==
715           GNUNET_CONFIGURATION_get_value_number (cfg,
716                                                  "statistics",
717                                                  "PORT",
718                                                  &remote_port))
719       {
720         FPRINTF (stderr,
721                  _("A port is required to connect to host `%s'\n"),
722                  remote_host);
723         return;
724       }
725     }
726     else if (65535 <= remote_port)
727     {
728       FPRINTF (stderr,
729                _("A port has to be between 1 and 65535 to connect to host `%s'\n"),
730                remote_host);
731       return;
732     }
733
734     /* Manipulate configuration */
735     GNUNET_CONFIGURATION_set_value_string (c,
736                                            "statistics",
737                                            "UNIXPATH",
738                                            "");
739     GNUNET_CONFIGURATION_set_value_string (c,
740                                            "statistics",
741                                            "HOSTNAME",
742                                            remote_host);
743     GNUNET_CONFIGURATION_set_value_number (c,
744                                            "statistics",
745                                            "PORT",
746                                            remote_port);
747   }
748   if (NULL == path_testbed)
749   {
750     values = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
751     GNUNET_array_grow (nodes, num_nodes, 1);
752     nodes[0].index_node = 0;
753     nodes[0].conf = c;
754     GNUNET_SCHEDULER_add_now (&main_task, &nodes[0].index_node);
755   }
756   else
757   {
758     if (GNUNET_YES == watch)
759     {
760       printf (_("Not able to watch testbed nodes (yet - feel free to implement)\n"));
761       ret = 1;
762       return;
763     }
764     values = GNUNET_CONTAINER_multihashmap_create (4, GNUNET_NO);
765     if (-1 == discover_testbed_nodes (path_testbed))
766     {
767       return;
768     }
769     /* For each config/node collect statistics */
770     for (unsigned i = 0; i < num_nodes; i++)
771     {
772       GNUNET_SCHEDULER_add_now (&main_task,
773               &nodes[i].index_node);
774     }
775   }
776 }
777
778
779 /**
780  * The main function to obtain statistics in GNUnet.
781  *
782  * @param argc number of arguments from the command line
783  * @param argv command line arguments
784  * @return 0 ok, 1 on error
785  */
786 int
787 main (int argc, char *const *argv)
788 {
789   struct GNUNET_GETOPT_CommandLineOption options[] = {
790     GNUNET_GETOPT_option_string ('n',
791                                  "name",
792                                  "NAME",
793                                  gettext_noop ("limit output to statistics for the given NAME"),
794                                  &name),
795
796     GNUNET_GETOPT_option_flag ('p',
797                                   "persistent",
798                                   gettext_noop ("make the value being set persistent"),
799                                   &persistent),
800
801     GNUNET_GETOPT_option_string ('s',
802                                  "subsystem",
803                                  "SUBSYSTEM",
804                                  gettext_noop ("limit output to the given SUBSYSTEM"),
805                                  &subsystem),
806
807     GNUNET_GETOPT_option_string ('S',
808                                  "csv-separator",
809                                  "CSV_SEPARATOR",
810                                  gettext_noop ("use as csv separator"),
811                                  &csv_separator),
812
813     GNUNET_GETOPT_option_filename ('t',
814                                   "testbed",
815                                   "TESTBED",
816                                   gettext_noop ("path to the folder containing the testbed data"),
817                                   &path_testbed),
818
819     GNUNET_GETOPT_option_flag ('q',
820                                   "quiet",
821                                   gettext_noop ("just print the statistics value"),
822                                   &quiet),
823
824     GNUNET_GETOPT_option_flag ('w',
825                                   "watch",
826                                   gettext_noop ("watch value continuously"),
827                                   &watch),
828
829     GNUNET_GETOPT_option_string ('r',
830                                  "remote",
831                                  "REMOTE",
832                                  gettext_noop ("connect to remote host"),
833                                  &remote_host),
834
835     GNUNET_GETOPT_option_ulong ('o',
836                                     "port",
837                                     "PORT",
838                                     gettext_noop ("port for remote host"),
839                                     &remote_port),
840
841     GNUNET_GETOPT_OPTION_END
842   };
843   remote_port = 0;
844   remote_host = NULL;
845   if (GNUNET_OK !=
846       GNUNET_STRINGS_get_utf8_args (argc, argv,
847                                     &argc, &argv))
848     return 2;
849
850   ret = (GNUNET_OK ==
851          GNUNET_PROGRAM_run (argc,
852                              argv,
853                              "gnunet-statistics [options [value]]",
854                              gettext_noop
855                              ("Print statistics about GNUnet operations."),
856                              options,
857                              &run,
858                              NULL)) ? ret : 1;
859   GNUNET_free_non_null (remote_host);
860   GNUNET_free ((void*) argv);
861   return ret;
862 }
863
864 /* end of gnunet-statistics.c */