--- 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 + * + * 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 +#include +#include +#include +#include +#include + +#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 + * 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 "); +MODULE_LICENSE("GPL");