summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVince Harron <vharron@google.com>2016-05-19 15:32:46 -0700
committerVince Harron <vharron@google.com>2016-06-03 16:01:19 -0700
commit0c2bd9f2ebfbbce4135d49e82a43761fee1d8f3a (patch)
tree2a3bcafbd7b79b999646a78674a6cd7a8aa273e5
parent65404466e5bec13ee304b3efbb617a940b12278a (diff)
downloademulator-0c2bd9f2ebfbbce4135d49e82a43761fee1d8f3a.tar.gz
Added iobench program
This program benchmarks emulator guest IO performance using time measurements from the host. It also stops/starts the emulator between phases to clear guest caches. Change-Id: I487c67154ddc751af25923c58434498b1c96d0d9
-rw-r--r--benchmarks/iobench/CMakeLists.txt7
-rw-r--r--benchmarks/iobench/README16
-rwxr-xr-xbenchmarks/iobench/android-bench.sh125
-rwxr-xr-xbenchmarks/iobench/flush.sh4
-rwxr-xr-xbenchmarks/iobench/linux-bench.sh26
-rw-r--r--benchmarks/iobench/main.cpp187
6 files changed, 365 insertions, 0 deletions
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;
+}