2b136efd6a4a27f92e44f691daa411d55e24400c
[oweals/gnunet.git] / src / regex / gnunet-regex-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011 - 2013 Christian Grothoff (and other contributing authors)
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 regex/gnunet-regex-profiler.c
23  * @brief Regex profiler for testing distributed regex use.
24  * @author Bartlomiej Polot
25  * @author Maximilian Szengel
26  *
27  */
28
29 #include <string.h>
30
31 #include "platform.h"
32 #include "gnunet_applications.h"
33 #include "gnunet_util_lib.h"
34 #include "gnunet_regex_lib.h"
35 #include "gnunet_arm_service.h"
36 #include "gnunet_dht_service.h"
37 #include "gnunet_testbed_service.h"
38
39 #define FIND_TIMEOUT \
40         GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 90)
41
42 /**
43  * DLL of operations
44  */
45 struct DLLOperation
46 {
47   /**
48    * The testbed operation handle
49    */
50   struct GNUNET_TESTBED_Operation *op;
51
52   /**
53    * Closure
54    */
55   void *cls;
56
57   /**
58    * The next pointer for DLL
59    */
60   struct DLLOperation *next;
61
62   /**
63    * The prev pointer for DLL
64    */
65   struct DLLOperation *prev;
66 };
67
68
69 /**
70  * Available states during profiling
71  */
72 enum State
73 {
74   /**
75    * Initial state
76    */
77   STATE_INIT = 0,
78
79   /**
80    * Starting slaves
81    */
82   STATE_SLAVES_STARTING,
83
84   /**
85    * Creating peers
86    */
87   STATE_PEERS_CREATING,
88
89   /**
90    * Starting peers
91    */
92   STATE_PEERS_STARTING,
93
94   /**
95    * Linking peers
96    */
97   STATE_PEERS_LINKING,
98
99   /**
100    * Matching strings against announced regexes
101    */
102   STATE_SEARCH_REGEX,
103
104   /**
105    * Destroying peers; we can do this as the controller takes care of stopping a
106    * peer if it is running
107    */
108   STATE_PEERS_DESTROYING
109 };
110
111
112 /**
113  * Peer handles.
114  */
115 struct RegexPeer
116 {
117   /**
118    * Peer id.
119    */
120   unsigned int id;
121
122   /**
123    * Peer configuration handle.
124    */
125   struct GNUNET_CONFIGURATION_Handle *cfg;
126
127   /**
128    * The actual testbed peer handle.
129    */
130   struct GNUNET_TESTBED_Peer *peer_handle;
131
132   /**
133    * Peer's search string.
134    */
135   const char *search_str;
136
137   /**
138    * Set to GNUNET_YES if the peer successfully matched the above
139    * search string. GNUNET_NO if the string could not be matched
140    * during the profiler run. GNUNET_SYSERR if the string matching
141    * timed out. Undefined if search_str is NULL
142    */
143   int search_str_matched;
144
145   /**
146    * Peer's DHT handle.
147    */
148   struct GNUNET_DHT_Handle *dht_handle;
149
150   /**
151    * Handle to a running regex search.
152    */
153    struct GNUNET_REGEX_search_handle *search_handle;
154
155   /**
156    * Testbed operation handle for DHT.
157    */
158   struct GNUNET_TESTBED_Operation *op_handle;
159
160   /**
161    * Peers's statistics handle.
162    */
163   struct GNUNET_STATISTICS_Handle *stats_handle;
164
165   /**
166    * Testbed operation handle for the statistics service.
167    */
168   struct GNUNET_TESTBED_Operation *stats_op_handle;
169
170   /**
171    * The starting time of a profiling step.
172    */
173   struct GNUNET_TIME_Absolute prof_start_time;
174
175   /**
176    * Operation timeout
177    */
178   GNUNET_SCHEDULER_TaskIdentifier timeout;
179
180   /**
181    * Deamon start
182    */
183   struct GNUNET_TESTBED_Operation *daemon_op;
184 };
185
186
187 /**
188  * The array of peers; we fill this as the peers are given to us by the testbed
189  */
190 static struct RegexPeer *peers;
191
192 /**
193  * Host registration handle
194  */
195 static struct GNUNET_TESTBED_HostRegistrationHandle *reg_handle;
196
197 /**
198  * Handle to the master controller process
199  */
200 static struct GNUNET_TESTBED_ControllerProc *mc_proc;
201
202 /**
203  * Handle to the master controller
204  */
205 static struct GNUNET_TESTBED_Controller *mc;
206
207 /**
208  * Handle to global configuration
209  */
210 static struct GNUNET_CONFIGURATION_Handle *cfg;
211
212 /**
213  * Abort task identifier
214  */
215 static GNUNET_SCHEDULER_TaskIdentifier abort_task;
216
217 /**
218  * Shutdown task identifier
219  */
220 static GNUNET_SCHEDULER_TaskIdentifier shutdown_task;
221
222 /**
223  * Host registration task identifier
224  */
225 static GNUNET_SCHEDULER_TaskIdentifier register_hosts_task;
226
227 /**
228  * Global event mask for all testbed events
229  */
230 static uint64_t event_mask;
231
232 /**
233  * The starting time of a profiling step
234  */
235 static struct GNUNET_TIME_Absolute prof_start_time;
236
237 /**
238  * Duration profiling step has taken
239  */
240 static struct GNUNET_TIME_Relative prof_time;
241
242 /**
243  * Number of peers to be started by the profiler
244  */
245 static unsigned int num_peers;
246
247 /**
248  * Global testing status
249  */
250 static int result;
251
252 /**
253  * current state of profiling
254  */
255 enum State state;
256
257 /**
258  * Folder where policy files are stored.
259  */
260 static char * policy_dir;
261
262 /**
263  * File with hostnames where to execute the test.
264  */
265 static char *hosts_file;
266
267 /**
268  * File with the strings to look for.
269  */
270 static char *strings_file;
271
272 /**
273  * Search strings.
274  */
275 static char **search_strings;
276
277 /**
278  * Number of search strings.
279  */
280 static int num_search_strings;
281
282 /**
283  * How many searches are we going to start in parallel
284  */
285 static long long unsigned int init_parallel_searches;
286
287 /**
288  * How many searches are running in parallel
289  */
290 static unsigned int parallel_searches;
291
292 /**
293  * Number of peers found with search strings.
294  */
295 static unsigned int peers_found;
296
297 /**
298  * Index of peer to start next announce/search.
299  */
300 static unsigned int next_search;
301
302 /**
303  * Search timeout task identifier.
304  */
305 static GNUNET_SCHEDULER_TaskIdentifier search_timeout_task;
306
307 /**
308  * Search timeout in seconds.
309  */
310 static struct GNUNET_TIME_Relative search_timeout_time = { 60000 };
311
312 /**
313  * File to log statistics to.
314  */
315 static struct GNUNET_DISK_FileHandle *data_file;
316
317 /**
318  * Filename to log statistics to.
319  */
320 static char *data_filename;
321
322 /**
323  * Prefix used for regex announcing. We need to prefix the search
324  * strings with it, in order to find something.
325  */
326 static char * regex_prefix;
327
328 /**
329  * What's the maximum regex reannounce period.
330  */
331 static struct GNUNET_TIME_Relative reannounce_period_max;
332
333
334 /******************************************************************************/
335 /******************************  DECLARATIONS  ********************************/
336 /******************************************************************************/
337
338 /**
339  * DHT connect callback.
340  *
341  * @param cls internal peer id.
342  * @param op operation handle.
343  * @param ca_result connect adapter result.
344  * @param emsg error message.
345  */
346 static void
347 dht_connect_cb (void *cls, struct GNUNET_TESTBED_Operation *op,
348                 void *ca_result, const char *emsg);
349
350 /**
351  * DHT connect adapter.
352  *
353  * @param cls not used.
354  * @param cfg configuration handle.
355  *
356  * @return
357  */
358 static void *
359 dht_ca (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg);
360
361
362 /**
363  * Adapter function called to destroy a connection to
364  * the DHT service
365  *
366  * @param cls closure
367  * @param op_result service handle returned from the connect adapter
368  */
369 static void
370 dht_da (void *cls, void *op_result);
371
372
373 /**
374  * Function called by testbed once we are connected to stats
375  * service. Get the statistics for the services of interest.
376  *
377  * @param cls the 'struct RegexPeer' for which we connected to stats
378  * @param op connect operation handle
379  * @param ca_result handle to stats service
380  * @param emsg error message on failure
381  */
382 static void
383 stats_connect_cb (void *cls,
384                   struct GNUNET_TESTBED_Operation *op,
385                   void *ca_result,
386                   const char *emsg);
387
388
389 /**
390  * Task to collect all statistics from s, will shutdown the
391  * profiler, when done.
392  *
393  * @param cls NULL
394  * @param tc the task context
395  */
396 static void
397 do_collect_stats (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
398
399
400 /**
401  * Start announcing the next regex in the DHT.
402  *
403  * @param cls Index of the next peer in the peers array.
404  * @param tc TaskContext.
405  */
406 static void
407 announce_next_regex (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
408
409
410 /******************************************************************************/
411 /********************************  SHUTDOWN  **********************************/
412 /******************************************************************************/
413
414
415 /**
416  * Shutdown nicely
417  *
418  * @param cls NULL
419  * @param tc the task context
420  */
421 static void
422 do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
423 {
424   struct RegexPeer *peer;
425   unsigned int peer_cnt;
426   unsigned int search_str_cnt;
427   char output_buffer[512];
428   size_t size;
429
430   shutdown_task = GNUNET_SCHEDULER_NO_TASK;
431   if (GNUNET_SCHEDULER_NO_TASK != abort_task)
432     GNUNET_SCHEDULER_cancel (abort_task);
433   if (GNUNET_SCHEDULER_NO_TASK != register_hosts_task)
434     GNUNET_SCHEDULER_cancel (register_hosts_task);
435
436   for (peer_cnt = 0; peer_cnt < num_peers; peer_cnt++)
437   {
438     peer = &peers[peer_cnt];
439
440     if (GNUNET_YES != peer->search_str_matched && NULL != data_file)
441     {
442       prof_time = GNUNET_TIME_absolute_get_duration (peer->prof_start_time);
443       size =
444         GNUNET_snprintf (output_buffer,
445                          sizeof (output_buffer),
446                          "%p Search string not found: %s (%d)\n"
447                          "%p On peer: %u (%p)\n"
448                          "%p After: %s\n",
449                          peer, peer->search_str, peer->search_str_matched,
450                          peer, peer->id, peer,
451                          peer,
452                          GNUNET_STRINGS_relative_time_to_string (prof_time,
453                                                                  GNUNET_NO));
454       if (size != GNUNET_DISK_file_write (data_file, output_buffer, size))
455         GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
456     }
457
458     if (NULL != peers[peer_cnt].op_handle)
459       GNUNET_TESTBED_operation_done (peers[peer_cnt].op_handle);
460     if (NULL != peers[peer_cnt].stats_op_handle)
461       GNUNET_TESTBED_operation_done (peers[peer_cnt].stats_op_handle);
462   }
463
464   if (NULL != data_file)
465     GNUNET_DISK_file_close (data_file);
466
467   for (search_str_cnt = 0;
468        search_str_cnt < num_search_strings && NULL != search_strings;
469        search_str_cnt++)
470   {
471     GNUNET_free_non_null (search_strings[search_str_cnt]);
472   }
473   GNUNET_free_non_null (search_strings);
474
475   if (NULL != reg_handle)
476     GNUNET_TESTBED_cancel_registration (reg_handle);
477
478   if (NULL != mc)
479     GNUNET_TESTBED_controller_disconnect (mc);
480   if (NULL != mc_proc)
481     GNUNET_TESTBED_controller_stop (mc_proc);
482   if (NULL != cfg)
483     GNUNET_CONFIGURATION_destroy (cfg);
484
485   GNUNET_SCHEDULER_shutdown (); /* Stop scheduler to shutdown testbed run */
486 }
487
488
489 /**
490  * abort task to run on test timed out
491  *
492  * @param cls NULL
493  * @param tc the task context
494  */
495 static void
496 do_abort (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
497 {
498   unsigned long i = (unsigned long) cls;
499
500   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Aborting from line %lu...\n", i);
501   abort_task = GNUNET_SCHEDULER_NO_TASK;
502   result = GNUNET_SYSERR;
503   if (GNUNET_SCHEDULER_NO_TASK != shutdown_task)
504     GNUNET_SCHEDULER_cancel (shutdown_task);
505   shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
506 }
507
508
509 /******************************************************************************/
510 /*********************  STATISTICS SERVICE CONNECTIONS  ***********************/
511 /******************************************************************************/
512
513 /**
514  * Adapter function called to establish a connection to
515  * statistics service.
516  *
517  * @param cls closure
518  * @param cfg configuration of the peer to connect to; will be available until
519  *          GNUNET_TESTBED_operation_done() is called on the operation returned
520  *          from GNUNET_TESTBED_service_connect()
521  * @return service handle to return in 'op_result', NULL on error
522  */
523 static void *
524 stats_ca (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg)
525 {
526   return GNUNET_STATISTICS_create ("<driver>", cfg);
527 }
528
529
530 /**
531  * Adapter function called to destroy a connection to
532  * statistics service.
533  *
534  * @param cls closure
535  * @param op_result service handle returned from the connect adapter
536  */
537 static void
538 stats_da (void *cls, void *op_result)
539 {
540   struct RegexPeer *peer = cls;
541
542   GNUNET_assert (op_result == peer->stats_handle);
543
544   GNUNET_STATISTICS_destroy (peer->stats_handle, GNUNET_NO);
545   peer->stats_handle = NULL;
546 }
547
548
549 /**
550  * Process statistic values. Write all values to global 'data_file', if present.
551  *
552  * @param cls closure
553  * @param subsystem name of subsystem that created the statistic
554  * @param name the name of the datum
555  * @param value the current value
556  * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not
557  * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration
558  */
559 static int
560 stats_iterator (void *cls, const char *subsystem, const char *name,
561                 uint64_t value, int is_persistent)
562 {
563   struct RegexPeer *peer = cls;
564   char output_buffer[512];
565   size_t size;
566
567   if (NULL == data_file)
568   {
569     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
570                 "%p -> %s [%s]: %llu\n",
571                 peer, subsystem, name, value);
572     return GNUNET_OK;
573   }
574   size =
575     GNUNET_snprintf (output_buffer,
576                      sizeof (output_buffer),
577                      "%p [%s] %llu %s\n",
578                      peer,
579                      subsystem, value, name);
580   if (size != GNUNET_DISK_file_write (data_file, output_buffer, size))
581     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
582
583   return GNUNET_OK;
584 }
585
586
587 /**
588  * Stats callback. Finish the stats testbed operation and when all stats have
589  * been iterated, shutdown the profiler.
590  *
591  * @param cls closure
592  * @param success GNUNET_OK if statistics were
593  *        successfully obtained, GNUNET_SYSERR if not.
594  */
595 static void
596 stats_cb (void *cls,
597           int success)
598 {
599   static unsigned int peer_cnt;
600   struct RegexPeer *peer = cls;
601
602   if (GNUNET_OK != success)
603   {
604     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
605                 "Getting statistics for peer %u failed!\n",
606                 peer->id);
607     return;
608   }
609
610   GNUNET_assert (NULL != peer->stats_op_handle);
611
612   GNUNET_TESTBED_operation_done (peer->stats_op_handle);
613   peer->stats_op_handle = NULL;
614
615   peer_cnt++;
616   peer = &peers[peer_cnt];
617
618   if (peer_cnt == num_peers)
619   {
620     struct GNUNET_TIME_Relative delay = { 100 };
621     shutdown_task = GNUNET_SCHEDULER_add_delayed (delay, &do_shutdown, NULL);
622   }
623   else
624   {
625     peer->stats_op_handle =
626       GNUNET_TESTBED_service_connect (NULL,
627                                       peer->peer_handle,
628                                       "statistics",
629                                       &stats_connect_cb,
630                                       peer,
631                                       &stats_ca,
632                                       &stats_da,
633                                       peer);
634   }
635 }
636
637
638 /**
639  * Function called by testbed once we are connected to stats
640  * service. Get the statistics for the services of interest.
641  *
642  * @param cls the 'struct RegexPeer' for which we connected to stats
643  * @param op connect operation handle
644  * @param ca_result handle to stats service
645  * @param emsg error message on failure
646  */
647 static void
648 stats_connect_cb (void *cls,
649                   struct GNUNET_TESTBED_Operation *op,
650                   void *ca_result,
651                   const char *emsg)
652 {
653   struct RegexPeer *peer = cls;
654
655   if (NULL == ca_result || NULL != emsg)
656   {
657     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
658                 "Failed to connect to statistics service on peer %u: %s\n",
659                 peer->id, emsg);
660
661     peer->stats_handle = NULL;
662     return;
663   }
664
665   peer->stats_handle = ca_result;
666
667   if (NULL == GNUNET_STATISTICS_get (peer->stats_handle, NULL, NULL,
668                                      GNUNET_TIME_UNIT_FOREVER_REL,
669                                      &stats_cb,
670                                      &stats_iterator, peer))
671   {
672     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
673                 "Could not get statistics of peer %u!\n", peer->id);
674   }
675 }
676
677
678 /**
679  * Task to collect all statistics from all peers, will shutdown the
680  * profiler, when done.
681  *
682  * @param cls NULL
683  * @param tc the task context
684  */
685 static void
686 do_collect_stats (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
687 {
688   struct RegexPeer *peer = &peers[0];
689
690   GNUNET_assert (NULL != peer->peer_handle);
691
692   peer->stats_op_handle =
693     GNUNET_TESTBED_service_connect (NULL,
694                                     peer->peer_handle,
695                                     "statistics",
696                                     &stats_connect_cb,
697                                     peer,
698                                     &stats_ca,
699                                     &stats_da,
700                                     peer);
701 }
702
703
704 /******************************************************************************/
705 /************************   REGEX FIND CONNECTIONS   **************************/
706 /******************************************************************************/
707
708
709 /**
710  * Start searching for the next string in the DHT.
711  *
712  * @param cls Index of the next peer in the peers array.
713  * @param tc TaskContext.
714  */
715 static void
716 find_string (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
717
718
719 /**
720  * Method called when we've found a peer that announced a regex
721  * that matches our search string. Now get the statistics.
722  *
723  * @param cls Closure provided in GNUNET_REGEX_search.
724  * @param id Peer providing a regex that matches the string.
725  * @param get_path Path of the get request.
726  * @param get_path_length Lenght of get_path.
727  * @param put_path Path of the put request.
728  * @param put_path_length Length of the put_path.
729  */
730 static void
731 regex_found_handler (void *cls,
732                      const struct GNUNET_PeerIdentity *id,
733                      const struct GNUNET_PeerIdentity *get_path,
734                      unsigned int get_path_length,
735                      const struct GNUNET_PeerIdentity *put_path,
736                      unsigned int put_path_length)
737 {
738   struct RegexPeer *peer = cls;
739   char output_buffer[512];
740   size_t size;
741
742   if (GNUNET_YES == peer->search_str_matched)
743   {
744     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
745                 "String %s on peer %u already matched!\n",
746                 peer->search_str, peer->id);
747     return;
748   }
749
750   peers_found++;
751   parallel_searches--;
752
753   if (GNUNET_SCHEDULER_NO_TASK != peer->timeout)
754   {
755     GNUNET_SCHEDULER_cancel (peer->timeout);
756     peer->timeout = GNUNET_SCHEDULER_NO_TASK;
757     GNUNET_SCHEDULER_add_now (&announce_next_regex, NULL);
758   }
759
760   if (NULL == id)
761   {
762     // FIXME not possible right now
763     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
764                 "String matching timed out for string %s on peer %u (%i/%i)\n",
765                 peer->search_str, peer->id, peers_found, num_search_strings);
766     peer->search_str_matched = GNUNET_SYSERR;
767   }
768   else
769   {
770     prof_time = GNUNET_TIME_absolute_get_duration (peer->prof_start_time);
771
772     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
773                 "String %s found on peer %u after %s (%i/%i) (%u||)\n",
774                 peer->search_str, peer->id,
775                 GNUNET_STRINGS_relative_time_to_string (prof_time, GNUNET_NO),
776                 peers_found, num_search_strings, parallel_searches);
777
778     peer->search_str_matched = GNUNET_YES;
779
780     if (NULL != data_file)
781     {
782       size =
783         GNUNET_snprintf (output_buffer,
784                          sizeof (output_buffer),
785                          "%p Peer: %u\n"
786                          "%p Search string: %s\n"
787                          "%p Search duration: %s\n\n",
788                          peer, peer->id,
789                          peer, peer->search_str,
790                          peer,
791                          GNUNET_STRINGS_relative_time_to_string (prof_time,
792                                                                  GNUNET_NO));
793
794       if (size != GNUNET_DISK_file_write (data_file, output_buffer, size))
795         GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
796     }
797   }
798
799   GNUNET_TESTBED_operation_done (peer->op_handle);
800   peer->op_handle = NULL;
801
802   if (peers_found == num_search_strings)
803   {
804     prof_time = GNUNET_TIME_absolute_get_duration (prof_start_time);
805     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
806                 "All strings successfully matched in %s\n",
807                 GNUNET_STRINGS_relative_time_to_string (prof_time, GNUNET_NO));
808
809     if (GNUNET_SCHEDULER_NO_TASK != search_timeout_task)
810       GNUNET_SCHEDULER_cancel (search_timeout_task);
811
812     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Collecting stats and shutting down.\n");
813     GNUNET_SCHEDULER_add_now (&do_collect_stats, NULL);
814   }
815 }
816
817
818 /**
819  * Connect by string timeout task. This will cancel the profiler after the
820  * specified timeout 'search_timeout'.
821  *
822  * @param cls NULL
823  * @param tc the task context
824  */
825 static void
826 search_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
827 {
828   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
829               "Finding matches to all strings did not succeed after %s.\n",
830               GNUNET_STRINGS_relative_time_to_string (search_timeout_time,
831                                                       GNUNET_NO));
832   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
833               "Found %i of %i strings\n", peers_found, num_search_strings);
834
835   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
836               "Search timed out after %s."
837               "Collecting stats and shutting down.\n", 
838               GNUNET_STRINGS_relative_time_to_string (search_timeout_time,
839                                                       GNUNET_NO));
840
841   GNUNET_SCHEDULER_add_now (&do_collect_stats, NULL);
842 }
843
844
845 /**
846  * Search timed out. It might still complete in the future,
847  * but we should start another one.
848  *
849  * @param cls Index of the next peer in the peers array.
850  * @param tc TaskContext.
851  */
852 static void
853 find_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
854 {
855   struct RegexPeer *p = cls;
856
857   p->timeout = GNUNET_SCHEDULER_NO_TASK;
858
859   if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0)
860     return;
861   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
862               "Searching for string \"%s\" on peer %d timed out. Starting new search.\n",
863               p->search_str,
864               p->id);
865   GNUNET_SCHEDULER_add_now (&announce_next_regex, NULL);
866 }
867
868
869 /**
870  * Start searching for a string in the DHT.
871  *
872  * @param cls Index of the next peer in the peers array.
873  * @param tc TaskContext.
874  */
875 static void
876 find_string (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
877 {
878   unsigned int search_peer = (unsigned int) (long) cls;
879
880   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) ||
881       search_peer >= num_search_strings)
882     return;
883
884   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
885               "Searching for string \"%s\" on peer %d (%u||)\n",
886               peers[search_peer].search_str,
887               search_peer,
888               parallel_searches);
889
890   peers[search_peer].op_handle =
891     GNUNET_TESTBED_service_connect (NULL,
892                                     peers[search_peer].peer_handle,
893                                     "dht",
894                                     &dht_connect_cb,
895                                     &peers[search_peer],
896                                     &dht_ca,
897                                     &dht_da,
898                                     &peers[search_peer]);
899   GNUNET_assert (NULL != peers[search_peer].op_handle);
900   peers[search_peer].timeout = GNUNET_SCHEDULER_add_delayed (FIND_TIMEOUT,
901                                                           &find_timeout,
902                                                           &peers[search_peer]);
903 }
904
905
906
907
908 /**
909  * Callback called when testbed has started the daemon we asked for.
910  *
911  * @param cls NULL
912  * @param op the operation handle
913  * @param emsg NULL on success; otherwise an error description
914  */
915 static void
916 daemon_started (void *cls, struct GNUNET_TESTBED_Operation *op,
917                 const char *emsg)
918 {
919   struct RegexPeer *peer = (struct RegexPeer *) cls;
920   unsigned long search_peer;
921   unsigned int i;
922   unsigned int me;
923
924   GNUNET_TESTBED_operation_done (peer->daemon_op);
925   peer->daemon_op = NULL;
926   me = peer - peers;
927   if (NULL != emsg)
928   {
929     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
930                 "Failed to start/stop daemon at peer %u: %s\n", me, emsg);
931     GNUNET_abort ();
932   }
933
934   /* Find a peer to look for a string matching the regex announced */
935   search_peer = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
936                                           num_peers);
937   for (i = 0; peers[search_peer].search_str != NULL; i++)
938   {
939     search_peer = (search_peer + 1) % num_peers;
940     if (i > num_peers)
941       GNUNET_abort (); /* we ran out of peers, must be a bug */
942   }
943   peers[search_peer].search_str = search_strings[me];
944   peers[search_peer].search_str_matched = GNUNET_NO;
945   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(
946                                   reannounce_period_max,
947                                   2),
948                                 &find_string,
949                                 (void *) search_peer);
950 }
951
952
953 /**
954  * Task to start the daemons on each peer so that the regexes are announced
955  * into the DHT.
956  *
957  * @param cls NULL
958  * @param tc the task context
959  */
960 static void
961 do_announce (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
962 {
963   unsigned int i;
964
965   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting announce.\n");
966
967   for (i = 0; i < init_parallel_searches; i++)
968   {
969     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
970                 "  scheduling announce %u\n",
971                 i);
972     (void) GNUNET_SCHEDULER_add_now (&announce_next_regex, NULL);
973   }
974 }
975
976
977 /**
978  * Start announcing the next regex in the DHT.
979  *
980  * @param cls Closure (unused).
981  * @param tc TaskContext.
982  */
983 static void
984 announce_next_regex (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
985 {
986   struct RegexPeer *peer;
987
988   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) ||
989             next_search >= num_peers)
990     return;
991
992   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting daemon %u\n", next_search);
993   peer = &peers[next_search];
994   peer->daemon_op = 
995   GNUNET_TESTBED_peer_manage_service (NULL,
996                                       peer->peer_handle,
997                                       "regexprofiler",
998                                       &daemon_started,
999                                       peer,
1000                                       1);
1001   next_search++;
1002   parallel_searches++;
1003 }
1004
1005 /**
1006  * DHT connect callback. Called when we are connected to the dht service for
1007  * the peer in 'cls'. If successfull we connect to the stats service of this
1008  * peer and then try to match the search string of this peer.
1009  *
1010  * @param cls internal peer id.
1011  * @param op operation handle.
1012  * @param ca_result connect adapter result.
1013  * @param emsg error message.
1014  */
1015 static void
1016 dht_connect_cb (void *cls, struct GNUNET_TESTBED_Operation *op,
1017                 void *ca_result, const char *emsg)
1018 {
1019   struct RegexPeer *peer = (struct RegexPeer *) cls;
1020
1021   if (NULL != emsg || NULL == op || NULL == ca_result)
1022   {
1023     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "DHT connect failed: %s\n", emsg);
1024     GNUNET_abort ();
1025   }
1026
1027   GNUNET_assert (NULL != peer->dht_handle);
1028   GNUNET_assert (peer->op_handle == op);
1029   GNUNET_assert (peer->dht_handle == ca_result);
1030
1031   peer->search_str_matched = GNUNET_NO;
1032   peer->search_handle = GNUNET_REGEX_search (peer->dht_handle,
1033                                              peer->search_str,
1034                                              &regex_found_handler, peer,
1035                                              NULL);
1036   peer->prof_start_time = GNUNET_TIME_absolute_get ();
1037 }
1038
1039
1040 /**
1041  * DHT connect adapter. Opens a connection to the dht service.
1042  *
1043  * @param cls Closure (peer).
1044  * @param cfg Configuration handle.
1045  *
1046  * @return
1047  */
1048 static void *
1049 dht_ca (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg)
1050 {
1051   struct RegexPeer *peer = cls;
1052
1053   peer->dht_handle = GNUNET_DHT_connect (cfg, 32);
1054
1055   return peer->dht_handle;
1056 }
1057
1058
1059 /**
1060  * Adapter function called to destroy a connection to the dht service.
1061  *
1062  * @param cls Closure (peer).
1063  * @param op_result Service handle returned from the connect adapter.
1064  */
1065 static void
1066 dht_da (void *cls, void *op_result)
1067 {
1068   struct RegexPeer *peer = (struct RegexPeer *) cls;
1069
1070   GNUNET_assert (peer->dht_handle == op_result);
1071
1072   if (NULL != peer->search_handle)
1073   {
1074     GNUNET_REGEX_search_cancel (peer->search_handle);
1075     peer->search_handle = NULL;
1076   }
1077
1078   if (NULL != peer->dht_handle)
1079   {
1080     GNUNET_DHT_disconnect (peer->dht_handle);
1081     peer->dht_handle = NULL;
1082   }
1083 }
1084
1085
1086 /**
1087  * Signature of a main function for a testcase.
1088  *
1089  * @param cls NULL
1090  * @param num_peers_ number of peers in 'peers'
1091  * @param peers handle to peers run in the testbed.  NULL upon timeout (see
1092  *          GNUNET_TESTBED_test_run()).
1093  * @param links_succeeded the number of overlay link connection attempts that
1094  *          succeeded
1095  * @param links_failed the number of overlay link connection attempts that
1096  *          failed
1097  */
1098 static void 
1099 test_master (void *cls,
1100              unsigned int num_peers_,
1101              struct GNUNET_TESTBED_Peer **testbed_peers,
1102              unsigned int links_succeeded,
1103              unsigned int links_failed)
1104 {
1105   unsigned int i;
1106
1107   GNUNET_assert (num_peers_ == num_peers);
1108
1109   prof_time = GNUNET_TIME_absolute_get_duration (prof_start_time);
1110   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1111               "Testbed started in %s\n",
1112               GNUNET_STRINGS_relative_time_to_string (prof_time, GNUNET_NO));
1113
1114   if (GNUNET_SCHEDULER_NO_TASK != abort_task)
1115   {
1116     GNUNET_SCHEDULER_cancel (abort_task);
1117     abort_task = GNUNET_SCHEDULER_NO_TASK;
1118   }
1119
1120   for (i = 0; i < num_peers; i++)
1121   {
1122     peers[i].peer_handle = testbed_peers[i];
1123   }
1124   GNUNET_SCHEDULER_add_now (&do_announce, NULL);
1125   search_timeout_task =
1126       GNUNET_SCHEDULER_add_delayed (search_timeout_time, &search_timeout, NULL);
1127 }
1128
1129 /**
1130  * Function that will be called whenever something in the testbed changes.
1131  *
1132  * @param cls closure, NULL
1133  * @param event information on what is happening
1134  */
1135 static void
1136 master_controller_cb (void *cls, 
1137                       const struct GNUNET_TESTBED_EventInformation *event)
1138 {
1139   switch (event->type)
1140   {
1141   case GNUNET_TESTBED_ET_CONNECT:
1142     printf(".");
1143     break;
1144   case GNUNET_TESTBED_ET_PEER_START:
1145     printf("#");
1146     break;
1147   default:
1148     break;
1149   }
1150   fflush(stdout);
1151 }
1152
1153
1154 /******************************************************************************/
1155 /***************************  TESTBED PEER SETUP  *****************************/
1156 /******************************************************************************/
1157
1158
1159 /**
1160  * Load search strings from given filename. One search string per line.
1161  *
1162  * @param filename filename of the file containing the search strings.
1163  * @param strings set of strings loaded from file. Caller needs to free this
1164  *                if number returned is greater than zero.
1165  * @param limit upper limit on the number of strings read from the file
1166  * @return number of strings found in the file. GNUNET_SYSERR on error.
1167  */
1168 static int
1169 load_search_strings (const char *filename, char ***strings, unsigned int limit)
1170 {
1171   char *data;
1172   char *buf;
1173   uint64_t filesize;
1174   unsigned int offset;
1175   int str_cnt;
1176   unsigned int i;
1177
1178   if (NULL == filename)
1179   {
1180     return GNUNET_SYSERR;
1181   }
1182
1183   if (GNUNET_YES != GNUNET_DISK_file_test (filename))
1184   {
1185     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1186                 "Could not find search strings file %s\n", filename);
1187     return GNUNET_SYSERR;
1188   }
1189   if (GNUNET_OK != GNUNET_DISK_file_size (filename, &filesize, GNUNET_YES, GNUNET_YES))
1190     filesize = 0;
1191   if (0 == filesize)
1192   {
1193     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Search strings file %s is empty.\n", filename);
1194     return GNUNET_SYSERR;
1195   }
1196   data = GNUNET_malloc (filesize);
1197   if (filesize != GNUNET_DISK_fn_read (filename, data, filesize))
1198   {
1199     GNUNET_free (data);
1200     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Could not read search strings file %s.\n",
1201          filename);
1202     return GNUNET_SYSERR;
1203   }
1204   buf = data;
1205   offset = 0;
1206   str_cnt = 0;
1207   while (offset < (filesize - 1) && str_cnt < limit)
1208   {
1209     offset++;
1210     if (((data[offset] == '\n')) && (buf != &data[offset]))
1211     {
1212       data[offset] = '\0';
1213       str_cnt++;
1214       buf = &data[offset + 1];
1215     }
1216     else if ((data[offset] == '\n') || (data[offset] == '\0'))
1217       buf = &data[offset + 1];
1218   }
1219   *strings = GNUNET_malloc (sizeof (char *) * str_cnt);
1220   offset = 0;
1221   for (i = 0; i < str_cnt; i++)
1222   {
1223     GNUNET_asprintf (&(*strings)[i], "%s%s", regex_prefix, &data[offset]);
1224     offset += strlen (&data[offset]) + 1;
1225   }
1226   GNUNET_free (data);
1227   return str_cnt;
1228 }
1229
1230
1231 /**
1232  * Main function that will be run by the scheduler.
1233  *
1234  * @param cls closure
1235  * @param args remaining command-line arguments
1236  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1237  * @param config configuration
1238  */
1239 static void
1240 run (void *cls, char *const *args, const char *cfgfile,
1241      const struct GNUNET_CONFIGURATION_Handle *config)
1242 {
1243   unsigned int nsearchstrs;
1244   unsigned int i;
1245   
1246   /* Check config */
1247   if (NULL == config)
1248   {
1249     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1250                 _("No configuration file given. Exiting\n"));
1251     shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
1252     return;
1253   }
1254   cfg = GNUNET_CONFIGURATION_dup (config);
1255   if (GNUNET_OK !=
1256       GNUNET_CONFIGURATION_get_value_string (cfg, "REGEXPROFILER",
1257                                              "REGEX_PREFIX",
1258                                              &regex_prefix))
1259   {
1260     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1261                 _("Configuration option \"regex_prefix\" missing. Exiting\n"));
1262     shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
1263     return;
1264   }
1265   if (GNUNET_OK !=
1266       GNUNET_CONFIGURATION_get_value_number (cfg, "REGEXPROFILER",
1267                                              "PARALLEL_SEARCHES",
1268                                              &init_parallel_searches))
1269   {
1270     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1271                 "Configuration option \"PARALLEL_SEARCHES\" missing."
1272                 " Using default (%d)\n", 10);
1273     init_parallel_searches = 10;
1274   }
1275   if (GNUNET_OK !=
1276       GNUNET_CONFIGURATION_get_value_time (cfg, "REGEXPROFILER",
1277                                            "REANNOUNCE_PERIOD_MAX",
1278                                            &reannounce_period_max))
1279   {
1280     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
1281                 "reannounce_period_max not given. Using 10 minutes.\n");
1282     reannounce_period_max =
1283       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 10);
1284   }
1285
1286   /* Check arguments */
1287   if (NULL == hosts_file)
1288   {
1289     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1290                 _("No hosts-file specified on command line. Exiting.\n"));
1291     return;
1292   }
1293   if (NULL == policy_dir)
1294   {
1295     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1296                 _("No policy directory specified on command line. Exiting.\n"));
1297     return;
1298   }
1299   if (GNUNET_YES != GNUNET_DISK_directory_test (policy_dir, GNUNET_YES))
1300   {
1301     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1302                 _("Specified policies directory does not exist. Exiting.\n"));
1303     shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
1304     return;
1305   }
1306   if (-1 == (num_peers = GNUNET_DISK_directory_scan (policy_dir, NULL, NULL)))
1307   {
1308     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1309                 _("No files found in `%s'\n"),
1310                 policy_dir);
1311     return;
1312   }
1313   GNUNET_CONFIGURATION_set_value_string (cfg, "REGEXPROFILER",
1314                                          "POLICY_DIR", policy_dir);
1315   if (GNUNET_YES != GNUNET_DISK_file_test (strings_file))
1316   {
1317     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1318                 _("No search strings file given. Exiting.\n"));
1319     shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
1320     return;
1321   }
1322   nsearchstrs = load_search_strings (strings_file,
1323                                      &search_strings,
1324                                      num_search_strings);
1325   if (num_search_strings != nsearchstrs)
1326   {
1327     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1328                 _("Error loading search strings."
1329                   "Given file does not contain enough strings. Exiting.\n"));
1330     shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
1331     return;
1332   }
1333   if (0 >= num_search_strings || NULL == search_strings)
1334   {
1335     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1336                 _("Error loading search strings. Exiting.\n"));
1337     shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
1338     return;
1339   }
1340   for (i = 0; i < num_search_strings; i++)
1341     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1342                 "search string: %s\n",
1343                 search_strings[i]);
1344
1345   /* Check logfile */
1346   if ( (NULL != data_filename) &&
1347        (NULL == (data_file =
1348                  GNUNET_DISK_file_open (data_filename,
1349                                         GNUNET_DISK_OPEN_READWRITE |
1350                                         GNUNET_DISK_OPEN_TRUNCATE |
1351                                         GNUNET_DISK_OPEN_CREATE,
1352                                         GNUNET_DISK_PERM_USER_READ |
1353                                         GNUNET_DISK_PERM_USER_WRITE))) )
1354   {
1355     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
1356                               "open",
1357                               data_filename);
1358     return;
1359   }
1360
1361   /* Initialize peers */
1362   peers = GNUNET_malloc (sizeof (struct RegexPeer) * num_peers);
1363   for (i = 0; i < num_peers; i++)
1364   {
1365     peers[i].id = i;
1366   }
1367
1368   event_mask = 0LL;
1369 /* For feedback about the start process activate these and pass master_cb */
1370   event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_START);
1371 //   event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_STOP);
1372   event_mask |= (1LL << GNUNET_TESTBED_ET_CONNECT);
1373 //   event_mask |= (1LL << GNUNET_TESTBED_ET_DISCONNECT);
1374   prof_start_time = GNUNET_TIME_absolute_get ();
1375   GNUNET_TESTBED_run (hosts_file,
1376                       cfg,
1377                       num_peers,
1378                       event_mask,
1379                       &master_controller_cb,
1380                       NULL,     /* master_controller_cb cls */
1381                       &test_master,
1382                       NULL);    /* test_master cls */
1383   abort_task =
1384       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
1385                                     (GNUNET_TIME_UNIT_MINUTES, 15),
1386                                     &do_abort,
1387                                     (void*) __LINE__);
1388 }
1389
1390
1391 /**
1392  * Main function.
1393  *
1394  * @param argc argument count
1395  * @param argv argument values
1396  * @return 0 on success
1397  */
1398 int
1399 main (int argc, char *const *argv)
1400 {
1401   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
1402     {'o', "output-file", "FILENAME",
1403      gettext_noop ("name of the file for writing statistics"),
1404      1, &GNUNET_GETOPT_set_string, &data_filename},
1405     {'t', "matching-timeout", "TIMEOUT",
1406       gettext_noop ("wait TIMEOUT before considering a string match as failed"),
1407       GNUNET_YES, &GNUNET_GETOPT_set_relative_time, &search_timeout_time },
1408     {'n', "num-search-strings", "COUNT",
1409       gettext_noop ("number of search strings to read from search strings file"),
1410       GNUNET_YES, &GNUNET_GETOPT_set_uint, &num_search_strings },
1411     {'p', "policy-dir", "DIRECTORY",
1412       gettext_noop ("directory with policy files"),
1413       GNUNET_YES, &GNUNET_GETOPT_set_filename, &policy_dir },
1414     {'s', "strings-file", "FILENAME",
1415       gettext_noop ("name of file with input strings"),
1416       GNUNET_YES, &GNUNET_GETOPT_set_filename, &strings_file },
1417     {'H', "hosts-file", "FILENAME",
1418       gettext_noop ("name of file with hosts' names"),
1419       GNUNET_NO, &GNUNET_GETOPT_set_filename, &hosts_file },
1420     GNUNET_GETOPT_OPTION_END
1421   };
1422   int ret;
1423
1424   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
1425     return 2;
1426   result = GNUNET_SYSERR;
1427   ret =
1428       GNUNET_PROGRAM_run (argc, argv,
1429                           "gnunet-regex-profiler",
1430                           _("Profiler for regex"),
1431                           options, &run, NULL);
1432   if (GNUNET_OK != ret)
1433     return ret;
1434   if (GNUNET_OK != result)
1435     return 1;
1436   return 0;
1437 }