From 0c2bd9f2ebfbbce4135d49e82a43761fee1d8f3a Mon Sep 17 00:00:00 2001 From: Vince Harron Date: Thu, 19 May 2016 15:32:46 -0700 Subject: 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 --- benchmarks/iobench/CMakeLists.txt | 7 ++ benchmarks/iobench/README | 16 +++ benchmarks/iobench/android-bench.sh | 125 ++++++++++++++++++++++++ benchmarks/iobench/flush.sh | 4 + benchmarks/iobench/linux-bench.sh | 26 +++++ benchmarks/iobench/main.cpp | 187 ++++++++++++++++++++++++++++++++++++ 6 files changed, 365 insertions(+) create mode 100644 benchmarks/iobench/CMakeLists.txt create mode 100644 benchmarks/iobench/README create mode 100755 benchmarks/iobench/android-bench.sh create mode 100755 benchmarks/iobench/flush.sh create mode 100755 benchmarks/iobench/linux-bench.sh create mode 100644 benchmarks/iobench/main.cpp 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 ] [ ...] +# +# 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 +#include +#include +#include +#include +#include +#include +#include +#include + +// 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 createRandomChunkList(size_t chunk_count) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dist(0, chunk_count-1); + + std::vector 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 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; +} -- cgit v1.2.3