error handling
[oweals/gnunet.git] / src / fs / gnunet-daemon-fsprofiler.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2012 Christian Grothoff
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 fs/gnunet-daemon-fsprofiler.c
23  * @brief daemon that publishes and downloads (random) files
24  * @author Christian Grothoff
25  *
26  * TODO:
27  * - how to signal driver that we're done?
28  */
29 #include "platform.h"
30 #include "gnunet_fs_service.h"
31 #include "gnunet_statistics_service.h"
32
33 /**
34  * We use 'patterns' of the form (x,y,t) to specify desired download/publish
35  * activities of a peer.  They are stored in a DLL.
36  */
37 struct Pattern
38 {
39   /**
40    * Kept in a DLL.
41    */
42   struct Pattern *next;
43
44   /**
45    * Kept in a DLL.
46    */
47   struct Pattern *prev;
48
49   /**
50    * Execution context for the pattern (FS-handle to the operation).
51    */
52   void *ctx;
53
54   /**
55    * Secondary execution context for the pattern (FS-handle to the operation).
56    */
57   void *sctx;
58
59   /**
60    * When did the operation start?
61    */
62   struct GNUNET_TIME_Absolute start_time;
63
64   /**
65    * With how much delay should this operation be started?
66    */
67   struct GNUNET_TIME_Relative delay;
68
69   /**
70    * Task to run the operation.
71    */
72   struct GNUNET_SCHEDULER_Task *task;
73
74   /**
75    * Secondary task to run the operation.
76    */
77   struct GNUNET_SCHEDULER_Task *stask;
78
79   /**
80    * X-value.
81    */
82   unsigned long long x;
83
84   /**
85    * Y-value.
86    */
87   unsigned long long y;
88 };
89
90
91 /**
92  * Return value from 'main'.
93  */
94 static int global_ret;
95
96 /**
97  * Configuration we use.
98  */
99 static const struct GNUNET_CONFIGURATION_Handle *cfg;
100
101 /**
102  * Handle to the statistics service.
103  */
104 static struct GNUNET_STATISTICS_Handle *stats_handle;
105
106 /**
107  * Peer's FS handle.
108  */
109 static struct GNUNET_FS_Handle *fs_handle;
110
111 /**
112  * Unique number for this peer in the testbed.
113  */
114 static unsigned long long my_peerid;
115
116 /**
117  * Desired anonymity level.
118  */
119 static unsigned long long anonymity_level;
120
121 /**
122  * Desired replication level.
123  */
124 static unsigned long long replication_level;
125
126 /**
127  * String describing which publishing operations this peer should
128  * perform.  The format is "(SIZE,SEED,TIME)*", for example:
129  * "(1,5,0)(7,3,13)" means to publish a file with 1 byte and
130  * seed/keyword 5 immediately and another file with 7 bytes and
131  * seed/keyword 3 after 13 ms.
132  */
133 static char *publish_pattern;
134
135 /**
136  * Head of the DLL of publish patterns.
137  */
138 static struct Pattern *publish_head;
139
140 /**
141  * Tail of the DLL of publish patterns.
142  */
143 static struct Pattern *publish_tail;
144
145 /**
146  * String describing which download operations this peer should
147  * perform. The format is "(KEYWORD,SIZE,DELAY)*"; for example,
148  * "(1,7,3)(3,8,8)" means to download one file of 7 bytes under
149  * keyword "1" starting the search after 3 ms; and another one of 8
150  * bytes under keyword '3' starting after 8 ms.  The file size is
151  * used to determine which search result(s) should be used or ignored.
152  */
153 static char *download_pattern;
154
155 /**
156  * Head of the DLL of publish patterns.
157  */
158 static struct Pattern *download_head;
159
160 /**
161  * Tail of the DLL of publish patterns.
162  */
163 static struct Pattern *download_tail;
164
165
166 /**
167  * Parse a pattern string and store the corresponding
168  * 'struct Pattern' in the given head/tail.
169  *
170  * @param head where to store the head
171  * @param tail where to store the tail
172  * @param pattern pattern to parse
173  * @return GNUNET_OK on success
174  */
175 static int
176 parse_pattern (struct Pattern **head,
177                struct Pattern **tail,
178                const char *pattern)
179 {
180   struct Pattern *p;
181   unsigned long long x;
182   unsigned long long y;
183   unsigned long long t;
184
185   while (3 == sscanf (pattern,
186                       "(%llu,%llu,%llu)",
187                       &x, &y, &t))
188   {
189     p = GNUNET_new (struct Pattern);
190     p->x = x;
191     p->y = y;
192     p->delay.rel_value_us = (uint64_t) t;
193     GNUNET_CONTAINER_DLL_insert (*head, *tail, p);
194     pattern = strstr (pattern, ")");
195     GNUNET_assert (NULL != pattern);
196     pattern++;
197   }
198   return (0 == strlen (pattern)) ? GNUNET_OK : GNUNET_SYSERR;
199 }
200
201
202 /**
203  * Create a KSK URI from a number.
204  *
205  * @param kval the number
206  * @return corresponding KSK URI
207  */
208 static struct GNUNET_FS_Uri *
209 make_keywords (uint64_t kval)
210 {
211   char kw[128];
212
213   GNUNET_snprintf (kw, sizeof(kw),
214                    "%llu", (unsigned long long) kval);
215   return GNUNET_FS_uri_ksk_create (kw, NULL);
216 }
217
218
219 /**
220  * Create a file of the given length with a deterministic amount
221  * of data to be published under keyword 'kval'.
222  *
223  * @param length number of bytes in the file
224  * @param kval keyword value and seed for the data of the file
225  * @param ctx context to pass to 'fi'
226  * @return file information handle for the file
227  */
228 static struct GNUNET_FS_FileInformation *
229 make_file (uint64_t length,
230            uint64_t kval,
231            void *ctx)
232 {
233   struct GNUNET_FS_FileInformation *fi;
234   struct GNUNET_FS_BlockOptions bo;
235   char *data;
236   struct GNUNET_FS_Uri *keywords;
237   unsigned long long i;
238   uint64_t xor;
239
240   data = NULL; /* to make compilers happy */
241   if ((0 != length) &&
242       (NULL == (data = GNUNET_malloc_large ((size_t) length))))
243     return NULL;
244   /* initialize data with 'unique' data only depending on 'kval' and 'size',
245      making sure that blocks do not repeat */
246   for (i = 0; i < length; i += 8)
247   {
248     xor = length ^ kval ^ (uint64_t) (i / 32 / 1024);
249     GNUNET_memcpy (&data[i], &xor, GNUNET_MIN (length - i, sizeof(uint64_t)));
250   }
251   bo.expiration_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS);
252   bo.anonymity_level = (uint32_t) anonymity_level;
253   bo.content_priority = 128;
254   bo.replication_level = (uint32_t) replication_level;
255   keywords = make_keywords (kval);
256   fi = GNUNET_FS_file_information_create_from_data (fs_handle,
257                                                     ctx,
258                                                     length,
259                                                     data, keywords,
260                                                     NULL, GNUNET_NO, &bo);
261   GNUNET_FS_uri_destroy (keywords);
262   return fi;
263 }
264
265
266 /**
267  * Task run during shutdown.
268  *
269  * @param cls unused
270  */
271 static void
272 shutdown_task (void *cls)
273 {
274   struct Pattern *p;
275
276   while (NULL != (p = publish_head))
277   {
278     if (NULL != p->task)
279       GNUNET_SCHEDULER_cancel (p->task);
280     if (NULL != p->ctx)
281       GNUNET_FS_publish_stop (p->ctx);
282     GNUNET_CONTAINER_DLL_remove (publish_head, publish_tail, p);
283     GNUNET_free (p);
284   }
285   while (NULL != (p = download_head))
286   {
287     if (NULL != p->task)
288       GNUNET_SCHEDULER_cancel (p->task);
289     if (NULL != p->stask)
290       GNUNET_SCHEDULER_cancel (p->stask);
291     if (NULL != p->ctx)
292       GNUNET_FS_download_stop (p->ctx, GNUNET_YES);
293     if (NULL != p->sctx)
294       GNUNET_FS_search_stop (p->sctx);
295     GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
296     GNUNET_free (p);
297   }
298   if (NULL != fs_handle)
299   {
300     GNUNET_FS_stop (fs_handle);
301     fs_handle = NULL;
302   }
303   if (NULL != stats_handle)
304   {
305     GNUNET_STATISTICS_destroy (stats_handle, GNUNET_YES);
306     stats_handle = NULL;
307   }
308 }
309
310
311 /**
312  * Task run when a publish operation should be stopped.
313  *
314  * @param cls the 'struct Pattern' of the publish operation to stop
315  */
316 static void
317 publish_stop_task (void *cls)
318 {
319   struct Pattern *p = cls;
320
321   p->task = NULL;
322   GNUNET_FS_publish_stop (p->ctx);
323 }
324
325
326 /**
327  * Task run when a download operation should be stopped.
328  *
329  * @param cls the 'struct Pattern' of the download operation to stop
330  */
331 static void
332 download_stop_task (void *cls)
333 {
334   struct Pattern *p = cls;
335
336   p->task = NULL;
337   GNUNET_FS_download_stop (p->ctx, GNUNET_YES);
338 }
339
340
341 /**
342  * Task run when a download operation should be stopped.
343  *
344  * @param cls the 'struct Pattern' of the download operation to stop
345  */
346 static void
347 search_stop_task (void *cls)
348 {
349   struct Pattern *p = cls;
350
351   p->stask = NULL;
352   GNUNET_FS_search_stop (p->sctx);
353 }
354
355
356 /**
357  * Notification of FS to a client about the progress of an
358  * operation.  Callbacks of this type will be used for uploads,
359  * downloads and searches.  Some of the arguments depend a bit
360  * in their meaning on the context in which the callback is used.
361  *
362  * @param cls closure
363  * @param info details about the event, specifying the event type
364  *        and various bits about the event
365  * @return client-context (for the next progress call
366  *         for this operation; should be set to NULL for
367  *         SUSPEND and STOPPED events).  The value returned
368  *         will be passed to future callbacks in the respective
369  *         field in the GNUNET_FS_ProgressInfo struct.
370  */
371 static void *
372 progress_cb (void *cls,
373              const struct GNUNET_FS_ProgressInfo *info)
374 {
375   struct Pattern *p;
376   const struct GNUNET_FS_Uri *uri;
377
378   switch (info->status)
379   {
380   case GNUNET_FS_STATUS_PUBLISH_START:
381   case GNUNET_FS_STATUS_PUBLISH_PROGRESS:
382     p = info->value.publish.cctx;
383     return p;
384
385   case GNUNET_FS_STATUS_PUBLISH_PROGRESS_DIRECTORY:
386     p = info->value.publish.cctx;
387     return p;
388
389   case GNUNET_FS_STATUS_PUBLISH_ERROR:
390     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
391                 "Publishing failed\n");
392     GNUNET_STATISTICS_update (stats_handle,
393                               "# failed publish operations", 1, GNUNET_NO);
394     p = info->value.publish.cctx;
395     p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
396     return p;
397
398   case GNUNET_FS_STATUS_PUBLISH_COMPLETED:
399     p = info->value.publish.cctx;
400     GNUNET_STATISTICS_update (stats_handle,
401                               "# publishing time (ms)",
402                               (long long) GNUNET_TIME_absolute_get_duration (
403                                 p->start_time).rel_value_us / 1000LL,
404                               GNUNET_NO);
405     p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
406     return p;
407
408   case GNUNET_FS_STATUS_PUBLISH_STOPPED:
409     p = info->value.publish.cctx;
410     p->ctx = NULL;
411     GNUNET_CONTAINER_DLL_remove (publish_head, publish_tail, p);
412     GNUNET_free (p);
413     return NULL;
414
415   case GNUNET_FS_STATUS_DOWNLOAD_START:
416   case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS:
417   case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE:
418   case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE:
419     p = info->value.download.cctx;
420     return p;
421
422   case GNUNET_FS_STATUS_DOWNLOAD_ERROR:
423     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
424                 "Download failed\n");
425     GNUNET_STATISTICS_update (stats_handle,
426                               "# failed downloads", 1, GNUNET_NO);
427     p = info->value.download.cctx;
428     p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
429     return p;
430
431   case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED:
432     p = info->value.download.cctx;
433     GNUNET_STATISTICS_update (stats_handle,
434                               "# download time (ms)",
435                               (long long) GNUNET_TIME_absolute_get_duration (
436                                 p->start_time).rel_value_us / 1000LL,
437                               GNUNET_NO);
438     p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
439     return p;
440
441   case GNUNET_FS_STATUS_DOWNLOAD_STOPPED:
442     p = info->value.download.cctx;
443     p->ctx = NULL;
444     if (NULL == p->sctx)
445     {
446       GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
447       GNUNET_free (p);
448     }
449     return NULL;
450
451   case GNUNET_FS_STATUS_SEARCH_START:
452   case GNUNET_FS_STATUS_SEARCH_RESULT_NAMESPACE:
453     p = info->value.search.cctx;
454     return p;
455
456   case GNUNET_FS_STATUS_SEARCH_RESULT:
457     p = info->value.search.cctx;
458     uri = info->value.search.specifics.result.uri;
459     if (GNUNET_YES != GNUNET_FS_uri_test_chk (uri))
460       return NULL;   /* not what we want */
461     if (p->y != GNUNET_FS_uri_chk_get_file_size (uri))
462       return NULL;   /* not what we want */
463     GNUNET_STATISTICS_update (stats_handle,
464                               "# search time (ms)",
465                               (long long) GNUNET_TIME_absolute_get_duration (
466                                 p->start_time).rel_value_us / 1000LL,
467                               GNUNET_NO);
468     p->start_time = GNUNET_TIME_absolute_get ();
469     p->ctx = GNUNET_FS_download_start (fs_handle, uri,
470                                        NULL, NULL, NULL,
471                                        0, GNUNET_FS_uri_chk_get_file_size (uri),
472                                        anonymity_level,
473                                        GNUNET_FS_DOWNLOAD_NO_TEMPORARIES,
474                                        p,
475                                        NULL);
476     p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
477     return NULL;
478
479   case GNUNET_FS_STATUS_SEARCH_UPDATE:
480   case GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED:
481     return NULL;   /* don't care */
482
483   case GNUNET_FS_STATUS_SEARCH_ERROR:
484     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
485                 "Search failed\n");
486     GNUNET_STATISTICS_update (stats_handle,
487                               "# failed searches", 1, GNUNET_NO);
488     p = info->value.search.cctx;
489     p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
490     return p;
491
492   case GNUNET_FS_STATUS_SEARCH_STOPPED:
493     p = info->value.search.cctx;
494     p->sctx = NULL;
495     if (NULL == p->ctx)
496     {
497       GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
498       GNUNET_free (p);
499     }
500     return NULL;
501
502   default:
503     /* unexpected event during profiling */
504     GNUNET_break (0);
505     return NULL;
506   }
507 }
508
509
510 /**
511  * Start publish operation.
512  *
513  * @param cls the 'struct Pattern' specifying the operation to perform
514  */
515 static void
516 start_publish (void *cls)
517 {
518   struct Pattern *p = cls;
519   struct GNUNET_FS_FileInformation *fi;
520
521   p->task = NULL;
522   fi = make_file (p->x, p->y, p);
523   p->start_time = GNUNET_TIME_absolute_get ();
524   p->ctx = GNUNET_FS_publish_start (fs_handle,
525                                     fi,
526                                     NULL, NULL, NULL,
527                                     GNUNET_FS_PUBLISH_OPTION_NONE);
528 }
529
530
531 /**
532  * Start download operation.
533  *
534  * @param cls the 'struct Pattern' specifying the operation to perform
535  */
536 static void
537 start_download (void *cls)
538 {
539   struct Pattern *p = cls;
540   struct GNUNET_FS_Uri *keywords;
541
542   p->task = NULL;
543   keywords = make_keywords (p->x);
544   p->start_time = GNUNET_TIME_absolute_get ();
545   p->sctx = GNUNET_FS_search_start (fs_handle, keywords,
546                                     anonymity_level,
547                                     GNUNET_FS_SEARCH_OPTION_NONE,
548                                     p);
549 }
550
551
552 /**
553  * @brief Main function that will be run by the scheduler.
554  *
555  * @param cls closure
556  * @param args remaining command-line arguments
557  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
558  * @param cfg_ configuration
559  */
560 static void
561 run (void *cls, char *const *args GNUNET_UNUSED,
562      const char *cfgfile GNUNET_UNUSED,
563      const struct GNUNET_CONFIGURATION_Handle *cfg_)
564 {
565   char myoptname[128];
566   struct Pattern *p;
567
568   cfg = cfg_;
569   /* Scheduled the task to clean up when shutdown is called */
570   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
571                                  NULL);
572
573   if (GNUNET_OK !=
574       GNUNET_CONFIGURATION_get_value_number (cfg,
575                                              "TESTBED", "PEERID",
576                                              &my_peerid))
577   {
578     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
579                                "TESTBED", "PEERID");
580     global_ret = GNUNET_SYSERR;
581     GNUNET_SCHEDULER_shutdown ();
582     return;
583   }
584   if (GNUNET_OK !=
585       GNUNET_CONFIGURATION_get_value_number (cfg,
586                                              "FSPROFILER", "ANONYMITY_LEVEL",
587                                              &anonymity_level))
588     anonymity_level = 1;
589   if (GNUNET_OK !=
590       GNUNET_CONFIGURATION_get_value_number (cfg,
591                                              "FSPROFILER", "REPLICATION_LEVEL",
592                                              &replication_level))
593     replication_level = 1;
594   GNUNET_snprintf (myoptname, sizeof(myoptname),
595                    "DOWNLOAD-PATTERN-%u", my_peerid);
596   if (GNUNET_OK !=
597       GNUNET_CONFIGURATION_get_value_string (cfg,
598                                              "FSPROFILER", myoptname,
599                                              &download_pattern))
600     download_pattern = GNUNET_strdup ("");
601   GNUNET_snprintf (myoptname, sizeof(myoptname),
602                    "PUBLISH-PATTERN-%u", my_peerid);
603   if (GNUNET_OK !=
604       GNUNET_CONFIGURATION_get_value_string (cfg,
605                                              "FSPROFILER", myoptname,
606                                              &publish_pattern))
607     publish_pattern = GNUNET_strdup ("");
608   if ((GNUNET_OK !=
609        parse_pattern (&download_head,
610                       &download_tail,
611                       download_pattern)) ||
612       (GNUNET_OK !=
613        parse_pattern (&publish_head,
614                       &publish_tail,
615                       publish_pattern)))
616   {
617     GNUNET_SCHEDULER_shutdown ();
618     return;
619   }
620
621   stats_handle = GNUNET_STATISTICS_create ("fsprofiler", cfg);
622   fs_handle =
623     GNUNET_FS_start (cfg,
624                      "fsprofiler",
625                      &progress_cb, NULL,
626                      GNUNET_FS_FLAGS_NONE,
627                      GNUNET_FS_OPTIONS_DOWNLOAD_PARALLELISM, 1,
628                      GNUNET_FS_OPTIONS_REQUEST_PARALLELISM, 1,
629                      GNUNET_FS_OPTIONS_END);
630   if (NULL == fs_handle)
631   {
632     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
633                 "Could not acquire FS handle. Exiting.\n");
634     global_ret = GNUNET_SYSERR;
635     GNUNET_SCHEDULER_shutdown ();
636     return;
637   }
638   for (p = publish_head; NULL != p; p = p->next)
639     p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
640                                             &start_publish, p);
641   for (p = download_head; NULL != p; p = p->next)
642     p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
643                                             &start_download, p);
644 }
645
646
647 /**
648  * Program that performs various "random" FS activities.
649  *
650  * @param argc number of arguments from the command line
651  * @param argv command line arguments
652  * @return 0 ok, 1 on error
653  */
654 int
655 main (int argc, char *const *argv)
656 {
657   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
658     GNUNET_GETOPT_OPTION_END
659   };
660
661   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
662     return 2;
663   return (GNUNET_OK ==
664           GNUNET_PROGRAM_run (argc, argv, "gnunet-daemon-fsprofiler",
665                               gettext_noop
666                               (
667                                 "Daemon to use file-sharing to measure its performance."),
668                               options, &run, NULL)) ? global_ret : 1;
669 }
670
671
672 /* end of gnunet-daemon-fsprofiler.c */