Blame SOURCES/0058-tests-qemu-boot-analysis-Add-support-for-logging-lib.patch

e76f14
From f009e084782ac692ce1a519b5255e9abf4a79632 Mon Sep 17 00:00:00 2001
e76f14
From: "Richard W.M. Jones" <rjones@redhat.com>
e76f14
Date: Wed, 6 Apr 2016 10:43:00 +0100
e76f14
Subject: [PATCH] tests/qemu: boot-analysis: Add support for logging libvirt
e76f14
 events.
e76f14
e76f14
(cherry picked from commit b332d91bc9685e8992ede2c8a10655f26fd0395e)
e76f14
---
e76f14
 tests/qemu/Makefile.am     |   1 +
e76f14
 tests/qemu/boot-analysis.c | 236 +++++++++++++++++++++++++++++++++++++++++++--
e76f14
 2 files changed, 227 insertions(+), 10 deletions(-)
e76f14
e76f14
diff --git a/tests/qemu/Makefile.am b/tests/qemu/Makefile.am
e76f14
index cc5cb6a..fdf643f 100644
e76f14
--- a/tests/qemu/Makefile.am
e76f14
+++ b/tests/qemu/Makefile.am
e76f14
@@ -84,6 +84,7 @@ boot_analysis_CPPFLAGS = \
e76f14
 	-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
e76f14
 	-I$(top_srcdir)/src -I$(top_builddir)/src
e76f14
 boot_analysis_CFLAGS = \
e76f14
+	-pthread \
e76f14
 	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
e76f14
 	$(PCRE_CFLAGS)
e76f14
 boot_analysis_LDADD = \
e76f14
diff --git a/tests/qemu/boot-analysis.c b/tests/qemu/boot-analysis.c
e76f14
index 37d8b3c..2a43976 100644
e76f14
--- a/tests/qemu/boot-analysis.c
e76f14
+++ b/tests/qemu/boot-analysis.c
e76f14
@@ -64,6 +64,7 @@
e76f14
 #include <inttypes.h>
e76f14
 #include <string.h>
e76f14
 #include <unistd.h>
e76f14
+#include <fcntl.h>
e76f14
 #include <getopt.h>
e76f14
 #include <limits.h>
e76f14
 #include <time.h>
e76f14
@@ -71,7 +72,10 @@
e76f14
 #include <error.h>
e76f14
 #include <ctype.h>
e76f14
 #include <assert.h>
e76f14
+#include <sys/types.h>
e76f14
+#include <sys/wait.h>
e76f14
 #include <math.h>
e76f14
+#include <pthread.h>
e76f14
 
e76f14
 #include "guestfs.h"
e76f14
 #include "guestfs-internal-frontend.h"
e76f14
@@ -84,20 +88,32 @@
e76f14
  */
e76f14
 #define WARNING_THRESHOLD 1.0
e76f14
 
e76f14
-struct pass_data pass_data[NR_TEST_PASSES];
e76f14
-size_t nr_activities;
e76f14
-struct activity *activities;
e76f14
-
e76f14
 static const char *append = NULL;
e76f14
 static int force_colour = 0;
e76f14
 static int memsize = 0;
e76f14
 static int smp = 1;
e76f14
 static int verbose = 0;
e76f14
 
e76f14
+static int libvirt_pipe[2] = { -1, -1 };
e76f14
+static ssize_t libvirt_pass = -1;
e76f14
+
e76f14
+/* Because there is a separate thread which collects libvirt log data,
e76f14
+ * we must protect the pass_data struct with a mutex.  This only
e76f14
+ * applies during the data collection passes.
e76f14
+ */
e76f14
+static pthread_mutex_t pass_data_lock = PTHREAD_MUTEX_INITIALIZER;
e76f14
+struct pass_data pass_data[NR_TEST_PASSES];
e76f14
+
e76f14
+size_t nr_activities;
e76f14
+struct activity *activities;
e76f14
+
e76f14
 static void run_test (void);
e76f14
 static struct event *add_event (struct pass_data *, uint64_t source);
e76f14
 static guestfs_h *create_handle (void);
e76f14
 static void set_up_event_handlers (guestfs_h *g, size_t pass);
e76f14
+static void libvirt_log_hack (int argc, char **argv);
e76f14
+static void start_libvirt_thread (size_t pass);
e76f14
+static void stop_libvirt_thread (void);
e76f14
 static void add_drive (guestfs_h *g);
e76f14
 static void check_pass_data (void);
e76f14
 static void dump_pass_data (void);
e76f14
@@ -152,6 +168,8 @@ main (int argc, char *argv[])
e76f14
     { "color", 0, 0, 0 },
e76f14
     { "colour", 0, 0, 0 },
e76f14
     { "memsize", 1, 0, 'm' },
e76f14
+    { "libvirt-pipe-0", 1, 0, 0 }, /* see libvirt_log_hack */
e76f14
+    { "libvirt-pipe-1", 1, 0, 0 },
e76f14
     { "smp", 1, 0, 0 },
e76f14
     { "verbose", 0, 0, 'v' },
e76f14
     { 0, 0, 0, 0 }
e76f14
@@ -173,12 +191,22 @@ main (int argc, char *argv[])
e76f14
         force_colour = 1;
e76f14
         break;
e76f14
       }
e76f14
+      else if (STREQ (long_options[option_index].name, "libvirt-pipe-0")) {
e76f14
+        if (sscanf (optarg, "%d", &libvirt_pipe[0]) != 1)
e76f14
+          error (EXIT_FAILURE, 0,
e76f14
+                 "could not parse libvirt-pipe-0 parameter: %s", optarg);
e76f14
+        break;
e76f14
+      }
e76f14
+      else if (STREQ (long_options[option_index].name, "libvirt-pipe-1")) {
e76f14
+        if (sscanf (optarg, "%d", &libvirt_pipe[1]) != 1)
e76f14
+          error (EXIT_FAILURE, 0,
e76f14
+                 "could not parse libvirt-pipe-1 parameter: %s", optarg);
e76f14
+        break;
e76f14
+      }
e76f14
       else if (STREQ (long_options[option_index].name, "smp")) {
e76f14
-        if (sscanf (optarg, "%d", &smp) != 1) {
e76f14
-          fprintf (stderr, "%s: could not parse smp parameter: %s\n",
e76f14
-                   guestfs_int_program_name, optarg);
e76f14
-          exit (EXIT_FAILURE);
e76f14
-        }
e76f14
+        if (sscanf (optarg, "%d", &smp) != 1)
e76f14
+          error (EXIT_FAILURE, 0,
e76f14
+                 "could not parse smp parameter: %s", optarg);
e76f14
         break;
e76f14
       }
e76f14
       fprintf (stderr, "%s: unknown long option: %s (%d)\n",
e76f14
@@ -205,6 +233,8 @@ main (int argc, char *argv[])
e76f14
     }
e76f14
   }
e76f14
 
e76f14
+  libvirt_log_hack (argc, argv);
e76f14
+
e76f14
   if (STRNEQ (host_cpu, "x86_64"))
e76f14
     fprintf (stderr, "WARNING: host_cpu != x86_64: This program may not work or give bogus results.\n");
e76f14
 
e76f14
@@ -230,10 +260,12 @@ run_test (void)
e76f14
   for (i = 0; i < NR_TEST_PASSES; ++i) {
e76f14
     g = create_handle ();
e76f14
     set_up_event_handlers (g, i);
e76f14
+    start_libvirt_thread (i);
e76f14
     add_drive (g);
e76f14
     if (guestfs_launch (g) == -1)
e76f14
       exit (EXIT_FAILURE);
e76f14
     guestfs_close (g);
e76f14
+    stop_libvirt_thread ();
e76f14
 
e76f14
     printf ("    pass %zu: %zu events collected in %" PRIi64 " ns\n",
e76f14
             i+1, pass_data[i].nr_events, pass_data[i].elapsed_ns);
e76f14
@@ -266,7 +298,7 @@ run_test (void)
e76f14
 }
e76f14
 
e76f14
 static struct event *
e76f14
-add_event (struct pass_data *data, uint64_t source)
e76f14
+add_event_unlocked (struct pass_data *data, uint64_t source)
e76f14
 {
e76f14
   struct event *ret;
e76f14
 
e76f14
@@ -282,6 +314,17 @@ add_event (struct pass_data *data, uint64_t source)
e76f14
   return ret;
e76f14
 }
e76f14
 
e76f14
+static struct event *
e76f14
+add_event (struct pass_data *data, uint64_t source)
e76f14
+{
e76f14
+  struct event *ret;
e76f14
+
e76f14
+  pthread_mutex_lock (&pass_data_lock);
e76f14
+  ret = add_event_unlocked (data, source);
e76f14
+  pthread_mutex_unlock (&pass_data_lock);
e76f14
+  return ret;
e76f14
+}
e76f14
+
e76f14
 /* Common function to create the handle and set various defaults. */
e76f14
 static guestfs_h *
e76f14
 create_handle (void)
e76f14
@@ -590,6 +633,179 @@ set_up_event_handlers (guestfs_h *g, size_t pass)
e76f14
   guestfs_set_trace (g, 1);
e76f14
 }
e76f14
 
e76f14
+/* libvirt debugging sucks in a number of concrete ways:
e76f14
+ *
e76f14
+ * - you can't get a synchronous callback from a log message
e76f14
+ * - you can't enable logging per handle (only globally
e76f14
+ *   by setting environment variables)
e76f14
+ * - you can't debug the daemon easily
e76f14
+ * - it's very complex
e76f14
+ * - it's very complex but not in ways that are practical or useful
e76f14
+ *
e76f14
+ * To get log messages at all, we need to create a pipe connected to a
e76f14
+ * second thread, and when libvirt prints something to the pipe we log
e76f14
+ * that.
e76f14
+ *
e76f14
+ * However that's not sufficient.  Because logging is only enabled
e76f14
+ * when libvirt examines environment variables at the start of the
e76f14
+ * program, we need to create the pipe and then fork+exec a new
e76f14
+ * instance of the whole program with the pipe and environment
e76f14
+ * variables set up.
e76f14
+ */
e76f14
+static int is_libvirt_backend (guestfs_h *g);
e76f14
+static void *libvirt_log_thread (void *datavp);
e76f14
+
e76f14
+static void
e76f14
+libvirt_log_hack (int argc, char **argv)
e76f14
+{
e76f14
+  guestfs_h *g;
e76f14
+
e76f14
+  g = guestfs_create ();
e76f14
+  if (!is_libvirt_backend (g)) {
e76f14
+    guestfs_close (g);
e76f14
+    return;
e76f14
+  }
e76f14
+  guestfs_close (g);
e76f14
+
e76f14
+  /* Have we set up the pipes and environment and forked yet?  If not,
e76f14
+   * do that first.
e76f14
+   */
e76f14
+  if (libvirt_pipe[0] == -1 || libvirt_pipe[1] == -1) {
e76f14
+    char log_outputs[64];
e76f14
+    char **new_argv;
e76f14
+    char param1[64], param2[64];
e76f14
+    size_t i;
e76f14
+    pid_t pid;
e76f14
+    int status;
e76f14
+
e76f14
+    /* Create the pipe.  NB: do NOT use O_CLOEXEC since we want to pass
e76f14
+     * this pipe into a child process.
e76f14
+     */
e76f14
+    if (pipe (libvirt_pipe) == -1)
e76f14
+      error (EXIT_FAILURE, 0, "pipe2");
e76f14
+
e76f14
+    /* Create the environment variables to enable logging in libvirt. */
e76f14
+    setenv ("LIBVIRT_DEBUG", "1", 1);
e76f14
+    snprintf (log_outputs, sizeof log_outputs,
e76f14
+              "1:file:/dev/fd/%d", libvirt_pipe[1]);
e76f14
+    setenv ("LIBVIRT_LOG_OUTPUTS", log_outputs, 1);
e76f14
+
e76f14
+    /* Run self again. */
e76f14
+    new_argv = malloc ((argc+3) * sizeof (char *));
e76f14
+    if (new_argv == NULL)
e76f14
+      error (EXIT_FAILURE, errno, "malloc");
e76f14
+
e76f14
+    for (i = 0; i < (size_t) argc; ++i)
e76f14
+      new_argv[i] = argv[i];
e76f14
+
e76f14
+    snprintf (param1, sizeof param1, "--libvirt-pipe-0=%d", libvirt_pipe[0]);
e76f14
+    new_argv[argc] = param1;
e76f14
+    snprintf (param2, sizeof param2, "--libvirt-pipe-1=%d", libvirt_pipe[1]);
e76f14
+    new_argv[argc+1] = param2;
e76f14
+    new_argv[argc+2] = NULL;
e76f14
+
e76f14
+    pid = fork ();
e76f14
+    if (pid == -1)
e76f14
+      error (EXIT_FAILURE, errno, "fork");
e76f14
+    if (pid == 0) {             /* Child process. */
e76f14
+      execvp (argv[0], new_argv);
e76f14
+      perror ("execvp");
e76f14
+      _exit (EXIT_FAILURE);
e76f14
+    }
e76f14
+
e76f14
+    if (waitpid (pid, &status, 0) == -1)
e76f14
+      error (EXIT_FAILURE, errno, "waitpid");
e76f14
+    if (WIFEXITED (status))
e76f14
+      exit (WEXITSTATUS (status));
e76f14
+    error (EXIT_FAILURE, 0, "unexpected exit status from process: %d", status);
e76f14
+  }
e76f14
+
e76f14
+  /* If we reach this else clause, then we have forked.  Now we must
e76f14
+   * create a thread to read events from the pipe.  This must be
e76f14
+   * constantly reading from the pipe, otherwise we will deadlock.
e76f14
+   * During the warm-up phase we end up throwing away messages.
e76f14
+   */
e76f14
+  else {
e76f14
+    pthread_t thread;
e76f14
+    pthread_attr_t attr;
e76f14
+    int r;
e76f14
+
e76f14
+    r = pthread_attr_init (&attr);
e76f14
+    if (r != 0)
e76f14
+      error (EXIT_FAILURE, r, "pthread_attr_init");
e76f14
+    r = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
e76f14
+    if (r != 0)
e76f14
+      error (EXIT_FAILURE, r, "pthread_attr_setdetachstate");
e76f14
+    r = pthread_create (&thread, &attr, libvirt_log_thread, NULL);
e76f14
+    if (r != 0)
e76f14
+      error (EXIT_FAILURE, r, "pthread_create");
e76f14
+    pthread_attr_destroy (&attr);
e76f14
+  }
e76f14
+}
e76f14
+
e76f14
+static void
e76f14
+start_libvirt_thread (size_t pass)
e76f14
+{
e76f14
+  /* In the non-libvirt case, this variable is ignored. */
e76f14
+  pthread_mutex_lock (&pass_data_lock);
e76f14
+  libvirt_pass = pass;
e76f14
+  pthread_mutex_unlock (&pass_data_lock);
e76f14
+}
e76f14
+
e76f14
+static void
e76f14
+stop_libvirt_thread (void)
e76f14
+{
e76f14
+  /* In the non-libvirt case, this variable is ignored. */
e76f14
+  pthread_mutex_lock (&pass_data_lock);
e76f14
+  libvirt_pass = -1;
e76f14
+  pthread_mutex_unlock (&pass_data_lock);
e76f14
+}
e76f14
+
e76f14
+/* The separate "libvirt thread".  It loops reading debug messages
e76f14
+ * printed by libvirt and adds them to the pass_data.
e76f14
+ */
e76f14
+static void *
e76f14
+libvirt_log_thread (void *arg)
e76f14
+{
e76f14
+  struct event *event;
e76f14
+  CLEANUP_FREE char *buf = NULL;
e76f14
+  ssize_t r;
e76f14
+
e76f14
+  buf = malloc (BUFSIZ);
e76f14
+  if (buf == NULL)
e76f14
+    error (EXIT_FAILURE, errno, "malloc");
e76f14
+
e76f14
+  while ((r = read (libvirt_pipe[0], buf, BUFSIZ)) > 0) {
e76f14
+    pthread_mutex_lock (&pass_data_lock);
e76f14
+    if (libvirt_pass == -1) goto discard;
e76f14
+    event =
e76f14
+      add_event_unlocked (&pass_data[libvirt_pass], GUESTFS_EVENT_LIBRARY);
e76f14
+    event->message = strndup (buf, r);
e76f14
+    if (event->message == NULL)
e76f14
+      error (EXIT_FAILURE, errno, "strndup");
e76f14
+  discard:
e76f14
+    pthread_mutex_unlock (&pass_data_lock);
e76f14
+  }
e76f14
+
e76f14
+  if (r == -1)
e76f14
+    error (EXIT_FAILURE, errno, "libvirt_log_thread: read");
e76f14
+
e76f14
+  /* It's possible for the pipe to be closed (r == 0) if thread
e76f14
+   * cancellation is delayed after the main thread exits, so just
e76f14
+   * ignore that case and exit.
e76f14
+   */
e76f14
+  pthread_exit (NULL);
e76f14
+}
e76f14
+
e76f14
+static int
e76f14
+is_libvirt_backend (guestfs_h *g)
e76f14
+{
e76f14
+  CLEANUP_FREE char *backend = guestfs_get_backend (g);
e76f14
+
e76f14
+  return backend &&
e76f14
+    (STREQ (backend, "libvirt") || STRPREFIX (backend, "libvirt:"));
e76f14
+}
e76f14
+
e76f14
 /* Sanity check the collected events. */
e76f14
 static void
e76f14
 check_pass_data (void)
e76f14
-- 
e76f14
1.8.3.1
e76f14