From a856f9bffd70603002773c05531db6cecf014cc9 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 24 Nov 2014 12:25:30 +0000 Subject: [PATCH] Add nbdkit bmaplogger plugin. --- Makefile.am | 18 +++ configure.ac | 10 ++ logger.c | 379 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ranges.cpp | 27 +++++ ranges.h | 1 + 5 files changed, 435 insertions(+) create mode 100644 logger.c diff --git a/Makefile.am b/Makefile.am index a377cce..6a40691 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/configure.ac b/configure.ac index 5ae490e..426d905 100644 --- a/configure.ac +++ b/configure.ac @@ -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 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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= Input disk filename\n" \ + "logfile= Log file (default: stdout)\n" \ + "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) diff --git a/ranges.cpp b/ranges.cpp index f8f173c..46b48ae 100644 --- a/ranges.cpp +++ b/ranges.cpp @@ -25,6 +25,7 @@ #include #include +#include #include 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::type window; + window = boost::icl::interval::right_open (start, end); + + ranges r = *map & window; + + ranges::iterator iter = r.begin (); + while (iter != r.end ()) { + boost::icl::interval::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++; + } +} diff --git a/ranges.h b/ranges.h index c02e05c..6738921 100644 --- 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 }; -- 1.8.3.1