X-Git-Url: https://git.kernel.dk/?a=blobdiff_plain;f=engines%2Fcmdprio.c;h=153e36911a458e8c746d1b4a23bb3aa49c7a8334;hb=79012fece0a0ecb02ad6a3322913980efdb1d726;hp=d36b6264e07d1dfb735ee2efb3ef2b735b8021a2;hpb=127715b61286b1b77cd2c3fe7efacc01d71222ea;p=fio.git diff --git a/engines/cmdprio.c b/engines/cmdprio.c index d36b6264..153e3691 100644 --- a/engines/cmdprio.c +++ b/engines/cmdprio.c @@ -5,87 +5,258 @@ #include "cmdprio.h" -static int fio_cmdprio_bssplit_ddir(struct thread_options *to, void *cb_arg, - enum fio_ddir ddir, char *str, bool data) +/* + * Temporary array used during parsing. Will be freed after the corresponding + * struct bsprio_desc has been generated and saved in cmdprio->bsprio_desc. + */ +struct cmdprio_parse_result { + struct split_prio *entries; + int nr_entries; +}; + +/* + * Temporary array used during init. Will be freed after the corresponding + * struct clat_prio_stat array has been saved in td->ts.clat_prio and the + * matching clat_prio_indexes have been saved in each struct cmdprio_prio. + */ +struct cmdprio_values { + unsigned int *prios; + int nr_prios; +}; + +static int find_clat_prio_index(unsigned int *all_prios, int nr_prios, + int32_t prio) { - struct cmdprio *cmdprio = cb_arg; - struct split split; - unsigned int i; + int i; - if (ddir == DDIR_TRIM) - return 0; + for (i = 0; i < nr_prios; i++) { + if (all_prios[i] == prio) + return i; + } + + return -1; +} - memset(&split, 0, sizeof(split)); +/** + * assign_clat_prio_index - In order to avoid stat.c the need to loop through + * all possible priorities each time add_clat_sample() / add_lat_sample() is + * called, save which index to use in each cmdprio_prio. This will later be + * propagated to the io_u, if the specific io_u was determined to use a cmdprio + * priority value. + */ +static void assign_clat_prio_index(struct cmdprio_prio *prio, + struct cmdprio_values *values) +{ + int clat_prio_index = find_clat_prio_index(values->prios, + values->nr_prios, + prio->prio); + if (clat_prio_index == -1) { + clat_prio_index = values->nr_prios; + values->prios[clat_prio_index] = prio->prio; + values->nr_prios++; + } + prio->clat_prio_index = clat_prio_index; +} - if (split_parse_ddir(to, &split, str, data, BSSPLIT_MAX)) +/** + * init_cmdprio_values - Allocate a temporary array that can hold all unique + * priorities (per ddir), so that we can assign_clat_prio_index() for each + * cmdprio_prio during setup. This temporary array is freed after setup. + */ +static int init_cmdprio_values(struct cmdprio_values *values, + int max_unique_prios, struct thread_stat *ts) +{ + values->prios = calloc(max_unique_prios + 1, + sizeof(*values->prios)); + if (!values->prios) return 1; - if (!split.nr) - return 0; - cmdprio->bssplit_nr[ddir] = split.nr; - cmdprio->bssplit[ddir] = malloc(split.nr * sizeof(struct bssplit)); - if (!cmdprio->bssplit[ddir]) + /* td->ioprio/ts->ioprio is always stored at index 0. */ + values->prios[0] = ts->ioprio; + values->nr_prios++; + + return 0; +} + +/** + * init_ts_clat_prio - Allocates and fills a clat_prio_stat array which holds + * all unique priorities (per ddir). + */ +static int init_ts_clat_prio(struct thread_stat *ts, enum fio_ddir ddir, + struct cmdprio_values *values) +{ + int i; + + if (alloc_clat_prio_stat_ddir(ts, ddir, values->nr_prios)) + return 1; + + for (i = 0; i < values->nr_prios; i++) + ts->clat_prio[ddir][i].ioprio = values->prios[i]; + + return 0; +} + +static int fio_cmdprio_fill_bsprio(struct cmdprio_bsprio *bsprio, + struct split_prio *entries, + struct cmdprio_values *values, + int implicit_cmdprio, int start, int end) +{ + struct cmdprio_prio *prio; + int i = end - start + 1; + + bsprio->prios = calloc(i, sizeof(*bsprio->prios)); + if (!bsprio->prios) return 1; - for (i = 0; i < split.nr; i++) { - cmdprio->bssplit[ddir][i].bs = split.val1[i]; - if (split.val2[i] == -1U) { - cmdprio->bssplit[ddir][i].perc = 0; - } else { - if (split.val2[i] > 100) - cmdprio->bssplit[ddir][i].perc = 100; - else - cmdprio->bssplit[ddir][i].perc = split.val2[i]; + bsprio->bs = entries[start].bs; + bsprio->nr_prios = 0; + for (i = start; i <= end; i++) { + prio = &bsprio->prios[bsprio->nr_prios]; + prio->perc = entries[i].perc; + if (entries[i].prio == -1) + prio->prio = implicit_cmdprio; + else + prio->prio = entries[i].prio; + assign_clat_prio_index(prio, values); + bsprio->tot_perc += entries[i].perc; + if (bsprio->tot_perc > 100) { + log_err("fio: cmdprio_bssplit total percentage " + "for bs: %"PRIu64" exceeds 100\n", + bsprio->bs); + free(bsprio->prios); + return 1; } + bsprio->nr_prios++; + } + + return 0; +} + +static int +fio_cmdprio_generate_bsprio_desc(struct cmdprio_bsprio_desc *bsprio_desc, + struct cmdprio_parse_result *parse_res, + struct cmdprio_values *values, + int implicit_cmdprio) +{ + struct split_prio *entries = parse_res->entries; + int nr_entries = parse_res->nr_entries; + struct cmdprio_bsprio *bsprio; + int i, start, count = 0; + + /* + * The parsed result is sorted by blocksize, so count only the number + * of different blocksizes, to know how many cmdprio_bsprio we need. + */ + for (i = 0; i < nr_entries; i++) { + while (i + 1 < nr_entries && entries[i].bs == entries[i + 1].bs) + i++; + count++; + } + + /* + * This allocation is not freed on error. Instead, the calling function + * is responsible for calling fio_cmdprio_cleanup() on error. + */ + bsprio_desc->bsprios = calloc(count, sizeof(*bsprio_desc->bsprios)); + if (!bsprio_desc->bsprios) + return 1; + + start = 0; + bsprio_desc->nr_bsprios = 0; + for (i = 0; i < nr_entries; i++) { + while (i + 1 < nr_entries && entries[i].bs == entries[i + 1].bs) + i++; + bsprio = &bsprio_desc->bsprios[bsprio_desc->nr_bsprios]; + /* + * All parsed entries with the same blocksize get saved in the + * same cmdprio_bsprio, to expedite the search in the hot path. + */ + if (fio_cmdprio_fill_bsprio(bsprio, entries, values, + implicit_cmdprio, start, i)) + return 1; + + start = i + 1; + bsprio_desc->nr_bsprios++; } return 0; } -int fio_cmdprio_bssplit_parse(struct thread_data *td, const char *input, - struct cmdprio *cmdprio) +static int fio_cmdprio_bssplit_ddir(struct thread_options *to, void *cb_arg, + enum fio_ddir ddir, char *str, bool data) +{ + struct cmdprio_parse_result *parse_res_arr = cb_arg; + struct cmdprio_parse_result *parse_res = &parse_res_arr[ddir]; + + if (ddir == DDIR_TRIM) + return 0; + + if (split_parse_prio_ddir(to, &parse_res->entries, + &parse_res->nr_entries, str)) + return 1; + + return 0; +} + +static int fio_cmdprio_bssplit_parse(struct thread_data *td, const char *input, + struct cmdprio_parse_result *parse_res) { char *str, *p; - int i, ret = 0; + int ret = 0; p = str = strdup(input); strip_blank_front(&str); strip_blank_end(str); - ret = str_split_parse(td, str, fio_cmdprio_bssplit_ddir, cmdprio, false); - - if (parse_dryrun()) { - for (i = 0; i < CMDPRIO_RWDIR_CNT; i++) { - free(cmdprio->bssplit[i]); - cmdprio->bssplit[i] = NULL; - cmdprio->bssplit_nr[i] = 0; - } - } + ret = str_split_parse(td, str, fio_cmdprio_bssplit_ddir, parse_res, + false); free(p); return ret; } -static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u) +/** + * fio_cmdprio_percentage - Returns the percentage of I/Os that should + * use a cmdprio priority value (rather than the default context priority). + * + * For CMDPRIO_MODE_BSSPLIT, if the percentage is non-zero, we will also + * return the matching bsprio, to avoid the same linear search elsewhere. + * For CMDPRIO_MODE_PERC, we will never return a bsprio. + */ +static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u, + struct cmdprio_bsprio **bsprio) { + struct cmdprio_bsprio *bsprio_entry; enum fio_ddir ddir = io_u->ddir; - unsigned int p = cmdprio->percentage[ddir]; int i; - /* - * If cmdprio_percentage option was specified, then use that - * percentage. Otherwise, use cmdprio_bssplit percentages depending - * on the IO size. - */ - if (p) - return p; - - for (i = 0; i < cmdprio->bssplit_nr[ddir]; i++) { - if (cmdprio->bssplit[ddir][i].bs == io_u->buflen) - return cmdprio->bssplit[ddir][i].perc; + switch (cmdprio->mode) { + case CMDPRIO_MODE_PERC: + *bsprio = NULL; + return cmdprio->perc_entry[ddir].perc; + case CMDPRIO_MODE_BSSPLIT: + for (i = 0; i < cmdprio->bsprio_desc[ddir].nr_bsprios; i++) { + bsprio_entry = &cmdprio->bsprio_desc[ddir].bsprios[i]; + if (bsprio_entry->bs == io_u->buflen) { + *bsprio = bsprio_entry; + return bsprio_entry->tot_perc; + } + } + break; + default: + /* + * An I/O engine should never call this function if cmdprio + * is not is use. + */ + assert(0); } + /* + * This is totally fine, the given blocksize simply does not + * have any (non-zero) cmdprio_bssplit entries defined. + */ + *bsprio = NULL; return 0; } @@ -96,67 +267,248 @@ static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u) * to be set. If the random percentage value is within the user specified * percentage of I/Os that should use a cmdprio priority value (rather than * the default priority), then this function updates the io_u with an ioprio - * value as defined by the cmdprio/cmdprio_class or cmdprio_bssplit options. + * value as defined by the cmdprio/cmdprio_hint/cmdprio_class or + * cmdprio_bssplit options. * * Return true if the io_u ioprio was changed and false otherwise. */ bool fio_cmdprio_set_ioprio(struct thread_data *td, struct cmdprio *cmdprio, struct io_u *io_u) { - enum fio_ddir ddir = io_u->ddir; - unsigned int p = fio_cmdprio_percentage(cmdprio, io_u); - unsigned int cmdprio_value = - ioprio_value(cmdprio->class[ddir], cmdprio->level[ddir]); - - if (p && rand_between(&td->prio_state, 0, 99) < p) { - io_u->ioprio = cmdprio_value; - if (!td->ioprio || cmdprio_value < td->ioprio) { - /* - * The async IO priority is higher (has a lower value) - * than the default priority (which is either 0 or the - * value set by "prio" and "prioclass" options). - */ - io_u->flags |= IO_U_F_HIGH_PRIO; - } + struct cmdprio_bsprio *bsprio; + unsigned int p, rand; + uint32_t perc = 0; + int i; + + p = fio_cmdprio_percentage(cmdprio, io_u, &bsprio); + if (!p) + return false; + + rand = rand_between(&td->prio_state, 0, 99); + if (rand >= p) + return false; + + switch (cmdprio->mode) { + case CMDPRIO_MODE_PERC: + io_u->ioprio = cmdprio->perc_entry[io_u->ddir].prio; + io_u->clat_prio_index = + cmdprio->perc_entry[io_u->ddir].clat_prio_index; return true; + case CMDPRIO_MODE_BSSPLIT: + assert(bsprio); + for (i = 0; i < bsprio->nr_prios; i++) { + struct cmdprio_prio *prio = &bsprio->prios[i]; + + perc += prio->perc; + if (rand < perc) { + io_u->ioprio = prio->prio; + io_u->clat_prio_index = prio->clat_prio_index; + return true; + } + } + break; + default: + assert(0); } - if (td->ioprio && td->ioprio < cmdprio_value) { + /* When rand < p (total perc), we should always find a cmdprio_prio. */ + assert(0); + return false; +} + +static int fio_cmdprio_gen_perc(struct thread_data *td, struct cmdprio *cmdprio) +{ + struct cmdprio_options *options = cmdprio->options; + struct cmdprio_prio *prio; + struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {}; + struct thread_stat *ts = &td->ts; + enum fio_ddir ddir; + int ret; + + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { /* - * The IO will be executed with the default priority (which is - * either 0 or the value set by "prio" and "prioclass options), - * and this priority is higher (has a lower value) than the - * async IO priority. + * Do not allocate a clat_prio array nor set the cmdprio struct + * if zero percent of the I/Os (for the ddir) should use a + * cmdprio priority value, or when the ddir is not enabled. */ - io_u->flags |= IO_U_F_HIGH_PRIO; + if (!options->percentage[ddir] || + (ddir == DDIR_READ && !td_read(td)) || + (ddir == DDIR_WRITE && !td_write(td))) + continue; + + ret = init_cmdprio_values(&values[ddir], 1, ts); + if (ret) + goto err; + + prio = &cmdprio->perc_entry[ddir]; + prio->perc = options->percentage[ddir]; + prio->prio = ioprio_value(options->class[ddir], + options->level[ddir], + options->hint[ddir]); + assign_clat_prio_index(prio, &values[ddir]); + + ret = init_ts_clat_prio(ts, ddir, &values[ddir]); + if (ret) + goto err; + + free(values[ddir].prios); + values[ddir].prios = NULL; + values[ddir].nr_prios = 0; } - return false; + return 0; + +err: + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) + free(values[ddir].prios); + free_clat_prio_stats(ts); + + return ret; +} + +static int fio_cmdprio_parse_and_gen_bssplit(struct thread_data *td, + struct cmdprio *cmdprio) +{ + struct cmdprio_options *options = cmdprio->options; + struct cmdprio_parse_result parse_res[CMDPRIO_RWDIR_CNT] = {}; + struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {}; + struct thread_stat *ts = &td->ts; + int ret, implicit_cmdprio; + enum fio_ddir ddir; + + ret = fio_cmdprio_bssplit_parse(td, options->bssplit_str, + &parse_res[0]); + if (ret) + goto err; + + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { + /* + * Do not allocate a clat_prio array nor set the cmdprio structs + * if there are no non-zero entries (for the ddir), or when the + * ddir is not enabled. + */ + if (!parse_res[ddir].nr_entries || + (ddir == DDIR_READ && !td_read(td)) || + (ddir == DDIR_WRITE && !td_write(td))) { + free(parse_res[ddir].entries); + parse_res[ddir].entries = NULL; + parse_res[ddir].nr_entries = 0; + continue; + } + + ret = init_cmdprio_values(&values[ddir], + parse_res[ddir].nr_entries, ts); + if (ret) + goto err; + + implicit_cmdprio = ioprio_value(options->class[ddir], + options->level[ddir], + options->hint[ddir]); + + ret = fio_cmdprio_generate_bsprio_desc(&cmdprio->bsprio_desc[ddir], + &parse_res[ddir], + &values[ddir], + implicit_cmdprio); + if (ret) + goto err; + + free(parse_res[ddir].entries); + parse_res[ddir].entries = NULL; + parse_res[ddir].nr_entries = 0; + + ret = init_ts_clat_prio(ts, ddir, &values[ddir]); + if (ret) + goto err; + + free(values[ddir].prios); + values[ddir].prios = NULL; + values[ddir].nr_prios = 0; + } + + return 0; + +err: + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { + free(parse_res[ddir].entries); + free(values[ddir].prios); + } + free_clat_prio_stats(ts); + fio_cmdprio_cleanup(cmdprio); + + return ret; +} + +static int fio_cmdprio_parse_and_gen(struct thread_data *td, + struct cmdprio *cmdprio) +{ + struct cmdprio_options *options = cmdprio->options; + int i, ret; + + /* + * If cmdprio_percentage/cmdprio_bssplit is set and cmdprio_class + * is not set, default to RT priority class. + */ + for (i = 0; i < CMDPRIO_RWDIR_CNT; i++) { + /* + * A cmdprio value is only used when fio_cmdprio_percentage() + * returns non-zero, so it is safe to set a class even for a + * DDIR that will never use it. + */ + if (!options->class[i]) + options->class[i] = IOPRIO_CLASS_RT; + } + + switch (cmdprio->mode) { + case CMDPRIO_MODE_BSSPLIT: + ret = fio_cmdprio_parse_and_gen_bssplit(td, cmdprio); + break; + case CMDPRIO_MODE_PERC: + ret = fio_cmdprio_gen_perc(td, cmdprio); + break; + default: + assert(0); + return 1; + } + + return ret; +} + +void fio_cmdprio_cleanup(struct cmdprio *cmdprio) +{ + enum fio_ddir ddir; + int i; + + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { + for (i = 0; i < cmdprio->bsprio_desc[ddir].nr_bsprios; i++) + free(cmdprio->bsprio_desc[ddir].bsprios[i].prios); + free(cmdprio->bsprio_desc[ddir].bsprios); + cmdprio->bsprio_desc[ddir].bsprios = NULL; + cmdprio->bsprio_desc[ddir].nr_bsprios = 0; + } + + /* + * options points to a cmdprio_options struct that is part of td->eo. + * td->eo itself will be freed by free_ioengine(). + */ + cmdprio->options = NULL; } int fio_cmdprio_init(struct thread_data *td, struct cmdprio *cmdprio, - bool *has_cmdprio) + struct cmdprio_options *options) { struct thread_options *to = &td->o; bool has_cmdprio_percentage = false; bool has_cmdprio_bssplit = false; int i; - /* - * If cmdprio_percentage/cmdprio_bssplit is set and cmdprio_class - * is not set, default to RT priority class. - */ + cmdprio->options = options; + + if (options->bssplit_str && strlen(options->bssplit_str)) + has_cmdprio_bssplit = true; + for (i = 0; i < CMDPRIO_RWDIR_CNT; i++) { - if (cmdprio->percentage[i]) { - if (!cmdprio->class[i]) - cmdprio->class[i] = IOPRIO_CLASS_RT; + if (options->percentage[i]) has_cmdprio_percentage = true; - } - if (cmdprio->bssplit_nr[i]) { - if (!cmdprio->class[i]) - cmdprio->class[i] = IOPRIO_CLASS_RT; - has_cmdprio_bssplit = true; - } } /* @@ -169,7 +521,16 @@ int fio_cmdprio_init(struct thread_data *td, struct cmdprio *cmdprio, return 1; } - *has_cmdprio = has_cmdprio_percentage || has_cmdprio_bssplit; + if (has_cmdprio_bssplit) + cmdprio->mode = CMDPRIO_MODE_BSSPLIT; + else if (has_cmdprio_percentage) + cmdprio->mode = CMDPRIO_MODE_PERC; + else + cmdprio->mode = CMDPRIO_MODE_NONE; - return 0; + /* Nothing left to do if cmdprio is not used */ + if (cmdprio->mode == CMDPRIO_MODE_NONE) + return 0; + + return fio_cmdprio_parse_and_gen(td, cmdprio); }