OSDN Git Service

Merge "simpleperf: notify user for unsupported modifiers."
authorYabin Cui <yabinc@google.com>
Wed, 24 Aug 2016 20:16:24 +0000 (20:16 +0000)
committerandroid-build-merger <android-build-merger@google.com>
Wed, 24 Aug 2016 20:16:24 +0000 (20:16 +0000)
am: 43908a31c1

Change-Id: I4f53fa84ea663a4090a99f2261f153faf8f0b8fb

57 files changed:
ANRdaemon/ANRdaemon.cpp
ANRdaemon/ANRdaemon_get_trace.sh [new file with mode: 0755]
ANRdaemon/Android.mk
ANRdaemon/README [new file with mode: 0644]
alloc-stress/Android.mk
alloc-stress/alloc-stress.cpp
cpustats/cpustats.c
crypto-perf/Android.mk [new file with mode: 0644]
crypto-perf/NOTICE [new file with mode: 0644]
crypto-perf/crypto.cpp [new file with mode: 0644]
ext4_utils/Android.mk
ext4_utils/allocate.c
ext4_utils/allocate.h
ext4_utils/blk_alloc_to_base_fs.c [new file with mode: 0644]
ext4_utils/ext4_crypt.cpp
ext4_utils/ext4_crypt.h [new file with mode: 0644]
ext4_utils/ext4_crypt_init_extensions.cpp
ext4_utils/ext4_crypt_init_extensions.h
ext4_utils/ext4_utils.c
ext4_utils/ext4_utils.h
ext4_utils/extent.c
ext4_utils/make_ext4fs.c
ext4_utils/make_ext4fs.h
ext4_utils/make_ext4fs_main.c
ext4_utils/mkuserimg.sh
ext4_utils/unencrypted_properties.cpp [deleted file]
ext4_utils/unencrypted_properties.h [deleted file]
mmap-perf/Android.mk
mmap-perf/mmapPerf.cpp
multinetwork/Android.mk [new file with mode: 0644]
multinetwork/common.cpp [new file with mode: 0644]
multinetwork/common.h [new file with mode: 0644]
multinetwork/dnschk.cpp [new file with mode: 0644]
multinetwork/httpurl.cpp [new file with mode: 0644]
multinetwork/quick_test.sh [new file with mode: 0755]
pagecache/Android.mk [new file with mode: 0644]
pagecache/MODULE_LICENSE_APACHE2 [new file with mode: 0644]
pagecache/NOTICE [new file with mode: 0644]
pagecache/README [new file with mode: 0644]
pagecache/dumpcache.c [new file with mode: 0644]
pagecache/pagecache.py [new file with mode: 0755]
procrank/procrank.cpp
showmap/showmap.cpp
simpleperf/cmd_record_test.cpp
simpleperf/cmd_stat_test.cpp
squashfs_utils/mksquashfsimage.sh
tests/binder/benchmarks/Android.mk
tests/binder/benchmarks/binderAddInts.cpp
tests/workloads/capture.sh
tests/workloads/chromefling.sh [new file with mode: 0755]
tests/workloads/defs.sh
tests/workloads/powerave.py [new file with mode: 0755]
tests/workloads/pwrsummary.sh [new file with mode: 0755]
tests/workloads/pwrtest.sh [new file with mode: 0755]
tests/workloads/recentfling.sh
tests/workloads/systemapps.sh
tests/workloads/youtube.sh [new file with mode: 0755]

index 5a4f8bf..7b77a86 100644 (file)
 
 using namespace android;
 
-#define CHECK_PERIOD 1  // in sec
-#define TRACING_CHECK_PERIOD 500000 // in micro sec
-#define MIN_BUFFER_SIZE 4
-#define MIN_BUFFER_SIZE_STR "4"
-#define MAX_BUFFER_SIZE 128
-#define MAX_BUFFER_SIZE_STR "128"
-#define CPU_STAT_ENTRIES 7 // number of cpu stat entries
-
 #ifdef LOG_TAG
 #undef LOG_TAG
 #endif
 
 #define LOG_TAG "anrdaemon"
 
+static const int check_period = 1;              // in sec
+static const int tracing_check_period = 500000; // in micro sec
+static const int cpu_stat_entries = 7;          // number of cpu stat entries
+static const int min_buffer_size = 16;
+static const int max_buffer_size = 2048;
+static const char *min_buffer_size_str = "16";
+static const char *max_buffer_size_str = "2048";
+
 typedef struct cpu_stat {
     unsigned long utime, ntime, stime, itime;
     unsigned long iowtime, irqtime, sirqtime, steal;
     unsigned long total;
 } cpu_stat_t;
 
-/* Make the logging on/off threshold equal to 95% cpu usage. */
-static int idle_threshold = 5;
+/*
+ * Logging on/off threshold.
+ * Uint: 0.01%; default to 99.90% cpu.
+ */
+static int idle_threshold = 10;
 
 static bool quit = false;
 static bool suspend= false;
@@ -83,13 +86,12 @@ static bool err = false;
 static char err_msg[100];
 static bool tracing = false;
 
-static const char *buf_size_kb = "16";
+static const char *buf_size_kb = "2048";
+static const char *apps = "";
 static uint64_t tag = 0;
-static const char* apps = "";
 
 static cpu_stat_t new_cpu;
 static cpu_stat_t old_cpu;
-static Vector<String16> targets;
 
 /* Log certain kernel activity when enabled */
 static bool log_sched = false;
@@ -133,7 +135,7 @@ static void get_cpu_stat(cpu_stat_t *cpu) {
     } else {
         if (fscanf(fp, params, &cpu->utime, &cpu->ntime,
                 &cpu->stime, &cpu->itime, &cpu->iowtime, &cpu->irqtime,
-                &cpu->sirqtime) != CPU_STAT_ENTRIES) {
+                &cpu->sirqtime) != cpu_stat_entries) {
             /*
              * If failed in getting status, new_cpu won't be updated and
              * is_heavy_loaded() will return false.
@@ -151,26 +153,26 @@ static void get_cpu_stat(cpu_stat_t *cpu) {
 
 /*
  * Calculate cpu usage in the past interval.
- * If tracing is on, increase the idle threshold by 1% so that we do not
+ * If tracing is on, increase the idle threshold by 1.00% so that we do not
  * turn on and off tracing frequently whe the cpu load is right close to
  * threshold.
  */
 static bool is_heavy_load(void) {
     unsigned long diff_idle, diff_total;
-    int threshold = idle_threshold + (tracing?1:0);
+    int threshold = idle_threshold + (tracing?100:0);
     get_cpu_stat(&new_cpu);
     diff_idle = new_cpu.itime - old_cpu.itime;
     diff_total = new_cpu.total - old_cpu.total;
     old_cpu = new_cpu;
-    return (diff_idle * 100 < diff_total * threshold);
+    return (diff_idle * 10000 < diff_total * threshold);
 }
 
 /*
  * Force the userland processes to refresh their property for logging.
  */
-static void dfs_poke_binder(Vector<String16> services) {
+static void dfs_poke_binder(void) {
     sp<IServiceManager> sm = defaultServiceManager();
-    services = sm->listServices();
+    Vector<String16> services = sm->listServices();
     for (size_t i = 0; i < services.size(); i++) {
         sp<IBinder> obj = sm->checkService(services[i]);
         if (obj != NULL) {
@@ -194,8 +196,10 @@ static int dfs_enable(bool enable, const char* path) {
     ssize_t len = strlen(control);
     int max_try = 10; // Fail if write was interrupted for 10 times
     while (write(fd, control, len) != len) {
-        if (errno == EINTR && max_try-- > 0)
+        if (errno == EINTR && max_try-- > 0) {
+            usleep(100);
             continue;
+        }
 
         err = true;
         sprintf(err_msg, "Error %d in writing to %s.", errno, path);
@@ -246,9 +250,6 @@ static void dfs_set_property(uint64_t mtag, const char* mapp, bool enable) {
 static void start_tracing(void) {
     ALOGD("High cpu usage, start logging.");
 
-    dfs_set_property(tag, apps, true);
-    dfs_poke_binder(targets);
-
     if (dfs_enable(true, dfs_control_path) != 0) {
         ALOGE("Failed to start tracing.");
         return;
@@ -257,16 +258,13 @@ static void start_tracing(void) {
 
     /* Stop logging when cpu usage drops or the daemon is suspended.*/
     do {
-        usleep(TRACING_CHECK_PERIOD);
+        usleep(tracing_check_period);
     } while (!suspend && is_heavy_load());
 
     if (dfs_enable(false, dfs_control_path) != 0) {
         ALOGE("Failed to stop tracing.");
     }
 
-    dfs_set_property(0, "", false);
-    dfs_poke_binder(targets);
-
     ALOGD("Usage back to low, stop logging.");
     tracing = false;
 }
@@ -299,8 +297,12 @@ static int set_tracing_buffer_size(void) {
 static void start(void) {
     if ((set_tracing_buffer_size()) != 0)
         return;
+
+    dfs_set_property(tag, apps, true);
+    dfs_poke_binder();
+
     get_cpu_stat(&old_cpu);
-    sleep(CHECK_PERIOD);
+    sleep(check_period);
 
     while (!quit && !err) {
         if (!suspend && is_heavy_load()) {
@@ -312,7 +314,7 @@ static void start(void) {
             start_tracing();
             setpriority(PRIO_PROCESS, 0, 0);
         }
-        sleep(CHECK_PERIOD);
+        sleep(check_period);
     }
     return;
 }
@@ -326,7 +328,7 @@ static void dump_trace()
     suspend = true;
     while (tracing) {
         ALOGI("Waiting logging to stop.");
-        usleep(TRACING_CHECK_PERIOD);
+        usleep(tracing_check_period);
         remain_attempts--;
         if (remain_attempts == 0) {
             ALOGE("Can't stop logging after 5 attempts. Dump aborted.");
@@ -335,7 +337,18 @@ static void dump_trace()
     }
 
     /*
-     * Create a dump file "dump_of_anrdaemon.<current_time>" under /data/anr/
+     * Create /sdcard/ANRdaemon/ if it doesn't exist
+     */
+    struct stat st;
+    if (stat("/sdcard/ANRdaemon", &st) == -1) {
+        ALOGI("Creating /sdcard/ANRdaemon/");
+        int err = mkdir("/sdcard/ANRdaemon", 0700);
+        if (err != 0)
+            ALOGI("Creating /sdcard/ANRdaemon/ failed with %s", strerror(err));
+    }
+
+    /*
+     * Create a dump file "dump_of_anrdaemon.<current_time>" under /sdcard/ANRdaemon/
      */
     time_t now = time(0);
     struct tm  tstruct;
@@ -345,7 +358,7 @@ static void dump_trace()
     ssize_t header_len = strlen(header);
     tstruct = *localtime(&now);
     strftime(time_buf, sizeof(time_buf), "%Y-%m-%d.%X", &tstruct);
-    sprintf(path_buf, "/data/anr/dump_of_anrdaemon.%s", time_buf);
+    sprintf(path_buf, "/sdcard/ANRdaemon/dump_of_anrdaemon.%s", time_buf);
     int output_fd = creat(path_buf, S_IRWXU);
     if (output_fd == -1) {
         ALOGE("Failed to create %s. Dump aborted.", path_buf);
@@ -494,16 +507,19 @@ static void show_help(void) {
                     "   -a appname  enable app-level tracing for a comma "
                        "separated list of cmdlines\n"
                     "   -t N        cpu threshold for logging to start "
-                        "(min = 50, max = 100, default = 95)\n"
+                        "(uint = 0.01%%, min = 5000, max = 9999, default = 9990)\n"
                     "   -s N        use a trace buffer size of N KB "
-                        "default to 16KB\n"
+                        "default to 2048KB\n"
                     "   -h          show helps\n");
     fprintf(stdout, "Categoris includes:\n"
                     "   am         - activity manager\n"
                     "   sm         - sync manager\n"
                     "   input      - input\n"
-                    "   app        - application\n"
                     "   dalvik     - dalvik VM\n"
+                    "   audio      - Audio\n"
+                    "   gfx        - Graphics\n"
+                    "   rs         - RenderScript\n"
+                    "   hal        - Hardware Modules\n"
                     "   irq        - kernel irq events\n"
                     "   sched      - kernel scheduler activity\n"
                     "   stack      - kernel stack\n"
@@ -526,20 +542,20 @@ static int get_options(int argc, char *argv[]) {
                 apps = optarg;
                 break;
             case 's':
-                if (atoi(optarg) > MAX_BUFFER_SIZE)
-                    buf_size_kb = MAX_BUFFER_SIZE_STR;
-                else if (atoi(optarg) < MIN_BUFFER_SIZE)
-                    buf_size_kb = MIN_BUFFER_SIZE_STR;
+                if (atoi(optarg) > max_buffer_size)
+                    buf_size_kb = max_buffer_size_str;
+                else if (atoi(optarg) < min_buffer_size)
+                    buf_size_kb = min_buffer_size_str;
                 else
                     buf_size_kb = optarg;
                 break;
             case 't':
                 threshold = atoi(optarg);
-                if (threshold > 100 || threshold < 50) {
-                    fprintf(stderr, "logging threshold should be 50-100\n");
+                if (threshold > 9999 || threshold < 5000) {
+                    fprintf(stderr, "logging threshold should be 5000-9999\n");
                     return 1;
                 }
-                idle_threshold = 100 - threshold;
+                idle_threshold = 10000 - threshold;
                 break;
             case 'h':
                 show_help();
@@ -558,10 +574,16 @@ static int get_options(int argc, char *argv[]) {
             tag |= ATRACE_TAG_INPUT;
         } else if (strcmp(argv[i], "sm") == 0) {
             tag |= ATRACE_TAG_SYNC_MANAGER;
-        } else if (strcmp(argv[i], "app") == 0) {
-            tag |= ATRACE_TAG_APP;
         } else if (strcmp(argv[i], "dalvik") == 0) {
             tag |= ATRACE_TAG_DALVIK;
+        } else if (strcmp(argv[i], "gfx") == 0) {
+            tag |= ATRACE_TAG_GRAPHICS;
+        } else if (strcmp(argv[i], "audio") == 0) {
+            tag |= ATRACE_TAG_AUDIO;
+        } else if (strcmp(argv[i], "hal") == 0) {
+            tag |= ATRACE_TAG_HAL;
+        } else if (strcmp(argv[i], "rs") == 0) {
+            tag |= ATRACE_TAG_RS;
         } else if (strcmp(argv[i], "sched") == 0) {
             log_sched = true;
         } else if (strcmp(argv[i], "stack") == 0) {
@@ -579,29 +601,10 @@ static int get_options(int argc, char *argv[]) {
         }
     }
 
-    bool kernel_log = log_sched || log_stack || log_workq || log_irq || log_sync;
-    bool app_log = (tag == 0);
-
-    /*
-     * There are ~80 services. Too expensive to poke all of them. Just include
-     * service that may help high CPU ANR analysis.
-     */
-    if (app_log) {
-       targets.push_back(String16("activity"));
-       targets.push_back(String16("alarm"));
-       targets.push_back(String16("appops"));
-       targets.push_back(String16("cpuinfo"));
-       targets.push_back(String16("meminfo"));
-       targets.push_back(String16("procstats"));
-       targets.push_back(String16("input"));
-       targets.push_back(String16("lancherapps"));
-       targets.push_back(String16("bluetooth_manager"));
-       targets.push_back(String16("SurfaceFlinger"));
-       targets.push_back(String16("ClockworkProxyNativeService"));
-    }
-    if (!kernel_log && !app_log) {
-        tag |= ATRACE_TAG_ACTIVITY_MANAGER;
-        targets.push_back(String16("activity"));
+    /* If nothing is enabled, don't run */
+    if (!tag && !log_sched && !log_stack && !log_workq && !log_irq && !log_sync) {
+        ALOGE("Specify at least one category to trace.");
+        return 1;
     }
 
     return 0;
diff --git a/ANRdaemon/ANRdaemon_get_trace.sh b/ANRdaemon/ANRdaemon_get_trace.sh
new file mode 100755 (executable)
index 0000000..be4062c
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+TRACE_DIR=/sdcard/ANRdaemon
+
+if [ $# -eq 1 ]; then
+    DEVICE=$(echo "-s $1")
+else
+    DEVICE=""
+fi
+
+PID=$(adb $DEVICE shell "ps | grep anrd")
+
+if [ $? -ne 0 ]; then
+    echo "FAILED. ADB failed or Daemon is not running."
+    exit 1
+fi
+
+PID=$(echo "$PID" | awk '{ print $2 }')
+adb $DEVICE shell "kill -s SIGUSR1 $PID"
+
+TRACE_FILE=$(adb $DEVICE shell "ls $TRACE_DIR | tail -n1" | tr -d '\r')
+
+# Wiat the trace file generation to complete
+adb $DEVICE shell "lsof $PID" | grep $TRACE_FILE > /dev/null
+while [ $? -eq 0 ];
+do
+    sleep 1
+    adb $DEVICE shell "lsof $PID" | grep "$TRACE_FILE" > /dev/null
+done
+
+if [ -z "$TRACE_FILE" ]; then
+    echo "FAILED. Trace file not created"
+fi
+
+adb $DEVICE pull "${TRACE_DIR}/${TRACE_FILE}" ${TRACE_FILE}
+
+CURRENT_DIR=$(pwd)
+echo SUCCEED!
+echo Trace stored at ${CURRENT_DIR}/${TRACE_FILE}
index 535eb8c..24072e2 100644 (file)
@@ -1,19 +1,19 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
+ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
 
-LOCAL_SRC_FILES:= ANRdaemon.cpp
+LOCAL_PATH:= $(call my-dir)
 
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := ANRdaemon.cpp
 LOCAL_C_INCLUDES += external/zlib
-
-LOCAL_MODULE:= anrdaemon
-
-LOCAL_MODULE_TAGS:= optional
-
+LOCAL_MODULE := anrd
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
 LOCAL_SHARED_LIBRARIES := \
     liblog \
     libbinder \
     libcutils \
     libutils \
-    libz \
-
+    libz
 include $(BUILD_EXECUTABLE)
+
+endif
diff --git a/ANRdaemon/README b/ANRdaemon/README
new file mode 100644 (file)
index 0000000..57ed594
--- /dev/null
@@ -0,0 +1,30 @@
+ANRdaemon is a daemon to help analyze ANR due to CPU starvation by logging system
+activity when CPU usage is very high. The daemon uses debugfs underlying for
+logging. Trace are configured ahead by setting different modules in /d/tracing.
+Depending on the CPU usage level, the trace is turn on/off by writting to the
+global control /d/trace/trace_on. The raw trace file is stored at
+/d/tracing/trace.
+
+The daemon will be started at boot time and will be running with the following
+settings:
+$ ANRdaemon -t 9990 sched gfx am
+This means tracing will be enabled above 99.90% CPU utilization and will trace
+sched, gfx and am modules (See -h for more info).
+
+Use ANRdaemon_get_trace.sh [device serial] to dump and fetch the compressed trace file.
+
+The compressed trace file can be parsed using systrace:
+$ systrace.py --from-file=<path to compressed trace file>
+
+Known issue: in the systrace output, anrdaemon will show up when the trace is
+not running. This is because the daemon process turns off tracing when CPU usage
+drops, the last entry it leaves in the raw trace file is the scheduler switched
+from some other process to the daemon. Then sometime later (say 20 secs later),
+when the CPU usage becomes high and the daemon process turn on tracing again,
+the first entry in /d/tracing/trace logged by sched is switching away from the
+daemon process to some other process. Due to this artifact, when the raw trace
+file is parsed by systrace.py, the daemon process is shown as running for the
+whole 20secs (because from systrace's view, the two 20 sec apart sched trace
+entries regarding the daemon process indicates the daemon process ran continuously
+for all 20sec). However, this will not affect the actual captured trace during
+high CPU usage case.
index 3185d55..06b5818 100644 (file)
@@ -4,9 +4,12 @@ include $(CLEAR_VARS)
 LOCAL_CLANG := true
 LOCAL_MODULE := alloc-stress
 LOCAL_CFLAGS += -g -Wall -Werror -std=gnu++11 -Wno-missing-field-initializers -Wno-sign-compare
+ifneq ($(ENABLE_MEM_CGROUPS),)
+    LOCAL_CFLAGS += -DENABLE_MEM_CGROUPS
+endif
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
-LOCAL_SHARED_LIBRARIES := libhardware
+LOCAL_SHARED_LIBRARIES := libhardware libcutils
 LOCAL_SRC_FILES := \
     alloc-stress.cpp
 include $(BUILD_EXECUTABLE)
index 7473ea2..12eecc5 100644 (file)
@@ -1,5 +1,7 @@
+#include <arpa/inet.h>
 #include <iostream>
 #include <chrono>
+#include <cutils/sockets.h>
 #include <hardware/gralloc.h>
 #include <vector>
 #include <tuple>
@@ -9,6 +11,7 @@
 #include <fcntl.h>
 #include <string>
 #include <fstream>
+#include <sys/stat.h>
 #include <sys/wait.h>
 
 using namespace std;
@@ -108,7 +111,6 @@ void createProcess(Pipe pipe, const char *exName, const char *arg)
         char writeFdStr[16];
         snprintf(readFdStr, sizeof(readFdStr), "%d", pipe.getReadFd());
         snprintf(writeFdStr, sizeof(writeFdStr), "%d", pipe.getWriteFd());
-
         execl(exName, exName, "--worker", arg, readFdStr, writeFdStr, nullptr);
         ASSERT_TRUE(0);
     }
@@ -123,22 +125,64 @@ void createProcess(Pipe pipe, const char *exName, const char *arg)
 }
 
 
-void writeToFile(const char * path, const char *data)
-{
-    ofstream file(path);
-    file << data;
-    file.close();
+static void write_oomadj_to_lmkd(int oomadj) {
+    // Connect to lmkd and store our oom_adj
+    int lmk_procprio_cmd[4];
+    int sock;
+    int tries = 10;
+    while ((sock = socket_local_client("lmkd",
+                    ANDROID_SOCKET_NAMESPACE_RESERVED,
+                    SOCK_SEQPACKET)) < 0) {
+        usleep(100000);
+        if (tries-- < 0) break;
+    }
+    if (sock < 0) {
+        cout << "Failed to connect to lmkd, errno " << errno << endl;
+        exit(1);
+    }
+    lmk_procprio_cmd[0] = htonl(1);
+    lmk_procprio_cmd[1] = htonl(getpid());
+    lmk_procprio_cmd[2] = htonl(getuid());
+    lmk_procprio_cmd[3] = htonl(oomadj);
+
+    int written = write(sock, lmk_procprio_cmd, sizeof(lmk_procprio_cmd));
+    cout << "Wrote " << written << " bytes to lmkd control socket." << endl;
 }
 
+#ifdef ENABLE_MEM_CGROUPS
+static void create_memcg() {
+    char buf[256];
+    pid_t pid = getpid();
+    snprintf(buf, sizeof(buf), "/dev/memctl/apps/%u", pid);
+
+    int tasks = mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+    if (tasks < 0) {
+        cout << "Failed to create memory cgroup" << endl;
+        return;
+    }
+    snprintf(buf, sizeof(buf), "/dev/memctl/apps/%u/tasks", pid);
+    tasks = open(buf, O_WRONLY);
+    if (tasks < 0) {
+        cout << "Unable to add process to memory cgroup" << endl;
+        return;
+    }
+    snprintf(buf, sizeof(buf), "%u", pid);
+    write(tasks, buf, strlen(buf));
+    close(tasks);
+}
+#endif
+
 size_t s = 4 * (1 << 20);
 void *gptr;
 int main(int argc, char *argv[])
 {
     if ((argc > 1) && (std::string(argv[1]) == "--worker")) {
+#ifdef ENABLE_MEM_CGROUPS
+        create_memcg();
+#endif
+        write_oomadj_to_lmkd(atoi(argv[2]));
         Pipe p{atoi(argv[3]), atoi(argv[4])};
 
-        writeToFile("/proc/self/oom_adj", argv[2]);
-
         long long allocCount = 0;
         while (1) {
             p.wait();
@@ -155,10 +199,10 @@ int main(int argc, char *argv[])
             allocCount += s;
         }
     } else {
-        writeToFile("/proc/self/oom_adj", "-17");
         cout << "parent:" << argc << endl;
 
-        for (int i = 10; i >= 0; i--) {
+        write_oomadj_to_lmkd(-1000);
+        for (int i = 1000; i >= 0; i -= 100) {
             auto pipes = Pipe::createPipePair();
             char arg[16];
             snprintf(arg, sizeof(arg), "%d", i);
index 32d75b2..0042caf 100644 (file)
@@ -267,16 +267,22 @@ static void read_freq_stats(int cpu) {
 
     sprintf(filename, "/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state", cpu);
     file = fopen(filename, "r");
-    if (!file) die("Could not open %s\n", filename);
     for (i = 0; i < new_cpus[cpu].freq_count; i++) {
-        fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq,
+        if (file) {
+            fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq,
                &new_cpus[cpu].freqs[i].time);
+        } else {
+            /* The CPU has been off lined for some reason */
+            new_cpus[cpu].freqs[i].freq = old_cpus[cpu].freqs[i].freq;
+            new_cpus[cpu].freqs[i].time = old_cpus[cpu].freqs[i].time;
+        }
         if (aggregate_freq_stats) {
             new_total_cpu.freqs[i].freq = new_cpus[cpu].freqs[i].freq;
             new_total_cpu.freqs[i].time += new_cpus[cpu].freqs[i].time;
         }
     }
-    fclose(file);
+    if (file)
+        fclose(file);
 }
 
 /*
diff --git a/crypto-perf/Android.mk b/crypto-perf/Android.mk
new file mode 100644 (file)
index 0000000..291aca1
--- /dev/null
@@ -0,0 +1,13 @@
+LOCAL_PATH := $(call my-dir)
+ifeq ($(TARGET_ARCH),arm64)
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS := -O0 -march=armv8-a+crypto
+LOCAL_SRC_FILES := crypto.cpp
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := crypto
+
+include $(BUILD_EXECUTABLE)
+endif
diff --git a/crypto-perf/NOTICE b/crypto-perf/NOTICE
new file mode 100644 (file)
index 0000000..c77f135
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2012, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/crypto-perf/crypto.cpp b/crypto-perf/crypto.cpp
new file mode 100644 (file)
index 0000000..2cd2709
--- /dev/null
@@ -0,0 +1,170 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <ctype.h>
+#define USEC_PER_SEC 1000000ULL
+#define MAX_COUNT 1000000000ULL
+#define NUM_INSTS_GARBAGE 18
+
+// Contains information about benchmark options.
+typedef struct {
+    int cpu_to_lock;
+    int locked_freq;
+} command_data_t;
+
+void usage() {
+    printf("--------------------------------------------------------------------------------\n");
+    printf("Usage:");
+    printf("   crypto [--cpu_to_lock CPU] [--locked_freq FREQ_IN_KHZ]\n\n");
+    printf("!!!!!!Lock the desired core to a desired frequency before invoking this benchmark.\n");
+    printf(
+          "Hint: Set scaling_max_freq=scaling_min_freq=FREQ_IN_KHZ. FREQ_IN_KHZ "
+          "can be obtained from scaling_available_freq\n");
+    printf("--------------------------------------------------------------------------------\n");
+}
+
+int processOptions(int argc, char **argv, command_data_t *cmd_data) {
+    // Initialize the command_flags.
+    cmd_data->cpu_to_lock = 0;
+    cmd_data->locked_freq = 1;
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            int *save_value = NULL;
+            if (strcmp(argv[i], "--cpu_to_lock") == 0) {
+                save_value = &cmd_data->cpu_to_lock;
+           } else if (strcmp(argv[i], "--locked_freq") == 0) {
+                save_value = &cmd_data->locked_freq;
+            } else {
+                printf("Unknown option %s\n", argv[i]);
+                return -1;
+            }
+            if (save_value) {
+                // Checking both characters without a strlen() call should be
+                // safe since as long as the argument exists, one character will
+                // be present (\0). And if the first character is '-', then
+                // there will always be a second character (\0 again).
+                if (i == argc - 1 ||
+                    (argv[i + 1][0] == '-' && !isdigit(argv[i + 1][1]))) {
+                    printf("The option %s requires one argument.\n", argv[i]);
+                    return -1;
+                }
+                *save_value = (int)strtol(argv[++i], NULL, 0);
+            }
+       }
+    }
+    return 0;
+}
+/* Performs encryption on garbage values. In Cortex-A57 r0p1 and later
+ * revisions, pairs of dependent AESE/AESMC and AESD/AESIMC instructions are
+ * higher performance when adjacent, and in the described order below. */
+void garbage_encrypt() {
+    __asm__ __volatile__(
+       "aese  v0.16b, v4.16b ;"
+        "aesmc v0.16b, v0.16b ;"
+        "aese  v1.16b, v4.16b ;"
+        "aesmc v1.16b, v1.16b ;"
+        "aese  v2.16b, v4.16b ;"
+        "aesmc v2.16b, v2.16b ;"
+        "aese  v0.16b, v5.16b ;"
+        "aesmc v0.16b, v0.16b ;"
+        "aese  v1.16b, v5.16b ;"
+        "aesmc v1.16b, v1.16b ;"
+        "aese  v2.16b, v5.16b ;"
+        "aesmc v2.16b, v2.16b ;"
+        "aese  v0.16b, v6.16b ;"
+        "aesmc v0.16b, v0.16b ;"
+        "aese  v1.16b, v6.16b ;"
+        "aesmc v1.16b, v1.16b ;"
+        "aese  v2.16b, v6.16b ;"
+        "aesmc v2.16b, v2.16b ;");
+}
+
+void garbage_decrypt() {
+    __asm__ __volatile__(
+       "aesd  v0.16b, v4.16b ;"
+        "aesimc        v0.16b, v0.16b ;"
+        "aesd  v1.16b, v4.16b ;"
+        "aesimc        v1.16b, v1.16b ;"
+        "aesd  v2.16b, v4.16b ;"
+        "aesimc        v2.16b, v2.16b ;"
+        "aesd  v0.16b, v5.16b ;"
+        "aesimc        v0.16b, v0.16b ;"
+        "aesd  v1.16b, v5.16b ;"
+        "aesimc        v1.16b, v1.16b ;"
+        "aesd  v2.16b, v5.16b ;"
+        "aesimc        v2.16b, v2.16b ;"
+        "aesd  v0.16b, v6.16b ;"
+        "aesimc        v0.16b, v0.16b ;"
+        "aesd  v1.16b, v6.16b ;"
+        "aesimc        v1.16b, v1.16b ;"
+        "aesd  v2.16b, v6.16b ;"
+        "aesimc        v2.16b, v2.16b ;");
+}
+
+
+int main(int argc, char **argv) {
+    usage();
+    command_data_t cmd_data;
+
+    if(processOptions(argc, argv, &cmd_data) == -1) {
+        usage();
+        return -1;
+    }
+    unsigned long long count = 0;
+    struct timeval begin_time, end_time, elapsed_time;
+    cpu_set_t cpuset;
+    CPU_ZERO(&cpuset);
+    CPU_SET(cmd_data.cpu_to_lock, &cpuset);
+    if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
+       perror("sched_setaffinity failed");
+       return false;
+    }
+    gettimeofday(&begin_time, NULL);
+    while (count < MAX_COUNT) {
+      garbage_encrypt();
+      count++;
+    }
+    gettimeofday(&end_time, NULL);
+    timersub(&end_time, &begin_time, &elapsed_time);
+    fprintf(stderr, "encrypt: %llu us\n",
+            elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
+    fprintf(stderr, "encrypt instructions: %llu\n",
+            MAX_COUNT * NUM_INSTS_GARBAGE);
+    fprintf(stderr, "encrypt instructions per second: %f\n",
+            (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+                (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
+    if (cmd_data.locked_freq != 0) {
+       fprintf(stderr, "encrypt instructions per cycle: %f\n",
+               (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+               ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) *
+                1000 * cmd_data.locked_freq));
+    }
+    printf("--------------------------------------------------------------------------------\n");
+
+    count = 0;
+    gettimeofday(&begin_time, NULL);
+    while (count < MAX_COUNT) {
+      garbage_decrypt();
+      count++;
+    }
+    gettimeofday(&end_time, NULL);
+    timersub(&end_time, &begin_time, &elapsed_time);
+    fprintf(stderr, "decrypt: %llu us\n",
+            elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
+    fprintf(stderr, "decrypt instructions: %llu\n",
+            MAX_COUNT * NUM_INSTS_GARBAGE);
+    fprintf(stderr, "decrypt instructions per second: %f\n",
+            (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+                (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
+    if (cmd_data.locked_freq != 0) {
+       fprintf(stderr, "decrypt instructions per cycle: %f\n",
+               (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+               ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) *
+                1000 * cmd_data.locked_freq));
+    }
+    return 0;
+}
index ddad863..a8362b2 100644 (file)
@@ -48,6 +48,13 @@ LOCAL_CFLAGS_darwin := -DHOST
 LOCAL_CFLAGS_linux := -DHOST
 include $(BUILD_HOST_EXECUTABLE)
 
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := blk_alloc_to_base_fs.c
+LOCAL_MODULE := blk_alloc_to_base_fs
+LOCAL_SHARED_LIBRARIES += libcutils
+LOCAL_CFLAGS_darwin := -DHOST
+LOCAL_CFLAGS_linux := -DHOST
+include $(BUILD_HOST_EXECUTABLE)
 
 #
 # -- All host/targets excluding windows
@@ -55,8 +62,7 @@ include $(BUILD_HOST_EXECUTABLE)
 
 libext4_utils_src_files += \
     key_control.cpp \
-    ext4_crypt.cpp \
-    unencrypted_properties.cpp
+    ext4_crypt.cpp
 
 ifneq ($(HOST_OS),windows)
 
@@ -67,6 +73,7 @@ LOCAL_C_INCLUDES += system/core/logwrapper/include
 # Various instances of dereferencing a type-punned pointer in extent.c
 LOCAL_CFLAGS += -fno-strict-aliasing
 LOCAL_SHARED_LIBRARIES := \
+    libbase \
     libcutils \
     libext2_uuid \
     libselinux \
@@ -83,8 +90,11 @@ LOCAL_MODULE := libext4_utils_static
 # Various instances of dereferencing a type-punned pointer in extent.c
 LOCAL_CFLAGS += -fno-strict-aliasing
 LOCAL_STATIC_LIBRARIES := \
+    libbase \
+    liblogwrap \
     libsparse_static \
-    libselinux
+    libselinux \
+    libbase
 include $(BUILD_STATIC_LIBRARY)
 
 
index cca3dc1..497f580 100644 (file)
@@ -5,7 +5,7 @@
  * 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
+ *       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,
 #include <stdio.h>
 #include <stdlib.h>
 
-struct region {
-       u32 block;
-       u32 len;
-       int bg;
-       struct region *next;
-       struct region *prev;
-};
-
-struct block_group_info {
-       u32 first_block;
-       int header_blocks;
-       int data_blocks_used;
-       int has_superblock;
-       u8 *bitmaps;
-       u8 *block_bitmap;
-       u8 *inode_bitmap;
-       u8 *inode_table;
-       u32 free_blocks;
-       u32 first_free_block;
-       u32 free_inodes;
-       u32 first_free_inode;
-       u16 flags;
-       u16 used_dirs;
-};
-
 struct xattr_list_element {
        struct ext4_inode *inode;
        struct ext4_xattr_header *header;
@@ -106,7 +81,7 @@ static void region_list_remove(struct region_list *list, struct region *reg)
        reg->prev = NULL;
 }
 
-static void region_list_append(struct region_list *list, struct region *reg)
+void region_list_append(struct region_list *list, struct region *reg)
 {
        if (list->first == NULL) {
                list->first = reg;
@@ -122,6 +97,20 @@ static void region_list_append(struct region_list *list, struct region *reg)
        reg->next = NULL;
 }
 
+void region_list_merge(struct region_list *list1, struct region_list *list2)
+{
+       if (list1->first == NULL) {
+               list1->first = list2->first;
+               list1->last = list2->last;
+               list1->iter = list2->first;
+               list1->partial_iter = 0;
+               list1->first->prev = NULL;
+       } else {
+               list1->last->next = list2->first;
+               list2->first->prev = list1->last;
+               list1->last = list2->last;
+       }
+}
 #if 0
 static void dump_starting_from(struct region *reg)
 {
@@ -141,15 +130,17 @@ static void dump_region_lists(struct block_allocation *alloc) {
 }
 #endif
 
-void print_blocks(FILE* f, struct block_allocation *alloc)
+void print_blocks(FILE* f, struct block_allocation *alloc, char separator)
 {
        struct region *reg;
+       fputc(' ', f);
        for (reg = alloc->list.first; reg; reg = reg->next) {
                if (reg->len == 1) {
-                       fprintf(f, " %d", reg->block);
+                       fprintf(f, "%d", reg->block);
                } else {
-                       fprintf(f, " %d-%d", reg->block, reg->block + reg->len - 1);
+                       fprintf(f, "%d-%d", reg->block, reg->block + reg->len - 1);
                }
+               fputc(separator, f);
        }
        fputc('\n', f);
 }
@@ -205,50 +196,43 @@ static int bitmap_set_8_bits(u8 *bitmap, u32 bit)
 
 /* Marks a the first num_blocks blocks in a block group as used, and accounts
  for them in the block group free block info. */
-static int reserve_blocks(struct block_group_info *bg, u32 start, u32 num)
+static int reserve_blocks(struct block_group_info *bg, u32 bg_num, u32 start, u32 num)
 {
        unsigned int i = 0;
 
        u32 block = start;
-       if (num > bg->free_blocks)
-               return -1;
-
        for (i = 0; i < num && block % 8 != 0; i++, block++) {
                if (bitmap_set_bit(bg->block_bitmap, block)) {
-                       error("attempted to reserve already reserved block");
+                       error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
                        return -1;
                }
        }
 
        for (; i + 8 <= (num & ~7); i += 8, block += 8) {
                if (bitmap_set_8_bits(bg->block_bitmap, block)) {
-                       error("attempted to reserve already reserved block");
+                       error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
                        return -1;
                }
        }
 
        for (; i < num; i++, block++) {
                if (bitmap_set_bit(bg->block_bitmap, block)) {
-                       error("attempted to reserve already reserved block");
+                       error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
                        return -1;
                }
        }
 
        bg->free_blocks -= num;
-       if (start == bg->first_free_block)
-               bg->first_free_block = start + num;
 
        return 0;
 }
 
-static void free_blocks(struct block_group_info *bg, u32 num_blocks)
+static void free_blocks(struct block_group_info *bg, u32 block, u32 num_blocks)
 {
        unsigned int i;
-       u32 block = bg->first_free_block - 1;
        for (i = 0; i < num_blocks; i++, block--)
                bg->block_bitmap[block / 8] &= ~(1 << (block % 8));
        bg->free_blocks += num_blocks;
-       bg->first_free_block -= num_blocks;
 }
 
 /* Reduces an existing allocation by len blocks by return the last blocks
@@ -258,14 +242,15 @@ void reduce_allocation(struct block_allocation *alloc, u32 len)
 {
        while (len) {
                struct region *last_reg = alloc->list.last;
+               struct block_group_info *bg = &aux_info.bgs[last_reg->bg];
 
                if (last_reg->len > len) {
-                       free_blocks(&aux_info.bgs[last_reg->bg], len);
+                       free_blocks(bg, last_reg->block + last_reg->len - bg->first_block - 1, len);
                        last_reg->len -= len;
                        len = 0;
                } else {
                        struct region *reg = alloc->list.last->prev;
-                       free_blocks(&aux_info.bgs[last_reg->bg], last_reg->len);
+                       free_blocks(bg, last_reg->block + last_reg->len - bg->first_block - 1, last_reg->len);
                        len -= last_reg->len;
                        if (reg) {
                                reg->next = NULL;
@@ -304,18 +289,28 @@ static void init_bg(struct block_group_info *bg, unsigned int i)
 
        bg->data_blocks_used = 0;
        bg->free_blocks = info.blocks_per_group;
-       bg->first_free_block = 0;
        bg->free_inodes = info.inodes_per_group;
        bg->first_free_inode = 1;
        bg->flags = EXT4_BG_INODE_UNINIT;
 
-       if (reserve_blocks(bg, bg->first_free_block, bg->header_blocks) < 0)
+       bg->chunk_count = 0;
+       bg->max_chunk_count = 1;
+       bg->chunks = (struct region*) calloc(bg->max_chunk_count, sizeof(struct region));
+
+       if (reserve_blocks(bg, i, 0, bg->header_blocks) < 0)
                error("failed to reserve %u blocks in block group %u\n", bg->header_blocks, i);
+       // Add empty starting delimiter chunk
+       reserve_bg_chunk(i, bg->header_blocks, 0);
 
        if (bg->first_block + info.blocks_per_group > aux_info.len_blocks) {
                u32 overrun = bg->first_block + info.blocks_per_group - aux_info.len_blocks;
-               reserve_blocks(bg, info.blocks_per_group - overrun, overrun);
+               reserve_blocks(bg, i, info.blocks_per_group - overrun, overrun);
+               // Add empty ending delimiter chunk
+               reserve_bg_chunk(i, info.blocks_per_group - overrun, 0);
+       } else {
+               reserve_bg_chunk(i, info.blocks_per_group - 1, 0);
        }
+
 }
 
 void block_allocator_init()
@@ -341,73 +336,80 @@ void block_allocator_free()
        free(aux_info.bgs);
 }
 
-static u32 ext4_allocate_blocks_from_block_group(u32 len, int bg_num)
-{
-       if (get_free_blocks(bg_num) < len)
-               return EXT4_ALLOCATE_FAILED;
-
-       u32 block = aux_info.bgs[bg_num].first_free_block;
-       struct block_group_info *bg = &aux_info.bgs[bg_num];
-       if (reserve_blocks(bg, bg->first_free_block, len) < 0) {
-               error("failed to reserve %u blocks in block group %u\n", len, bg_num);
-               return EXT4_ALLOCATE_FAILED;
-       }
-
-       aux_info.bgs[bg_num].data_blocks_used += len;
-
-       return bg->first_block + block;
-}
-
 /* Allocate a single block and return its block number */
 u32 allocate_block()
 {
-       unsigned int i;
-       for (i = 0; i < aux_info.groups; i++) {
-               u32 block = ext4_allocate_blocks_from_block_group(1, i);
-
-               if (block != EXT4_ALLOCATE_FAILED)
-                       return block;
+       u32 block;
+       struct block_allocation *blk_alloc = allocate_blocks(1);
+       if (!blk_alloc) {
+               return EXT4_ALLOCATE_FAILED;
        }
-
-       return EXT4_ALLOCATE_FAILED;
+       block = blk_alloc->list.first->block;
+       free_alloc(blk_alloc);
+       return block;
 }
 
 static struct region *ext4_allocate_best_fit_partial(u32 len)
 {
-       unsigned int i;
-       unsigned int found_bg = 0;
-       u32 found_bg_len = 0;
+       unsigned int i, j;
+       unsigned int found_bg = 0, found_prev_chunk = 0, found_block = 0;
+       u32 found_allocate_len = 0;
+       bool minimize = false;
+       struct block_group_info *bgs = aux_info.bgs;
+       struct region *reg;
 
        for (i = 0; i < aux_info.groups; i++) {
-               u32 bg_len = aux_info.bgs[i].free_blocks;
-
-               if ((len <= bg_len && (found_bg_len == 0 || bg_len < found_bg_len)) ||
-                   (len > found_bg_len && bg_len > found_bg_len)) {
-                       found_bg = i;
-                       found_bg_len = bg_len;
+               for (j = 1; j < bgs[i].chunk_count; j++) {
+                       u32 hole_start, hole_size;
+                       hole_start = bgs[i].chunks[j-1].block + bgs[i].chunks[j-1].len;
+                       hole_size =  bgs[i].chunks[j].block - hole_start;
+                       if (hole_size == len) {
+                               // Perfect fit i.e. right between 2 chunks no need to keep searching
+                               found_bg = i;
+                               found_prev_chunk = j - 1;
+                               found_block = hole_start;
+                               found_allocate_len = hole_size;
+                               goto done;
+                       } else if (hole_size > len && (found_allocate_len == 0 || (found_allocate_len > hole_size))) {
+                               found_bg = i;
+                               found_prev_chunk = j - 1;
+                               found_block = hole_start;
+                               found_allocate_len = hole_size;
+                               minimize = true;
+                       } else if (!minimize) {
+                               if (found_allocate_len < hole_size) {
+                                       found_bg = i;
+                                       found_prev_chunk = j - 1;
+                                       found_block = hole_start;
+                                       found_allocate_len = hole_size;
+                               }
+                       }
                }
        }
 
-       if (found_bg_len) {
-               u32 allocate_len = min(len, found_bg_len);
-               struct region *reg;
-               u32 block = ext4_allocate_blocks_from_block_group(allocate_len, found_bg);
-               if (block == EXT4_ALLOCATE_FAILED) {
-                       error("failed to allocate %d blocks in block group %d", allocate_len, found_bg);
-                       return NULL;
-               }
-               reg = malloc(sizeof(struct region));
-               reg->block = block;
-               reg->len = allocate_len;
-               reg->next = NULL;
-               reg->prev = NULL;
-               reg->bg = found_bg;
-               return reg;
-       } else {
+       if (found_allocate_len == 0) {
                error("failed to allocate %u blocks, out of space?", len);
+               return NULL;
        }
-
-       return NULL;
+       if (found_allocate_len > len) found_allocate_len = len;
+done:
+       // reclaim allocated space in chunk
+       bgs[found_bg].chunks[found_prev_chunk].len += found_allocate_len;
+       if (reserve_blocks(&bgs[found_bg],
+                               found_bg,
+                               found_block,
+                               found_allocate_len) < 0) {
+               error("failed to reserve %u blocks in block group %u\n", found_allocate_len, found_bg);
+               return NULL;
+       }
+       bgs[found_bg].data_blocks_used += found_allocate_len;
+       reg = malloc(sizeof(struct region));
+       reg->block = found_block + bgs[found_bg].first_block;
+       reg->len = found_allocate_len;
+       reg->next = NULL;
+       reg->prev = NULL;
+       reg->bg = found_bg;
+       return reg;
 }
 
 static struct region *ext4_allocate_best_fit(u32 len)
@@ -439,9 +441,9 @@ static struct region *ext4_allocate_best_fit(u32 len)
 /* Allocate len blocks.  The blocks may be spread across multiple block groups,
    and are returned in a linked list of the blocks in each block group.  The
    allocation algorithm is:
-      1.  If the remaining allocation is larger than any available contiguous region,
-          allocate the largest contiguous region and loop
-      2.  Otherwise, allocate the smallest contiguous region that it fits in
+         1.  If the remaining allocation is larger than any available contiguous region,
+                 allocate the largest contiguous region and loop
+         2.  Otherwise, allocate the smallest contiguous region that it fits in
 */
 struct block_allocation *allocate_blocks(u32 len)
 {
@@ -452,6 +454,8 @@ struct block_allocation *allocate_blocks(u32 len)
 
        struct block_allocation *alloc = create_allocation();
        alloc->list.first = reg;
+       while (reg->next != NULL)
+               reg = reg->next;
        alloc->list.last = reg;
        alloc->list.iter = alloc->list.first;
        alloc->list.partial_iter = 0;
@@ -779,3 +783,35 @@ void free_alloc(struct block_allocation *alloc)
 
        free(alloc);
 }
+
+void reserve_bg_chunk(int bg, u32 start_block, u32 size) {
+       struct block_group_info *bgs = aux_info.bgs;
+       int chunk_count;
+       if (bgs[bg].chunk_count == bgs[bg].max_chunk_count) {
+               bgs[bg].max_chunk_count *= 2;
+               bgs[bg].chunks = realloc(bgs[bg].chunks, bgs[bg].max_chunk_count * sizeof(struct region));
+               if (!bgs[bg].chunks)
+                       critical_error("realloc failed");
+       }
+       chunk_count = bgs[bg].chunk_count;
+       bgs[bg].chunks[chunk_count].block = start_block;
+       bgs[bg].chunks[chunk_count].len = size;
+       bgs[bg].chunks[chunk_count].bg = bg;
+       bgs[bg].chunk_count++;
+}
+
+int reserve_blocks_for_allocation(struct block_allocation *alloc) {
+       struct region *reg;
+       struct block_group_info *bgs = aux_info.bgs;
+
+       if (!alloc) return 0;
+       reg = alloc->list.first;
+       while (reg != NULL) {
+               if (reserve_blocks(&bgs[reg->bg], reg->bg, reg->block - bgs[reg->bg].first_block, reg->len) < 0) {
+                       return -1;
+               }
+               reg = reg->next;
+       }
+       return 0;
+}
+
index 5c26792..4a733d0 100644 (file)
@@ -5,7 +5,7 @@
  * 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
+ *       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,
 
 #include "ext4_utils.h"
 
-struct region;
+struct region {
+       u32 block;
+       u32 len;
+       int bg;
+       struct region *next;
+       struct region *prev;
+};
 
 struct region_list {
        struct region *first;
@@ -37,6 +43,24 @@ struct block_allocation {
        struct block_allocation* next;
 };
 
+struct block_group_info {
+       u32 first_block;
+       int header_blocks;
+       int data_blocks_used;
+       int has_superblock;
+       u8 *bitmaps;
+       u8 *block_bitmap;
+       u8 *inode_bitmap;
+       u8 *inode_table;
+       u32 free_blocks;
+       u32 free_inodes;
+       u32 first_free_inode;
+       u16 flags;
+       u16 used_dirs;
+       int chunk_count;
+       int max_chunk_count;
+       struct region *chunks;
+};
 
 void block_allocator_init();
 void block_allocator_free();
@@ -69,6 +93,9 @@ void append_region(struct block_allocation *alloc,
        u32 block, u32 len, int bg);
 struct block_allocation *create_allocation();
 int append_oob_allocation(struct block_allocation *alloc, u32 len);
-void print_blocks(FILE* f, struct block_allocation *alloc);
-
+void region_list_append(struct region_list *list, struct region *reg);
+void region_list_merge(struct region_list *list1, struct region_list *list2);
+void print_blocks(FILE* f, struct block_allocation *alloc, char separator);
+void reserve_bg_chunk(int bg, u32 start_block, u32 size);
+int reserve_blocks_for_allocation(struct block_allocation *alloc);
 #endif
diff --git a/ext4_utils/blk_alloc_to_base_fs.c b/ext4_utils/blk_alloc_to_base_fs.c
new file mode 100644 (file)
index 0000000..1761fda
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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 <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_PATH 4096
+#define MAX_FILE_VERSION 100
+
+static void usage(char *filename)
+{
+    fprintf(stderr, "Usage: %s input_blk_alloc_file output_base_fs_file \n", filename);
+}
+
+int main(int argc, char **argv)
+{
+    FILE *blk_alloc_file = NULL, *base_fs_file = NULL;
+    char filename[MAX_PATH], file_version[MAX_FILE_VERSION], *spaced_allocs = NULL;
+    size_t spaced_allocs_len = 0;
+
+    if (argc != 3) {
+        usage(argv[0]);
+        exit(EXIT_FAILURE);
+    }
+    blk_alloc_file = fopen(argv[1], "r");
+    if (blk_alloc_file == NULL) {
+        fprintf(stderr, "failed to open %s: %s\n", argv[1], strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    base_fs_file = fopen(argv[2], "w");
+    if (base_fs_file == NULL) {
+        fprintf(stderr, "failed to open %s: %s\n", argv[2], strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    if (fscanf(blk_alloc_file, "Base EXT4 version %s", file_version) > 0) {
+        char c;
+        printf("%s is already in *.base_fs format, just copying into %s...\n", argv[1], argv[2]);
+        rewind(blk_alloc_file);
+        while ((c = fgetc(blk_alloc_file)) != EOF) {
+            fputc(c, base_fs_file);
+        }
+        return 0;
+    } else {
+        printf("Converting %s into *.base_fs format as %s...\n", argv[1], argv[2]);
+        rewind(blk_alloc_file);
+    }
+    fprintf(base_fs_file, "Base EXT4 version 1.0\n");
+    while(fscanf(blk_alloc_file, "%s ", filename) != EOF) {
+        int i;
+        fprintf(base_fs_file, "%s ", filename);
+        if (getline(&spaced_allocs, &spaced_allocs_len, blk_alloc_file) == -1) {
+            fprintf(stderr, "Bad blk_alloc format\n");
+            exit(EXIT_FAILURE);
+        }
+        for (i = 0; spaced_allocs[i]; i++) {
+            if (spaced_allocs[i] == ' ') {
+                if (!isspace(spaced_allocs[i + 1])) fputc(',', base_fs_file);
+            } else fputc(spaced_allocs[i], base_fs_file);
+        }
+    }
+    free(spaced_allocs);
+    fclose(blk_alloc_file);
+    fclose(base_fs_file);
+    return 0;
+}
index 886d17a..be77b79 100644 (file)
@@ -1,10 +1,20 @@
 /*
- * Copyright (c) 2015 Google, Inc.
+ * Copyright (C) 2015 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.
  */
 
-#define TAG "ext4_utils"
-
-#include "ext4_crypt_init_extensions.h"
+#include "ext4_crypt.h"
 
 #include <dirent.h>
 #include <errno.h>
@@ -17,9 +27,8 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <cutils/klog.h>
-
-#include "unencrypted_properties.h"
+#include <android-base/logging.h>
+#include <cutils/properties.h>
 
 #define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy"
 #define EXT4_KEYREF_DELIMITER ((char)'.')
@@ -27,6 +36,8 @@
 // ext4enc:TODO Include structure from somewhere sensible
 // MUST be in sync with ext4_crypto.c in kernel
 #define EXT4_KEY_DESCRIPTOR_SIZE 8
+#define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17
+
 struct ext4_encryption_policy {
     char version;
     char contents_encryption_mode;
@@ -39,93 +50,148 @@ struct ext4_encryption_policy {
 #define EXT4_ENCRYPTION_MODE_AES_256_CTS    4
 
 // ext4enc:TODO Get value from somewhere sensible
-#define EXT4_IOC_SET_ENCRYPTION_POLICY \
-    _IOR('f', 19, struct ext4_encryption_policy)
+#define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy)
+#define EXT4_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct ext4_encryption_policy)
 
-/* Validate that all path items are available and accessible. */
-static int is_path_valid(const char *path)
-{
-    if (access(path, W_OK)) {
-        KLOG_ERROR(TAG, "Can't access %s: %s\n",strerror(errno), path);
-        return 0;
-    }
+#define HEX_LOOKUP "0123456789abcdef"
 
-    return 1;
+bool e4crypt_is_native() {
+    char value[PROPERTY_VALUE_MAX];
+    property_get("ro.crypto.type", value, "none");
+    return !strcmp(value, "file");
 }
 
-static int is_dir_empty(const char *dirname)
+static void policy_to_hex(const char* policy, char* hex) {
+    for (size_t i = 0, j = 0; i < EXT4_KEY_DESCRIPTOR_SIZE; i++) {
+        hex[j++] = HEX_LOOKUP[(policy[i] & 0xF0) >> 4];
+        hex[j++] = HEX_LOOKUP[policy[i] & 0x0F];
+    }
+    hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX - 1] = '\0';
+}
+
+static bool is_dir_empty(const char *dirname, bool *is_empty)
 {
     int n = 0;
-    struct dirent *d;
-    DIR *dir;
-
-    dir = opendir(dirname);
-    while ((d = readdir(dir)) != NULL) {
-        if (strcmp(d->d_name, "lost+found") == 0) {
-            // Skip lost+found directory
-        } else if (++n > 2) {
+    auto dirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(dirname), closedir);
+    if (!dirp) {
+        PLOG(ERROR) << "Unable to read directory: " << dirname;
+        return false;
+    }
+    for (;;) {
+        errno = 0;
+        auto entry = readdir(dirp.get());
+        if (!entry) {
+            if (errno) {
+                PLOG(ERROR) << "Unable to read directory: " << dirname;
+                return false;
+            }
             break;
         }
+        if (strcmp(entry->d_name, "lost+found") != 0) { // Skip lost+found
+            ++n;
+            if (n > 2) {
+                *is_empty = false;
+                return true;
+            }
+        }
     }
-    closedir(dir);
-    return n <= 2;
+    *is_empty = true;
+    return true;
 }
 
-int do_policy_set(const char *directory, const char *policy, int policy_length)
-{
-    struct stat st;
-    ssize_t ret;
-
+static bool e4crypt_policy_set(const char *directory, const char *policy, size_t policy_length) {
     if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
-        KLOG_ERROR("Policy wrong length\n");
-        return -EINVAL;
+        LOG(ERROR) << "Policy wrong length: " << policy_length;
+        return false;
     }
-
-    if (!is_path_valid(directory)) {
-        return -EINVAL;
+    int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+    if (fd == -1) {
+        PLOG(ERROR) << "Failed to open directory " << directory;
+        return false;
     }
 
-    stat(directory, &st);
-    if (!S_ISDIR(st.st_mode)) {
-        KLOG_ERROR(TAG, "Can only set policy on a directory (%s)\n", directory);
-        return -EINVAL;
+    ext4_encryption_policy eep;
+    eep.version = 0;
+    eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
+    eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
+    eep.flags = 0;
+    memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
+    if (ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep)) {
+        PLOG(ERROR) << "Failed to set encryption policy for " << directory;
+        close(fd);
+        return false;
     }
+    close(fd);
+
+    char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+    policy_to_hex(policy, policy_hex);
+    LOG(INFO) << "Policy for " << directory << " set to " << policy_hex;
+    return true;
+}
 
-    if (!is_dir_empty(directory)) {
-        KLOG_ERROR(TAG, "Can only set policy on an empty directory (%s)\n",
-                   directory);
-        return -EINVAL;
+static bool e4crypt_policy_get(const char *directory, char *policy, size_t policy_length) {
+    if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
+        LOG(ERROR) << "Policy wrong length: " << policy_length;
+        return false;
     }
 
-    int fd = open(directory, O_DIRECTORY);
+    int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
     if (fd == -1) {
-        KLOG_ERROR(TAG, "Failed to open directory (%s)\n", directory);
-        return -EINVAL;
+        PLOG(ERROR) << "Failed to open directory " << directory;
+        return false;
     }
 
     ext4_encryption_policy eep;
-    eep.version = 0;
-    eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
-    eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
-    eep.flags = 0;
-    memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
-    ret = ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep);
-    auto preserve_errno = errno;
+    memset(&eep, 0, sizeof(ext4_encryption_policy));
+    if (ioctl(fd, EXT4_IOC_GET_ENCRYPTION_POLICY, &eep) != 0) {
+        PLOG(ERROR) << "Failed to get encryption policy for " << directory;
+        close(fd);
+        return -1;
+    }
     close(fd);
 
-    if (ret) {
-        KLOG_ERROR(TAG, "Failed to set encryption policy for %s: %s\n",
-                   directory, strerror(preserve_errno));
-        return -EINVAL;
+    if ((eep.version != 0)
+            || (eep.contents_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_XTS)
+            || (eep.filenames_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_CTS)
+            || (eep.flags != 0)) {
+        LOG(ERROR) << "Failed to find matching encryption policy for " << directory;
+        return false;
     }
+    memcpy(policy, eep.master_key_descriptor, EXT4_KEY_DESCRIPTOR_SIZE);
 
-    KLOG_INFO(TAG, "Encryption policy for %s is set to %02x%02x%02x%02x\n",
-              directory, policy[0], policy[1], policy[2], policy[3]);
-    return 0;
+    return true;
 }
 
-bool e4crypt_non_default_key(const char* dir)
-{
-    UnencryptedProperties props(dir);
-    return props.Get<int>(properties::is_default, 1) != 1;
+static bool e4crypt_policy_check(const char *directory, const char *policy, size_t policy_length) {
+    if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
+        LOG(ERROR) << "Policy wrong length: " << policy_length;
+        return false;
+    }
+    char existing_policy[EXT4_KEY_DESCRIPTOR_SIZE];
+    if (!e4crypt_policy_get(directory, existing_policy, EXT4_KEY_DESCRIPTOR_SIZE)) return false;
+    char existing_policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+
+    policy_to_hex(existing_policy, existing_policy_hex);
+
+    if (memcmp(policy, existing_policy, EXT4_KEY_DESCRIPTOR_SIZE) != 0) {
+        char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+        policy_to_hex(policy, policy_hex);
+        LOG(ERROR) << "Found policy " << existing_policy_hex << " at " << directory
+                   << " which doesn't match expected value " << policy_hex;
+        return false;
+    }
+    LOG(INFO) << "Found policy " << existing_policy_hex << " at " << directory
+              << " which matches expected value";
+    return true;
+}
+
+int e4crypt_policy_ensure(const char *directory, const char *policy, size_t policy_length) {
+    bool is_empty;
+    if (!is_dir_empty(directory, &is_empty)) return -1;
+    if (is_empty) {
+        if (!e4crypt_policy_set(directory, policy, policy_length)) return -1;
+    } else {
+        if (!e4crypt_policy_check(directory, policy, policy_length)) return -1;
+    }
+    return 0;
 }
diff --git a/ext4_utils/ext4_crypt.h b/ext4_utils/ext4_crypt.h
new file mode 100644 (file)
index 0000000..ddc09a7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 <sys/cdefs.h>
+#include <stdbool.h>
+#include <cutils/multiuser.h>
+
+__BEGIN_DECLS
+
+bool e4crypt_is_native();
+
+int e4crypt_policy_ensure(const char *directory, const char* policy, size_t policy_length);
+
+static const char* e4crypt_unencrypted_folder = "/unencrypted";
+static const char* e4crypt_key_ref = "/unencrypted/ref";
+
+__END_DECLS
index b8ba51d..c6baea7 100644 (file)
@@ -1,8 +1,28 @@
+/*
+ * 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.
+ */
+
 #define TAG "ext4_utils"
 
 #include "ext4_crypt_init_extensions.h"
+#include "ext4_crypt.h"
+
+#include <android-base/logging.h>
 
 #include <string>
+#include <vector>
 
 #include <dirent.h>
 #include <errno.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
+
 #include <cutils/klog.h>
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
-#include <poll.h>
+#include <logwrap/logwrap.h>
 
 #include "key_control.h"
-#include "unencrypted_properties.h"
 
 static const std::string arbitrary_sequence_number = "42";
 static const int vold_command_timeout_ms = 60 * 1000;
 
-static std::string vold_command(std::string const& command)
-{
-    KLOG_INFO(TAG, "Running command %s\n", command.c_str());
-    int sock = -1;
-
-    while (true) {
-        sock = socket_local_client("cryptd",
-                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_STREAM);
-        if (sock >= 0) {
-            break;
-        }
-        usleep(10000);
-    }
-
-    if (sock < 0) {
-        KLOG_INFO(TAG, "Cannot open vold, failing command (%s)\n", strerror(errno));
-        return "";
-    }
-
-    class CloseSocket
-    {
-        int sock_;
-    public:
-        explicit CloseSocket(int sock) : sock_(sock) {}
-        ~CloseSocket() { close(sock_); }
-    };
-
-    CloseSocket cs(sock);
-
-    // Use arbitrary sequence number. This should only be used when the
-    // framework is down, so this is (mostly) OK.
-    std::string actual_command = arbitrary_sequence_number + " " + command;
-    if (write(sock, actual_command.c_str(), actual_command.size() + 1) < 0) {
-        KLOG_ERROR(TAG, "Cannot write command (%s)\n", strerror(errno));
-        return "";
-    }
-
-    struct pollfd poll_sock = {sock, POLLIN, 0};
-
-    int rc = TEMP_FAILURE_RETRY(poll(&poll_sock, 1, vold_command_timeout_ms));
-    if (rc < 0) {
-        KLOG_ERROR(TAG, "Error in poll (%s)\n", strerror(errno));
-        return "";
-    }
-
-    if (!(poll_sock.revents & POLLIN)) {
-        KLOG_ERROR(TAG, "Timeout\n");
-        return "";
-    }
-    char buffer[4096];
-    memset(buffer, 0, sizeof(buffer));
-    rc = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
-    if (rc <= 0) {
-        if (rc == 0) {
-            KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
-        } else {
-            KLOG_ERROR(TAG, "Error reading data (%s)\n", strerror(errno));
-        }
-        return "";
+static void kernel_logger(android::base::LogId, android::base::LogSeverity severity, const char*,
+        const char*, unsigned int, const char* message) {
+    if (severity == android::base::ERROR || severity == android::base::FATAL) {
+        KLOG_ERROR(TAG, "%s\n", message);
+    } else if (severity == android::base::WARNING) {
+        KLOG_WARNING(TAG, "%s\n", message);
+    } else {
+        KLOG_INFO(TAG, "%s\n", message);
     }
+}
 
-    // We don't truly know that this is the correct result. However,
-    // since this will only be used when the framework is down,
-    // it should be OK unless someone is running vdc at the same time.
-    // Worst case we force a reboot in the very rare synchronization
-    // error
-    return std::string(buffer, rc);
+static void init_logging() {
+    android::base::SetLogger(kernel_logger);
 }
 
 int e4crypt_create_device_key(const char* dir,
                               int ensure_dir_exists(const char*))
 {
-    // Already encrypted with password? If so bail
-    std::string temp_folder = std::string() + dir + "/tmp_mnt";
-    DIR* temp_dir = opendir(temp_folder.c_str());
-    if (temp_dir) {
-        closedir(temp_dir);
-        return 0;
-    }
+    init_logging();
 
     // Make sure folder exists. Use make_dir to set selinux permissions.
-    if (ensure_dir_exists(UnencryptedProperties::GetPath(dir).c_str())) {
+    std::string unencrypted_dir = std::string(dir) + e4crypt_unencrypted_folder;
+    if (ensure_dir_exists(unencrypted_dir.c_str())) {
         KLOG_ERROR(TAG, "Failed to create %s (%s)\n",
-                   UnencryptedProperties::GetPath(dir).c_str(),
+                   unencrypted_dir.c_str(),
                    strerror(errno));
         return -1;
     }
 
-    auto result = vold_command("cryptfs enablefilecrypto");
-    // ext4enc:TODO proper error handling
-    KLOG_INFO(TAG, "enablefilecrypto returned with result %s\n",
-              result.c_str());
-
-    return 0;
+    const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "enablefilecrypto" };
+    int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
+    LOG(INFO) << "enablefilecrypto result: " << rc;
+    return rc;
 }
 
 int e4crypt_install_keyring()
 {
+    init_logging();
+
     key_serial_t device_keyring = add_key("keyring", "e4crypt", 0, 0,
                                           KEY_SPEC_SESSION_KEYRING);
 
@@ -128,14 +89,26 @@ int e4crypt_install_keyring()
         return -1;
     }
 
-    KLOG_INFO(TAG, "Keyring created wth id %d in process %d\n",
+    KLOG_INFO(TAG, "Keyring created with id %d in process %d\n",
               device_keyring, getpid());
 
     return 0;
 }
 
+int e4crypt_do_init_user0()
+{
+    init_logging();
+
+    const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "init_user0" };
+    int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
+    LOG(INFO) << "init_user0 result: " << rc;
+    return rc;
+}
+
 int e4crypt_set_directory_policy(const char* dir)
 {
+    init_logging();
+
     // Only set policy on first level /data directories
     // To make this less restrictive, consider using a policy file.
     // However this is overkill for as long as the policy is simply
@@ -144,25 +117,32 @@ int e4crypt_set_directory_policy(const char* dir)
         return 0;
     }
 
-    // Don't encrypt lost+found - ext4 doesn't like it
-    if (!strcmp(dir, "/data/lost+found")) {
-        return 0;
-    }
-
-    // ext4enc:TODO exclude /data/user with a horrible special case.
-    if (!strcmp(dir, "/data/user")) {
-        return 0;
+    // Special case various directories that must not be encrypted,
+    // often because their subdirectories must be encrypted.
+    // This isn't a nice way to do this, see b/26641735
+    std::vector<std::string> directories_to_exclude = {
+        "lost+found",
+        "system_ce", "system_de",
+        "misc_ce", "misc_de",
+        "media",
+        "data", "user", "user_de",
+    };
+    std::string prefix = "/data/";
+    for (auto d: directories_to_exclude) {
+        if ((prefix + d) == dir) {
+            KLOG_INFO(TAG, "Not setting policy on %s\n", dir);
+            return 0;
+        }
     }
 
-    UnencryptedProperties props("/data");
-    std::string policy = props.Get<std::string>(properties::ref);
-    if (policy.empty()) {
-        // ext4enc:TODO why is this OK?
-        return 0;
+    std::string ref_filename = std::string("/data") + e4crypt_key_ref;
+    std::string policy;
+    if (!android::base::ReadFileToString(ref_filename, &policy)) {
+        KLOG_ERROR(TAG, "Unable to read system policy to set on %s\n", dir);
+        return -1;
     }
-
     KLOG_INFO(TAG, "Setting policy on %s\n", dir);
-    int result = do_policy_set(dir, policy.c_str(), policy.size());
+    int result = e4crypt_policy_ensure(dir, policy.c_str(), policy.size());
     if (result) {
         KLOG_ERROR(TAG, "Setting %02x%02x%02x%02x policy on %s failed!\n",
                    policy[0], policy[1], policy[2], policy[3], dir);
@@ -171,13 +151,3 @@ int e4crypt_set_directory_policy(const char* dir)
 
     return 0;
 }
-
-int e4crypt_set_user_crypto_policies(const char* dir)
-{
-    auto command = std::string() + "cryptfs setusercryptopolicies " + dir;
-    auto result = vold_command(command);
-    // ext4enc:TODO proper error handling
-    KLOG_INFO(TAG, "setusercryptopolicies returned with result %s\n",
-              result.c_str());
-    return 0;
-}
index d02d181..63f6d88 100644 (file)
@@ -1,5 +1,22 @@
+/*
+ * 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 <sys/cdefs.h>
 #include <stdbool.h>
+#include <cutils/multiuser.h>
 
 __BEGIN_DECLS
 
@@ -9,8 +26,6 @@ int e4crypt_install_keyring();
 int e4crypt_create_device_key(const char* path,
                               int ensure_dir_exists(const char* dir));
 int e4crypt_set_directory_policy(const char* path);
-bool e4crypt_non_default_key(const char* path);
-int do_policy_set(const char *directory, const char *policy, int policy_length);
-int e4crypt_set_user_crypto_policies(const char* path);
+int e4crypt_do_init_user0();
 
 __END_DECLS
index 28f650d..fba4f9f 100644 (file)
@@ -49,6 +49,7 @@ int force = 0;
 struct fs_info info;
 struct fs_aux_info aux_info;
 struct sparse_file *ext4_sparse_file;
+struct block_allocation *base_fs_allocations = NULL;
 
 jmp_buf setjmp_env;
 
index 0159dbe..0fbbdd3 100644 (file)
@@ -119,6 +119,7 @@ struct fs_aux_info {
 extern struct fs_info info;
 extern struct fs_aux_info aux_info;
 extern struct sparse_file *ext4_sparse_file;
+extern struct block_allocation *base_fs_allocations;
 
 extern jmp_buf setjmp_env;
 
@@ -161,7 +162,7 @@ int make_ext4fs_internal(int fd, const char *directory, const char *_target_out_
                                                 const char *mountpoint, fs_config_func_t fs_config_func, int gzip,
                                                 int sparse, int crc, int wipe, int real_uuid,
                                                 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
-                                                FILE* block_list_file);
+                                                FILE* block_list_file, FILE* base_alloc_file_in, FILE* base_alloc_file_out);
 
 int read_ext(int fd, int verbose);
 
index 1900b10..7887488 100644 (file)
@@ -5,7 +5,7 @@
  * 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
+ *       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,
@@ -72,23 +72,43 @@ static void extent_create_backing_file(struct block_allocation *alloc,
 }
 
 static struct block_allocation *do_inode_allocate_extents(
-       struct ext4_inode *inode, u64 len)
+       struct ext4_inode *inode, u64 len, struct block_allocation *prealloc)
 {
-       u32 block_len = DIV_ROUND_UP(len, info.block_size);
-       struct block_allocation *alloc = allocate_blocks(block_len + 1);
+       u32 block_len = DIV_ROUND_UP(len, info.block_size), prealloc_block_len;
+       struct block_allocation *alloc;
        u32 extent_block = 0;
        u32 file_block = 0;
        struct ext4_extent *extent;
        u64 blocks;
 
-       if (alloc == NULL) {
-               error("Failed to allocate %d blocks\n", block_len + 1);
-               return NULL;
+       if (!prealloc) {
+               alloc = allocate_blocks(block_len + 1);
+               if (alloc == NULL) {
+                       error("Failed to allocate %d blocks\n", block_len + 1);
+                       return NULL;
+               }
+       } else {
+               prealloc_block_len = block_allocation_len(prealloc);
+               if (block_len + 1 > prealloc_block_len) {
+                       alloc = allocate_blocks(block_len + 1 - prealloc_block_len);
+                       if (alloc == NULL) {
+                               error("Failed to allocate %d blocks\n",
+                                               block_len + 1 - prealloc_block_len);
+                               return NULL;
+                       }
+                       region_list_merge(&prealloc->list, &alloc->list);
+                       free(alloc);
+               }
+               alloc = prealloc;
        }
 
        int allocation_len = block_allocation_num_regions(alloc);
        if (allocation_len <= 3) {
                reduce_allocation(alloc, 1);
+               // IMPORTANT: reduce_allocation may have changed allocation
+               // length, otherwise file corruption happens when fs thinks
+               // a block is missing from extent header.
+               allocation_len = block_allocation_num_regions(alloc);
        } else {
                reserve_oob_blocks(alloc, 1);
                extent_block = get_oob_block(alloc, 0);
@@ -183,7 +203,7 @@ u8 *inode_allocate_data_extents(struct ext4_inode *inode, u64 len,
        struct block_allocation *alloc;
        u8 *data = NULL;
 
-       alloc = do_inode_allocate_extents(inode, len);
+       alloc = do_inode_allocate_extents(inode, len, NULL);
        if (alloc == NULL) {
                error("failed to allocate extents for %"PRIu64" bytes", len);
                return NULL;
@@ -205,9 +225,26 @@ u8 *inode_allocate_data_extents(struct ext4_inode *inode, u64 len,
 struct block_allocation* inode_allocate_file_extents(struct ext4_inode *inode, u64 len,
        const char *filename)
 {
-       struct block_allocation *alloc;
+       struct block_allocation *alloc, *prealloc = base_fs_allocations, *prev_prealloc = NULL;
+       // TODO(mkayyash): base_fs_allocations is sorted by filename, consider
+       // storing it in an array and then binary searching for a filename match instead
+       while (prealloc && prealloc->filename != NULL) {
+               if (!strcmp(filename, prealloc->filename)) {
+                       break;
+               }
+               prev_prealloc = prealloc;
+               prealloc = prealloc->next;
+       }
+       if (prealloc) {
+               if (!prev_prealloc) {
+                       base_fs_allocations = base_fs_allocations->next;
+               } else {
+                       prev_prealloc->next = prealloc->next;
+               }
+               prealloc->next = NULL;
+       }
 
-       alloc = do_inode_allocate_extents(inode, len);
+       alloc = do_inode_allocate_extents(inode, len, prealloc);
        if (alloc == NULL) {
                error("failed to allocate extents for %"PRIu64" bytes", len);
                return NULL;
@@ -222,7 +259,7 @@ void inode_allocate_extents(struct ext4_inode *inode, u64 len)
 {
        struct block_allocation *alloc;
 
-       alloc = do_inode_allocate_extents(inode, len);
+       alloc = do_inode_allocate_extents(inode, len, NULL);
        if (alloc == NULL) {
                error("failed to allocate extents for %"PRIu64" bytes", len);
                return;
index 1ba415b..f45a699 100644 (file)
@@ -5,7 +5,7 @@
  * 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
+ *       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,
 
 #endif
 
+#define MAX_PATH 4096
+#define MAX_BLK_MAPPING_STR 1000
+
+const int blk_file_major_ver = 1;
+const int blk_file_minor_ver = 0;
+const char *blk_file_header_fmt = "Base EXT4 version %d.%d";
+
 /* TODO: Not implemented:
    Allocating blocks in the same block group as the file inode
    Hash or binary tree directories
@@ -91,7 +98,7 @@ static int filter_dot(const struct dirent *d)
 }
 
 static u32 build_default_directory_structure(const char *dir_path,
-                                            struct selabel_handle *sehnd)
+                                                struct selabel_handle *sehnd)
 {
        u32 inode;
        u32 root_inode;
@@ -414,15 +421,31 @@ void reset_ext4fs_info() {
 int make_ext4fs_sparse_fd(int fd, long long len,
                                const char *mountpoint, struct selabel_handle *sehnd)
 {
+       return make_ext4fs_sparse_fd_directory(fd, len, mountpoint, sehnd, NULL);
+}
+
+int make_ext4fs_sparse_fd_directory(int fd, long long len,
+                               const char *mountpoint, struct selabel_handle *sehnd,
+                               const char *directory)
+{
        reset_ext4fs_info();
        info.len = len;
 
-       return make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 1, 0, 0, 0, sehnd, 0, -1, NULL);
+       return make_ext4fs_internal(fd, directory, NULL, mountpoint, NULL,
+                                                               0, 1, 0, 0, 0,
+                                                               sehnd, 0, -1, NULL, NULL, NULL);
 }
 
 int make_ext4fs(const char *filename, long long len,
                                const char *mountpoint, struct selabel_handle *sehnd)
 {
+       return make_ext4fs_directory(filename, len, mountpoint, sehnd, NULL);
+}
+
+int make_ext4fs_directory(const char *filename, long long len,
+                                                 const char *mountpoint, struct selabel_handle *sehnd,
+                                                 const char *directory)
+{
        int fd;
        int status;
 
@@ -435,7 +458,9 @@ int make_ext4fs(const char *filename, long long len,
                return EXIT_FAILURE;
        }
 
-       status = make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 0, 0, 1, 0, sehnd, 0, -1, NULL);
+       status = make_ext4fs_internal(fd, directory, NULL, mountpoint, NULL,
+                                                                 0, 0, 0, 1, 0,
+                                                                 sehnd, 0, -1, NULL, NULL, NULL);
        close(fd);
 
        return status;
@@ -501,17 +526,170 @@ static char *canonicalize_rel_slashes(const char *str)
        return canonicalize_slashes(str, false);
 }
 
+static int compare_chunks(const void* chunk1, const void* chunk2) {
+       struct region* c1 = (struct region*) chunk1;
+       struct region* c2 = (struct region*) chunk2;
+       return c1->block - c2->block;
+}
+
+static int get_block_group(u32 block) {
+       int i, group = 0;
+       for(i = 0; i < aux_info.groups; i++) {
+               if (block >= aux_info.bgs[i].first_block)
+                       group = i;
+               else
+                       break;
+       }
+       return group;
+}
+
+static void extract_base_fs_allocations(const char *directory, const char *mountpoint,
+                                                                               FILE* base_alloc_file_in) {
+#define err_msg "base file badly formatted"
+#ifndef USE_MINGW
+       // FORMAT Version 1.0: filename blk_mapping
+       const char *base_alloc_file_in_format = "%s %s";
+       const int base_file_format_param_count = 2;
+
+       char stored_file_name[MAX_PATH], real_file_name[MAX_PATH], file_map[MAX_BLK_MAPPING_STR];
+       struct block_allocation *fs_alloc;
+       struct block_group_info *bgs = aux_info.bgs;
+       int i, major_version = 0, minor_version = 0;
+       char *base_file_line = NULL;
+       size_t base_file_line_len = 0;
+
+       printf("[v%d.%d] Generating an Incremental EXT4 image\n",
+                       blk_file_major_ver, blk_file_minor_ver);
+       if (base_fs_allocations == NULL)
+               base_fs_allocations = create_allocation();
+       fs_alloc = base_fs_allocations;
+
+       fscanf(base_alloc_file_in, blk_file_header_fmt, &major_version, &minor_version);
+       if (major_version == 0) {
+               critical_error("Invalid base file");
+       }
+
+       if (major_version != blk_file_major_ver) {
+               critical_error("Incompatible base file: version required is %d.X",
+                               blk_file_major_ver);
+       }
+
+       if (minor_version < blk_file_minor_ver) {
+               critical_error("Incompatible base file: version required is %d.%d or above",
+                               blk_file_major_ver, blk_file_minor_ver);
+       }
+
+       while (getline(&base_file_line, &base_file_line_len, base_alloc_file_in) != -1) {
+               if (sscanf(base_file_line, base_alloc_file_in_format, &stored_file_name, &file_map)
+                               != base_file_format_param_count) {
+                       continue;
+               }
+               if (strlen(stored_file_name) < strlen(mountpoint)) {
+                       continue;
+               }
+               snprintf(real_file_name, MAX_PATH, "%s%s", directory, stored_file_name + strlen(mountpoint));
+               if (!access(real_file_name, R_OK)) {
+                       char *block_range, *end_string;
+                       int real_file_fd;
+                       u32 start_block, end_block, block_file_size;
+                       u32 real_file_block_size;
+
+                       real_file_fd = open(real_file_name, O_RDONLY);
+                       if (real_file_fd == -1) {
+                               critical_error(err_msg);
+                       }
+                       real_file_block_size = get_file_size(real_file_fd);
+                       close(real_file_fd);
+                       real_file_block_size = DIV_ROUND_UP(real_file_block_size, info.block_size);
+                       fs_alloc->filename = strdup(real_file_name);
+                       block_range = strtok_r(file_map, ",", &end_string);
+                       while (block_range && real_file_block_size) {
+                               int block_group;
+                               char *range, *end_token = NULL;
+                               range = strtok_r(block_range, "-", &end_token);
+                               if (!range) {
+                                       critical_error(err_msg);
+                               }
+                               start_block = parse_num(range);
+                               range = strtok_r(NULL, "-", &end_token);
+                               if (!range) {
+                                       end_block = start_block;
+                               } else {
+                                       end_block = parse_num(range);
+                               }
+                               // Assummption is that allocations are within the same block group
+                               block_group = get_block_group(start_block);
+                               if (block_group != get_block_group(end_block)) {
+                                       critical_error("base file allocation's end block is in a different "
+                                                                  "block group than start block. did you change fs params?");
+                               }
+                               block_range = strtok_r(NULL, ",", &end_string);
+                               int bg_first_block = bgs[block_group].first_block;
+                               int min_bg_bound = bgs[block_group].chunks[0].block + bgs[block_group].chunks[0].len;
+                               int max_bg_bound = bgs[block_group].chunks[bgs[block_group].chunk_count - 1].block;
+
+                               if (min_bg_bound >= start_block - bg_first_block ||
+                                       max_bg_bound <= end_block - bg_first_block) {
+                                       continue;
+                               }
+                               block_file_size = end_block - start_block + 1;
+                               if (block_file_size > real_file_block_size) {
+                                       block_file_size = real_file_block_size;
+                               }
+                               append_region(fs_alloc, start_block, block_file_size, block_group);
+                               reserve_bg_chunk(block_group, start_block - bgs[block_group].first_block, block_file_size);
+                               real_file_block_size -= block_file_size;
+                       }
+                       if (reserve_blocks_for_allocation(fs_alloc) < 0)
+                               critical_error("failed to reserve base fs allocation");
+                       fs_alloc->next = create_allocation();
+                       fs_alloc = fs_alloc->next;
+               }
+       }
+
+       for (i = 0; i < aux_info.groups; i++) {
+               qsort(bgs[i].chunks, bgs[i].chunk_count, sizeof(struct region), compare_chunks);
+       }
+
+       free(base_file_line);
+
+#else
+    return;
+#endif
+#undef err_msg
+}
+
+void generate_base_alloc_file_out(FILE* base_alloc_file_out, char* dir, char* mountpoint,
+                                                                 struct block_allocation* p)
+{
+       size_t dirlen = dir ? strlen(dir) : 0;
+       fprintf(base_alloc_file_out, blk_file_header_fmt, blk_file_major_ver, blk_file_minor_ver);
+       fputc('\n', base_alloc_file_out);
+       while (p) {
+               if (dir && strncmp(p->filename, dir, dirlen) == 0) {
+                       // substitute mountpoint for the leading directory in the filename, in the output file
+                       fprintf(base_alloc_file_out, "%s%s", mountpoint, p->filename + dirlen);
+               } else {
+                       fprintf(base_alloc_file_out, "%s", p->filename);
+               }
+               print_blocks(base_alloc_file_out, p, ',');
+               struct block_allocation* pn = p->next;
+               p = pn;
+       }
+}
+
 int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out_directory,
                                                 const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
                                                 int sparse, int crc, int wipe, int real_uuid,
                                                 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
-                                                FILE* block_list_file)
+                                                FILE* block_list_file, FILE* base_alloc_file_in, FILE* base_alloc_file_out)
 {
        u32 root_inode_num;
        u16 root_mode;
        char *mountpoint;
        char *directory = NULL;
        char *target_out_directory = NULL;
+       struct block_allocation* p;
 
        if (setjmp(setjmp_env))
                return EXIT_FAILURE; /* Handle a call to longjmp() */
@@ -610,6 +788,9 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
 
        ext4_fill_in_sb(real_uuid);
 
+       if (base_alloc_file_in) {
+               extract_base_fs_allocations(directory, mountpoint, base_alloc_file_in);
+       }
        if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED)
                error("failed to reserve first 10 inodes");
 
@@ -653,6 +834,8 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
 
        ext4_update_free();
 
+       // TODO: Consider migrating the OTA tools to the new base alloc file format
+       // used for generating incremental images (see go/incremental-ext4)
        if (block_list_file) {
                size_t dirlen = directory ? strlen(directory) : 0;
                struct block_allocation* p = get_saved_allocation_chain();
@@ -663,13 +846,17 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
                        } else {
                                fprintf(block_list_file, "%s", p->filename);
                        }
-                       print_blocks(block_list_file, p);
+                       print_blocks(block_list_file, p, ' ');
                        struct block_allocation* pn = p->next;
-                       free_alloc(p);
                        p = pn;
                }
        }
 
+       if (base_alloc_file_out) {
+               struct block_allocation* p = get_saved_allocation_chain();
+               generate_base_alloc_file_out(base_alloc_file_out, directory, mountpoint, p);
+       }
+
        printf("Created filesystem with %d/%d inodes and %d/%d blocks\n",
                        aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count,
                        aux_info.sb->s_inodes_count,
@@ -685,6 +872,13 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
        sparse_file_destroy(ext4_sparse_file);
        ext4_sparse_file = NULL;
 
+       p = get_saved_allocation_chain();
+       while (p) {
+               struct block_allocation* pn = p->next;
+               free_alloc(p);
+               p = pn;
+       }
+
        free(mountpoint);
        free(directory);
 
index 3784a9e..4498e62 100644 (file)
@@ -25,8 +25,14 @@ struct selabel_handle;
 
 int make_ext4fs(const char *filename, long long len,
                 const char *mountpoint, struct selabel_handle *sehnd);
+int make_ext4fs_directory(const char *filename, long long len,
+                const char *mountpoint, struct selabel_handle *sehnd,
+                const char *directory);
 int make_ext4fs_sparse_fd(int fd, long long len,
                 const char *mountpoint, struct selabel_handle *sehnd);
+int make_ext4fs_sparse_fd_directory(int fd, long long len,
+                const char *mountpoint, struct selabel_handle *sehnd,
+                const char *directory);
 
 #ifdef __cplusplus
 }
index 8261f0c..323a445 100644 (file)
@@ -57,6 +57,7 @@ static void usage(char *path)
        fprintf(stderr, "    [ -L <label> ] [ -f ] [ -a <android mountpoint> ] [ -u ]\n");
        fprintf(stderr, "    [ -S file_contexts ] [ -C fs_config ] [ -T timestamp ]\n");
        fprintf(stderr, "    [ -z | -s ] [ -w ] [ -c ] [ -J ] [ -v ] [ -B <block_list_file> ]\n");
+       fprintf(stderr, "    [ -d <base_alloc_file_in> ] [ -D <base_alloc_file_out> ]\n");
        fprintf(stderr, "    <filename> [[<directory>] <target_out_directory>]\n");
 }
 
@@ -80,11 +81,13 @@ int main(int argc, char **argv)
        time_t fixed_time = -1;
        struct selabel_handle *sehnd = NULL;
        FILE* block_list_file = NULL;
+       FILE* base_alloc_file_in = NULL;
+       FILE* base_alloc_file_out = NULL;
 #ifndef USE_MINGW
        struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "" } };
 #endif
 
-       while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:fwzJsctvu")) != -1) {
+       while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:d:D:fwzJsctvu")) != -1) {
                switch (opt) {
                case 'l':
                        info.len = parse_num(optarg);
@@ -166,6 +169,20 @@ int main(int argc, char **argv)
                                exit(EXIT_FAILURE);
                        }
                        break;
+               case 'd':
+                       base_alloc_file_in = fopen(optarg, "r");
+                       if (base_alloc_file_in == NULL) {
+                               fprintf(stderr, "failed to open base_alloc_file_in: %s\n", strerror(errno));
+                               exit(EXIT_FAILURE);
+                       }
+                       break;
+               case 'D':
+                       base_alloc_file_out = fopen(optarg, "w");
+                       if (base_alloc_file_out == NULL) {
+                               fprintf(stderr, "failed to open base_alloc_file_out: %s\n", strerror(errno));
+                               exit(EXIT_FAILURE);
+                       }
+                       break;
                default: /* '?' */
                        usage(argv[0]);
                        exit(EXIT_FAILURE);
@@ -237,10 +254,15 @@ int main(int argc, char **argv)
        }
 
        exitcode = make_ext4fs_internal(fd, directory, target_out_directory, mountpoint, fs_config_func, gzip,
-               sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time, block_list_file);
+               sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time,
+               block_list_file, base_alloc_file_in, base_alloc_file_out);
        close(fd);
        if (block_list_file)
                fclose(block_list_file);
+       if (base_alloc_file_out)
+               fclose(base_alloc_file_out);
+       if (base_alloc_file_in)
+               fclose(base_alloc_file_in);
        if (exitcode && strcmp(filename, "-"))
                unlink(filename);
        return exitcode;
index 8667013..b79baf9 100755 (executable)
@@ -6,7 +6,7 @@ function usage() {
 cat<<EOT
 Usage:
 mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>]
-             [-T TIMESTAMP] [-C FS_CONFIG] [-D PRODUCT_OUT] [-B BLOCK_LIST_FILE] [-L LABEL] [FILE_CONTEXTS]
+             [-T TIMESTAMP] [-C FS_CONFIG] [-D PRODUCT_OUT] [-B BLOCK_LIST_FILE] [-d BASE_ALLOC_FILE_IN ] [-A BASE_ALLOC_FILE_OUT ] [-L LABEL] [FILE_CONTEXTS]
 EOT
 }
 
@@ -67,6 +67,18 @@ if [[ "$1" == "-B" ]]; then
   shift; shift
 fi
 
+BASE_ALLOC_FILE_IN=
+if [[ "$1" == "-d" ]]; then
+  BASE_ALLOC_FILE_IN=$2
+  shift; shift
+fi
+
+BASE_ALLOC_FILE_OUT=
+if [[ "$1" == "-A" ]]; then
+  BASE_ALLOC_FILE_OUT=$2
+  shift; shift
+fi
+
 LABEL=
 if [[ "$1" == "-L" ]]; then
   LABEL=$2
@@ -100,6 +112,12 @@ fi
 if [ -n "$BLOCK_LIST" ]; then
   OPT="$OPT -B $BLOCK_LIST"
 fi
+if [ -n "$BASE_ALLOC_FILE_IN" ]; then
+  OPT="$OPT -d $BASE_ALLOC_FILE_IN"
+fi
+if [ -n "$BASE_ALLOC_FILE_OUT" ]; then
+  OPT="$OPT -D $BASE_ALLOC_FILE_OUT"
+fi
 if [ -n "$LABEL" ]; then
   OPT="$OPT -L $LABEL"
 fi
diff --git a/ext4_utils/unencrypted_properties.cpp b/ext4_utils/unencrypted_properties.cpp
deleted file mode 100644 (file)
index ed36e20..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#include "unencrypted_properties.h"
-
-#include <sys/stat.h>
-#include <dirent.h>
-
-namespace properties {
-    const char* key = "key";
-    const char* ref = "ref";
-    const char* props = "props";
-    const char* is_default = "is_default";
-}
-
-namespace
-{
-    const char* unencrypted_folder = "unencrypted";
-}
-
-std::string UnencryptedProperties::GetPath(const char* device)
-{
-    return std::string() + device + "/" + unencrypted_folder;
-}
-
-UnencryptedProperties::UnencryptedProperties(const char* device)
-  : folder_(GetPath(device))
-{
-    DIR* dir = opendir(folder_.c_str());
-    if (dir) {
-        closedir(dir);
-    } else {
-        folder_.clear();
-    }
-}
-
-UnencryptedProperties::UnencryptedProperties()
-{
-}
-
-template<> std::string UnencryptedProperties::Get(const char* name,
-                                      std::string default_value) const
-{
-    if (!OK()) return default_value;
-    std::ifstream i(folder_ + "/" + name, std::ios::binary);
-    if (!i) {
-        return default_value;
-    }
-
-    i.seekg(0, std::ios::end);
-    int length = i.tellg();
-    i.seekg(0, std::ios::beg);
-    if (length == -1) {
-        return default_value;
-    }
-
-    std::string s(length, 0);
-    i.read(&s[0], length);
-    if (!i) {
-        return default_value;
-    }
-
-    return s;
-}
-
-template<> bool UnencryptedProperties::Set(const char* name, std::string const& value)
-{
-    if (!OK()) return false;
-    std::ofstream o(folder_ + "/" + name, std::ios::binary);
-    o << value;
-    return !o.fail();
-}
-
-UnencryptedProperties UnencryptedProperties::GetChild(const char* name) const
-{
-    UnencryptedProperties up;
-    if (!OK()) return up;
-
-    std::string directory(folder_ + "/" + name);
-    if (mkdir(directory.c_str(), 700) == -1 && errno != EEXIST) {
-        return up;
-    }
-
-    up.folder_ = directory;
-    return up;
-}
-
-bool UnencryptedProperties::Remove(const char* name)
-{
-    if (!OK()) return false;
-    if (remove((folder_ + "/" + name).c_str())
-        && errno != ENOENT) {
-        return false;
-    }
-
-    return true;
-}
-
-bool UnencryptedProperties::OK() const
-{
-    return !folder_.empty();
-}
diff --git a/ext4_utils/unencrypted_properties.h b/ext4_utils/unencrypted_properties.h
deleted file mode 100644 (file)
index 1e41e1d..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-#include <string>
-#include <fstream>
-
-// key names for properties we use
-namespace properties {
-    extern const char* key;
-    extern const char* ref;
-    extern const char* props;
-    extern const char* is_default;
-}
-
-/**
- * Class to store data on the unencrypted folder of a device.
- * Note that the folder must exist before this class is constructed.
- * All names must be valid single level (no '/') file or directory names
- * Data is organized hierarchically so we can get a child folder
- */
-class UnencryptedProperties
-{
-public:
-    // Get path of folder. Must create before using any properties
-    // This is to allow proper setting of SELinux policy
-    static std::string GetPath(const char* device);
-
-    // Opens properties folder on named device.
-    // If folder does not exist, OK will return false, all
-    // getters will return default properties and setters will fail.
-    explicit UnencryptedProperties(const char* device);
-
-    // Get named object. Return default if object does not exist or error.
-    template<typename t> t Get(const char* name, t default_value = t()) const;
-
-    // Set named object. Return true if success, false otherwise
-    template<typename t> bool Set(const char* name, t const& value);
-
-    // Get child properties
-    UnencryptedProperties GetChild(const char* name) const;
-
-    // Remove named object
-    bool Remove(const char* name);
-
-    // Does folder exist?
-    bool OK() const;
-
-private:
-    UnencryptedProperties();
-    std::string folder_;
-};
-
-
-template<typename t> t UnencryptedProperties::Get(const char* name,
-                                                  t default_value) const
-{
-    if (!OK()) return default_value;
-    t value = default_value;
-    std::ifstream(folder_ + "/" + name) >> value;
-    return value;
-}
-
-template<typename t> bool UnencryptedProperties::Set(const char* name,
-                                                     t const& value)
-{
-    if (!OK()) return false;
-    std::ofstream o(folder_ + "/" + name);
-    o << value;
-    return !o.fail();
-}
-
-// Specialized getters/setters for strings
-template<> std::string UnencryptedProperties::Get(const char* name,
-                                      std::string default_value) const;
-
-template<> bool UnencryptedProperties::Set(const char* name,
-                                           std::string const& value);
index 3c6f489..bf6e00e 100644 (file)
 #
 
 LOCAL_PATH:= $(call my-dir)
-
 include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES :=  mmapPerf.cpp
 LOCAL_MODULE := mmapPerf
-LOCAL_SRC_FILES_64 := mmapPerf.cpp
-LOCAL_SRC_FILES_32 := unsupported.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
 LOCAL_CLANG := true
 LOCAL_CFLAGS += -g -Wall -Werror -std=c++11 -Wno-missing-field-initializers -Wno-sign-compare -O3
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_CXX_STL := libc++_static
 LOCAL_STATIC_LIBRARIES := libc
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_EXECUTABLE)
+include $(BUILD_NATIVE_BENCHMARK)
index bbc83c7..d195850 100644 (file)
@@ -1,3 +1,4 @@
+#include "benchmark/benchmark_api.h"
 #include <string>
 #include <cstring>
 #include <cstdlib>
@@ -12,7 +13,9 @@
 #include <sys/mman.h>
 
 using namespace std;
-static const size_t pageSize = 4096;
+static const size_t pageSize = PAGE_SIZE;
+static size_t fsize = 1024 * (1ull << 20);
+static size_t pagesTotal = fsize / pageSize;
 
 class Fd {
     int m_fd = -1;
@@ -54,17 +57,16 @@ public:
     FileMap(const string &name, size_t size, Hint hint = FILE_MAP_HINT_NONE) : m_name{name}, m_size{size} {
         int fd = open(name.c_str(), O_CREAT | O_RDWR, S_IRWXU);
         if (fd < 0) {
-            cerr << "open failed: " << fd << endl;
-            return;
+            cout << "Error: open failed for " << name << ": " << strerror(errno) << endl;
+            exit(1);
         }
         m_fileFd.set(fd);
         fallocate(m_fileFd.get(), 0, 0, size);
         unlink(name.c_str());
         m_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileFd.get(), 0);
         if ((int)(uintptr_t)m_ptr == -1) {
-            cerr << "mmap failed: " << (int)(uintptr_t)m_ptr << endl;
-            m_ptr = nullptr;
-            return;
+            cout << "Error: mmap failed: " << (int)(uintptr_t)m_ptr << ": " << strerror(errno) << endl;
+            exit(1);
         }
         switch (hint) {
         case FILE_MAP_HINT_NONE: break;
@@ -80,50 +82,21 @@ public:
             fillPageJunk(targetPtr);
         }
     }
-    void benchRandom(bool write) {
-        size_t pagesTotal = m_size / pageSize;
-        size_t pagesToHit = pagesTotal / 128;
-        uint64_t nsTotal = 0;
-
-        chrono::time_point<chrono::high_resolution_clock> start, end;
-        start = chrono::high_resolution_clock::now();
-        for (int j = 0; j < pagesToHit; j++) {
-            int targetPage = rand() % pagesTotal;
-            uint8_t *targetPtr = (uint8_t*)m_ptr + 4096ull * targetPage;
-            if (write) {
-                *targetPtr = dummy;
-            }
-            else {
-                dummy += *targetPtr;
-            }
-        }
-        end = chrono::high_resolution_clock::now();
-        nsTotal += chrono::duration_cast<chrono::nanoseconds>(end - start).count();
-        //cout << "random: " << nsTotal / 1000.0 / (pagesToHit) << "us/page" << endl;
-        cout << "random " << (write ? "write" : "read") << ": " << ((4096.0 * pagesToHit) / (1 << 20)) / (nsTotal / 1.0E9) << "MB/s" << endl;
+    void benchRandomRead(unsigned int targetPage) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
+        dummy += *targetPtr;
     }
-    void benchLinear(bool write) {
-        int pagesTotal = m_size / pageSize;
-        int iterations = 4;
-        uint64_t nsTotal = 0;
-
-        chrono::time_point<chrono::high_resolution_clock> start, end;
-        start = chrono::high_resolution_clock::now();
-        for (int i = 0; i < iterations; i++) {
-            for (int j = 0; j < pagesTotal; j++) {
-                uint8_t *targetPtr = (uint8_t*)m_ptr + 4096ull * j;
-                if (write) {
-                    *targetPtr = dummy;
-                }
-                else {
-                    dummy += *targetPtr;
-                }
-            }
-        }
-        end = chrono::high_resolution_clock::now();
-        nsTotal += chrono::duration_cast<chrono::nanoseconds>(end - start).count();
-        //cout << "linear: " << nsTotal / 1000.0 / (pagesTotal * iterations) << "us/page" << endl;
-        cout << "linear " << (write ? "write" : "read") << ": " << ((4096.0 * pagesTotal * iterations) / (1 << 20)) / (nsTotal / 1.0E9 ) << "MB/s" << endl;
+    void benchRandomWrite(unsigned int targetPage) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
+        *targetPtr = dummy;
+    }
+    void benchLinearRead(unsigned int j) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
+        dummy += *targetPtr;
+    }
+    void benchLinearWrite(unsigned int j) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
+        *targetPtr = dummy;
     }
     void dropCache() {
         int ret1 = msync(m_ptr, m_size, MS_SYNC | MS_INVALIDATE);
@@ -137,27 +110,46 @@ public:
 
 };
 
-int main(int argc, char *argv[])
-{
-    (void)argc;
-    (void)argv;
-    srand(0);
-
-    {
-        FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
-        file.benchRandom(false);
-    }
-    {
-        FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
-        file.benchLinear(false);
+static void benchRandomRead(benchmark::State& state) {
+    FileMap file{"/data/local/tmp/mmap_test", fsize};
+    while (state.KeepRunning()) {
+        unsigned int targetPage = rand() % pagesTotal;
+        file.benchRandomRead(targetPage);
     }
-    {
-        FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
-        file.benchRandom(true);
-    }
-    {
-        FileMap file{"/data/local/tmp/mmap_test", 16000 * (1ull << 20)};
-        file.benchLinear(true);
+    state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchRandomRead);
+
+static void benchRandomWrite(benchmark::State& state) {
+    FileMap file{"/data/local/tmp/mmap_test", fsize};
+    while (state.KeepRunning()) {
+        unsigned int targetPage = rand() % pagesTotal;
+        file.benchRandomWrite(targetPage);
     }
-    return 0;
+    state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchRandomWrite);
+
+static void benchLinearRead(benchmark::State& state) {
+   FileMap file{"/data/local/tmp/mmap_test", fsize};
+   unsigned int j = 0;
+   while (state.KeepRunning()) {
+       file.benchLinearRead(j);
+       j = (j + 1) % pagesTotal;
+   }
+   state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchLinearRead);
+
+static void benchLinearWrite(benchmark::State& state) {
+   FileMap file{"/data/local/tmp/mmap_test", fsize};
+   unsigned int j = 0;
+   while (state.KeepRunning()) {
+       file.benchLinearWrite(j);
+       j = (j + 1) % pagesTotal;
+   }
+   state.SetBytesProcessed(state.iterations() * pageSize);
 }
+BENCHMARK(benchLinearWrite);
+
+BENCHMARK_MAIN()
diff --git a/multinetwork/Android.mk b/multinetwork/Android.mk
new file mode 100644 (file)
index 0000000..28e56d4
--- /dev/null
@@ -0,0 +1,29 @@
+LOCAL_PATH := $(call my-dir)
+
+# The PDK build does not have access to frameworks/native elements.
+ifneq ($(TARGET_BUILD_PDK), true)
+
+# Sample util binaries.
+include $(CLEAR_VARS)
+LOCAL_MODULE := dnschk
+
+LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
+LOCAL_SRC_FILES := dnschk.cpp common.cpp
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := httpurl
+
+LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
+LOCAL_SRC_FILES := httpurl.cpp common.cpp
+include $(BUILD_EXECUTABLE)
+
+endif  # ifneq ($(TARGET_BUILD_PDK), true)
diff --git a/multinetwork/common.cpp b/multinetwork/common.cpp
new file mode 100644 (file)
index 0000000..7a5e7be
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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 "common.h"
+
+#include <android/api-level.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iostream>
+
+
+namespace {
+
+bool strEqual(const char *a, const char *b) {
+    return strcmp(a, b) == 0;
+}
+
+// Allow specifying network handles in decimal and hexadecimal.
+bool parseNetworkHandle(const char *arg, net_handle_t *nethandle) {
+    if (arg == nullptr || !isdigit(arg[0]) || nethandle == nullptr) {
+        return false;
+    }
+
+    net_handle_t nh;
+    char *end = nullptr;
+
+    nh = strtoull(arg, &end, 0);
+    if (end != nullptr && *end == '\0') {
+        *nethandle = nh;
+        return true;
+    }
+    return false;
+}
+
+}  // namespace
+
+
+void printUsage(const char *progname) {
+    std::cerr << "Usage: " << progname
+              << " [--nethandle <nethandle>]"
+              << " [--mode explicit|process]"
+              << " [--family unspec|ipv4|ipv6]"
+              << " <argument>"
+              << std::endl;
+    std::cerr << std::endl;
+    std::cerr << "Learn nethandle values from 'dumpsys connectivity --short' "
+              << "or 'dumpsys connectivity --diag'"
+              << std::endl;
+}
+
+Arguments::~Arguments() {}
+
+bool Arguments::parseArguments(int argc, const char* argv[]) {
+    if (argc < 1 || argv == nullptr) { return false; }
+
+    for (int i = 1; i < argc; i++) {
+        if (strEqual(argv[i], "--nethandle")) {
+            i++;
+            if (argc == i) break;
+            if (!parseNetworkHandle(argv[i], &nethandle)) {
+                std::cerr << "Failed to parse nethandle: '" << argv[i] << "'"
+                          << std::endl;
+                break;
+            }
+        } else if (strEqual(argv[i], "--family")) {
+            i++;
+            if (argc == i) break;
+            if (strEqual(argv[i], "unspec")) {
+                family = AF_UNSPEC;
+            } else if (strEqual(argv[i], "ipv4")) {
+                family = AF_INET;
+            } else if (strEqual(argv[i], "ipv6")) {
+                family = AF_INET6;
+            } else {
+                break;
+            }
+        } else if (strEqual(argv[i], "--mode")) {
+            i++;
+            if (argc == i) break;
+            if (strEqual(argv[i], "explicit")) {
+                api_mode = ApiMode::EXPLICIT;
+            } else if (strEqual(argv[i], "process")) {
+                api_mode = ApiMode::PROCESS;
+            } else {
+                break;
+            }
+        } else if (arg1 == nullptr) {
+            arg1 = argv[i];
+        } else {
+            arg1 = nullptr;
+            break;
+        }
+    }
+
+    if (arg1 != nullptr) {
+        return true;
+    }
+
+    printUsage(argv[0]);
+    return false;
+}
+
+
+std::string inetSockaddrToString(const sockaddr* sa) {
+    const bool is_ipv6 = (sa->sa_family == AF_INET6);
+    char host[INET6_ADDRSTRLEN];
+    char port[sizeof("65535")];
+    getnameinfo(sa, is_ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in),
+                host, sizeof(host),
+                port, sizeof(port),
+                NI_NUMERICHOST | NI_NUMERICSERV);
+
+    if (port[0] == '0' || port[0] == '\0') {
+        return std::string(host);
+    }
+    return (is_ipv6 ? "[" : "") + std::string(host) + (is_ipv6 ? "]:" : ":") + std::string(port);
+}
diff --git a/multinetwork/common.h b/multinetwork/common.h
new file mode 100644 (file)
index 0000000..f431ea9
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
+#define SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
+
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string>
+#include <android/multinetwork.h>
+
+enum class ApiMode {
+    EXPLICIT,
+    PROCESS,
+};
+
+
+struct Arguments {
+    Arguments() : nethandle(NETWORK_UNSPECIFIED),
+                  api_mode(ApiMode::EXPLICIT),
+                  family(AF_UNSPEC),
+                  arg1(nullptr) {}
+    ~Arguments();
+
+    bool parseArguments(int argc, const char* argv[]);
+
+    net_handle_t nethandle;
+    ApiMode api_mode;
+    sa_family_t family;
+    const char* arg1;
+};
+
+
+void printUsage(const char *progname);
+
+// If port is non-zero returns strings of the form "192.0.2.1:port" or
+// "[2001:db8::1]:port", else it returns the bare IP string literal.
+std::string inetSockaddrToString(const sockaddr* sa);
+
+
+struct FdAutoCloser {
+    FdAutoCloser() : fd(-1) {}
+    /* not explicit */ FdAutoCloser(int fd) : fd(fd) {}
+    ~FdAutoCloser() {
+        if (fd > -1) {
+            close(fd);
+        }
+        fd = -1;
+    }
+
+    int fd;
+};
+
+#endif  // SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
diff --git a/multinetwork/dnschk.cpp b/multinetwork/dnschk.cpp
new file mode 100644 (file)
index 0000000..a2c42d4
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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 <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <iostream>
+#include <string>
+
+#include <android/multinetwork.h>
+#include "common.h"
+
+
+int main(int argc, const char* argv[]) {
+    int rval = -1;
+
+    struct Arguments args;
+    if (!args.parseArguments(argc, argv)) { return rval; }
+
+    const struct addrinfo hints = {
+            .ai_family = args.family,
+            .ai_socktype = SOCK_DGRAM,
+    };
+    struct addrinfo *result = nullptr;
+
+    std::cout << "# " << args.arg1
+              << " (via nethandle " << args.nethandle << "):"
+              << std::endl;
+
+    switch (args.api_mode) {
+        case ApiMode::EXPLICIT:
+            rval = android_getaddrinfofornetwork(args.nethandle,
+                    args.arg1, nullptr, &hints, &result);
+            break;
+        case ApiMode::PROCESS:
+            if (args.nethandle != NETWORK_UNSPECIFIED) {
+                rval = android_setprocnetwork(args.nethandle);
+                if (rval != 0) {
+                    std::cerr << "android_setprocnetwork returned " << rval
+                              << std::endl;
+                    return rval;
+                }
+            }
+            rval = getaddrinfo(args.arg1, nullptr, &hints, &result);
+            break;
+        default:
+            // Unreachable.
+            std::cerr << "Unknown api mode." << std::endl;
+            return -1;
+    }
+
+    if (rval != 0) {
+        std::cerr << "DNS resolution failure; gaierror=" << rval
+                  << " [" << gai_strerror(rval) << "]"
+                  << std::endl;
+        return rval;
+    }
+
+    for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
+        std::cout << inetSockaddrToString(rp->ai_addr) << std::endl;
+    }
+
+    freeaddrinfo(result);
+    return 0;
+}
diff --git a/multinetwork/httpurl.cpp b/multinetwork/httpurl.cpp
new file mode 100644 (file)
index 0000000..e079c1d
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <iostream>
+#include <string>
+
+#include <android/multinetwork.h>
+#include <android-base/stringprintf.h>
+#include "common.h"
+
+
+
+struct Parameters {
+    Parameters() : ss({}), port("80"), path("/") {}
+
+    struct sockaddr_storage ss;
+    std::string host;
+    std::string hostname;
+    std::string port;
+    std::string path;
+};
+
+
+bool parseUrl(const struct Arguments& args, struct Parameters* parameters) {
+    if (parameters == nullptr) { return false; }
+
+    static const char HTTP_PREFIX[] = "http://";
+    if (strncmp(args.arg1, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0) {
+        std::cerr << "Only " << HTTP_PREFIX << " URLs supported." << std::endl;
+        return false;
+    }
+
+    parameters->host = std::string(args.arg1).substr(strlen(HTTP_PREFIX));
+    const auto first_slash = parameters->host.find_first_of("/");
+    if (first_slash != std::string::npos) {
+        parameters->path = parameters->host.substr(first_slash);
+        parameters->host.erase(first_slash);
+    }
+
+    if (parameters->host.size() == 0) {
+        std::cerr << "Host portion cannot be empty." << std::endl;
+        return false;
+    }
+
+    if (parameters->host[0] == '[') {
+        const auto closing_bracket = parameters->host.find_first_of("]");
+        if (closing_bracket == std::string::npos) {
+            std::cerr << "Missing closing bracket." << std::endl;
+            return false;
+        }
+        parameters->hostname = parameters->host.substr(1, closing_bracket - 1);
+
+        const auto colon_port = closing_bracket + 1;
+        if (colon_port < parameters->host.size()) {
+            if (parameters->host[colon_port] != ':') {
+                std::cerr << "Malformed port portion." << std::endl;
+                return false;
+            }
+            parameters->port = parameters->host.substr(closing_bracket + 2);
+        }
+    } else {
+        const auto first_colon = parameters->host.find_first_of(":");
+        if (first_colon != std::string::npos) {
+            parameters->port = parameters->host.substr(first_colon + 1);
+            parameters->hostname = parameters->host.substr(0, first_colon);
+        } else {
+            parameters->hostname = parameters->host;
+        }
+    }
+
+    // TODO: find the request portion to send (before '#...').
+
+    std::cerr << "Resolving hostname=" << parameters->hostname
+              << ", port=" << parameters->port
+              << std::endl;
+
+    struct addrinfo hints = {
+            .ai_family = args.family,
+            .ai_socktype = SOCK_STREAM,
+    };
+    struct addrinfo *result = nullptr;
+
+    int rval = -1;
+    switch (args.api_mode) {
+        case ApiMode::EXPLICIT:
+            rval = android_getaddrinfofornetwork(args.nethandle,
+                                                 parameters->hostname.c_str(),
+                                                 parameters->port.c_str(),
+                                                 &hints, &result);
+            break;
+        case ApiMode::PROCESS:
+            rval = getaddrinfo(parameters->hostname.c_str(),
+                               parameters->port.c_str(),
+                               &hints, &result);
+            break;
+        default:
+            // Unreachable.
+            std::cerr << "Unknown api mode." << std::endl;
+            return false;
+    }
+
+    if (rval != 0) {
+        std::cerr << "DNS resolution failure; gaierror=" << rval
+                  << " [" << gai_strerror(rval) << "]"
+                  << std::endl;
+        return rval;
+    }
+
+    memcpy(&(parameters->ss), result[0].ai_addr, result[0].ai_addrlen);
+    std::cerr << "Connecting to: "
+              << inetSockaddrToString(result[0].ai_addr)
+              << std::endl;
+
+    freeaddrinfo(result);
+    return true;
+}
+
+
+int makeTcpSocket(sa_family_t address_family, net_handle_t nethandle) {
+    int fd = socket(address_family, SOCK_STREAM, IPPROTO_TCP);
+    if (fd < 0) {
+        std::cerr << "failed to create TCP socket" << std::endl;
+        return -1;
+    }
+
+    // Don't let reads or writes block indefinitely. We cannot control
+    // connect() timeouts without nonblocking sockets and select/poll/epoll.
+    const struct timeval timeo = { 5, 0 };  // 5 seconds
+    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
+
+    if (nethandle != NETWORK_UNSPECIFIED) {
+        if (android_setsocknetwork(nethandle, fd) != 0) {
+            int errnum = errno;
+            std::cerr << "android_setsocknetwork() failed;"
+                      << " errno: " << errnum << " [" << strerror(errnum) << "]"
+                      << std::endl;
+            close(fd);
+            return -1;
+        }
+    }
+    return fd;
+}
+
+
+int doHttpQuery(int fd, const struct Parameters& parameters) {
+    int rval = -1;
+    if (connect(fd,
+                reinterpret_cast<const struct sockaddr *>(&(parameters.ss)),
+                (parameters.ss.ss_family == AF_INET6)
+                        ? sizeof(struct sockaddr_in6)
+                        : sizeof(struct sockaddr_in)) != 0) {
+        int errnum = errno;
+        std::cerr << "Failed to connect; errno=" << errnum
+                  << " [" << strerror(errnum) << "]"
+                  << std::endl;
+        return -1;
+    }
+
+    const std::string request(android::base::StringPrintf(
+            "GET %s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "Accept: */*\r\n"
+            "Connection: close\r\n"
+            "User-Agent: httpurl/0.0\r\n"
+            "\r\n",
+            parameters.path.c_str(), parameters.host.c_str()));
+    const ssize_t sent = write(fd, request.c_str(), request.size());
+    if (sent != static_cast<ssize_t>(request.size())) {
+        std::cerr << "Sent only " << sent << "/" << request.size() << " bytes"
+                  << std::endl;
+        return -1;
+    }
+
+    char buf[4*1024];
+    do {
+        rval = recv(fd, buf, sizeof(buf), 0);
+
+        if (rval < 0) {
+            const int saved_errno = errno;
+            std::cerr << "Failed to recv; errno=" << saved_errno
+                      << " [" << strerror(saved_errno) << "]"
+                      << std::endl;
+        } else if (rval > 0) {
+            std::cout.write(buf, rval);
+            std::cout.flush();
+        }
+    } while (rval > 0);
+    std::cout << std::endl;
+
+    return 0;
+}
+
+
+int main(int argc, const char* argv[]) {
+    int rval = -1;
+
+    struct Arguments args;
+    if (!args.parseArguments(argc, argv)) { return rval; }
+
+    if (args.api_mode == ApiMode::PROCESS) {
+        rval = android_setprocnetwork(args.nethandle);
+        if (rval != 0) {
+            int errnum = errno;
+            std::cerr << "android_setprocnetwork(" << args.nethandle << ") failed;"
+                      << " errno: " << errnum << " [" << strerror(errnum) << "]"
+                      << std::endl;
+            return rval;
+        }
+    }
+
+    struct Parameters parameters;
+    if (!parseUrl(args, &parameters)) { return -1; }
+
+    // TODO: Fall back from IPv6 to IPv4 if ss.ss_family is AF_UNSPEC.
+    // This will involve changes to parseUrl() as well.
+    struct FdAutoCloser closer = makeTcpSocket(
+            parameters.ss.ss_family,
+            (args.api_mode == ApiMode::EXPLICIT) ? args.nethandle
+                                                 : NETWORK_UNSPECIFIED);
+    if (closer.fd < 0) { return closer.fd; }
+
+    return doHttpQuery(closer.fd, parameters);
+}
diff --git a/multinetwork/quick_test.sh b/multinetwork/quick_test.sh
new file mode 100755 (executable)
index 0000000..f586bae
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+nethandle=0
+
+readonly TEST_HOST="connectivitycheck.gstatic.com"
+readonly TEST_PATH="/generate_204"
+readonly PREFIX=">>>"
+
+function getUrls() {
+    if [ ! -z $(echo "$1" | sed -e 's/[^:]//g') ]; then
+        echo "http://[$1]$TEST_PATH"
+        echo "http://[$1]:80$TEST_PATH"
+    else
+        echo "http://$1$TEST_PATH"
+        echo "http://$1:80$TEST_PATH"
+    fi
+}
+
+function toHex() {
+    readonly local hexValue=$(bc -q 2>/dev/null << EOT
+obase=16
+$1
+EOT
+)
+    if [ ! -z "$hexValue" ]; then
+        echo "0x$hexValue"
+    fi
+}
+
+
+if [ ! -z "$1" ]; then
+    nethandle="$1"
+fi
+echo "$PREFIX Using nethandle $nethandle ($(toHex $nethandle))"
+echo ""
+
+readonly IPADDRESSES=$(
+    adb shell /system/xbin/dnschk --nethandle $nethandle $TEST_HOST |
+    sed -e 's/#.*//' -e '/^$/d')
+
+
+for host in $TEST_HOST $IPADDRESSES; do
+    urls=$(getUrls $host)
+    for url in $urls; do
+        echo "$PREFIX Checking $url" >&2
+        adb shell /system/xbin/httpurl --nethandle $nethandle "$url"
+    done
+done
diff --git a/pagecache/Android.mk b/pagecache/Android.mk
new file mode 100644 (file)
index 0000000..fe06410
--- /dev/null
@@ -0,0 +1,13 @@
+# Copyright 2015 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= dumpcache.c
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE:= dumpcache
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/pagecache/MODULE_LICENSE_APACHE2 b/pagecache/MODULE_LICENSE_APACHE2
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pagecache/NOTICE b/pagecache/NOTICE
new file mode 100644 (file)
index 0000000..34bdaf1
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2015, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/pagecache/README b/pagecache/README
new file mode 100644 (file)
index 0000000..08f4b53
--- /dev/null
@@ -0,0 +1,4 @@
+Pagecache tools.
+
+dumpcache.c: dumps complete pagecache of device.
+pagecache.py: shows live info on files going in/out of pagecache.
diff --git a/pagecache/dumpcache.c b/pagecache/dumpcache.c
new file mode 100644 (file)
index 0000000..eb11bba
--- /dev/null
@@ -0,0 +1,153 @@
+#include <ftw.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <ctype.h>
+#include <stddef.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+// Initial size of the array holding struct file_info
+#define INITIAL_NUM_FILES 512
+
+// Max number of file descriptors to use for ntfw
+#define MAX_NUM_FD 1
+
+struct file_info {
+    char *name;
+    size_t file_size;
+    size_t num_cached_pages;
+};
+
+// Size of pages on this system
+static int g_page_size;
+
+// Total number of cached pages found so far
+static size_t g_total_cached = 0;
+
+// Total number of files scanned so far
+static size_t g_num_files = 0;
+
+// Scanned files and their associated cached page counts
+static struct file_info **g_files;
+
+// Current size of files array
+size_t g_files_size;
+
+static struct file_info *get_file_info(const char* fpath, size_t file_size) {
+    struct file_info *info;
+    if (g_num_files >= g_files_size) {
+        g_files = realloc(g_files, 2 * g_files_size * sizeof(struct file_info*));
+        if (!g_files) {
+            fprintf(stderr, "Couldn't allocate space for files array: %s\n", strerror(errno));
+            exit(EXIT_FAILURE);
+        }
+        g_files_size = 2 * g_files_size;
+    }
+
+    info = calloc(1, sizeof(*info));
+    if (!info) {
+        fprintf(stderr, "Couldn't allocate space for file struct: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    info->name = malloc(strlen(fpath) + 1);
+    if (!info->name) {
+        fprintf(stderr, "Couldn't allocate space for file struct: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    strcpy(info->name, fpath);
+
+    info->num_cached_pages = 0;
+    info->file_size = file_size;
+
+    g_files[g_num_files++] = info;
+
+    return info;
+}
+
+static int store_num_cached(const char* fpath, const struct stat *sb) {
+    int fd;
+    fd = open (fpath, O_RDONLY);
+
+    if (fd == -1) {
+        printf("Could not open file.");
+        return -1;
+    }
+
+    void* mapped_addr = mmap(NULL, sb->st_size, PROT_NONE, MAP_SHARED, fd, 0);
+
+    if (mapped_addr != MAP_FAILED) {
+        // Calculate bit-vector size
+        size_t num_file_pages = (sb->st_size + g_page_size - 1) / g_page_size;
+        unsigned char* mincore_data = calloc(1, num_file_pages);
+        int ret = mincore(mapped_addr, sb->st_size, mincore_data);
+        int num_cached = 0;
+        unsigned int page = 0;
+        for (page = 0; page < num_file_pages; page++) {
+           if (mincore_data[page]) num_cached++;
+        }
+        if (num_cached > 0) {
+            struct file_info *info = get_file_info(fpath, sb->st_size);
+            info->num_cached_pages += num_cached;
+            g_total_cached += num_cached;
+        }
+        munmap(mapped_addr, sb->st_size);
+    }
+
+    close(fd);
+    return 0;
+}
+
+static int scan_entry(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
+    if (typeflag == FTW_F) {
+        store_num_cached(fpath, sb);
+    }
+    return 0;
+}
+
+static int cmpsize(size_t a, size_t b) {
+    if (a < b) return -1;
+    if (a > b) return 1;
+    return 0;
+}
+
+static int cmpfiles(const void *a, const void *b) {
+    return cmpsize((*((struct file_info**)a))->num_cached_pages,
+            (*((struct file_info**)b))->num_cached_pages);
+}
+
+int main()
+{
+    size_t i;
+    g_page_size = getpagesize();
+
+    g_files = malloc(INITIAL_NUM_FILES * sizeof(struct file_info*));
+    g_files_size = INITIAL_NUM_FILES;
+
+    // Walk filesystem trees
+    nftw("/system/", &scan_entry, MAX_NUM_FD, 0);
+    nftw("/vendor/", &scan_entry, MAX_NUM_FD, 0);
+    nftw("/data/", &scan_entry, MAX_NUM_FD, 0);
+
+    // Sort entries
+    qsort(g_files, g_num_files, sizeof(g_files[0]), &cmpfiles);
+
+    // Dump entries
+    for (i = 0; i < g_num_files; i++) {
+        struct file_info *info = g_files[i];
+        fprintf(stdout, "%s: %zu cached pages (%.2f MB, %zu%% of total file size.)\n", info->name,
+                info->num_cached_pages,
+                (float) (info->num_cached_pages * g_page_size) / 1024 / 1024,
+                (100 * info->num_cached_pages * g_page_size) / info->file_size);
+    }
+
+    fprintf(stdout, "TOTAL CACHED: %zu pages (%f MB)\n", g_total_cached,
+            (float) (g_total_cached * 4096) / 1024 / 1024);
+    return 0;
+}
diff --git a/pagecache/pagecache.py b/pagecache/pagecache.py
new file mode 100755 (executable)
index 0000000..c822ff0
--- /dev/null
@@ -0,0 +1,404 @@
+#!/usr/bin/env python
+
+import curses
+import operator
+import optparse
+import os
+import re
+import subprocess
+import sys
+import threading
+import Queue
+
+STATS_UPDATE_INTERVAL = 0.2
+PAGE_SIZE = 4096
+
+class PagecacheStats():
+  """Holds pagecache stats by accounting for pages added and removed.
+
+  """
+  def __init__(self, inode_to_filename):
+    self._inode_to_filename = inode_to_filename
+    self._file_size = {}
+    self._file_pages = {}
+    self._total_pages_added = 0
+    self._total_pages_removed = 0
+
+  def add_page(self, device_number, inode, offset):
+    # See if we can find the page in our lookup table
+    if (device_number, inode) in self._inode_to_filename:
+      filename, filesize = self._inode_to_filename[(device_number, inode)]
+      if filename not in self._file_pages:
+        self._file_pages[filename] = [1, 0]
+      else:
+        self._file_pages[filename][0] += 1
+
+      self._total_pages_added += 1
+
+      if filename not in self._file_size:
+        self._file_size[filename] = filesize
+
+  def remove_page(self, device_number, inode, offset):
+    if (device_number, inode) in self._inode_to_filename:
+      filename, filesize = self._inode_to_filename[(device_number, inode)]
+      if filename not in self._file_pages:
+        self._file_pages[filename] = [0, 1]
+      else:
+        self._file_pages[filename][1] += 1
+
+      self._total_pages_removed += 1
+
+      if filename not in self._file_size:
+        self._file_size[filename] = filesize
+
+  def pages_to_mb(self, num_pages):
+    return "%.2f" % round(num_pages * PAGE_SIZE / 1024.0 / 1024.0, 2)
+
+  def bytes_to_mb(self, num_bytes):
+    return "%.2f" % round(int(num_bytes) / 1024.0 / 1024.0, 2)
+
+  def print_pages_and_mb(self, num_pages):
+    pages_string = str(num_pages) + ' (' + str(self.pages_to_mb(num_pages)) + ' MB)'
+    return pages_string
+
+  def reset_stats(self):
+    self._file_pages.clear()
+    self._total_pages_added = 0;
+    self._total_pages_removed = 0;
+
+  def print_stats(self):
+    # Create new merged dict
+    sorted_added = sorted(self._file_pages.items(), key=operator.itemgetter(1), reverse=True)
+    row_format = "{:<70}{:<12}{:<14}{:<9}"
+    print row_format.format('NAME', 'ADDED (MB)', 'REMOVED (MB)', 'SIZE (MB)')
+    for filename, added in sorted_added:
+      filesize = self._file_size[filename]
+      added = self._file_pages[filename][0]
+      removed = self._file_pages[filename][1]
+      if (filename > 64):
+        filename = filename[-64:]
+      print row_format.format(filename, self.pages_to_mb(added), self.pages_to_mb(removed), self.bytes_to_mb(filesize))
+
+    print row_format.format('TOTAL', self.pages_to_mb(self._total_pages_added), self.pages_to_mb(self._total_pages_removed), '')
+
+  def print_stats_curses(self, pad):
+    sorted_added = sorted(self._file_pages.items(), key=operator.itemgetter(1), reverse=True)
+    height, width = pad.getmaxyx()
+    pad.clear()
+    pad.addstr(0, 2, 'NAME'.ljust(68), curses.A_REVERSE)
+    pad.addstr(0, 70, 'ADDED (MB)'.ljust(12), curses.A_REVERSE)
+    pad.addstr(0, 82, 'REMOVED (MB)'.ljust(14), curses.A_REVERSE)
+    pad.addstr(0, 96, 'SIZE (MB)'.ljust(9), curses.A_REVERSE)
+    y = 1
+    for filename, added_removed in sorted_added:
+      filesize = self._file_size[filename]
+      added  = self._file_pages[filename][0]
+      removed = self._file_pages[filename][1]
+      if (filename > 64):
+        filename = filename[-64:]
+      pad.addstr(y, 2, filename)
+      pad.addstr(y, 70, self.pages_to_mb(added).rjust(10))
+      pad.addstr(y, 80, self.pages_to_mb(removed).rjust(14))
+      pad.addstr(y, 96, self.bytes_to_mb(filesize).rjust(9))
+      y += 1
+      if y == height - 2:
+        pad.addstr(y, 4, "<more...>")
+        break
+    y += 1
+    pad.addstr(y, 2, 'TOTAL'.ljust(74), curses.A_REVERSE)
+    pad.addstr(y, 70, str(self.pages_to_mb(self._total_pages_added)).rjust(10), curses.A_REVERSE)
+    pad.addstr(y, 80, str(self.pages_to_mb(self._total_pages_removed)).rjust(14), curses.A_REVERSE)
+    pad.refresh(0,0, 0,0, height,width)
+
+class FileReaderThread(threading.Thread):
+  """Reads data from a file/pipe on a worker thread.
+
+  Use the standard threading. Thread object API to start and interact with the
+  thread (start(), join(), etc.).
+  """
+
+  def __init__(self, file_object, output_queue, text_file, chunk_size=-1):
+    """Initializes a FileReaderThread.
+
+    Args:
+      file_object: The file or pipe to read from.
+      output_queue: A Queue.Queue object that will receive the data
+      text_file: If True, the file will be read one line at a time, and
+          chunk_size will be ignored.  If False, line breaks are ignored and
+          chunk_size must be set to a positive integer.
+      chunk_size: When processing a non-text file (text_file = False),
+          chunk_size is the amount of data to copy into the queue with each
+          read operation.  For text files, this parameter is ignored.
+    """
+    threading.Thread.__init__(self)
+    self._file_object = file_object
+    self._output_queue = output_queue
+    self._text_file = text_file
+    self._chunk_size = chunk_size
+    assert text_file or chunk_size > 0
+
+  def run(self):
+    """Overrides Thread's run() function.
+
+    Returns when an EOF is encountered.
+    """
+    if self._text_file:
+      # Read a text file one line at a time.
+      for line in self._file_object:
+        self._output_queue.put(line)
+    else:
+      # Read binary or text data until we get to EOF.
+      while True:
+        chunk = self._file_object.read(self._chunk_size)
+        if not chunk:
+          break
+        self._output_queue.put(chunk)
+
+  def set_chunk_size(self, chunk_size):
+    """Change the read chunk size.
+
+    This function can only be called if the FileReaderThread object was
+    created with an initial chunk_size > 0.
+    Args:
+      chunk_size: the new chunk size for this file.  Must be > 0.
+    """
+    # The chunk size can be changed asynchronously while a file is being read
+    # in a worker thread.  However, type of file can not be changed after the
+    # the FileReaderThread has been created.  These asserts verify that we are
+    # only changing the chunk size, and not the type of file.
+    assert not self._text_file
+    assert chunk_size > 0
+    self._chunk_size = chunk_size
+
+class AdbUtils():
+  @staticmethod
+  def add_adb_serial(adb_command, device_serial):
+    if device_serial is not None:
+      adb_command.insert(1, device_serial)
+      adb_command.insert(1, '-s')
+
+  @staticmethod
+  def construct_adb_shell_command(shell_args, device_serial):
+    adb_command = ['adb', 'shell', ' '.join(shell_args)]
+    AdbUtils.add_adb_serial(adb_command, device_serial)
+    return adb_command
+
+  @staticmethod
+  def run_adb_shell(shell_args, device_serial):
+    """Runs "adb shell" with the given arguments.
+
+    Args:
+      shell_args: array of arguments to pass to adb shell.
+      device_serial: if not empty, will add the appropriate command-line
+          parameters so that adb targets the given device.
+    Returns:
+      A tuple containing the adb output (stdout & stderr) and the return code
+      from adb.  Will exit if adb fails to start.
+    """
+    adb_command = AdbUtils.construct_adb_shell_command(shell_args, device_serial)
+
+    adb_output = []
+    adb_return_code = 0
+    try:
+      adb_output = subprocess.check_output(adb_command, stderr=subprocess.STDOUT,
+                                           shell=False, universal_newlines=True)
+    except OSError as error:
+      # This usually means that the adb executable was not found in the path.
+      print >> sys.stderr, ('\nThe command "%s" failed with the following error:'
+                            % ' '.join(adb_command))
+      print >> sys.stderr, '    %s' % str(error)
+      print >> sys.stderr, 'Is adb in your path?'
+      adb_return_code = error.errno
+      adb_output = error
+    except subprocess.CalledProcessError as error:
+      # The process exited with an error.
+      adb_return_code = error.returncode
+      adb_output = error.output
+
+    return (adb_output, adb_return_code)
+
+  @staticmethod
+  def do_preprocess_adb_cmd(command, serial):
+    args = [command]
+    dump, ret_code = AdbUtils.run_adb_shell(args, serial)
+    if ret_code != 0:
+      return None
+
+    dump = ''.join(dump)
+    return dump
+
+def parse_atrace_line(line, pagecache_stats, app_name):
+  # Find a mm_filemap_add_to_page_cache entry
+  m = re.match('.* (mm_filemap_add_to_page_cache|mm_filemap_delete_from_page_cache): dev (\d+):(\d+) ino ([0-9a-z]+) page=([0-9a-z]+) pfn=\d+ ofs=(\d+).*', line)
+  if m != None:
+    # Get filename
+    device_number = int(m.group(2)) << 8 | int(m.group(3))
+    if device_number == 0:
+      return
+    inode = int(m.group(4), 16)
+    if app_name != None and not (app_name in m.group(0)):
+      return
+    if m.group(1) == 'mm_filemap_add_to_page_cache':
+      pagecache_stats.add_page(device_number, inode, m.group(4))
+    elif m.group(1) == 'mm_filemap_delete_from_page_cache':
+      pagecache_stats.remove_page(device_number, inode, m.group(4))
+
+def build_inode_lookup_table(inode_dump):
+  inode2filename = {}
+  text = inode_dump.splitlines()
+  for line in text:
+    result = re.match('([0-9]+)d? ([0-9]+) ([0-9]+) (.*)', line)
+    if result:
+      inode2filename[(int(result.group(1)), int(result.group(2)))] = (result.group(4), result.group(3))
+
+  return inode2filename;
+
+def get_inode_data(datafile, dumpfile, adb_serial):
+  if datafile is not None and os.path.isfile(datafile):
+    print('Using cached inode data from ' + datafile)
+    f = open(datafile, 'r')
+    stat_dump = f.read();
+  else:
+    # Build inode maps if we were tracing page cache
+    print('Downloading inode data from device')
+    stat_dump = AdbUtils.do_preprocess_adb_cmd('find /system /data /vendor ' +
+                                    '-exec stat -c "%d %i %s %n" {} \;', adb_serial)
+    if stat_dump is None:
+      print 'Could not retrieve inode data from device.'
+      sys.exit(1)
+
+    if dumpfile is not None:
+      print 'Storing inode data in ' + dumpfile
+      f = open(dumpfile, 'w')
+      f.write(stat_dump)
+      f.close()
+
+    sys.stdout.write('Done.\n')
+
+  return stat_dump
+
+def read_and_parse_trace_file(trace_file, pagecache_stats, app_name):
+  for line in trace_file:
+    parse_atrace_line(line, pagecache_stats, app_name)
+  pagecache_stats.print_stats();
+
+def read_and_parse_trace_data_live(stdout, stderr, pagecache_stats, app_name):
+  # Start reading trace data
+  stdout_queue = Queue.Queue(maxsize=128)
+  stderr_queue = Queue.Queue()
+
+  stdout_thread = FileReaderThread(stdout, stdout_queue,
+                                   text_file=True, chunk_size=64)
+  stderr_thread = FileReaderThread(stderr, stderr_queue,
+                                   text_file=True)
+  stdout_thread.start()
+  stderr_thread.start()
+
+  stdscr = curses.initscr()
+
+  try:
+    height, width = stdscr.getmaxyx()
+    curses.noecho()
+    curses.cbreak()
+    stdscr.keypad(True)
+    stdscr.nodelay(True)
+    stdscr.refresh()
+    # We need at least a 30x100 window
+    used_width = max(width, 100)
+    used_height = max(height, 30)
+
+    # Create a pad for pagecache stats
+    pagecache_pad = curses.newpad(used_height - 2, used_width)
+
+    stdscr.addstr(used_height - 1, 0, 'KEY SHORTCUTS: (r)eset stats, CTRL-c to quit')
+    while (stdout_thread.isAlive() or stderr_thread.isAlive() or
+           not stdout_queue.empty() or not stderr_queue.empty()):
+      while not stderr_queue.empty():
+        # Pass along errors from adb.
+        line = stderr_queue.get()
+        sys.stderr.write(line)
+      while True:
+        try:
+          line = stdout_queue.get(True, STATS_UPDATE_INTERVAL)
+          parse_atrace_line(line, pagecache_stats, app_name)
+        except Queue.Empty:
+          break
+
+      key = ''
+      try:
+        key = stdscr.getkey()
+      except:
+        pass
+
+      if key == 'r':
+        pagecache_stats.reset_stats()
+
+      pagecache_stats.print_stats_curses(pagecache_pad)
+  except Exception, e:
+    curses.endwin()
+    print e
+  finally:
+    curses.endwin()
+    # The threads should already have stopped, so this is just for cleanup.
+    stdout_thread.join()
+    stderr_thread.join()
+
+    stdout.close()
+    stderr.close()
+
+def parse_options(argv):
+  usage = 'Usage: %prog [options]'
+  desc = 'Example: %prog'
+  parser = optparse.OptionParser(usage=usage, description=desc)
+  parser.add_option('-d', dest='inode_dump_file', metavar='FILE',
+                    help='Dump the inode data read from a device to a file.'
+                    ' This file can then be reused with the -i option to speed'
+                    ' up future invocations of this script.')
+  parser.add_option('-i', dest='inode_data_file', metavar='FILE',
+                    help='Read cached inode data from a file saved arlier with the'
+                    ' -d option.')
+  parser.add_option('-s', '--serial', dest='device_serial', type='string',
+                    help='adb device serial number')
+  parser.add_option('-f', dest='trace_file', metavar='FILE',
+                    help='Show stats from a trace file, instead of running live.')
+  parser.add_option('-a', dest='app_name', type='string',
+                    help='filter a particular app')
+
+  options, categories = parser.parse_args(argv[1:])
+  if options.inode_dump_file and options.inode_data_file:
+    parser.error('options -d and -i can\'t be used at the same time')
+  return (options, categories)
+
+def main():
+  options, categories = parse_options(sys.argv)
+
+  # Load inode data for this device
+  inode_data = get_inode_data(options.inode_data_file, options.inode_dump_file,
+      options.device_serial)
+  # Build (dev, inode) -> filename hash
+  inode_lookup_table = build_inode_lookup_table(inode_data)
+  # Init pagecache stats
+  pagecache_stats = PagecacheStats(inode_lookup_table)
+
+  if options.trace_file is not None:
+    if not os.path.isfile(options.trace_file):
+      print >> sys.stderr, ('Couldn\'t load trace file.')
+      sys.exit(1)
+    trace_file = open(options.trace_file, 'r')
+    read_and_parse_trace_file(trace_file, pagecache_stats, options.app_name)
+  else:
+    # Construct and execute trace command
+    trace_cmd = AdbUtils.construct_adb_shell_command(['atrace', '--stream', 'pagecache'],
+        options.device_serial)
+
+    try:
+      atrace = subprocess.Popen(trace_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+    except OSError as error:
+      print >> sys.stderr, ('The command failed')
+      sys.exit(1)
+
+    read_and_parse_trace_data_live(atrace.stdout, atrace.stderr, pagecache_stats, options.app_name)
+
+if __name__ == "__main__":
+  main()
index 361b3e8..36a2043 100644 (file)
@@ -153,6 +153,35 @@ void get_mem_info(uint64_t mem[]) {
     }
 }
 
+static uint64_t get_zram_mem_used() {
+#define ZRAM_SYSFS "/sys/block/zram0/"
+    FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
+    if (f) {
+        uint64_t mem_used_total = 0;
+
+        int matched = fscanf(f, "%*d %*d %" SCNu64 " %*d %*d %*d %*d", &mem_used_total);
+        if (matched != 1)
+            fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mm_stat\n");
+
+        fclose(f);
+        return mem_used_total;
+    }
+
+    f = fopen(ZRAM_SYSFS "mem_used_total", "r");
+    if (f) {
+        uint64_t mem_used_total = 0;
+
+        int matched = fscanf(f, "%" SCNu64, &mem_used_total);
+        if (matched != 1)
+            fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mem_used_total\n");
+
+        fclose(f);
+        return mem_used_total;
+    }
+
+    return 0;
+}
+
 int main(int argc, char *argv[]) {
     pm_kernel_t *ker;
     pm_process_t *proc;
@@ -180,8 +209,6 @@ int main(int argc, char *argv[]) {
 
     uint64_t mem[MEMINFO_COUNT] = { };
     pm_proportional_swap_t *p_swap;
-    int fd, len;
-    char buffer[1024];
     float zram_cr = 0.0;
 
     signal(SIGPIPE, SIG_IGN);
@@ -275,17 +302,12 @@ int main(int argc, char *argv[]) {
     qsort(procs.data(), procs.size(), sizeof(procs[0]), compfn);
 
     if (has_swap) {
-        fd = open("/sys/block/zram0/mem_used_total", O_RDONLY);
-        if (fd >= 0) {
-            len = read(fd, buffer, sizeof(buffer)-1);
-            close(fd);
-            if (len > 0) {
-                buffer[len] = 0;
-                mem[MEMINFO_ZRAM_TOTAL] = atoll(buffer)/1024;
-                zram_cr = (float) mem[MEMINFO_ZRAM_TOTAL] /
-                        (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]);
-                has_zram = true;
-            }
+        uint64_t zram_mem_used = get_zram_mem_used();
+        if (zram_mem_used) {
+            mem[MEMINFO_ZRAM_TOTAL] = zram_mem_used/1024;
+            zram_cr = (float) mem[MEMINFO_ZRAM_TOTAL] /
+                    (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]);
+            has_zram = true;
         }
     }
 
index d7d41b6..6efe260 100644 (file)
@@ -25,6 +25,11 @@ struct mapinfo {
     char name[1];
 };
 
+static bool verbose = false;
+static bool terse = false;
+static bool addresses = false;
+static bool quiet = false;
+
 static int is_library(const char *name) {
     int len = strlen(name);
     return len >= 4 && name[0] == '/'
@@ -173,7 +178,7 @@ static mapinfo *load_maps(int pid, int sort_by_address, int coalesce_by_name)
     snprintf(fn, sizeof(fn), "/proc/%d/smaps", pid);
     fp = fopen(fn, "r");
     if (fp == 0) {
-        fprintf(stderr, "cannot open /proc/%d/smaps: %s\n", pid, strerror(errno));
+        if (!quiet) fprintf(stderr, "cannot open /proc/%d/smaps: %s\n", pid, strerror(errno));
         return NULL;
     }
 
@@ -202,17 +207,13 @@ static mapinfo *load_maps(int pid, int sort_by_address, int coalesce_by_name)
     fclose(fp);
 
     if (!head) {
-        fprintf(stderr, "could not read /proc/%d/smaps\n", pid);
+        if (!quiet) fprintf(stderr, "could not read /proc/%d/smaps\n", pid);
         return NULL;
     }
 
     return head;
 }
 
-static bool verbose = false;
-static bool terse = false;
-static bool addresses = false;
-
 static void print_header()
 {
     const char *addr1 = addresses ? "   start      end " : "";
@@ -264,7 +265,7 @@ static int show_map(int pid)
 
     mapinfo *milist = load_maps(pid, addresses, !verbose && !addresses);
     if (milist == NULL) {
-        return 1;
+        return quiet ? 0 : 1;
     }
 
     print_header();
@@ -282,7 +283,7 @@ static int show_map(int pid)
         total.pss += mi->pss;
         total.size += mi->size;
         total.count += mi->count;
-        
+
         if (terse && !mi->private_dirty) {
             goto out;
         }
@@ -328,6 +329,10 @@ int main(int argc, char *argv[])
             addresses = true;
             continue;
         }
+        if (!strcmp(arg,"-q")) {
+            quiet = true;
+            continue;
+        }
         if (argc != 1) {
             fprintf(stderr, "too many arguments\n");
             break;
@@ -346,10 +351,11 @@ int main(int argc, char *argv[])
 
     if (usage) {
         fprintf(stderr,
-                "showmap [-t] [-v] [-c] <pid>\n"
+                "showmap [-t] [-v] [-c] [-q] <pid>\n"
                 "        -t = terse (show only items with private pages)\n"
                 "        -v = verbose (don't coalesce maps with the same name)\n"
                 "        -a = addresses (show virtual memory map)\n"
+                "        -q = quiet (don't show error if map could not be read)\n"
                 );
         result = 1;
     }
index c0e5844..9729904 100644 (file)
@@ -185,8 +185,7 @@ TEST(record_cmd, existing_processes) {
   CreateProcesses(2, &workloads);
   std::string pid_list = android::base::StringPrintf(
       "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
-  TemporaryFile tmpfile;
-  ASSERT_TRUE(RecordCmd()->Run({"-p", pid_list, "-o", tmpfile.path}));
+  ASSERT_TRUE(RunRecordCmd({"-p", pid_list}));
 }
 
 TEST(record_cmd, existing_threads) {
@@ -195,8 +194,7 @@ TEST(record_cmd, existing_threads) {
   // Process id can also be used as thread id in linux.
   std::string tid_list = android::base::StringPrintf(
       "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
-  TemporaryFile tmpfile;
-  ASSERT_TRUE(RecordCmd()->Run({"-t", tid_list, "-o", tmpfile.path}));
+  ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
 }
 
 TEST(record_cmd, no_monitored_threads) { ASSERT_FALSE(RecordCmd()->Run({""})); }
index b1f016c..5748b7a 100644 (file)
@@ -56,7 +56,8 @@ void CreateProcesses(size_t count,
                      std::vector<std::unique_ptr<Workload>>* workloads) {
   workloads->clear();
   for (size_t i = 0; i < count; ++i) {
-    auto workload = Workload::CreateWorkload({"sleep", "1"});
+    // Create a workload runs longer than profiling time.
+    auto workload = Workload::CreateWorkload({"sleep", "1000"});
     ASSERT_TRUE(workload != nullptr);
     ASSERT_TRUE(workload->Start());
     workloads->push_back(std::move(workload));
@@ -68,7 +69,7 @@ TEST(stat_cmd, existing_processes) {
   CreateProcesses(2, &workloads);
   std::string pid_list = android::base::StringPrintf(
       "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
-  ASSERT_TRUE(StatCmd()->Run({"-p", pid_list}));
+  ASSERT_TRUE(StatCmd()->Run({"-p", pid_list, "sleep", "1"}));
 }
 
 TEST(stat_cmd, existing_threads) {
@@ -77,7 +78,7 @@ TEST(stat_cmd, existing_threads) {
   // Process id can be used as thread id in linux.
   std::string tid_list = android::base::StringPrintf(
       "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
-  ASSERT_TRUE(StatCmd()->Run({"-t", tid_list}));
+  ASSERT_TRUE(StatCmd()->Run({"-t", tid_list, "sleep", "1"}));
 }
 
 TEST(stat_cmd, no_monitored_threads) { ASSERT_FALSE(StatCmd()->Run({""})); }
index 5f8cfea..5f45a64 100755 (executable)
@@ -5,7 +5,7 @@
 function usage() {
 cat<<EOT
 Usage:
-${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-d PRODUCT_OUT] [-C FS_CONFIG ] [-c FILE_CONTEXTS] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT]
+${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-d PRODUCT_OUT] [-C FS_CONFIG ] [-c FILE_CONTEXTS] [-B BLOCK_MAP_FILE] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT] [-a ]
 EOT
 }
 
@@ -54,6 +54,12 @@ if [[ "$1" == "-c" ]]; then
     shift; shift
 fi
 
+BLOCK_MAP_FILE=
+if [[ "$1" == "-B" ]]; then
+    BLOCK_MAP_FILE=$2
+    shift; shift
+fi
+
 BLOCK_SIZE=131072
 if [[ "$1" == "-b" ]]; then
     BLOCK_SIZE=$2
@@ -73,6 +79,12 @@ if [[ "$1" == "-zo" ]]; then
     shift; shift
 fi
 
+DISABLE_4K_ALIGN=false
+if [[ "$1" == "-a" ]]; then
+    DISABLE_4K_ALIGN=true
+    shift;
+fi
+
 OPT=""
 if [ -n "$MOUNT_POINT" ]; then
   OPT="$OPT -mount-point $MOUNT_POINT"
@@ -86,11 +98,17 @@ fi
 if [ -n "$FILE_CONTEXTS" ]; then
   OPT="$OPT -context-file $FILE_CONTEXTS"
 fi
+if [ -n "$BLOCK_MAP_FILE" ]; then
+  OPT="$OPT -block-map $BLOCK_MAP_FILE"
+fi
 if [ -n "$BLOCK_SIZE" ]; then
   OPT="$OPT -b $BLOCK_SIZE"
 fi
+if [ "$DISABLE_4K_ALIGN" = true ]; then
+  OPT="$OPT -disable-4k-align"
+fi
 
-MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp $COMPRESSOR $COMPRESSOR_OPT -no-exports -noappend -no-recovery -android-fs-config $OPT"
+MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp $COMPRESSOR $COMPRESSOR_OPT -no-exports -noappend -no-recovery -no-fragments -no-duplicates -android-fs-config $OPT"
 echo $MAKE_SQUASHFS_CMD
 $MAKE_SQUASHFS_CMD
 
index eb2ead2..fa7629c 100644 (file)
@@ -20,7 +20,6 @@ include $(CLEAR_VARS)
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
 LOCAL_MODULE_TAGS := eng tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativebenchmark
 
 LOCAL_STATIC_LIBRARIES += \
     libtestUtil
@@ -37,4 +36,4 @@ LOCAL_C_INCLUDES += \
 LOCAL_MODULE := binderAddInts
 LOCAL_SRC_FILES := binderAddInts.cpp
 
-include $(BUILD_EXECUTABLE)
+include $(BUILD_NATIVE_BENCHMARK)
index f061f7c..afa464a 100644 (file)
  */
 
 /*
- * Binder add integers benchmark
+ * Binder add integers benchmark (Using google-benchmark library)
  *
- * Measures the rate at which a short binder IPC operation can be
- * performed.  The operation consists of the client sending a parcel
- * that contains two integers.  For each parcel that the server
- * receives, it adds the two integers and sends the sum back to
- * the client.
- *
- * This benchmark supports the following command-line options:
- *
- *   -c cpu - bind client to specified cpu (default: unbound)
- *   -s cpu - bind server to specified cpu (default: unbound)
- *   -n num - perform IPC operation num times (default: 1000)
- *   -d time - delay specified amount of seconds after each
- *             IPC operation. (default 1e-3)
  */
 
 #include <cerrno>
 #include <grp.h>
 #include <iostream>
+#include <iomanip>
 #include <libgen.h>
 #include <time.h>
 #include <unistd.h>
@@ -48,6 +36,9 @@
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
+
+#include <benchmark/benchmark.h>
+
 #include <utils/Log.h>
 #include <testUtil.h>
 
@@ -61,13 +52,11 @@ String16 serviceName("test.binderAddInts");
 struct options {
     int serverCPU;
     int clientCPU;
-    unsigned int iterations;
     float        iterDelay; // End of iteration delay in seconds
 } options = { // Set defaults
     unbound, // Server CPU
     unbound, // Client CPU
-    1000,    // Iterations
-    1e-3,    // End of iteration delay
+    0.0,    // End of iteration delay
 };
 
 class AddIntsService : public BBinder
@@ -89,137 +78,13 @@ class AddIntsService : public BBinder
 };
 
 // File scope function prototypes
-static void server(void);
-static void client(void);
+static bool server(void);
+static void BM_client(benchmark::State& state);
 static void bindCPU(unsigned int cpu);
 static ostream &operator<<(ostream &stream, const String16& str);
 static ostream &operator<<(ostream &stream, const cpu_set_t& set);
 
-int main(int argc, char *argv[])
-{
-    int rv;
-
-    // Determine CPUs available for use.
-    // This testcase limits its self to using CPUs that were
-    // available at the start of the benchmark.
-    cpu_set_t availCPUs;
-    if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
-        cerr << "sched_getaffinity failure, rv: " << rv
-            << " errno: " << errno << endl;
-        exit(1);
-    }
-
-    // Parse command line arguments
-    int opt;
-    while ((opt = getopt(argc, argv, "s:c:n:d:?")) != -1) {
-        char *chptr; // character pointer for command-line parsing
-
-        switch (opt) {
-        case 'c': // client CPU
-        case 's': { // server CPU
-            // Parse the CPU number
-            int cpu = strtoul(optarg, &chptr, 10);
-            if (*chptr != '\0') {
-                cerr << "Invalid cpu specified for -" << (char) opt
-                    << " option of: " << optarg << endl;
-                exit(2);
-            }
-
-            // Is the CPU available?
-            if (!CPU_ISSET(cpu, &availCPUs)) {
-                cerr << "CPU " << optarg << " not currently available" << endl;
-                cerr << "  Available CPUs: " << availCPUs << endl;
-                exit(3);
-            }
-
-            // Record the choice
-            *((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
-            break;
-        }
-
-        case 'n': // iterations
-            options.iterations = strtoul(optarg, &chptr, 10);
-            if (*chptr != '\0') {
-                cerr << "Invalid iterations specified of: " << optarg << endl;
-                exit(4);
-            }
-            if (options.iterations < 1) {
-                cerr << "Less than 1 iteration specified by: "
-                    << optarg << endl;
-                exit(5);
-            }
-            break;
-
-        case 'd': // Delay between each iteration
-            options.iterDelay = strtod(optarg, &chptr);
-            if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
-                cerr << "Invalid delay specified of: " << optarg << endl;
-                exit(6);
-            }
-            break;
-
-        case '?':
-        default:
-            cerr << basename(argv[0]) << " [options]" << endl;
-            cerr << "  options:" << endl;
-            cerr << "    -s cpu - server CPU number" << endl;
-            cerr << "    -c cpu - client CPU number" << endl;
-            cerr << "    -n num - iterations" << endl;
-            cerr << "    -d time - delay after operation in seconds" << endl;
-            exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
-        }
-    }
-
-    // Display selected options
-    cout << "serverCPU: ";
-    if (options.serverCPU == unbound) {
-        cout << " unbound";
-    } else {
-        cout << options.serverCPU;
-    }
-    cout << endl;
-    cout << "clientCPU: ";
-    if (options.clientCPU == unbound) {
-        cout << " unbound";
-    } else {
-        cout << options.clientCPU;
-    }
-    cout << endl;
-    cout << "iterations: " << options.iterations << endl;
-    cout << "iterDelay: " << options.iterDelay << endl;
-
-    // Fork client, use this process as server
-    fflush(stdout);
-    switch (pid_t pid = fork()) {
-    case 0: // Child
-        client();
-        return 0;
-
-    default: // Parent
-        server();
-
-        // Wait for all children to end
-        do {
-            int stat;
-            rv = wait(&stat);
-            if ((rv == -1) && (errno == ECHILD)) { break; }
-            if (rv == -1) {
-                cerr << "wait failed, rv: " << rv << " errno: "
-                    << errno << endl;
-                perror(NULL);
-                exit(8);
-            }
-        } while (1);
-        return 0;
-
-    case -1: // Error
-        exit(9);
-    }
-
-    return 0;
-}
-
-static void server(void)
+static bool server(void)
 {
     int rv;
 
@@ -230,63 +95,62 @@ static void server(void)
         new AddIntsService(options.serverCPU))) != 0) {
         cerr << "addService " << serviceName << " failed, rv: " << rv
             << " errno: " << errno << endl;
+        return false;
     }
 
     // Start threads to handle server work
     proc->startThreadPool();
+    return true;
 }
 
-static void client(void)
+static void BM_client(benchmark::State& state)
 {
+    server();
     int rv;
     sp<IServiceManager> sm = defaultServiceManager();
-    double min = FLT_MAX, max = 0.0, total = 0.0; // Time in seconds for all
-                                                  // the IPC calls.
 
     // If needed bind to client CPU
     if (options.clientCPU != unbound) { bindCPU(options.clientCPU); }
 
     // Attach to service
     sp<IBinder> binder;
-    do {
+    for (int i = 0; i < 3; i++) {
         binder = sm->getService(serviceName);
         if (binder != 0) break;
         cout << serviceName << " not published, waiting..." << endl;
         usleep(500000); // 0.5 s
-    } while(true);
+    }
 
-    // Perform the IPC operations
-    for (unsigned int iter = 0; iter < options.iterations; iter++) {
+    if (binder == 0) {
+        cout << serviceName << " failed to publish, aborting" << endl;
+        return;
+    }
+
+    unsigned int iter = 0;
+    // Perform the IPC operations in the benchmark
+    while (state.KeepRunning()) {
         Parcel send, reply;
 
         // Create parcel to be sent.  Will use the iteration cound
         // and the iteration count + 3 as the two integer values
         // to be sent.
+        state.PauseTiming();
         int val1 = iter;
         int val2 = iter + 3;
         int expected = val1 + val2;  // Expect to get the sum back
         send.writeInt32(val1);
         send.writeInt32(val2);
-
+        state.ResumeTiming();
         // Send the parcel, while timing how long it takes for
         // the answer to return.
-        struct timespec start;
-        clock_gettime(CLOCK_MONOTONIC, &start);
         if ((rv = binder->transact(AddIntsService::ADD_INTS,
             send, &reply)) != 0) {
             cerr << "binder->transact failed, rv: " << rv
                 << " errno: " << errno << endl;
             exit(10);
         }
-        struct timespec current;
-        clock_gettime(CLOCK_MONOTONIC, &current);
-
-        // Calculate how long this operation took and update the stats
-        struct timespec deltaTimespec = tsDelta(&start, &current);
-        double delta = ts2double(&deltaTimespec);
-        min = (delta < min) ? delta : min;
-        max = (delta > max) ? delta : max;
-        total += delta;
+
+        state.PauseTiming();
         int result = reply.readInt32();
         if (result != (int) (iter + iter + 3)) {
             cerr << "Unexpected result for iteration " << iter << endl;
@@ -295,14 +159,11 @@ static void client(void)
         }
 
         if (options.iterDelay > 0.0) { testDelaySpin(options.iterDelay); }
+        state.ResumeTiming();
     }
-
-    // Display the results
-    cout << "Time per iteration min: " << min
-        << " avg: " << (total / options.iterations)
-        << " max: " << max
-        << endl;
 }
+BENCHMARK(BM_client);
+
 
 AddIntsService::AddIntsService(int cpu): cpu_(cpu) {
     if (cpu != unbound) { bindCPU(cpu); }
@@ -382,3 +243,68 @@ static ostream &operator<<(ostream &stream, const cpu_set_t& set)
 
     return stream;
 }
+
+int main(int argc, char *argv[])
+{
+    int rv;
+    ::benchmark::Initialize(&argc, argv);
+    // Determine CPUs available for use.
+    // This testcase limits its self to using CPUs that were
+    // available at the start of the benchmark.
+    cpu_set_t availCPUs;
+    if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
+        cerr << "sched_getaffinity failure, rv: " << rv
+            << " errno: " << errno << endl;
+        exit(1);
+    }
+
+    // Parse command line arguments
+    int opt;
+    while ((opt = getopt(argc, argv, "s:c:d:?")) != -1) {
+        char *chptr; // character pointer for command-line parsing
+
+        switch (opt) {
+        case 'c': // client CPU
+        case 's': { // server CPU
+            // Parse the CPU number
+            int cpu = strtoul(optarg, &chptr, 10);
+            if (*chptr != '\0') {
+                cerr << "Invalid cpu specified for -" << (char) opt
+                    << " option of: " << optarg << endl;
+                exit(2);
+            }
+
+            // Is the CPU available?
+            if (!CPU_ISSET(cpu, &availCPUs)) {
+                cerr << "CPU " << optarg << " not currently available" << endl;
+                cerr << "  Available CPUs: " << availCPUs << endl;
+                exit(3);
+            }
+
+            // Record the choice
+            *((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
+            break;
+        }
+
+        case 'd': // delay between each iteration
+            options.iterDelay = strtod(optarg, &chptr);
+            if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
+                cerr << "Invalid delay specified of: " << optarg << endl;
+                exit(6);
+            }
+            break;
+
+        case '?':
+        default:
+            cerr << basename(argv[0]) << " [options]" << endl;
+            cerr << "  options:" << endl;
+            cerr << "    -s cpu - server CPU number" << endl;
+            cerr << "    -c cpu - client CPU number" << endl;
+            cerr << "    -d time - delay after operation in seconds" << endl;
+            exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
+        }
+    }
+
+    ::benchmark::RunSpecifiedBenchmarks();
+}
+
index 3b2f446..9c3eadc 100755 (executable)
@@ -38,7 +38,7 @@ function convert {
 
 
 case $DEVICE in
-(shamu|hammerhead|bullhead)
+(shamu|hammerhead|bullhead|ariel)
        # no scaling necessary
        xmax=0
        ymax=0;;
diff --git a/tests/workloads/chromefling.sh b/tests/workloads/chromefling.sh
new file mode 100755 (executable)
index 0000000..db6aa2e
--- /dev/null
@@ -0,0 +1,153 @@
+#
+# Script to start 3 chrome tabs, fling each of them, repeat
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+iterations=10
+startapps=1
+capturesystrace=0
+waittime=4
+app=chrome
+
+function processLocalOption {
+       ret=0
+       case "$1" in
+       (-N) startapps=0;;
+       (-A) unset appList;;
+       (-L) appList=$2; shift; ret=1;;
+       (-T) capturesystrace=1;;
+       (-W) waittime=$2; shift; ret=1;;
+       (*)
+               echo "$0: unrecognized option: $1"
+               echo; echo "Usage: $0 [options]"
+               echo "-A : use all known applications"
+               echo "-L applist : list of applications"
+               echo "   default: $appList"
+               echo "-N : no app startups, just fling"
+               echo "-g : generate activity strings"
+               echo "-i iterations"
+               echo "-T : capture systrace on each iteration"
+               echo "-d device : device type (shamu, volantis, bullhead,...)"
+               exit 1;;
+       esac
+       return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(hammerhead)
+       flingtime=300
+       downCount=2
+       upCount=6
+       UP="70 400 70 100 $flingtime"
+       DOWN="70 100 70 400 $flingtime";;
+(shamu)
+       flingtime=100
+       downCount=2 
+       upCount=2
+       UP="700 1847 700 400 $flingtime"
+       DOWN="700 400 700 1847 $flingtime";;
+(angler)
+       flingtime=150
+       downCount=4
+       upCount=3
+       UP="500 1200 500 550 $flingtime"
+       DOWN="500 550 500 1200 $flingtime";;
+(bullhead|volantis)
+       flingtime=200
+       downCount=5
+       upCount=5
+       UP="500 1400 500 400 $flingtime"
+       DOWN="500 400 500 1400 $flingtime";;
+(ariel)
+       flingtime=200
+       downCount=5
+       upCount=5
+       UP="500 1560 500 530 $flingtime"
+       DOWN="500 530 500 1560 $flingtime";;
+(*)
+       echo "Error: No display information available for $DEVICE"
+       exit 1;;
+esac
+
+function swipe {
+       count=0
+       while [ $count -lt $2 ]
+       do
+               doSwipe $1
+               ((count=count+1))
+       done
+       sleep 1
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames $(getPackageName $app)
+
+while [ $cur -le $iterations ]
+do
+       if [ $capturesystrace -gt 0 ]; then
+               ${ADB}atrace --async_start -z -c -b 16000 freq gfx view idle sched
+       fi
+       t=$(startActivity $app)
+       sleep $waittime
+       swipe "$UP" $upCount
+
+       sleep $waittime
+       swipe "$DOWN" $downCount
+
+       doKeyevent BACK
+       sleep 0.5
+
+       if [ $capturesystrace -gt 0 ]; then
+               ${ADB}atrace --async_dump -z -c -b 16000 freq gfx view idle sched > trace.${cur}.out
+       fi
+       doKeyevent HOME
+       sleep 0.5
+
+       set -- $(getJankyFrames $(getPackageName $app))
+       totalDiff=$1
+       jankyDiff=$2
+       latency90=$3
+       latency95=$4
+       latency99=$5
+       if [ ${totalDiff:=0} -eq 0 ]; then
+               echo Error: could not read frame info with \"dumpsys gfxinfo\"
+               exit 1
+       fi
+
+       ((frameSum=frameSum+totalDiff))
+       ((jankSum=jankSum+jankyDiff))
+       ((latency90Sum=latency90Sum+latency90))
+       ((latency95Sum=latency95Sum+latency95))
+       ((latency99Sum=latency99Sum+latency99))
+       if [ "$totalDiff" -eq 0 ]; then
+               echo Error: no frames detected. Is the display off?
+               exit 1
+       fi
+       ((jankPct=jankyDiff*100/totalDiff))
+       resetJankyFrames $(getPackageName $app)
+
+
+       echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+       ((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)
index a2b7138..400722b 100755 (executable)
@@ -10,21 +10,25 @@ generateActivities=0
 
 # default activities. Can dynamically generate with -g.
 gmailActivity='com.google.android.gm/com.google.android.gm.ConversationListActivityGmail'
+clockActivity='com.google.android.deskclock/com.android.deskclock.DeskClock'
 hangoutsActivity='com.google.android.talk/com.google.android.talk.SigningInActivity'
 chromeActivity='com.android.chrome/_not_used'
+contactsActivity='com.google.android.contacts/com.android.contacts.activities.PeopleActivity'
 youtubeActivity='com.google.android.youtube/com.google.android.apps.youtube.app.WatchWhileActivity'
 cameraActivity='com.google.android.GoogleCamera/com.android.camera.CameraActivity'
 playActivity='com.android.vending/com.google.android.finsky.activities.MainActivity'
 feedlyActivity='com.devhd.feedly/com.devhd.feedly.Main'
-photosActivity='com.google.android.apps.plus/com.google.android.apps.photos.phone.PhotosHomeActivity'
+photosActivity='com.google.android.apps.photos/com.google.android.apps.photos.home.HomeActivity'
 mapsActivity='com.google.android.apps.maps/com.google.android.maps.MapsActivity'
 calendarActivity='com.google.android.calendar/com.android.calendar.AllInOneActivity'
 earthActivity='com.google.earth/com.google.earth.EarthActivity'
-calculatorActivity='com.android.calculator2/com.android.calculator2.Calculator'
+calculatorActivity='com.google.android.calculator/com.android.calculator2.Calculator'
+calculatorLActivity='com.android.calculator2/com.android.calculator2.Calculator'
 sheetsActivity='com.google.android.apps.docs.editors.sheets/com.google.android.apps.docs.app.NewMainProxyActivity'
 docsActivity='com.google.android.apps.docs.editors.docs/com.google.android.apps.docs.app.NewMainProxyActivity'
 operaActivity='com.opera.mini.native/com.opera.mini.android.Browser'
 firefoxActivity='org.mozilla.firefox/org.mozilla.firefox.App'
+suntempleActivity='com.BrueComputing.SunTemple/com.epicgames.ue4.GameActivity'
 homeActivity='com.google.android.googlequicksearchbox/com.google.android.launcher.GEL'
 
 function showUsage {
@@ -93,10 +97,24 @@ else
        fi
        deviceName=$1
        ADB="adb -s $deviceName shell "
-       DEVICE=$(echo $4 | sed 's/product://')
+       if [ "$DEVICE" = "" -o "$DEVICE" = unknown ]; then
+               DEVICE=$(echo $4 | sed 's/product://')
+       fi
        isOnDevice=0
 fi
 
+if [ $isOnDevice -gt 0 ]; then
+       case "$DEVICE" in
+       (bullhead|angler)
+               if ! echo $$ > /dev/cpuset/background/tasks; then
+                       echo Could not put PID $$ in background
+               fi
+               ;;
+       (*)
+               ;;
+       esac
+fi
+
 # default values if not set by options or calling script
 appList=${appList:=$dfltAppList}
 savetmpfiles=${savetmpfiles:=0}
@@ -109,7 +127,9 @@ ADB=${ADB:=""}
 output=${output:="./out"}
 
 # clear the output file
-> $output
+if [ -f $output ]; then
+       > $output
+fi
 
 # ADB commands
 AM_FORCE_START="${ADB}am start -W -S"
@@ -162,6 +182,7 @@ function log2msec {
        in=$1
        in=${in:=0.0}
        set -- $(echo $in | tr . " ")
+
        # shell addition via (( )) doesn't like leading zeroes in msecs
        # field so remove leading zeroes
        msecfield=$(expr 0 + $2)
@@ -209,7 +230,7 @@ function getEndTime {
 
 function resetJankyFrames {
        _gfxapp=$1
-       _gfxapp=${app:="com.android.systemui"}
+       _gfxapp=${_gfxapp:="com.android.systemui"}
        ${ADB}dumpsys gfxinfo $_gfxapp reset 2>&1 >/dev/null
 }
 
@@ -256,14 +277,21 @@ function checkForDirectReclaim {
 }
 
 function startInstramentation {
+       _iter=$1
+       _iter=${_iter:=0}
+       enableAtrace=$2
+       enableAtrace=${enableAtrace:=1}
        # Called at beginning of loop. Turn on instramentation like atrace
        vout start instramentation $(date)
        echo =============================== >> $output
-       echo Before iteration >> $output
+       echo Before iteration $_iter >> $output
        echo =============================== >> $output
        ${ADB}cat /proc/meminfo 2>&1 >> $output
        ${ADB}dumpsys meminfo 2>&1 >> $output
-       if [ "$user" = root ]; then
+       if [ "$DEVICE" = volantis ]; then
+               ${ADB}cat /d/nvmap/iovmm/procrank 2>&1 >> $output
+       fi
+       if [ "$user" = root -a $enableAtrace -gt 0 ]; then
                vout ${ADB}atrace -b 32768 --async_start $tracecategories
                ${ADB}atrace -b 32768 --async_start $tracecategories >> $output
                echo >> $output
@@ -271,14 +299,15 @@ function startInstramentation {
 }
 
 function stopInstramentation {
-       if [ "$user" = root ]; then
+       enableAtrace=$1
+       enableAtrace=${enableAtrace:=1}
+       if [ "$user" = root -a $enableAtrace -gt 0 ]; then
                vout ${ADB}atrace --async_stop
                ${ADB}atrace --async_stop > /dev/null
        fi
 }
 
 function stopAndDumpInstramentation {
-       # Called at beginning of loop. Turn on instramentation like atrace
        vout stop instramentation $(date)
        echo =============================== >> $output
        echo After iteration >> $output
@@ -300,9 +329,9 @@ function stopAndDumpInstramentation {
                        python $UNCOMPRESS $tmpTrace >> $traceout
                        rm -f $tmpTrace
                else
-                       ${ADB}atrace $zarg -b 32768 --async_dump >> $traceout
+                       ${ADB}atrace -b 32768 --async_dump > $traceout
                fi
-               vout ${ADB}atrace $zarg --async_dump
+               vout ${ADB}atrace $zarg -b 32768 --async_dump
                vout ${ADB}atrace --async_stop
                ${ADB}atrace --async_stop > /dev/null
        fi
@@ -336,7 +365,7 @@ function startActivity {
                echo 0
                return 0
        elif [ "$1" = chrome ]; then
-               if [ "$DEVICE" = volantis ]; then
+               if [ "$DEVICE" = volantis -o "$DEVICE" = ariel ]; then
                        vout $AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com
                        $AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com > /dev/null
                        set -- 0 0
@@ -375,7 +404,14 @@ function checkActivity {
 
 function doSwipe {
        vout ${ADB}input swipe $*
-       ${ADB}input swipe $*
+       ${ADB}nice input swipe $*
+}
+
+function doText {
+       echo $* > ./tmpOutput
+       vout ${ADB}input text \"$*\"
+       ${ADB}input text "$(cat ./tmpOutput)"
+       rm -f ./tmpOutput
 }
 
 function doTap {
diff --git a/tests/workloads/powerave.py b/tests/workloads/powerave.py
new file mode 100755 (executable)
index 0000000..4d786b9
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+
+import sys
+import getopt
+
+def usage():
+    print "powersum.py [OPTIONS] HZ VOLTAGE [FILE]"
+    print "OPTIONS: "
+    print "-o OFFSET: subtract OFFSET from all data points"
+    print "\nHZ: samples per second in FILE or stdin"
+    sys.exit(0)
+
+offset = 0.0
+voltage = 4.3
+
+parsedargv,argvrem = getopt.getopt(sys.argv[1:], "vo:w:l:h", ["help"])
+for o,a in parsedargv:
+    if o == '-o': offset = float(a)
+    if o == '-h' or o == '--help': usage()
+
+hz = float(argvrem[0])
+voltage = float(argvrem[1])
+if len(argvrem) > 1:
+    f = open(argvrem[2], "r")
+else:
+    f = sys.stdin
+
+totalpower = 0.0
+samplectr = 0
+
+for line in f:
+    try:
+        val = float(line.split(" ")[1]) # xxx take 2nd arg in line
+        val -= offset
+    except:
+        print "Can't parse data line, did you remember the timestamp?"
+        print "data was: %s" % line
+        sys.exit(1)
+
+    samplectr+=1
+    totalpower += val/hz
+
+avecurrent = totalpower * hz *1000 / samplectr
+avepower = avecurrent * voltage
+
+print "%.3f %.3f" % (avecurrent, avepower)
diff --git a/tests/workloads/pwrsummary.sh b/tests/workloads/pwrsummary.sh
new file mode 100755 (executable)
index 0000000..3d3aeb8
--- /dev/null
@@ -0,0 +1,235 @@
+# print summary of output generated by pwrtest.sh
+#
+# default results directories are <device>-<date>[-experiment]. By default
+# match any device and the year 201*.
+#
+# Examples:
+#
+# - show output for all bullhead tests in july 2015:
+#    ./pwrsummary.sh -r "bh-201507*"
+#
+# - generate CSV file for import into spreadsheet:
+#    ./pwrsummary.sh -o csv
+#
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+cd $CMDDIR
+CMDDIR=$(pwd)
+cd -
+POWERAVE="python $CMDDIR/powerave.py"
+
+defaultPattern="*-201*"
+defaultVoltage=4.3
+defaultFrequency=5
+
+function Usage {
+       echo "$0 [-o format] [-v voltage] [-h freq] [-f resultsDirectories]"
+}
+
+while [ $# -gt 0 ]
+do
+       case "$1" in
+       (-o) format=$2; shift;;
+       (-v) voltage=$2; shift;;
+       (-h) hz=$2; shift;;
+       (-r) testResults="$2"; shift;;
+       (--help) Usage; exit 0;;
+       (--) shift; break;;
+       (*)
+               echo Unknown option: $1
+               Usage
+               exit 1;;
+       esac
+       shift
+done
+
+testResults=${testResults:=$defaultPattern}
+voltage=${voltage:=$defaultVoltage}
+hz=${hz:=$defaultFrequency}
+
+function printHeader {
+       workload=$1
+       units="unknown"
+       case $workload in
+       (suntemple|shadowgrid2)
+               units="FPS";;
+       (recentfling|youtube|chrome)
+               units="FPS from app point of view: 1/(90th percentile render time)";;
+       (sysapps)
+               units="App start/switch per second";;
+       esac
+
+       echo "Performance unit for $workload is: $units"
+       if [ "$format" = csv ]; then
+               printf "%s,%s,%s,%s,%s,%s,%s,%s,%s\n" " " build min ave max net-mA@${voltage}v base-mW net-mW perf/W
+       else
+               printf "%-30s %-8s %12.12s %12.12s %12.12s %12.12s %12.12s %12.12s %12.12s\n" " " build min ave max net-mA@${voltage}v base-mW net-mW perf/W
+       fi
+}
+
+function average {
+       awk 'BEGIN { count=0; sum=0; max=-1000000000; min=1000000000; }
+       {
+               cur = $1;
+               sum = sum + cur; 
+               if (cur > max) max = cur;
+               if (cur < min) min = cur;
+               count++;
+       }
+
+       END {
+               if (count > 0) {
+                       ave = sum / count;
+                       printf "%.2f %.2f %.2f\n", min, ave, max; 
+               }
+       }'
+}
+
+function hwuiOutputParser {
+       # Stats since: 60659316905953ns
+       # Total frames rendered: 150
+       # Janky frames: 89 (59.33%)
+       # 90th percentile: 23ms
+       # 95th percentile: 27ms
+       # 99th percentile: 32ms
+       # Number Missed Vsync: 0
+       # Number High input latency: 0
+       # Number Slow UI thread: 0
+       # Number Slow bitmap uploads: 12
+       # Number Slow draw: 89
+       # use with "stdbuf -o0 " to disable pipe buffering
+       # stdbuf -o0 adb shell /data/hwuitest shadowgrid2 400 | stdbuf -o0 ./hwuitestfilter.sh  | tee t.csv
+       sed -e 's/ns//' -e 's/[\(\)%]/ /g' | awk '
+       BEGIN { startTime=0; lastTime=0; }
+       /^Stats since:/ {
+               curTime = $3;
+               if (startTime == 0) {
+                       startTime = curTime;
+               }
+               if (lastTime) {
+                       interval = curTime - lastTime;
+                       fps = totalFrames*1000000000 / interval;
+                       diffTime = curTime - startTime;
+                       printf "%.2f, %.2f, ",diffTime/1000000, fps;
+               }
+       }
+       /^Total frames/ { totalFrames=$4; }
+       /^Janky frames:/ {
+               if (lastTime) {
+                       printf "%.2f\n",$4; lastTime=curTime;
+               }
+               lastTime = curTime;
+       }'
+}
+
+function sysappOutputParser {
+       awk '
+       BEGIN { fmt=0; count=0; sum=0; }
+       /^App/ {
+               if (count != 0) {
+                       if (fmt > 2) printf "Ave: %0.2fms\n", sum/count;
+                       else printf " %0.2f\n", sum/count;
+                       count = 0;
+                       sum = 0;
+               }
+       }
+       /^[a-z]/ { val=$2; if (val != 0) { count++; sum+=val; } }
+       /^Iteration/ { if (fmt > 2) printf "%s : ", $0; else if (fmt) printf "%d ", $2; }
+       '
+}
+
+function calcPerfData {
+       testdir=$1
+       workload=$2
+       baselineCurrent=$3
+       baselinePower=$4
+
+       file=${workload}.out
+       powerfile=${workload}-power.out
+       build="$(cat build 2>/dev/null)"
+       build=${build:="Unknown"}
+
+       lines=$(wc -l $file 2>/dev/null | cut -f1 -d\ )
+
+       if [ ${lines:=0} -eq -0 ]; then
+               # No performance data captured
+               if [ "$format" = csv ]; then
+                       printf "%s,%s,%s\n" $testdir "$build" "no data"
+               else
+                       printf "%-30s %-8s %12.12s\n" $testdir "$build" "no data"
+               fi
+               return 1
+       fi
+
+       set -- $($POWERAVE $hz $voltage $powerfile)
+       current=$(echo $1 $baselineCurrent | awk '{ printf "%.2f", $1-$2; }')
+       power=$(echo $2 $baselinePower | awk '{ printf "%.2f", $1-$2; }')
+
+       case $workload in
+       (idle)
+               set -- 0 0 0
+               ;;
+       (suntemple)
+               # units are fps
+               set -- $(grep "FPS average" $file  | sed 's/^.*seconds for a //' | awk '{ print $1; }' | average)
+               ;;
+       (recentfling|youtube|chrome)
+               # units are ms, so need to convert to app/ms
+               set -- $(grep ^Frames:  $file | tr "/" " " | awk '{ print $4; }' | average | awk '{ printf "%.3f %.3f %.3f\n", 1000/$3, 1000/$2, 1000/$1;}'  )
+               ;;
+       (sysapps)
+               # units are ms, so need to convert to app/ms
+               set -- $(cat $file | sysappOutputParser | average | awk '{ printf "%.3f %.3f %.3f\n", 1000/$3, 1000/$2, 1000/$1;}'  )
+               ;;
+       (shadowgrid2)
+               # units are fps
+               set -- $(cat $file | hwuiOutputParser | tr ',' ' ' | awk '{print $2;}' | average)
+               ;;
+       esac
+
+       minperf=$1
+       aveperf=$2
+       maxperf=$3
+       perfPerWatt=$(echo $aveperf $power | awk '{ if ($2) { val=$1*1000/$2; printf "%.3f\n", val; } else print "unknown"; }')
+       if [ "$format" = csv ]; then
+               printf "%s,%s,%f,%f,%f,%f,%f,%f," $testdir "$build" $minperf $aveperf $maxperf $current $baselinePower $power
+               printf "%s\n" $perfPerWatt
+       else
+               printf "%-30s %-8s %12.2f %12.2f %12.2f %12.2f %12.2f %12.2f " $testdir "$build" $minperf $aveperf $maxperf $current $baselinePower $power
+               printf "%12s\n" $perfPerWatt
+       fi
+}
+
+function calcBaselinePower {
+       workload=$1
+       defaultPowerFile="idle-display-power.out"
+       powerFile=$defaultPowerFile
+       case $workload in
+       (shadowgrid2|suntemple|recentfling)
+               powerFile="idle-airplane-display-power.out"
+               if [ ! -f  $powerFile ]; then
+                       powerFile=$defaultPowerFile
+               fi;;
+       esac
+       if [ -f  $powerFile ]; then
+               $POWERAVE 5 4.3 $powerFile
+       fi
+}
+
+for t in $(cat tests)
+do
+       echo .======================= $t ================================
+       printHeader $t
+       for i in $testResults
+       do
+               cd $i
+               baseline="$(calcBaselinePower $t)"
+               if [ "$baseline" != "" ]; then
+                       calcPerfData $i $t $baseline
+               else
+                       echo "$i : no baseline current"
+               fi
+               cd - > /dev/null
+       done
+done
diff --git a/tests/workloads/pwrtest.sh b/tests/workloads/pwrtest.sh
new file mode 100755 (executable)
index 0000000..39f7b11
--- /dev/null
@@ -0,0 +1,365 @@
+# Script to gather perf and perf/watt data for several workloads
+#
+# Setup:
+#
+# - device connected to monsoon with USB passthrough enabled
+# - network enabled (baseline will be measured and subtracted
+#   from results) (network needed for chrome, youtube tests)
+# - the device is rebooted after each test (can be inhibited
+#   with "-r 0")
+#
+# Default behavior is to run each of the known workloads for
+# 30 minutes gathering both performance and power data.
+#
+# The default time can be overridden with the -t option. To
+# change individual test times, a config file can be specifed
+# via -f with times for individual tests. Example file contents:
+#
+#      idleTime=60
+#      recentflingTime=60
+#      chromeTime=60
+#      youtubeTime=0
+#      sysappsTime=60
+#      suntempleTime=5
+#
+# Output goes to the current directory.
+#
+# Examples:
+#
+# - Run all tests for 15 minutes (default is 30): ./pwrtest.sh -t 15 -R MDA20
+#
+# - Use a config file for test times: ./pwrtest.sh -f ./myconfig -R MDA20
+#
+# - Use a init file to setup device tuneables after each restart (this is
+#   a bash script which should include adb commands to set up device):
+#     ./pwrtest.sh -F devtunables
+#
+
+defaultTime=30
+garbageminutes=8
+
+function Usage {
+       echo "Usage: $0 [OPTIONS]"
+       echo "-d device : device type (shamu, bullhead, ...)"
+       echo "-f configFile : config file to override individual test times"
+       echo "-g garbageMinutes : time to skip power measurement at beginning of test"
+       echo "                    default=$garbagetime minutes"
+       echo "-r restart : 0=no reboot between tests, 1=reboot (default)"
+       echo "-t defaultTimeMin : default time to run each test"
+       echo "                    default=$defaultTime minutes"
+       echo "-D cmddir : directory to find defs.sh"
+       echo "-F restartHookFile : file of commands to set device tunables after restart (optional)"
+       echo "-R release : release running on device (MDA20, 2054728, etc)"
+}
+
+restart=1
+hz=5
+shadowgrid2TimeMax=25
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+
+MONSOON=monsoon.par
+
+while [ $# -gt 0 ]
+do
+       case "$1" in
+       (-D) CMDDIR=$2; shift;;
+       (-r) restart=$2; shift;;
+       (-t) defaultTime=$2; shift;;
+       (-F) restartfile=$2; shift;;
+       (-g) garbageminutes=$2; shift;;
+       (-f)
+               configFile=$2;
+               echo "Reading configs from $configFile..."
+               . ./$configFile
+               shift;;
+       (-R) echo $2 > ./build; shift;;
+       (--) ;;
+       (--help)
+               Usage
+               exit 0;;
+       (*)
+               echo "Unknown option: $1"
+               Usage
+               exit 1;;
+       esac
+       shift
+done
+
+. $CMDDIR/defs.sh --
+
+devdir="/data/local/tmp"
+suntempledir=${CMDDIR}/suntemple
+
+case $DEVICE in
+(shamu|hammerhead)
+       HWUITEST=hwuitest
+       onSwipe="700 1847 700 400 50"
+       ;;
+(*)
+       HWUITEST=hwuitest64
+       onSwipe="500 1200 500 550 150"
+       ;;
+esac
+
+scripts="defs.sh systemapps.sh recentfling.sh youtube.sh chromefling.sh $HWUITEST"
+
+if ! $MONSOON >/dev/null 2>&1; then
+       echo $MONSOON must be in your PATH >&2
+       exit 1
+fi
+
+function usbpassthru {
+       if [ "$1" = off ]; then
+               state=off
+       else
+               state=on
+       fi
+       echo Setting usb pass-thru to $state
+       monsoon.par --usbpassthrough=$state
+}
+
+function pwrcollect {
+       collectmin=$1
+       collectmin=${collectmin:=60}
+       # samples = hz * 60 * minutes
+       ((samples=5*60*collectmin))
+       monsoon.par --timestamp --samples $samples --hz 5
+}
+
+function copy_files {
+       adb shell mkdir -p $devdir
+       for file in $scripts
+       do
+               adb push $CMDDIR/$file $devdir
+       done
+}
+
+function install_suntemple {
+       echo Checking for suntemple installation...
+       #stdest=/storage/sdcard0/obb/com.BrueComputing.SunTemple
+       stdest=/storage/emulated/0/obb/com.BrueComputing.SunTemple
+       dircontents=$(adb ls $stdest 2>/dev/null)
+       if [ "$dircontents" = "" ]; then
+               echo Installing suntemple...
+               adb install $suntempledir/*.apk
+               adb shell mkdir -p $stdest
+               adb push $suntempledir/main*obb $stdest
+       else
+               echo dircontents=$dircontents
+               echo Suntemple already installed.
+       fi
+}
+
+function run_test {
+       testName=$1
+       collectMinutes=$2
+       collectOutput=${testName}-power-raw.out
+       powerOutput=${testName}-power.out
+       echo -----------------------------------------------------
+       echo TEST: $testName
+       echo enabled Cores $(adb shell "cat /sys/devices/system/cpu/online")
+       date
+       echo -----------------------------------------------------
+       usbpassthru off
+       pwrcollect $collectMinutes > $collectOutput 2>/dev/null
+       # take off the first 2min of samples
+       totalSamples=$(cat $collectOutput | wc -l)
+       # we throw away the first "garbageminutes" of the data
+       # since it is volatile
+       ((garbage=hz*60*garbageminutes))
+       ((remaining=totalSamples-garbage))
+       if [ $remaining -gt 0 ]; then
+               tail -$remaining $collectOutput > $powerOutput
+       else
+               cp $collectOutput $powerOutput
+       fi
+       echo power data for $testName copied to $collectOutput
+       usbpassthru on
+       sleep 10
+       adb devices
+       sleep 10
+}
+
+function start_job {
+       cmdline="$1"
+       echo Running $cmdline
+       (adb shell "cd $devdir && nohup $cmdline > test.out") &
+       sleep 5
+       kill %1 2>/dev/null
+}
+
+function cleanup_job {
+       testName=$1
+       processName=$2
+       processName=${processName:=" sh "}
+       set -- $(adb shell ps | tr "\r" " " | grep "$processName")
+       echo killing PID=$2...
+       adb shell kill $2
+       sleep 1
+       echo copying test output to $testName...
+       adb pull $devdir/test.out
+       mv test.out ${testName}.out
+       if [ $restart -gt 0 ]; then
+               restart_device
+       else
+               doKeyevent HOME
+       fi
+}
+
+function airplane_mode {
+       if [ "$1" = "on" ]; then
+               mode=true
+               setting=1
+       else
+               mode=false
+               setting=0
+       fi
+       adb shell settings put global airplane_mode_on $setting
+       adb shell am broadcast -a android.intent.action.AIRPLANE_MODE --ez state $mode
+       echo Set airplane mode to $mode
+}
+
+function restart_device {
+       adb reboot
+       echo Wait 60s for device to restart...
+       sleep 60
+       while ! adb root
+       do
+               echo Waiting for device to come up...
+               sleep 10
+       done
+       echo Wait 30s to complete boot activities...
+       sleep 30
+       echo Restart complete.
+       doTap 897 1075
+       sleep 2
+       doSwipe $onSwipe
+       restartfile=${restartfile:="./restarthook"}
+       if [ -f $restartfile ]; then
+               # hook to change tunables after a restart
+               . $restartfile
+       fi
+}
+
+usbpassthru on
+adb devices 2>/dev/null
+
+airplane_mode off
+if [ $restart -gt 0 ]; then
+       restart_device
+fi
+
+echo Copying $scripts to device $devdir...
+copy_files
+tests=""
+
+# measure background power
+idleTime=${idleTime:=$defaultTime}
+if [ $idleTime -gt 0 ]; then
+       echo Test 1 : measure idle power for $idleTime minutes
+       run_test idle $idleTime
+       airplane_mode on
+       echo Restarting for power baseline in airplane mode...
+       restart_device
+       run_test idle-airplane $idleTime
+       airplane_mode off
+       # the screen blanks after 30 minutes. The first 2 minutes of the test
+       # have already been filtered off. For our power baseline, keep the first
+       # 20 minutes of the results
+       ((twentyminutes=hz*20*60))
+       powerOutput="idle-power.out"
+       displayPowerOutput="idle-display-power.out"
+       airplanePowerOutput="idle-airplane-power.out"
+       airplaneDisplayPowerOutput="idle-airplane-display-power.out"
+       totalSamples=$(cat $powerOutput | wc -l)
+       if [ $twentyminutes -lt $totalSamples ]; then
+               head -$twentyminutes $powerOutput > $displayPowerOutput
+               head -$twentyminutes $airplanePowerOutput > $airplaneDisplayPowerOutput
+       else
+               cp $powerOutput $displayPowerOutput
+               cp $airplanePowerOutput $airplaneDisplayPowerOutput
+       fi
+       tests="$tests idle"
+fi
+
+recentflingTime=${recentflingTime:=$defaultTime}
+if [ $recentflingTime -gt 0 ]; then
+       echo $(date) Test 2 : recents fling for $recentflingTime minutes
+       airplane_mode on
+       adb shell "cd $devdir && ./systemapps.sh -A -T -i 1"
+       start_job "./recentfling.sh -N -i 1000 -d $DEVICE"
+       run_test recentfling $recentflingTime
+       cleanup_job recentfling
+       airplane_mode off
+       date
+       tests="$tests recentfling"
+fi
+
+suntempleTime=${suntempleTime:=$defaultTime}
+if [ $suntempleTime -gt 0 ]; then
+       echo $(date) Test 2 : run Sun Temple $suntempleTime minutes
+       airplane_mode on
+       install_suntemple
+       adb shell "am start $suntempleActivity"
+       run_test suntemple $suntempleTime
+       adb pull /sdcard/SunTemple/SunTemple/Saved/Logs/SunTemple.log
+       cleanup_job suntemple BrueComp
+       airplane_mode off
+       mv SunTemple.log suntemple.out
+       # grab the suntemple log
+       date
+       tests="$tests suntemple"
+fi
+
+chromeTime=${chromeTime:=$defaultTime}
+if [ $chromeTime -gt 0 ]; then
+       echo $(date) Test 3 : chrome fling for $chromeTime minutes
+       start_job "./chromefling.sh -i 1000 -d $DEVICE"
+       run_test chrome $chromeTime
+       cleanup_job chrome
+       date
+       tests="$tests chrome"
+fi
+
+shadowgrid2Time=${shadowgrid2Time:=$defaultTime}
+if [ $shadowgrid2Time -gt $shadowgrid2TimeMax ]; then
+       # we cap shadowgrid2 time since the display goes
+       # off after 30 minutes
+       $shadowgrid2Time=$shadowgrid2TimeMax
+fi
+if [ $shadowgrid2Time -gt 0 ]; then
+       airplane_mode on
+       echo $(date) Test 4 : shadowgrid2 for $shadowgrid2Time minutes
+       start_job "./$HWUITEST shadowgrid2 100000"
+       run_test shadowgrid2 $shadowgrid2Time
+       cleanup_job shadowgrid2 $HWUITEST
+       airplane_mode off
+       date
+       tests="$tests shadowgrid2"
+fi
+
+youtubeTime=${youtubeTime:=$defaultTime}
+if [ $youtubeTime -gt 0 ]; then
+       echo $(date) Test 5 : youtube for $youtubeTime minutes
+       start_job "./youtube.sh -i 1000 -d $DEVICE"
+       run_test youtube $youtubeTime
+       cleanup_job youtube
+       date
+       tests="$tests youtube"
+fi
+
+sysappsTime=${sysappsTime:=$defaultTime}
+if [ $sysappsTime -gt 0 ]; then
+       echo $(date) Test 6 : app switching for $sysappsTime minutes
+       start_job "./systemapps.sh -T -i 1000 -d $DEVICE"
+       run_test sysapps $sysappsTime
+       cleanup_job sysapps
+       date
+       tests="$tests sysapps"
+fi
+
+echo Ran tests: $tests
+echo $tests > tests
+
index 092c8d9..79713f3 100755 (executable)
@@ -18,10 +18,12 @@ function processLocalOption {
        (-A) unset appList;;
        (-L) appList=$2; shift; ret=1;;
        (-T) capturesystrace=1;;
+       (-B) echo $$ > /dev/cpuset/background/tasks;;
        (*)
                echo "$0: unrecognized option: $1"
                echo; echo "Usage: $0 [options]"
                echo "-A : use all known applications"
+               echo "-B : run in background cpuset"
                echo "-L applist : list of applications"
                echo "   default: $appList"
                echo "-N : no app startups, just fling"
@@ -44,6 +46,12 @@ case $DEVICE in
        upCount=6
        UP="70 400 70 100 $flingtime"
        DOWN="70 100 70 400 $flingtime";;
+(angler|ariel|mtp8996)
+       flingtime=150
+       downCount=4
+       upCount=3
+       UP="500 1200 500 550 $flingtime"
+       DOWN="500 550 500 1200 $flingtime";;
 (bullhead)
        flingtime=200
        downCount=5
@@ -122,7 +130,6 @@ do
        latency99=$5
        if [ ${totalDiff:=0} -eq 0 ]; then
                echo Error: could not read frame info with \"dumpsys gfxinfo\"
-               exit 1
        fi
 
        ((frameSum=frameSum+totalDiff))
@@ -132,7 +139,6 @@ do
        ((latency99Sum=latency99Sum+latency99))
        if [ "$totalDiff" -eq 0 ]; then
                echo Error: no frames detected. Is the display off?
-               exit 1
        fi
        ((jankPct=jankyDiff*100/totalDiff))
        resetJankyFrames
index a263e7d..6c0db35 100755 (executable)
 # Other options are described below.
 #
 iterations=1
-tracecategories="gfx view am input memreclaim"
+tracecategories="gfx am memreclaim"
 totaltimetest=0
 forcecoldstart=0
 waitTime=3.0
+memstats=0
 
-appList="gmail hangouts chrome youtube play home"
+appList="gmail maps chrome youtube play home"
 
 function processLocalOption {
        ret=0
@@ -38,6 +39,7 @@ function processLocalOption {
        (-L) appList=$2; shift; ret=1;;
        (-T) totaltimetest=1;;
        (-W) waitTime=$2; shift; ret=1;;
+       (-M) memstats=1;;
        (*)
                echo "$0: unrecognized option: $1"
                echo; echo "Usage: $0 [options]"
@@ -141,6 +143,7 @@ do
        if [ $iterations -gt 1 ]; then
                echo =========================================
                echo Iteration $cur of $iterations
+               date
                echo =========================================
        fi
        if [ $iterations -gt 1 -o $cur -eq 1 ]; then
@@ -160,8 +163,11 @@ do
                if [ $totaltimetest -eq 0 ]; then
                        tmpTraceOut="$tmpTraceOutBase-$app.out"
                        >$tmpTraceOut
-                       startInstramentation
+                       startInstramentation "$app-$cur"
                else
+                       if [ "$memstats" -gt 0 ]; then
+                               startInstramentation "$app-$cur" 0
+                       fi
                        if [ $appnum -eq 0 ]; then
                                printf "%-8s %5s(ms) %3s(ms) %s      %s\n" App Start Iter Jank Latency
                        fi
@@ -239,6 +245,8 @@ if [ $totaltimetest -gt 0 ]; then
        printf "%-10s %5.0f   %5.0f\n" TOTAL $totaltime $diffTime
 fi
 
+overallSum=0
+appCount=0
 if [ $iterations -gt 1 -a $totaltimetest -eq 0 ]; then
        echo
        echo =========================================
@@ -258,7 +266,14 @@ if [ $iterations -gt 1 -a $totaltimetest -eq 0 ]; then
                ((ave90=l90/iterations))
                ((ave95=l95/iterations))
                ((ave99=l99/iterations))
-               ((jankPct=100*janks/frames))
+               if [ $frames -gt 0 ]; then
+                       ((jankPct=100*janks/frames))
+               fi
                printf "%-12s %5d      %5d      %5d      %5d      %5d     %5d(%d%%) %d/%d/%d\n" $app $1 $ave $2 $4 $5 $janks $jankPct $ave90 $ave95 $ave99
+               ((overallSum=overallSum+ave))
+               ((appCount=appCount+1))
        done
+       if [ $appCount -gt 0 ]; then
+               printf "Average Start Time: %.2f\n", $(echo $overallSum $appCount | awk '{ printf "%.2f\n", $1/$2 }')
+       fi
 fi
diff --git a/tests/workloads/youtube.sh b/tests/workloads/youtube.sh
new file mode 100755 (executable)
index 0000000..558e53c
--- /dev/null
@@ -0,0 +1,149 @@
+#
+# Script to play a john oliver youtube video N times.
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+iterations=10
+app=youtube
+searchText="last week tonight with john oliver: online harassment"
+vidMinutes=15
+
+function processLocalOption {
+       ret=0
+       case "$1" in
+       (-S) searchText="$2"; shift;;
+       (-t) vidMinutes="$2"; shift;;
+       (*)
+               echo "$0: unrecognized option: $1"
+               echo; echo "Usage: $0 [options]"
+               echo "-i iterations"
+               echo "-S youtube search text"
+               echo "-d device"
+               echo "-t vidMinutes"
+               exit 1;;
+       esac
+       return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(angler)
+       searchButton="860 177"
+       selectFirstVideo="225 400"
+       enableControls="1000 610"
+       fullScreen="1011 632"
+       ;;
+(shamu)
+       searchButton="1200 160"
+       selectFirstVideo="480 653"
+       enableControls="1377 812"
+       fullScreen="1377 812"
+       ;;
+(bullhead|hammerhead)
+       searchButton="860 177"
+       selectFirstVideo="225 400"
+       enableControls="1000 610"
+       fullScreen="1011 632"
+       ;;
+(volantis)
+       searchButton="1356 93"
+       selectFirstVideo="378 264"
+       enableControls="1464 812"
+       fullScreen="1480 835"
+       ;;
+(ariel)
+       searchButton="1440 70"
+       selectFirstVideo="228 224"
+       enableControls="1528 880"
+       fullScreen="1528 880"
+       ;;
+
+(*)
+       echo "Error: No display information available for $DEVICE"
+       exit 1;;
+esac
+
+function swipe {
+       count=0
+       while [ $count -lt $2 ]
+       do
+               echo doSwipe...
+               doSwipe $1
+               ((count=count+1))
+       done
+       sleep 1
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames $(getPackageName $app)
+
+while [ $cur -le $iterations ]
+do
+       t=$(startActivity $app)
+       sleep 4.0
+       doTap $searchButton
+       sleep 1.0
+       doText "$searchText"
+       sleep 1.0
+       doKeyevent ENTER
+       sleep 5.0
+       doTap $selectFirstVideo
+       sleep 10.0
+       doTap $fullScreen
+       sleep 0.5
+       doTap $fullScreen
+       # 15 minutes
+       ((vidTime=60*vidMinutes))
+       sleep $vidTime
+       doKeyevent BACK
+       sleep 0.5
+       doKeyevent BACK
+       sleep 0.5
+       doKeyevent BACK
+       sleep 0.5
+
+       set -- $(getJankyFrames $(getPackageName $app))
+       totalDiff=$1
+       jankyDiff=$2
+       latency90=$3
+       latency95=$4
+       latency99=$5
+       if [ ${totalDiff:=0} -eq 0 ]; then
+               echo Error: could not read frame info with \"dumpsys gfxinfo\"
+       fi
+
+       ((frameSum=frameSum+totalDiff))
+       ((jankSum=jankSum+jankyDiff))
+       ((latency90Sum=latency90Sum+latency90))
+       ((latency95Sum=latency95Sum+latency95))
+       ((latency99Sum=latency99Sum+latency99))
+       if [ "$totalDiff" -eq 0 ]; then
+               echo Error: no frames detected. Is the display off?
+       fi
+       ((jankPct=jankyDiff*100/totalDiff))
+       resetJankyFrames $(getPackageName $app)
+
+
+       echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+       ((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)