[Libguestfs] [PATCH 4/4] lib: qemu: Memoize qemu feature detection.

Richard W.M. Jones rjones at redhat.com
Thu May 12 21:26:25 UTC 2016


qemu feature detection takes about 95ms on my laptop.  The overhead is
almost all due to the time taken by the glibc link loader opening the
170+ libraries that qemu is linked to (×2 because we need to run qemu
twice).

Fixing that is seriously hard work.

Therefore memoize the results of guestfs_int_test_qemu.

This is keyed on the size and mtime of the qemu binary, so if the user
changes the qemu binary (eg. setting LIBGUESTFS_HV) we discard the
memoized result and rerun the qemu commands.  There is also a
generation number so we can bump the generation in future versions of
libguestfs to invalidate all previously cached data.

The memo is stored in the supermin cache directory (eg. /var/tmp/.guestfs-*)
in the files:

  qemu.stat     Result of 'stat(2)' of the qemu binary
  qemu.help     qemu -help output
  qemu.devices  qemu -devices ? output

Note these files are only stored when using the 'direct' backend.  For
the libvirt backend, libvirt itself memoizes this data in its own
place.
---
 src/qemu.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 146 insertions(+), 8 deletions(-)

diff --git a/src/qemu.c b/src/qemu.c
index feb8bd6..259a6fd 100644
--- a/src/qemu.c
+++ b/src/qemu.c
@@ -58,23 +58,163 @@ struct qemu_data {
                              guestfs_int_qemu_supports_virtio_scsi */
 };
 
+static int test_qemu (guestfs_h *g, struct qemu_data *data);
 static void parse_qemu_version (guestfs_h *g, struct qemu_data *data);
 static void read_all (guestfs_h *g, void *retv, const char *buf, size_t len);
 
+/* This is saved in the qemu.stat file, so if we decide to change the
+ * test_qemu memoization format/data in future, we should increment
+ * this to discard any memoized data cached by previous versions of
+ * libguestfs.
+ */
+#define MEMO_GENERATION 1
+
 /**
  * Test qemu binary (or wrapper) runs, and do C<qemu -help> so we know
  * the version of qemu what options this qemu supports, and
  * C<qemu -device ?> so we know what devices are available.
+ *
+ * This caches the results in the cachedir so that as long as the qemu
+ * binary does not change, calling this is effectively free.
  */
 struct qemu_data *
 guestfs_int_test_qemu (guestfs_h *g)
 {
+  struct qemu_data *data;
+  struct stat statbuf;
+  CLEANUP_FREE char *cachedir = NULL, *qemu_stat_filename = NULL,
+    *qemu_help_filename = NULL, *qemu_devices_filename = NULL;
+  FILE *fp;
+  int generation;
+  uint64_t prev_size, prev_mtime;
+
+  if (stat (g->hv, &statbuf) == -1) {
+    perrorf (g, "stat: %s", g->hv);
+    return NULL;
+  }
+
+  cachedir = guestfs_int_lazy_make_supermin_appliance_dir (g);
+  if (cachedir == NULL)
+    return NULL;
+
+  qemu_stat_filename = safe_asprintf (g, "%s/qemu.stat", cachedir);
+  qemu_help_filename = safe_asprintf (g, "%s/qemu.help", cachedir);
+  qemu_devices_filename = safe_asprintf (g, "%s/qemu.devices", cachedir);
+
+  /* Did we previously test the same version of qemu? */
+  debug (g, "checking for previously cached test results of %s, in %s",
+         g->hv, cachedir);
+
+  fp = fopen (qemu_stat_filename, "r");
+  if (fp == NULL)
+    goto do_test;
+  if (fscanf (fp, "%d %" SCNu64 " %" SCNu64,
+              &generation, &prev_size, &prev_mtime) != 3) {
+    fclose (fp);
+    goto do_test;
+  }
+  fclose (fp);
+
+  if (generation == MEMO_GENERATION &&
+      (uint64_t) statbuf.st_size == prev_size &&
+      (uint64_t) statbuf.st_mtime == prev_mtime) {
+    /* Same binary as before, so read the previously cached qemu -help
+     * and qemu -devices ? output.
+     */
+    if (access (qemu_help_filename, R_OK) == -1 ||
+        access (qemu_devices_filename, R_OK) == -1)
+      goto do_test;
+
+    debug (g, "loading previously cached test results");
+
+    data = safe_calloc (g, 1, sizeof *data);
+
+    if (guestfs_int_read_whole_file (g, qemu_help_filename,
+                                     &data->qemu_help, NULL) == -1) {
+      guestfs_int_free_qemu_data (data);
+      return NULL;
+    }
+
+    parse_qemu_version (g, data);
+
+    if (guestfs_int_read_whole_file (g, qemu_devices_filename,
+                                     &data->qemu_devices, NULL) == -1) {
+      guestfs_int_free_qemu_data (data);
+      return NULL;
+    }
+
+    return data;
+  }
+
+ do_test:
+  data = safe_calloc (g, 1, sizeof *data);
+
+  if (test_qemu (g, data) == -1) {
+    free (data);
+    return NULL;
+  }
+
+  /* Now memoize the qemu output in the cache directory. */
+  debug (g, "saving test results");
+
+  fp = fopen (qemu_help_filename, "w");
+  if (fp == NULL) {
+  help_error:
+    perrorf (g, "%s", qemu_help_filename);
+    if (fp != NULL) fclose (fp);
+    guestfs_int_free_qemu_data (data);
+    return NULL;
+  }
+  if (fprintf (fp, "%s", data->qemu_help) == -1)
+    goto help_error;
+  if (fclose (fp) == -1)
+    goto help_error;
+
+  fp = fopen (qemu_devices_filename, "w");
+  if (fp == NULL) {
+  devices_error:
+    perrorf (g, "%s", qemu_devices_filename);
+    if (fp != NULL) fclose (fp);
+    guestfs_int_free_qemu_data (data);
+    return NULL;
+  }
+  if (fprintf (fp, "%s", data->qemu_devices) == -1)
+    goto devices_error;
+  if (fclose (fp) == -1)
+    goto devices_error;
+
+  /* Write the qemu.stat file last so that its presence indicates that
+   * the qemu.help and qemu.devices files ought to exist.
+   */
+  fp = fopen (qemu_stat_filename, "w");
+  if (fp == NULL) {
+  stat_error:
+    perrorf (g, "%s", qemu_stat_filename);
+    if (fp != NULL) fclose (fp);
+    guestfs_int_free_qemu_data (data);
+    return NULL;
+  }
+  /* The path to qemu is stored for information only, it is not
+   * used when we parse the file.
+   */
+  if (fprintf (fp, "%d %" PRIu64 " %" PRIu64 " %s\n",
+               MEMO_GENERATION,
+               (uint64_t) statbuf.st_size,
+               (uint64_t) statbuf.st_mtime,
+               g->hv) == -1)
+    goto stat_error;
+  if (fclose (fp) == -1)
+    goto stat_error;
+
+  return data;
+}
+
+static int
+test_qemu (guestfs_h *g, struct qemu_data *data)
+{
   CLEANUP_CMD_CLOSE struct command *cmd1 = guestfs_int_new_command (g);
   CLEANUP_CMD_CLOSE struct command *cmd2 = guestfs_int_new_command (g);
   int r;
-  struct qemu_data *data;
-
-  data = safe_calloc (g, 1, sizeof *data);
 
   guestfs_int_cmd_add_arg (cmd1, g->hv);
   guestfs_int_cmd_add_arg (cmd1, "-display");
@@ -107,16 +247,14 @@ guestfs_int_test_qemu (guestfs_h *g)
   if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0)
     goto error;
 
-  return data;
+  return 0;
 
  error:
-  free (data);
-
   if (r == -1)
-    return NULL;
+    return -1;
 
   guestfs_int_external_command_failed (g, r, g->hv, NULL);
-  return NULL;
+  return -1;
 }
 
 /* Parse the first line of data->qemu_help (if not NULL) into the
-- 
2.7.4




More information about the Libguestfs mailing list