small API change: do no longer pass rarely needed GNUNET_SCHEDULER_TaskContext to...
[oweals/gnunet.git] / src / regex / gnunet-regex-simulation-profiler.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2011, 2012 GNUnet e.V.
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file regex/gnunet-regex-simulation-profiler.c
23  * @brief Regex profiler that dumps all DFAs into a database instead of
24  *        using the DHT (with cadet).
25  * @author Maximilian Szengel
26  *
27  */
28
29 #include "platform.h"
30 #include "gnunet_util_lib.h"
31 #include "regex_internal_lib.h"
32 #include "gnunet_mysql_lib.h"
33 #include <mysql/mysql.h>
34
35 /**
36  * MySQL statement to insert an edge.
37  */
38 #define INSERT_EDGE_STMT "INSERT IGNORE INTO `%s` "\
39                          "(`key`, `label`, `to_key`, `accepting`) "\
40                          "VALUES (?, ?, ?, ?);"
41
42 /**
43  * MySQL statement to select a key count.
44  */
45 #define SELECT_KEY_STMT "SELECT COUNT(*) FROM `%s` "\
46                         "WHERE `key` = ? AND `label` = ?;"
47
48 /**
49  * Simple struct to keep track of progress, and print a
50  * nice little percentage meter for long running tasks.
51  */
52 struct ProgressMeter
53 {
54   /**
55    * Total number of elements.
56    */
57   unsigned int total;
58
59   /**
60    * Intervall for printing percentage.
61    */
62   unsigned int modnum;
63
64   /**
65    * Number of dots to print.
66    */
67   unsigned int dotnum;
68
69   /**
70    * Completed number.
71    */
72   unsigned int completed;
73
74   /**
75    * Should the meter be printed?
76    */
77   int print;
78
79   /**
80    * String to print on startup.
81    */
82   char *startup_string;
83 };
84
85
86 /**
87  * Handle for the progress meter
88  */
89 static struct ProgressMeter *meter;
90
91 /**
92  * Abort task identifier.
93  */
94 static struct GNUNET_SCHEDULER_Task * abort_task;
95
96 /**
97  * Shutdown task identifier.
98  */
99 static struct GNUNET_SCHEDULER_Task * shutdown_task;
100
101 /**
102  * Scan task identifier;
103  */
104 static struct GNUNET_SCHEDULER_Task * scan_task;
105
106 /**
107  * Global testing status.
108  */
109 static int result;
110
111 /**
112  * MySQL context.
113  */
114 static struct GNUNET_MYSQL_Context *mysql_ctx;
115
116 /**
117  * MySQL prepared statement handle.
118  */
119 static struct GNUNET_MYSQL_StatementHandle *stmt_handle;
120
121 /**
122  * MySQL prepared statement handle for `key` select.
123  */
124 static struct GNUNET_MYSQL_StatementHandle *select_stmt_handle;
125
126 /**
127  * MySQL table name.
128  */
129 static char *table_name;
130
131 /**
132  * Policy dir containing files that contain policies.
133  */
134 static char *policy_dir;
135
136 /**
137  * Number of policy files.
138  */
139 static unsigned int num_policy_files;
140
141 /**
142  * Number of policies.
143  */
144 static unsigned int num_policies;
145
146 /**
147  * Maximal path compression length.
148  */
149 static unsigned int max_path_compression;
150
151 /**
152  * Number of merged transitions.
153  */
154 static unsigned long long num_merged_transitions;
155
156 /**
157  * Number of merged states from different policies.
158  */
159 static unsigned long long num_merged_states;
160
161 /**
162  * Prefix to add before every regex we're announcing.
163  */
164 static char *regex_prefix;
165
166
167 /**
168  * Create a meter to keep track of the progress of some task.
169  *
170  * @param total the total number of items to complete
171  * @param start_string a string to prefix the meter with (if printing)
172  * @param print GNUNET_YES to print the meter, GNUNET_NO to count
173  *              internally only
174  *
175  * @return the progress meter
176  */
177 static struct ProgressMeter *
178 create_meter (unsigned int total, char *start_string, int print)
179 {
180   struct ProgressMeter *ret;
181
182   ret = GNUNET_new (struct ProgressMeter);
183   ret->print = print;
184   ret->total = total;
185   ret->modnum = total / 4;
186   if (ret->modnum == 0)         /* Divide by zero check */
187     ret->modnum = 1;
188   ret->dotnum = (total / 50) + 1;
189   if (start_string != NULL)
190     ret->startup_string = GNUNET_strdup (start_string);
191   else
192     ret->startup_string = GNUNET_strdup ("");
193
194   return ret;
195 }
196
197
198 /**
199  * Update progress meter (increment by one).
200  *
201  * @param meter the meter to update and print info for
202  *
203  * @return GNUNET_YES if called the total requested,
204  *         GNUNET_NO if more items expected
205  */
206 static int
207 update_meter (struct ProgressMeter *meter)
208 {
209   if (meter->print == GNUNET_YES)
210   {
211     if (meter->completed % meter->modnum == 0)
212     {
213       if (meter->completed == 0)
214       {
215         FPRINTF (stdout, "%sProgress: [0%%", meter->startup_string);
216       }
217       else
218         FPRINTF (stdout, "%d%%",
219                  (int) (((float) meter->completed / meter->total) * 100));
220     }
221     else if (meter->completed % meter->dotnum == 0)
222       FPRINTF (stdout, "%s", ".");
223
224     if (meter->completed + 1 == meter->total)
225       FPRINTF (stdout, "%d%%]\n", 100);
226     fflush (stdout);
227   }
228   meter->completed++;
229
230   if (meter->completed == meter->total)
231     return GNUNET_YES;
232   if (meter->completed > meter->total)
233     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Progress meter overflow!!\n");
234   return GNUNET_NO;
235 }
236
237
238 /**
239  * Reset progress meter.
240  *
241  * @param meter the meter to reset
242  *
243  * @return GNUNET_YES if meter reset,
244  *         GNUNET_SYSERR on error
245  */
246 static int
247 reset_meter (struct ProgressMeter *meter)
248 {
249   if (meter == NULL)
250     return GNUNET_SYSERR;
251
252   meter->completed = 0;
253   return GNUNET_YES;
254 }
255
256
257 /**
258  * Release resources for meter
259  *
260  * @param meter the meter to free
261  */
262 static void
263 free_meter (struct ProgressMeter *meter)
264 {
265   GNUNET_free_non_null (meter->startup_string);
266   GNUNET_free (meter);
267 }
268
269
270 /**
271  * Shutdown task.
272  *
273  * @param cls NULL
274  */
275 static void
276 do_shutdown (void *cls)
277 {
278   shutdown_task = NULL;
279   if (NULL != abort_task)
280     GNUNET_SCHEDULER_cancel (abort_task);
281   if (NULL != mysql_ctx)
282     GNUNET_MYSQL_context_destroy (mysql_ctx);
283   if (NULL != meter)
284     free_meter (meter);
285
286   GNUNET_SCHEDULER_shutdown (); /* Stop scheduler to shutdown testbed run */
287 }
288
289
290 /**
291  * abort task to run on test timed out
292  *
293  * @param cls NULL
294  */
295 static void
296 do_abort (void *cls)
297 {
298   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Aborting\n");
299   abort_task = NULL;
300   GNUNET_SCHEDULER_cancel (scan_task);
301   scan_task = NULL;
302   result = GNUNET_SYSERR;
303   GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
304 }
305
306
307 /**
308  * Dummy function for prepared select. Always return GNUNET_OK.
309  *
310  * @param cls closure
311  * @param num_values number of values.
312  * @param values returned values from select stmt.
313  *
314  * @return GNUNET_OK
315  */
316 static int
317 return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
318 {
319   return GNUNET_OK;
320 }
321
322
323 /**
324  * Iterator over all states that inserts each state into the MySQL db.
325  *
326  * @param cls closure.
327  * @param key hash for current state.
328  * @param proof proof for current state.
329  * @param accepting GNUNET_YES if this is an accepting state, GNUNET_NO if not.
330  * @param num_edges number of edges leaving current state.
331  * @param edges edges leaving current state.
332  */
333 static void
334 regex_iterator (void *cls, const struct GNUNET_HashCode *key, const char *proof,
335                 int accepting, unsigned int num_edges,
336                 const struct REGEX_BLOCK_Edge *edges)
337 {
338   unsigned int i;
339   int result;
340   unsigned long k_length;
341   unsigned long e_length;
342   unsigned long d_length;
343   MYSQL_BIND rbind[1];
344   unsigned long long total;
345
346   GNUNET_assert (NULL != mysql_ctx);
347
348   for (i = 0; i < num_edges; i++)
349   {
350     k_length = sizeof (struct GNUNET_HashCode);
351     e_length = strlen (edges[i].label);
352     d_length = sizeof (struct GNUNET_HashCode);
353     memset (rbind, 0, sizeof (rbind));
354     total = -1;
355     rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
356     rbind[0].buffer = &total;
357     rbind[0].is_unsigned = GNUNET_YES;
358
359     result =
360         GNUNET_MYSQL_statement_run_prepared_select (mysql_ctx,
361                                                     select_stmt_handle, 1,
362                                                     rbind, &return_ok, NULL,
363                                                     MYSQL_TYPE_BLOB, key,
364                                                     sizeof (struct
365                                                             GNUNET_HashCode),
366                                                     &k_length,
367                                                     MYSQL_TYPE_STRING,
368                                                     edges[i].label,
369                                                     strlen (edges[i].label),
370                                                     &e_length, -1);
371
372     if (GNUNET_SYSERR == result)
373     {
374       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
375                   "Error executing prepared mysql select statement\n");
376       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
377       return;
378     }
379
380     if (-1 != total && total > 0)
381     {
382       GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Total: %llu (%s, %s)\n", total,
383                   GNUNET_h2s (key), edges[i].label);
384     }
385
386     result =
387         GNUNET_MYSQL_statement_run_prepared (mysql_ctx, stmt_handle, NULL,
388                                              MYSQL_TYPE_BLOB, key,
389                                              sizeof (struct GNUNET_HashCode),
390                                              &k_length, MYSQL_TYPE_STRING,
391                                              edges[i].label,
392                                              strlen (edges[i].label), &e_length,
393                                              MYSQL_TYPE_BLOB,
394                                              &edges[i].destination,
395                                              sizeof (struct GNUNET_HashCode),
396                                              &d_length, MYSQL_TYPE_LONG,
397                                              &accepting, GNUNET_YES, -1);
398
399     if (0 == result)
400     {
401       char *key_str = GNUNET_strdup (GNUNET_h2s (key));
402       char *to_key_str = GNUNET_strdup (GNUNET_h2s (&edges[i].destination));
403
404       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Merged (%s, %s, %s, %i)\n", key_str,
405                   edges[i].label, to_key_str, accepting);
406       GNUNET_free (key_str);
407       GNUNET_free (to_key_str);
408       num_merged_transitions++;
409     }
410     else if (-1 != total)
411     {
412       num_merged_states++;
413     }
414
415     if (GNUNET_SYSERR == result || (1 != result && 0 != result))
416     {
417       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
418                   "Error executing prepared mysql statement for edge: Affected rows: %i, expected 0 or 1!\n",
419                   result);
420       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
421     }
422   }
423
424   if (0 == num_edges)
425   {
426     k_length = sizeof (struct GNUNET_HashCode);
427     e_length = 0;
428     d_length = 0;
429
430     result =
431         GNUNET_MYSQL_statement_run_prepared (mysql_ctx, stmt_handle, NULL,
432                                              MYSQL_TYPE_BLOB, key,
433                                              sizeof (struct GNUNET_HashCode),
434                                              &k_length, MYSQL_TYPE_STRING, NULL,
435                                              0, &e_length, MYSQL_TYPE_BLOB,
436                                              NULL, 0, &d_length,
437                                              MYSQL_TYPE_LONG, &accepting,
438                                              GNUNET_YES, -1);
439
440     if (1 != result && 0 != result)
441     {
442       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
443                   "Error executing prepared mysql statement for edge: Affected rows: %i, expected 0 or 1!\n",
444                   result);
445       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
446     }
447   }
448 }
449
450
451 /**
452  * Announce a regex by creating the DFA and iterating over each state, inserting
453  * each state into a MySQL database.
454  *
455  * @param regex regular expression.
456  * @return GNUNET_OK on success, GNUNET_SYSERR on failure.
457  */
458 static int
459 announce_regex (const char *regex)
460 {
461   struct REGEX_INTERNAL_Automaton *dfa;
462
463   dfa =
464       REGEX_INTERNAL_construct_dfa (regex, strlen (regex), max_path_compression);
465
466   if (NULL == dfa)
467   {
468     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to create DFA for regex %s\n",
469                 regex);
470     abort_task = GNUNET_SCHEDULER_add_now (&do_abort, NULL);
471     return GNUNET_SYSERR;
472   }
473
474   REGEX_INTERNAL_iterate_all_edges (dfa, &regex_iterator, NULL);
475
476   REGEX_INTERNAL_automaton_destroy (dfa);
477
478   return GNUNET_OK;
479 }
480
481
482 /**
483  * Function called with a filename.
484  *
485  * @param cls closure
486  * @param filename complete filename (absolute path)
487  * @return GNUNET_OK to continue to iterate,
488  *  GNUNET_SYSERR to abort iteration with error!
489  */
490 static int
491 policy_filename_cb (void *cls, const char *filename)
492 {
493   char *regex;
494   char *data;
495   char *buf;
496   uint64_t filesize;
497   unsigned int offset;
498
499   GNUNET_assert (NULL != filename);
500
501   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Announcing regexes from file %s\n",
502               filename);
503
504   if (GNUNET_YES != GNUNET_DISK_file_test (filename))
505   {
506     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Could not find policy file %s\n",
507                 filename);
508     return GNUNET_OK;
509   }
510   if (GNUNET_OK !=
511       GNUNET_DISK_file_size (filename, &filesize, GNUNET_YES, GNUNET_YES))
512     filesize = 0;
513   if (0 == filesize)
514   {
515     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Policy file %s is empty.\n",
516                 filename);
517     return GNUNET_OK;
518   }
519   data = GNUNET_malloc (filesize);
520   if (filesize != GNUNET_DISK_fn_read (filename, data, filesize))
521   {
522     GNUNET_free (data);
523     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Could not read policy file %s.\n",
524                 filename);
525     return GNUNET_OK;
526   }
527
528   update_meter (meter);
529
530   buf = data;
531   offset = 0;
532   regex = NULL;
533   while (offset < (filesize - 1))
534   {
535     offset++;
536     if (((data[offset] == '\n')) && (buf != &data[offset]))
537     {
538       data[offset] = '|';
539       num_policies++;
540       buf = &data[offset + 1];
541     }
542     else if ((data[offset] == '\n') || (data[offset] == '\0'))
543       buf = &data[offset + 1];
544   }
545   data[offset] = '\0';
546   GNUNET_asprintf (&regex, "%s(%s)", regex_prefix, data);
547   GNUNET_assert (NULL != regex);
548   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Announcing regex: %s\n", regex);
549
550   if (GNUNET_OK != announce_regex (regex))
551   {
552     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not announce regex %s\n",
553                 regex);
554   }
555   GNUNET_free (regex);
556   GNUNET_free (data);
557   return GNUNET_OK;
558 }
559
560
561 /**
562  * Iterate over files contained in policy_dir.
563  *
564  * @param cls NULL
565  */
566 static void
567 do_directory_scan (void *cls)
568 {
569   struct GNUNET_TIME_Absolute start_time;
570   struct GNUNET_TIME_Relative duration;
571   char *stmt;
572
573   /* Create an MySQL prepared statement for the inserts */
574   GNUNET_asprintf (&stmt, INSERT_EDGE_STMT, table_name);
575   stmt_handle = GNUNET_MYSQL_statement_prepare (mysql_ctx, stmt);
576   GNUNET_free (stmt);
577
578   GNUNET_asprintf (&stmt, SELECT_KEY_STMT, table_name);
579   select_stmt_handle = GNUNET_MYSQL_statement_prepare (mysql_ctx, stmt);
580   GNUNET_free (stmt);
581
582   GNUNET_assert (NULL != stmt_handle);
583
584   meter =
585       create_meter (num_policy_files, "Announcing policy files\n", GNUNET_YES);
586   start_time = GNUNET_TIME_absolute_get ();
587   GNUNET_DISK_directory_scan (policy_dir, &policy_filename_cb, stmt_handle);
588   duration = GNUNET_TIME_absolute_get_duration (start_time);
589   reset_meter (meter);
590   free_meter (meter);
591   meter = NULL;
592
593   printf ("Announced %u files containing %u policies in %s\n"
594           "Duplicate transitions: %llu\nMerged states: %llu\n",
595           num_policy_files, num_policies,
596           GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_NO),
597           num_merged_transitions, num_merged_states);
598
599   result = GNUNET_OK;
600   shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
601 }
602
603
604 /**
605  * Main function that will be run by the scheduler.
606  *
607  * @param cls closure
608  * @param args remaining command-line arguments
609  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
610  * @param config configuration
611  */
612 static void
613 run (void *cls, char *const *args, const char *cfgfile,
614      const struct GNUNET_CONFIGURATION_Handle *config)
615 {
616   if (NULL == args[0])
617   {
618     fprintf (stderr,
619              _("No policy directory specified on command line. Exiting.\n"));
620     result = GNUNET_SYSERR;
621     return;
622   }
623   if (GNUNET_YES != GNUNET_DISK_directory_test (args[0], GNUNET_YES))
624   {
625     fprintf (stderr,
626              _("Specified policies directory does not exist. Exiting.\n"));
627     result = GNUNET_SYSERR;
628     return;
629   }
630   policy_dir = args[0];
631
632   num_policy_files = GNUNET_DISK_directory_scan (policy_dir, NULL, NULL);
633   meter = NULL;
634
635   if (NULL == table_name)
636   {
637     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
638                 "No table name specified, using default \"NFA\".\n");
639     table_name = "NFA";
640   }
641
642   mysql_ctx = GNUNET_MYSQL_context_create (config, "regex-mysql");
643   if (NULL == mysql_ctx)
644   {
645     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to create mysql context\n");
646     result = GNUNET_SYSERR;
647     return;
648   }
649
650   if (GNUNET_OK !=
651       GNUNET_CONFIGURATION_get_value_string (config, "regex-mysql",
652                                              "REGEX_PREFIX", &regex_prefix))
653   {
654     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
655                 _
656                 ("%s service is lacking key configuration settings (%s).  Exiting.\n"),
657                 "regexprofiler", "regex_prefix");
658     result = GNUNET_SYSERR;
659     return;
660   }
661
662
663   result = GNUNET_OK;
664
665   scan_task = GNUNET_SCHEDULER_add_now (&do_directory_scan, NULL);
666
667   /* Scheduled the task to clean up when shutdown is called */
668   shutdown_task =
669       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown,
670                                     NULL);
671 }
672
673
674 /**
675  * Main function.
676  *
677  * @param argc argument count
678  * @param argv argument values
679  * @return 0 on success
680  */
681 int
682 main (int argc, char *const *argv)
683 {
684   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
685     {'t', "table", "TABLENAME",
686      gettext_noop ("name of the table to write DFAs"),
687      1, &GNUNET_GETOPT_set_string, &table_name},
688     {'p', "max-path-compression", "MAX_PATH_COMPRESSION",
689      gettext_noop ("maximum path compression length"),
690      1, &GNUNET_GETOPT_set_uint, &max_path_compression},
691     GNUNET_GETOPT_OPTION_END
692   };
693   int ret;
694
695   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
696     return 2;
697
698   result = GNUNET_SYSERR;
699   ret =
700       GNUNET_PROGRAM_run (argc, argv,
701                           "gnunet-regex-simulationprofiler [OPTIONS] policy-dir",
702                           _("Profiler for regex library"), options, &run, NULL);
703   if (GNUNET_OK != ret)
704     return ret;
705   if (GNUNET_OK != result)
706     return 1;
707   return 0;
708 }