| 1 | /* |
| 2 | * CPU engine |
| 3 | * |
| 4 | * Doesn't transfer any data, merely burns CPU cycles according to |
| 5 | * the settings. |
| 6 | * |
| 7 | */ |
| 8 | #include "../fio.h" |
| 9 | #include "../optgroup.h" |
| 10 | |
| 11 | // number of 32 bit integers to sort |
| 12 | size_t qsort_size = (256 * (1ULL << 10)); // 256KB |
| 13 | |
| 14 | struct mwc { |
| 15 | uint32_t w; |
| 16 | uint32_t z; |
| 17 | }; |
| 18 | |
| 19 | enum stress_mode { |
| 20 | FIO_CPU_NOOP = 0, |
| 21 | FIO_CPU_QSORT = 1, |
| 22 | }; |
| 23 | |
| 24 | struct cpu_options { |
| 25 | void *pad; |
| 26 | unsigned int cpuload; |
| 27 | unsigned int cpucycle; |
| 28 | enum stress_mode cpumode; |
| 29 | unsigned int exit_io_done; |
| 30 | int32_t *qsort_data; |
| 31 | }; |
| 32 | |
| 33 | static struct fio_option options[] = { |
| 34 | { |
| 35 | .name = "cpuload", |
| 36 | .lname = "CPU load", |
| 37 | .type = FIO_OPT_INT, |
| 38 | .off1 = offsetof(struct cpu_options, cpuload), |
| 39 | .help = "Use this percentage of CPU", |
| 40 | .category = FIO_OPT_C_ENGINE, |
| 41 | .group = FIO_OPT_G_INVALID, |
| 42 | }, |
| 43 | { |
| 44 | .name = "cpumode", |
| 45 | .lname = "cpumode", |
| 46 | .type = FIO_OPT_STR, |
| 47 | .help = "Stress mode", |
| 48 | .off1 = offsetof(struct cpu_options, cpumode), |
| 49 | .def = "noop", |
| 50 | .posval = { |
| 51 | { .ival = "noop", |
| 52 | .oval = FIO_CPU_NOOP, |
| 53 | .help = "NOOP instructions", |
| 54 | }, |
| 55 | { .ival = "qsort", |
| 56 | .oval = FIO_CPU_QSORT, |
| 57 | .help = "QSORT computation", |
| 58 | }, |
| 59 | }, |
| 60 | .category = FIO_OPT_C_ENGINE, |
| 61 | .group = FIO_OPT_G_INVALID, |
| 62 | }, |
| 63 | { |
| 64 | .name = "cpuchunks", |
| 65 | .lname = "CPU chunk", |
| 66 | .type = FIO_OPT_INT, |
| 67 | .off1 = offsetof(struct cpu_options, cpucycle), |
| 68 | .help = "Length of the CPU burn cycles (usecs)", |
| 69 | .def = "50000", |
| 70 | .parent = "cpuload", |
| 71 | .hide = 1, |
| 72 | .category = FIO_OPT_C_ENGINE, |
| 73 | .group = FIO_OPT_G_INVALID, |
| 74 | }, |
| 75 | { |
| 76 | .name = "exit_on_io_done", |
| 77 | .lname = "Exit when IO threads are done", |
| 78 | .type = FIO_OPT_BOOL, |
| 79 | .off1 = offsetof(struct cpu_options, exit_io_done), |
| 80 | .help = "Exit when IO threads finish", |
| 81 | .def = "0", |
| 82 | .category = FIO_OPT_C_ENGINE, |
| 83 | .group = FIO_OPT_G_INVALID, |
| 84 | }, |
| 85 | { |
| 86 | .name = NULL, |
| 87 | }, |
| 88 | }; |
| 89 | |
| 90 | /* |
| 91 | * mwc32() |
| 92 | * Multiply-with-carry random numbers |
| 93 | * fast pseudo random number generator, see |
| 94 | * http://www.cse.yorku.ca/~oz/marsaglia-rng.html |
| 95 | */ |
| 96 | uint32_t mwc32(struct mwc *mwc) |
| 97 | { |
| 98 | mwc->z = 36969 * (mwc->z & 65535) + (mwc->z >> 16); |
| 99 | mwc->w = 18000 * (mwc->w & 65535) + (mwc->w >> 16); |
| 100 | return (mwc->z << 16) + mwc->w; |
| 101 | } |
| 102 | |
| 103 | /* |
| 104 | * stress_qsort_cmp_1() |
| 105 | * qsort comparison - sort on int32 values |
| 106 | */ |
| 107 | static int stress_qsort_cmp_1(const void *p1, const void *p2) |
| 108 | { |
| 109 | const int32_t *i1 = (const int32_t *)p1; |
| 110 | const int32_t *i2 = (const int32_t *)p2; |
| 111 | |
| 112 | if (*i1 > *i2) |
| 113 | return 1; |
| 114 | else if (*i1 < *i2) |
| 115 | return -1; |
| 116 | else |
| 117 | return 0; |
| 118 | } |
| 119 | |
| 120 | /* |
| 121 | * stress_qsort_cmp_2() |
| 122 | * qsort comparison - reverse sort on int32 values |
| 123 | */ |
| 124 | static int stress_qsort_cmp_2(const void *p1, const void *p2) |
| 125 | { |
| 126 | return stress_qsort_cmp_1(p2, p1); |
| 127 | } |
| 128 | |
| 129 | /* |
| 130 | * stress_qsort_cmp_3() |
| 131 | * qsort comparison - sort on int8 values |
| 132 | */ |
| 133 | static int stress_qsort_cmp_3(const void *p1, const void *p2) |
| 134 | { |
| 135 | const int8_t *i1 = (const int8_t *)p1; |
| 136 | const int8_t *i2 = (const int8_t *)p2; |
| 137 | |
| 138 | /* Force re-ordering on 8 bit value */ |
| 139 | return *i1 - *i2; |
| 140 | } |
| 141 | |
| 142 | static int do_qsort(struct thread_data *td) |
| 143 | { |
| 144 | struct thread_options *o = &td->o; |
| 145 | struct cpu_options *co = td->eo; |
| 146 | struct timespec start, now; |
| 147 | |
| 148 | fio_get_mono_time(&start); |
| 149 | |
| 150 | /* Sort "random" data */ |
| 151 | qsort(co->qsort_data, qsort_size, sizeof(*(co->qsort_data)), stress_qsort_cmp_1); |
| 152 | |
| 153 | /* Reverse sort */ |
| 154 | qsort(co->qsort_data, qsort_size, sizeof(*(co->qsort_data)), stress_qsort_cmp_2); |
| 155 | |
| 156 | /* And re-order by byte compare */ |
| 157 | qsort((uint8_t *)co->qsort_data, qsort_size * 4, sizeof(uint8_t), stress_qsort_cmp_3); |
| 158 | |
| 159 | /* Reverse sort this again */ |
| 160 | qsort(co->qsort_data, qsort_size, sizeof(*(co->qsort_data)), stress_qsort_cmp_2); |
| 161 | fio_get_mono_time(&now); |
| 162 | |
| 163 | /* Adjusting cpucycle automatically to be as close as possible to the |
| 164 | * expected cpuload The time to execute do_qsort() may change over time |
| 165 | * as per : - the job concurrency - the cpu clock adjusted by the power |
| 166 | * management After every do_qsort() call, the next thinktime is |
| 167 | * adjusted regarding the last run performance |
| 168 | */ |
| 169 | co->cpucycle = utime_since(&start, &now); |
| 170 | o->thinktime = ((unsigned long long) co->cpucycle * |
| 171 | (100 - co->cpuload)) / co->cpuload; |
| 172 | |
| 173 | return 0; |
| 174 | } |
| 175 | |
| 176 | static enum fio_q_status fio_cpuio_queue(struct thread_data *td, |
| 177 | struct io_u fio_unused *io_u) |
| 178 | { |
| 179 | struct cpu_options *co = td->eo; |
| 180 | |
| 181 | if (co->exit_io_done && !fio_running_or_pending_io_threads()) { |
| 182 | td->done = 1; |
| 183 | return FIO_Q_BUSY; |
| 184 | } |
| 185 | |
| 186 | switch (co->cpumode) { |
| 187 | case FIO_CPU_NOOP: |
| 188 | usec_spin(co->cpucycle); |
| 189 | break; |
| 190 | case FIO_CPU_QSORT: |
| 191 | do_qsort(td); |
| 192 | break; |
| 193 | } |
| 194 | |
| 195 | return FIO_Q_COMPLETED; |
| 196 | } |
| 197 | |
| 198 | static int noop_init(struct thread_data *td) |
| 199 | { |
| 200 | struct cpu_options *co = td->eo; |
| 201 | |
| 202 | log_info("%s (noop): ioengine=%s, cpuload=%u, cpucycle=%u\n", |
| 203 | td->o.name, td->io_ops->name, co->cpuload, co->cpucycle); |
| 204 | return 0; |
| 205 | } |
| 206 | |
| 207 | static int qsort_cleanup(struct thread_data *td) |
| 208 | { |
| 209 | struct cpu_options *co = td->eo; |
| 210 | |
| 211 | if (co->qsort_data) { |
| 212 | free(co->qsort_data); |
| 213 | co->qsort_data = NULL; |
| 214 | } |
| 215 | |
| 216 | return 0; |
| 217 | } |
| 218 | |
| 219 | static int qsort_init(struct thread_data *td) |
| 220 | { |
| 221 | /* Setting up a default entropy */ |
| 222 | struct mwc mwc = { 521288629UL, 362436069UL }; |
| 223 | struct cpu_options *co = td->eo; |
| 224 | int32_t *ptr; |
| 225 | int i; |
| 226 | |
| 227 | co->qsort_data = calloc(qsort_size, sizeof(*co->qsort_data)); |
| 228 | if (co->qsort_data == NULL) { |
| 229 | td_verror(td, ENOMEM, "qsort_init"); |
| 230 | return 1; |
| 231 | } |
| 232 | |
| 233 | /* This is expensive, init the memory once */ |
| 234 | for (ptr = co->qsort_data, i = 0; i < qsort_size; i++) |
| 235 | *ptr++ = mwc32(&mwc); |
| 236 | |
| 237 | log_info("%s (qsort): ioengine=%s, cpuload=%u, cpucycle=%u\n", |
| 238 | td->o.name, td->io_ops->name, co->cpuload, co->cpucycle); |
| 239 | |
| 240 | return 0; |
| 241 | } |
| 242 | |
| 243 | static int fio_cpuio_init(struct thread_data *td) |
| 244 | { |
| 245 | struct thread_options *o = &td->o; |
| 246 | struct cpu_options *co = td->eo; |
| 247 | int td_previous_state; |
| 248 | char *msg; |
| 249 | |
| 250 | if (!co->cpuload) { |
| 251 | td_vmsg(td, EINVAL, "cpu thread needs rate (cpuload=)","cpuio"); |
| 252 | return 1; |
| 253 | } |
| 254 | |
| 255 | if (co->cpuload > 100) |
| 256 | co->cpuload = 100; |
| 257 | |
| 258 | /* Saving the current thread state */ |
| 259 | td_previous_state = td->runstate; |
| 260 | |
| 261 | /* Reporting that we are preparing the engine |
| 262 | * This is useful as the qsort() calibration takes time |
| 263 | * This prevents the job from starting before init is completed |
| 264 | */ |
| 265 | td_set_runstate(td, TD_SETTING_UP); |
| 266 | |
| 267 | /* |
| 268 | * set thinktime_sleep and thinktime_spin appropriately |
| 269 | */ |
| 270 | o->thinktime_blocks = 1; |
| 271 | o->thinktime_blocks_type = THINKTIME_BLOCKS_TYPE_COMPLETE; |
| 272 | o->thinktime_spin = 0; |
| 273 | o->thinktime = ((unsigned long long) co->cpucycle * |
| 274 | (100 - co->cpuload)) / co->cpuload; |
| 275 | |
| 276 | o->nr_files = o->open_files = 1; |
| 277 | |
| 278 | switch (co->cpumode) { |
| 279 | case FIO_CPU_NOOP: |
| 280 | noop_init(td); |
| 281 | break; |
| 282 | case FIO_CPU_QSORT: |
| 283 | qsort_init(td); |
| 284 | break; |
| 285 | default: |
| 286 | if (asprintf(&msg, "bad cpu engine mode: %d", co->cpumode) < 0) |
| 287 | msg = NULL; |
| 288 | td_vmsg(td, EINVAL, msg ? : "(?)", __func__); |
| 289 | free(msg); |
| 290 | return 1; |
| 291 | } |
| 292 | |
| 293 | /* Let's restore the previous state. */ |
| 294 | td_set_runstate(td, td_previous_state); |
| 295 | return 0; |
| 296 | } |
| 297 | |
| 298 | static void fio_cpuio_cleanup(struct thread_data *td) |
| 299 | { |
| 300 | struct cpu_options *co = td->eo; |
| 301 | |
| 302 | switch (co->cpumode) { |
| 303 | case FIO_CPU_NOOP: |
| 304 | break; |
| 305 | case FIO_CPU_QSORT: |
| 306 | qsort_cleanup(td); |
| 307 | break; |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | static int fio_cpuio_open(struct thread_data fio_unused *td, |
| 312 | struct fio_file fio_unused *f) |
| 313 | { |
| 314 | return 0; |
| 315 | } |
| 316 | |
| 317 | static struct ioengine_ops ioengine = { |
| 318 | .name = "cpuio", |
| 319 | .version = FIO_IOOPS_VERSION, |
| 320 | .queue = fio_cpuio_queue, |
| 321 | .init = fio_cpuio_init, |
| 322 | .cleanup = fio_cpuio_cleanup, |
| 323 | .open_file = fio_cpuio_open, |
| 324 | .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOIO, |
| 325 | .options = options, |
| 326 | .option_struct_size = sizeof(struct cpu_options), |
| 327 | }; |
| 328 | |
| 329 | static void fio_init fio_cpuio_register(void) |
| 330 | { |
| 331 | register_ioengine(&ioengine); |
| 332 | } |
| 333 | |
| 334 | static void fio_exit fio_cpuio_unregister(void) |
| 335 | { |
| 336 | unregister_ioengine(&ioengine); |
| 337 | } |