Abort long-running benchmarks, report progress.

A typical storage device finishes the benchmark in under 10 seconds,
but some extremely slow devices can take minutes, resulting in a
confusing UX that looks like we've frozen.  Even worse, we keep
churning through all that I/O even though we know the device will
blow past our user-warning threshold.

So periodically check if we've timed out, and also use that to report
progress up into the Settings UI.

Test: manual
Bug: 62201209, 65639764, 67055204
Change-Id: I321397bcff230976f034cede0947d4a5a1f3e8a7
diff --git a/Benchmark.cpp b/Benchmark.cpp
index 63b4dd3..dfe3366 100644
--- a/Benchmark.cpp
+++ b/Benchmark.cpp
@@ -18,8 +18,10 @@
 #include "BenchmarkGen.h"
 #include "VolumeManager.h"
 
+#include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
+
 #include <cutils/iosched_policy.h>
 #include <hardware_legacy/power.h>
 #include <private/android_filesystem_config.h>
@@ -30,18 +32,65 @@
 #include <sys/resource.h>
 #include <unistd.h>
 
-#define ENABLE_DROP_CACHES 1
-
 using android::base::ReadFileToString;
 using android::base::WriteStringToFile;
 
 namespace android {
 namespace vold {
 
+// Benchmark currently uses chdir(), which means we can only
+// safely run one at a time.
+static std::mutex kBenchmarkLock;
+
 static const char* kWakeLock = "Benchmark";
 
+// Reasonable cards are able to complete the create/run stages
+// in under 20 seconds.
+constexpr auto kTimeout = 20s;
+
+// RAII class for boosting device performance during benchmarks.
+class PerformanceBoost {
+private:
+    int orig_prio;
+    int orig_ioprio;
+    IoSchedClass orig_clazz;
+
+public:
+    PerformanceBoost() {
+        errno = 0;
+        orig_prio = getpriority(PRIO_PROCESS, 0);
+        if (errno != 0) {
+            PLOG(WARNING) << "Failed to getpriority";
+            orig_prio = 0;
+        }
+        if (setpriority(PRIO_PROCESS, 0, -10) != 0) {
+            PLOG(WARNING) << "Failed to setpriority";
+        }
+        if (android_get_ioprio(0, &orig_clazz, &orig_ioprio)) {
+            PLOG(WARNING) << "Failed to android_get_ioprio";
+            orig_ioprio = 0;
+            orig_clazz = IoSchedClass_NONE;
+        }
+        if (android_set_ioprio(0, IoSchedClass_RT, 0)) {
+            PLOG(WARNING) << "Failed to android_set_ioprio";
+        }
+    }
+
+    ~PerformanceBoost() {
+        if (android_set_ioprio(0, orig_clazz, orig_ioprio)) {
+            PLOG(WARNING) << "Failed to android_set_ioprio";
+        }
+        if (setpriority(PRIO_PROCESS, 0, orig_prio) != 0) {
+            PLOG(WARNING) << "Failed to setpriority";
+        }
+    }
+};
+
 static status_t benchmarkInternal(const std::string& rootPath,
+        const android::sp<android::os::IVoldTaskListener>& listener,
         android::os::PersistableBundle* extras) {
+    status_t res = 0;
+
     auto path = rootPath;
     path += "/misc";
     if (android::vold::PrepareDir(path, 01771, AID_SYSTEM, AID_MISC)) {
@@ -56,28 +105,6 @@
         return -1;
     }
 
-    errno = 0;
-    int orig_prio = getpriority(PRIO_PROCESS, 0);
-    if (errno != 0) {
-        PLOG(ERROR) << "Failed to getpriority";
-        return -1;
-    }
-    if (setpriority(PRIO_PROCESS, 0, -10) != 0) {
-        PLOG(ERROR) << "Failed to setpriority";
-        return -1;
-    }
-
-    IoSchedClass orig_clazz = IoSchedClass_NONE;
-    int orig_ioprio = 0;
-    if (android_get_ioprio(0, &orig_clazz, &orig_ioprio)) {
-        PLOG(ERROR) << "Failed to android_get_ioprio";
-        return -1;
-    }
-    if (android_set_ioprio(0, IoSchedClass_RT, 0)) {
-        PLOG(ERROR) << "Failed to android_set_ioprio";
-        return -1;
-    }
-
     char orig_cwd[PATH_MAX];
     if (getcwd(orig_cwd, PATH_MAX) == NULL) {
         PLOG(ERROR) << "Failed getcwd";
@@ -90,66 +117,76 @@
 
     sync();
 
-    LOG(INFO) << "Benchmarking " << path;
-    nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);
+    extras->putString(String16("path"), String16(path.c_str()));
+    extras->putString(String16("ident"), String16(BenchmarkIdent().c_str()));
 
-    BenchmarkCreate();
-    sync();
-    nsecs_t create = systemTime(SYSTEM_TIME_BOOTTIME);
-
-#if ENABLE_DROP_CACHES
-    LOG(VERBOSE) << "Before drop_caches";
-    if (!WriteStringToFile("3", "/proc/sys/vm/drop_caches")) {
-        PLOG(ERROR) << "Failed to drop_caches";
+    // Always create
+    {
+        android::base::Timer timer;
+        LOG(INFO) << "Creating " << path;
+        res |= BenchmarkCreate([&](int progress) -> bool {
+            if (listener) {
+                listener->onStatus(progress, *extras);
+            }
+            return (timer.duration() < kTimeout);
+        });
+        sync();
+        if (res == OK) extras->putLong(String16("create"), timer.duration().count());
     }
-    LOG(VERBOSE) << "After drop_caches";
-#endif
-    nsecs_t drop = systemTime(SYSTEM_TIME_BOOTTIME);
 
-    BenchmarkRun();
-    sync();
-    nsecs_t run = systemTime(SYSTEM_TIME_BOOTTIME);
+    // Only drop when we haven't aborted
+    if (res == OK) {
+        android::base::Timer timer;
+        LOG(VERBOSE) << "Before drop_caches";
+        if (!WriteStringToFile("3", "/proc/sys/vm/drop_caches")) {
+            PLOG(ERROR) << "Failed to drop_caches";
+            res = -1;
+        }
+        LOG(VERBOSE) << "After drop_caches";
+        sync();
+        if (res == OK) extras->putLong(String16("drop"), timer.duration().count());
+    }
 
-    BenchmarkDestroy();
-    sync();
-    nsecs_t destroy = systemTime(SYSTEM_TIME_BOOTTIME);
+    // Only run when we haven't aborted
+    if (res == OK) {
+        android::base::Timer timer;
+        LOG(INFO) << "Running " << path;
+        res |= BenchmarkRun([&](int progress) -> bool {
+            if (listener) {
+                listener->onStatus(progress, *extras);
+            }
+            return (timer.duration() < kTimeout);
+        });
+        sync();
+        if (res == OK) extras->putLong(String16("run"), timer.duration().count());
+    }
+
+    // Always destroy
+    {
+        android::base::Timer timer;
+        LOG(INFO) << "Destroying " << path;
+        res |= BenchmarkDestroy();
+        sync();
+        if (res == OK) extras->putLong(String16("destroy"), timer.duration().count());
+    }
 
     if (chdir(orig_cwd) != 0) {
         PLOG(ERROR) << "Failed to chdir";
-    }
-    if (android_set_ioprio(0, orig_clazz, orig_ioprio)) {
-        PLOG(ERROR) << "Failed to android_set_ioprio";
-    }
-    if (setpriority(PRIO_PROCESS, 0, orig_prio) != 0) {
-        PLOG(ERROR) << "Failed to setpriority";
+        return -1;
     }
 
-    nsecs_t create_d = create - start;
-    nsecs_t drop_d = drop - create;
-    nsecs_t run_d = run - drop;
-    nsecs_t destroy_d = destroy - run;
-
-    LOG(INFO) << "create took " << nanoseconds_to_milliseconds(create_d) << "ms";
-    LOG(INFO) << "drop took " << nanoseconds_to_milliseconds(drop_d) << "ms";
-    LOG(INFO) << "run took " << nanoseconds_to_milliseconds(run_d) << "ms";
-    LOG(INFO) << "destroy took " << nanoseconds_to_milliseconds(destroy_d) << "ms";
-
-    extras->putString(String16("path"), String16(path.c_str()));
-    extras->putString(String16("ident"), String16(BenchmarkIdent().c_str()));
-    extras->putLong(String16("create"), create_d);
-    extras->putLong(String16("drop"), drop_d);
-    extras->putLong(String16("run"), run_d);
-    extras->putLong(String16("destroy"), destroy_d);
-
-    return 0;
+    return res;
 }
 
 void Benchmark(const std::string& path,
         const android::sp<android::os::IVoldTaskListener>& listener) {
+    std::lock_guard<std::mutex> lock(kBenchmarkLock);
     acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakeLock);
 
+    PerformanceBoost boost;
     android::os::PersistableBundle extras;
-    status_t res = benchmarkInternal(path, &extras);
+
+    status_t res = benchmarkInternal(path, listener, &extras);
     if (listener) {
         listener->onFinished(res, extras);
     }