Merge branch 'master' of ssh://gnunet.org/gnunet
[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
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
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   case GNUNET_FS_STATUS_PUBLISH_PROGRESS_DIRECTORY:
385     p = info->value.publish.cctx;
386     return p;
387   case GNUNET_FS_STATUS_PUBLISH_ERROR:
388     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
389                 "Publishing failed\n");
390     GNUNET_STATISTICS_update (stats_handle,
391                               "# failed publish operations", 1, GNUNET_NO);
392     p = info->value.publish.cctx;
393     p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
394     return p;
395   case GNUNET_FS_STATUS_PUBLISH_COMPLETED:
396     p = info->value.publish.cctx;
397     GNUNET_STATISTICS_update (stats_handle,
398                               "# publishing time (ms)",
399                               (long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL,
400                               GNUNET_NO);
401     p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
402     return p;
403   case GNUNET_FS_STATUS_PUBLISH_STOPPED:
404     p = info->value.publish.cctx;
405     p->ctx = NULL;
406     GNUNET_CONTAINER_DLL_remove (publish_head, publish_tail, p);
407     GNUNET_free (p);
408     return NULL;
409   case GNUNET_FS_STATUS_DOWNLOAD_START:
410   case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS:
411   case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE:
412   case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE:
413     p = info->value.download.cctx;
414     return p;
415   case GNUNET_FS_STATUS_DOWNLOAD_ERROR:
416     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
417                 "Download failed\n");
418     GNUNET_STATISTICS_update (stats_handle,
419                               "# failed downloads", 1, GNUNET_NO);
420     p = info->value.download.cctx;
421     p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
422     return p;
423   case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED:
424     p = info->value.download.cctx;
425     GNUNET_STATISTICS_update (stats_handle,
426                               "# download time (ms)",
427                               (long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL,
428                               GNUNET_NO);
429     p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
430     return p;
431   case GNUNET_FS_STATUS_DOWNLOAD_STOPPED:
432     p = info->value.download.cctx;
433     p->ctx = NULL;
434     if (NULL == p->sctx)
435     {
436       GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
437       GNUNET_free (p);
438     }
439     return NULL;
440   case GNUNET_FS_STATUS_SEARCH_START:
441   case GNUNET_FS_STATUS_SEARCH_RESULT_NAMESPACE:
442     p = info->value.search.cctx;
443     return p;
444   case GNUNET_FS_STATUS_SEARCH_RESULT:
445     p = info->value.search.cctx;
446     uri = info->value.search.specifics.result.uri;
447     if (GNUNET_YES != GNUNET_FS_uri_test_chk (uri))
448       return NULL; /* not what we want */
449     if (p->y != GNUNET_FS_uri_chk_get_file_size (uri))
450       return NULL; /* not what we want */
451     GNUNET_STATISTICS_update (stats_handle,
452                               "# search time (ms)",
453                               (long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL,
454                               GNUNET_NO);
455     p->start_time = GNUNET_TIME_absolute_get ();
456     p->ctx = GNUNET_FS_download_start (fs_handle, uri,
457                                        NULL, NULL, NULL,
458                                        0, GNUNET_FS_uri_chk_get_file_size (uri),
459                                        anonymity_level,
460                                        GNUNET_FS_DOWNLOAD_NO_TEMPORARIES,
461                                        p,
462                                        NULL);
463     p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
464     return NULL;
465   case GNUNET_FS_STATUS_SEARCH_UPDATE:
466   case GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED:
467     return NULL; /* don't care */
468   case GNUNET_FS_STATUS_SEARCH_ERROR:
469     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
470                 "Search failed\n");
471     GNUNET_STATISTICS_update (stats_handle,
472                               "# failed searches", 1, GNUNET_NO);
473     p = info->value.search.cctx;
474     p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
475     return p;
476   case GNUNET_FS_STATUS_SEARCH_STOPPED:
477     p = info->value.search.cctx;
478     p->sctx = NULL;
479     if (NULL == p->ctx)
480     {
481       GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
482       GNUNET_free (p);
483     }
484     return NULL;
485   default:
486     /* unexpected event during profiling */
487     GNUNET_break (0);
488     return NULL;
489   }
490 }
491
492
493 /**
494  * Start publish operation.
495  *
496  * @param cls the 'struct Pattern' specifying the operation to perform
497  */
498 static void
499 start_publish (void *cls)
500 {
501   struct Pattern *p = cls;
502   struct GNUNET_FS_FileInformation *fi;
503
504   p->task = NULL;
505   fi = make_file (p->x, p->y, p);
506   p->start_time = GNUNET_TIME_absolute_get ();
507   p->ctx = GNUNET_FS_publish_start (fs_handle,
508                                     fi,
509                                     NULL, NULL, NULL,
510                                     GNUNET_FS_PUBLISH_OPTION_NONE);
511 }
512
513
514 /**
515  * Start download operation.
516  *
517  * @param cls the 'struct Pattern' specifying the operation to perform
518  */
519 static void
520 start_download (void *cls)
521 {
522   struct Pattern *p = cls;
523   struct GNUNET_FS_Uri *keywords;
524
525   p->task = NULL;
526   keywords = make_keywords (p->x);
527   p->start_time = GNUNET_TIME_absolute_get ();
528   p->sctx = GNUNET_FS_search_start (fs_handle, keywords,
529                                     anonymity_level,
530                                     GNUNET_FS_SEARCH_OPTION_NONE,
531                                     p);
532 }
533
534
535 /**
536  * @brief Main function that will be run by the scheduler.
537  *
538  * @param cls closure
539  * @param args remaining command-line arguments
540  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
541  * @param cfg_ configuration
542  */
543 static void
544 run (void *cls, char *const *args GNUNET_UNUSED,
545      const char *cfgfile GNUNET_UNUSED,
546      const struct GNUNET_CONFIGURATION_Handle *cfg_)
547 {
548   char myoptname[128];
549   struct Pattern *p;
550
551   cfg = cfg_;
552   /* Scheduled the task to clean up when shutdown is called */
553   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
554                                  NULL);
555
556   if (GNUNET_OK !=
557       GNUNET_CONFIGURATION_get_value_number (cfg,
558                                              "TESTBED", "PEERID",
559                                              &my_peerid))
560   {
561     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
562                                "TESTBED", "PEERID");
563     global_ret = GNUNET_SYSERR;
564     GNUNET_SCHEDULER_shutdown ();
565     return;
566   }
567   if (GNUNET_OK !=
568       GNUNET_CONFIGURATION_get_value_number (cfg,
569                                              "FSPROFILER", "ANONYMITY_LEVEL",
570                                              &anonymity_level))
571     anonymity_level = 1;
572   if (GNUNET_OK !=
573       GNUNET_CONFIGURATION_get_value_number (cfg,
574                                              "FSPROFILER", "REPLICATION_LEVEL",
575                                              &replication_level))
576     replication_level = 1;
577   GNUNET_snprintf (myoptname, sizeof (myoptname),
578                    "DOWNLOAD-PATTERN-%u", my_peerid);
579   if (GNUNET_OK !=
580       GNUNET_CONFIGURATION_get_value_string (cfg,
581                                              "FSPROFILER", myoptname,
582                                              &download_pattern))
583     download_pattern = GNUNET_strdup ("");
584   GNUNET_snprintf (myoptname, sizeof (myoptname),
585                    "PUBLISH-PATTERN-%u", my_peerid);
586   if (GNUNET_OK !=
587       GNUNET_CONFIGURATION_get_value_string (cfg,
588                                              "FSPROFILER", myoptname,
589                                              &publish_pattern))
590     publish_pattern = GNUNET_strdup ("");
591   if ( (GNUNET_OK !=
592         parse_pattern (&download_head,
593                        &download_tail,
594                        download_pattern)) ||
595        (GNUNET_OK !=
596         parse_pattern (&publish_head,
597                        &publish_tail,
598                        publish_pattern)) )
599   {
600     GNUNET_SCHEDULER_shutdown ();
601     return;
602   }
603
604   stats_handle = GNUNET_STATISTICS_create ("fsprofiler", cfg);
605   fs_handle =
606     GNUNET_FS_start (cfg,
607                      "fsprofiler",
608                      &progress_cb, NULL,
609                      GNUNET_FS_FLAGS_NONE,
610                      GNUNET_FS_OPTIONS_DOWNLOAD_PARALLELISM, 1,
611                      GNUNET_FS_OPTIONS_REQUEST_PARALLELISM, 1,
612                      GNUNET_FS_OPTIONS_END);
613   if (NULL == fs_handle)
614   {
615     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not acquire FS handle. Exiting.\n");
616     global_ret = GNUNET_SYSERR;
617     GNUNET_SCHEDULER_shutdown ();
618     return;
619   }
620   for (p = publish_head; NULL != p; p = p->next)
621     p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
622                                             &start_publish, p);
623   for (p = download_head; NULL != p; p = p->next)
624     p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
625                                             &start_download, p);
626 }
627
628
629 /**
630  * Program that performs various "random" FS activities.
631  *
632  * @param argc number of arguments from the command line
633  * @param argv command line arguments
634  * @return 0 ok, 1 on error
635  */
636 int
637 main (int argc, char *const *argv)
638 {
639   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
640     GNUNET_GETOPT_OPTION_END
641   };
642
643   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
644     return 2;
645   return (GNUNET_OK ==
646           GNUNET_PROGRAM_run (argc, argv, "gnunet-daemon-fsprofiler",
647                               gettext_noop
648                               ("Daemon to use file-sharing to measure its performance."),
649                               options, &run, NULL)) ? global_ret : 1;
650 }
651
652 /* end of gnunet-daemon-fsprofiler.c */