/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
#include <sys/statfs.h>
#include <sys/resource.h>
#include <inttypes.h>
#include "ioshark.h"
#define IOSHARK_MAIN
#include "ioshark_bench.h"

/*
 * Note on "quick" mode where we do reads on existing /system,
 * /vendor and other files in ro partitions, instead of creating
 * them. The ioshark compiler builds up a table of all the files
 * in /system, /vendor and other ro partitions. For files in this
 * list, the benchmark skips the pre-creation of these files and
 * reads them directly.
 * The code relevant to this is in *filename_cache*.
 */

char *progname;

#define MAX_INPUT_FILES		8192
#define MAX_THREADS		8192

struct thread_state_s {
	char *filename;
	FILE *fp;
	int num_files;
	void *db_handle;
};

struct thread_state_s thread_state[MAX_INPUT_FILES];
int num_input_files = 0;
int next_input_file;

pthread_t tid[MAX_THREADS];

/*
 * Global options
 */
int do_delay = 0;
int verbose = 0;
int summary_mode = 0;
int quick_mode = 0;
char *blockdev_name = NULL;	/* if user would like to specify blockdev */

#if 0
static long gettid()
{
        return syscall(__NR_gettid);
}
#endif

void usage()
{
	fprintf(stderr, "%s [-b blockdev_name] [-d preserve_delays] [-n num_iterations] [-t num_threads] -q -v | -s <list of parsed input files>\n",
		progname);
	fprintf(stderr, "%s -s, -v are mutually exclusive\n",
		progname);
	exit(EXIT_FAILURE);
}

pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER;
struct timeval aggregate_file_create_time;
struct timeval debug_file_create_time;
struct timeval aggregate_file_remove_time;
struct timeval aggregate_IO_time;
struct timeval aggregate_delay_time;

u_int64_t aggr_op_counts[IOSHARK_MAX_FILE_OP];
struct rw_bytes_s aggr_io_rw_bytes;
struct rw_bytes_s aggr_create_rw_bytes;

/*
 * Locking needed here because aggregate_delay_time is updated
 * from multiple threads concurrently.
 */
static void
update_time(struct timeval *aggr_time,
	    struct timeval *delta_time)
{
	struct timeval tmp;

	pthread_mutex_lock(&time_mutex);
	timeradd(aggr_time, delta_time, &tmp);
	*aggr_time = tmp;
	pthread_mutex_unlock(&time_mutex);
}

static void
update_op_counts(u_int64_t *op_counts)
{
	int i;

	pthread_mutex_lock(&stats_mutex);
	for (i = IOSHARK_LSEEK ; i < IOSHARK_MAX_FILE_OP ; i++)
		aggr_op_counts[i] += op_counts[i];
	pthread_mutex_unlock(&stats_mutex);
}

static void
update_byte_counts(struct rw_bytes_s *dest, struct rw_bytes_s *delta)
{
	pthread_mutex_lock(&stats_mutex);
	dest->bytes_read += delta->bytes_read;
	dest->bytes_written += delta->bytes_written;
	pthread_mutex_unlock(&stats_mutex);
}

static int work_next_file;
static int work_num_files;

void
init_work(int next_file, int num_files)
{
	pthread_mutex_lock(&work_mutex);
	work_next_file = next_file;
	work_num_files = work_next_file + num_files;
	pthread_mutex_unlock(&work_mutex);
}

/* Dole out the next file to work on to the thread */
static struct thread_state_s *
get_work()
{
	struct thread_state_s *work = NULL;

	pthread_mutex_lock(&work_mutex);
	if (work_next_file < work_num_files)
		work = &thread_state[work_next_file++];
	pthread_mutex_unlock(&work_mutex);
	return work;
}

static void
create_files(struct thread_state_s *state)
{
	int i;
	struct ioshark_file_state file_state;
	char path[MAX_IOSHARK_PATHLEN];
	void *db_node;
	struct rw_bytes_s rw_bytes;
	char *filename;
	int readonly;

	memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
	for (i = 0 ; i < state->num_files ; i++) {
		if (ioshark_read_file_state(state->fp, &file_state) != 1) {
			fprintf(stderr, "%s read error tracefile\n",
				progname);
			exit(EXIT_FAILURE);
		}
		/*
		 * Check to see if the file is in a readonly partition,
		 * in which case, we don't have to pre-create the file
		 * we can just read the existing file.
		 */
		filename =
			get_ro_filename(file_state.global_filename_ix);
		if (quick_mode)
			assert(filename != NULL);
		if (quick_mode == 0 ||
		    is_readonly_mount(filename, file_state.size) == 0) {
			sprintf(path, "file.%d.%"PRIu64"",
				(int)(state - thread_state),
				file_state.fileno);
			create_file(path, file_state.size,
				    &rw_bytes);
			filename = path;
			readonly = 0;
		} else {
			readonly = 1;
		}
		db_node = files_db_add_byfileno(state->db_handle,
						file_state.fileno,
						readonly);
		files_db_update_size(db_node, file_state.size);
		files_db_update_filename(db_node, filename);
	}
	update_byte_counts(&aggr_create_rw_bytes, &rw_bytes);
}

static void
do_one_io(void *db_node,
	  struct ioshark_file_operation *file_op,
	  u_int64_t *op_counts,
	  struct rw_bytes_s *rw_bytes,
	  char **bufp, int *buflen)
{
	assert(file_op->ioshark_io_op < IOSHARK_MAX_FILE_OP);
	op_counts[file_op->ioshark_io_op]++;
	switch (file_op->ioshark_io_op) {
	int ret;
	char *p;
	int fd;

	case IOSHARK_LSEEK:
	case IOSHARK_LLSEEK:
		ret = lseek(files_db_get_fd(db_node),
			    file_op->lseek_offset,
			    file_op->lseek_action);
		if (ret < 0) {
			fprintf(stderr,
				"%s: lseek(%s %"PRIu64" %d) returned error %d\n",
				progname, files_db_get_filename(db_node),
				file_op->lseek_offset,
				file_op->lseek_action, errno);
			exit(EXIT_FAILURE);
		}
		break;
	case IOSHARK_PREAD64:
		p = get_buf(bufp, buflen, file_op->prw_len, 0);
		ret = pread(files_db_get_fd(db_node), p,
			    file_op->prw_len, file_op->prw_offset);
		rw_bytes->bytes_read += file_op->prw_len;
		if (ret < 0) {
			fprintf(stderr,
				"%s: pread(%s %"PRIu64" %"PRIu64") error %d\n",
				progname,
				files_db_get_filename(db_node),
				file_op->prw_len,
				file_op->prw_offset, errno);
			exit(EXIT_FAILURE);
		}
		break;
	case IOSHARK_PWRITE64:
		p = get_buf(bufp, buflen, file_op->prw_len, 1);
		ret = pwrite(files_db_get_fd(db_node), p,
			     file_op->prw_len, file_op->prw_offset);
		rw_bytes->bytes_written += file_op->prw_len;
		if (ret < 0) {
			fprintf(stderr,
				"%s: pwrite(%s %"PRIu64" %"PRIu64") error %d\n",
				progname,
				files_db_get_filename(db_node),
				file_op->prw_len,
				file_op->prw_offset, errno);
			exit(EXIT_FAILURE);
		}
		break;
	case IOSHARK_READ:
		p = get_buf(bufp, buflen, file_op->rw_len, 0);
		ret = read(files_db_get_fd(db_node), p,
			   file_op->rw_len);
		rw_bytes->bytes_read += file_op->rw_len;
		if (ret < 0) {
			fprintf(stderr,
				"%s: read(%s %"PRIu64") error %d\n",
				progname,
				files_db_get_filename(db_node),
				file_op->rw_len,
				errno);
			exit(EXIT_FAILURE);
		}
		break;
	case IOSHARK_WRITE:
		p = get_buf(bufp, buflen, file_op->rw_len, 1);
		ret = write(files_db_get_fd(db_node), p,
			    file_op->rw_len);
		rw_bytes->bytes_written += file_op->rw_len;
		if (ret < 0) {
			fprintf(stderr,
				"%s: write(%s %"PRIu64") error %d\n",
				progname,
				files_db_get_filename(db_node),
				file_op->rw_len,
				errno);
			exit(EXIT_FAILURE);
		}
		break;
	case IOSHARK_MMAP:
	case IOSHARK_MMAP2:
		ioshark_handle_mmap(db_node, file_op,
				    bufp, buflen, op_counts,
				    rw_bytes);
		break;
	case IOSHARK_OPEN:
		if (file_op->open_flags & O_CREAT) {
			fd = open(files_db_get_filename(db_node),
				  file_op->open_flags,
				  file_op->open_mode);
			if (fd < 0) {
				/*
				 * EEXIST error acceptable, others are fatal.
				 * Although we failed to O_CREAT the file (O_EXCL)
				 * We will force an open of the file before any
				 * IO.
				 */
				if (errno == EEXIST) {
					return;
				} else {
					fprintf(stderr,
						"%s: O_CREAT open(%s %x %o) error %d\n",
						progname,
						files_db_get_filename(db_node),
						file_op->open_flags,
						file_op->open_mode, errno);
					exit(EXIT_FAILURE);
				}
			}
		} else {
			fd = open(files_db_get_filename(db_node),
				  file_op->open_flags);
			if (fd < 0) {
				if (file_op->open_flags & O_DIRECTORY) {
					/* O_DIRECTORY open()s should fail */
					return;
				} else {
					fprintf(stderr,
						"%s: open(%s %x) error %d\n",
						progname,
						files_db_get_filename(db_node),
						file_op->open_flags,
						errno);
					exit(EXIT_FAILURE);
				}
			}
		}
		files_db_close_fd(db_node);
		files_db_update_fd(db_node, fd);
		break;
	case IOSHARK_FSYNC:
	case IOSHARK_FDATASYNC:
		if (file_op->ioshark_io_op == IOSHARK_FSYNC) {
			ret = fsync(files_db_get_fd(db_node));
			if (ret < 0) {
				fprintf(stderr,
					"%s: fsync(%s) error %d\n",
					progname,
					files_db_get_filename(db_node),
					errno);
				exit(EXIT_FAILURE);
			}
		} else {
			ret = fdatasync(files_db_get_fd(db_node));
			if (ret < 0) {
				fprintf(stderr,
					"%s: fdatasync(%s) error %d\n",
					progname,
					files_db_get_filename(db_node),
					errno);
				exit(EXIT_FAILURE);
			}
		}
		break;
	case IOSHARK_CLOSE:
		ret = close(files_db_get_fd(db_node));
		if (ret < 0) {
			fprintf(stderr,
				"%s: close(%s) error %d\n",
				progname,
				files_db_get_filename(db_node), errno);
			exit(EXIT_FAILURE);
		}
		files_db_update_fd(db_node, -1);
		break;
	default:
		fprintf(stderr, "%s: unknown FILE_OP %d\n",
			progname, file_op->ioshark_io_op);
		exit(EXIT_FAILURE);
		break;
	}
}

static void
do_io(struct thread_state_s *state)
{
	void *db_node;
	struct ioshark_header header;
	struct ioshark_file_operation file_op;
	int fd;
	int i;
	char *buf = NULL;
	int buflen = 0;
	struct timeval total_delay_time;
	u_int64_t op_counts[IOSHARK_MAX_FILE_OP];
	struct rw_bytes_s rw_bytes;

	rewind(state->fp);
	if (ioshark_read_header(state->fp, &header) != 1) {
		fprintf(stderr, "%s read error %s\n",
			progname, state->filename);
		exit(EXIT_FAILURE);
	}
	/*
	 * First open and pre-create all the files. Indexed by fileno.
	 */
	timerclear(&total_delay_time);
	memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
	memset(op_counts, 0, sizeof(op_counts));
	fseek(state->fp,
	      sizeof(struct ioshark_header) +
	      header.num_files * sizeof(struct ioshark_file_state),
	      SEEK_SET);
	/*
	 * Loop over all the IOs, and launch each
	 */
	for (i = 0 ; i < (int)header.num_io_operations ; i++) {
		if (ioshark_read_file_op(state->fp, &file_op) != 1) {
			fprintf(stderr, "%s read error trace.outfile\n",
				progname);
			goto fail;
		}
		if (do_delay) {
			struct timeval start;

			(void)gettimeofday(&start, (struct timezone *)NULL);
			usleep(file_op.delta_us);
			update_delta_time(&start, &total_delay_time);
		}
		db_node = files_db_lookup_byfileno(state->db_handle,
						   file_op.fileno);
		if (db_node == NULL) {
			fprintf(stderr,
				"%s Can't lookup fileno %"PRIu64", fatal error\n",
				progname, file_op.fileno);
			fprintf(stderr,
				"%s state filename %s, i %d\n",
				progname, state->filename, i);
			goto fail;
		}
		if (file_op.ioshark_io_op != IOSHARK_OPEN &&
		    files_db_get_fd(db_node) == -1) {
			int openflags;

			/*
			 * This is a hack to workaround the fact that we did not
			 * see an open() for this file until now. open() the
			 * file O_RDWR, so that we can perform the IO.
			 */
			if (files_db_readonly(db_node))
				openflags = O_RDONLY;
			else
				openflags = O_RDWR;
			fd = open(files_db_get_filename(db_node),
				  openflags);
			if (fd < 0) {
				fprintf(stderr, "%s: open(%s %x) error %d\n",
					progname,
					files_db_get_filename(db_node),
					openflags,
					errno);
				goto fail;
			}
			files_db_update_fd(db_node, fd);
		}
		do_one_io(db_node, &file_op,
			  op_counts, &rw_bytes, &buf, &buflen);
	}

	free(buf);
	files_db_fsync_discard_files(state->db_handle);
	files_db_close_files(state->db_handle);
	update_time(&aggregate_delay_time, &total_delay_time);
	update_op_counts(op_counts);
	update_byte_counts(&aggr_io_rw_bytes, &rw_bytes);
	return;

fail:
	free(buf);
	exit(EXIT_FAILURE);
}

void *
io_thread(void *unused __attribute__((unused)))
{
	struct thread_state_s *state;

	srand(gettid());
	while ((state = get_work()))
		do_io(state);
	pthread_exit(NULL);
        return(NULL);
}

static void
do_create(struct thread_state_s *state)
{
	struct ioshark_header header;

	if (ioshark_read_header(state->fp, &header) != 1) {
		fprintf(stderr, "%s read error %s\n",
			progname, state->filename);
		exit(EXIT_FAILURE);
	}
	state->num_files = header.num_files;
	state->db_handle = files_db_create_handle();
	create_files(state);
}

void *
create_files_thread(void *unused __attribute__((unused)))
{
	struct thread_state_s *state;

	while ((state = get_work()))
		do_create(state);
	pthread_exit(NULL);
	return(NULL);
}

int
get_start_end(int *start_ix)
{
	int i, j, ret_numfiles;
	u_int64_t free_fs_bytes;
	char *infile;
	FILE *fp;
	struct ioshark_header header;
	struct ioshark_file_state file_state;
	struct statfs fsstat;
	static int fssize_clamp_next_index = 0;
	static int chunk = 0;

	if (fssize_clamp_next_index == num_input_files)
		return 0;
	if (statfs("/data/local/tmp", &fsstat) < 0) {
		fprintf(stderr, "%s: Can't statfs /data/local/tmp\n",
			progname);
		exit(EXIT_FAILURE);
	}
	free_fs_bytes = (fsstat.f_bavail * fsstat.f_bsize) * 9 /10;
	for (i = fssize_clamp_next_index; i < num_input_files; i++) {
		infile = thread_state[i].filename;
		fp = fopen(infile, "r");
		if (fp == NULL) {
			fprintf(stderr, "%s: Can't open %s\n",
				progname, infile);
			exit(EXIT_FAILURE);
		}
		if (ioshark_read_header(fp, &header) != 1) {
			fprintf(stderr, "%s read error %s\n",
				progname, infile);
			exit(EXIT_FAILURE);
		}
		for (j = 0 ; j < (int)header.num_files ; j++) {
			if (ioshark_read_file_state(fp, &file_state) != 1) {
				fprintf(stderr, "%s read error tracefile\n",
					progname);
				exit(EXIT_FAILURE);
			}
			if (quick_mode == 0 ||
			    !is_readonly_mount(
				    get_ro_filename(file_state.global_filename_ix),
				    file_state.size)) {
				if (file_state.size > free_fs_bytes) {
					fclose(fp);
					goto out;
				}
				free_fs_bytes -= file_state.size;
			}
		}
		fclose(fp);
	}
out:
	if (verbose) {
		if (chunk > 0 || i < num_input_files) {
			printf("Breaking up input files, Chunk %d: %d to %d\n",
			       chunk++, fssize_clamp_next_index, i - 1);
		} else {
			printf("Entire Dataset fits start = %d to %d, free_bytes = %ju\n",
			       fssize_clamp_next_index,
			       i - fssize_clamp_next_index,
			       free_fs_bytes);
		}
	}
	*start_ix = fssize_clamp_next_index;
	ret_numfiles = i - fssize_clamp_next_index;
	fssize_clamp_next_index = i;
	return ret_numfiles;
}

int
ioshark_pthread_create(pthread_t *tidp, void *(*start_routine)(void *))
{
	pthread_attr_t attr;

	pthread_attr_init(&attr);
	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
	pthread_attr_setstacksize(&attr, (size_t)(1024*1024));
	return pthread_create(tidp, &attr, start_routine, (void *)NULL);
}

void
wait_for_threads(int num_threads)
{
	int i;

	for (i = 0; i < num_threads; i++) {
		pthread_join(tid[i], NULL);
		tid[i] = 0;
	}
}

#define IOSHARK_FD_LIM		8192

static void
sizeup_fd_limits(void)
{
	struct rlimit r;

	getrlimit(RLIMIT_NOFILE, &r);
	if (r.rlim_cur >= IOSHARK_FD_LIM)
		/* cur limit already at what we want */
		return;
	/*
	 * Size up both the Max and Cur to IOSHARK_FD_LIM.
	 * If we are not running as root, this will fail,
	 * catch that below and exit.
	 */
	if (r.rlim_max < IOSHARK_FD_LIM)
		r.rlim_max = IOSHARK_FD_LIM;
	r.rlim_cur = IOSHARK_FD_LIM;
	if (setrlimit(RLIMIT_NOFILE, &r) < 0) {
		fprintf(stderr, "%s: Can't setrlimit (RLIMIT_NOFILE, 8192)\n",
			progname);
		exit(EXIT_FAILURE);
	}
	getrlimit(RLIMIT_NOFILE, &r);
	if (r.rlim_cur < IOSHARK_FD_LIM) {
		fprintf(stderr, "%s: Can't setrlimit up to 8192\n",
			progname);
		fprintf(stderr, "%s: Running as root ?\n",
			progname);
		exit(EXIT_FAILURE);
	}
}

int
main(int argc, char **argv)
{
	int i;
	FILE *fp;
	struct stat st;
	char *infile;
	int num_threads = 0;
	int num_iterations = 1;
	int c;
	int num_files, start_file;
	struct thread_state_s *state;

	progname = argv[0];
        while ((c = getopt(argc, argv, "b:dn:st:qv")) != EOF) {
                switch (c) {
                case 'b':
			blockdev_name = strdup(optarg);
			break;
                case 'd':
			do_delay = 1;
			break;
                case 'n':
			num_iterations = atoi(optarg);
			break;
                case 's':
			/* Non-verbose summary mode for nightly runs */
			summary_mode = 1;
			break;
                case 't':
			num_threads = atoi(optarg);
			break;
                case 'q':
			/*
			 * If quick mode is enabled, then we won't
			 * pre-create files that we are doing IO on that
			 * live in readonly partitions (/system, /vendor etc)
			 */
			quick_mode = 1;
			break;
                case 'v':
			verbose = 1;
			break;
 	        default:
			usage();
		}
	}

	if ((verbose + summary_mode) == 2)
		usage();

	if (num_threads > MAX_THREADS)
		usage();

	if (optind == argc)
                usage();

	sizeup_fd_limits();

	for (i = optind; i < argc; i++) {
		infile = argv[i];
		if (stat(infile, &st) < 0) {
			fprintf(stderr, "%s: Can't stat %s\n",
				progname, infile);
			exit(EXIT_FAILURE);
		}
		if (st.st_size == 0) {
			fprintf(stderr, "%s: Empty file %s\n",
				progname, infile);
			continue;
		}
		fp = fopen(infile, "r");
		if (fp == NULL) {
			fprintf(stderr, "%s: Can't open %s\n",
				progname, infile);
			continue;
		}
		thread_state[num_input_files].filename = infile;
		thread_state[num_input_files].fp = fp;
		num_input_files++;
	}

	if (num_input_files == 0) {
		exit(EXIT_SUCCESS);
	}
	if (verbose) {
		printf("Total Input Files = %d\n", num_input_files);
		printf("Num Iterations = %d\n", num_iterations);
	}
	timerclear(&aggregate_file_create_time);
	timerclear(&aggregate_file_remove_time);
	timerclear(&aggregate_IO_time);

	if (quick_mode)
		init_filename_cache();

	capture_util_state_before();

	/*
	 * We pre-create the files that we need once and then we
	 * loop around N times doing IOs on the pre-created files.
	 *
	 * get_start_end() breaks up the total work here to make sure
	 * that all the files we need to pre-create fit into the
	 * available space in /data/local/tmp (hardcoded for now).
	 *
	 * If it won't fit, then we do several sweeps.
	 */
	while ((num_files = get_start_end(&start_file))) {
		struct timeval time_for_pass;

		/* Create files once */
		if (!summary_mode)
			printf("Doing Pre-creation of Files\n");
		if (quick_mode && !summary_mode)
			printf("Skipping Pre-creation of read-only Files\n");
		if (num_threads == 0 || num_threads > num_files)
			num_threads = num_files;
		(void)system("echo 3 > /proc/sys/vm/drop_caches");
		init_work(start_file, num_files);
		(void)gettimeofday(&time_for_pass,
				   (struct timezone *)NULL);
		for (i = 0; i < num_threads; i++) {
			if (ioshark_pthread_create(&(tid[i]),
						   create_files_thread)) {
				fprintf(stderr,
					"%s: Can't create creator thread %d\n",
					progname, i);
				exit(EXIT_FAILURE);
			}
		}
		wait_for_threads(num_threads);
		update_delta_time(&time_for_pass, &aggregate_file_create_time);
		/* Do the IOs N times */
		for (i = 0 ; i < num_iterations ; i++) {
			(void)system("echo 3 > /proc/sys/vm/drop_caches");
			if (!summary_mode) {
				if (num_iterations > 1)
					printf("Starting Test. Iteration %d...\n",
					       i);
				else
					printf("Starting Test...\n");
			}
			init_work(start_file, num_files);
			(void)gettimeofday(&time_for_pass,
					   (struct timezone *)NULL);
			for (c = 0; c < num_threads; c++) {
				if (ioshark_pthread_create(&(tid[c]),
							   io_thread)) {
					fprintf(stderr,
						"%s: Can't create thread %d\n",
						progname, c);
					exit(EXIT_FAILURE);
				}
			}
			wait_for_threads(num_threads);
			update_delta_time(&time_for_pass,
					  &aggregate_IO_time);
		}

		/*
		 * We are done with the N iterations of IO.
		 * Destroy the files we pre-created.
		 */
		init_work(start_file, num_files);
		while ((state = get_work())) {
			struct timeval start;

			(void)gettimeofday(&start, (struct timezone *)NULL);
			files_db_unlink_files(state->db_handle);
			update_delta_time(&start, &aggregate_file_remove_time);
			files_db_free_memory(state->db_handle);
		}
	}
	if (!summary_mode) {
		printf("Total Creation time = %ju.%ju (msecs.usecs)\n",
		       get_msecs(&aggregate_file_create_time),
		       get_usecs(&aggregate_file_create_time));
		printf("Total Remove time = %ju.%ju (msecs.usecs)\n",
		       get_msecs(&aggregate_file_remove_time),
		       get_usecs(&aggregate_file_remove_time));
		if (do_delay)
			printf("Total delay time = %ju.%ju (msecs.usecs)\n",
			       get_msecs(&aggregate_delay_time),
			       get_usecs(&aggregate_delay_time));
		printf("Total Test (IO) time = %ju.%ju (msecs.usecs)\n",
		       get_msecs(&aggregate_IO_time),
		       get_usecs(&aggregate_IO_time));
		if (verbose)
			print_bytes("Upfront File Creation bytes",
				    &aggr_create_rw_bytes);
		print_bytes("Total Test (IO) bytes", &aggr_io_rw_bytes);
		if (verbose)
			print_op_stats(aggr_op_counts);
		report_cpu_disk_util();
	} else {
		printf("%ju.%ju ",
		       get_msecs(&aggregate_file_create_time),
		       get_usecs(&aggregate_file_create_time));
		printf("%ju.%ju ",
		       get_msecs(&aggregate_file_remove_time),
		       get_usecs(&aggregate_file_remove_time));
		if (do_delay)
			printf("%ju.%ju ",
			       get_msecs(&aggregate_delay_time),
			       get_usecs(&aggregate_delay_time));
		printf("%ju.%ju ",
		       get_msecs(&aggregate_IO_time),
		       get_usecs(&aggregate_IO_time));
		print_bytes(NULL, &aggr_io_rw_bytes);
		report_cpu_disk_util();
		printf("\n");
	}
	if (quick_mode)
		free_filename_cache();
}
