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