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