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