1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <stdio.h>
18#include <sys/time.h>
19#include <sys/types.h>
20#include <unistd.h>
21#include <sys/syscall.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/stat.h>
25#include <sys/errno.h>
26#include <fcntl.h>
27#include <string.h>
28#include <assert.h>
29#include <pthread.h>
30#include <sys/statfs.h>
31#include <sys/resource.h>
32#include "ioshark.h"
33#define IOSHARK_MAIN
34#include "ioshark_bench.h"
35
36/*
37 * Note on "quick" mode where we do reads on existing /system,
38 * /vendor and other files in ro partitions, instead of creating
39 * them. The ioshark compiler builds up a table of all the files
40 * in /system, /vendor and other ro partitions. For files in this
41 * list, the benchmark skips the pre-creation of these files and
42 * reads them directly.
43 * The code relevant to this is in *filename_cache*.
44 */
45
46char *progname;
47
48#define MAX_INPUT_FILES		8192
49#define MAX_THREADS		8192
50
51struct thread_state_s {
52	char *filename;
53	FILE *fp;
54	int num_files;
55	void *db_handle;
56};
57
58struct thread_state_s thread_state[MAX_INPUT_FILES];
59int num_input_files = 0;
60int next_input_file;
61
62pthread_t tid[MAX_THREADS];
63
64/*
65 * Global options
66 */
67int do_delay = 0;
68int verbose = 0;
69int summary_mode = 0;
70int quick_mode = 0;
71
72#if 0
73static long gettid()
74{
75        return syscall(__NR_gettid);
76}
77#endif
78
79void usage()
80{
81	fprintf(stderr, "%s [-d preserve_delays] [-n num_iterations] [-t num_threads] -q -v | -s <list of parsed input files>\n",
82		progname);
83	fprintf(stderr, "%s -s, -v are mutually exclusive\n",
84		progname);
85	exit(EXIT_FAILURE);
86}
87
88pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;
89pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER;
90pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER;
91struct timeval aggregate_file_create_time;
92struct timeval debug_file_create_time;
93struct timeval aggregate_file_remove_time;
94struct timeval aggregate_IO_time;
95struct timeval aggregate_delay_time;
96
97u_int64_t aggr_op_counts[IOSHARK_MAX_FILE_OP];
98struct rw_bytes_s aggr_io_rw_bytes;
99struct rw_bytes_s aggr_create_rw_bytes;
100
101/*
102 * Locking needed here because aggregate_delay_time is updated
103 * from multiple threads concurrently.
104 */
105static void
106update_time(struct timeval *aggr_time,
107	    struct timeval *delta_time)
108{
109	struct timeval tmp;
110
111	pthread_mutex_lock(&time_mutex);
112	timeradd(aggr_time, delta_time, &tmp);
113	*aggr_time = tmp;
114	pthread_mutex_unlock(&time_mutex);
115}
116
117static void
118update_op_counts(u_int64_t *op_counts)
119{
120	int i;
121
122	pthread_mutex_lock(&stats_mutex);
123	for (i = IOSHARK_LSEEK ; i < IOSHARK_MAX_FILE_OP ; i++)
124		aggr_op_counts[i] += op_counts[i];
125	pthread_mutex_unlock(&stats_mutex);
126}
127
128static void
129update_byte_counts(struct rw_bytes_s *dest, struct rw_bytes_s *delta)
130{
131	pthread_mutex_lock(&stats_mutex);
132	dest->bytes_read += delta->bytes_read;
133	dest->bytes_written += delta->bytes_written;
134	pthread_mutex_unlock(&stats_mutex);
135}
136
137static int work_next_file;
138static int work_num_files;
139
140void
141init_work(int next_file, int num_files)
142{
143	pthread_mutex_lock(&work_mutex);
144	work_next_file = next_file;
145	work_num_files = work_next_file + num_files;
146	pthread_mutex_unlock(&work_mutex);
147}
148
149/* Dole out the next file to work on to the thread */
150static struct thread_state_s *
151get_work()
152{
153	struct thread_state_s *work = NULL;
154
155	pthread_mutex_lock(&work_mutex);
156	if (work_next_file < work_num_files)
157		work = &thread_state[work_next_file++];
158	pthread_mutex_unlock(&work_mutex);
159	return work;
160}
161
162static void
163create_files(struct thread_state_s *state)
164{
165	int i;
166	struct ioshark_file_state file_state;
167	char path[MAX_IOSHARK_PATHLEN];
168	void *db_node;
169	struct rw_bytes_s rw_bytes;
170	char *filename;
171	int readonly;
172
173	memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
174	for (i = 0 ; i < state->num_files ; i++) {
175		if (fread(&file_state, sizeof(struct ioshark_file_state),
176			  1, state->fp) != 1) {
177			fprintf(stderr, "%s read error tracefile\n",
178				progname);
179			exit(EXIT_FAILURE);
180		}
181		/*
182		 * Check to see if the file is in a readonly partition,
183		 * in which case, we don't have to pre-create the file
184		 * we can just read the existing file.
185		 */
186		filename =
187			get_ro_filename(file_state.global_filename_ix);
188		if (quick_mode)
189			assert(filename != NULL);
190		if (quick_mode == 0 ||
191		    is_readonly_mount(filename, file_state.size) == 0) {
192			sprintf(path, "file.%d.%d",
193				(int)(state - thread_state),
194				file_state.fileno);
195			create_file(path, file_state.size,
196				    &rw_bytes);
197			filename = path;
198			readonly = 0;
199		} else {
200			readonly = 1;
201		}
202		db_node = files_db_add_byfileno(state->db_handle,
203						file_state.fileno,
204						readonly);
205		files_db_update_size(db_node, file_state.size);
206		files_db_update_filename(db_node, filename);
207	}
208	update_byte_counts(&aggr_create_rw_bytes, &rw_bytes);
209}
210
211static void
212do_one_io(void *db_node,
213	  struct ioshark_file_operation *file_op,
214	  u_int64_t *op_counts,
215	  struct rw_bytes_s *rw_bytes,
216	  char **bufp, int *buflen)
217{
218	assert(file_op->file_op < IOSHARK_MAX_FILE_OP);
219	op_counts[file_op->file_op]++;
220	switch (file_op->file_op) {
221	int ret;
222	char *p;
223	int fd;
224
225	case IOSHARK_LSEEK:
226	case IOSHARK_LLSEEK:
227		ret = lseek(files_db_get_fd(db_node),
228			    file_op->lseek_offset,
229			    file_op->lseek_action);
230		if (ret < 0) {
231			fprintf(stderr,
232				"%s: lseek(%s %lu %d) returned error %d\n",
233				progname, files_db_get_filename(db_node),
234				file_op->lseek_offset,
235				file_op->lseek_action, errno);
236			exit(EXIT_FAILURE);
237		}
238		break;
239	case IOSHARK_PREAD64:
240		p = get_buf(bufp, buflen, file_op->prw_len, 0);
241		ret = pread(files_db_get_fd(db_node), p,
242			    file_op->prw_len, file_op->prw_offset);
243		rw_bytes->bytes_read += file_op->prw_len;
244		if (ret < 0) {
245			fprintf(stderr,
246				"%s: pread(%s %zu %lu) error %d\n",
247				progname,
248				files_db_get_filename(db_node),
249				file_op->prw_len,
250				file_op->prw_offset, errno);
251			exit(EXIT_FAILURE);
252		}
253		break;
254	case IOSHARK_PWRITE64:
255		p = get_buf(bufp, buflen, file_op->prw_len, 1);
256		ret = pwrite(files_db_get_fd(db_node), p,
257			     file_op->prw_len, file_op->prw_offset);
258		rw_bytes->bytes_written += file_op->prw_len;
259		if (ret < 0) {
260			fprintf(stderr,
261				"%s: pwrite(%s %zu %lu) error %d\n",
262				progname,
263				files_db_get_filename(db_node),
264				file_op->prw_len,
265				file_op->prw_offset, errno);
266			exit(EXIT_FAILURE);
267		}
268		break;
269	case IOSHARK_READ:
270		p = get_buf(bufp, buflen, file_op->rw_len, 0);
271		ret = read(files_db_get_fd(db_node), p,
272			   file_op->rw_len);
273		rw_bytes->bytes_read += file_op->rw_len;
274		if (ret < 0) {
275			fprintf(stderr,
276				"%s: read(%s %zu) error %d\n",
277				progname,
278				files_db_get_filename(db_node),
279				file_op->rw_len,
280				errno);
281			exit(EXIT_FAILURE);
282		}
283		break;
284	case IOSHARK_WRITE:
285		p = get_buf(bufp, buflen, file_op->rw_len, 1);
286		ret = write(files_db_get_fd(db_node), p,
287			    file_op->rw_len);
288		rw_bytes->bytes_written += file_op->rw_len;
289		if (ret < 0) {
290			fprintf(stderr,
291				"%s: write(%s %zu) error %d\n",
292				progname,
293				files_db_get_filename(db_node),
294				file_op->rw_len,
295				errno);
296			exit(EXIT_FAILURE);
297		}
298		break;
299	case IOSHARK_MMAP:
300	case IOSHARK_MMAP2:
301		ioshark_handle_mmap(db_node, file_op,
302				    bufp, buflen, op_counts,
303				    rw_bytes);
304		break;
305	case IOSHARK_OPEN:
306		if (file_op->open_flags & O_CREAT) {
307			fd = open(files_db_get_filename(db_node),
308				  file_op->open_flags,
309				  file_op->open_mode);
310			if (fd < 0) {
311				/*
312				 * EEXIST error acceptable, others are fatal.
313				 * Although we failed to O_CREAT the file (O_EXCL)
314				 * We will force an open of the file before any
315				 * IO.
316				 */
317				if (errno == EEXIST) {
318					return;
319				} else {
320					fprintf(stderr,
321						"%s: O_CREAT open(%s %x %o) error %d\n",
322						progname,
323						files_db_get_filename(db_node),
324						file_op->open_flags,
325						file_op->open_mode, errno);
326					exit(EXIT_FAILURE);
327				}
328			}
329		} else {
330			fd = open(files_db_get_filename(db_node),
331				  file_op->open_flags);
332			if (fd < 0) {
333				if (file_op->open_flags & O_DIRECTORY) {
334					/* O_DIRECTORY open()s should fail */
335					return;
336				} else {
337					fprintf(stderr,
338						"%s: open(%s %x) error %d\n",
339						progname,
340						files_db_get_filename(db_node),
341						file_op->open_flags,
342						errno);
343					exit(EXIT_FAILURE);
344				}
345			}
346		}
347		files_db_close_fd(db_node);
348		files_db_update_fd(db_node, fd);
349		break;
350	case IOSHARK_FSYNC:
351	case IOSHARK_FDATASYNC:
352		if (file_op->file_op == IOSHARK_FSYNC) {
353			ret = fsync(files_db_get_fd(db_node));
354			if (ret < 0) {
355				fprintf(stderr,
356					"%s: fsync(%s) error %d\n",
357					progname,
358					files_db_get_filename(db_node),
359					errno);
360				exit(EXIT_FAILURE);
361			}
362		} else {
363			ret = fdatasync(files_db_get_fd(db_node));
364			if (ret < 0) {
365				fprintf(stderr,
366					"%s: fdatasync(%s) error %d\n",
367					progname,
368					files_db_get_filename(db_node),
369					errno);
370				exit(EXIT_FAILURE);
371			}
372		}
373		break;
374	case IOSHARK_CLOSE:
375		ret = close(files_db_get_fd(db_node));
376		if (ret < 0) {
377			fprintf(stderr,
378				"%s: close(%s) error %d\n",
379				progname,
380				files_db_get_filename(db_node), errno);
381			exit(EXIT_FAILURE);
382		}
383		files_db_update_fd(db_node, -1);
384		break;
385	default:
386		fprintf(stderr, "%s: unknown FILE_OP %d\n",
387			progname, file_op->file_op);
388		exit(EXIT_FAILURE);
389		break;
390	}
391}
392
393static void
394do_io(struct thread_state_s *state)
395{
396	void *db_node;
397	struct ioshark_header header;
398	struct ioshark_file_operation file_op;
399	int fd;
400	int i;
401	char *buf = NULL;
402	int buflen = 0;
403	struct timeval total_delay_time;
404	u_int64_t op_counts[IOSHARK_MAX_FILE_OP];
405	struct rw_bytes_s rw_bytes;
406
407	rewind(state->fp);
408	if (fread(&header, sizeof(struct ioshark_header), 1, state->fp) != 1) {
409		fprintf(stderr, "%s read error %s\n",
410			progname, state->filename);
411		exit(EXIT_FAILURE);
412	}
413	/*
414	 * First open and pre-create all the files. Indexed by fileno.
415	 */
416	timerclear(&total_delay_time);
417	memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
418	memset(op_counts, 0, sizeof(op_counts));
419	fseek(state->fp,
420	      sizeof(struct ioshark_header) +
421	      header.num_files * sizeof(struct ioshark_file_state),
422	      SEEK_SET);
423	/*
424	 * Loop over all the IOs, and launch each
425	 */
426	for (i = 0 ; i < header.num_io_operations ; i++) {
427		if (fread(&file_op, sizeof(struct ioshark_file_operation),
428			  1, state->fp) != 1) {
429			fprintf(stderr, "%s read error trace.outfile\n",
430				progname);
431			exit(EXIT_FAILURE);
432		}
433		if (do_delay) {
434			struct timeval start;
435
436			(void)gettimeofday(&start, (struct timezone *)NULL);
437			usleep(file_op.delta_us);
438			update_delta_time(&start, &total_delay_time);
439		}
440		db_node = files_db_lookup_byfileno(state->db_handle,
441						   file_op.fileno);
442		if (db_node == NULL) {
443			fprintf(stderr,
444				"%s Can't lookup fileno %d, fatal error\n",
445				progname, file_op.fileno);
446			exit(EXIT_FAILURE);
447		}
448		if (file_op.file_op != IOSHARK_OPEN &&
449		    files_db_get_fd(db_node) == -1) {
450			int openflags;
451
452			/*
453			 * This is a hack to workaround the fact that we did not
454			 * see an open() for this file until now. open() the
455			 * file O_RDWR, so that we can perform the IO.
456			 */
457			if (files_db_readonly(db_node))
458				openflags = O_RDONLY;
459			else
460				openflags = O_RDWR;
461			fd = open(files_db_get_filename(db_node),
462				  openflags);
463			if (fd < 0) {
464				fprintf(stderr, "%s: open(%s %x) error %d\n",
465					progname,
466					files_db_get_filename(db_node),
467					openflags,
468					errno);
469				exit(EXIT_FAILURE);
470			}
471			files_db_update_fd(db_node, fd);
472		}
473		do_one_io(db_node, &file_op,
474			  op_counts, &rw_bytes, &buf, &buflen);
475	}
476	files_db_fsync_discard_files(state->db_handle);
477	files_db_close_files(state->db_handle);
478	update_time(&aggregate_delay_time, &total_delay_time);
479	update_op_counts(op_counts);
480	update_byte_counts(&aggr_io_rw_bytes, &rw_bytes);
481}
482
483void *
484io_thread(void *unused __attribute__((unused)))
485{
486	struct thread_state_s *state;
487
488	srand(gettid());
489	while ((state = get_work()))
490		do_io(state);
491	pthread_exit(NULL);
492        return(NULL);
493}
494
495static void
496do_create(struct thread_state_s *state)
497{
498	struct ioshark_header header;
499
500	if (fread(&header, sizeof(struct ioshark_header), 1, state->fp) != 1) {
501		fprintf(stderr, "%s read error %s\n",
502			progname, state->filename);
503		exit(EXIT_FAILURE);
504	}
505	state->num_files = header.num_files;
506	state->db_handle = files_db_create_handle();
507	create_files(state);
508}
509
510void *
511create_files_thread(void *unused __attribute__((unused)))
512{
513	struct thread_state_s *state;
514
515	while ((state = get_work()))
516		do_create(state);
517	pthread_exit(NULL);
518	return(NULL);
519}
520
521int
522get_start_end(int *start_ix)
523{
524	int i, j, ret_numfiles;
525	u_int64_t free_fs_bytes;
526	char *infile;
527	FILE *fp;
528	struct ioshark_header header;
529	struct ioshark_file_state file_state;
530	struct statfs fsstat;
531	static int fssize_clamp_next_index = 0;
532	static int chunk = 0;
533
534	if (fssize_clamp_next_index == num_input_files)
535		return 0;
536	if (statfs("/data/local/tmp", &fsstat) < 0) {
537		fprintf(stderr, "%s: Can't statfs /data/local/tmp\n",
538			progname);
539		exit(EXIT_FAILURE);
540	}
541	free_fs_bytes = (fsstat.f_bavail * fsstat.f_bsize) * 9 /10;
542	for (i = fssize_clamp_next_index; i < num_input_files; i++) {
543		infile = thread_state[i].filename;
544		fp = fopen(infile, "r");
545		if (fp == NULL) {
546			fprintf(stderr, "%s: Can't open %s\n",
547				progname, infile);
548			exit(EXIT_FAILURE);
549		}
550		if (fread(&header, sizeof(struct ioshark_header),
551			  1, fp) != 1) {
552			fprintf(stderr, "%s read error %s\n",
553				progname, infile);
554			exit(EXIT_FAILURE);
555		}
556		for (j = 0 ; j < header.num_files ; j++) {
557			if (fread(&file_state, sizeof(struct ioshark_file_state),
558				  1, fp) != 1) {
559				fprintf(stderr, "%s read error tracefile\n",
560					progname);
561				exit(EXIT_FAILURE);
562			}
563			if (quick_mode == 0 ||
564			    !is_readonly_mount(
565				    get_ro_filename(file_state.global_filename_ix),
566				    file_state.size)) {
567				if (file_state.size > free_fs_bytes) {
568					fclose(fp);
569					goto out;
570				}
571				free_fs_bytes -= file_state.size;
572			}
573		}
574		fclose(fp);
575	}
576out:
577	if (verbose) {
578		if (chunk > 0 || i < num_input_files) {
579			printf("Breaking up input files, Chunk %d: %d to %d\n",
580			       chunk++, fssize_clamp_next_index, i - 1);
581		} else {
582			printf("Entire Dataset fits start = %d to %d, free_bytes = %ju\n",
583			       fssize_clamp_next_index,
584			       i - fssize_clamp_next_index,
585			       free_fs_bytes);
586		}
587	}
588	*start_ix = fssize_clamp_next_index;
589	ret_numfiles = i - fssize_clamp_next_index;
590	fssize_clamp_next_index = i;
591	return ret_numfiles;
592}
593
594int
595ioshark_pthread_create(pthread_t *tidp, void *(*start_routine)(void *))
596{
597	pthread_attr_t attr;
598
599	pthread_attr_init(&attr);
600	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
601	pthread_attr_setstacksize(&attr, (size_t)(1024*1024));
602	return pthread_create(tidp, &attr, start_routine, (void *)NULL);
603}
604
605void
606wait_for_threads(int num_threads)
607{
608	int i;
609
610	for (i = 0; i < num_threads; i++) {
611		pthread_join(tid[i], NULL);
612		tid[i] = 0;
613	}
614}
615
616#define IOSHARK_FD_LIM		8192
617
618static void
619sizeup_fd_limits(void)
620{
621	struct rlimit r;
622
623	getrlimit(RLIMIT_NOFILE, &r);
624	if (r.rlim_cur >= IOSHARK_FD_LIM)
625		/* cur limit already at what we want */
626		return;
627	/*
628	 * Size up both the Max and Cur to IOSHARK_FD_LIM.
629	 * If we are not running as root, this will fail,
630	 * catch that below and exit.
631	 */
632	if (r.rlim_max < IOSHARK_FD_LIM)
633		r.rlim_max = IOSHARK_FD_LIM;
634	r.rlim_cur = IOSHARK_FD_LIM;
635	if (setrlimit(RLIMIT_NOFILE, &r) < 0) {
636		fprintf(stderr, "%s: Can't setrlimit (RLIMIT_NOFILE, 8192)\n",
637			progname);
638		exit(EXIT_FAILURE);
639	}
640	getrlimit(RLIMIT_NOFILE, &r);
641	if (r.rlim_cur < IOSHARK_FD_LIM) {
642		fprintf(stderr, "%s: Can't setrlimit up to 8192\n",
643			progname);
644		fprintf(stderr, "%s: Running as root ?\n",
645			progname);
646		exit(EXIT_FAILURE);
647	}
648}
649
650int
651main(int argc, char **argv)
652{
653	int i;
654	FILE *fp;
655	struct stat st;
656	char *infile;
657	int num_threads = 0;
658	int num_iterations = 1;
659	int c;
660	int num_files, start_file;
661	struct thread_state_s *state;
662
663	progname = argv[0];
664        while ((c = getopt(argc, argv, "dn:st:qv")) != EOF) {
665                switch (c) {
666                case 'd':
667			do_delay = 1;
668			break;
669                case 'n':
670			num_iterations = atoi(optarg);
671			break;
672                case 's':
673			/* Non-verbose summary mode for nightly runs */
674			summary_mode = 1;
675			break;
676                case 't':
677			num_threads = atoi(optarg);
678			break;
679                case 'q':
680			/*
681			 * If quick mode is enabled, then we won't
682			 * pre-create files that we are doing IO on that
683			 * live in readonly partitions (/system, /vendor etc)
684			 */
685			quick_mode = 1;
686			break;
687                case 'v':
688			verbose = 1;
689			break;
690 	        default:
691			usage();
692		}
693	}
694
695	if ((verbose + summary_mode) == 2)
696		usage();
697
698	if (num_threads > MAX_THREADS)
699		usage();
700
701	if (optind == argc)
702                usage();
703
704	sizeup_fd_limits();
705
706	for (i = optind; i < argc; i++) {
707		infile = argv[i];
708		if (stat(infile, &st) < 0) {
709			fprintf(stderr, "%s: Can't stat %s\n",
710				progname, infile);
711			exit(EXIT_FAILURE);
712		}
713		if (st.st_size == 0) {
714			fprintf(stderr, "%s: Empty file %s\n",
715				progname, infile);
716			continue;
717		}
718		fp = fopen(infile, "r");
719		if (fp == NULL) {
720			fprintf(stderr, "%s: Can't open %s\n",
721				progname, infile);
722			continue;
723		}
724		thread_state[num_input_files].filename = infile;
725		thread_state[num_input_files].fp = fp;
726		num_input_files++;
727	}
728
729	if (num_input_files == 0) {
730		exit(EXIT_SUCCESS);
731	}
732	if (verbose) {
733		printf("Total Input Files = %d\n", num_input_files);
734		printf("Num Iterations = %d\n", num_iterations);
735	}
736	timerclear(&aggregate_file_create_time);
737	timerclear(&aggregate_file_remove_time);
738	timerclear(&aggregate_IO_time);
739
740	if (quick_mode)
741		init_filename_cache();
742
743	capture_util_state_before();
744
745	/*
746	 * We pre-create the files that we need once and then we
747	 * loop around N times doing IOs on the pre-created files.
748	 *
749	 * get_start_end() breaks up the total work here to make sure
750	 * that all the files we need to pre-create fit into the
751	 * available space in /data/local/tmp (hardcoded for now).
752	 *
753	 * If it won't fit, then we do several sweeps.
754	 */
755	while ((num_files = get_start_end(&start_file))) {
756		struct timeval time_for_pass;
757
758		/* Create files once */
759		if (!summary_mode)
760			printf("Doing Pre-creation of Files\n");
761		if (quick_mode && !summary_mode)
762			printf("Skipping Pre-creation of read-only Files\n");
763		if (num_threads == 0 || num_threads > num_files)
764			num_threads = num_files;
765		(void)system("echo 3 > /proc/sys/vm/drop_caches");
766		init_work(start_file, num_files);
767		(void)gettimeofday(&time_for_pass,
768				   (struct timezone *)NULL);
769		for (i = 0; i < num_threads; i++) {
770			if (ioshark_pthread_create(&(tid[i]),
771						   create_files_thread)) {
772				fprintf(stderr,
773					"%s: Can't create creator thread %d\n",
774					progname, i);
775				exit(EXIT_FAILURE);
776			}
777		}
778		wait_for_threads(num_threads);
779		update_delta_time(&time_for_pass, &aggregate_file_create_time);
780		/* Do the IOs N times */
781		for (i = 0 ; i < num_iterations ; i++) {
782			(void)system("echo 3 > /proc/sys/vm/drop_caches");
783			if (!summary_mode) {
784				if (num_iterations > 1)
785					printf("Starting Test. Iteration %d...\n",
786					       i);
787				else
788					printf("Starting Test...\n");
789			}
790			init_work(start_file, num_files);
791			(void)gettimeofday(&time_for_pass,
792					   (struct timezone *)NULL);
793			for (c = 0; c < num_threads; c++) {
794				if (ioshark_pthread_create(&(tid[c]),
795							   io_thread)) {
796					fprintf(stderr,
797						"%s: Can't create thread %d\n",
798						progname, c);
799					exit(EXIT_FAILURE);
800				}
801			}
802			wait_for_threads(num_threads);
803			update_delta_time(&time_for_pass,
804					  &aggregate_IO_time);
805		}
806
807		/*
808		 * We are done with the N iterations of IO.
809		 * Destroy the files we pre-created.
810		 */
811		init_work(start_file, num_files);
812		while ((state = get_work())) {
813			struct timeval start;
814
815			(void)gettimeofday(&start, (struct timezone *)NULL);
816			files_db_unlink_files(state->db_handle);
817			update_delta_time(&start, &aggregate_file_remove_time);
818			files_db_free_memory(state->db_handle);
819		}
820	}
821	if (!summary_mode) {
822		printf("Total Creation time = %ju.%ju (msecs.usecs)\n",
823		       get_msecs(&aggregate_file_create_time),
824		       get_usecs(&aggregate_file_create_time));
825		printf("Total Remove time = %ju.%ju (msecs.usecs)\n",
826		       get_msecs(&aggregate_file_remove_time),
827		       get_usecs(&aggregate_file_remove_time));
828		if (do_delay)
829			printf("Total delay time = %ju.%ju (msecs.usecs)\n",
830			       get_msecs(&aggregate_delay_time),
831			       get_usecs(&aggregate_delay_time));
832		printf("Total Test (IO) time = %ju.%ju (msecs.usecs)\n",
833		       get_msecs(&aggregate_IO_time),
834		       get_usecs(&aggregate_IO_time));
835		if (verbose)
836			print_bytes("Upfront File Creation bytes",
837				    &aggr_create_rw_bytes);
838		print_bytes("Total Test (IO) bytes", &aggr_io_rw_bytes);
839		if (verbose)
840			print_op_stats(aggr_op_counts);
841		report_cpu_disk_util();
842	} else {
843		printf("%ju.%ju ",
844		       get_msecs(&aggregate_file_create_time),
845		       get_usecs(&aggregate_file_create_time));
846		printf("%ju.%ju ",
847		       get_msecs(&aggregate_file_remove_time),
848		       get_usecs(&aggregate_file_remove_time));
849		if (do_delay)
850			printf("%ju.%ju ",
851			       get_msecs(&aggregate_delay_time),
852			       get_usecs(&aggregate_delay_time));
853		printf("%ju.%ju ",
854		       get_msecs(&aggregate_IO_time),
855		       get_usecs(&aggregate_IO_time));
856		print_bytes(NULL, &aggr_io_rw_bytes);
857		report_cpu_disk_util();
858		printf("\n");
859	}
860	if (quick_mode)
861		free_filename_cache();
862}
863