74e88ea9f86e60c52dbb40a405ca18f5802a0200
[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 "gnunet_regex_lib.h"
32 #include "gnunet_mysql_lib.h"
33 #include <mysql/mysql.h>
34
35 #define INSERT_EDGE_STMT "INSERT IGNORE INTO `%s` "\
36                          "(`key`, `label`, `to_key`, `accepting`) "\
37                          "VALUES (?, ?, ?, ?);"
38
39 /**
40  * Simple struct to keep track of progress, and print a
41  * nice little percentage meter for long running tasks.
42  */
43 struct ProgressMeter
44 {
45   unsigned int total;
46
47   unsigned int modnum;
48
49   unsigned int dotnum;
50
51   unsigned int completed;
52
53   int print;
54
55   char *startup_string;
56 };
57
58
59 /**
60  * Handle for the progress meter
61  */
62 static struct ProgressMeter *meter;
63
64 /**
65  * Abort task identifier.
66  */
67 static GNUNET_SCHEDULER_TaskIdentifier abort_task;
68
69 /**
70  * Scan task identifier;
71  */
72 static GNUNET_SCHEDULER_TaskIdentifier scan_task;
73
74 /**
75  * Global testing status.
76  */
77 static int result;
78
79 /**
80  * MySQL context.
81  */
82 static struct GNUNET_MYSQL_Context *mysql_ctx;
83
84 /**
85  * MySQL prepared statement handle.
86  */
87 static struct GNUNET_MYSQL_StatementHandle *stmt_handle;
88
89 /**
90  * MySQL table name.
91  */
92 static char *table_name;
93
94 /**
95  * Policy dir containing files that contain policies.
96  */
97 static char *policy_dir;
98
99 /**
100  * Number of policy files.
101  */
102 static unsigned int num_policy_files;
103
104 /**
105  * Number of policies.
106  */
107 static unsigned int num_policies;
108
109 /**
110  * Maximal path compression length.
111  */
112 static unsigned int max_path_compression;
113
114
115 /**
116  * Create a meter to keep track of the progress of some task.
117  *
118  * @param total the total number of items to complete
119  * @param start_string a string to prefix the meter with (if printing)
120  * @param print GNUNET_YES to print the meter, GNUNET_NO to count
121  *              internally only
122  *
123  * @return the progress meter
124  */
125 static struct ProgressMeter *
126 create_meter (unsigned int total, char *start_string, int print)
127 {
128   struct ProgressMeter *ret;
129
130   ret = GNUNET_malloc (sizeof (struct ProgressMeter));
131   ret->print = print;
132   ret->total = total;
133   ret->modnum = total / 4;
134   if (ret->modnum == 0)         /* Divide by zero check */
135     ret->modnum = 1;
136   ret->dotnum = (total / 50) + 1;
137   if (start_string != NULL)
138     ret->startup_string = GNUNET_strdup (start_string);
139   else
140     ret->startup_string = GNUNET_strdup ("");
141
142   return ret;
143 }
144
145
146 /**
147  * Update progress meter (increment by one).
148  *
149  * @param meter the meter to update and print info for
150  *
151  * @return GNUNET_YES if called the total requested,
152  *         GNUNET_NO if more items expected
153  */
154 static int
155 update_meter (struct ProgressMeter *meter)
156 {
157   if (meter->print == GNUNET_YES)
158   {
159     if (meter->completed % meter->modnum == 0)
160     {
161       if (meter->completed == 0)
162       {
163         FPRINTF (stdout, "%sProgress: [0%%", meter->startup_string);
164       }
165       else
166         FPRINTF (stdout, "%d%%",
167                  (int) (((float) meter->completed / meter->total) * 100));
168     }
169     else if (meter->completed % meter->dotnum == 0)
170       FPRINTF (stdout, "%s",  ".");
171
172     if (meter->completed + 1 == meter->total)
173       FPRINTF (stdout, "%d%%]\n", 100);
174     fflush (stdout);
175   }
176   meter->completed++;
177
178   if (meter->completed == meter->total)
179     return GNUNET_YES;
180   if (meter->completed > meter->total)
181     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Progress meter overflow!!\n");
182   return GNUNET_NO;
183 }
184
185
186 /**
187  * Reset progress meter.
188  *
189  * @param meter the meter to reset
190  *
191  * @return GNUNET_YES if meter reset,
192  *         GNUNET_SYSERR on error
193  */
194 static int
195 reset_meter (struct ProgressMeter *meter)
196 {
197   if (meter == NULL)
198     return GNUNET_SYSERR;
199
200   meter->completed = 0;
201   return GNUNET_YES;
202 }
203
204
205 /**
206  * Release resources for meter
207  *
208  * @param meter the meter to free
209  */
210 static void
211 free_meter (struct ProgressMeter *meter)
212 {
213   GNUNET_free_non_null (meter->startup_string);
214   GNUNET_free (meter);
215 }
216
217
218 /**
219  * Shutdown task.
220  *
221  * @param cls NULL
222  * @param tc the task context
223  */
224 static void
225 do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
226 {
227   if (NULL != mysql_ctx)
228     GNUNET_MYSQL_context_destroy (mysql_ctx);
229   if (NULL != meter)
230     free_meter (meter);
231
232   GNUNET_SCHEDULER_shutdown (); /* Stop scheduler to shutdown testbed run */
233 }
234
235
236 /**
237  * abort task to run on test timed out
238  *
239  * @param cls NULL
240  * @param tc the task context
241  */
242 static void
243 do_abort (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
244 {
245   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Aborting\n");
246   abort_task = GNUNET_SCHEDULER_NO_TASK;
247   GNUNET_SCHEDULER_cancel (scan_task);
248   scan_task = GNUNET_SCHEDULER_NO_TASK;
249   result = GNUNET_SYSERR;
250   GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
251 }
252
253
254 /**
255  * Iterator over all states that inserts each state into the MySQL db.
256  *
257  * @param cls closure.
258  * @param key hash for current state.
259  * @param proof proof for current state.
260  * @param accepting GNUNET_YES if this is an accepting state, GNUNET_NO if not.
261  * @param num_edges number of edges leaving current state.
262  * @param edges edges leaving current state.
263  */
264 static void
265 regex_iterator (void *cls,
266                 const struct GNUNET_HashCode *key,
267                 const char *proof,
268                 int accepting,
269                 unsigned int num_edges,
270                 const struct GNUNET_REGEX_Edge *edges)
271 {
272   unsigned int i;
273   int result;
274   unsigned long k_length;
275   unsigned long e_length;
276   unsigned long d_length;
277
278   GNUNET_assert (NULL != mysql_ctx);
279
280   for (i = 0; i < num_edges; i++)
281   {
282     k_length = sizeof (struct GNUNET_HashCode);
283     e_length = strlen (edges[i].label);
284     d_length = sizeof (struct GNUNET_HashCode);
285
286     result =
287       GNUNET_MYSQL_statement_run_prepared (
288         mysql_ctx,
289         stmt_handle,
290         NULL,
291         MYSQL_TYPE_BLOB, key, sizeof (struct GNUNET_HashCode), &k_length,
292         MYSQL_TYPE_STRING, edges[i].label, strlen (edges[i].label), &e_length,
293         MYSQL_TYPE_BLOB, &edges[i].destination, sizeof (struct GNUNET_HashCode), &d_length,
294         MYSQL_TYPE_LONG, &accepting, GNUNET_YES,
295         -1);
296
297     if (1 != result && 0 != result)
298     {
299       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
300                   "Error executing prepared mysql statement for edge: Affected rows: %i, expected 0 or 1!\n",
301                   result);
302       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
303     }
304   }
305
306   if (0 == num_edges)
307   {
308     k_length = sizeof (struct GNUNET_HashCode);
309     e_length = 0;
310     d_length = 0;
311
312     result =
313       GNUNET_MYSQL_statement_run_prepared (
314         mysql_ctx,
315         stmt_handle,
316         NULL,
317         MYSQL_TYPE_BLOB, key, sizeof (struct GNUNET_HashCode), &k_length,
318         MYSQL_TYPE_STRING, NULL, 0, &e_length,
319         MYSQL_TYPE_BLOB, NULL, 0, &d_length,
320         MYSQL_TYPE_LONG, &accepting, GNUNET_YES,
321         -1);
322
323     if (1 != result && 0 != result)
324     {
325       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
326                   "Error executing prepared mysql statement for edge: Affected rows: %i, expected 0 or 1!\n",
327                   result);
328       GNUNET_SCHEDULER_add_now (&do_abort, NULL);
329     }
330   }
331 }
332
333
334 /**
335  * Announce a regex by creating the DFA and iterating over each state, inserting
336  * each state into a MySQL database.
337  *
338  * @param regex regular expression.
339  * @return GNUNET_OK on success, GNUNET_SYSERR on failure.
340  */
341 static int
342 announce_regex (const char *regex)
343 {
344   struct GNUNET_REGEX_Automaton *dfa;
345
346   dfa = GNUNET_REGEX_construct_dfa (regex, strlen (regex), max_path_compression);
347
348   if (NULL == dfa)
349   {
350     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
351                 "Failed to create DFA for regex %s\n", regex);
352     abort_task = GNUNET_SCHEDULER_add_now (&do_abort, NULL);
353     return GNUNET_SYSERR;
354   }
355
356   GNUNET_REGEX_iterate_all_edges (dfa, &regex_iterator, NULL);
357
358   GNUNET_REGEX_automaton_destroy (dfa);
359
360   return GNUNET_OK;
361 }
362
363
364 /**
365  * Function called with a filename.
366  *
367  * @param cls closure
368  * @param filename complete filename (absolute path)
369  * @return GNUNET_OK to continue to iterate,
370  *  GNUNET_SYSERR to abort iteration with error!
371  */
372 int
373 policy_filename_cb (void *cls, const char *filename)
374 {
375   char *regex;
376   char *data;
377   char *buf;
378   uint64_t filesize;
379   unsigned int offset;
380
381   GNUNET_assert (NULL != filename);
382
383   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
384               "Announcing regexes from file %s\n",
385               filename);
386
387   if (GNUNET_YES != GNUNET_DISK_file_test (filename))
388   {
389     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
390                 "Could not find policy file %s\n", filename);
391     return GNUNET_OK;
392   }
393   if (GNUNET_OK != GNUNET_DISK_file_size (filename, &filesize, GNUNET_YES, GNUNET_YES))
394     filesize = 0;
395   if (0 == filesize)
396   {
397     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Policy file %s is empty.\n", filename);
398     return GNUNET_OK;
399   }
400   data = GNUNET_malloc (filesize);
401   if (filesize != GNUNET_DISK_fn_read (filename, data, filesize))
402   {
403     GNUNET_free (data);
404     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Could not read policy file %s.\n",
405                 filename);
406     return GNUNET_OK;
407   }
408
409   update_meter (meter);
410
411   buf = data;
412   offset = 0;
413   regex = NULL;
414   while (offset < (filesize - 1))
415   {
416     offset++;
417     if (((data[offset] == '\n')) && (buf != &data[offset]))
418     {
419       data[offset] = '\0';
420       regex = buf;
421       GNUNET_assert (NULL != regex);
422       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Announcing regex: %s\n",
423                   regex);
424       num_policies++;
425
426       if (GNUNET_OK != announce_regex (regex))
427       {
428         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
429                     "Could not announce regex %s\n", regex);
430       }
431
432       buf = &data[offset + 1];
433     }
434     else if ((data[offset] == '\n') || (data[offset] == '\0'))
435       buf = &data[offset + 1];
436   }
437   GNUNET_free (data);
438   return GNUNET_OK;
439 }
440
441
442 /**
443  * Iterate over files contained in policy_dir.
444  *
445  * @param cls NULL
446  * @param tc the task context
447  */
448 static void
449 do_directory_scan (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
450 {
451   struct GNUNET_TIME_Absolute start_time;
452   struct GNUNET_TIME_Relative duration;
453   char *stmt;
454
455   if (GNUNET_SCHEDULER_NO_TASK != abort_task)
456     GNUNET_SCHEDULER_cancel (abort_task);
457
458   /* Create an MySQL prepared statement for the inserts */
459   GNUNET_asprintf (&stmt, INSERT_EDGE_STMT, table_name);
460   stmt_handle = GNUNET_MYSQL_statement_prepare (mysql_ctx, stmt);
461   GNUNET_free (stmt);
462
463   GNUNET_assert (NULL != stmt_handle);
464
465   meter = create_meter (num_policy_files, "Announcing policy files\n", GNUNET_YES);
466   start_time = GNUNET_TIME_absolute_get ();
467   GNUNET_DISK_directory_scan (policy_dir,
468                               &policy_filename_cb,
469                               stmt_handle);
470   duration = GNUNET_TIME_absolute_get_duration (start_time);
471   reset_meter (meter);
472   free_meter (meter);
473   meter = NULL;
474
475   printf ("Announced %u files containing %u policies in %s\n",
476           num_policy_files, num_policies,
477           GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_NO));
478
479   result = GNUNET_OK;
480   GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
481 }
482
483
484 /**
485  * Main function that will be run by the scheduler.
486  *
487  * @param cls closure
488  * @param args remaining command-line arguments
489  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
490  * @param config configuration
491  */
492 static void
493 run (void *cls, char *const *args, const char *cfgfile,
494      const struct GNUNET_CONFIGURATION_Handle *config)
495 {
496   if (NULL == args[0])
497   {
498     fprintf (stderr, _("No policy directory specified on command line. Exiting.\n"));
499     result = GNUNET_SYSERR;
500     return;
501   }
502   if (GNUNET_YES != GNUNET_DISK_directory_test (args[0]))
503   {
504     fprintf (stderr, _("Specified policies directory does not exist. Exiting.\n"));
505     result = GNUNET_SYSERR;
506     return;
507   }
508   policy_dir = args[0];
509
510   num_policy_files = GNUNET_DISK_directory_scan (policy_dir,
511                                                  NULL,
512                                                  NULL);
513   meter = NULL;
514
515   if (NULL == table_name)
516   {
517     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No table name specified, using default \"NFA\".\n");
518     table_name = "NFA";
519   }
520
521   mysql_ctx = GNUNET_MYSQL_context_create (config, "regex-mysql");
522   if (NULL == mysql_ctx)
523   {
524     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to create mysql context\n");
525     result = GNUNET_SYSERR;
526     return;
527   }
528
529   result = GNUNET_OK;
530
531   scan_task = GNUNET_SCHEDULER_add_now (&do_directory_scan, NULL);
532
533   abort_task =
534     GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
535                                   (GNUNET_TIME_UNIT_SECONDS, 10), &do_abort,
536                                   NULL);
537 }
538
539
540 /**
541  * Main function.
542  *
543  * @param argc argument count
544  * @param argv argument values
545  * @return 0 on success
546  */
547 int
548 main (int argc, char *const *argv)
549 {
550   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
551     {'t', "table", "TABLENAME",
552      gettext_noop ("name of the table to write DFAs"),
553      1, &GNUNET_GETOPT_set_string, &table_name},
554     {'p', "max-path-compression", "MAX_PATH_COMPRESSION",
555      gettext_noop ("maximum path compression length"),
556      1, &GNUNET_GETOPT_set_uint, &max_path_compression},
557     GNUNET_GETOPT_OPTION_END
558   };
559   int ret;
560
561   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
562     return 2;
563
564   result = GNUNET_SYSERR;
565   ret =
566       GNUNET_PROGRAM_run (argc, argv, "gnunet-regex-simulationprofiler [OPTIONS] policy-dir",
567                           _("Profiler for regex library"),
568                           options, &run, NULL);
569   if (GNUNET_OK != ret)
570     return ret;
571   if (GNUNET_OK != result)
572     return 1;
573   return 0;
574 }