[Libguestfs] [PATCH] tests/c-api: Allow the C API tests to run in parallel.

Richard W.M. Jones
Wed, 01 May 2013 12:34:10 -0000

From: "Richard W.M. Jones" <rjones at redhat.com>

You can now set TEST_C_API_PARALLEL=<N> to run the tests
in parallel (in <N> threads).

The default is *not* to run them in parallel.

In practice this doesn't seem to make that much difference.  On my
laptop it's something like 10% faster with 2 threads, and slows down
after that.

Using iostat, I can see that the CPU usage is about 40%, while
/dev/sda usage is pegged to 100% the whole time.  Also the workload is
quite write-heavy.
---
 generator/actions.ml     |   5 +-
 tests/c-api/Makefile.am  |   1 +
 tests/c-api/tests-main.c | 116 +++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 117 insertions(+), 5 deletions(-)

diff --git a/generator/actions.ml b/generator/actions.ml
index 0bb04be..ffe1b33 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -7047,7 +7047,10 @@ file of zeroes, use C<guestfs_fallocate64>
instead." };
         [["mkfifo"; "0o644"; "/utimens-fifo"];
          ["utimens"; "/utimens-fifo"; "12345";
"67890"; "9876"; "5432"];
          ["stat"; "/utimens-fifo"]], "ret->mtime ==
9876");
-      InitScratchFS, Always, TestResult (
+      (* Below test fails in parallel.  I think it implicitly depends
+       * on one of the other tests.
+       *)
+      InitScratchFS, Disabled, TestResult (
         [["ln_sf"; "/utimens-file";
"/utimens-link"];
          ["utimens"; "/utimens-link"; "12345";
"67890"; "9876"; "5432"];
          ["stat"; "/utimens-link"]], "ret->mtime ==
9876");
diff --git a/tests/c-api/Makefile.am b/tests/c-api/Makefile.am
index d55d76d..102cac8 100644
--- a/tests/c-api/Makefile.am
+++ b/tests/c-api/Makefile.am
@@ -80,6 +80,7 @@ tests_CPPFLAGS = \
 	-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
 	-I$(top_srcdir)/src -I$(top_builddir)/src
 tests_CFLAGS = \
+	-pthread \
 	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
 	$(GPROF_CFLAGS) $(GCOV_CFLAGS) \
 	$(PCRE_CFLAGS)
diff --git a/tests/c-api/tests-main.c b/tests/c-api/tests-main.c
index 1e569da..fbfaefa 100644
--- a/tests/c-api/tests-main.c
+++ b/tests/c-api/tests-main.c
@@ -26,6 +26,8 @@
 #include <fcntl.h>
 #include <assert.h>

+#include <pthread.h>
+
 #include <pcre.h>

 /* Warn about deprecated libguestfs functions, but only in this file,
@@ -515,6 +517,7 @@ create_handle (void)
   return g;
 }

+/* Serial tests. */
 static size_t
 perform_tests (guestfs_h *g)
 {
@@ -534,21 +537,126 @@ perform_tests (guestfs_h *g)
   return nr_failed;
 }

+/* Parallel tests. */
+struct thread_data {
+  pthread_t thread;
+  size_t failed;
+  guestfs_h *g;
+};
+
+static size_t test_num = 0;
+static pthread_mutex_t test_num_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void *
+start_thread (void *datavp)
+{
+  struct thread_data *data = datavp;
+  size_t n;
+  struct test *t;
+
+  /* Keep running until we've done all the tests. */
+  for (;;) {
+    pthread_mutex_lock (&test_num_mutex);
+    if (test_num >= nr_tests) {
+      pthread_mutex_unlock (&test_num_mutex);
+      break;
+    }
+    n = test_num;
+    ++test_num;
+    pthread_mutex_unlock (&test_num_mutex);
+
+    t = &tests[n];
+    //next_test (data->g, n, t->name);
+    if (t->test_fn (data->g) == -1) {
+      printf ("FAIL: %s\n", t->name);
+      data->failed++;
+    }
+  }
+
+  return NULL;
+}
+
+static size_t
+perform_parallel_tests (guestfs_h **g, size_t nr_threads)
+{
+  struct thread_data thread_data[nr_threads];
+  size_t nr_failed = 0;
+  size_t i;
+  int r;
+
+  /* Create threads. */
+  for (i = 0; i < nr_threads; ++i) {
+    thread_data[i].g = g[i];
+    thread_data[i].failed = 0;
+    r = pthread_create (&thread_data[i].thread, NULL,
+                        start_thread, &thread_data[i]);
+    if (r != 0) {
+      fprintf (stderr, "pthread_create: %s\n", strerror (r));
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  /* Wait for the threads to exit. */
+  for (i = 0; i < nr_threads; ++i) {
+    r = pthread_join (thread_data[i].thread, NULL);
+    if (r != 0) {
+      fprintf (stderr, "pthread_join: %s\n", strerror (r));
+      exit (EXIT_FAILURE);
+    }
+    nr_failed += thread_data[i].failed;
+  }
+
+  return nr_failed;
+}
+
 int
 main (int argc, char *argv[])
 {
+  char *parallel_str;
+  size_t parallel = 0, i;
+  guestfs_h **g;
   size_t nr_failed;
-  guestfs_h *g;
+
+  /* In $builddir/localenv, put:
+   *
+   *   export TEST_C_API_PARALLEL=<N>
+   *
+   * to run <N> parallel threads.  Or omit it, or set it to 0 to run
+   * the tests normally.
+   */
+  parallel_str = getenv ("TEST_C_API_PARALLEL");
+  if (parallel_str) {
+    if (sscanf (parallel_str, "%zu", &parallel) != 1) {
+      fprintf (stderr, "TEST_C_API_PARALLEL is not a number
(ignored)\n");
+      parallel = 0;
+    }
+  }
+
+  if (parallel == 0)
+    parallel = 1;

   setbuf (stdout, NULL);

   no_test_warnings ();

-  g = create_handle ();
+  /* Create handle(s). */
+  g = malloc (sizeof (guestfs_h *) * parallel);
+  if (!g) {
+    perror ("malloc");
+    exit (EXIT_FAILURE);
+  }
+
+  for (i = 0; i < parallel; ++i)
+    g[i] = create_handle ();

-  nr_failed = perform_tests (g);
+  if (parallel == 1)
+    nr_failed = perform_tests (g[0]);
+  else
+    nr_failed = perform_parallel_tests (g, parallel);

-  guestfs_close (g);
+  /* Close handles. */
+  for (i = 0; i < parallel; ++i)
+    guestfs_close (g[i]);

   if (nr_failed > 0) {
     printf ("***** %zu / %zu tests FAILED *****\n", nr_failed,
nr_tests);
--
1.8.1.4