Add a contrib directory which we can use to store random tests and
other contributions that we don't necessarily want to support long
term or incorporate into nbdkit.

Into this directory place a test program which tests
nbdkit-memory-plugin with allocator=sparse.
---
 configure.ac             |   1 +
 Makefile.am              |   1 +
 contrib/Makefile.am      |  44 +++++
 contrib/sparseloadtest.c | 399 +++++++++++++++++++++++++++++++++++++++
 .gitignore               |   1 +
 5 files changed, 446 insertions(+)

diff --git a/configure.ac b/configure.ac
index fc4928b5d..d8a93ac14 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1470,6 +1470,7 @@ AC_CONFIG_FILES([Makefile
                  common/replacements/Makefile
                  common/replacements/win32/Makefile
                  common/utils/Makefile
+                 contrib/Makefile
                  docs/Makefile
                  include/Makefile
                  include/nbdkit-version.h
diff --git a/Makefile.am b/Makefile.am
index b1fcc815a..3fe149909 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -84,6 +84,7 @@ SUBDIRS = \
        common/replacements \
        common/utils \
        server \
+       contrib
        $(NULL)
 
 if HAVE_PLUGINS
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644
index 000000000..b9866e47d
--- /dev/null
+++ b/contrib/Makefile.am
@@ -0,0 +1,44 @@
+# nbdkit
+# Copyright Red Hat
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+include $(top_srcdir)/common-rules.mk
+
+if HAVE_LIBNBD
+
+noinst_PROGRAMS = sparseloadtest
+
+sparseloadtest_SOURCES = sparseloadtest.c
+sparseloadtest_CPPFLAGS = -I$(top_srcdir)/common/include
+sparseloadtest_CFLAGS = $(PTHREAD_CFLAGS) $(WARNINGS_CFLAGS) $(LIBNBD_CFLAGS)
+sparseloadtest_LDADD = $(LIBNBD_LIBS)
+sparseloadtest_LDFLAGS = $(PTHREAD_LIBS)
+
+endif HAVE_LIBNBD
diff --git a/contrib/sparseloadtest.c b/contrib/sparseloadtest.c
new file mode 100644
index 000000000..4aa3f5104
--- /dev/null
+++ b/contrib/sparseloadtest.c
@@ -0,0 +1,399 @@
+/* nbdkit
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* Load nbdkit-memory-plugin + allocator=sparse
+ *
+ * This is better than fio for this plugin since it exercises
+ * allocation and deallocation of pages and locking.
+ *
+ * To test a mainly read workload (90% reads, 10% writes, 10% trims):
+ * ./contrib/sparseloadtest 4 90
+ *
+ * To test a write-heavy workload (20% reads, 40% writes, 40% trims):
+ * ./contrib/sparseloadtest 4 20
+ *
+ * nbdkit is run from the current $PATH environment variable, so to
+ * run the locally built nbdkit you should do:
+ *
+ * PATH=.:$PATH ./contrib/sparseloadtest [...]
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <pthread.h>
+
+#include <libnbd.h>
+
+#include "random.h"
+
+#define PAGE_SIZE   32768       /* See common/allocators/sparse.c */
+#define L2_SIZE     4096
+#define DISK_SIZE   (4*L2_SIZE*PAGE_SIZE)
+#define MAX_THREADS 16
+#define DURATION    60          /* seconds */
+#define MAX_IN_FLIGHT 64
+#define MAX_REQUEST (128*1024)  /* Should be larger than PAGE_SIZE. */
+
+static pid_t pid;
+static char sockfile[] = "/tmp/sockXXXXXX";
+static char pidfile[] = "/tmp/pidXXXXXX";
+static unsigned nr_threads;
+static double pc_read;          /* % read operations. */
+
+struct stats {
+  size_t ops;
+  size_t bytes;
+};
+
+struct thread_data {
+  pthread_t thread;
+  int status;                   /* returned status from the thread */
+  struct nbd_handle *nbd;       /* per-thread handle */
+  struct stats read_stats, write_stats, trim_stats;
+  struct random_state state;
+};
+static struct thread_data thread[MAX_THREADS];
+
+static time_t start_t;
+
+struct command_data {
+  struct stats *stats;
+  size_t count;
+};
+
+/* We don't care about the data that is read, so this is just a sink
+ * buffer shared across all threads.
+ */
+static char sink[MAX_REQUEST];
+
+static char wrbuf[MAX_REQUEST];
+
+static void start_nbdkit (void);
+static void create_random_name (char *template);
+static void cleanup (void);
+static void *start_thread (void *);
+
+int
+main (int argc, char *argv[])
+{
+  unsigned i;
+  int err;
+  struct stats read_total = { 0 };
+  struct stats write_total = { 0 };
+  struct stats trim_total = { 0 };
+
+  if (argc != 3) {
+    fprintf (stderr, "%s nr_threads percent_reads\n", argv[0]);
+    exit (EXIT_FAILURE);
+  }
+  if (sscanf (argv[1], "%u", &nr_threads) != 1 ||
+      nr_threads == 0 || nr_threads > MAX_THREADS ||
+      sscanf (argv[2], "%lg", &pc_read) != 1 ||
+      pc_read <= 0 || pc_read > 100) {
+    fprintf (stderr, "%s: incorrect parameters, read the source!\n", argv[0]);
+    exit (EXIT_FAILURE);
+  }
+
+  start_nbdkit ();
+  atexit (cleanup);
+
+  /* Connect to nbdkit. */
+  for (i = 0; i < nr_threads; ++i) {
+    struct nbd_handle *nbd = nbd_create ();
+    if (nbd == NULL) {
+    got_nbd_error:
+      fprintf (stderr, "%s: thread %u: %s\n", argv[0], i, nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    if (nbd_connect_unix (nbd, sockfile) == -1)
+      goto got_nbd_error;
+    thread[i].nbd = nbd;
+  }
+
+  time (&start_t);
+
+  /* Start threads. */
+  for (i = 0; i < nr_threads; ++i) {
+    xsrandom (i+1, &thread[i].state);
+
+    if (i == 0) {
+      size_t j;
+      for (j = 0; j < sizeof wrbuf; ++j)
+        wrbuf[j] = xrandom (&thread[i].state);
+    }
+
+    err = pthread_create (&thread[i].thread, NULL, start_thread, &thread[i]);
+    if (err != 0) {
+      errno = err;
+      perror ("pthread_create");
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  /* Wait for the threads to exit. */
+  for (i = 0; i < nr_threads; ++i) {
+    err = pthread_join (thread[i].thread, NULL);
+    if (err != 0) {
+      errno = err;
+      perror ("pthread_join");
+      exit (EXIT_FAILURE);
+    }
+    if (thread[i].status != 0) {
+      fprintf (stderr, "%s: thread %u failed, see earlier errors\n",
+               argv[0], i);
+      exit (EXIT_FAILURE);
+    }
+
+    read_total.ops += thread[i].read_stats.ops;
+    read_total.bytes += thread[i].read_stats.bytes;
+    write_total.ops += thread[i].write_stats.ops;
+    write_total.bytes += thread[i].write_stats.bytes;
+    trim_total.ops += thread[i].trim_stats.ops;
+    trim_total.bytes += thread[i].trim_stats.bytes;
+
+    /* Drain the command queue just to avoid errors.  These requests
+     * don't count in the final totals.
+     */
+    while (nbd_aio_in_flight (thread[i].nbd) > 0) {
+      if (nbd_poll (thread[i].nbd, -1) == -1) {
+        fprintf (stderr, "%s\n", nbd_get_error ());
+        exit (EXIT_FAILURE);
+      }
+    }
+
+    nbd_close (thread[i].nbd);
+  }
+
+  /* Print the throughput. */
+  printf ("READ: %.1f ops/s %.1f bytes/s\n",
+          (double) read_total.ops / DURATION,
+          (double) read_total.bytes / DURATION);
+  printf ("WRITE: %.1f ops/s %.1f bytes/s\n",
+          (double) write_total.ops / DURATION,
+          (double) write_total.bytes / DURATION);
+  printf ("TRIM: %.1f ops/s %.1f bytes/s\n",
+          (double) trim_total.ops / DURATION,
+          (double) trim_total.bytes / DURATION);
+  printf ("TOTAL: %.1f ops/s %.1f bytes/s\n",
+          (double) (read_total.ops + write_total.ops + trim_total.ops)
+          / DURATION,
+          (double) (read_total.bytes + write_total.bytes + trim_total.bytes)
+          / DURATION);
+  printf ("--\n");
+  printf ("%%read operations requested: %.1f%%, achieved: %.1f%%\n",
+          pc_read,
+          100.0 * read_total.ops /
+          (read_total.ops + write_total.ops + trim_total.ops));
+  printf ("%%write operations requested: %.1f%%, achieved: %.1f%%\n",
+          (100 - pc_read) / 2,
+          100.0 * write_total.ops /
+          (read_total.ops + write_total.ops + trim_total.ops));
+  printf ("%%trim operations requested: %.1f%%, achieved: %.1f%%\n",
+          (100 - pc_read) / 2,
+          100.0 * trim_total.ops /
+          (read_total.ops + write_total.ops + trim_total.ops));
+
+  exit (EXIT_SUCCESS);
+}
+
+static void
+cleanup (void)
+{
+  if (pid > 0) {
+    kill (pid, SIGTERM);
+    unlink (sockfile);
+    pid = 0;
+  }
+}
+
+/* Start nbdkit.
+ *
+ * We cannot use systemd socket activation because we want to use
+ * multi-conn.
+ */
+static void
+start_nbdkit (void)
+{
+  char size[64];
+  int i;
+
+  snprintf (size, sizeof size, "%d", DISK_SIZE);
+
+  create_random_name (sockfile);
+  create_random_name (pidfile);
+
+  /* Start nbdkit. */
+  pid = fork ();
+  if (pid == -1) {
+    perror ("fork");
+    exit (EXIT_FAILURE);
+  }
+  if (pid == 0) {               /* Child - nbdkit */
+    execlp ("nbdkit",
+            "nbdkit", "--exit-with-parent", "-f",
+            "-U", sockfile, "-P", pidfile,
+            "memory", size, "allocator=sparse",
+            NULL);
+    perror ("execlp");
+    _exit (EXIT_FAILURE);
+  }
+
+  /* Wait for the pidfile to appear, indicating that nbdkit is ready. */
+  for (i = 0; i < 60; ++i) {
+    if (access (pidfile, F_OK) == 0)
+      break;
+    sleep (1);
+  }
+  if (i == 60) {
+    fprintf (stderr, "nbdkit did not start up, look for errors above\n");
+    exit (EXIT_FAILURE);
+  }
+
+  unlink (pidfile);
+}
+
+/* This is racy but it doesn't matter for a test. */
+static void
+create_random_name (char *template)
+{
+  int fd;
+
+  fd = mkstemp (template);
+  if (fd == -1) {
+    perror (template);
+    exit (EXIT_FAILURE);
+  }
+  close (fd);
+  unlink (template);
+}
+
+static int
+cb (void *user_data, int *error)
+{
+  struct command_data *data = user_data;
+
+  if (*error != 0) {
+    fprintf (stderr, "unexpected error in completion callback\n");
+    exit (EXIT_FAILURE);
+  }
+
+  data->stats->ops++;
+  data->stats->bytes += data->count;
+
+  free (data);
+
+  return 1;                     /* retire the command */
+}
+
+static void *
+start_thread (void *thread_data_vp)
+{
+  struct thread_data *thread_data = thread_data_vp;
+  struct nbd_handle *nbd = thread_data->nbd;
+  time_t end_t;
+  size_t total_ops;
+  double pc_read_actual;
+  uint64_t offset;
+  size_t count;
+  struct command_data *data;
+  int64_t r;
+
+  while (time (&end_t), end_t - start_t < DURATION) {
+    /* Run the poll loop if there are too many requests in flight. */
+    while (nbd_aio_in_flight (nbd) >= MAX_IN_FLIGHT) {
+      if (nbd_poll (nbd, -1) == -1) {
+        fprintf (stderr, "%s\n", nbd_get_error ());
+        exit (EXIT_FAILURE);
+      }
+    }
+
+    /* Aim to send about pc_read% read operations, and an equal random
+     * distribution of writes and trims.
+     */
+    total_ops =
+      thread_data->read_stats.ops + thread_data->write_stats.ops +
+      thread_data->trim_stats.ops;
+    if (total_ops == 0)
+      pc_read_actual = 100;
+    else
+      pc_read_actual = 100 * (thread_data->read_stats.ops / (double) 
total_ops);
+
+    offset = xrandom (&thread_data->state) & (DISK_SIZE - MAX_REQUEST);
+    count = xrandom (&thread_data->state) & (MAX_REQUEST - 1);
+    if (count == 0) count = 1;
+
+    data = malloc (sizeof *data); /* freed in callback */
+    data->count = count;
+
+    if (pc_read_actual < pc_read) { /* read op */
+      data->stats = &thread_data->read_stats;
+      r = nbd_aio_pread (nbd, sink, count, offset,
+                         (nbd_completion_callback) {
+                           .callback = cb,
+                           .user_data = data,
+                         }, 0);
+    }
+    else {
+      if (xrandom (&thread_data->state) & 1) { /* write op */
+        data->stats = &thread_data->write_stats;
+        r = nbd_aio_pwrite (nbd, wrbuf, count, offset,
+                            (nbd_completion_callback) {
+                              .callback = cb,
+                              .user_data = data,
+                            }, 0);
+      }
+      else {                    /* trim op */
+        data->stats = &thread_data->trim_stats;
+        r = nbd_aio_trim (nbd, count, offset,
+                          (nbd_completion_callback) {
+                            .callback = cb,
+                            .user_data = data,
+                          }, 0);
+      }
+    }
+    if (r == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  return NULL;
+}
diff --git a/.gitignore b/.gitignore
index 74b4e8361..e62c23d8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,6 +66,7 @@ plugins/*/*.3
 /config.status
 /config.sub
 /configure
+/contrib/sparseloadtest
 /depcomp
 /docs/filter-links.pod
 /docs/lang-plugin-links.pod
-- 
2.39.2

_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to