Add nbdkit rate filter.

This commit is contained in:
Richard W.M. Jones 2019-03-05 19:34:23 +00:00
parent df26f1b46f
commit 18f1736652
3 changed files with 855 additions and 2 deletions

View File

@ -1,7 +1,7 @@
From d77c93b1cc42fc6cbda1b2abf941b001ad741cba Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Sat, 2 Mar 2019 08:28:04 +0000
Subject: [PATCH] common: utils: Use include/ directory to get
Subject: [PATCH 1/2] common: utils: Use include/ directory to get
<nbdkit-plugin.h>
This worked before because nbdkit was installed by the system package

View File

@ -0,0 +1,844 @@
From 53d75b09d9873c69a2d0e7f514a24af746f14292 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Tue, 5 Mar 2019 06:24:29 +0000
Subject: [PATCH 2/2] Add new filter for rate-limiting connections.
As an example this creates a RAM disk with the rate filter on top,
then copies the data out to a local file:
$ time ./nbdkit --filter=rate memory size=50M rate=10M \
--run 'qemu-img convert $nbd /var/tmp/disk.img'
real 0m40.023s
user 0m0.032s
sys 0m0.081s
The size of the RAM disk is 8*50 = 400 Mbits, and we copy out at a
rate of 10 Mbps, so it takes exactly 40 seconds.
---
filters/delay/nbdkit-delay-filter.pod | 4 +-
filters/rate/nbdkit-rate-filter.pod | 86 ++++++++++
configure.ac | 2 +
filters/rate/bucket.h | 71 ++++++++
filters/rate/bucket.c | 166 ++++++++++++++++++
filters/rate/rate.c | 231 ++++++++++++++++++++++++++
TODO | 9 +
filters/rate/Makefile.am | 64 +++++++
tests/Makefile.am | 6 +-
tests/test-rate.sh | 60 +++++++
10 files changed, 697 insertions(+), 2 deletions(-)
create mode 100644 filters/rate/nbdkit-rate-filter.pod
create mode 100644 filters/rate/bucket.h
create mode 100644 filters/rate/bucket.c
create mode 100644 filters/rate/rate.c
create mode 100644 filters/rate/Makefile.am
create mode 100755 tests/test-rate.sh
diff --git a/filters/delay/nbdkit-delay-filter.pod b/filters/delay/nbdkit-delay-filter.pod
index 7009a8c..c2eb172 100644
--- a/filters/delay/nbdkit-delay-filter.pod
+++ b/filters/delay/nbdkit-delay-filter.pod
@@ -17,6 +17,7 @@ nbdkit-delay-filter - nbdkit delay filter
C<nbdkit-delay-filter> is a filter that delays read and write requests
by some seconds or milliseconds. This is used to simulate a slow or
remote server, or to test certain kinds of race conditions in Linux.
+To limit server bandwidth use L<nbdkit-rate-filter(1)> instead.
=head1 EXAMPLES
@@ -74,7 +75,8 @@ milliseconds.
=head1 SEE ALSO
L<nbdkit(1)>,
-L<nbdkit-filter(3)>.
+L<nbdkit-filter(3)>,
+L<nbdkit-rate-filter(1)>.
=head1 AUTHORS
diff --git a/filters/rate/nbdkit-rate-filter.pod b/filters/rate/nbdkit-rate-filter.pod
new file mode 100644
index 0000000..99a0373
--- /dev/null
+++ b/filters/rate/nbdkit-rate-filter.pod
@@ -0,0 +1,86 @@
+=head1 NAME
+
+nbdkit-rate-filter - limit bandwidth by connection or server
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=rate PLUGIN [PLUGIN-ARGS...]
+ [rate=BITSPERSEC]
+ [connection-rate=BITSPERSEC]
+
+=head1 DESCRIPTION
+
+C<nbdkit-rate-filter> is a filter that limits the bandwidth that can
+be used by the server. Limits can be applied per connection and/or
+for the server as a whole.
+
+=head1 EXAMPLES
+
+=over 4
+
+=item nbdkit --filter=rate memory size=64M rate=1M
+
+Create a 64M RAM disk and limit server bandwidth as a whole to a
+maximum of S<1 Mbps> (megabit per second).
+
+=item nbdkit --filter=rate memory size=64M connection-rate=50K
+
+Limit each connection to S<50 Kbps> (kilobits per second). However as
+there is no limit to the number of simultaneous connections this does
+not limit overall server bandwidth.
+
+=item nbdkit --filter=rate memory size=64M connection-rate=50K rate=1M
+
+Limit each connection to S<50 Kbps>. Additionally the total bandwidth
+across all connections to the server is limited to S<1 Mbps>.
+
+=back
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<connection-rate=>BITSPERSEC
+
+Limit each connection to C<BITSPERSEC>.
+
+=item B<rate=>BITSPERSEC
+
+Limit total bandwidth across all connections to C<BITSPERSEC>.
+
+=back
+
+C<BITSPERSEC> can be specified as a simple number, or you can use a
+number followed by C<K>, C<M> etc to mean kilobits, megabits and so
+on.
+
+=head1 NOTES
+
+You can specify C<rate> and C<connection-rate> on their own or
+together. If you specify neither, the filter is turned off.
+
+The rate filter approximates the bandwidth used by the NBD protocol on
+the wire. Some operations such as zeroing and trimming are
+effectively free (because only a tiny NBD message is sent over the
+network) and so do not count against the bandwidth limit. NBD and TCP
+protocol overhead is not included, so you may find that other tools
+such as L<tc(8)> and L<iptables(8)> give more accurate results.
+
+here are separate bandwidth limits for read and write (ie. download
+and upload to the server).
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-delay-filter(1)>,
+L<nbdkit-filter(3)>,
+L<iptables(8)>,
+L<tc(8)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2019 Red Hat Inc.
diff --git a/configure.ac b/configure.ac
index 9e7e5ca..467d48f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -819,6 +819,7 @@ filters="\
nozero \
offset \
partition \
+ rate \
truncate \
xz \
"
@@ -889,6 +890,7 @@ AC_CONFIG_FILES([Makefile
filters/nozero/Makefile
filters/offset/Makefile
filters/partition/Makefile
+ filters/rate/Makefile
filters/truncate/Makefile
filters/xz/Makefile
fuzzing/Makefile
diff --git a/filters/rate/bucket.h b/filters/rate/bucket.h
new file mode 100644
index 0000000..bc36fc1
--- /dev/null
+++ b/filters/rate/bucket.h
@@ -0,0 +1,71 @@
+/* nbdkit
+ * Copyright (C) 2018-2019 Red Hat Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef NBDKIT_BUCKET_H
+#define NBDKIT_BUCKET_H
+
+#include <stdint.h>
+#include <time.h>
+#include <sys/time.h>
+
+/* A token bucket. */
+struct bucket {
+ uint64_t rate; /* Fill rate. 0 = no limit set. */
+ uint64_t capacity; /* Maximum capacity of the bucket. */
+ uint64_t level; /* How full is the bucket now? */
+ struct timeval tv; /* Last time we updated the level. */
+};
+
+/* Initialize the bucket structure. Capacity is expressed in
+ * rate-equivalent seconds.
+ */
+extern void bucket_init (struct bucket *bucket,
+ uint64_t rate, double capacity);
+
+/* Take up to N tokens from the bucket. Returns the number
+ * of tokens remaining (that could not be taken from the bucket),
+ * or 0 if we were able to take all N tokens from the bucket.
+ *
+ * In the case that the return value > 0, *TS is initialized with the
+ * estimated length of time you should sleep. Note that *TS is _NOT_
+ * initialized if the return value == 0, because the caller should not
+ * sleep in that case.
+ *
+ * In the case where the caller needs to sleep, it must make a further
+ * call to bucket_run before proceeding, since another thread may have
+ * "stolen" the tokens while you were sleeping.
+ */
+extern uint64_t bucket_run (struct bucket *bucket, uint64_t n,
+ struct timespec *ts);
+
+#endif /* NBDKIT_BUCKET_H */
diff --git a/filters/rate/bucket.c b/filters/rate/bucket.c
new file mode 100644
index 0000000..b68d7b3
--- /dev/null
+++ b/filters/rate/bucket.c
@@ -0,0 +1,166 @@
+/* nbdkit
+ * Copyright (C) 2018-2019 Red Hat Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/* This filter is implemented using a Token Bucket
+ * (https://en.wikipedia.org/wiki/Token_bucket). There are two
+ * buckets per connection (one each for reading and writing) and two
+ * global buckets (also for reading and writing).
+ *
+ * We add tokens at the desired rate (the per-connection rate for the
+ * connection buckets, and the global rate for the global buckets).
+ * Note that we don't actually keep the buckets updated in real time
+ * because as a filter we are called asynchronously. Instead for each
+ * bucket we store the last time we were called and add the
+ * appropriate number of tokens when we are called next.
+ *
+ * The bucket capacity controls the burstiness allowed. This is
+ * hard-coded at the moment but could be configurable. All buckets
+ * start off full.
+ *
+ * When a packet is to be read or written, if there are sufficient
+ * tokens in the bucket then the packet may be immediately passed
+ * through to the underlying plugin. The number of bits used is
+ * deducted from the appropriate per-connection and global bucket.
+ *
+ * If there are insufficient tokens then the packet must be delayed.
+ * This is done by inserting a sleep which has an estimated length
+ * that is long enough based on the rate at which enough tokens will
+ * replenish the bucket to allow the packet to be sent next time.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <nbdkit-filter.h>
+
+#include "minmax.h"
+
+#include "bucket.h"
+
+int rate_debug_bucket; /* -D rate.bucket=1 */
+
+void
+bucket_init (struct bucket *bucket, uint64_t rate, double capacity)
+{
+ bucket->rate = rate;
+
+ /* Capacity is expressed in seconds, but we want to know the
+ * capacity in tokens, so multiply by the rate to get this.
+ */
+ bucket->capacity = rate * capacity;
+
+ /* Buckets start off full. */
+ bucket->level = capacity;
+
+ gettimeofday (&bucket->tv, NULL);
+}
+
+/* Return the number of microseconds in y - x. */
+static int64_t
+tvdiff (const struct timeval *x, const struct timeval *y)
+{
+ int64_t usec;
+
+ usec = (y->tv_sec - x->tv_sec) * 1000000;
+ usec += y->tv_usec - x->tv_usec;
+ return usec;
+}
+
+uint64_t
+bucket_run (struct bucket *bucket, uint64_t n, struct timespec *ts)
+{
+ struct timeval now;
+ int64_t usec;
+ uint64_t add, nsec;
+
+ /* rate == 0 is a special case meaning that there is no limit being
+ * enforced.
+ */
+ if (bucket->rate == 0)
+ return 0;
+
+ gettimeofday (&now, NULL);
+
+ /* Work out how much time has elapsed since we last added tokens to
+ * the bucket, and add the correct number of tokens.
+ */
+ usec = tvdiff (&bucket->tv, &now);
+ if (usec < 0) /* Maybe happens if system time not monotonic? */
+ usec = 0;
+
+ add = bucket->rate * usec / 1000000;
+ add = MIN (add, bucket->capacity - bucket->level);
+ if (rate_debug_bucket)
+ nbdkit_debug ("bucket %p: adding %" PRIu64 " tokens, new level %" PRIu64,
+ bucket, add, bucket->level + add);
+ bucket->level += add;
+ bucket->tv = now;
+
+ /* Can we deduct N tokens from the bucket? If yes then we're good,
+ * and we can return 0 which means the caller won't sleep.
+ */
+ if (bucket->level >= n) {
+ if (rate_debug_bucket)
+ nbdkit_debug ("bucket %p: deducting %" PRIu64 " tokens", bucket, n);
+ bucket->level -= n;
+ return 0;
+ }
+
+ if (rate_debug_bucket)
+ nbdkit_debug ("bucket %p: deducting %" PRIu64 " tokens, bucket empty, "
+ "need another %" PRIu64 " tokens",
+ bucket, bucket->level, n - bucket->level);
+
+ n -= bucket->level;
+ bucket->level = 0;
+
+ /* Now we need to estimate how long it will take to add N tokens to
+ * the bucket, which is how long the caller must sleep for.
+ */
+ nsec = 1000000000 * n / bucket->rate;
+ ts->tv_sec = nsec / 1000000000;
+ ts->tv_nsec = nsec % 1000000000;
+
+ if (rate_debug_bucket)
+ nbdkit_debug ("bucket %p: sleeping for %.1f seconds", bucket,
+ nsec / 1000000000.);
+
+ return n;
+}
diff --git a/filters/rate/rate.c b/filters/rate/rate.c
new file mode 100644
index 0000000..a0c6ae3
--- /dev/null
+++ b/filters/rate/rate.c
@@ -0,0 +1,231 @@
+/* nbdkit
+ * Copyright (C) 2018-2019 Red Hat Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/* For a note on the implementation of this filter, see bucket.c. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <pthread.h>
+
+#include <nbdkit-filter.h>
+
+#include "bucket.h"
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+
+/* Per-connection and global limit, both in bits per second, with zero
+ * meaning not set / not enforced.
+ */
+static uint64_t connection_rate = 0;
+static uint64_t rate = 0;
+
+/* Bucket capacity controls the burst rate. It is expressed as the
+ * length of time in "rate-equivalent seconds" that the client can
+ * burst for after a period of inactivity. This could be adjustable
+ * in future.
+ */
+#define BUCKET_CAPACITY 2.0
+
+/* Global read and write buckets. */
+static struct bucket read_bucket;
+static pthread_mutex_t read_bucket_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct bucket write_bucket;
+static pthread_mutex_t write_bucket_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Per-connection handle. */
+struct rate_handle {
+ /* Per-connection read and write buckets. */
+ struct bucket read_bucket;
+ pthread_mutex_t read_bucket_lock;
+ struct bucket write_bucket;
+ pthread_mutex_t write_bucket_lock;
+};
+
+/* Called for each key=value passed on the command line. */
+static int
+rate_config (nbdkit_next_config *next, void *nxdata,
+ const char *key, const char *value)
+{
+ if (strcmp (key, "rate") == 0) {
+ if (rate > 0) {
+ nbdkit_error ("rate set twice on the command line");
+ return -1;
+ }
+ rate = nbdkit_parse_size (value);
+ if (rate == -1)
+ return -1;
+ if (rate == 0) {
+ nbdkit_error ("rate cannot be set to 0");
+ return -1;
+ }
+ return 0;
+ }
+ else if (strcmp (key, "connection-rate") == 0) {
+ if (connection_rate > 0) {
+ nbdkit_error ("connection-rate set twice on the command line");
+ return -1;
+ }
+ connection_rate = nbdkit_parse_size (value);
+ if (connection_rate == -1)
+ return -1;
+ if (connection_rate == 0) {
+ nbdkit_error ("connection-rate cannot be set to 0");
+ return -1;
+ }
+ return 0;
+ }
+ else
+ return next (nxdata, key, value);
+}
+
+static int
+rate_config_complete (nbdkit_next_config_complete *next, void *nxdata)
+{
+ /* Initialize the global buckets. */
+ bucket_init (&read_bucket, rate, BUCKET_CAPACITY);
+ bucket_init (&write_bucket, rate, BUCKET_CAPACITY);
+
+ return next (nxdata);
+}
+
+#define rate_config_help \
+ "rate=BITSPERSEC Limit total bandwidth.\n" \
+ "connection-rate=BITSPERSEC Limit per-connection bandwidth."
+
+/* Create the per-connection handle. */
+static void *
+rate_open (nbdkit_next_open *next, void *nxdata, int readonly)
+{
+ struct rate_handle *h;
+
+ if (next (nxdata, readonly) == -1)
+ return NULL;
+
+ h = malloc (sizeof *h);
+ if (h == NULL) {
+ nbdkit_error ("malloc: %m");
+ return NULL;
+ }
+
+ bucket_init (&h->read_bucket, connection_rate, BUCKET_CAPACITY);
+ bucket_init (&h->write_bucket, connection_rate, BUCKET_CAPACITY);
+ pthread_mutex_init (&h->read_bucket_lock, NULL);
+ pthread_mutex_init (&h->write_bucket_lock, NULL);
+
+ return h;
+}
+
+/* Free up the per-connection handle. */
+static void
+rate_close (void *handle)
+{
+ struct rate_handle *h = handle;
+
+ pthread_mutex_destroy (&h->read_bucket_lock);
+ pthread_mutex_destroy (&h->write_bucket_lock);
+ free (h);
+}
+
+static inline void
+maybe_sleep (struct bucket *bucket, pthread_mutex_t *lock, uint32_t count)
+{
+ struct timespec ts;
+ uint64_t bits;
+
+ /* Count is in bytes, but we rate limit using bits. We could
+ * multiply this by 10 to include start/stop but let's not
+ * second-guess the transport layers underneath.
+ */
+ bits = count * UINT64_C(8);
+
+ while (bits > 0) {
+ /* Run the token bucket algorithm. */
+ pthread_mutex_lock (lock);
+ bits = bucket_run (bucket, bits, &ts);
+ pthread_mutex_unlock (lock);
+
+ if (bits > 0)
+ nanosleep (&ts, NULL);
+ }
+}
+
+/* Read data. */
+static int
+rate_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, void *buf, uint32_t count, uint64_t offset,
+ uint32_t flags, int *err)
+{
+ struct rate_handle *h = handle;
+
+ maybe_sleep (&read_bucket, &read_bucket_lock, count);
+ maybe_sleep (&h->read_bucket, &h->read_bucket_lock, count);
+
+ return next_ops->pread (nxdata, buf, count, offset, flags, err);
+}
+
+/* Write data. */
+static int
+rate_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle,
+ const void *buf, uint32_t count, uint64_t offset, uint32_t flags,
+ int *err)
+{
+ struct rate_handle *h = handle;
+
+ maybe_sleep (&write_bucket, &write_bucket_lock, count);
+ maybe_sleep (&h->write_bucket, &h->write_bucket_lock, count);
+
+ return next_ops->pwrite (nxdata, buf, count, offset, flags, err);
+}
+
+static struct nbdkit_filter filter = {
+ .name = "rate",
+ .longname = "nbdkit rate filter",
+ .version = PACKAGE_VERSION,
+ .config = rate_config,
+ .config_complete = rate_config_complete,
+ .config_help = rate_config_help,
+ .open = rate_open,
+ .close = rate_close,
+ .pread = rate_pread,
+ .pwrite = rate_pwrite,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/TODO b/TODO
index 59590a1..b589127 100644
--- a/TODO
+++ b/TODO
@@ -129,6 +129,15 @@ Suggestions for filters
* nbdkit-cache-filter should handle ENOSPC errors automatically by
reclaiming blocks from the cache
+nbdkit-rate-filter:
+
+* allow other kinds of traffic shaping such as VBR
+
+* limit traffic per client (ie. per IP address)
+
+* split large requests to avoid long, lumpy sleeps when request size
+ is much larger than rate limit
+
Filters for security
--------------------
diff --git a/filters/rate/Makefile.am b/filters/rate/Makefile.am
new file mode 100644
index 0000000..c39aa01
--- /dev/null
+++ b/filters/rate/Makefile.am
@@ -0,0 +1,64 @@
+# nbdkit
+# Copyright (C) 2018-2019 Red Hat Inc.
+# All rights reserved.
+#
+# 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
+
+EXTRA_DIST = nbdkit-rate-filter.pod
+
+filter_LTLIBRARIES = nbdkit-rate-filter.la
+
+nbdkit_rate_filter_la_SOURCES = \
+ bucket.c \
+ bucket.h \
+ rate.c \
+ $(top_srcdir)/include/nbdkit-filter.h
+
+nbdkit_rate_filter_la_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/include
+nbdkit_rate_filter_la_CFLAGS = \
+ $(WARNINGS_CFLAGS)
+nbdkit_rate_filter_la_LDFLAGS = \
+ -module -avoid-version -shared \
+ -Wl,--version-script=$(top_srcdir)/filters/filters.syms
+
+if HAVE_POD
+
+man_MANS = nbdkit-rate-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-rate-filter.1: nbdkit-rate-filter.pod
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3992d9b..d1e6f0e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,5 +1,5 @@
# nbdkit
-# Copyright (C) 2013-2018 Red Hat Inc.
+# Copyright (C) 2013-2019 Red Hat Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -97,6 +97,7 @@ EXTRA_DIST = \
test-python-exception.sh \
test.pl \
test.py \
+ test-rate.sh \
test.rb \
test.tcl \
test-shebang-perl.sh \
@@ -810,6 +811,9 @@ if HAVE_GUESTFISH
TESTS += test-partition2.sh
endif HAVE_GUESTFISH
+# rate filter test.
+TESTS += test-rate.sh
+
# truncate filter tests.
TESTS += \
test-truncate1.sh \
diff --git a/tests/test-rate.sh b/tests/test-rate.sh
new file mode 100755
index 0000000..010ef19
--- /dev/null
+++ b/tests/test-rate.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018-2019 Red Hat Inc.
+# All rights reserved.
+#
+# 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.
+
+source ./functions.sh
+set -e
+set -x
+
+requires qemu-img --version
+
+files="rate.img rate.time rate.err"
+rm -f $files
+cleanup_fn rm -f $files
+
+# This should take no less than 20 seconds to run:
+# (8 * 25 * 1024 * 1024) / (10 * 1024 * 1024) = 20
+
+# We are using the bash time builtin, so setting TIMEFORMAT will
+# control the output format of the time builtin. For strange use of
+# { ; } here, see: https://stackoverflow.com/a/13356654
+set +x
+{ TIMEFORMAT="%0R" ; time nbdkit --filter=rate memory size=25M rate=10M --run 'qemu-img convert -p $nbd rate.img' 2>rate.err ; } 2>rate.time
+set -x
+
+cat rate.err ||:
+
+seconds="$( cat rate.time )"
+if [ "$seconds" -lt 20 ]; then
+ echo "$0: rate filter failed: command took $seconds seconds, expected > 20"
+ exit 1
+fi
--
2.20.1

View File

@ -32,7 +32,7 @@
Name: nbdkit
Version: 1.11.6
Release: 1%{?dist}
Release: 2%{?dist}
Summary: NBD server
License: BSD
@ -47,6 +47,8 @@ Source2: libguestfs.keyring
# Upstream fix to include directory.
Patch1: 0001-common-utils-Use-include-directory-to-get-nbdkit-plu.patch
# Posted but not upstream patch to add nbdkit-rate-filter.
Patch2: 0002-Add-new-filter-for-rate-limiting-connections.patch
%if 0%{patches_touch_autotools}
BuildRequires: autoconf, automake, libtool
@ -546,6 +548,8 @@ nbdkit-offset-filter Serve an offset and range.
nbdkit-partition-filter Serve a single partition.
nbdkit-rate-filter Limit bandwidth by connection or server.
nbdkit-truncate-filter Truncate, expand, round up or round down size.
@ -938,6 +942,7 @@ popd
%{_libdir}/%{name}/filters/nbdkit-nozero-filter.so
%{_libdir}/%{name}/filters/nbdkit-offset-filter.so
%{_libdir}/%{name}/filters/nbdkit-partition-filter.so
%{_libdir}/%{name}/filters/nbdkit-rate-filter.so
%{_libdir}/%{name}/filters/nbdkit-truncate-filter.so
%{_mandir}/man1/nbdkit-blocksize-filter.1*
%{_mandir}/man1/nbdkit-cache-filter.1*
@ -949,6 +954,7 @@ popd
%{_mandir}/man1/nbdkit-nozero-filter.1*
%{_mandir}/man1/nbdkit-offset-filter.1*
%{_mandir}/man1/nbdkit-partition-filter.1*
%{_mandir}/man1/nbdkit-rate-filter.1*
%{_mandir}/man1/nbdkit-truncate-filter.1*
@ -986,6 +992,9 @@ popd
%changelog
* Tue Mar 05 2019 Richard W.M. Jones <rjones@redhat.com> - 1.11.6-2
- Add nbdkit rate filter.
* Fri Mar 01 2019 Richard W.M. Jones <rjones@redhat.com> - 1.11.6-1
- New upstream version 1.11.6.
- Add linuxdisk plugin.