Merge "Added iobench program" into emu-master-dev
diff --git a/benchmarks/iobench/CMakeLists.txt b/benchmarks/iobench/CMakeLists.txt
new file mode 100644
index 0000000..8251a8d
--- /dev/null
+++ b/benchmarks/iobench/CMakeLists.txt
@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 3.3)
+project(iobench)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+
+set(SOURCE_FILES main.cpp)
+add_executable(iobench ${SOURCE_FILES})
diff --git a/benchmarks/iobench/README b/benchmarks/iobench/README
new file mode 100644
index 0000000..17ed9a9
--- /dev/null
+++ b/benchmarks/iobench/README
@@ -0,0 +1,16 @@
+MOTIVATION
+
+Running storage benchmarks inside of the emulator raise some concerns that time
+might not be reported correctly in the guest.  This benchmark is intended to be
+run on an Android device with time measurement done on the host.
+
+USAGE
+
+1) Start an x86 Android emulator
+   (must have at least 1024MB of free internal storage space)
+2) Disconnect any other devices/stop any other emulators
+3) ./android-bench.sh
+
+It is also possible to run this benchmark on Linux for comparison
+1) ./linux-bench.sh
+
diff --git a/benchmarks/iobench/android-bench.sh b/benchmarks/iobench/android-bench.sh
new file mode 100755
index 0000000..90bbe48
--- /dev/null
+++ b/benchmarks/iobench/android-bench.sh
@@ -0,0 +1,125 @@
+#!/bin/bash -e
+#
+# ./android-bench.sh [-adb <adb-path>] <emulator-path> [<emulator arguments> ...]
+#
+# This script builds and runs the iobenchmark against a running emulator.
+# Make sure your emulator has as least 1024MB of free space in
+# /data/local/tmp
+# TODO: reboot the emulator between each invocation to ensure
+# cache is completely flushed
+
+# size of benchmark scratch file in MB
+MB=1024
+
+DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+while [[ $# > 1 ]]
+do
+  key="$1"
+
+  case $key in
+    -a|--adb)
+    shift
+    ADB="$1"
+    ;;
+    *)
+    # not a flag, must be emulator binary
+    break
+    ;;
+  esac
+  shift # past key
+done
+
+if [ -z "$ADB" ]; then
+  ADB="$(which adb)"
+fi
+
+EMU="$(which $1)"
+shift
+EMU_ARGS=$@
+
+echo ADB=$ADB
+echo EMU=$EMU $EMU_ARGS
+
+function wait_for_emulator() {
+  echo "waiting for emulator..."
+  while true; do
+    BOOT="$($ADB shell getprop sys.boot_completed 2> /dev/null)" || true
+    if [[ "$BOOT" = 1* ]]; then
+      break
+    fi
+    sleep 1
+  done
+}
+
+function error_exit() {
+  echo "ERROR: $1"
+  exit 1
+}
+
+# prompt user for password at the beginning of the script
+sudo true
+
+rm -rf $DIR/build-android || true
+mkdir $DIR/build-android
+cd $DIR/build-android
+export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle
+$ANDROID_SDK_ROOT/cmake/bin/cmake .. \
+  -DCMAKE_TOOLCHAIN_FILE=$ANDROID_SDK_ROOT/cmake/android.toolchain.cmake \
+  -DANDROID_ABI=x86 \
+  -DANDROID_DEFAULT_NDK_API_LEVEL=9 \
+  -DCMAKE_CXX_FLAGS="-fPIE -pie"
+
+make
+
+TMP=/data/local/tmp
+
+SCRATCH=$(mktemp /tmp/iobench.XXXXXX)
+
+function bench {
+  killall qemu-system-i386 qemu-system-x86_64 emulator-x86 emulator-x86_64 player 2> /dev/null || true
+  sleep 4 # give the emulator a chance to shut down or it will still appear to be running below
+
+  $EMU $EMU_ARGS &
+  wait_for_emulator
+
+  sleep 2 # give the emulator a chance to finish booting
+
+  sudo $DIR/flush.sh > /dev/null || error_exit "flush"
+
+  if [ -z "$READY" ]; then
+    # first time device setup
+    $ADB push iobench $TMP/iobench || error_exit "$ADB push iobench $TMP/iobench"
+    $ADB shell rm $TMP/iobench.dat || true
+    READY=true
+  fi
+
+  echo iobench "$@"
+
+  # run the benchmark, capture just the "real" time
+  (time $ADB shell $TMP/iobench "$@") 2> $SCRATCH
+
+  # convert "real    0m1.234s" -> "0m1.234"
+  REAL=$(grep real $SCRATCH)
+  TIME=$(echo $REAL | sed -e 's/real \([m0-9\.]*\)s/\1/g')
+  if [[ "$TIME" = "" ]]; then
+    # invalid response
+    cat $SCRATCH
+    error_exit "running iobench failed"
+  fi
+
+  # TIME is in the format 0m1.234 where the value to the left of the 'm' is
+  # minutes and the value to the right is seconds
+  MIN=$(echo $TIME | cut -f1 -dm)
+  SEC=$(echo $TIME | cut -f2 -dm)
+  SEC=$(echo "scale=3; $MIN * 60 + $SEC" | bc)
+
+  MBPS=$(echo "scale=3; $MB / $SEC" | bc)
+  echo "Host measurement: $MBPS MB/S"
+}
+
+bench -seq -write $MB  # this invocation is just to create the file
+bench -seq -write $MB
+bench -seq -read $MB
+bench -rand -write $MB
+bench -rand -read $MB
diff --git a/benchmarks/iobench/flush.sh b/benchmarks/iobench/flush.sh
new file mode 100755
index 0000000..396c863
--- /dev/null
+++ b/benchmarks/iobench/flush.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+# use this script to flush the file caches on linux or android
+free && sync && echo 3 > /proc/sys/vm/drop_caches && free
+
diff --git a/benchmarks/iobench/linux-bench.sh b/benchmarks/iobench/linux-bench.sh
new file mode 100755
index 0000000..1ea9d1b
--- /dev/null
+++ b/benchmarks/iobench/linux-bench.sh
@@ -0,0 +1,26 @@
+#!/bin/bash -e
+
+# prompt user for password
+sudo true
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+rm -rf $DIR/build || true
+mkdir $DIR/build
+cd $DIR/build
+$ANDROID_SDK_ROOT/cmake/bin/cmake ..
+make
+rm /tmp/iobench.tmp || true
+./iobench -seq -write 1024 > /dev/null # create the file
+
+sudo $DIR/flush.sh > /dev/null
+./iobench -seq -write 1024
+
+sudo $DIR/flush.sh > /dev/null
+./iobench -seq -read 1024
+
+sudo $DIR/flush.sh > /dev/null
+./iobench -rand -write 1024
+
+sudo $DIR/flush.sh > /dev/null
+./iobench -rand -read 1024
diff --git a/benchmarks/iobench/main.cpp b/benchmarks/iobench/main.cpp
new file mode 100644
index 0000000..a9b6211
--- /dev/null
+++ b/benchmarks/iobench/main.cpp
@@ -0,0 +1,187 @@
+/*
+ * 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 <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <algorithm>
+#include <vector>
+#include <errno.h>
+#include <cassert>
+
+// iobench -seq -write 1024 - sequentially write 1024MB to /tmp/iobench.dat
+// iobench -rand -write 1024 - sequentially write 1024MB (in 256K chunks) to
+// /tmp/iobench.dat
+// iobench -seq -read 1024 - sequentially read 1024MB from /tmp/iobench.dat
+// iobench -rand -read 1024 - sequentially read 1024MB (in 256K chunks) from
+// /tmp/iobench.dat
+
+#ifdef __ANDROID__
+#define TMP_DIR "/data/local/tmp"
+#else  // __linux__
+#define TMP_DIR "/tmp"
+#endif
+
+#define DATA_FILE TMP_DIR "/iobench.dat"
+
+// Allocate a buffer of size |buffer_size|.  Fill it with
+// non-zero data to prevent sparse files from being created
+void* mallocNoise(size_t buffer_size) {
+    void* result = malloc(buffer_size);
+    for (int i = 0; i < buffer_size; i += 512) {
+        int* block = (int*)((char*)result + i);
+        *block = i + 1;
+    }
+    return result;
+}
+
+// We want to test random file access but we want to make sure that
+// every read/write chunk is read/written exactly once.
+// So we make a sequential list of chunk indexes and shuffle it
+std::vector<size_t> createRandomChunkList(size_t chunk_count) {
+    std::random_device rd;
+    std::mt19937 gen(rd());
+    std::uniform_int_distribution<size_t> dist(0, chunk_count-1);
+
+    std::vector<size_t> result(chunk_count);
+
+    // a sequential list of chunk indexes
+    for (int64_t i = 0; i < chunk_count; i++) {
+        result[i] = i;
+    }
+
+    // shuffle the list of chunk indexes
+    // swap every chunk with a random chunk
+    std::shuffle(result.begin(), result.end(), gen);
+
+    return result;
+}
+
+const int NS_PER_SEC = 1000 * 1000 * 1000;
+
+int64_t subtractTimespec(const struct timespec* hi, const struct timespec* lo) {
+    int64_t delta = hi->tv_sec - lo->tv_sec;
+    delta *= NS_PER_SEC;
+    delta += hi->tv_nsec;
+    delta -= lo->tv_nsec;
+    return delta;
+}
+
+int main(int argc, const char* argv[]) {
+    const size_t kBufferSize = 256 * 1024;
+    bool read_mode = false;
+    size_t chunk_size = kBufferSize;
+    bool rand_mode = false;
+    size_t file_size_mb = 1024;
+    for (int i = 1; i < argc; i++) {
+        if (strcmp("-rand", argv[i]) == 0) {
+            rand_mode = true;
+            chunk_size = 4 * 1024;
+        } else if (strcmp("-seq", argv[i]) == 0) {
+            rand_mode = false;
+            chunk_size = kBufferSize;
+        } else if (strcmp("-read", argv[i]) == 0) {
+            read_mode = true;
+        } else if (strcmp("-write", argv[i]) == 0) {
+            read_mode = false;
+        } else {
+            file_size_mb = strtoul(argv[i], nullptr, 10);
+        }
+    }
+
+    size_t file_size = file_size_mb * 1024 * 1024;
+    size_t chunk_count = file_size / chunk_size;
+    std::vector<size_t> chunk_list = createRandomChunkList(chunk_count);
+
+    int open_flags = O_CREAT;
+    if (read_mode) {
+        open_flags |= O_RDONLY;
+    } else {
+        open_flags |= O_WRONLY;
+    }
+    int fd = open(DATA_FILE, open_flags, 0600);
+    if (fd < 0) {
+        perror("open");
+        return -1;
+    }
+    void* buffer = mallocNoise(kBufferSize);
+
+    struct timespec start;
+    if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
+        perror("clock_gettime");
+        return -1;
+    }
+
+    for (int i = 0; i < chunk_count; i++) {
+        if (rand_mode) {
+            off64_t off = chunk_list[i] * chunk_size;
+            off64_t result = lseek64(fd, off, SEEK_SET);
+            if (result != off) {
+                perror("lseek64");
+                return -1;
+            }
+        }
+        if (read_mode) {
+            ssize_t read_result = read(fd, buffer, chunk_size);
+            if (chunk_size != read_result) {
+                perror("read");
+                return -1;
+            }
+        } else {
+            if (chunk_size != write(fd, buffer, chunk_size)) {
+                perror("write");
+                return -1;
+            }
+        }
+    }
+
+    if (!read_mode) {
+        if (fsync(fd) < 0) {
+            perror("fsync");
+            return -1;
+        }
+    }
+
+    if (close(fd) < 0) {
+        perror("close");
+        return -1;
+    }
+
+    struct timespec end;
+    if (clock_gettime(CLOCK_REALTIME, &end) < 0) {
+        perror("clock_gettime");
+        return -1;
+    }
+    int64_t elapsed_ns = subtractTimespec(&end, &start);
+#ifdef DEBUG
+    printf("start sec %li nsec %li\n", start.tv_sec, start.tv_nsec);
+    printf("end   sec %li nsec %li\n", end.tv_sec, end.tv_nsec);
+    printf("megabytes: %zu\n", file_size_mb);
+    printf("time elapsed: %lins\n", elapsed_ns);
+    printf("time elapsed: %fs\n", (double)elapsed_ns / (double)NS_PER_SEC);
+#endif
+    if (elapsed_ns > 0) {
+        double megabytes_per_second =
+                (double) file_size_mb * (double) NS_PER_SEC / (double) elapsed_ns;
+        printf("%.2fMB/s\n", megabytes_per_second);
+    }
+    else {
+        printf("elapsed_ns == 0\n");
+    }
+    return 0;
+}