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