Add nbdkit bmaplogger plugin.
authorRichard W.M. Jones <rjones@redhat.com>
Mon, 24 Nov 2014 12:25:30 +0000 (12:25 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Mon, 24 Nov 2014 18:55:11 +0000 (18:55 +0000)
Makefile.am
configure.ac
logger.c [new file with mode: 0644]
ranges.cpp
ranges.h

index a377cce..6a40691 100644 (file)
@@ -21,6 +21,7 @@ bin_SCRIPTS = virt-bmap
 
 lib_LTLIBRARIES = \
        virtbmapexaminer.la
+
 virtbmapexaminer_la_CPPFLAGS = \
        -I$(srcdir)
 virtbmapexaminer_la_CFLAGS = \
@@ -40,6 +41,23 @@ virtbmapexaminer_la_SOURCES = \
        visit.c \
        visit.h
 
+nbdkitplugin_LTLIBRARIES = \
+       bmaplogger.la
+
+bmaplogger_la_CPPFLAGS = \
+       -I$(srcdir)
+bmaplogger_la_CFLAGS = \
+       -Wall \
+       -pthread
+bmaplogger_la_LDFLAGS = \
+       -module -avoid-version -shared
+bmaplogger_la_SOURCES = \
+       cleanups.c \
+       cleanups.h \
+       logger.c \
+       ranges.cpp \
+       ranges.h
+
 man_MANS = virt-bmap.1
 
 virt-bmap.1: virt-bmap.pod
index 5ae490e..426d905 100644 (file)
@@ -100,6 +100,16 @@ AC_CHECK_HEADER([nbdkit-plugin.h],[],[
     AC_MSG_ERROR([you need to install the nbdkit development package])
 ])
 
+dnl Get nbdkit plugin directory.
+AC_MSG_CHECKING([for nbdkit plugin directory])
+eval `$NBDKIT --dump-config`
+if test "x$plugindir" = "x"; then
+    AC_MSG_ERROR([something went wrong getting nbdkit plugin directory])
+fi
+nbdkitplugindir="$plugindir"
+AC_SUBST([nbdkitplugindir])
+AC_MSG_RESULT([$nbdkitplugindir])
+
 dnl Check for C++ boost library with interval_map.hpp
 AC_LANG_PUSH([C++])
 AC_CHECK_HEADERS([boost/icl/interval_map.hpp], [],
diff --git a/logger.c b/logger.c
new file mode 100644 (file)
index 0000000..0cee8cf
--- /dev/null
+++ b/logger.c
@@ -0,0 +1,379 @@
+/* virt-bmap logger plugin
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* This is an nbdkit plugin which observes guest activity and displays
+ * what files are being accessed.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+
+#include <nbdkit-plugin.h>
+
+#include "cleanups.h"
+#include "ranges.h"
+
+static char *file = NULL;
+static char *bmap = NULL;
+static char *logfile = NULL;
+static void *ranges = NULL;
+static FILE *logfp = NULL;
+
+static int
+logger_config (const char *key, const char *value)
+{
+  if (strcmp (key, "file") == 0) {
+    free (file);
+    file = nbdkit_absolute_path (value);
+    if (file == NULL)
+      return -1;
+  }
+  else if (strcmp (key, "bmap") == 0) {
+    free (bmap);
+    bmap = nbdkit_absolute_path (value);
+    if (bmap == NULL)
+      return -1;
+  }
+  else if (strcmp (key, "logfile") == 0) {
+    free (logfile);
+    logfile = nbdkit_absolute_path (value);
+    if (logfile == NULL)
+      return -1;
+  }
+  else {
+    nbdkit_error ("unknown parameter '%s'", key);
+    return -1;
+  }
+
+  return 0;
+}
+
+static int
+logger_config_complete (void)
+{
+  FILE *fp;
+  const char *bmap_file = bmap ? bmap : "bmap";
+  CLEANUP_FREE char *line = NULL;
+  size_t alloc;
+  ssize_t len;
+  size_t count;
+
+  if (!file) {
+    nbdkit_error ("missing 'file=...' parameter, see virt-bmap(1)");
+    return -1;
+  }
+
+  ranges = new_ranges ();
+
+  /* Load ranges from bmap file. */
+  fp = fopen (bmap_file, "r");
+  if (fp == NULL) {
+    nbdkit_error ("open: %s: %m", bmap_file);
+    return -1;
+  }
+
+  count = 0;
+  while (errno = 0, (len = getline (&line, &alloc, fp)) != -1) {
+    uint64_t start, end;
+    int object_offset;
+    const char *object;
+
+    if (len > 0 && line[len-1] == '\n')
+      line[--len] = '\0';
+
+    if (sscanf (line, "1 %" SCNx64 " %" SCNx64 " %n",
+                &start, &end, &object_offset) >= 2) {
+      count++;
+      object = line + object_offset;
+      insert_range (ranges, start, end, object);
+    }
+  }
+
+  if (errno) {
+    nbdkit_error ("getline: %s: %m", bmap_file);
+    fclose (fp);
+    return -1;
+  }
+
+  if (count == 0) {
+    nbdkit_error ("no ranges were read from block map file: %s", bmap_file);
+    return -1;
+  }
+
+  if (fclose (fp) == -1) {
+    nbdkit_error ("fclose: %s: %m", bmap_file);
+    return -1;
+  }
+
+  /* Set up log file. */
+  if (logfile) {
+    logfp = fopen (logfile, "w");
+    if (logfp == NULL) {
+      nbdkit_error ("cannot open log file: %s: %m", logfile);
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+static void
+logger_unload (void)
+{
+  if (logfp)
+    fclose (logfp);
+
+  free_ranges (ranges);
+  free (logfile);
+  free (bmap);
+  free (file);
+}
+
+#define logger_config_help                                        \
+  "file=<DISK>         Input disk filename\n"                     \
+  "logfile=<OUTPUT>    Log file (default: stdout)\n"              \
+  "bmap=<BMAP>         Block map (default: \"bmap\")"             \
+
+/* See log_operation below. */
+struct operation {
+  int is_read;
+  int priority;
+  uint64_t start;
+  uint64_t end;
+  const char *object;
+};
+
+/* The per-connection handle. */
+struct handle {
+  int fd;
+
+  /* See log_operation below. */
+  struct operation last;
+  struct operation current;
+};
+
+/* Create the per-connection handle. */
+static void *
+logger_open (int readonly)
+{
+  struct handle *h;
+  int flags;
+
+  h = malloc (sizeof *h);
+  if (h == NULL) {
+    nbdkit_error ("malloc: %m");
+    return NULL;
+  }
+
+  memset (&h->last, 0, sizeof h->last);
+  memset (&h->current, 0, sizeof h->current);
+
+  flags = O_CLOEXEC|O_NOCTTY;
+  if (readonly)
+    flags |= O_RDONLY;
+  else
+    flags |= O_RDWR;
+
+  h->fd = open (file, flags);
+  if (h->fd == -1) {
+    nbdkit_error ("open: %s: %m", file);
+    free (h);
+    return NULL;
+  }
+
+  return h;
+}
+
+/* Free up the per-connection handle. */
+static void
+logger_close (void *handle)
+{
+  struct handle *h = handle;
+
+  close (h->fd);
+  free (h);
+}
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
+
+/* Get the file size. */
+static int64_t
+logger_get_size (void *handle)
+{
+  struct handle *h = handle;
+  struct stat statbuf;
+
+  if (fstat (h->fd, &statbuf) == -1) {
+    nbdkit_error ("stat: %m");
+    return -1;
+  }
+
+  return statbuf.st_size;
+}
+
+static int
+priority_of_object (const char *object)
+{
+  switch (object[0]) {
+  case 'v': return 1;           /* whole device (least important) */
+  case 'p': return 2;
+  case 'l': return 3;
+  case 'd': return 4;
+  case 'f': return 5;           /* file (most important) */
+  default: return 6;
+  }
+}
+
+/* Callback from find_range.  Save the highest priority object into
+ * the handle for later printing.
+ */
+static void
+log_callback (uint64_t start, uint64_t end, const char *object, void *opaque)
+{
+  struct handle *h = opaque;
+  int priority = priority_of_object (object);
+
+  if (priority > h->current.priority) {
+    h->current.priority = priority;
+    h->current.start = start;
+    h->current.end = end;
+    h->current.object = object;
+  }
+}
+
+static void
+log_operation (struct handle *h, uint64_t offset, uint32_t count, int is_read)
+{
+  h->current.is_read = is_read;
+  h->current.priority = 0;
+  find_range (ranges, offset, offset+count, log_callback, h);
+
+  if (h->current.priority > 0) {
+    FILE *fp = logfp ? logfp : stdout;
+
+    if (h->current.priority != h->last.priority ||
+        h->current.is_read != h->last.is_read ||
+        h->last.object == NULL ||
+        strcmp (h->current.object, h->last.object) != 0) {
+      fprintf (fp,
+               "\n"
+               "%s %s\n",
+               is_read ? "read" : "write", h->current.object);
+
+      h->last = h->current;
+    }
+
+    /* It would be nice to print an offset relative to the current
+     * object here, but that's not possible since we don't have the
+     * information about precisely what file offsets map to what
+     * blocks.
+     */
+    fprintf (fp, " %" PRIx64 "-%" PRIx64, offset, offset+count);
+    fflush (fp);
+  }
+}
+
+/* Read data from the file. */
+static int
+logger_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
+{
+  struct handle *h = handle;
+
+  log_operation (h, offset, count, 1);
+
+  while (count > 0) {
+    ssize_t r = pread (h->fd, buf, count, offset);
+    if (r == -1) {
+      nbdkit_error ("pread: %m");
+      return -1;
+    }
+    if (r == 0) {
+      nbdkit_error ("pread: unexpected end of file");
+      return -1;
+    }
+    buf += r;
+    count -= r;
+    offset += r;
+  }
+
+  return 0;
+}
+
+/* Write data to the file. */
+static int
+logger_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
+{
+  struct handle *h = handle;
+
+  log_operation (h, offset, count, 0);
+
+  while (count > 0) {
+    ssize_t r = pwrite (h->fd, buf, count, offset);
+    if (r == -1) {
+      nbdkit_error ("pwrite: %m");
+      return -1;
+    }
+    buf += r;
+    count -= r;
+    offset += r;
+  }
+
+  return 0;
+}
+
+/* Flush the file to disk. */
+static int
+logger_flush (void *handle)
+{
+  struct handle *h = handle;
+
+  if (fdatasync (h->fd) == -1) {
+    nbdkit_error ("fdatasync: %m");
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Register the nbdkit plugin. */
+
+static struct nbdkit_plugin plugin = {
+  .name              = "bmaplogger",
+  .version           = PACKAGE_VERSION,
+  .config            = logger_config,
+  .config_help       = logger_config_help,
+  .config_complete   = logger_config_complete,
+  .open              = logger_open,
+  .close             = logger_close,
+  .get_size          = logger_get_size,
+  .pread             = logger_pread,
+  .unload            = logger_unload,
+};
+
+NBDKIT_REGISTER_PLUGIN(plugin)
index f8f173c..46b48ae 100644 (file)
@@ -25,6 +25,7 @@
 #include <assert.h>
 
 #include <boost/icl/interval.hpp>
+#include <boost/icl/interval_set.hpp>
 #include <boost/icl/interval_map.hpp>
 
 using namespace std;
@@ -77,3 +78,29 @@ iter_range (void *mapv, void (*f) (uint64_t start, uint64_t end, const char *obj
     iter++;
   }
 }
+
+extern "C" void
+find_range (void *mapv, uint64_t start, uint64_t end, void (*f) (uint64_t start, uint64_t end, const char *object, void *opaque), void *opaque)
+{
+  ranges *map = (ranges *) mapv;
+
+  boost::icl::interval<uint64_t>::type window;
+  window = boost::icl::interval<uint64_t>::right_open (start, end);
+
+  ranges r = *map & window;
+
+  ranges::iterator iter = r.begin ();
+  while (iter != r.end ()) {
+    boost::icl::interval<uint64_t>::type range = iter->first;
+    uint64_t start = range.lower ();
+    uint64_t end = range.upper ();
+
+    objects obj_set = iter->second;
+    objects::iterator iter2 = obj_set.begin ();
+    while (iter2 != obj_set.end ()) {
+      f (start, end, iter2->c_str (), opaque);
+      iter2++;
+    }
+    iter++;
+  }
+}
index c02e05c..6738921 100644 (file)
--- a/ranges.h
+++ b/ranges.h
@@ -27,6 +27,7 @@ extern void *new_ranges (void);
 extern void free_ranges (void *mapv);
 extern void insert_range (void *mapv, uint64_t start, uint64_t end, const char *object);
 extern void iter_range (void *mapv, void (*f) (uint64_t start, uint64_t end, const char *object, void *opaque), void *opaque);
+extern void find_range (void *mapv, uint64_t start, uint64_t end, void (*f) (uint64_t start, uint64_t end, const char *object, void *opaque), void *opaque);
 
 #ifdef __cplusplus
 };