Add version 2 of the iolog format

The old format had some short comings - notably not supporting > 1
file workloads. So add a new format that defines adding/open/close
of files. Fio will still load the older format iologs, but the new
ones wont work on older fio versions.

Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
diff --git a/filesetup.c b/filesetup.c
index 93cddb1..7a9a2af 100644
--- a/filesetup.c
+++ b/filesetup.c
@@ -513,7 +513,7 @@
 	}
 }
 
-void add_file(struct thread_data *td, const char *fname)
+int add_file(struct thread_data *td, const char *fname)
 {
 	int cur_files = td->files_index;
 	char file_name[PATH_MAX];
@@ -543,6 +543,8 @@
 	td->files_index++;
 	if (f->filetype == FIO_TYPE_FILE)
 		td->nr_normal_files++;
+
+	return cur_files;
 }
 
 void get_file(struct fio_file *f)
@@ -645,3 +647,28 @@
 			f->file_name = strdup(f->file_name);
 	}
 }
+
+/*
+ * Returns the index that matches the filename, or -1 if not there
+ */
+int get_fileno(struct thread_data *td, const char *fname)
+{
+	struct fio_file *f;
+	unsigned int i;
+
+	for_each_file(td, f, i)
+		if (!strcmp(f->file_name, fname))
+			return i;
+
+	return -1;
+}
+
+/*
+ * For log usage, where we add/open/close files automatically
+ */
+void free_release_files(struct thread_data *td)
+{
+	close_files(td);
+	td->files_index = 0;
+	td->nr_normal_files = 0;
+}
diff --git a/fio.c b/fio.c
index 6e0dae2..16e19c8 100644
--- a/fio.c
+++ b/fio.c
@@ -921,8 +921,6 @@
 		finish_log(td, td->ts.slat_log, "slat");
 	if (td->ts.clat_log)
 		finish_log(td, td->ts.clat_log, "clat");
-	if (td->o.write_iolog_file)
-		write_iolog_close(td);
 	if (td->o.exec_postrun) {
 		if (system(td->o.exec_postrun) < 0)
 			log_err("fio: postrun %s failed\n", td->o.exec_postrun);
@@ -937,6 +935,13 @@
 	close_files(td);
 	close_ioengine(td);
 	cleanup_io_u(td);
+
+	/*
+	 * do this very late, it will log file closing as well
+	 */
+	if (td->o.write_iolog_file)
+		write_iolog_close(td);
+
 	options_mem_free(td);
 	td_set_runstate(td, TD_EXITED);
 	return (void *) (unsigned long) td->error;
diff --git a/fio.h b/fio.h
index f2e69a7..b7d8582 100644
--- a/fio.h
+++ b/fio.h
@@ -35,6 +35,7 @@
 	DDIR_READ = 0,
 	DDIR_WRITE,
 	DDIR_SYNC,
+	DDIR_INVAL = -1,
 };
 
 enum td_ddir {
@@ -85,11 +86,17 @@
 		struct rb_node rb_node;
 		struct list_head list;
 	};
-	struct fio_file *file;
+	union {
+		int fileno;
+		struct fio_file *file;
+	};
 	unsigned long long offset;
 	unsigned long len;
 	enum fio_ddir ddir;
-	unsigned long delay;
+	union {
+		unsigned long delay;
+		unsigned int file_action;
+	};
 };
 
 #ifdef FIO_HAVE_SYSLET
@@ -674,8 +681,15 @@
 /*
  * Log exports
  */
+enum file_log_act {
+	FIO_LOG_ADD_FILE,
+	FIO_LOG_OPEN_FILE,
+	FIO_LOG_CLOSE_FILE,
+};
+
 extern int __must_check read_iolog_get(struct thread_data *, struct io_u *);
-extern void write_iolog_put(struct thread_data *, struct io_u *);
+extern void log_io_u(struct thread_data *, struct io_u *);
+extern void log_file(struct thread_data *, struct fio_file *, enum file_log_act);
 extern int __must_check init_iolog(struct thread_data *td);
 extern void log_io_piece(struct thread_data *, struct io_u *);
 extern void prune_io_piece_log(struct thread_data *);
@@ -738,12 +752,14 @@
 extern int __must_check file_invalidate_cache(struct thread_data *, struct fio_file *);
 extern int __must_check generic_open_file(struct thread_data *, struct fio_file *);
 extern void generic_close_file(struct thread_data *, struct fio_file *);
-extern void add_file(struct thread_data *, const char *);
+extern int add_file(struct thread_data *, const char *);
 extern void get_file(struct fio_file *);
 extern void put_file(struct thread_data *, struct fio_file *);
 extern int add_dir_files(struct thread_data *, const char *);
 extern int init_random_map(struct thread_data *);
 extern void dup_files(struct thread_data *, struct thread_data *);
+extern int get_fileno(struct thread_data *, const char *);
+extern void free_release_files(struct thread_data *);
 
 /*
  * ETA/status stuff
diff --git a/io_u.c b/io_u.c
index b636f57..309969a 100644
--- a/io_u.c
+++ b/io_u.c
@@ -357,9 +357,7 @@
 	 * If using a write iolog, store this entry.
 	 */
 out:
-	if (td->o.write_iolog_file)
-		write_iolog_put(td, io_u);
-
+	log_io_u(td, io_u);
 	return 0;
 }
 
diff --git a/ioengines.c b/ioengines.c
index feb986a..76e71b9 100644
--- a/ioengines.c
+++ b/ioengines.c
@@ -309,6 +309,7 @@
 		memset(f->file_map, 0, f->num_maps * sizeof(long));
 
 done:
+	log_file(td, f, FIO_LOG_OPEN_FILE);
 	td->nr_open_files++;
 	get_file(f);
 	return 0;
@@ -320,6 +321,9 @@
 
 void td_io_close_file(struct thread_data *td, struct fio_file *f)
 {
+	if (!(f->flags & FIO_FILE_CLOSING))
+		log_file(td, f, FIO_LOG_CLOSE_FILE);
+
 	/*
 	 * mark as closing, do real close when last io on it has completed
 	 */
diff --git a/log.c b/log.c
index 5ed30eb..07ba950 100644
--- a/log.c
+++ b/log.c
@@ -1,11 +1,38 @@
+/*
+ * Code related to writing an iolog of what a thread is doing, and to
+ * later read that back and replay
+ */
 #include <stdio.h>
 #include <stdlib.h>
+#include <assert.h>
 #include "list.h"
 #include "fio.h"
 
-void write_iolog_put(struct thread_data *td, struct io_u *io_u)
+static const char iolog_ver2[] = "fio version 2 iolog";
+
+void log_io_u(struct thread_data *td, struct io_u *io_u)
 {
-	fprintf(td->iolog_f, "%u,%llu,%lu\n", io_u->ddir, io_u->offset, io_u->buflen);
+	const char *act[] = { "read", "write", "sync" };
+
+	assert(io_u->ddir < 3);
+
+	if (!td->o.write_iolog_file)
+		return;
+
+	fprintf(td->iolog_f, "%s %s %llu %lu\n", io_u->file->file_name, act[io_u->ddir], io_u->offset, io_u->buflen);
+}
+
+void log_file(struct thread_data *td, struct fio_file *f,
+	      enum file_log_act what)
+{
+	const char *act[] = { "add", "open", "close" };
+
+	assert(what < 3);
+
+	if (!td->o.write_iolog_file)
+		return;
+
+	fprintf(td->iolog_f, "%s %s\n", f->file_name, act[what]);
 }
 
 static void iolog_delay(struct thread_data *td, unsigned long delay)
@@ -30,6 +57,7 @@
 {
 	struct io_piece *ipo;
 
+restart:
 	if (!list_empty(&td->io_log_list)) {
 		ipo = list_entry(td->io_log_list.next, struct io_piece, list);
 		list_del(&ipo->list);
@@ -38,6 +66,23 @@
 		io_u->ddir = ipo->ddir;
 		io_u->file = ipo->file;
 
+		/*
+		 * invalid ddir, this is a file action
+		 */
+		if (io_u->ddir == DDIR_INVAL) {
+			struct fio_file *f = &td->files[ipo->fileno];
+
+			if (ipo->file_action == FIO_LOG_OPEN_FILE) {
+				assert(!td_io_open_file(td, f));
+				free(ipo);
+				goto restart;
+			} else if (ipo->file_action == FIO_LOG_CLOSE_FILE) {
+				td_io_close_file(td, f);
+				free(ipo);
+				goto restart;
+			}
+		}
+
 		if (ipo->delay)
 			iolog_delay(td, ipo->delay);
 
@@ -120,25 +165,127 @@
 	fflush(td->iolog_f);
 	fclose(td->iolog_f);
 	free(td->iolog_buf);
+	td->iolog_f = NULL;
+	td->iolog_buf = NULL;
 }
 
 /*
- * Open a stored log and read in the entries.
+ * Read version 2 iolog data. It is enhanced to include per-file logging,
+ * syncs, etc.
  */
-static int init_iolog_read(struct thread_data *td)
+static int read_iolog2(struct thread_data *td, FILE *f)
+{
+	unsigned long long offset;
+	unsigned int bytes;
+	int rw, reads, writes, fileno = 0, file_action = 0; /* stupid gcc */
+	char *fname, *act;
+	char *str, *p;
+
+	free_release_files(td);
+
+	/*
+	 * Read in the read iolog and store it, reuse the infrastructure
+	 * for doing verifications.
+	 */
+	str = malloc(4096);
+	fname = malloc(256+16);
+	act = malloc(256+16);
+
+	reads = writes = 0;
+	while ((p = fgets(str, 4096, f)) != NULL) {
+		struct io_piece *ipo;
+		int r;
+
+		r = sscanf(p, "%256s %256s %llu %u", fname, act, &offset, &bytes);
+		if (r == 4) {
+			/*
+			 * Check action first
+			 */
+			if (!strcmp(act, "read"))
+				rw = DDIR_READ;
+			else if (!strcmp(act, "write"))
+				rw = DDIR_WRITE;
+			else if (!strcmp(act, "sync"))
+				rw = DDIR_SYNC;
+			else {
+				log_err("fio: bad iolog file action: %s\n",act);
+				continue;
+			}
+		} else if (r == 2) {
+			rw = DDIR_INVAL;
+			if (!strcmp(act, "add")) {
+				td->o.nr_files++;
+				fileno = add_file(td, fname);
+				file_action = FIO_LOG_ADD_FILE;
+				continue;
+			} else if (!strcmp(act, "open")) {
+				fileno = get_fileno(td, fname);
+				file_action = FIO_LOG_OPEN_FILE;
+			} else if (!strcmp(act, "close")) {
+				fileno = get_fileno(td, fname);
+				file_action = FIO_LOG_CLOSE_FILE;
+			} else {
+				log_err("fio: bad iolog file action: %s\n",act);
+				continue;
+			}
+		} else {
+			log_err("bad iolog2: %s", p);
+			continue;
+		}
+			
+		if (rw == DDIR_READ)
+			reads++;
+		else if (rw == DDIR_WRITE)
+			writes++;
+		else if (rw != DDIR_SYNC && rw != DDIR_INVAL) {
+			log_err("bad ddir: %d\n", rw);
+			continue;
+		}
+
+		/*
+		 * Make note of file
+		 */
+		ipo = malloc(sizeof(*ipo));
+		memset(ipo, 0, sizeof(*ipo));
+		INIT_LIST_HEAD(&ipo->list);
+		ipo->offset = offset;
+		ipo->len = bytes;
+		ipo->ddir = (enum fio_ddir) rw;
+		if (bytes > td->o.max_bs[rw])
+			td->o.max_bs[rw] = bytes;
+		if (rw == DDIR_INVAL) {
+			ipo->fileno = fileno;
+			ipo->file_action = file_action;
+		}
+		list_add_tail(&ipo->list, &td->io_log_list);
+	}
+
+	free(str);
+	free(act);
+	free(fname);
+
+	if (!reads && !writes)
+		return 1;
+	else if (reads && !writes)
+		td->o.td_ddir = TD_DDIR_READ;
+	else if (!reads && writes)
+		td->o.td_ddir = TD_DDIR_WRITE;
+	else
+		td->o.td_ddir = TD_DDIR_RW;
+
+	return 0;
+}
+
+/*
+ * Read version 1 iolog data.
+ */
+static int read_iolog(struct thread_data *td, FILE *f)
 {
 	unsigned long long offset;
 	unsigned int bytes;
 	char *str, *p;
-	FILE *f;
 	int rw, reads, writes;
 
-	f = fopen(td->o.read_iolog_file, "r");
-	if (!f) {
-		perror("fopen read iolog");
-		return 1;
-	}
-
 	/*
 	 * Read in the read iolog and store it, reuse the infrastructure
 	 * for doing verifications.
@@ -173,7 +320,6 @@
 	}
 
 	free(str);
-	fclose(f);
 
 	if (!reads && !writes)
 		return 1;
@@ -188,16 +334,58 @@
 }
 
 /*
+ * open iolog, check version, and call appropriate parser
+ */
+static int init_iolog_read(struct thread_data *td)
+{
+	char buffer[256], *p;
+	FILE *f;
+	int ret;
+
+	f = fopen(td->o.read_iolog_file, "r");
+	if (!f) {
+		perror("fopen read iolog");
+		return 1;
+	}
+
+	p = fgets(buffer, sizeof(buffer), f);
+	if (!p) {
+		td_verror(td, errno, "iolog read");
+		log_err("fio: unable to read iolog\n");
+		return 1;
+	}
+
+	/*
+	 * version 2 of the iolog stores a specific string as the
+	 * first line, check for that
+	 */
+	if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2)))
+		ret = read_iolog2(td, f);
+	else {
+		/*
+		 * seek back to the beginning
+		 */
+		if (fseek(f, 0, SEEK_SET) < 0) {
+			td_verror(td, errno, "iolog read");
+			log_err("fio: unable to read iolog\n");
+			return 1;
+		}
+
+		ret = read_iolog(td, f);
+	}
+
+	fclose(f);
+	return ret;
+}
+
+/*
  * Setup a log for storing io patterns.
  */
 static int init_iolog_write(struct thread_data *td)
 {
+	struct fio_file *ff;
 	FILE *f;
-
-	if (td->o.nr_files > 1) {
-		log_err("fio: write_iolog only works with 1 file currently\n");
-		return 1;
-	}
+	unsigned int i;
 
 	f = fopen(td->o.write_iolog_file, "w+");
 	if (!f) {
@@ -211,6 +399,21 @@
 	td->iolog_f = f;
 	td->iolog_buf = malloc(8192);
 	setvbuf(f, td->iolog_buf, _IOFBF, 8192);
+
+	/*
+	 * write our version line
+	 */
+	if (fprintf(f, "%s\n", iolog_ver2) < 0) {
+		perror("iolog init\n");
+		return 1;
+	}
+
+	/*
+	 * add all known files
+	 */
+	for_each_file(td, ff, i)
+		log_file(td, ff, FIO_LOG_ADD_FILE);
+
 	return 0;
 }