X-Git-Url: https://git.kernel.dk/?p=fio.git;a=blobdiff_plain;f=fio.c;h=a2651f0325490d780ee7a9352fa01d53b81350b5;hp=c95d7b57deb4ad903ae84d34849fc19bb26f4378;hb=b902ceb53977a12062c615ab529ca0c3422e3cff;hpb=eb8bbf48e79a8c6afd3c84e3d64263d10ee45daa diff --git a/fio.c b/fio.c index c95d7b57..a2651f03 100644 --- a/fio.c +++ b/fio.c @@ -2,6 +2,7 @@ * fio - the flexible io tester * * Copyright (C) 2005 Jens Axboe + * Copyright (C) 2006 Jens Axboe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,13 +44,17 @@ int thread_number = 0; static char run_str[MAX_JOBS + 1]; int shm_id = 0; static struct timeval genesis; +static int temp_stall_ts; static void print_thread_status(void); extern unsigned long long mlock_size; /* - * thread life cycle + * Thread life cycle. Once a thread has a runstate beyond TD_INITIALIZED, it + * will never back again. It may cycle between running/verififying/fsyncing. + * Once the thread reaches TD_EXITED, it is just waiting for the core to + * reap it. */ enum { TD_NOT_CREATED = 0, @@ -99,6 +104,10 @@ static void sig_handler(int sig) } } +/* + * The ->file_map[] contains a map of blocks we have or have not done io + * to yet. Used to make sure we cover the entire range in a fair fashion. + */ static int random_map_free(struct thread_data *td, unsigned long long block) { unsigned int idx = RAND_MAP_IDX(td, block); @@ -107,6 +116,9 @@ static int random_map_free(struct thread_data *td, unsigned long long block) return (td->file_map[idx] & (1UL << bit)) == 0; } +/* + * Return the next free block in the map. + */ static int get_next_free_block(struct thread_data *td, unsigned long long *b) { int i; @@ -126,6 +138,9 @@ static int get_next_free_block(struct thread_data *td, unsigned long long *b) return 1; } +/* + * Mark a given offset as used in the map. + */ static void mark_random_map(struct thread_data *td, struct io_u *io_u) { unsigned long long block = io_u->offset / (unsigned long long) td->min_bs; @@ -151,6 +166,11 @@ static void mark_random_map(struct thread_data *td, struct io_u *io_u) io_u->buflen = blocks * td->min_bs; } +/* + * For random io, generate a random new block and see if it's used. Repeat + * until we find a free one. For sequential io, just return the end of + * the last io issued. + */ static int get_next_offset(struct thread_data *td, unsigned long long *offset) { unsigned long long b, rb; @@ -200,6 +220,9 @@ static unsigned int get_next_buflen(struct thread_data *td) return buflen; } +/* + * Check if we are above the minimum rate given. + */ static int check_min_rate(struct thread_data *td, struct timeval *now) { unsigned long spent; @@ -287,8 +310,8 @@ static int verify_io_u_crc32(struct verify_header *hdr, struct io_u *io_u) c = crc32(p, hdr->len - sizeof(*hdr)); if (c != hdr->crc32) { - fprintf(f_err, "crc32: verify failed at %llu/%u\n", io_u->offset, io_u->buflen); - fprintf(f_err, "crc32: wanted %lx, got %lx\n", hdr->crc32, c); + log_err("crc32: verify failed at %llu/%u\n", io_u->offset, io_u->buflen); + log_err("crc32: wanted %lx, got %lx\n", hdr->crc32, c); return 1; } @@ -305,7 +328,7 @@ static int verify_io_u_md5(struct verify_header *hdr, struct io_u *io_u) md5_update(&md5_ctx, p, hdr->len - sizeof(*hdr)); if (memcmp(hdr->md5_digest, md5_ctx.hash, sizeof(md5_ctx.hash))) { - fprintf(f_err, "md5: verify failed at %llu/%u\n", io_u->offset, io_u->buflen); + log_err("md5: verify failed at %llu/%u\n", io_u->offset, io_u->buflen); hexdump(hdr->md5_digest, sizeof(hdr->md5_digest)); hexdump(md5_ctx.hash, sizeof(md5_ctx.hash)); return 1; @@ -327,7 +350,7 @@ static int verify_io_u(struct io_u *io_u) else if (hdr->verify_type == VERIFY_CRC32) ret = verify_io_u_crc32(hdr, io_u); else { - fprintf(f_err, "Bad verify type %d\n", hdr->verify_type); + log_err("Bad verify type %d\n", hdr->verify_type); ret = 1; } @@ -348,6 +371,11 @@ static void fill_md5(struct verify_header *hdr, void *p, unsigned int len) memcpy(hdr->md5_digest, md5_ctx.hash, sizeof(md5_ctx.hash)); } +/* + * Return the data direction for the next io_u. If the job is a + * mixed read/write workload, check the rwmix cycle and switch if + * necessary. + */ static int get_rw_ddir(struct thread_data *td) { if (td_rw(td)) { @@ -406,7 +434,7 @@ static void populate_io_u(struct thread_data *td, struct io_u *io_u) static int td_io_prep(struct thread_data *td, struct io_u *io_u) { - if (td->io_prep && td->io_prep(td, io_u)) + if (td->io_ops->prep && td->io_ops->prep(td, io_u)) return 1; return 0; @@ -468,6 +496,10 @@ struct io_u *__get_io_u(struct thread_data *td) return io_u; } +/* + * Return an io_u to be processed. Gets a buflen and offset, sets direction, + * etc. The returned io_u is fully ready to be prepped and submitted. + */ static struct io_u *get_io_u(struct thread_data *td) { struct io_u *io_u; @@ -537,8 +569,8 @@ static int get_next_verify(struct thread_data *td, struct io_u *io_u) static int sync_td(struct thread_data *td) { - if (td->io_sync) - return td->io_sync(td); + if (td->io_ops->sync) + return td->io_ops->sync(td); return 0; } @@ -546,14 +578,14 @@ static int sync_td(struct thread_data *td) static int io_u_getevents(struct thread_data *td, int min, int max, struct timespec *t) { - return td->io_getevents(td, min, max, t); + return td->io_ops->getevents(td, min, max, t); } static int io_u_queue(struct thread_data *td, struct io_u *io_u) { gettimeofday(&io_u->issue_time, NULL); - return td->io_queue(td, io_u); + return td->io_ops->queue(td, io_u); } #define iocb_time(iocb) ((unsigned long) (iocb)->data) @@ -597,13 +629,17 @@ static void ios_completed(struct thread_data *td,struct io_completion_data *icd) icd->bytes_done[0] = icd->bytes_done[1] = 0; for (i = 0; i < icd->nr; i++) { - io_u = td->io_event(td, i); + io_u = td->io_ops->event(td, i); io_completed(td, io_u, icd); put_io_u(td, io_u); } } +/* + * When job exits, we can cancel the in-flight IO if we are using async + * io. Attempt to do so. + */ static void cleanup_pending_aio(struct thread_data *td) { struct timespec ts = { .tv_sec = 0, .tv_nsec = 0}; @@ -624,11 +660,11 @@ static void cleanup_pending_aio(struct thread_data *td) /* * now cancel remaining active events */ - if (td->io_cancel) { + if (td->io_ops->cancel) { list_for_each_safe(entry, n, &td->io_u_busylist) { io_u = list_entry(entry, struct io_u, list); - r = td->io_cancel(td, io_u); + r = td->io_ops->cancel(td, io_u); if (!r) put_io_u(td, io_u); } @@ -657,6 +693,10 @@ static int do_io_u_verify(struct thread_data *td, struct io_u **io_u) return ret; } +/* + * The main verify engine. Runs over the writes we previusly submitted, + * reads the blocks back in, and checks the crc/md5 of the data. + */ static void do_verify(struct thread_data *td) { struct timeval t; @@ -709,7 +749,7 @@ static void do_verify(struct thread_data *td) break; } - v_io_u = td->io_event(td, 0); + v_io_u = td->io_ops->event(td, 0); icd.nr = 1; icd.error = 0; io_completed(td, v_io_u, &icd); @@ -738,7 +778,32 @@ static void do_verify(struct thread_data *td) } /* - * Main IO worker functions. It retrieves io_u's to process and queues + * Not really an io thread, all it does is burn CPU cycles in the specified + * manner. + */ +static void do_cpuio(struct thread_data *td) +{ + struct timeval e; + int split = 100 / td->cpuload; + int i = 0; + + while (!td->terminate) { + gettimeofday(&e, NULL); + + if (runtime_exceeded(td, &e)) + break; + + if (!(i % split)) + __usec_sleep(10000); + else + usec_sleep(td, 10000); + + i++; + } +} + +/* + * Main IO worker function. It retrieves io_u's to process and queues * and reaps them, checking for rate and errors along the way. */ static void do_io(struct thread_data *td) @@ -830,30 +895,12 @@ static void do_io(struct thread_data *td) } } -static void cleanup_io(struct thread_data *td) -{ - if (td->io_cleanup) - td->io_cleanup(td); -} - static int init_io(struct thread_data *td) { - if (td->io_engine == FIO_SYNCIO) - return fio_syncio_init(td); - else if (td->io_engine == FIO_MMAPIO) - return fio_mmapio_init(td); - else if (td->io_engine == FIO_LIBAIO) - return fio_libaio_init(td); - else if (td->io_engine == FIO_POSIXAIO) - return fio_posixaio_init(td); - else if (td->io_engine == FIO_SGIO) - return fio_sgio_init(td); - else if (td->io_engine == FIO_SPLICEIO) - return fio_spliceio_init(td); - else { - fprintf(f_err, "bad io_engine %d\n", td->io_engine); - return 1; - } + if (td->io_ops->init) + return td->io_ops->init(td); + + return 0; } static void cleanup_io_u(struct thread_data *td) @@ -878,7 +925,7 @@ static void cleanup_io_u(struct thread_data *td) } else if (td->mem_type == MEM_MMAP) munmap(td->orig_buffer, td->orig_buffer_size); else - fprintf(f_err, "Bad memory type %d\n", td->mem_type); + log_err("Bad memory type %d\n", td->mem_type); td->orig_buffer = NULL; } @@ -889,7 +936,10 @@ static int init_io_u(struct thread_data *td) int i, max_units; char *p; - if (td->io_engine & FIO_SYNCIO) + if (td->io_ops->flags & FIO_CPUIO) + return 0; + + if (td->io_ops->flags & FIO_SYNCIO) max_units = 1; else max_units = td->iodepth; @@ -937,43 +987,39 @@ static int init_io_u(struct thread_data *td) return 0; } -static int create_file(struct thread_data *td, unsigned long long size, - int extend) +static int create_file(struct thread_data *td, unsigned long long size) { unsigned long long left; unsigned int bs; - int r, oflags; char *b; + int r; /* * unless specifically asked for overwrite, let normal io extend it */ - if (td_write(td) && !td->overwrite) + if (!td->overwrite) { + td->real_file_size = size; return 0; + } if (!size) { - fprintf(f_err, "Need size for create\n"); + log_err("Need size for create\n"); td_verror(td, EINVAL); return 1; } - if (!extend) { - oflags = O_CREAT | O_TRUNC; - fprintf(f_out, "%s: Laying out IO file (%LuMiB)\n", td->name, size >> 20); - } else { - oflags = O_APPEND; - fprintf(f_out, "%s: Extending IO file (%Lu -> %LuMiB)\n", td->name, (td->file_size - size) >> 20, td->file_size >> 20); - } + temp_stall_ts = 1; + fprintf(f_out, "%s: Laying out IO file (%LuMiB)\n",td->name,size >> 20); - td->fd = open(td->file_name, O_WRONLY | oflags, 0644); + td->fd = open(td->file_name, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (td->fd < 0) { td_verror(td, errno); - return 1; + goto done_noclose; } - if (!extend && ftruncate(td->fd, td->file_size) == -1) { + if (ftruncate(td->fd, td->file_size) == -1) { td_verror(td, errno); - return 1; + goto done; } td->io_size = td->file_size; @@ -1006,9 +1052,12 @@ static int create_file(struct thread_data *td, unsigned long long size, else if (td->create_fsync) fsync(td->fd); + free(b); +done: close(td->fd); td->fd = -1; - free(b); +done_noclose: + temp_stall_ts = 0; return 0; } @@ -1016,15 +1065,17 @@ static int file_size(struct thread_data *td) { struct stat st; - if (fstat(td->fd, &st) == -1) { - td_verror(td, errno); - return 1; - } + if (td->overwrite) { + if (fstat(td->fd, &st) == -1) { + td_verror(td, errno); + return 1; + } - td->real_file_size = st.st_size; + td->real_file_size = st.st_size; - if (!td->file_size || td->file_size > td->real_file_size) - td->file_size = td->real_file_size; + if (!td->file_size || td->file_size > td->real_file_size) + td->file_size = td->real_file_size; + } td->file_size -= td->file_offset; return 0; @@ -1068,13 +1119,13 @@ static int get_file_size(struct thread_data *td) return ret; if (td->file_offset > td->real_file_size) { - fprintf(f_err, "%s: offset extends end (%Lu > %Lu)\n", td->name, td->file_offset, td->real_file_size); + log_err("%s: offset extends end (%Lu > %Lu)\n", td->name, td->file_offset, td->real_file_size); return 1; } td->io_size = td->file_size; if (td->io_size == 0) { - fprintf(f_err, "%s: no io blocks\n", td->name); + log_err("%s: no io blocks\n", td->name); td_verror(td, EINVAL); return 1; } @@ -1158,6 +1209,9 @@ static int setup_file(struct thread_data *td) struct stat st; int flags = 0; + if (td->io_ops->flags & FIO_CPUIO) + return 0; + if (stat(td->file_name, &st) == -1) { if (errno != ENOENT) { td_verror(td, errno); @@ -1167,13 +1221,12 @@ static int setup_file(struct thread_data *td) td_verror(td, ENOENT); return 1; } - if (create_file(td, td->file_size, 0)) + if (create_file(td, td->file_size)) + return 1; + } else if (td->filetype == FIO_TYPE_FILE && + st.st_size < (off_t) td->file_size) { + if (create_file(td, td->file_size)) return 1; - } else if (td->filetype == FIO_TYPE_FILE) { - if (st.st_size < (off_t) td->file_size) { - if (create_file(td, td->file_size - st.st_size, 1)) - return 1; - } } if (td->odirect) @@ -1209,10 +1262,10 @@ static int setup_file(struct thread_data *td) if (get_file_size(td)) return 1; - if (td->io_engine != FIO_MMAPIO) - return setup_file_plain(td); - else + if (td->io_ops->flags & FIO_MMAPIO) return setup_file_mmap(td); + else + return setup_file_plain(td); } static int switch_ioscheduler(struct thread_data *td) @@ -1253,7 +1306,7 @@ static int switch_ioscheduler(struct thread_data *td) sprintf(tmp2, "[%s]", td->ioscheduler); if (!strstr(tmp, tmp2)) { - fprintf(f_err, "fio: io scheduler %s not found\n", td->ioscheduler); + log_err("fio: io scheduler %s not found\n", td->ioscheduler); td_verror(td, EINVAL); fclose(f); return 1; @@ -1265,7 +1318,7 @@ static int switch_ioscheduler(struct thread_data *td) static void clear_io_state(struct thread_data *td) { - if (td->io_engine == FIO_SYNCIO) + if (td->io_ops->flags & FIO_SYNCIO) lseek(td->fd, SEEK_SET, 0); td->last_pos = 0; @@ -1277,6 +1330,10 @@ static void clear_io_state(struct thread_data *td) memset(td->file_map, 0, td->num_maps * sizeof(long)); } +/* + * Entry point for the thread based jobs. The process based jobs end up + * here as well, after a little setup. + */ static void *thread_main(void *data) { struct thread_data *td = data; @@ -1312,7 +1369,7 @@ static void *thread_main(void *data) } } - if (nice(td->nice) < 0) { + if (nice(td->nice) == -1) { td_verror(td, errno); goto err; } @@ -1346,7 +1403,10 @@ static void *thread_main(void *data) clear_io_state(td); prune_io_piece_log(td); - do_io(td); + if (td->io_ops->flags & FIO_CPUIO) + do_cpuio(td); + else + do_io(td); td->runtime[td->ddir] += mtime_since_now(&td->start); if (td_rw(td) && td->io_bytes[td->ddir ^ 1]) @@ -1392,13 +1452,17 @@ err: } if (td->mmap) munmap(td->mmap, td->file_size); - cleanup_io(td); + close_ioengine(td); cleanup_io_u(td); td_set_runstate(td, TD_EXITED); return NULL; } +/* + * We cannot pass the td data into a forked process, so attach the td and + * pass it to the thread worker. + */ static void *fork_main(int shmid, int offset) { struct thread_data *td; @@ -1416,6 +1480,9 @@ static void *fork_main(int shmid, int offset) return NULL; } +/* + * Sets the status of the 'td' in the printed status map. + */ static void check_str_update(struct thread_data *td) { char c = run_str[td->thread_number - 1]; @@ -1461,12 +1528,15 @@ static void check_str_update(struct thread_data *td) c = 'P'; break; default: - fprintf(f_err, "state %d\n", td->runstate); + log_err("state %d\n", td->runstate); } run_str[td->thread_number - 1] = c; } +/* + * Convert seconds to a printable string. + */ static void eta_to_str(char *str, int eta_sec) { unsigned int d, h, m, s; @@ -1495,6 +1565,9 @@ static void eta_to_str(char *str, int eta_sec) str += sprintf(str, "%02ds", s); } +/* + * Best effort calculation of the estimated pending runtime of a job. + */ static int thread_eta(struct thread_data *td, unsigned long elapsed) { unsigned long long bytes_total, bytes_done; @@ -1549,7 +1622,7 @@ static int thread_eta(struct thread_data *td, unsigned long elapsed) else if (t_eta) eta_sec = t_eta; else - eta_sec = INT_MAX; + eta_sec = 0; } else { /* * thread is already done or waiting for fsync @@ -1560,6 +1633,10 @@ static int thread_eta(struct thread_data *td, unsigned long elapsed) return eta_sec; } +/* + * Print status of the jobs we know about. This includes rate estimates, + * ETA, thread state, etc. + */ static void print_thread_status(void) { unsigned long elapsed = time_since_now(&genesis); @@ -1567,6 +1644,9 @@ static void print_thread_status(void) char eta_str[32]; double perc = 0.0; + if (temp_stall_ts || terse_output) + return; + eta_secs = malloc(thread_number * sizeof(int)); memset(eta_secs, 0, thread_number * sizeof(int)); @@ -1616,7 +1696,7 @@ static void print_thread_status(void) printf("Threads running: %d", nr_running); if (m_rate || t_rate) printf(", commitrate %d/%dKiB/sec", t_rate, m_rate); - if (eta_sec != INT_MAX) { + if (eta_sec != INT_MAX && nr_running) { perc *= 100.0; printf(": [%s] [%3.2f%% done] [eta %s]", run_str, perc,eta_str); } @@ -1625,16 +1705,22 @@ static void print_thread_status(void) free(eta_secs); } +/* + * Run over the job map and reap the threads that have exited, if any. + */ static void reap_threads(int *nr_running, int *t_rate, int *m_rate) { - int i; + int i, cputhreads; /* * reap exited threads (TD_EXITED -> TD_REAPED) */ - for (i = 0; i < thread_number; i++) { + for (i = 0, cputhreads = 0; i < thread_number; i++) { struct thread_data *td = &threads[i]; + if (td->io_ops->flags & FIO_CPUIO) + cputhreads++; + if (td->runstate != TD_EXITED) continue; @@ -1652,6 +1738,9 @@ static void reap_threads(int *nr_running, int *t_rate, int *m_rate) (*m_rate) -= td->ratemin; (*t_rate) -= td->rate; } + + if (*nr_running == cputhreads) + terminate_threads(TERMINATE_ALL); } static void fio_unpin_memory(void *pinned) @@ -1696,6 +1785,9 @@ static void *fio_pin_memory(void) return ptr; } +/* + * Main function for kicking off and reaping jobs, as needed. + */ static void run_threads(void) { struct thread_data *td; @@ -1705,8 +1797,10 @@ static void run_threads(void) mlocked_mem = fio_pin_memory(); - printf("Starting %d thread%s\n", thread_number, thread_number > 1 ? "s" : ""); - fflush(stdout); + if (!terse_output) { + printf("Starting %d thread%s\n", thread_number, thread_number > 1 ? "s" : ""); + fflush(stdout); + } signal(SIGINT, sig_handler); signal(SIGALRM, sig_handler); @@ -1800,7 +1894,6 @@ static void run_threads(void) * Wait for the started threads to transition to * TD_INITIALIZED. */ - printf("fio: Waiting for threads to initialize...\n"); gettimeofday(&this_start, NULL); left = this_jobs; while (left) { @@ -1826,7 +1919,7 @@ static void run_threads(void) } if (left) { - fprintf(f_err, "fio: %d jobs failed to start\n", left); + log_err("fio: %d jobs failed to start\n", left); for (i = 0; i < this_jobs; i++) { td = map[i]; if (!td) @@ -1839,7 +1932,6 @@ static void run_threads(void) /* * start created threads (TD_INITIALIZED -> TD_RUNNING). */ - printf("fio: Go for launch\n"); for (i = 0; i < thread_number; i++) { td = &threads[i]; @@ -1876,7 +1968,7 @@ int main(int argc, char *argv[]) return 1; if (!thread_number) { - fprintf(f_err, "Nothing to do\n"); + log_err("Nothing to do\n"); return 1; }