| 1 | /* |
| 2 | * IO priority handling helper functions common to the libaio and io_uring |
| 3 | * engines. |
| 4 | */ |
| 5 | |
| 6 | #include "cmdprio.h" |
| 7 | |
| 8 | static int fio_cmdprio_bssplit_ddir(struct thread_options *to, void *cb_arg, |
| 9 | enum fio_ddir ddir, char *str, bool data) |
| 10 | { |
| 11 | struct cmdprio *cmdprio = cb_arg; |
| 12 | struct split split; |
| 13 | unsigned int i; |
| 14 | |
| 15 | if (ddir == DDIR_TRIM) |
| 16 | return 0; |
| 17 | |
| 18 | memset(&split, 0, sizeof(split)); |
| 19 | |
| 20 | if (split_parse_ddir(to, &split, str, data, BSSPLIT_MAX)) |
| 21 | return 1; |
| 22 | if (!split.nr) |
| 23 | return 0; |
| 24 | |
| 25 | cmdprio->bssplit_nr[ddir] = split.nr; |
| 26 | cmdprio->bssplit[ddir] = malloc(split.nr * sizeof(struct bssplit)); |
| 27 | if (!cmdprio->bssplit[ddir]) |
| 28 | return 1; |
| 29 | |
| 30 | for (i = 0; i < split.nr; i++) { |
| 31 | cmdprio->bssplit[ddir][i].bs = split.val1[i]; |
| 32 | if (split.val2[i] == -1U) { |
| 33 | cmdprio->bssplit[ddir][i].perc = 0; |
| 34 | } else { |
| 35 | if (split.val2[i] > 100) |
| 36 | cmdprio->bssplit[ddir][i].perc = 100; |
| 37 | else |
| 38 | cmdprio->bssplit[ddir][i].perc = split.val2[i]; |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | return 0; |
| 43 | } |
| 44 | |
| 45 | int fio_cmdprio_bssplit_parse(struct thread_data *td, const char *input, |
| 46 | struct cmdprio *cmdprio) |
| 47 | { |
| 48 | char *str, *p; |
| 49 | int ret = 0; |
| 50 | |
| 51 | p = str = strdup(input); |
| 52 | |
| 53 | strip_blank_front(&str); |
| 54 | strip_blank_end(str); |
| 55 | |
| 56 | ret = str_split_parse(td, str, fio_cmdprio_bssplit_ddir, cmdprio, |
| 57 | false); |
| 58 | |
| 59 | free(p); |
| 60 | return ret; |
| 61 | } |
| 62 | |
| 63 | static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u) |
| 64 | { |
| 65 | enum fio_ddir ddir = io_u->ddir; |
| 66 | struct cmdprio_options *options = cmdprio->options; |
| 67 | int i; |
| 68 | |
| 69 | switch (cmdprio->mode) { |
| 70 | case CMDPRIO_MODE_PERC: |
| 71 | return options->percentage[ddir]; |
| 72 | case CMDPRIO_MODE_BSSPLIT: |
| 73 | for (i = 0; i < cmdprio->bssplit_nr[ddir]; i++) { |
| 74 | if (cmdprio->bssplit[ddir][i].bs == io_u->buflen) |
| 75 | return cmdprio->bssplit[ddir][i].perc; |
| 76 | } |
| 77 | break; |
| 78 | default: |
| 79 | /* |
| 80 | * An I/O engine should never call this function if cmdprio |
| 81 | * is not is use. |
| 82 | */ |
| 83 | assert(0); |
| 84 | } |
| 85 | |
| 86 | return 0; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * fio_cmdprio_set_ioprio - Set an io_u ioprio according to cmdprio options |
| 91 | * |
| 92 | * Generates a random percentage value to determine if an io_u ioprio needs |
| 93 | * to be set. If the random percentage value is within the user specified |
| 94 | * percentage of I/Os that should use a cmdprio priority value (rather than |
| 95 | * the default priority), then this function updates the io_u with an ioprio |
| 96 | * value as defined by the cmdprio/cmdprio_class or cmdprio_bssplit options. |
| 97 | * |
| 98 | * Return true if the io_u ioprio was changed and false otherwise. |
| 99 | */ |
| 100 | bool fio_cmdprio_set_ioprio(struct thread_data *td, struct cmdprio *cmdprio, |
| 101 | struct io_u *io_u) |
| 102 | { |
| 103 | enum fio_ddir ddir = io_u->ddir; |
| 104 | struct cmdprio_options *options = cmdprio->options; |
| 105 | unsigned int p; |
| 106 | unsigned int cmdprio_value = |
| 107 | ioprio_value(options->class[ddir], options->level[ddir]); |
| 108 | |
| 109 | p = fio_cmdprio_percentage(cmdprio, io_u); |
| 110 | if (p && rand_between(&td->prio_state, 0, 99) < p) { |
| 111 | io_u->ioprio = cmdprio_value; |
| 112 | if (!td->ioprio || cmdprio_value < td->ioprio) { |
| 113 | /* |
| 114 | * The async IO priority is higher (has a lower value) |
| 115 | * than the default priority (which is either 0 or the |
| 116 | * value set by "prio" and "prioclass" options). |
| 117 | */ |
| 118 | io_u->flags |= IO_U_F_HIGH_PRIO; |
| 119 | } |
| 120 | return true; |
| 121 | } |
| 122 | |
| 123 | if (td->ioprio && td->ioprio < cmdprio_value) { |
| 124 | /* |
| 125 | * The IO will be executed with the default priority (which is |
| 126 | * either 0 or the value set by "prio" and "prioclass options), |
| 127 | * and this priority is higher (has a lower value) than the |
| 128 | * async IO priority. |
| 129 | */ |
| 130 | io_u->flags |= IO_U_F_HIGH_PRIO; |
| 131 | } |
| 132 | |
| 133 | return false; |
| 134 | } |
| 135 | |
| 136 | static int fio_cmdprio_parse_and_gen_bssplit(struct thread_data *td, |
| 137 | struct cmdprio *cmdprio) |
| 138 | { |
| 139 | struct cmdprio_options *options = cmdprio->options; |
| 140 | int ret; |
| 141 | |
| 142 | ret = fio_cmdprio_bssplit_parse(td, options->bssplit_str, cmdprio); |
| 143 | if (ret) |
| 144 | goto err; |
| 145 | |
| 146 | return 0; |
| 147 | |
| 148 | err: |
| 149 | fio_cmdprio_cleanup(cmdprio); |
| 150 | |
| 151 | return ret; |
| 152 | } |
| 153 | |
| 154 | static int fio_cmdprio_parse_and_gen(struct thread_data *td, |
| 155 | struct cmdprio *cmdprio) |
| 156 | { |
| 157 | struct cmdprio_options *options = cmdprio->options; |
| 158 | int i, ret; |
| 159 | |
| 160 | switch (cmdprio->mode) { |
| 161 | case CMDPRIO_MODE_BSSPLIT: |
| 162 | ret = fio_cmdprio_parse_and_gen_bssplit(td, cmdprio); |
| 163 | break; |
| 164 | case CMDPRIO_MODE_PERC: |
| 165 | ret = 0; |
| 166 | break; |
| 167 | default: |
| 168 | assert(0); |
| 169 | return 1; |
| 170 | } |
| 171 | |
| 172 | /* |
| 173 | * If cmdprio_percentage/cmdprio_bssplit is set and cmdprio_class |
| 174 | * is not set, default to RT priority class. |
| 175 | */ |
| 176 | for (i = 0; i < CMDPRIO_RWDIR_CNT; i++) { |
| 177 | if (options->percentage[i] || cmdprio->bssplit_nr[i]) { |
| 178 | if (!options->class[i]) |
| 179 | options->class[i] = IOPRIO_CLASS_RT; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | return ret; |
| 184 | } |
| 185 | |
| 186 | void fio_cmdprio_cleanup(struct cmdprio *cmdprio) |
| 187 | { |
| 188 | int ddir; |
| 189 | |
| 190 | for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { |
| 191 | free(cmdprio->bssplit[ddir]); |
| 192 | cmdprio->bssplit[ddir] = NULL; |
| 193 | cmdprio->bssplit_nr[ddir] = 0; |
| 194 | } |
| 195 | |
| 196 | /* |
| 197 | * options points to a cmdprio_options struct that is part of td->eo. |
| 198 | * td->eo itself will be freed by free_ioengine(). |
| 199 | */ |
| 200 | cmdprio->options = NULL; |
| 201 | } |
| 202 | |
| 203 | int fio_cmdprio_init(struct thread_data *td, struct cmdprio *cmdprio, |
| 204 | struct cmdprio_options *options) |
| 205 | { |
| 206 | struct thread_options *to = &td->o; |
| 207 | bool has_cmdprio_percentage = false; |
| 208 | bool has_cmdprio_bssplit = false; |
| 209 | int i; |
| 210 | |
| 211 | cmdprio->options = options; |
| 212 | |
| 213 | if (options->bssplit_str && strlen(options->bssplit_str)) |
| 214 | has_cmdprio_bssplit = true; |
| 215 | |
| 216 | for (i = 0; i < CMDPRIO_RWDIR_CNT; i++) { |
| 217 | if (options->percentage[i]) |
| 218 | has_cmdprio_percentage = true; |
| 219 | } |
| 220 | |
| 221 | /* |
| 222 | * Check for option conflicts |
| 223 | */ |
| 224 | if (has_cmdprio_percentage && has_cmdprio_bssplit) { |
| 225 | log_err("%s: cmdprio_percentage and cmdprio_bssplit options " |
| 226 | "are mutually exclusive\n", |
| 227 | to->name); |
| 228 | return 1; |
| 229 | } |
| 230 | |
| 231 | if (has_cmdprio_bssplit) |
| 232 | cmdprio->mode = CMDPRIO_MODE_BSSPLIT; |
| 233 | else if (has_cmdprio_percentage) |
| 234 | cmdprio->mode = CMDPRIO_MODE_PERC; |
| 235 | else |
| 236 | cmdprio->mode = CMDPRIO_MODE_NONE; |
| 237 | |
| 238 | /* Nothing left to do if cmdprio is not used */ |
| 239 | if (cmdprio->mode == CMDPRIO_MODE_NONE) |
| 240 | return 0; |
| 241 | |
| 242 | return fio_cmdprio_parse_and_gen(td, cmdprio); |
| 243 | } |