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