missing fi
[oweals/gnunet.git] / src / regex / gnunet-regex-simulation-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011, 2012 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-simulation-profiler.c
23  * @brief Regex profiler that dumps all DFAs into a database instead of
24  *        using the DHT (with mesh).
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 GNUNET_SCHEDULER_TaskIdentifier abort_task;
95
96 /**
97  * Shutdown task identifier.
98  */
99 static GNUNET_SCHEDULER_TaskIdentifier shutdown_task;
100
101 /**
102  * Scan task identifier;
103  */
104 static GNUNET_SCHEDULER_TaskIdentifier 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_malloc (sizeof (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  * @param tc the task context
275  */
276 static void
277 do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
278 {
279   shutdown_task = GNUNET_SCHEDULER_NO_TASK;
280   if (GNUNET_SCHEDULER_NO_TASK != abort_task)
281     GNUNET_SCHEDULER_cancel (abort_task);
282   if (NULL != mysql_ctx)
283     GNUNET_MYSQL_context_destroy (mysql_ctx);
284   if (NULL != meter)
285     free_meter (meter);
286
287   GNUNET_SCHEDULER_shutdown (); /* Stop scheduler to shutdown testbed run */
288 }
289
290
291 /**
292  * abort task to run on test timed out
293  *
294  * @param cls NULL
295  * @param tc the task context
296  */
297 static void
298 do_abort (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
299 {
300   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Aborting\n");
301   abort_task = GNUNET_SCHEDULER_NO_TASK;
302   GNUNET_SCHEDULER_cancel (scan_task);
303   scan_task = GNUNET_SCHEDULER_NO_TASK;
304   result = GNUNET_SYSERR;
305   GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
306 }
307
308
309 /**
310  * Dummy function for prepared select. Always return GNUNET_OK.
311  *
312  * @param cls closure
313  * @param num_values number of values.
314  * @param values returned values from select stmt.
315  *
316  * @return GNUNET_OK
317  */
318 static int
319 return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
320 {
321   return GNUNET_OK;
322 }
323
324
325 /**
326  * Iterator over all states that inserts each state into the MySQL db.
327  *
328  * @param cls closure.
329  * @param key hash for current state.
330  * @param proof proof for current state.
331  * @param accepting GNUNET_YES if this is an accepting state, GNUNET_NO if not.
332  * @param num_edges number of edges leaving current state.
333  * @param edges edges leaving current state.
334  */
335 static void
336 regex_iterator (void *cls, const struct GNUNET_HashCode *key, const char *proof,
337                 int accepting, unsigned int num_edges,
338                 const struct REGEX_BLOCK_Edge *edges)
339 {
340   unsigned int i;
341   int result;
342   unsigned long k_length;
343   unsigned long e_length;
344   unsigned long d_length;
345   MYSQL_BIND rbind[1];
346   unsigned long long total;
347
348   GNUNET_assert (NULL != mysql_ctx);
349
350   for (i = 0; i < num_edges; i++)
351   {
352     k_length = sizeof (struct GNUNET_HashCode);
353     e_length = strlen (edges[i].label);
354     d_length = sizeof (struct GNUNET_HashCode);
355     memset (rbind, 0, sizeof (rbind));
356     total = -1;
357     rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
358     rbind[0].buffer = &total;
359     rbind[0].is_unsigned = GNUNET_YES;
360
361     result =
362         GNUNET_MYSQL_statement_run_prepared_select (mysql_ctx,
363                                                     select_stmt_handle, 1,
364                                                     rbind, &return_ok, NULL,
365                                                     MYSQL_TYPE_BLOB, key,
366                                                     sizeof (struct
367                                                             GNUNET_HashCode),
368                                                     &k_length,
369                                                     MYSQL_TYPE_STRING,
370                                                     edges[i].label,
371                                                     strlen (edges[i].label),
372                                                     &e_length, -1);
373
374     if (GNUNET_SYSERR == result)
375     {
376       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
377                   "Error executing prepared mysql select statement\n");
378       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
379       return;
380     }
381
382     if (-1 != total && total > 0)
383     {
384       GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Total: %llu (%s, %s)\n", total,
385                   GNUNET_h2s (key), edges[i].label);
386     }
387
388     result =
389         GNUNET_MYSQL_statement_run_prepared (mysql_ctx, stmt_handle, NULL,
390                                              MYSQL_TYPE_BLOB, key,
391                                              sizeof (struct GNUNET_HashCode),
392                                              &k_length, MYSQL_TYPE_STRING,
393                                              edges[i].label,
394                                              strlen (edges[i].label), &e_length,
395                                              MYSQL_TYPE_BLOB,
396                                              &edges[i].destination,
397                                              sizeof (struct GNUNET_HashCode),
398                                              &d_length, MYSQL_TYPE_LONG,
399                                              &accepting, GNUNET_YES, -1);
400
401     if (0 == result)
402     {
403       char *key_str = GNUNET_strdup (GNUNET_h2s (key));
404       char *to_key_str = GNUNET_strdup (GNUNET_h2s (&edges[i].destination));
405
406       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Merged (%s, %s, %s, %i)\n", key_str,
407                   edges[i].label, to_key_str, accepting);
408       GNUNET_free (key_str);
409       GNUNET_free (to_key_str);
410       num_merged_transitions++;
411     }
412     else if (-1 != total)
413     {
414       num_merged_states++;
415     }
416
417     if (GNUNET_SYSERR == result || (1 != result && 0 != result))
418     {
419       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
420                   "Error executing prepared mysql statement for edge: Affected rows: %i, expected 0 or 1!\n",
421                   result);
422       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
423     }
424   }
425
426   if (0 == num_edges)
427   {
428     k_length = sizeof (struct GNUNET_HashCode);
429     e_length = 0;
430     d_length = 0;
431
432     result =
433         GNUNET_MYSQL_statement_run_prepared (mysql_ctx, stmt_handle, NULL,
434                                              MYSQL_TYPE_BLOB, key,
435                                              sizeof (struct GNUNET_HashCode),
436                                              &k_length, MYSQL_TYPE_STRING, NULL,
437                                              0, &e_length, MYSQL_TYPE_BLOB,
438                                              NULL, 0, &d_length,
439                                              MYSQL_TYPE_LONG, &accepting,
440                                              GNUNET_YES, -1);
441
442     if (1 != result && 0 != result)
443     {
444       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
445                   "Error executing prepared mysql statement for edge: Affected rows: %i, expected 0 or 1!\n",
446                   result);
447       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
448     }
449   }
450 }
451
452
453 /**
454  * Announce a regex by creating the DFA and iterating over each state, inserting
455  * each state into a MySQL database.
456  *
457  * @param regex regular expression.
458  * @return GNUNET_OK on success, GNUNET_SYSERR on failure.
459  */
460 static int
461 announce_regex (const char *regex)
462 {
463   struct REGEX_INTERNAL_Automaton *dfa;
464
465   dfa =
466       REGEX_INTERNAL_construct_dfa (regex, strlen (regex), max_path_compression);
467
468   if (NULL == dfa)
469   {
470     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to create DFA for regex %s\n",
471                 regex);
472     abort_task = GNUNET_SCHEDULER_add_now (&do_abort, NULL);
473     return GNUNET_SYSERR;
474   }
475
476   REGEX_INTERNAL_iterate_all_edges (dfa, &regex_iterator, NULL);
477
478   REGEX_INTERNAL_automaton_destroy (dfa);
479
480   return GNUNET_OK;
481 }
482
483
484 /**
485  * Function called with a filename.
486  *
487  * @param cls closure
488  * @param filename complete filename (absolute path)
489  * @return GNUNET_OK to continue to iterate,
490  *  GNUNET_SYSERR to abort iteration with error!
491  */
492 static int
493 policy_filename_cb (void *cls, const char *filename)
494 {
495   char *regex;
496   char *data;
497   char *buf;
498   uint64_t filesize;
499   unsigned int offset;
500
501   GNUNET_assert (NULL != filename);
502
503   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Announcing regexes from file %s\n",
504               filename);
505
506   if (GNUNET_YES != GNUNET_DISK_file_test (filename))
507   {
508     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Could not find policy file %s\n",
509                 filename);
510     return GNUNET_OK;
511   }
512   if (GNUNET_OK !=
513       GNUNET_DISK_file_size (filename, &filesize, GNUNET_YES, GNUNET_YES))
514     filesize = 0;
515   if (0 == filesize)
516   {
517     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Policy file %s is empty.\n",
518                 filename);
519     return GNUNET_OK;
520   }
521   data = GNUNET_malloc (filesize);
522   if (filesize != GNUNET_DISK_fn_read (filename, data, filesize))
523   {
524     GNUNET_free (data);
525     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Could not read policy file %s.\n",
526                 filename);
527     return GNUNET_OK;
528   }
529
530   update_meter (meter);
531
532   buf = data;
533   offset = 0;
534   regex = NULL;
535   while (offset < (filesize - 1))
536   {
537     offset++;
538     if (((data[offset] == '\n')) && (buf != &data[offset]))
539     {
540       data[offset] = '|';
541       num_policies++;
542       buf = &data[offset + 1];
543     }
544     else if ((data[offset] == '\n') || (data[offset] == '\0'))
545       buf = &data[offset + 1];
546   }
547   data[offset] = '\0';
548   GNUNET_asprintf (&regex, "%s(%s)", regex_prefix, data);
549   GNUNET_assert (NULL != regex);
550   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Announcing regex: %s\n", regex);
551
552   if (GNUNET_OK != announce_regex (regex))
553   {
554     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not announce regex %s\n",
555                 regex);
556   }
557   GNUNET_free (regex);
558   GNUNET_free (data);
559   return GNUNET_OK;
560 }
561
562
563 /**
564  * Iterate over files contained in policy_dir.
565  *
566  * @param cls NULL
567  * @param tc the task context
568  */
569 static void
570 do_directory_scan (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
571 {
572   struct GNUNET_TIME_Absolute start_time;
573   struct GNUNET_TIME_Relative duration;
574   char *stmt;
575
576   /* Create an MySQL prepared statement for the inserts */
577   GNUNET_asprintf (&stmt, INSERT_EDGE_STMT, table_name);
578   stmt_handle = GNUNET_MYSQL_statement_prepare (mysql_ctx, stmt);
579   GNUNET_free (stmt);
580
581   GNUNET_asprintf (&stmt, SELECT_KEY_STMT, table_name);
582   select_stmt_handle = GNUNET_MYSQL_statement_prepare (mysql_ctx, stmt);
583   GNUNET_free (stmt);
584
585   GNUNET_assert (NULL != stmt_handle);
586
587   meter =
588       create_meter (num_policy_files, "Announcing policy files\n", GNUNET_YES);
589   start_time = GNUNET_TIME_absolute_get ();
590   GNUNET_DISK_directory_scan (policy_dir, &policy_filename_cb, 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, num_policies,
599           GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_NO),
600           num_merged_transitions, num_merged_states);
601
602   result = GNUNET_OK;
603   shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
604 }
605
606
607 /**
608  * Main function that will be run by the scheduler.
609  *
610  * @param cls closure
611  * @param args remaining command-line arguments
612  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
613  * @param config configuration
614  */
615 static void
616 run (void *cls, char *const *args, const char *cfgfile,
617      const struct GNUNET_CONFIGURATION_Handle *config)
618 {
619   if (NULL == args[0])
620   {
621     fprintf (stderr,
622              _("No policy directory specified on command line. Exiting.\n"));
623     result = GNUNET_SYSERR;
624     return;
625   }
626   if (GNUNET_YES != GNUNET_DISK_directory_test (args[0], GNUNET_YES))
627   {
628     fprintf (stderr,
629              _("Specified policies directory does not exist. Exiting.\n"));
630     result = GNUNET_SYSERR;
631     return;
632   }
633   policy_dir = args[0];
634
635   num_policy_files = GNUNET_DISK_directory_scan (policy_dir, NULL, NULL);
636   meter = NULL;
637
638   if (NULL == table_name)
639   {
640     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
641                 "No table name specified, using default \"NFA\".\n");
642     table_name = "NFA";
643   }
644
645   mysql_ctx = GNUNET_MYSQL_context_create (config, "regex-mysql");
646   if (NULL == mysql_ctx)
647   {
648     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to create mysql context\n");
649     result = GNUNET_SYSERR;
650     return;
651   }
652
653   if (GNUNET_OK !=
654       GNUNET_CONFIGURATION_get_value_string (config, "regex-mysql",
655                                              "REGEX_PREFIX", &regex_prefix))
656   {
657     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
658                 _
659                 ("%s service is lacking key configuration settings (%s).  Exiting.\n"),
660                 "regexprofiler", "regex_prefix");
661     result = GNUNET_SYSERR;
662     return;
663   }
664
665
666   result = GNUNET_OK;
667
668   scan_task = GNUNET_SCHEDULER_add_now (&do_directory_scan, NULL);
669
670   /* Scheduled the task to clean up when shutdown is called */
671   shutdown_task =
672       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown,
673                                     NULL);
674 }
675
676
677 /**
678  * Main function.
679  *
680  * @param argc argument count
681  * @param argv argument values
682  * @return 0 on success
683  */
684 int
685 main (int argc, char *const *argv)
686 {
687   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
688     {'t', "table", "TABLENAME",
689      gettext_noop ("name of the table to write DFAs"),
690      1, &GNUNET_GETOPT_set_string, &table_name},
691     {'p', "max-path-compression", "MAX_PATH_COMPRESSION",
692      gettext_noop ("maximum path compression length"),
693      1, &GNUNET_GETOPT_set_uint, &max_path_compression},
694     GNUNET_GETOPT_OPTION_END
695   };
696   int ret;
697
698   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
699     return 2;
700
701   result = GNUNET_SYSERR;
702   ret =
703       GNUNET_PROGRAM_run (argc, argv,
704                           "gnunet-regex-simulationprofiler [OPTIONS] policy-dir",
705                           _("Profiler for regex library"), options, &run, NULL);
706   if (GNUNET_OK != ret)
707     return ret;
708   if (GNUNET_OK != result)
709     return 1;
710   return 0;
711 }