Recent changes (gfio)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The following changes since commit 90b7a96d9573a14f20470af2aa0dd4e60611a477:

  Merge branch 'master' into gfio (2013-02-04 12:51:09 +0100)

are available in the git repository at:

  git://git.kernel.dk/fio.git gfio

Bruce Cran (6):
      Update the Windows installer
      Fix copy/paste error in windowsaio register/unregister function names.
      windowsaio: fix file header format and improve error reporting.
      Add --build-32bit-win switch to configure --help output.
      Windows: fix sysconf(_SC_PHYS_PAGES).
      Windows: fix mlock, remove ftruncate and fix error handling.

Huadong Liu (1):
      Enable forced 32-bit build on Windows

Jens Axboe (10):
      Makefile: fixup init.c dependency
      Cleanup the percentile output formatting
      Handle normal output wrapping of the percentile list
      Get rid of fallocate on Windows
      Fix crash and precision of ETA with zones
      stat: fix wrong type used for 32-bit compiles
      Fix zones for numjobs=x, where x > 1
      Makfile: use LINK for the link phase
      t/log: include minmax.h for min()
      Merge branch 'master' into gfio

Vincent Kang Fu (1):
      Handle percentile lists with higher precision that 2 digits

 Makefile               |   17 ++--
 README                 |    3 +
 cconv.c                |    2 +
 configure              |   20 +++-
 engines/windowsaio.c   |  153 ++++++++-----------------
 eta.c                  |   12 ++-
 init.c                 |    3 +-
 options.c              |    1 +
 os/os-windows.h        |   12 +-
 os/windows/eula.rtf    |  Bin 1055 -> 1060 bytes
 os/windows/install.wxs |   12 +--
 os/windows/posix.c     |  293 +++++++++++++++++++++++++++++++-----------------
 os/windows/posix.h     |    1 +
 parse.c                |   32 +++++
 stat.c                 |   28 +++--
 stat.h                 |    1 +
 t/log.c                |    1 +
 thread_options.h       |    2 +
 18 files changed, 341 insertions(+), 252 deletions(-)

---

Diff of recent changes:

diff --git a/Makefile b/Makefile
index a14d1f3..aff4fba 100644
--- a/Makefile
+++ b/Makefile
@@ -164,6 +164,7 @@ T_PROGS += $(T_AXMAP_PROGS)
 ifneq ($(findstring $(MAKEFLAGS),s),s)
 ifndef V
 	QUIET_CC	= @echo '   ' CC $@;
+	QUIET_LINK	= @echo '   ' LINK $@;
 	QUIET_DEP	= @echo '   ' DEP $@;
 endif
 endif
@@ -189,7 +190,7 @@ FIO-VERSION-FILE: FORCE
 
 override CFLAGS += -DFIO_VERSION='"$(FIO_VERSION)"'
 
-.c.o: FORCE
+.c.o: FORCE FIO-VERSION-FILE
 	$(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) -c $<
 	@$(CC) -MM $(CFLAGS) $(CPPFLAGS) $*.c > $*.d
 	@mv -f $*.d $*.d.tmp
@@ -198,7 +199,7 @@ override CFLAGS += -DFIO_VERSION='"$(FIO_VERSION)"'
 		sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
 	@rm -f $*.d.tmp
 
-init.o: FIO-VERSION-FILE
+init.o: FIO-VERSION-FILE init.c
 	$(QUIET_CC)$(CC) -o init.o $(CFLAGS) $(CPPFLAGS) -c init.c
 
 gcompat.o: gcompat.c gcompat.h
@@ -229,22 +230,22 @@ printing.o: printing.c printing.h
 	$(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c printing.c
 
 t/stest: $(T_SMALLOC_OBJS)
-	$(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_SMALLOC_OBJS) $(LIBS) $(LDFLAGS)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_SMALLOC_OBJS) $(LIBS) $(LDFLAGS)
 
 t/ieee754: $(T_IEEE_OBJS)
-	$(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_IEEE_OBJS) $(LIBS) $(LDFLAGS)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_IEEE_OBJS) $(LIBS) $(LDFLAGS)
 
 fio: $(FIO_OBJS)
-	$(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(FIO_OBJS) $(LIBS) $(LDFLAGS)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(FIO_OBJS) $(LIBS) $(LDFLAGS)
 
 gfio: $(GFIO_OBJS)
-	$(QUIET_CC)$(CC) $(LIBS) -o gfio $(GFIO_OBJS) $(LIBS) $(GTK_LDFLAGS)
+	$(QUIET_LINK)$(CC) $(LIBS) -o gfio $(GFIO_OBJS) $(LIBS) $(GTK_LDFLAGS)
 
 t/genzipf: $(T_ZIPF_OBJS)
-	$(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_ZIPF_OBJS) $(LIBS) $(LDFLAGS)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_ZIPF_OBJS) $(LIBS) $(LDFLAGS)
 
 t/axmap: $(T_AXMAP_OBJS)
-	$(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_AXMAP_OBJS) $(LIBS) $(LDFLAGS)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_AXMAP_OBJS) $(LIBS) $(LDFLAGS)
 
 clean: FORCE
 	-rm -f .depend $(GFIO_OBJS) $(OBJS) $(T_OBJS) $(PROGS) $(T_PROGS) core.* core gfio FIO-VERSION-FILE config-host.mak cscope.out *.d
diff --git a/README b/README
index 4c7b542..4173237 100644
--- a/README
+++ b/README
@@ -125,6 +125,9 @@ How to compile FIO on 64-bit Windows:
  5. Run 'make clean'.
  6. Run 'make'.
 
+To build fio on 32-bit Windows, download x86/pthreadGC2.dll instead and do
+'./configure --build-32bit-win=yes' before 'make'.
+
 
 Command line
 ------------
diff --git a/cconv.c b/cconv.c
index e2548a8..5d575d3 100644
--- a/cconv.c
+++ b/cconv.c
@@ -182,6 +182,7 @@ void convert_thread_options_to_cpu(struct thread_options *o,
 	o->trim_batch = le32_to_cpu(top->trim_batch);
 	o->trim_zero = le32_to_cpu(top->trim_zero);
 	o->clat_percentiles = le32_to_cpu(top->clat_percentiles);
+	o->percentile_precision = le32_to_cpu(top->percentile_precision);
 	o->continue_on_error = le32_to_cpu(top->continue_on_error);
 	o->cgroup_weight = le32_to_cpu(top->cgroup_weight);
 	o->cgroup_nodelete = le32_to_cpu(top->cgroup_nodelete);
@@ -327,6 +328,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top,
 	top->trim_batch = cpu_to_le32(o->trim_batch);
 	top->trim_zero = cpu_to_le32(o->trim_zero);
 	top->clat_percentiles = cpu_to_le32(o->clat_percentiles);
+	top->percentile_precision = cpu_to_le32(o->percentile_precision);
 	top->continue_on_error = cpu_to_le32(o->continue_on_error);
 	top->cgroup_weight = cpu_to_le32(o->cgroup_weight);
 	top->cgroup_nodelete = cpu_to_le32(o->cgroup_nodelete);
diff --git a/configure b/configure
index cab3da8..1c8b2bb 100755
--- a/configure
+++ b/configure
@@ -137,9 +137,11 @@ for opt do
   --extra-cflags=*)
   CFLAGS="$CFLAGS $optarg"
   ;;
+  --build-32bit-win=*) build_32bit_win="$optarg"
+  ;;
   --enable-gfio)
-    gfio="yes"
-    ;;
+  gfio="yes"
+  ;;
   --help)
     show_help="yes"
     ;;
@@ -153,6 +155,7 @@ done
 if test "$show_help" = "yes" ; then
   echo "--cc=                  Specify compiler to use"
   echo "--extra-cflags=        Specify extra CFLAGS to pass to compiler"
+  echo "--build-32bit-win=     Specify yes for a 32-bit build on Windows"
   echo "--enable-gfio          Enable building of gtk gfio"
   exit $exit_val
 fi
@@ -191,13 +194,20 @@ SunOS)
 CYGWIN*)
   echo "Forcing known good options on Windows"
   if test -z "$CC" ; then
-    CC="x86_64-w64-mingw32-gcc"
+    if test ! -z "$build_32bit_win" && test "$build_32bit_win" = "yes"; then
+      CC="i686-w64-mingw32-gcc"
+    else
+      CC="x86_64-w64-mingw32-gcc"
+    fi
   fi
   output_sym "CONFIG_LITTLE_ENDIAN"
-  output_sym "CONFIG_64BIT_LLP64"
+  if test ! -z "$build_32bit_win" && test "$build_32bit_win" = "yes"; then
+    output_sym "CONFIG_32BIT"
+  else
+    output_sym "CONFIG_64BIT_LLP64"
+  fi
   output_sym "CONFIG_FADVISE"
   output_sym "CONFIG_SOCKLEN_T"
-  output_sym "CONFIG_POSIX_FALLOCATE"
   output_sym "CONFIG_FADVISE"
   output_sym "CONFIG_SFAA"
   output_sym "CONFIG_RUSAGE_THREAD"
diff --git a/engines/windowsaio.c b/engines/windowsaio.c
index 773f027..3a24fa7 100644
--- a/engines/windowsaio.c
+++ b/engines/windowsaio.c
@@ -1,6 +1,7 @@
 /*
- * Native Windows async IO engine
- * Copyright (C) 2012 Bruce Cran <bruce@xxxxxxxxxxx>
+ * windowsaio engine
+ *
+ * IO engine using Windows IO Completion Ports.
  */
 
 #include <stdio.h>
@@ -37,94 +38,18 @@ struct thread_ctx {
 };
 
 static int fio_windowsaio_cancel(struct thread_data *td,
-			       struct io_u *io_u);
+				   struct io_u *io_u);
 static BOOL timeout_expired(DWORD start_count, DWORD end_count);
 static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min,
-				    unsigned int max, struct timespec *t);
+					unsigned int max, struct timespec *t);
 static struct io_u *fio_windowsaio_event(struct thread_data *td, int event);
 static int fio_windowsaio_queue(struct thread_data *td,
-			      struct io_u *io_u);
+				  struct io_u *io_u);
 static void fio_windowsaio_cleanup(struct thread_data *td);
 static DWORD WINAPI IoCompletionRoutine(LPVOID lpParameter);
 static int fio_windowsaio_init(struct thread_data *td);
 static int fio_windowsaio_open_file(struct thread_data *td, struct fio_file *f);
 static int fio_windowsaio_close_file(struct thread_data fio_unused *td, struct fio_file *f);
-static int win_to_posix_error(DWORD winerr);
-
-static int win_to_posix_error(DWORD winerr)
-{
-	switch (winerr)
-	{
-	case ERROR_FILE_NOT_FOUND:		return ENOENT;
-	case ERROR_PATH_NOT_FOUND:		return ENOENT;
-	case ERROR_ACCESS_DENIED:		return EACCES;
-	case ERROR_INVALID_HANDLE:		return EBADF;
-	case ERROR_NOT_ENOUGH_MEMORY:	return ENOMEM;
-	case ERROR_INVALID_DATA:		return EINVAL;
-	case ERROR_OUTOFMEMORY:			return ENOMEM;
-	case ERROR_INVALID_DRIVE:		return ENODEV;
-	case ERROR_NOT_SAME_DEVICE:		return EXDEV;
-	case ERROR_WRITE_PROTECT:		return EROFS;
-	case ERROR_BAD_UNIT:			return ENODEV;
-	case ERROR_SHARING_VIOLATION:	return EACCES;
-	case ERROR_LOCK_VIOLATION:		return EACCES;
-	case ERROR_SHARING_BUFFER_EXCEEDED:	return ENOLCK;
-	case ERROR_HANDLE_DISK_FULL:	return ENOSPC;
-	case ERROR_NOT_SUPPORTED:		return ENOSYS;
-	case ERROR_FILE_EXISTS:			return EEXIST;
-	case ERROR_CANNOT_MAKE:			return EPERM;
-	case ERROR_INVALID_PARAMETER:	return EINVAL;
-	case ERROR_NO_PROC_SLOTS:		return EAGAIN;
-	case ERROR_BROKEN_PIPE:			return EPIPE;
-	case ERROR_OPEN_FAILED:			return EIO;
-	case ERROR_NO_MORE_SEARCH_HANDLES:	return ENFILE;
-	case ERROR_CALL_NOT_IMPLEMENTED:	return ENOSYS;
-	case ERROR_INVALID_NAME:		return ENOENT;
-	case ERROR_WAIT_NO_CHILDREN:	return ECHILD;
-	case ERROR_CHILD_NOT_COMPLETE:	return EBUSY;
-	case ERROR_DIR_NOT_EMPTY:		return ENOTEMPTY;
-	case ERROR_SIGNAL_REFUSED:		return EIO;
-	case ERROR_BAD_PATHNAME:		return ENOENT;
-	case ERROR_SIGNAL_PENDING:		return EBUSY;
-	case ERROR_MAX_THRDS_REACHED:	return EAGAIN;
-	case ERROR_BUSY:				return EBUSY;
-	case ERROR_ALREADY_EXISTS:		return EEXIST;
-	case ERROR_NO_SIGNAL_SENT:		return EIO;
-	case ERROR_FILENAME_EXCED_RANGE:	return EINVAL;
-	case ERROR_META_EXPANSION_TOO_LONG:	return EINVAL;
-	case ERROR_INVALID_SIGNAL_NUMBER:	return EINVAL;
-	case ERROR_THREAD_1_INACTIVE:	return EINVAL;
-	case ERROR_BAD_PIPE:			return EINVAL;
-	case ERROR_PIPE_BUSY:			return EBUSY;
-	case ERROR_NO_DATA:				return EPIPE;
-	case ERROR_MORE_DATA:			return EAGAIN;
-	case ERROR_DIRECTORY:			return ENOTDIR;
-	case ERROR_PIPE_CONNECTED:		return EBUSY;
-	case ERROR_NO_TOKEN:			return EINVAL;
-	case ERROR_PROCESS_ABORTED:		return EFAULT;
-	case ERROR_BAD_DEVICE:			return ENODEV;
-	case ERROR_BAD_USERNAME:		return EINVAL;
-	case ERROR_OPEN_FILES:			return EAGAIN;
-	case ERROR_ACTIVE_CONNECTIONS:	return EAGAIN;
-	case ERROR_DEVICE_IN_USE:		return EAGAIN;
-	case ERROR_INVALID_AT_INTERRUPT_TIME:	return EINTR;
-	case ERROR_IO_DEVICE:			return EIO;
-	case ERROR_NOT_OWNER:			return EPERM;
-	case ERROR_END_OF_MEDIA:		return ENOSPC;
-	case ERROR_EOM_OVERFLOW:		return ENOSPC;
-	case ERROR_BEGINNING_OF_MEDIA:	return ESPIPE;
-	case ERROR_SETMARK_DETECTED:	return ESPIPE;
-	case ERROR_NO_DATA_DETECTED:	return ENOSPC;
-	case ERROR_POSSIBLE_DEADLOCK:	return EDEADLOCK;
-	case ERROR_CRC:					return EIO;
-	case ERROR_NEGATIVE_SEEK:		return EINVAL;
-	case ERROR_DISK_FULL:			return ENOSPC;
-	case ERROR_NOACCESS:			return EFAULT;
-	case ERROR_FILE_INVALID:		return ENXIO;
-	}
-
-	return winerr;
-}
 
 static int fio_windowsaio_init(struct thread_data *td)
 {
@@ -132,23 +57,27 @@ static int fio_windowsaio_init(struct thread_data *td)
 	HANDLE hKernel32Dll;
 	int rc = 0;
 
-	wd = malloc(sizeof(struct windowsaio_data));
-	if (wd != NULL)
-		ZeroMemory(wd, sizeof(struct windowsaio_data));
-	else
+	wd = calloc(1, sizeof(struct windowsaio_data));
+	if (wd == NULL) {
+		 log_err("windowsaio: failed to allocate memory for engine data\n");
 		rc = 1;
+	}
 
 	if (!rc) {
 		wd->aio_events = malloc(td->o.iodepth * sizeof(struct io_u*));
-		if (wd->aio_events == NULL)
+		if (wd->aio_events == NULL) {
+			log_err("windowsaio: failed to allocate memory for aio events list\n");
 			rc = 1;
+		}
 	}
 
 	if (!rc) {
 		/* Create an auto-reset event */
 		wd->iocomplete_event = CreateEvent(NULL, FALSE, FALSE, NULL);
-		if (wd->iocomplete_event == NULL)
+		if (wd->iocomplete_event == NULL) {
+			log_err("windowsaio: failed to create io complete event handle\n");
 			rc = 1;
+		}
 	}
 
 	if (rc) {
@@ -164,15 +93,16 @@ static int fio_windowsaio_init(struct thread_data *td)
 	wd->pCancelIoEx = (CANCELIOEX)GetProcAddress(hKernel32Dll, "CancelIoEx");
 	td->io_ops->data = wd;
 
-
 	if (!rc) {
 		struct thread_ctx *ctx;
 		struct windowsaio_data *wd;
 		HANDLE hFile;
 
 		hFile = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
-		if (hFile == INVALID_HANDLE_VALUE)
+		if (hFile == INVALID_HANDLE_VALUE) {
+			log_err("windowsaio: failed to create io completion port\n");
 			rc = 1;
+		}
 
 		wd = td->io_ops->data;
 		wd->iothread_running = TRUE;
@@ -183,7 +113,7 @@ static int fio_windowsaio_init(struct thread_data *td)
 
 		if (!rc && ctx == NULL)
 		{
-			log_err("fio: out of memory in windowsaio\n");
+			log_err("windowsaio: failed to allocate memory for thread context structure\n");
 			CloseHandle(hFile);
 			rc = 1;
 		}
@@ -193,6 +123,8 @@ static int fio_windowsaio_init(struct thread_data *td)
 			ctx->iocp = hFile;
 			ctx->wd = wd;
 			wd->iothread = CreateThread(NULL, 0, IoCompletionRoutine, ctx, 0, NULL);
+			if (wd->iothread == NULL)
+				log_err("windowsaio: failed to create io completion thread\n");
 		}
 
 		if (rc || wd->iothread == NULL)
@@ -234,12 +166,12 @@ static int fio_windowsaio_open_file(struct thread_data *td, struct fio_file *f)
 	dprint(FD_FILE, "fd open %s\n", f->file_name);
 
 	if (f->filetype == FIO_TYPE_PIPE) {
-		log_err("fio: windowsaio doesn't support pipes\n");
+		log_err("windowsaio: pipes are not supported\n");
 		return 1;
 	}
 
 	if (!strcmp(f->file_name, "-")) {
-		log_err("fio: can't read/write to stdin/out\n");
+		log_err("windowsaio: can't read/write to stdin/out\n");
 		return 1;
 	}
 
@@ -271,8 +203,10 @@ static int fio_windowsaio_open_file(struct thread_data *td, struct fio_file *f)
 	f->hFile = CreateFile(f->file_name, access, sharemode,
 		NULL, openmode, flags, NULL);
 
-	if (f->hFile == INVALID_HANDLE_VALUE)
+	if (f->hFile == INVALID_HANDLE_VALUE) {
+		log_err("windowsaio: failed to open file \"%s\"\n", f->file_name);
 		rc = 1;
+	}
 
 	/* Only set up the completion port and thread if we're not just
 	 * querying the device size */
@@ -281,8 +215,10 @@ static int fio_windowsaio_open_file(struct thread_data *td, struct fio_file *f)
 
 		wd = td->io_ops->data;
 
-		if (CreateIoCompletionPort(f->hFile, wd->iocp, 0, 0) == NULL)
+		if (CreateIoCompletionPort(f->hFile, wd->iocp, 0, 0) == NULL) {
+			log_err("windowsaio: failed to create io completion port\n");
 			rc = 1;
+		}
 	}
 
 	return rc;
@@ -295,8 +231,10 @@ static int fio_windowsaio_close_file(struct thread_data fio_unused *td, struct f
 	dprint(FD_FILE, "fd close %s\n", f->file_name);
 
 	if (f->hFile != INVALID_HANDLE_VALUE) {
-		if (!CloseHandle(f->hFile))
+		if (!CloseHandle(f->hFile)) {
+			log_info("windowsaio: failed to close file handle for \"%s\"\n", f->file_name);
 			rc = 1;
+		}
 	}
 
 	f->hFile = INVALID_HANDLE_VALUE;
@@ -325,7 +263,7 @@ static struct io_u* fio_windowsaio_event(struct thread_data *td, int event)
 }
 
 static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min,
-				    unsigned int max, struct timespec *t)
+					unsigned int max, struct timespec *t)
 {
 	struct windowsaio_data *wd = td->io_ops->data;
 	struct flist_head *entry;
@@ -362,7 +300,7 @@ static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min,
 		if (dequeued < min) {
 			status = WaitForSingleObject(wd->iocomplete_event, mswait);
 			if (status != WAIT_OBJECT_0 && dequeued >= min)
-			    break;
+				break;
 		}
 
 		if (dequeued >= min || (t != NULL && timeout_expired(start_count, end_count)))
@@ -398,13 +336,15 @@ static int fio_windowsaio_queue(struct thread_data *td, struct io_u *io_u)
 	case DDIR_DATASYNC:
 	case DDIR_SYNC_FILE_RANGE:
 		success = FlushFileBuffers(io_u->file->hFile);
-		if (!success)
-		    io_u->error = win_to_posix_error(GetLastError());
+		if (!success) {
+			log_err("windowsaio: failed to flush file buffers\n");
+			io_u->error = win_to_posix_error(GetLastError());
+		}
 
 		return FIO_Q_COMPLETED;
 		break;
 	case DDIR_TRIM:
-		log_err("manual TRIM isn't supported on Windows");
+		log_err("windowsaio: manual TRIM isn't supported on Windows\n");
 		io_u->error = 1;
 		io_u->resid = io_u->xfer_buflen;
 		return FIO_Q_COMPLETED;
@@ -463,7 +403,7 @@ static DWORD WINAPI IoCompletionRoutine(LPVOID lpParameter)
 }
 
 static int fio_windowsaio_cancel(struct thread_data *td,
-			       struct io_u *io_u)
+				   struct io_u *io_u)
 {
 	int rc = 0;
 
@@ -473,8 +413,10 @@ static int fio_windowsaio_cancel(struct thread_data *td,
 	if (wd->pCancelIoEx != NULL) {
 		struct fio_overlapped *ovl = io_u->engine_data;
 
-		if (!wd->pCancelIoEx(io_u->file->hFile, &ovl->o))
+		if (!wd->pCancelIoEx(io_u->file->hFile, &ovl->o)) {
+			log_err("windowsaio: failed to cancel io\n");
 			rc = 1;
+		}
 	} else
 		rc = 1;
 
@@ -500,7 +442,8 @@ static int fio_windowsaio_io_u_init(struct thread_data *td, struct io_u *io_u)
 	o->io_complete = FALSE;
 	o->io_u = io_u;
 	o->o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (!o->o.hEvent) {
+	if (o->o.hEvent == NULL) {
+		log_err("windowsaio: failed to create event handle\n");
 		free(o);
 		return 1;
 	}
@@ -525,12 +468,12 @@ static struct ioengine_ops ioengine = {
 	.io_u_free	= fio_windowsaio_io_u_free,
 };
 
-static void fio_init fio_posixaio_register(void)
+static void fio_init fio_windowsaio_register(void)
 {
 	register_ioengine(&ioengine);
 }
 
-static void fio_exit fio_posixaio_unregister(void)
+static void fio_exit fio_windowsaio_unregister(void)
 {
 	unregister_ioengine(&ioengine);
 }
diff --git a/eta.c b/eta.c
index 6f897a4..a724fe6 100644
--- a/eta.c
+++ b/eta.c
@@ -139,6 +139,15 @@ static int thread_eta(struct thread_data *td)
 		bytes_total = td->fill_device_size;
 	}
 
+	if (td->o.zone_size && td->o.zone_skip && bytes_total) {
+		unsigned int nr_zones;
+		uint64_t zone_bytes;
+
+		zone_bytes = bytes_total + td->o.zone_size + td->o.zone_skip;
+		nr_zones = (zone_bytes - 1) / (td->o.zone_size + td->o.zone_skip);
+		bytes_total -= nr_zones * td->o.zone_skip;
+	}
+
 	/*
 	 * if writing and verifying afterwards, bytes_total will be twice the
 	 * size. In a mixed workload, verify phase will be the size of the
@@ -156,9 +165,6 @@ static int thread_eta(struct thread_data *td)
 			bytes_total <<= 1;
 	}
 
-	if (td->o.zone_size && td->o.zone_skip)
-		bytes_total /= (td->o.zone_skip / td->o.zone_size);
-
 	if (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING) {
 		double perc, perc_t;
 
diff --git a/init.c b/init.c
index 25c8d78..ce699df 100644
--- a/init.c
+++ b/init.c
@@ -415,7 +415,7 @@ static int fixup_options(struct thread_data *td)
 	/*
 	 * only really works with 1 file
 	 */
-	if (o->zone_size && o->open_files == 1)
+	if (o->zone_size && o->open_files > 1)
 		o->zone_size = 0;
 
 	/*
@@ -869,6 +869,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num,
 	td->mutex = fio_mutex_init(FIO_MUTEX_LOCKED);
 
 	td->ts.clat_percentiles = td->o.clat_percentiles;
+	td->ts.percentile_precision = td->o.percentile_precision;
 	memcpy(td->ts.percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list));
 
 	for (i = 0; i < DDIR_RWDIR_CNT; i++) {
diff --git a/options.c b/options.c
index 63293e0..fcf4270 100644
--- a/options.c
+++ b/options.c
@@ -2851,6 +2851,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
 		.lname	= "Completion latency percentile list",
 		.type	= FIO_OPT_FLOAT_LIST,
 		.off1	= td_var_offset(percentile_list),
+		.off2	= td_var_offset(percentile_precision),
 		.help	= "Specify a custom list of percentiles to report",
 		.def    = "1:5:10:20:30:40:50:60:70:80:90:95:99:99.5:99.9:99.95:99.99",
 		.maxlen	= FIO_IO_U_LIST_MAX_LEN,
diff --git a/os/os-windows.h b/os/os-windows.h
index 98f9030..09f9c54 100644
--- a/os/os-windows.h
+++ b/os/os-windows.h
@@ -151,12 +151,14 @@ static inline int blockdev_invalidate_cache(struct fio_file *f)
 
 static inline unsigned long long os_phys_mem(void)
 {
-	SYSTEM_INFO sysInfo;
-	uintptr_t addr;
+	long pagesize, pages;
 
-	GetSystemInfo(&sysInfo);
-	addr = (uintptr_t)sysInfo.lpMaximumApplicationAddress;
-	return (unsigned long long)addr;
+	pagesize = sysconf(_SC_PAGESIZE);
+	pages = sysconf(_SC_PHYS_PAGES);
+	if (pages == -1 || pagesize == -1)
+		return 0;
+
+	return (unsigned long long) pages * (unsigned long long) pagesize;
 }
 
 static inline void os_get_tmpdir(char *path, int len)
diff --git a/os/windows/eula.rtf b/os/windows/eula.rtf
index be37fad..cc7be7f 100755
Binary files a/os/windows/eula.rtf and b/os/windows/eula.rtf differ
diff --git a/os/windows/install.wxs b/os/windows/install.wxs
index 5845d5f..b43120a 100755
--- a/os/windows/install.wxs
+++ b/os/windows/install.wxs
@@ -7,14 +7,13 @@
 		<?define ProgramDirectory = ProgramFiles64Folder ?>
 	<?endif?>
 
-	<Product Id="F9883688-6AB3-4BD1-AB93-91D39F12F003"
+	<Product Id="*"
 	  Codepage="1252" Language="1033"
 	  Manufacturer="fio" Name="fio"
-	  UpgradeCode="2338A332-5511-43cf-b9BD-5C60496CCFCC" Version="2.0.13">
+	  UpgradeCode="2338A332-5511-43CF-B9BD-5C60496CCFCC" Version="2.0.13">
 		<Package
-		  Comments="Contact: Your local administrator"
 		  Description="Flexible IO Tester"
-		  InstallerVersion="200" Keywords="Installer,MSI,Database"
+		  InstallerVersion="301" Keywords="Installer,MSI,Database"
 		  Languages="1033" Manufacturer="fio"
 		  InstallScope="perMachine" InstallPrivileges="elevated" Compressed="yes"/>
 
@@ -67,11 +66,6 @@
 
 	<UIRef Id="WixUI_Minimal"/>
 
-	<Condition Message="Per-user installations are not supported">
-		Installed OR
-		ALLUSERS=1
-	</Condition>
-
 	<MajorUpgrade AllowDowngrades="no" DowngradeErrorMessage="A newer version of the application is already installed."/>
 </Product>
 </Wix>
diff --git a/os/windows/posix.c b/os/windows/posix.c
index 05fa5a9..6a7841d 100755
--- a/os/windows/posix.c
+++ b/os/windows/posix.c
@@ -43,6 +43,81 @@ int vsprintf_s(
   const char *format,
   va_list argptr);
 
+int win_to_posix_error(DWORD winerr)
+{
+	switch (winerr)
+	{
+	case ERROR_FILE_NOT_FOUND:		return ENOENT;
+	case ERROR_PATH_NOT_FOUND:		return ENOENT;
+	case ERROR_ACCESS_DENIED:		return EACCES;
+	case ERROR_INVALID_HANDLE:		return EBADF;
+	case ERROR_NOT_ENOUGH_MEMORY:	return ENOMEM;
+	case ERROR_INVALID_DATA:		return EINVAL;
+	case ERROR_OUTOFMEMORY:			return ENOMEM;
+	case ERROR_INVALID_DRIVE:		return ENODEV;
+	case ERROR_NOT_SAME_DEVICE:		return EXDEV;
+	case ERROR_WRITE_PROTECT:		return EROFS;
+	case ERROR_BAD_UNIT:			return ENODEV;
+	case ERROR_SHARING_VIOLATION:	return EACCES;
+	case ERROR_LOCK_VIOLATION:		return EACCES;
+	case ERROR_SHARING_BUFFER_EXCEEDED:	return ENOLCK;
+	case ERROR_HANDLE_DISK_FULL:	return ENOSPC;
+	case ERROR_NOT_SUPPORTED:		return ENOSYS;
+	case ERROR_FILE_EXISTS:			return EEXIST;
+	case ERROR_CANNOT_MAKE:			return EPERM;
+	case ERROR_INVALID_PARAMETER:	return EINVAL;
+	case ERROR_NO_PROC_SLOTS:		return EAGAIN;
+	case ERROR_BROKEN_PIPE:			return EPIPE;
+	case ERROR_OPEN_FAILED:			return EIO;
+	case ERROR_NO_MORE_SEARCH_HANDLES:	return ENFILE;
+	case ERROR_CALL_NOT_IMPLEMENTED:	return ENOSYS;
+	case ERROR_INVALID_NAME:		return ENOENT;
+	case ERROR_WAIT_NO_CHILDREN:	return ECHILD;
+	case ERROR_CHILD_NOT_COMPLETE:	return EBUSY;
+	case ERROR_DIR_NOT_EMPTY:		return ENOTEMPTY;
+	case ERROR_SIGNAL_REFUSED:		return EIO;
+	case ERROR_BAD_PATHNAME:		return ENOENT;
+	case ERROR_SIGNAL_PENDING:		return EBUSY;
+	case ERROR_MAX_THRDS_REACHED:	return EAGAIN;
+	case ERROR_BUSY:				return EBUSY;
+	case ERROR_ALREADY_EXISTS:		return EEXIST;
+	case ERROR_NO_SIGNAL_SENT:		return EIO;
+	case ERROR_FILENAME_EXCED_RANGE:	return EINVAL;
+	case ERROR_META_EXPANSION_TOO_LONG:	return EINVAL;
+	case ERROR_INVALID_SIGNAL_NUMBER:	return EINVAL;
+	case ERROR_THREAD_1_INACTIVE:	return EINVAL;
+	case ERROR_BAD_PIPE:			return EINVAL;
+	case ERROR_PIPE_BUSY:			return EBUSY;
+	case ERROR_NO_DATA:				return EPIPE;
+	case ERROR_MORE_DATA:			return EAGAIN;
+	case ERROR_DIRECTORY:			return ENOTDIR;
+	case ERROR_PIPE_CONNECTED:		return EBUSY;
+	case ERROR_NO_TOKEN:			return EINVAL;
+	case ERROR_PROCESS_ABORTED:		return EFAULT;
+	case ERROR_BAD_DEVICE:			return ENODEV;
+	case ERROR_BAD_USERNAME:		return EINVAL;
+	case ERROR_OPEN_FILES:			return EAGAIN;
+	case ERROR_ACTIVE_CONNECTIONS:	return EAGAIN;
+	case ERROR_DEVICE_IN_USE:		return EAGAIN;
+	case ERROR_INVALID_AT_INTERRUPT_TIME:	return EINTR;
+	case ERROR_IO_DEVICE:			return EIO;
+	case ERROR_NOT_OWNER:			return EPERM;
+	case ERROR_END_OF_MEDIA:		return ENOSPC;
+	case ERROR_EOM_OVERFLOW:		return ENOSPC;
+	case ERROR_BEGINNING_OF_MEDIA:	return ESPIPE;
+	case ERROR_SETMARK_DETECTED:	return ESPIPE;
+	case ERROR_NO_DATA_DETECTED:	return ENOSPC;
+	case ERROR_POSSIBLE_DEADLOCK:	return EDEADLOCK;
+	case ERROR_CRC:					return EIO;
+	case ERROR_NEGATIVE_SEEK:		return EINVAL;
+	case ERROR_DISK_FULL:			return ENOSPC;
+	case ERROR_NOACCESS:			return EFAULT;
+	case ERROR_FILE_INVALID:		return ENXIO;
+	}
+
+	return winerr;
+}
+
 int GetNumLogicalProcessors(void)
 {
 	SYSTEM_LOGICAL_PROCESSOR_INFORMATION *processor_info = NULL;
@@ -79,6 +154,7 @@ int GetNumLogicalProcessors(void)
 long sysconf(int name)
 {
 	long val = -1;
+	long val2 = -1;
 	SYSTEM_INFO sysInfo;
 	MEMORYSTATUSEX status;
 
@@ -87,7 +163,7 @@ long sysconf(int name)
 	case _SC_NPROCESSORS_ONLN:
 		val = GetNumLogicalProcessors();
 		if (val == -1)
-			log_err("_SC_NPROCESSORS_ONLN failed\n");
+			log_err("sysconf(_SC_NPROCESSORS_ONLN) failed\n");
 
 		break;
 
@@ -98,8 +174,11 @@ long sysconf(int name)
 
 	case _SC_PHYS_PAGES:
 		status.dwLength = sizeof(status);
-		GlobalMemoryStatusEx(&status);
-		val = status.ullTotalPhys;
+		val2 = sysconf(_SC_PAGESIZE);
+		if (GlobalMemoryStatusEx(&status) && val2 != -1)
+			val = status.ullTotalPhys / val2;
+		else
+			log_err("sysconf(_SC_PHYS_PAGES) failed\n");
 		break;
 	default:
 		log_err("sysconf(%d) is not implemented\n", name);
@@ -210,6 +289,8 @@ void *mmap(void *addr, size_t len, int prot, int flags,
 	if ((flags & MAP_ANON) | (flags & MAP_ANONYMOUS))
 	{
 		allocAddr = VirtualAlloc(addr, len, MEM_COMMIT, vaProt);
+		if (allocAddr == NULL)
+			errno = win_to_posix_error(GetLastError());
 	}
 
 	return allocAddr;
@@ -217,21 +298,26 @@ void *mmap(void *addr, size_t len, int prot, int flags,
 
 int munmap(void *addr, size_t len)
 {
-	return !VirtualFree(addr, 0, MEM_RELEASE);
+	if (!VirtualFree(addr, 0, MEM_RELEASE)) {
+		errno = win_to_posix_error(GetLastError());
+		return -1;
+	}
+
+	return 0;
 }
 
 int fork(void)
 {
 	log_err("%s is not implemented\n", __func__);
 	errno = ENOSYS;
-	return (-1);
+	return -1;
 }
 
 pid_t setsid(void)
 {
 	log_err("%s is not implemented\n", __func__);
 	errno = ENOSYS;
-	return (-1);
+	return -1;
 }
 
 static HANDLE log_file = INVALID_HANDLE_VALUE;
@@ -276,7 +362,7 @@ void syslog(int priority, const char *message, ... /* argument */)
 int kill(pid_t pid, int sig)
 {
 	errno = ESRCH;
-	return (-1);
+	return -1;
 }
 
 /*
@@ -297,7 +383,7 @@ int fcntl(int fildes, int cmd, ...)
 		return 0;
 	else if (cmd != F_SETFL) {
 		errno = EINVAL;
-		return (-1);
+		return -1;
 	}
 
 	va_start(ap, 1);
@@ -369,12 +455,42 @@ int clock_gettime(clockid_t clock_id, struct timespec *tp)
 
 int mlock(const void * addr, size_t len)
 {
-	return !VirtualLock((LPVOID)addr, len);
+	SIZE_T min, max;
+	BOOL success;
+	HANDLE process = GetCurrentProcess();
+
+	success = GetProcessWorkingSetSize(process, &min, &max);
+	if (!success) {
+		errno = win_to_posix_error(GetLastError());
+		return -1;
+	}
+
+	min += len;
+	max += len;
+	success = SetProcessWorkingSetSize(process, min, max);
+	if (!success) {
+		errno = win_to_posix_error(GetLastError());
+		return -1;
+	}
+
+	success = VirtualLock((LPVOID)addr, len);
+	if (!success) {
+		errno = win_to_posix_error(GetLastError());
+		return -1;
+	}
+
+	return 0;
 }
 
 int munlock(const void * addr, size_t len)
 {
-	return !VirtualUnlock((LPVOID)addr, len);
+	BOOL success = VirtualUnlock((LPVOID)addr, len);
+	if (!success) {
+		errno = win_to_posix_error(GetLastError());
+		return -1;
+	}
+
+	return 0;
 }
 
 pid_t waitpid(pid_t pid, int *stat_loc, int options)
@@ -408,68 +524,15 @@ char *basename(char *path)
 	return name;
 }
 
-int posix_fallocate(int fd, off_t offset, off_t len)
-{
-	const int BUFFER_SIZE = 256 * 1024;
-	int rc = 0;
-	char *buf;
-	unsigned int write_len;
-	unsigned int bytes_written;
-	off_t bytes_remaining = len;
-
-	if (len == 0 || offset < 0)
-		return EINVAL;
-
-	buf = malloc(BUFFER_SIZE);
-
-	if (buf == NULL)
-		return ENOMEM;
-
-	memset(buf, 0, BUFFER_SIZE);
-
-	int64_t prev_pos = _telli64(fd);
-
-	if (_lseeki64(fd, offset, SEEK_SET) == -1)
-		return errno;
-
-	while (bytes_remaining > 0) {
-		if (bytes_remaining < BUFFER_SIZE)
-			write_len = (unsigned int)bytes_remaining;
-		else
-			write_len = BUFFER_SIZE;
-
-		bytes_written = _write(fd, buf, write_len);
-		if (bytes_written == -1) {
-			rc = errno;
-			break;
-		}
-
-		/* Don't allow Windows to cache the write: flush it to disk */
-		_commit(fd);
-
-		bytes_remaining -= bytes_written;
-	}
-
-	free(buf);
-	_lseeki64(fd, prev_pos, SEEK_SET);
-	return rc;
-}
-
-int ftruncate(int fildes, off_t length)
-{
-	BOOL bSuccess;
-	int64_t prev_pos = _telli64(fildes);
-	_lseeki64(fildes, length, SEEK_SET);
-	HANDLE hFile = (HANDLE)_get_osfhandle(fildes);
-	bSuccess = SetEndOfFile(hFile);
-	_lseeki64(fildes, prev_pos, SEEK_SET);
-	return !bSuccess;
-}
-
 int fsync(int fildes)
 {
 	HANDLE hFile = (HANDLE)_get_osfhandle(fildes);
-	return !FlushFileBuffers(hFile);
+	if (!FlushFileBuffers(hFile)) {
+		errno = win_to_posix_error(GetLastError());
+		return -1;
+	}
+
+	return 0;
 }
 
 int nFileMappings = 0;
@@ -497,14 +560,33 @@ void *shmat(int shmid, const void *shmaddr, int shmflg)
 	void* mapAddr;
 	MEMORY_BASIC_INFORMATION memInfo;
 	mapAddr = MapViewOfFile(fileMappings[shmid], FILE_MAP_ALL_ACCESS, 0, 0, 0);
-	VirtualQuery(mapAddr, &memInfo, sizeof(memInfo));
+	if (mapAddr == NULL) {
+		errno = win_to_posix_error(GetLastError());
+		return (void*)-1;
+	}
+
+	if (VirtualQuery(mapAddr, &memInfo, sizeof(memInfo)) == 0) {
+		errno = win_to_posix_error(GetLastError());
+		return (void*)-1;
+	}
+
 	mapAddr = VirtualAlloc(mapAddr, memInfo.RegionSize, MEM_COMMIT, PAGE_READWRITE);
+	if (mapAddr == NULL) {
+		errno = win_to_posix_error(GetLastError());
+		return (void*)-1;
+	}
+
 	return mapAddr;
 }
 
 int shmdt(const void *shmaddr)
 {
-	return !UnmapViewOfFile(shmaddr);
+	if (!UnmapViewOfFile(shmaddr)) {
+		errno = win_to_posix_error(GetLastError());
+		return -1;
+	}
+
+	return 0;
 }
 
 int shmctl(int shmid, int cmd, struct shmid_ds *buf)
@@ -515,21 +597,22 @@ int shmctl(int shmid, int cmd, struct shmid_ds *buf)
 	} else {
 		log_err("%s is not implemented\n", __func__);
 	}
-	return (-1);
+	errno = ENOSYS;
+	return -1;
 }
 
 int setuid(uid_t uid)
 {
 	log_err("%s is not implemented\n", __func__);
 	errno = ENOSYS;
-	return (-1);
+	return -1;
 }
 
 int setgid(gid_t gid)
 {
 	log_err("%s is not implemented\n", __func__);
 	errno = ENOSYS;
-	return (-1);
+	return -1;
 }
 
 int nice(int incr)
@@ -622,14 +705,14 @@ ssize_t readv(int fildes, const struct iovec *iov, int iovcnt)
 {
 	log_err("%s is not implemented\n", __func__);
 	errno = ENOSYS;
-	return (-1);
+	return -1;
 }
 
 ssize_t writev(int fildes, const struct iovec *iov, int iovcnt)
 {
 	log_err("%s is not implemented\n", __func__);
 	errno = ENOSYS;
-	return (-1);
+	return -1;
 }
 
 long long strtoll(const char *restrict str, char **restrict endptr,
@@ -725,40 +808,40 @@ int nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
 
 DIR *opendir(const char *dirname)
 {
-    struct dirent_ctx *dc = NULL;
-
-    /* See if we can open it. If not, we'll return an error here */
-    HANDLE file = CreateFileA(dirname, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
-    if (file != INVALID_HANDLE_VALUE) {
-        CloseHandle(file);
-        dc = (struct dirent_ctx*)malloc(sizeof(struct dirent_ctx));
-        StringCchCopyA(dc->dirname, MAX_PATH, dirname);
-        dc->find_handle = INVALID_HANDLE_VALUE;
-    } else {
-        DWORD error = GetLastError();
-        if (error == ERROR_FILE_NOT_FOUND)
-            errno = ENOENT;
-
-        else if (error == ERROR_PATH_NOT_FOUND)
-            errno = ENOTDIR;
-        else if (error == ERROR_TOO_MANY_OPEN_FILES)
-            errno = ENFILE;
-        else if (error == ERROR_ACCESS_DENIED)
-            errno = EACCES;
-        else
-            errno = error;
-    }
-
-    return dc;
+	struct dirent_ctx *dc = NULL;
+
+	/* See if we can open it. If not, we'll return an error here */
+	HANDLE file = CreateFileA(dirname, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+	if (file != INVALID_HANDLE_VALUE) {
+		CloseHandle(file);
+		dc = (struct dirent_ctx*)malloc(sizeof(struct dirent_ctx));
+		StringCchCopyA(dc->dirname, MAX_PATH, dirname);
+		dc->find_handle = INVALID_HANDLE_VALUE;
+	} else {
+		DWORD error = GetLastError();
+		if (error == ERROR_FILE_NOT_FOUND)
+			errno = ENOENT;
+
+		else if (error == ERROR_PATH_NOT_FOUND)
+			errno = ENOTDIR;
+		else if (error == ERROR_TOO_MANY_OPEN_FILES)
+			errno = ENFILE;
+		else if (error == ERROR_ACCESS_DENIED)
+			errno = EACCES;
+		else
+			errno = error;
+	}
+
+	return dc;
 }
 
 int closedir(DIR *dirp)
 {
-    if (dirp != NULL && dirp->find_handle != INVALID_HANDLE_VALUE)
-        FindClose(dirp->find_handle);
+	if (dirp != NULL && dirp->find_handle != INVALID_HANDLE_VALUE)
+		FindClose(dirp->find_handle);
 
-    free(dirp);
-    return 0;
+	free(dirp);
+	return 0;
 }
 
 struct dirent *readdir(DIR *dirp)
diff --git a/os/windows/posix.h b/os/windows/posix.h
index cb89cf6..85640a2 100644
--- a/os/windows/posix.h
+++ b/os/windows/posix.h
@@ -6,5 +6,6 @@ typedef int clockid_t;
 
 extern int clock_gettime(clockid_t clock_id, struct timespec *tp);
 extern int inet_aton(const char *, struct in_addr *);
+extern int win_to_posix_error(DWORD winerr);
 
 #endif
diff --git a/parse.c b/parse.c
index 7652a4d..9250133 100644
--- a/parse.c
+++ b/parse.c
@@ -501,6 +501,21 @@ static int __handle_option(struct fio_option *o, const char *ptr, void *data,
 		break;
 	}
 	case FIO_OPT_FLOAT_LIST: {
+		char *cp2;
+
+		if (first) {
+			/*
+			** Initialize precision to 0 and zero out list
+			** in case specified list is shorter than default
+			*/
+			ul2 = 0;
+			ilp = td_var(data, o->off2);
+			*ilp = ul2;
+
+			flp = td_var(data, o->off1);
+			for(i = 0; i < o->maxlen; i++)
+				flp[i].u.f = 0.0;
+		}
 		if (curr >= o->maxlen) {
 			log_err("the list exceeding max length %d\n",
 					o->maxlen);
@@ -524,6 +539,23 @@ static int __handle_option(struct fio_option *o, const char *ptr, void *data,
 		flp = td_var(data, o->off1);
 		flp[curr].u.f = uf;
 
+		/*
+		** Calculate precision for output by counting
+		** number of digits after period. Find first
+		** period in entire remaining list each time
+		*/
+		cp2 = strchr(ptr, '.');
+		if (cp2 != NULL) {
+			int len = 0;
+
+			while (*++cp2 != '\0' && *cp2 >= '0' && *cp2 <= '9')
+				len++;
+
+			ilp = td_var(data, o->off2);
+			if (len > *ilp)
+				*ilp = len;
+		}
+
 		break;
 	}
 	case FIO_OPT_STR_STORE: {
diff --git a/stat.c b/stat.c
index b16c55c..8835f7f 100644
--- a/stat.c
+++ b/stat.c
@@ -180,11 +180,12 @@ unsigned int calc_clat_percentiles(unsigned int *io_u_plat, unsigned long nr,
  * Find and display the p-th percentile of clat
  */
 static void show_clat_percentiles(unsigned int *io_u_plat, unsigned long nr,
-				  fio_fp64_t *plist)
+				  fio_fp64_t *plist, unsigned int precision)
 {
 	unsigned int len, j = 0, minv, maxv;
 	unsigned int *ovals;
-	int is_last, scale_down;
+	int is_last, per_line, scale_down;
+	char fmt[32];
 
 	len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
 	if (!len)
@@ -202,20 +203,23 @@ static void show_clat_percentiles(unsigned int *io_u_plat, unsigned long nr,
 		log_info("    clat percentiles (usec):\n     |");
 	}
 
+	snprintf(fmt, sizeof(fmt), "%%1.%uf", precision);
+	per_line = (80 - 7) / (precision + 14);
+
 	for (j = 0; j < len; j++) {
-		char fbuf[8];
+		char fbuf[16], *ptr = fbuf;
 
 		/* for formatting */
-		if (j != 0 && (j % 4) == 0)
+		if (j != 0 && (j % per_line) == 0)
 			log_info("     |");
 
 		/* end of the list */
 		is_last = (j == len - 1);
 
 		if (plist[j].u.f < 10.0)
-			sprintf(fbuf, " %2.2f", plist[j].u.f);
-		else
-			sprintf(fbuf, "%2.2f", plist[j].u.f);
+			ptr += sprintf(fbuf, " ");
+
+		snprintf(ptr, sizeof(fbuf), fmt, plist[j].u.f);
 
 		if (scale_down)
 			ovals[j] = (ovals[j] + 999) / 1000;
@@ -225,7 +229,7 @@ static void show_clat_percentiles(unsigned int *io_u_plat, unsigned long nr,
 		if (is_last)
 			break;
 
-		if (j % 4 == 3)	/* for formatting */
+		if ((j % per_line) == per_line - 1)	/* for formatting */
 			log_info("\n");
 	}
 
@@ -397,7 +401,8 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts,
 	if (ts->clat_percentiles) {
 		show_clat_percentiles(ts->io_u_plat[ddir],
 					ts->clat_stat[ddir].samples,
-					ts->percentile_list);
+					ts->percentile_list,
+					ts->percentile_precision);
 	}
 	if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
 		double p_of_agg = 100.0;
@@ -614,7 +619,7 @@ static void show_ddir_status_terse(struct thread_stat *ts,
 			log_info(";0%%=0");
 			continue;
 		}
-		log_info(";%2.2f%%=%u", ts->percentile_list[i].u.f, ovals[i]);
+		log_info(";%f%%=%u", ts->percentile_list[i].u.f, ovals[i]);
 	}
 
 	if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
@@ -712,7 +717,7 @@ static void add_ddir_status_json(struct thread_stat *ts,
 			json_object_add_value_int(percentile_object, "0.00", 0);
 			continue;
 		}
-		snprintf(buf, sizeof(buf), "%2.2f", ts->percentile_list[i].u.f);
+		snprintf(buf, sizeof(buf), "%f", ts->percentile_list[i].u.f);
 		json_object_add_value_int(percentile_object, (const char *)buf, ovals[i]);
 	}
 
@@ -1169,6 +1174,7 @@ void show_run_stats(void)
 		ts = &threadstats[j];
 
 		ts->clat_percentiles = td->o.clat_percentiles;
+		ts->percentile_precision = td->o.percentile_precision;
 		memcpy(ts->percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list));
 
 		idx++;
diff --git a/stat.h b/stat.h
index ba4c2bf..a3b391c 100644
--- a/stat.h
+++ b/stat.h
@@ -147,6 +147,7 @@ struct thread_stat {
 	 * IO depth and latency stats
 	 */
 	uint64_t clat_percentiles;
+	uint64_t percentile_precision;
 	fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 
 	uint32_t io_u_map[FIO_IO_U_MAP_NR];
diff --git a/t/log.c b/t/log.c
index 76ae68e..1ed3851 100644
--- a/t/log.c
+++ b/t/log.c
@@ -1,5 +1,6 @@
 #include <stdio.h>
 #include <stdarg.h>
+#include "../minmax.h"
 
 int log_err(const char *format, ...)
 {
diff --git a/thread_options.h b/thread_options.h
index 11e7af7..a28ccfe 100644
--- a/thread_options.h
+++ b/thread_options.h
@@ -197,6 +197,7 @@ struct thread_options {
 	unsigned int trim_zero;
 	unsigned long long trim_backlog;
 	unsigned int clat_percentiles;
+	unsigned int percentile_precision;	/* digits after decimal for percentiles */
 	fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 
 	char *read_iolog_file;
@@ -396,6 +397,7 @@ struct thread_options_pack {
 	uint32_t trim_zero;
 	uint64_t trim_backlog;
 	uint32_t clat_percentiles;
+	uint32_t percentile_precision;
 	fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
 
 	uint8_t read_iolog_file[FIO_TOP_STR_MAX];
--
To unsubscribe from this list: send the line "unsubscribe fio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel]     [Linux SCSI]     [Linux IDE]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux SCSI]

  Powered by Linux