---
filters/delay/nbdkit-delay-filter.pod | 4 +-
filters/rate/nbdkit-rate-filter.pod | 84 +++++++++
configure.ac | 2 +
filters/rate/bucket.h | 62 +++++++
filters/rate/bucket.c | 173 +++++++++++++++++++
filters/rate/rate.c | 235 ++++++++++++++++++++++++++
TODO | 9 +
filters/rate/Makefile.am | 64 +++++++
tests/Makefile.am | 6 +-
tests/test-rate.sh | 60 +++++++
10 files changed, 697 insertions(+), 2 deletions(-)
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..795c7b8
--- /dev/null
+++ b/filters/rate/nbdkit-rate-filter.pod
@@ -0,0 +1,84 @@
+=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
+
+You can specify C<rate> and C<connection-rate> on their own or
+together. If you specify neither then the filter is turned off.
+
+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
+
+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.
+
+There are separate bandwidth limits for read and write (ie. upload and
+download to the server).
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-delay-filter(1)>.
+L<nbdkit-filter(3)>.
+
+=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..0e8c8f7
--- /dev/null
+++ b/filters/rate/bucket.h
@@ -0,0 +1,62 @@
+/* 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 init_bucket (struct bucket *bucket,
+ uint64_t rate, double capacity);
+
+/* Take up to N tokens from the bucket. If we couldn't take N tokens,
+ * then *N is updated with the number of remaining tokens that we are
+ * waiting for. Returns the length of time we should sleep (which may
+ * be 0).
+ */
+extern struct timespec bucket_run (struct bucket *bucket, uint64_t *n);
+
+#endif /* NBDKIT_BUCKET_H */
diff --git a/filters/rate/bucket.c b/filters/rate/bucket.c
new file mode 100644
index 0000000..ccd657e
--- /dev/null
+++ b/filters/rate/bucket.c
@@ -0,0 +1,173 @@
+/* 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
+init_bucket (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;
+}
+
+struct timespec
+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) {
+ *n = 0;
+ ts.tv_sec = ts.tv_nsec = 0;
+ return ts;
+ }
+
+ gettimeofday (&now, NULL);
+
+ /* Can we add tokens to the bucket? Work out how much time has
+ * elapsed since we last did this.
+ */
+ 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,
+ * we can return with TS = 0 which means the caller won't sleep.
+ */
+ if (bucket->level >= *n) {
+ if (rate_debug_bucket)
+ nbdkit_debug ("bucket %p: deducting %" PRIu64 " tokens, no
sleep",
+ bucket, *n);
+ bucket->level -= *n;
+ *n = 0;
+ ts.tv_sec = ts.tv_nsec = 0;
+ return ts;
+ }
+
+ if (rate_debug_bucket)
+ nbdkit_debug ("bucket %p: deducting %" PRIu64 " tokens, 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 ts;
+}
diff --git a/filters/rate/rate.c b/filters/rate/rate.c
new file mode 100644
index 0000000..6827e16
--- /dev/null
+++ b/filters/rate/rate.c
@@ -0,0 +1,235 @@
+/* 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. */
+ init_bucket (&read_bucket, rate, BUCKET_CAPACITY);
+ init_bucket (&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;
+ }
+
+ init_bucket (&h->read_bucket, connection_rate, BUCKET_CAPACITY);
+ init_bucket (&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);
+
+ /* Run the token bucket algorithm. It will set ts to the sleep time
+ * required, if any, and update bits with the remaining number of
+ * bits we have to wait for.
+ */
+ again:
+ pthread_mutex_lock (lock);
+ ts = bucket_run (bucket, &bits);
+ pthread_mutex_unlock (lock);
+
+ if (ts.tv_sec > 0 || ts.tv_nsec > 0) {
+ nanosleep (&ts, NULL);
+ goto again;
+ }
+}
+
+/* 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