---
 drivers/md/dm-throttle.c |  420 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 420 insertions(+)

Index: linux/drivers/md/dm-throttle.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/md/dm-throttle.c	2007-06-06 20:40:11.000000000 +0100
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2007 Red Hat GmbH
+ *
+ * Module Author: Heinz Mauelshagen <Mauelshagen@RedHat.de>
+ *
+ * This file is released under the GPL.
+ *
+ * Throttling test target to stack on top of arbitrary other block device.
+ *
+ * Read and write throttling is configurable via
+ * constructor and message interfaces.
+ *
+ * FIXME:
+ */
+
+static const char *version = "0.091";
+
+#include "dm.h"
+#include "dm-bio-list.h"
+#include "dm-io.h"
+#include "kcopyd.h"
+
+#include <linux/bio.h>
+#include <linux/blkdev.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+
+#define	DM_MSG_PREFIX	"dm-throttle"
+
+/* FIXME: factor these macros out to dm.h */
+#define	TI_ERR_RET(str, ret) \
+	do { ti->error = DM_MSG_PREFIX ": " str; return ret; } while(0);
+#define	TI_ERR(str)	TI_ERR_RET(str, -EINVAL)
+
+#define	MEASURE_TIME	HZ
+#define	STR_LEN(ptr, str) ptr, str, strlen(ptr)
+
+/* Development statistics. */
+struct stats {
+	atomic_t accounted[2];
+	atomic_t account_reset[2];
+	atomic_t deferred_io[2];
+	atomic_t io[2];
+	atomic_t kbs_hits[2];
+};
+
+/* Reset statistics variables. */
+static void stats_reset(struct stats *stats)
+{
+	int i = 2;
+
+	while (i--) {
+		atomic_set(&stats->accounted[i], 0);
+		atomic_set(&stats->account_reset[i], 0);
+		atomic_set(&stats->deferred_io[i], 0);
+		atomic_set(&stats->io[i], 0);
+		atomic_set(&stats->kbs_hits[i], 0);
+	}
+
+}
+
+/* Throttle context. */
+enum rw_flags { THROTTLED };
+struct throttle_c {
+	/* Throttling. */
+	struct mutex mutex;
+
+	/* Device to throttle. */
+	struct {
+		struct dm_dev *dev;
+		sector_t start;
+	} dev;
+
+	/* ctr parameters. */
+	struct params {
+		unsigned params;
+		unsigned bs[2];
+		unsigned kbs_ctr[2];
+	} params;
+
+	struct account {
+		struct ac_rw {
+			unsigned long last_jiffies;
+			unsigned size;
+		} rw[2];
+		unsigned long flags;
+	} account;
+
+	struct stats stats;
+};
+
+/* Return bytes/s value for kilobytes/s. */
+static inline unsigned to_bs(unsigned kbs)
+{
+	return kbs * 1024;
+}
+
+static inline unsigned to_kbs(unsigned bs)
+{
+	return bs / 1024;
+}
+
+/* Reset account. */
+static void account_reset(struct throttle_c *tc, int write)
+{
+	struct account *ac = &tc->account;
+
+	/* REMOVEME: statistics. */
+	atomic_inc(&tc->stats.account_reset[write]);
+
+	ac->rw[write].last_jiffies = jiffies;
+	ac->rw[write].size = 0;
+	clear_bit(write, &ac->flags);
+	smp_wmb();
+}
+
+/* Account IO sizes. */
+static void account(struct throttle_c *tc, struct bio *bio)
+{
+	int write = bio_data_dir(bio) == WRITE;
+
+	/* REMOVEME: statistics. */
+	atomic_inc(tc->stats.accounted + write);
+	if (tc->params.bs[write])
+		tc->account.rw[write].size += bio->bi_size;
+
+	smp_wmb();
+}
+
+/* Decide about throttling (ie. deferring bios). */
+static int throttle(struct throttle_c *tc, struct bio *bio)
+{
+	int write = bio_data_dir(bio) == WRITE;
+	unsigned bps; /* Bytes per second. */
+
+	smp_rmb();
+	bps = tc->params.bs[write];
+	if (bps) {
+		unsigned long j;
+		struct account *ac = &tc->account;
+		struct ac_rw *rw = ac->rw + write;
+
+		/* REMOVME: statistics. */
+		atomic_inc(tc->stats.kbs_hits + write);
+
+		/* Measure time exceeded or jiffies overrun. */
+		j = jiffies;
+		if (j > rw->last_jiffies + MEASURE_TIME ||
+	    	    j < rw->last_jiffies) {
+			account_reset(tc, write);
+			return 0;
+		}
+
+		/* In case we're throttled already. */
+		if (test_bit(write, &ac->flags))
+			return 1;
+
+		/*
+		 * If we aren't, check accounted
+		 * size vs. kilobytes per second.
+		 */
+		if (rw->size >= bps) {
+			set_bit(write, &ac->flags);
+			smp_wmb();
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Destruct a throttle mapping.
+ */
+static void throttle_dtr(struct dm_target *ti)
+{
+	struct throttle_c *tc = ti->private;
+
+	if (tc->dev.dev)
+		dm_put_device(ti, tc->dev.dev);
+
+	kfree(tc);
+}
+
+/*
+ * Construct a throttle mapping:
+ *
+ * #throttle_params <throttle_params>
+ * orig_dev_name orig_dev_start
+ *
+ * #throttle_params = 0 - 2
+ * throttle_parms = [read_kbs [write_kbs]]
+ *
+ */
+static int throttle_ctr(struct dm_target *ti, unsigned argc, char **argv)
+{
+	int throttle_params, r, read_kbs = 0, write_kbs = 0;
+	unsigned long long tmp;
+	sector_t start;
+	struct throttle_c *tc;
+
+	if (argc < 3 || argc > 5)
+		TI_ERR("Invalid argument count");
+
+	/* Get #throttle_params. */
+	if (sscanf(argv[0], "%d", &throttle_params) != 1 ||
+	    throttle_params < 0 || throttle_params > 2)
+		TI_ERR("Invalid throttle parameter number argument");
+
+	/* Handle any variable throttle parameters. */
+	if (throttle_params) {
+		/* Get throttle read kilobytes per second. */
+		if (sscanf(argv[1], "%d", &read_kbs) != 1 || read_kbs < 0)
+			TI_ERR("Invalid throttle read kilobytes per second");
+
+		if (throttle_params > 1) {
+			/* Get throttle read kilobytes per second. */
+			if (sscanf(argv[2], "%d", &write_kbs) != 1 ||
+			    write_kbs < 0)
+				TI_ERR("Invalid throttle write "
+				       "kilobytes per second");
+		}
+	}
+
+	if (sscanf(argv[2 + throttle_params], "%llu", &tmp) != 1)
+		TI_ERR("Invalid throttle device offset");
+
+	start = tmp;
+
+	/* Allocate throttle context. */
+	tc = kzalloc(sizeof(*tc), GFP_KERNEL);
+	if (!tc)
+		TI_ERR_RET("Cannot allocate throttle context", -ENOMEM);
+
+	ti->private = tc;
+
+	/* Aquire throttle device. */
+	r = dm_get_device(ti, argv[1 + throttle_params], start, ti->len,
+			  dm_table_get_mode(ti->table), &tc->dev.dev);
+	if (r) {
+		DMERR("Throttle device lookup failed");
+		goto err;
+	}
+
+	tc->dev.start = start;
+	tc->params.params = throttle_params;
+	tc->params.kbs_ctr[0] = read_kbs;
+	tc->params.kbs_ctr[1] = write_kbs;
+	tc->params.bs[0] = to_bs(read_kbs);
+	tc->params.bs[1] = to_bs(write_kbs);
+	mutex_init(&tc->mutex);
+	stats_reset(&tc->stats);
+	return 0;
+
+    err:
+	throttle_dtr(ti);
+	return -EINVAL;
+}
+
+/*
+ * Map a throttle io by handling it in the daemon.
+ */
+static int throttle_map(struct dm_target *ti, struct bio *bio,
+		      union map_info *map_context)
+{
+	int write = bio_data_dir(bio) == WRITE;
+	struct throttle_c *tc = ti->private;
+
+	/* REMOVEME: statistics */
+	atomic_inc(&tc->stats.io[write]);
+
+	/* Do I need to throttle bandwidth and delay this bio ? */
+   retry:
+	mutex_lock(&tc->mutex);
+	if (throttle(tc, bio)) {
+		atomic_inc(&tc->stats.deferred_io[write]);
+		mutex_unlock(&tc->mutex);
+		schedule_timeout_uninterruptible(1);
+		goto retry;
+	} else {
+		account(tc, bio);
+		mutex_unlock(&tc->mutex);
+	}
+
+	bio->bi_bdev = tc->dev.dev->bdev;
+	bio->bi_sector = bio->bi_sector - ti->begin + tc->dev.start;
+
+	return 1; /* Done with the bio; let dm core submit it. */
+}
+
+/* Message method. */
+static int throttle_message(struct dm_target *ti, unsigned argc, char **argv)
+{
+	int kbs, write;
+	char *cmd = argv[0];
+	struct throttle_c *tc = ti->private;
+
+	if (argc == 1 &&
+	    !strnicmp(STR_LEN(cmd, "stats_reset"))) {
+		/* Reset statistics. */
+		stats_reset(&tc->stats);
+		return 0;
+	}
+
+	if (argc == 2) {
+		if (!strnicmp(STR_LEN(cmd, "read_kbs")))
+			/* Adjust read kilobytes per second. */
+			write = 0;
+		else if (!strnicmp(STR_LEN(cmd, "write_kbs")))
+			/* Adjust write kilobytes per second. */
+			write = 1;
+		else
+			goto err;
+
+		if (sscanf(argv[1], "%d", &kbs) != 1 || kbs < 0) {
+			DMWARN("Unrecognised throttle %s_kbs parameter.",
+			       write ? "write" : "read");
+			return -EINVAL;
+		}
+
+		mutex_lock(&tc->mutex);
+		tc->params.bs[write] = to_bs(kbs);
+		account_reset(tc, write);
+		mutex_unlock(&tc->mutex);
+
+		return 0;
+	}
+
+   err:
+	DMWARN("Unrecognised throttle message received.");
+	return -EINVAL;
+}
+
+/* Status output method. */
+static int throttle_status(struct dm_target *ti, status_type_t type,
+			   char *result, unsigned maxlen)
+{
+	ssize_t sz = 0;
+	struct throttle_c *tc = ti->private;
+	struct stats *s = &tc->stats;
+	struct params *p = &tc->params;
+
+	switch (type) {
+	case STATUSTYPE_INFO:
+		DMEMIT("v=%s rkb=%u wkb=%u r=%u w=%u rd=%u wd=%u acr=%u acw=%u "
+		       "acrr=%u acwr=%u rsz=%u wsz=%u kbhr=%u kbhw=%u",
+		       version,
+		       to_kbs(p->bs[0]), to_kbs(p->bs[1]),
+		       atomic_read(s->io),
+		       atomic_read(s->io + 1),
+		       atomic_read(s->deferred_io),
+		       atomic_read(s->deferred_io + 1),
+		       atomic_read(s->accounted),
+		       atomic_read(s->accounted + 1),
+		       atomic_read(s->account_reset),
+		       atomic_read(s->account_reset + 1),
+		       tc->account.rw[0].size, tc->account.rw[1].size,
+		       atomic_read(s->kbs_hits),
+		       atomic_read(s->kbs_hits + 1));
+		break;
+
+	case STATUSTYPE_TABLE:
+		DMEMIT("%u", p->params);
+
+		if (p->params) {
+			DMEMIT(" %u", p->kbs_ctr[0]);
+
+			if (p->params > 1)
+				DMEMIT(" %u", p->kbs_ctr[1]);
+		}
+
+		DMEMIT(" %s %llu", tc->dev.dev->name,
+		       (unsigned long long) tc->dev.start);
+	}
+
+	return 0;
+}
+
+static struct target_type throttle_target = {
+	.name		= "throttle",
+	.version	= {1, 0, 0},
+	.module		= THIS_MODULE,
+	.ctr		= throttle_ctr,
+	.dtr		= throttle_dtr,
+	.map		= throttle_map,
+	.message	= throttle_message,
+	.status		= throttle_status,
+};
+
+int __init dm_throttle_init(void)
+{
+	int r = dm_register_target(&throttle_target);
+
+	if (r < 0)
+		DMERR("register failed %d", r);
+	else
+		DMINFO("registered %s", version);
+
+	return r;
+}
+
+void dm_throttle_exit(void)
+{
+	int r = dm_unregister_target(&throttle_target);
+
+	if (r < 0)
+		DMERR("unregister failed %d", r);
+	else
+		DMINFO("unregistered %s", version);
+}
+
+/* Module hooks */
+module_init(dm_throttle_init);
+module_exit(dm_throttle_exit);
+
+MODULE_DESCRIPTION(DM_NAME "device-mapper throttle target");
+MODULE_AUTHOR("Heinz Mauelshagen <hjm@redhat.com>");
+MODULE_LICENSE("GPL");