-round expiration times to full seconds
[oweals/gnunet.git] / src / fs / gnunet-daemon-fsprofiler.c
1 /*
2      This file is part of GNUnet.
3      (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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, 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   GNUNET_SCHEDULER_TaskIdentifier task;
73
74   /**
75    * Secondary task to run the operation.
76    */
77   GNUNET_SCHEDULER_TaskIdentifier 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_malloc (sizeof (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     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  * @param tc unused
271  */
272 static void
273 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
274 {
275   struct Pattern *p;
276
277   while (NULL != (p = publish_head))
278   {
279     if (GNUNET_SCHEDULER_NO_TASK != p->task)
280       GNUNET_SCHEDULER_cancel (p->task);
281     if (NULL != p->ctx)
282       GNUNET_FS_publish_stop (p->ctx);
283     GNUNET_CONTAINER_DLL_remove (publish_head, publish_tail, p);
284     GNUNET_free (p);
285   }
286   while (NULL != (p = download_head))
287   {
288     if (GNUNET_SCHEDULER_NO_TASK != p->task)
289       GNUNET_SCHEDULER_cancel (p->task);
290     if (GNUNET_SCHEDULER_NO_TASK != p->stask)
291       GNUNET_SCHEDULER_cancel (p->stask);
292     if (NULL != p->ctx)
293       GNUNET_FS_download_stop (p->ctx, GNUNET_YES);
294     if (NULL != p->sctx)
295       GNUNET_FS_search_stop (p->sctx);
296     GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
297     GNUNET_free (p);
298   }
299   if (NULL != fs_handle)
300   {
301     GNUNET_FS_stop (fs_handle);
302     fs_handle = NULL;
303   }
304   if (NULL != stats_handle)
305   {
306     GNUNET_STATISTICS_destroy (stats_handle, GNUNET_YES);
307     stats_handle = NULL;
308   }
309 }
310
311
312 /**
313  * Task run when a publish operation should be stopped.
314  *
315  * @param cls the 'struct Pattern' of the publish operation to stop
316  * @param tc unused
317  */
318 static void
319 publish_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
320 {
321   struct Pattern *p = cls;
322
323   p->task = GNUNET_SCHEDULER_NO_TASK;
324   GNUNET_FS_publish_stop (p->ctx);
325 }
326
327
328 /**
329  * Task run when a download operation should be stopped.
330  *
331  * @param cls the 'struct Pattern' of the download operation to stop
332  * @param tc unused
333  */
334 static void
335 download_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
336 {
337   struct Pattern *p = cls;
338
339   p->task = GNUNET_SCHEDULER_NO_TASK;
340   GNUNET_FS_download_stop (p->ctx, GNUNET_YES);
341 }
342
343
344 /**
345  * Task run when a download operation should be stopped.
346  *
347  * @param cls the 'struct Pattern' of the download operation to stop
348  * @param tc unused
349  */
350 static void
351 search_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
352 {
353   struct Pattern *p = cls;
354
355   p->stask = GNUNET_SCHEDULER_NO_TASK;
356   GNUNET_FS_search_stop (p->sctx);
357 }
358
359
360 /**
361  * Notification of FS to a client about the progress of an
362  * operation.  Callbacks of this type will be used for uploads,
363  * downloads and searches.  Some of the arguments depend a bit
364  * in their meaning on the context in which the callback is used.
365  *
366  * @param cls closure
367  * @param info details about the event, specifying the event type
368  *        and various bits about the event
369  * @return client-context (for the next progress call
370  *         for this operation; should be set to NULL for
371  *         SUSPEND and STOPPED events).  The value returned
372  *         will be passed to future callbacks in the respective
373  *         field in the GNUNET_FS_ProgressInfo struct.
374  */
375 static void *
376 progress_cb (void *cls,
377              const struct GNUNET_FS_ProgressInfo *info)
378 {
379   struct Pattern *p;
380   const struct GNUNET_FS_Uri *uri;
381
382   switch (info->status)
383   {
384   case GNUNET_FS_STATUS_PUBLISH_START:
385   case GNUNET_FS_STATUS_PUBLISH_PROGRESS:
386     p = info->value.publish.cctx;
387     return p;
388   case GNUNET_FS_STATUS_PUBLISH_ERROR:
389     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
390                 "Publishing failed\n");
391     GNUNET_STATISTICS_update (stats_handle,
392                               "# failed publish operations", 1, GNUNET_NO);
393     p = info->value.publish.cctx;
394     p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
395     return p;
396   case GNUNET_FS_STATUS_PUBLISH_COMPLETED:
397     p = info->value.publish.cctx;
398     GNUNET_STATISTICS_update (stats_handle,
399                               "# publishing time (ms)", 
400                               (long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL, 
401                               GNUNET_NO);
402     p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
403     return p;
404   case GNUNET_FS_STATUS_PUBLISH_STOPPED:
405     p = info->value.publish.cctx;
406     p->ctx = NULL;
407     GNUNET_CONTAINER_DLL_remove (publish_head, publish_tail, p);
408     GNUNET_free (p);
409     return NULL;
410   case GNUNET_FS_STATUS_DOWNLOAD_START:
411   case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS:
412   case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE:
413   case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE:
414     p = info->value.download.cctx;
415     return p;
416   case GNUNET_FS_STATUS_DOWNLOAD_ERROR:
417     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
418                 "Download failed\n");
419     GNUNET_STATISTICS_update (stats_handle,
420                               "# failed downloads", 1, GNUNET_NO);
421     p = info->value.download.cctx;
422     p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
423     return p;
424   case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED:
425     p = info->value.download.cctx;
426     GNUNET_STATISTICS_update (stats_handle,
427                               "# download time (ms)", 
428                               (long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL, 
429                               GNUNET_NO); 
430     p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
431     return p;
432   case GNUNET_FS_STATUS_DOWNLOAD_STOPPED:
433     p = info->value.download.cctx;
434     p->ctx = NULL;
435     if (NULL == p->sctx)
436     {
437       GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
438       GNUNET_free (p);
439     }
440     return NULL;
441   case GNUNET_FS_STATUS_SEARCH_START:
442   case GNUNET_FS_STATUS_SEARCH_RESULT_NAMESPACE:
443     p = info->value.search.cctx;
444     return p;
445   case GNUNET_FS_STATUS_SEARCH_RESULT:
446     p = info->value.search.cctx;
447     uri = info->value.search.specifics.result.uri;
448     if (GNUNET_YES != GNUNET_FS_uri_test_chk (uri))
449       return NULL; /* not what we want */
450     if (p->y != GNUNET_FS_uri_chk_get_file_size (uri))
451       return NULL; /* not what we want */
452     GNUNET_STATISTICS_update (stats_handle,
453                               "# search time (ms)", 
454                               (long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL, 
455                               GNUNET_NO);
456     p->start_time = GNUNET_TIME_absolute_get ();
457     p->ctx = GNUNET_FS_download_start (fs_handle, uri,
458                                        NULL, NULL, NULL, 
459                                        0, GNUNET_FS_uri_chk_get_file_size (uri),
460                                        anonymity_level,
461                                        GNUNET_FS_DOWNLOAD_NO_TEMPORARIES,
462                                        p,
463                                        NULL);
464     p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
465     return NULL;
466   case GNUNET_FS_STATUS_SEARCH_UPDATE:
467   case GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED:
468     return NULL; /* don't care */
469   case GNUNET_FS_STATUS_SEARCH_ERROR:
470     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
471                 "Search failed\n");
472     GNUNET_STATISTICS_update (stats_handle,
473                               "# failed searches", 1, GNUNET_NO);
474     p = info->value.search.cctx;
475     p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
476     return p;
477   case GNUNET_FS_STATUS_SEARCH_STOPPED:
478     p = info->value.search.cctx;
479     p->sctx = NULL;
480     if (NULL == p->ctx)
481     {
482       GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
483       GNUNET_free (p);
484     }
485     return NULL;
486   default: 
487     /* unexpected event during profiling */
488     GNUNET_break (0);
489     return NULL;
490   }
491 }
492
493
494 /**
495  * Start publish operation.
496  *
497  * @param cls the 'struct Pattern' specifying the operation to perform
498  * @param tc scheduler context
499  */
500 static void 
501 start_publish (void *cls,
502                 const struct GNUNET_SCHEDULER_TaskContext *tc)
503 {
504   struct Pattern *p = cls;
505   struct GNUNET_FS_FileInformation *fi;
506
507   p->task = GNUNET_SCHEDULER_NO_TASK;
508   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
509     return;
510   fi = make_file (p->x, p->y, p);
511   p->start_time = GNUNET_TIME_absolute_get ();
512   p->ctx = GNUNET_FS_publish_start (fs_handle,
513                                     fi,
514                                     NULL, NULL, NULL,
515                                     GNUNET_FS_PUBLISH_OPTION_NONE);
516 }
517
518
519 /**
520  * Start download operation.
521  *
522  * @param cls the 'struct Pattern' specifying the operation to perform
523  * @param tc scheduler context
524  */
525 static void 
526 start_download (void *cls,
527                 const struct GNUNET_SCHEDULER_TaskContext *tc)
528 {
529   struct Pattern *p = cls;
530   struct GNUNET_FS_Uri *keywords;
531
532   p->task = GNUNET_SCHEDULER_NO_TASK;
533   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
534     return;
535   keywords = make_keywords (p->x);
536   p->start_time = GNUNET_TIME_absolute_get ();
537   p->sctx = GNUNET_FS_search_start (fs_handle, keywords,
538                                     anonymity_level,
539                                     GNUNET_FS_SEARCH_OPTION_NONE,
540                                     p);
541 }
542
543
544 /**
545  * @brief Main function that will be run by the scheduler.
546  *
547  * @param cls closure
548  * @param args remaining command-line arguments
549  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
550  * @param cfg_ configuration
551  */
552 static void
553 run (void *cls, char *const *args GNUNET_UNUSED,
554      const char *cfgfile GNUNET_UNUSED,
555      const struct GNUNET_CONFIGURATION_Handle *cfg_)
556 {
557   char myoptname[128];
558   struct Pattern *p;
559
560   cfg = cfg_;
561   /* Scheduled the task to clean up when shutdown is called */
562   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
563                                 NULL);
564
565   if (GNUNET_OK !=
566       GNUNET_CONFIGURATION_get_value_number (cfg, 
567                                              "TESTBED", "PEERID",
568                                              &my_peerid))
569   {
570     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
571                                "TESTBED", "PEERID");
572     global_ret = GNUNET_SYSERR;
573     GNUNET_SCHEDULER_shutdown ();
574     return;
575   }
576   if (GNUNET_OK !=
577       GNUNET_CONFIGURATION_get_value_number (cfg, 
578                                              "FSPROFILER", "ANONYMITY_LEVEL",
579                                              &anonymity_level))
580     anonymity_level = 1;
581   if (GNUNET_OK !=
582       GNUNET_CONFIGURATION_get_value_number (cfg, 
583                                              "FSPROFILER", "REPLICATION_LEVEL",
584                                              &replication_level))   
585     replication_level = 1;
586   GNUNET_snprintf (myoptname, sizeof (myoptname),
587                    "DOWNLOAD-PATTERN-%u", my_peerid);
588   if (GNUNET_OK !=
589       GNUNET_CONFIGURATION_get_value_string (cfg, 
590                                              "FSPROFILER", myoptname,
591                                              &download_pattern))   
592     download_pattern = GNUNET_strdup ("");
593   GNUNET_snprintf (myoptname, sizeof (myoptname),
594                    "PUBLISH-PATTERN-%u", my_peerid);
595   if (GNUNET_OK !=
596       GNUNET_CONFIGURATION_get_value_string (cfg, 
597                                              "FSPROFILER", myoptname,
598                                              &publish_pattern))   
599     publish_pattern = GNUNET_strdup ("");
600   if ( (GNUNET_OK !=
601         parse_pattern (&download_head,
602                        &download_tail,
603                        download_pattern)) ||
604        (GNUNET_OK !=
605         parse_pattern (&publish_head,
606                        &publish_tail,
607                        publish_pattern)) )
608   {
609     GNUNET_SCHEDULER_shutdown ();
610     return;
611   }
612
613   stats_handle = GNUNET_STATISTICS_create ("fsprofiler", cfg);
614   fs_handle =
615     GNUNET_FS_start (cfg,
616                      "fsprofiler",
617                      &progress_cb, NULL,
618                      GNUNET_FS_FLAGS_NONE,
619                      GNUNET_FS_OPTIONS_DOWNLOAD_PARALLELISM, 1,
620                      GNUNET_FS_OPTIONS_REQUEST_PARALLELISM, 1,
621                      GNUNET_FS_OPTIONS_END);
622   if (NULL == fs_handle)
623   {
624     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not acquire FS handle. Exiting.\n");
625     global_ret = GNUNET_SYSERR;
626     GNUNET_SCHEDULER_shutdown ();
627     return;
628   }
629   for (p = publish_head; NULL != p; p = p->next)
630     p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
631                                             &start_publish, p);
632   for (p = download_head; NULL != p; p = p->next)
633     p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
634                                             &start_download, p);
635 }
636
637
638 /**
639  * Program that performs various "random" FS activities.
640  *
641  * @param argc number of arguments from the command line
642  * @param argv command line arguments
643  * @return 0 ok, 1 on error
644  */
645 int
646 main (int argc, char *const *argv)
647 {
648   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
649     GNUNET_GETOPT_OPTION_END
650   };
651
652   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
653     return 2;
654   return (GNUNET_OK ==
655           GNUNET_PROGRAM_run (argc, argv, "gnunet-daemon-fsprofiler",
656                               gettext_noop
657                               ("Daemon to use file-sharing to measure its performance."),
658                               options, &run, NULL)) ? global_ret : 1;
659 }
660
661 /* end of gnunet-daemon-fsprofiler.c */