sensor: towards profiler
[oweals/gnunet.git] / src / sensor / gnunet-sensor-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C)
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 sensor/gnunet-sensor-profiler.c
23  * @brief Profiler for the sensor service
24  * @author Omar Tarabai
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_testbed_service.h"
29 #include "gnunet_peerstore_service.h"
30 #include "gnunet_sensor_service.h"
31 #include "gnunet_sensor_util_lib.h"
32
33 /**
34  * Time to wait for the peer to startup completely
35  */
36 #define PEER_STARTUP_TIME GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
37
38 /**
39  * Information about a single peer
40  */
41 struct PeerInfo
42 {
43
44   /**
45    * Peer Identity
46    */
47   struct GNUNET_PeerIdentity peer_id;
48
49   /**
50    * Testbed peer handle
51    */
52   struct GNUNET_TESTBED_Peer *testbed_peer;
53
54 };
55
56
57 /**
58  * Name of the configuration file used
59  */
60 static const char *cfg_filename = "gnunet-sensor-profiler.conf";
61
62 /**
63  * Directory to read sensor definitions from
64  */
65 static const char *sensor_src_dir = "sensors";
66
67 /**
68  * Directory to write new sensor definitions to
69  */
70 static const char *sensor_dst_dir = "/tmp/gnunet-sensor-profiler";
71
72 /**
73  * GNUnet configuration
74  */
75 struct GNUNET_CONFIGURATION_Handle *cfg;
76
77 /**
78  * Return value of the program
79  */
80 static int ok = 1;
81
82 /**
83  * Number of peers to run (Option -p)
84  */
85 static unsigned int num_peers = 0;
86
87 /**
88  * Set sensors running interval to this value (Option -i)
89  */
90 static unsigned int sensors_interval = 0;
91
92 /**
93  * Path to topology file (Option -t)
94  */
95 static char *topology_file;
96
97 /**
98  * Array of peer info for all peers
99  */
100 static struct PeerInfo *all_peers_info;
101
102 /**
103  * Number of peers that we already collected and start their info
104  */
105 static int peers_known = 0;
106
107 /**
108  * TESTBED operation connecting us to peerstore service on collection point
109  */
110 static struct GNUNET_TESTBED_Operation *peerstore_op;
111
112 /**
113  * Handle to peerstore service on collection point
114  */
115 static struct GNUNET_PEERSTORE_Handle *peerstore;
116
117 /**
118  * Dashboard service on collection point started?
119  */
120 static int dashboard_service_started = GNUNET_NO;
121
122 /**
123  * Number of peers started the sensor service successfully
124  */
125 static int sensor_services_started = 0;
126
127 /**
128  * Array of sensor names to be used for watching peerstore records
129  */
130 static char **sensor_names;
131
132 /**
133  * Size of 'sensor_names' array
134  */
135 static unsigned int sensor_names_size = 0;
136
137 /**
138  * Task run after any waiting period
139  */
140 static GNUNET_SCHEDULER_TaskIdentifier delayed_task = GNUNET_SCHEDULER_NO_TASK;
141
142
143 /**
144  * Copy directory recursively
145  *
146  * @param src Path to source directory
147  * @param dst Destination directory, will be created if it does not exist
148  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
149  */
150 static int
151 copy_dir (const char *src, const char *dst);
152
153
154 /**
155  * Do clean up and shutdown scheduler
156  */
157 static void
158 do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
159 {
160   int i;
161
162   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutting down.\n");
163   if (GNUNET_SCHEDULER_NO_TASK != delayed_task)
164   {
165     GNUNET_SCHEDULER_cancel (delayed_task);
166     delayed_task = GNUNET_SCHEDULER_NO_TASK;
167   }
168   if (NULL != cfg)
169   {
170     GNUNET_CONFIGURATION_destroy (cfg);
171     cfg = NULL;
172   }
173   if (NULL != sensor_names)
174   {
175     for (i = 0; i < sensor_names_size; i++)
176       GNUNET_free (sensor_names[i]);
177     GNUNET_array_grow (sensor_names, sensor_names_size, 0);
178   }
179   if (NULL != peerstore_op)
180   {
181     GNUNET_TESTBED_operation_done (peerstore_op);
182     peerstore_op = NULL;
183   }
184   if (NULL != all_peers_info)
185   {
186     GNUNET_free (all_peers_info);
187     all_peers_info = NULL;
188   }
189   GNUNET_SCHEDULER_shutdown ();
190 }
191
192
193 /**
194  * Function called with each file/folder inside a directory that is being copied.
195  *
196  * @param cls closure, destination directory
197  * @param filename complete filename (absolute path)
198  * @return #GNUNET_OK to continue to iterate.
199  *         #GNUNET_SYSERR to abort iteration with error
200  */
201 static int
202 copy_dir_scanner (void *cls, const char *filename)
203 {
204   char *dst_dir = cls;
205   char *dst;
206   int copy_result;
207
208   GNUNET_asprintf (&dst, "%s%s%s", dst_dir, DIR_SEPARATOR_STR,
209                    GNUNET_STRINGS_get_short_name (filename));
210   if (GNUNET_YES == GNUNET_DISK_directory_test (filename, GNUNET_YES))
211     copy_result = copy_dir (filename, dst);
212   else
213   {
214     if (GNUNET_YES == GNUNET_DISK_file_test (dst))
215       GNUNET_DISK_directory_remove (dst);
216     copy_result = GNUNET_DISK_file_copy (filename, dst);
217   }
218   GNUNET_free (dst);
219   return copy_result;
220 }
221
222
223 /**
224  * Copy directory recursively
225  *
226  * @param src Path to source directory
227  * @param dst Destination directory, will be created if it does not exist
228  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
229  */
230 static int
231 copy_dir (const char *src, const char *dst)
232 {
233   if (GNUNET_YES != GNUNET_DISK_directory_test (src, GNUNET_YES))
234     return GNUNET_SYSERR;
235   if (GNUNET_OK != GNUNET_DISK_directory_create (dst))
236     return GNUNET_SYSERR;
237   if (GNUNET_SYSERR ==
238       GNUNET_DISK_directory_scan (src, &copy_dir_scanner, (char *) dst))
239     return GNUNET_SYSERR;
240   return GNUNET_OK;
241 }
242
243
244 /**
245  * Function called with each file/folder inside source sensor directory.
246  *
247  * @param cls closure (unused)
248  * @param filename complete filename (absolute path)
249  * @return #GNUNET_OK to continue to iterate.
250  */
251 static int
252 sensor_dir_scanner (void *cls, const char *filename)
253 {
254   const char *file_basename;
255   char *dst_path;
256   struct GNUNET_CONFIGURATION_Handle *sensor_cfg;
257   char *sensor_name;
258
259   file_basename = GNUNET_STRINGS_get_short_name (filename);
260   GNUNET_asprintf (&dst_path, "%s%s%s", sensor_dst_dir, DIR_SEPARATOR_STR,
261                    file_basename);
262   if (GNUNET_YES == GNUNET_DISK_directory_test (filename, GNUNET_NO))
263   {
264     GNUNET_assert (GNUNET_OK == copy_dir (filename, dst_path));
265   }
266   else
267   {
268     sensor_name = GNUNET_strdup (file_basename);
269     GNUNET_array_append (sensor_names, sensor_names_size, sensor_name);
270     sensor_cfg = GNUNET_CONFIGURATION_create ();
271     GNUNET_assert (GNUNET_OK ==
272                    GNUNET_CONFIGURATION_parse (sensor_cfg, filename));
273     GNUNET_CONFIGURATION_set_value_string (sensor_cfg, file_basename,
274                                            "COLLECTION_POINT",
275                                            GNUNET_i2s_full (&all_peers_info
276                                                             [0].peer_id));
277     if (sensors_interval > 0)
278     {
279       GNUNET_CONFIGURATION_set_value_number (sensor_cfg, file_basename,
280                                              "INTERVAL",
281                                              (unsigned long long int)
282                                              sensors_interval);
283     }
284     GNUNET_CONFIGURATION_write (sensor_cfg, dst_path);
285     GNUNET_CONFIGURATION_destroy (sensor_cfg);
286   }
287   GNUNET_free (dst_path);
288   return GNUNET_OK;
289 }
290
291
292 /**
293  * Load sensor definitions and rewrite them to tmp location.
294  * Add collection point peer id and change running interval if needed.
295  */
296 static void
297 rewrite_sensors ()
298 {
299   GNUNET_assert (GNUNET_YES ==
300                  GNUNET_DISK_directory_test (sensor_src_dir, GNUNET_YES));
301   GNUNET_assert (GNUNET_OK == GNUNET_DISK_directory_create (sensor_dst_dir));
302   GNUNET_DISK_directory_scan (sensor_src_dir, &sensor_dir_scanner, NULL);
303 }
304
305
306 /**
307  * Callback to be called when dashboard service is started
308  *
309  * @param cls the callback closure from functions generating an operation
310  * @param op the operation that has been finished
311  * @param emsg error message in case the operation has failed; will be NULL if
312  *          operation has executed successfully.
313  */
314 static void
315 dashboard_started (void *cls, struct GNUNET_TESTBED_Operation *op,
316                    const char *emsg)
317 {
318   if (NULL != emsg)
319   {
320     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
321     GNUNET_assert (0);
322   }
323   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Dashboard service started.\n");
324   GNUNET_TESTBED_operation_done (op);
325   dashboard_service_started = GNUNET_YES;
326 }
327
328
329 /**
330  * Function called by PEERSTORE for each matching record.
331  *
332  * @param cls closure
333  * @param record peerstore record information
334  * @param emsg error message, or NULL if no errors
335  * @return #GNUNET_YES to continue iterating, #GNUNET_NO to stop
336  */
337 static int
338 peerstore_watch_cb (void *cls, struct GNUNET_PEERSTORE_Record *record,
339                     char *emsg)
340 {
341   struct PeerInfo *peer = cls;
342   struct GNUNET_SENSOR_DashboardAnomalyEntry *anomaly;
343
344   if (NULL != emsg)
345   {
346     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
347     GNUNET_assert (0);
348   }
349   GNUNET_assert (record->value_size ==
350                  sizeof (struct GNUNET_SENSOR_DashboardAnomalyEntry));
351   anomaly = record->value;
352   GNUNET_assert (0 ==
353                  GNUNET_CRYPTO_cmp_peer_identity (&peer->peer_id,
354                                                   record->peer));
355   printf ("Anomaly report:\n" "  Peer: `%s'\n" "  Sensor: `%s'\n"
356           "  Anomalous: `%d'\n" "  Anomalous neighbors: %f.\n\n",
357           GNUNET_i2s (&peer->peer_id), record->key, anomaly->anomalous,
358           anomaly->anomalous_neighbors);
359   return GNUNET_YES;
360 }
361
362
363 /**
364  * Callback to be called when peerstore service connect operation is completed
365  *
366  * @param cls the callback closure from functions generating an operation
367  * @param op the operation that has been finished
368  * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter()
369  * @param emsg error message in case the operation has failed; will be NULL if
370  *          operation has executed successfully.
371  */
372 static void
373 peerstore_connect_cb (void *cls, struct GNUNET_TESTBED_Operation *op,
374                       void *ca_result, const char *emsg)
375 {
376   int i;
377   int j;
378   struct PeerInfo *peer;
379
380   if (NULL != emsg)
381   {
382     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
383     GNUNET_assert (0);
384   }
385   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connected to peerstore service.\n");
386   /* Watch for anomaly reports from other peers */
387   for (i = 0; i < num_peers; i++)
388   {
389     peer = &all_peers_info[i];
390     for (j = 0; j < sensor_names_size; j++)
391     {
392       GNUNET_PEERSTORE_watch (peerstore, "sensordashboard-anomalies",
393                               &peer->peer_id, sensor_names[j],
394                               &peerstore_watch_cb, peer);
395     }
396   }
397 }
398
399
400 /**
401  * Adapter function called to establish a connection to peerstore service.
402  *
403  * @param cls closure
404  * @param cfg configuration of the peer to connect to; will be available until
405  *          GNUNET_TESTBED_operation_done() is called on the operation returned
406  *          from GNUNET_TESTBED_service_connect()
407  * @return service handle to return in 'op_result', NULL on error
408  */
409 static void *
410 peerstore_connect_adapter (void *cls,
411                            const struct GNUNET_CONFIGURATION_Handle *cfg)
412 {
413   peerstore = GNUNET_PEERSTORE_connect (cfg);
414   GNUNET_assert (NULL != peerstore);
415   return peerstore;
416 }
417
418
419 /**
420  * Adapter function called to destroy a connection to peerstore service.
421  *
422  * @param cls closure
423  * @param op_result service handle returned from the connect adapter
424  */
425 static void
426 peerstore_disconnect_adapter (void *cls, void *op_result)
427 {
428   GNUNET_PEERSTORE_disconnect (peerstore, GNUNET_NO);
429   peerstore = NULL;
430   peerstore_op = NULL;
431 }
432
433
434 /**
435  * This function is called after the estimated training period is over.
436  */
437 static void
438 simulate_anomalies (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
439 {
440   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
441               "Training period over, simulating anomalies now.\n");
442   //TODO
443 }
444
445
446 /**
447  * This function is called after a delay which ensures that all peers are
448  * properly initialized
449  */
450 static void
451 peers_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
452 {
453   unsigned long long int training_points;
454   struct GNUNET_TIME_Relative training_period;
455
456   delayed_task = GNUNET_SCHEDULER_NO_TASK;
457   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "All peers are ready.\n");
458   GNUNET_assert (GNUNET_OK ==
459                  GNUNET_CONFIGURATION_get_value_number (cfg,
460                                                         "sensor-model-gaussian",
461                                                         "TRAINING_WINDOW",
462                                                         &training_points));
463   training_period =
464       GNUNET_TIME_relative_multiply (GNUNET_TIME_relative_multiply
465                                      (GNUNET_TIME_UNIT_SECONDS,
466                                       (sensors_interval ==
467                                        0) ? 60 : sensors_interval),
468                                      training_points);
469   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
470               "Sleeping for a training period of %s.\n",
471               GNUNET_STRINGS_relative_time_to_string (training_period,
472                                                       GNUNET_NO));
473   delayed_task =
474       GNUNET_SCHEDULER_add_delayed (training_period, &simulate_anomalies, NULL);
475 }
476
477
478 /**
479  * Callback to be called when sensor service is started
480  *
481  * @param cls the callback closure from functions generating an operation
482  * @param op the operation that has been finished
483  * @param emsg error message in case the operation has failed; will be NULL if
484  *          operation has executed successfully.
485  */
486 static void
487 sensor_service_started (void *cls, struct GNUNET_TESTBED_Operation *op,
488                         const char *emsg)
489 {
490   struct PeerInfo *peer = cls;
491
492   if (NULL != emsg)
493   {
494     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
495     GNUNET_assert (0);
496   }
497   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sensor service started on peer `%s'.\n",
498               GNUNET_i2s (&peer->peer_id));
499   GNUNET_TESTBED_operation_done (op);
500   sensor_services_started++;
501   if (sensor_services_started == num_peers)
502   {
503     delayed_task =
504         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
505                                       (PEER_STARTUP_TIME, num_peers),
506                                       &peers_ready, NULL);
507   }
508 }
509
510
511 /**
512  * Callback to be called when the requested peer information is available
513  *
514  * @param cb_cls the closure from GNUNET_TETSBED_peer_get_information()
515  * @param op the operation this callback corresponds to
516  * @param pinfo the result; will be NULL if the operation has failed
517  * @param emsg error message if the operation has failed; will be NULL if the
518  *          operation is successfull
519  */
520 static void
521 peer_info_cb (void *cb_cls, struct GNUNET_TESTBED_Operation *op,
522               const struct GNUNET_TESTBED_PeerInformation *pinfo,
523               const char *emsg)
524 {
525   struct GNUNET_TESTBED_Peer *testbed_peer = cb_cls;
526   struct PeerInfo *peer = &all_peers_info[peers_known];
527
528   if (NULL != emsg)
529   {
530     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
531     GNUNET_assert (0);
532   }
533   peer->testbed_peer = testbed_peer;
534   GNUNET_CRYPTO_get_peer_identity (pinfo->result.cfg, &peer->peer_id);
535   peers_known++;
536   if (1 == peers_known)         /* First peer is collection point */
537   {
538     /* Rewrite sensors */
539     rewrite_sensors ();
540     /* Start dashboard */
541     GNUNET_TESTBED_peer_manage_service (NULL, testbed_peer, "sensordashboard",
542                                         &dashboard_started, NULL, 1);
543   }
544   /* Start sensor service on every peer */
545   GNUNET_TESTBED_peer_manage_service (NULL, testbed_peer, "sensor",
546                                       &sensor_service_started, peer, 1);
547   if (num_peers == peers_known) /* Last peer */
548   {
549     /* Connect to peerstore on first peer (collection point) */
550     peerstore_op =
551         GNUNET_TESTBED_service_connect (NULL, all_peers_info[0].testbed_peer,
552                                         "peerstore", &peerstore_connect_cb,
553                                         NULL, &peerstore_connect_adapter,
554                                         &peerstore_disconnect_adapter, NULL);
555   }
556   GNUNET_TESTBED_operation_done (op);
557 }
558
559
560 /**
561  * Signature of a main function for a testcase.
562  *
563  * @param cls closure
564  * @param h the run handle
565  * @param num number of peers in 'peers'
566  * @param peers handle to peers run in the testbed.  NULL upon timeout (see
567  *          GNUNET_TESTBED_test_run()).
568  * @param links_succeeded the number of overlay link connection attempts that
569  *          succeeded
570  * @param links_failed the number of overlay link connection attempts that
571  *          failed
572  * @see GNUNET_TESTBED_test_run()
573  */
574 static void
575 test_master (void *cls, struct GNUNET_TESTBED_RunHandle *h, unsigned int num,
576              struct GNUNET_TESTBED_Peer **peers, unsigned int links_succeeded,
577              unsigned int links_failed)
578 {
579   int i;
580
581   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
582               "%d peers started. %d links succeeded. %d links failed.\n",
583               num_peers, links_succeeded, links_failed);
584   GNUNET_assert (num == num_peers);
585   GNUNET_assert (0 == links_failed);
586   /* Collect peer information */
587   all_peers_info = GNUNET_new_array (num_peers, struct PeerInfo);
588
589   for (i = 0; i < num_peers; i++)
590   {
591     GNUNET_TESTBED_peer_get_information (peers[i],
592                                          GNUNET_TESTBED_PIT_CONFIGURATION,
593                                          &peer_info_cb, peers[i]);
594   }
595 }
596
597
598 /**
599  * Verify that the user passed correct CL args
600  *
601  * @return #GNUNET_OK if arguments are valid, #GNUNET_SYSERR otherwise
602  */
603 static int
604 verify_args ()
605 {
606   if (num_peers < 3)
607   {
608     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
609                 _
610                 ("Invalid or missing number of peers. Set at least 3 peers.\n"));
611     return GNUNET_SYSERR;
612   }
613   if (NULL == topology_file ||
614       GNUNET_YES != GNUNET_DISK_file_test (topology_file))
615   {
616     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
617                 _("Missing or invalid topology file.\n"));
618     return GNUNET_SYSERR;
619   }
620   return GNUNET_OK;
621 }
622
623
624 /**
625  * Actual main function.
626  *
627  * @param cls unused
628  * @param args remaining args, unused
629  * @param cfgfile name of the configuration
630  * @param cfg configuration handle
631  */
632 static void
633 run (void *cls, char *const *args, const char *cf,
634      const struct GNUNET_CONFIGURATION_Handle *c)
635 {
636   if (GNUNET_OK != verify_args ())
637   {
638     do_shutdown (NULL, NULL);
639     return;
640   }
641   cfg = GNUNET_CONFIGURATION_create ();
642   GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (cfg, cfg_filename));
643   GNUNET_CONFIGURATION_set_value_string ((struct GNUNET_CONFIGURATION_Handle *)
644                                          cfg, "TESTBED",
645                                          "OVERLAY_TOPOLOGY_FILE",
646                                          topology_file);
647   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown,
648                                 NULL);
649   GNUNET_TESTBED_run (NULL, cfg, num_peers, 0, NULL, NULL, &test_master, NULL);
650 }
651
652
653 /**
654  * Main function.
655  *
656  * @return 0 on success
657  */
658 int
659 main (int argc, char *const *argv)
660 {
661   static struct GNUNET_GETOPT_CommandLineOption options[] = {
662     {'p', "peers", "COUNT", gettext_noop ("Number of peers to run"), GNUNET_YES,
663      &GNUNET_GETOPT_set_uint, &num_peers},
664     {'t', "topology-file", "FILEPATH", gettext_noop ("Path to topology file"),
665      GNUNET_YES, &GNUNET_GETOPT_set_filename, &topology_file},
666     {'i', "sensors-interval", "INTERVAL",
667      gettext_noop ("Change the interval or running sensors to given value"),
668      GNUNET_YES, &GNUNET_GETOPT_set_uint, &sensors_interval},
669     GNUNET_GETOPT_OPTION_END
670   };
671
672   return (GNUNET_OK ==
673           GNUNET_PROGRAM_run (argc, argv, "gnunet-sensor-profiler",
674                               gettext_noop ("Profiler for sensor service"),
675                               options, &run, NULL)) ? ok : 1;
676 }
677
678 /* end of gnunet-sensor-profiler.c */