From 564ca97254984165e06d5fd4b1270bac95ca55fb Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 14 Dec 2007 12:21:19 +0100 Subject: [PATCH] Add support for 'bssplit' option, fine grained block size contrl From the HOWTO addition: bssplit=str Sometimes you want even finer grained control of the block sizes issued, not just an even split between them. This option allows you to weight various block sizes, so that you are able to define a specific amount of block sizes issued. The format for this option is: bssplit=blocksize/percentage:blocksize/percentage for as many block sizes as needed. So if you want to define a workload that has 50% 64k blocks, 10% 4k blocks, and 40% 32k blocks, you would write: bssplit=4k/10:64k/50:32k/40 Ordering does not matter. If the percentage is left blank, fio will fill in the remaining values evenly. So a bssplit option like this one: bssplit=4k/50:1k/:32k/ would have 50% 4k ios, and 25% 1k and 32k ios. The percentages always add up to 100, if bssplit is given a range that adds up to more, it will error out. Signed-off-by: Jens Axboe --- HOWTO | 24 +++++++++++ fio.h | 7 ++++ io_u.c | 16 +++++++- options.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ parse.c | 2 +- parse.h | 1 + 6 files changed, 169 insertions(+), 2 deletions(-) diff --git a/HOWTO b/HOWTO index d5fb52cf..569b998f 100644 --- a/HOWTO +++ b/HOWTO @@ -281,6 +281,30 @@ bsrange=irange Instead of giving a single block size, specify a range writes, however a second range can be given after a comma. See bs=. +bssplit=str Sometimes you want even finer grained control of the + block sizes issued, not just an even split between them. + This option allows you to weight various block sizes, + so that you are able to define a specific amount of + block sizes issued. The format for this option is: + + bssplit=blocksize/percentage:blocksize/percentage + + for as many block sizes as needed. So if you want to define + a workload that has 50% 64k blocks, 10% 4k blocks, and + 40% 32k blocks, you would write: + + bssplit=4k/10:64k/50:32k/40 + + Ordering does not matter. If the percentage is left blank, + fio will fill in the remaining values evenly. So a bssplit + option like this one: + + bssplit=4k/50:1k/:32k/ + + would have 50% 4k ios, and 25% 1k and 32k ios. The percentages + always add up to 100, if bssplit is given a range that adds + up to more, it will error out. + blocksize_unaligned bs_unaligned If this option is given, any byte size value within bsrange may be used as a block range. This typically wont work with diff --git a/fio.h b/fio.h index ca9fede6..ca6e2904 100644 --- a/fio.h +++ b/fio.h @@ -373,6 +373,11 @@ struct thread_stat { unsigned long total_run_time; }; +struct bssplit { + unsigned int bs; + unsigned char perc; +}; + struct thread_options { int pad; char *description; @@ -395,6 +400,8 @@ struct thread_options { unsigned int bs[2]; unsigned int min_bs[2]; unsigned int max_bs[2]; + struct bssplit *bssplit; + unsigned int bssplit_nr; unsigned int nr_files; unsigned int open_files; diff --git a/io_u.c b/io_u.c index 93c451cb..7890a870 100644 --- a/io_u.c +++ b/io_u.c @@ -186,7 +186,21 @@ static unsigned int get_next_buflen(struct thread_data *td, struct io_u *io_u) buflen = td->o.min_bs[ddir]; else { r = os_random_long(&td->bsrange_state); - buflen = (unsigned int) (1 + (double) (td->o.max_bs[ddir] - 1) * r / (RAND_MAX + 1.0)); + if (!td->o.bssplit_nr) + buflen = (unsigned int) (1 + (double) (td->o.max_bs[ddir] - 1) * r / (RAND_MAX + 1.0)); + else { + long perc = 0; + unsigned int i; + + for (i = 0; i < td->o.bssplit_nr; i++) { + struct bssplit *bsp = &td->o.bssplit[i]; + + buflen = bsp->bs; + perc += bsp->perc; + if (r <= ((LONG_MAX / 100L) * perc)) + break; + } + } if (!td->o.bs_unaligned) buflen = (buflen + td->o.min_bs[ddir] - 1) & ~(td->o.min_bs[ddir] - 1); } diff --git a/options.c b/options.c index c56b9dfb..055fbab7 100644 --- a/options.c +++ b/options.c @@ -28,6 +28,120 @@ static char *get_opt_postfix(const char *str) return strdup(p); } +static int bs_cmp(const void *p1, const void *p2) +{ + const struct bssplit *bsp1 = p1; + const struct bssplit *bsp2 = p2; + + return bsp1->perc < bsp2->perc; +} + +static int str_bssplit_cb(void *data, const char *input) +{ + struct thread_data *td = data; + char *fname, *str, *p; + unsigned int i, perc, perc_missing; + unsigned int max_bs, min_bs; + long long val; + + p = str = strdup(input); + + strip_blank_front(&str); + strip_blank_end(str); + + td->o.bssplit_nr = 4; + td->o.bssplit = malloc(4 * sizeof(struct bssplit)); + + i = 0; + max_bs = 0; + min_bs = -1; + while ((fname = strsep(&str, ":")) != NULL) { + char *perc_str; + + if (!strlen(fname)) + break; + + /* + * grow struct buffer, if needed + */ + if (i == td->o.bssplit_nr) { + td->o.bssplit_nr <<= 1; + td->o.bssplit = realloc(td->o.bssplit, td->o.bssplit_nr * sizeof(struct bssplit)); + } + + perc_str = strstr(fname, "/"); + if (perc_str) { + *perc_str = '\0'; + perc_str++; + perc = atoi(perc_str); + if (perc > 100) + perc = 100; + else if (!perc) + perc = -1; + } else + perc = -1; + + if (str_to_decimal(fname, &val, 1)) { + log_err("fio: bssplit conversion failed\n"); + free(td->o.bssplit); + return 1; + } + + if (val > max_bs) + max_bs = val; + if (val < min_bs) + min_bs = val; + + td->o.bssplit[i].bs = val; + td->o.bssplit[i].perc = perc; + i++; + } + + td->o.bssplit_nr = i; + + /* + * Now check if the percentages add up, and how much is missing + */ + perc = perc_missing = 0; + for (i = 0; i < td->o.bssplit_nr; i++) { + struct bssplit *bsp = &td->o.bssplit[i]; + + if (bsp->perc == (unsigned char) -1) + perc_missing++; + else + perc += bsp->perc; + } + + if (perc > 100) { + log_err("fio: bssplit percentages add to more than 100%%\n"); + free(td->o.bssplit); + return 1; + } + /* + * If values didn't have a percentage set, divide the remains between + * them. + */ + if (perc_missing) { + for (i = 0; i < td->o.bssplit_nr; i++) { + struct bssplit *bsp = &td->o.bssplit[i]; + + if (bsp->perc == (unsigned char) -1) + bsp->perc = (100 - perc) / perc_missing; + } + } + + td->o.min_bs[DDIR_READ] = td->o.min_bs[DDIR_WRITE] = min_bs; + td->o.max_bs[DDIR_READ] = td->o.max_bs[DDIR_WRITE] = max_bs; + + /* + * now sort based on percentages, for ease of lookup + */ + qsort(td->o.bssplit, td->o.bssplit_nr, sizeof(struct bssplit), bs_cmp); + + free(p); + return 0; +} + static int str_rw_cb(void *data, const char *str) { struct thread_data *td = data; @@ -459,6 +573,13 @@ static struct fio_option options[] = { .help = "Set block size range (in more detail than bs)", .parent = "rw", }, + { + .name = "bssplit", + .type = FIO_OPT_STR, + .cb = str_bssplit_cb, + .help = "Set a specific mix of block sizes", + .parent = "rw", + }, { .name = "bs_unaligned", .alias = "blocksize_unaligned", diff --git a/parse.c b/parse.c index 879a3aa3..f907a349 100644 --- a/parse.c +++ b/parse.c @@ -106,7 +106,7 @@ static unsigned long get_mult_bytes(char c) /* * convert string into decimal value, noting any size suffix */ -static int str_to_decimal(const char *str, long long *val, int kilo) +int str_to_decimal(const char *str, long long *val, int kilo) { int len; diff --git a/parse.h b/parse.h index c3b66d75..42046b5e 100644 --- a/parse.h +++ b/parse.h @@ -58,6 +58,7 @@ extern void options_init(struct fio_option *); extern void strip_blank_front(char **); extern void strip_blank_end(char *); +extern int str_to_decimal(const char *, long long *, int); /* * Handlers for the options -- 2.25.1