Merge "ext4_utils: Fix a looping bug and memory leak"
diff --git a/ANRdaemon/ANRdaemon.cpp b/ANRdaemon/ANRdaemon.cpp
index 35f2ecb..bd433b3 100644
--- a/ANRdaemon/ANRdaemon.cpp
+++ b/ANRdaemon/ANRdaemon.cpp
@@ -36,7 +36,6 @@
 #include <ctime>
 #include <cutils/properties.h>
 #include <signal.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -134,7 +133,7 @@
 
     if ((fp = fopen("/proc/stat", "r")) == NULL) {
         err = true;
-        sprintf(err_msg, "can't read from /proc/stat with errno %d", errno);
+        snprintf(err_msg, sizeof(err_msg), "can't read from /proc/stat with errno %d", errno);
     } else {
         if (fscanf(fp, params, &cpu->utime, &cpu->ntime,
                 &cpu->stime, &cpu->itime, &cpu->iowtime, &cpu->irqtime,
@@ -144,6 +143,7 @@
              * is_heavy_loaded() will return false.
              */
             ALOGE("Error in getting cpu status. Skipping this check.");
+            fclose(fp);
             return;
         }
 
@@ -157,7 +157,7 @@
 /*
  * Calculate cpu usage in the past interval.
  * 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
+ * turn on and off tracing frequently when the cpu load is right close to
  * threshold.
  */
 static bool is_heavy_load(void) {
@@ -192,7 +192,7 @@
     int fd = open(path, O_WRONLY);
     if (fd == -1) {
         err = true;
-        sprintf(err_msg, "Can't open %s. Error: %d", path, errno);
+        snprintf(err_msg, sizeof(err_msg), "Can't open %s. Error: %d", path, errno);
         return -1;
     }
     const char* control = (enable?"1":"0");
@@ -205,7 +205,7 @@
         }
 
         err = true;
-        sprintf(err_msg, "Error %d in writing to %s.", errno, path);
+        snprintf(err_msg, sizeof(err_msg), "Error %d in writing to %s.", errno, path);
     }
     close(fd);
     return (err?-1:0);
@@ -216,16 +216,16 @@
  */
 static void dfs_set_property(uint64_t mtag, const char* mapp, bool enable) {
     char buf[64];
-    snprintf(buf, 64, "%#" PRIx64, mtag);
+    snprintf(buf, sizeof(buf), "%#" PRIx64, mtag);
     if (property_set(dfs_tags_property, buf) < 0) {
         err = true;
-        sprintf(err_msg, "Failed to set debug tags system properties.");
+        snprintf(err_msg, sizeof(err_msg), "Failed to set debug tags system properties.");
     }
 
     if (strlen(mapp) > 0
             && property_set(dfs_apps_property, mapp) < 0) {
         err = true;
-        sprintf(err_msg, "Failed to set debug applications.");
+        snprintf(err_msg, sizeof(err_msg), "Failed to set debug applications.");
     }
 
     if (log_sched) {
@@ -403,13 +403,13 @@
     int fd = open(dfs_buffer_size_path, O_WRONLY);
     if (fd == -1) {
         err = true;
-        sprintf(err_msg, "Can't open atrace buffer size file under /d/tracing.");
+        snprintf(err_msg, sizeof(err_msg), "Can't open atrace buffer size file under /d/tracing.");
         return -1;
     }
     ssize_t len = strlen(buf_size_kb);
     if (write(fd, buf_size_kb, len) != len) {
         err = true;
-        sprintf(err_msg, "Error in writing to atrace buffer size file.");
+        snprintf(err_msg, sizeof(err_msg), "Error in writing to atrace buffer size file.");
     }
     close(fd);
     return (err?-1:0);
diff --git a/alloc-stress/Android.mk b/alloc-stress/Android.mk
index ec19d79..ad46f26 100644
--- a/alloc-stress/Android.mk
+++ b/alloc-stress/Android.mk
@@ -12,3 +12,13 @@
 LOCAL_SRC_FILES := \
     alloc-stress.cpp
 include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := mem-pressure
+LOCAL_CFLAGS += -g -Wall -Werror -Wno-missing-field-initializers -Wno-sign-compare
+ifneq ($(ENABLE_MEM_CGROUPS),)
+    LOCAL_CFLAGS += -DENABLE_MEM_CGROUPS
+endif
+LOCAL_SRC_FILES := \
+    mem-pressure.cpp
+include $(BUILD_EXECUTABLE)
diff --git a/alloc-stress/mem-pressure.cpp b/alloc-stress/mem-pressure.cpp
new file mode 100644
index 0000000..777015d
--- /dev/null
+++ b/alloc-stress/mem-pressure.cpp
@@ -0,0 +1,107 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <getopt.h>
+
+void *alloc_set(size_t size) {
+    void *addr = NULL;
+
+    addr = malloc(size);
+    if (!addr) {
+        printf("Allocating %zd MB failed\n", size / 1024 / 1024);
+    } else {
+        memset(addr, 0, size);
+    }
+    return addr;
+}
+
+void add_pressure(size_t *shared, size_t size, size_t step_size,
+                  size_t duration, const char *oom_score) {
+    int fd, ret;
+
+    fd = open("/proc/self/oom_score_adj", O_WRONLY);
+    ret = write(fd, oom_score, strlen(oom_score));
+    if (ret < 0) {
+        printf("Writing oom_score_adj failed with err %s\n",
+               strerror(errno));
+    }
+    close(fd);
+
+    if (alloc_set(size)) {
+        *shared = size;
+    }
+
+    while (alloc_set(step_size)) {
+        size += step_size;
+        *shared = size;
+        usleep(duration);
+    }
+}
+
+void usage()
+{
+    printf("Usage: [OPTIONS]\n\n"
+           "  -d N: Duration in microsecond to sleep between each allocation.\n"
+           "  -i N: Number of iterations to run the alloc process.\n"
+           "  -o N: The oom_score to set the child process to before alloc.\n"
+           "  -s N: Number of bytes to allocate in an alloc process loop.\n"
+           );
+}
+
+int main(int argc, char *argv[])
+{
+    pid_t pid;
+    size_t *shared;
+    int c, i = 0;
+
+    size_t duration = 1000;
+    int iterations = 0;
+    const char *oom_score = "899";
+    size_t step_size = 2 * 1024 * 1024; // 2 MB
+    size_t size = step_size;
+
+    while ((c = getopt(argc, argv, "hi:d:o:s:")) != -1) {
+        switch (c)
+            {
+            case 'i':
+                iterations = atoi(optarg);
+                break;
+            case 'd':
+                duration = atoi(optarg);
+                break;
+            case 'o':
+                oom_score = optarg;
+                break;
+            case 's':
+                step_size = atoi(optarg);
+                break;
+            case 'h':
+                usage();
+                abort();
+            default:
+                abort();
+            }
+    }
+
+    shared = (size_t *)mmap(NULL, sizeof(size_t), PROT_READ | PROT_WRITE,
+                MAP_ANONYMOUS | MAP_SHARED, 0, 0);
+
+    while (iterations == 0 || i < iterations) {
+        *shared = 0;
+        pid = fork();
+        if (!pid) {
+            /* Child */
+            add_pressure(shared, size, step_size, duration, oom_score);
+            /* Shoud not get here */
+            exit(0);
+        } else {
+            wait(NULL);
+            printf("Child %d allocated %zd MB\n", i,
+                   *shared / 1024 / 1024);
+            size = *shared / 2;
+        }
+        i++;
+    }
+}
diff --git a/cppreopts/cppreopts.sh b/cppreopts/cppreopts.sh
index 8798206..8a0d466 100644
--- a/cppreopts/cppreopts.sh
+++ b/cppreopts/cppreopts.sh
@@ -19,20 +19,21 @@
 
 # Helper function to copy files
 function do_copy() {
-  odex_file=$1
+  source_file=$1
   dest_name=$2
   # Move to a temporary file so we can do a rename and have the preopted file
   # appear atomically in the filesystem.
   temp_dest_name=${dest_name}.tmp
-  if ! cp ${odex_file} ${temp_dest_name} ; then
-    log -p w -t cppreopts "Unable to copy odex file ${odex_file} to ${temp_dest_name}!"
+  if ! cp ${source_file} ${temp_dest_name} ; then
+    log -p w -t cppreopts "Unable to copy file ${source_file} to ${temp_dest_name}!"
   else
-    log -p i -t cppreopts "Copied odex file from ${odex_file} to ${temp_dest_name}"
+    log -p i -t cppreopts "Copied file from ${source_file} to ${temp_dest_name}"
     sync
     if ! mv ${temp_dest_name} ${dest_name} ; then
-      log -p w -t cppreopts "Unable to rename temporary odex file from ${temp_dest_name} to ${dest_name}"
+      log -p w -t cppreopts "Unable to rename temporary file from ${temp_dest_name} to ${dest_name}"
+      rm ${temp_dest_name} || log -p w -t cppreopts "Unable to remove temporary file ${temp_dest_name}"
     else
-      log -p i -t cppreopts "Renamed temporary odex file from ${temp_dest_name} to ${dest_name}"
+      log -p i -t cppreopts "Renamed temporary file from ${temp_dest_name} to ${dest_name}"
     fi
   fi
 }
@@ -42,23 +43,23 @@
   mountpoint=$1
 
   if ! test -f ${mountpoint}/system-other-odex-marker ; then
-    log -p i -t cppreopts "system_other partition does not appear have been built to contain preopted files."
+    log -p i -t cppreopts "system_other partition does not appear to have been built to contain preopted files."
     exit 1
   fi
 
   log -p i -t cppreopts "cppreopts from ${mountpoint}"
-  # For each odex file do the copy task
+  # For each odex and vdex file do the copy task
   # NOTE: this implementation will break in any path with spaces to favor
   # background copy tasks
-  for odex_file in $(find ${mountpoint} -type f -name "*.odex"); do
-    real_odex_name=${odex_file/${mountpoint}/\/system}
-    dest_name=$(preopt2cachename ${real_odex_name})
+  for file in $(find ${mountpoint} -type f -name "*.odex" -o -type f -name "*.vdex"); do
+    real_name=${file/${mountpoint}/\/system}
+    dest_name=$(preopt2cachename ${real_name})
     if ! test $? -eq 0 ; then
-      log -p i -t cppreopts "Unable to figure out destination for ${odex_file}"
+      log -p i -t cppreopts "Unable to figure out destination for ${file}"
       continue
     fi
     # Copy files in background to speed things up
-    do_copy ${odex_file} ${dest_name} &
+    do_copy ${file} ${dest_name} &
   done
   # Wait for jobs to finish
   wait
diff --git a/ext4_utils/allocate.c b/ext4_utils/allocate.c
index 00f2203..e0f662c 100644
--- a/ext4_utils/allocate.c
+++ b/ext4_utils/allocate.c
@@ -234,6 +234,18 @@
 	for (i = 0; i < num_blocks; i++, block--)
 		bg->block_bitmap[block / 8] &= ~(1 << (block % 8));
 	bg->free_blocks += num_blocks;
+	for (i = bg->chunk_count; i > 0 ;) {
+		--i;
+		if (bg->chunks[i].len >= num_blocks && bg->chunks[i].block <= block) {
+			if (bg->chunks[i].block == block) {
+				bg->chunks[i].block += num_blocks;
+				bg->chunks[i].len -= num_blocks;
+			} else if (bg->chunks[i].block + bg->chunks[i].len - 1 == block + num_blocks) {
+				bg->chunks[i].len -= num_blocks;
+			}
+			break;
+		}
+	}
 }
 
 /* Reduces an existing allocation by len blocks by return the last blocks
@@ -255,6 +267,7 @@
 			len -= last_reg->len;
 			if (reg) {
 				reg->next = NULL;
+				alloc->list.last = reg;
 			} else {
 				alloc->list.first = NULL;
 				alloc->list.last = NULL;
diff --git a/ext4_utils/ext4_crypt.cpp b/ext4_utils/ext4_crypt.cpp
index d594a48..de85f7e 100644
--- a/ext4_utils/ext4_crypt.cpp
+++ b/ext4_utils/ext4_crypt.cpp
@@ -39,17 +39,25 @@
 #define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17
 
 struct ext4_encryption_policy {
-    char version;
-    char contents_encryption_mode;
-    char filenames_encryption_mode;
-    char flags;
-    char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];
+    uint8_t version;
+    uint8_t contents_encryption_mode;
+    uint8_t filenames_encryption_mode;
+    uint8_t flags;
+    uint8_t master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];
 } __attribute__((__packed__));
 
 #define EXT4_ENCRYPTION_MODE_AES_256_XTS    1
 #define EXT4_ENCRYPTION_MODE_AES_256_CTS    4
+#define EXT4_ENCRYPTION_MODE_AES_256_HEH    126
 #define EXT4_ENCRYPTION_MODE_PRIVATE        127
 
+#define EXT4_POLICY_FLAGS_PAD_4         0x00
+#define EXT4_POLICY_FLAGS_PAD_8         0x01
+#define EXT4_POLICY_FLAGS_PAD_16        0x02
+#define EXT4_POLICY_FLAGS_PAD_32        0x03
+#define EXT4_POLICY_FLAGS_PAD_MASK      0x03
+#define EXT4_POLICY_FLAGS_VALID         0x03
+
 // ext4enc:TODO Get value from somewhere sensible
 #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)
@@ -100,8 +108,25 @@
     return true;
 }
 
+static uint8_t e4crypt_get_policy_flags(int filenames_encryption_mode) {
+
+    // With HEH, pad filenames with zeroes to the next 16-byte boundary.  This
+    // is not required, but it's more secure (helps hide the length of
+    // filenames), makes the inputs evenly divisible into blocks which is more
+    // efficient for encryption and decryption, and we had the opportunity to
+    // make a breaking change when introducing a new mode anyway.
+    if (filenames_encryption_mode == EXT4_ENCRYPTION_MODE_AES_256_HEH) {
+        return EXT4_POLICY_FLAGS_PAD_16;
+    }
+
+    // Default flags (4-byte padding) for CTS
+    return EXT4_POLICY_FLAGS_PAD_4;
+}
+
 static bool e4crypt_policy_set(const char *directory, const char *policy,
-                               size_t policy_length, int contents_encryption_mode) {
+                               size_t policy_length,
+                               int contents_encryption_mode,
+                               int filenames_encryption_mode) {
     if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
         LOG(ERROR) << "Policy wrong length: " << policy_length;
         return false;
@@ -115,8 +140,8 @@
     ext4_encryption_policy eep;
     eep.version = 0;
     eep.contents_encryption_mode = contents_encryption_mode;
-    eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
-    eep.flags = 0;
+    eep.filenames_encryption_mode = filenames_encryption_mode;
+    eep.flags = e4crypt_get_policy_flags(filenames_encryption_mode);
     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;
@@ -132,7 +157,9 @@
 }
 
 static bool e4crypt_policy_get(const char *directory, char *policy,
-                               size_t policy_length, int contents_encryption_mode) {
+                               size_t policy_length,
+                               int contents_encryption_mode,
+                               int filenames_encryption_mode) {
     if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
         LOG(ERROR) << "Policy wrong length: " << policy_length;
         return false;
@@ -155,8 +182,9 @@
 
     if ((eep.version != 0)
             || (eep.contents_encryption_mode != contents_encryption_mode)
-            || (eep.filenames_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_CTS)
-            || (eep.flags != 0)) {
+            || (eep.filenames_encryption_mode != filenames_encryption_mode)
+            || (eep.flags !=
+                e4crypt_get_policy_flags(filenames_encryption_mode))) {
         LOG(ERROR) << "Failed to find matching encryption policy for " << directory;
         return false;
     }
@@ -166,14 +194,17 @@
 }
 
 static bool e4crypt_policy_check(const char *directory, const char *policy,
-                                 size_t policy_length, int contents_encryption_mode) {
+                                 size_t policy_length,
+                                 int contents_encryption_mode,
+                                 int filenames_encryption_mode) {
     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,
-                            contents_encryption_mode)) return false;
+                            contents_encryption_mode,
+                            filenames_encryption_mode)) return false;
     char existing_policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
 
     policy_to_hex(existing_policy, existing_policy_hex);
@@ -191,14 +222,30 @@
 }
 
 int e4crypt_policy_ensure(const char *directory, const char *policy,
-                          size_t policy_length, const char* contents_encryption_mode) {
-    int mode = 0;
-    if (!strcmp(contents_encryption_mode, "software")) {
-        mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
+                          size_t policy_length,
+                          const char *contents_encryption_mode,
+                          const char *filenames_encryption_mode) {
+    int contents_mode = 0;
+    int filenames_mode = 0;
+
+    if (!strcmp(contents_encryption_mode, "software") ||
+        !strcmp(contents_encryption_mode, "aes-256-xts")) {
+        contents_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
     } else if (!strcmp(contents_encryption_mode, "ice")) {
-        mode = EXT4_ENCRYPTION_MODE_PRIVATE;
+        contents_mode = EXT4_ENCRYPTION_MODE_PRIVATE;
     } else {
-        LOG(ERROR) << "Invalid encryption mode";
+        LOG(ERROR) << "Invalid file contents encryption mode: "
+                   << contents_encryption_mode;
+        return -1;
+    }
+
+    if (!strcmp(filenames_encryption_mode, "aes-256-cts")) {
+        filenames_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
+    } else if (!strcmp(filenames_encryption_mode, "aes-256-heh")) {
+        filenames_mode = EXT4_ENCRYPTION_MODE_AES_256_HEH;
+    } else {
+        LOG(ERROR) << "Invalid file names encryption mode: "
+                   << filenames_encryption_mode;
         return -1;
     }
 
@@ -206,10 +253,10 @@
     if (!is_dir_empty(directory, &is_empty)) return -1;
     if (is_empty) {
         if (!e4crypt_policy_set(directory, policy, policy_length,
-                                mode)) return -1;
+                                contents_mode, filenames_mode)) return -1;
     } else {
         if (!e4crypt_policy_check(directory, policy, policy_length,
-                                  mode)) return -1;
+                                  contents_mode, filenames_mode)) return -1;
     }
     return 0;
 }
diff --git a/ext4_utils/ext4_crypt_init_extensions.cpp b/ext4_utils/ext4_crypt_init_extensions.cpp
index 04d7119..2bf8801 100644
--- a/ext4_utils/ext4_crypt_init_extensions.cpp
+++ b/ext4_utils/ext4_crypt_init_extensions.cpp
@@ -28,6 +28,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
 #include <keyutils.h>
@@ -91,14 +92,23 @@
     }
 
     auto type_filename = std::string("/data") + e4crypt_key_mode;
-    std::string contents_encryption_mode;
-    if (!android::base::ReadFileToString(type_filename, &contents_encryption_mode)) {
+    std::string modestring;
+    if (!android::base::ReadFileToString(type_filename, &modestring)) {
         LOG(ERROR) << "Cannot read mode";
     }
 
+    std::vector<std::string> modes = android::base::Split(modestring, ":");
+
+    if (modes.size() < 1 || modes.size() > 2) {
+        LOG(ERROR) << "Invalid encryption mode string: " << modestring;
+        return -1;
+    }
+
     LOG(INFO) << "Setting policy on " << dir;
     int result = e4crypt_policy_ensure(dir, policy.c_str(), policy.length(),
-                                       contents_encryption_mode.c_str());
+                                       modes[0].c_str(),
+                                       modes.size() >= 2 ?
+                                            modes[1].c_str() : "aes-256-cts");
     if (result) {
         LOG(ERROR) << android::base::StringPrintf(
             "Setting %02x%02x%02x%02x policy on %s failed!",
diff --git a/ext4_utils/include/ext4_utils/ext4_crypt.h b/ext4_utils/include/ext4_utils/ext4_crypt.h
index 2be0bec..d410ccf 100644
--- a/ext4_utils/include/ext4_utils/ext4_crypt.h
+++ b/ext4_utils/include/ext4_utils/ext4_crypt.h
@@ -25,9 +25,10 @@
 
 bool e4crypt_is_native();
 
-int e4crypt_policy_ensure(const char *directory,
-                          const char* policy, size_t policy_length,
-                          const char* contents_encryption_mode);
+int e4crypt_policy_ensure(const char *directory, const char *policy,
+                          size_t policy_length,
+                          const char *contents_encryption_mode,
+                          const char *filenames_encryption_mode);
 
 static const char* e4crypt_unencrypted_folder = "/unencrypted";
 static const char* e4crypt_key_ref = "/unencrypted/ref";
diff --git a/ext4_utils/mkuserimg_mke2fs.sh b/ext4_utils/mkuserimg_mke2fs.sh
index 8cb9202..64b1fe3 100755
--- a/ext4_utils/mkuserimg_mke2fs.sh
+++ b/ext4_utils/mkuserimg_mke2fs.sh
@@ -21,6 +21,8 @@
 if [ "$1" = "-s" ]; then
   MKE2FS_EXTENDED_OPTS+="android_sparse"
   shift
+else
+  E2FSDROID_OPTS+="-e"
 fi
 
 if [ $# -lt 5 ]; then
@@ -90,7 +92,7 @@
 fi
 
 if [[ "$1" == "-e" ]]; then
-  if [[ MKE2FS_EXTENDED_OPTS ]]; then
+  if [[ $MKE2FS_EXTENDED_OPTS ]]; then
     MKE2FS_EXTENDED_OPTS+=","
   fi
   MKE2FS_EXTENDED_OPTS+="stripe_width=$(($2/BLOCKSIZE))"
@@ -98,7 +100,7 @@
 fi
 
 if [[ "$1" == "-o" ]]; then
-  if [[ MKE2FS_EXTENDED_OPTS ]]; then
+  if [[ $MKE2FS_EXTENDED_OPTS ]]; then
     MKE2FS_EXTENDED_OPTS+=","
   fi
   # stride should be the max of 8kb and the logical block size
@@ -106,11 +108,13 @@
   shift; shift
 fi
 
-if [[ MKE2FS_EXTENDED_OPTS ]]; then
+if [[ $MKE2FS_EXTENDED_OPTS ]]; then
   MKE2FS_OPTS+=" -E $MKE2FS_EXTENDED_OPTS"
 fi
 
-E2FSDROID_OPTS+=" -S $1"
+if [[ $1 ]]; then
+  E2FSDROID_OPTS+=" -S $1"
+fi
 
 case $EXT_VARIANT in
   ext4) ;;
@@ -122,6 +126,10 @@
   exit 2
 fi
 
+if [[ ${MOUNT_POINT:0:1} != "/" ]]; then
+  MOUNT_POINT="/"$MOUNT_POINT
+fi
+
 if [ -z $SIZE ]; then
   echo "Need size of filesystem"
   exit 2
@@ -130,6 +138,9 @@
 # Round down the filesystem length to be a multiple of the block size
 SIZE=$((SIZE / BLOCKSIZE))
 
+# truncate output file since mke2fs will keep verity section in existing file
+cat /dev/null >$OUTPUT_FILE
+
 MAKE_EXT4FS_CMD="mke2fs $MKE2FS_OPTS -t $EXT_VARIANT -b $BLOCKSIZE $OUTPUT_FILE $SIZE"
 echo $MAKE_EXT4FS_CMD
 MKE2FS_CONFIG=./system/extras/ext4_utils/mke2fs.conf $MAKE_EXT4FS_CMD
@@ -137,7 +148,7 @@
   exit 4
 fi
 
-E2FSDROID_CMD="e2fsdroid $E2FSDROID_OPTS -f $SRC_DIR -a /$MOUNT_POINT $OUTPUT_FILE"
+E2FSDROID_CMD="e2fsdroid $E2FSDROID_OPTS -f $SRC_DIR -a $MOUNT_POINT $OUTPUT_FILE"
 echo $E2FSDROID_CMD
 $E2FSDROID_CMD
 if [ $? -ne 0 ]; then
diff --git a/ioshark/Android.mk b/ioshark/Android.mk
new file mode 100644
index 0000000..a647d75
--- /dev/null
+++ b/ioshark/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+#LOCAL_32_BIT_ONLY = true
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_SRC_FILES := ioshark_bench.c ioshark_bench_subr.c ioshark_bench_mmap.c
+LOCAL_CFLAGS := -g -O2 -Wall  -Werror
+LOCAL_MODULE := ioshark_bench
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+#LOCAL_32_BIT_ONLY = true
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_SRC_FILES := compile_ioshark.c compile_ioshark_subr.c
+LOCAL_CFLAGS := -g -O2 -Wall -Werror -D_GNU_SOURCE
+LOCAL_MODULE := compile_ioshark
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
+#LOCAL_32_BIT_ONLY = true
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_SRC_FILES := dump_ioshark_filenames.c
+LOCAL_CFLAGS := -g -O2 -Wall -Werror -D_GNU_SOURCE
+LOCAL_MODULE := dump_ioshark_filenames
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/ioshark/README b/ioshark/README
new file mode 100644
index 0000000..0c50fec
--- /dev/null
+++ b/ioshark/README
@@ -0,0 +1,30 @@
+IOshark is a repeatable application workload storage benchmark. You
+can find more documentation on IOshark at :
+https://docs.google.com/a/google.com/document/d/1Bhq7iNPVc_JzwRrkmZqcPjMvWgpHX0r3Ncq-ZsRNOBA/edit?usp=sharing
+
+The short summary of what IOshark is : IOshark has 2 components, one
+is a strace+ftrace compiler that takes straces and select ftraces fed
+into it and compiles this into bytecodes (stored in *.wl files). The
+compiler runs on a Linux host. The second component (which runs on the
+device) is the tester that takes as input the bytecode files (*.wl
+files) and executes them on the device.
+
+How to Run :
+----------
+- First collect straces and compile these into bytecodes. The wrapper
+script provided (collect-straces.sh) collects straces, ships them to
+the host where the script runs, compiles and packages up the bytecode
+files into a wl.tar file.
+- Ship the wl.tar file and the iostark_bench binaries to the target
+device (on /data/local/tmp say). Explode the tarfile.
+- Run the tester. "ioshark_bench *.wl" runs the test with default
+options. Supported ioshark_bench options :
+-d : Preserve the delays between successive filesystem syscalls as
+seen in the original straces.
+-n <N> : Run for N iterations
+-t <N> : Limit to N threads. By default (without this option), IOshark
+will launch as many threads as there are input files, so 1 thread/file.
+-v : verbose. Chatty mode.
+-s : One line summary.
+-q : Don't create the files in read-only partitions like /system and
+/vendor. Instead do reads on those files.
diff --git a/ioshark/collect-straces-ftraces.sh b/ioshark/collect-straces-ftraces.sh
new file mode 100644
index 0000000..6e83a24
--- /dev/null
+++ b/ioshark/collect-straces-ftraces.sh
@@ -0,0 +1,202 @@
+#!/bin/sh
+
+# This function just re-writes the timestamp of the strace entries to be
+# seconds.usecs since boot. To match the timestamping of ftrace (so we can
+# merge them later).
+process_strace()
+{
+    strace=$1
+    # parse in data/system/vendor and parse out /sys/devices/system/...
+    egrep '\/system\/|\/data\/|\/vendor\/' $strace | egrep -v '\/sys\/devices\/system\/' > bar
+    fgrep -v '= -1' bar > foo
+    mv foo bar
+    # begin_time is seconds since epoch
+    begin_time=`cat trace.begin`
+    # replace seconds since epoch with SECONDS SINCE BOOT in the
+    # strace files
+    awk -v begin="$begin_time" '{ printf "%f strace ", $1 - begin; $1=""; print $0}' bar > $2
+    rm bar
+}
+
+#
+# This function processes the ftrace file, removing the fields that we don't care
+# about, breaks up the ftrace file into one file per pid.
+# Input : One single fstrace file.
+# Output : Multiple fstrace.pid files.
+prep_fstrace()
+{
+    # Remove leading junk
+    fgrep android_fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+    # Sanitize the filenames, removing spaces within the filename etc
+    sed 's/android_fs_dataread_start/read/' foo > bar1
+    mv bar1 bar
+    # First column is timestamp SECONDS SINCE BOOT
+    awk '{ print $2, "ftrace", $3, $5, $7, $9, $13 }' bar > foo
+    #awk '{ s ="" ; for (i=2; i <= NF ; i++) s = s $i " "; print s}' bar > foo
+    rm bar
+    # Get all the uniq pids
+    awk '{print $7}' foo | sort | uniq > pidlist
+    for i in `cat pidlist`
+    do
+	awk -v pid=$i '{ if (pid == $7) print $0}' foo > fstrace.$i
+    done
+    rm pidlist
+    rm foo
+}
+
+# Merge straces and ftraces.
+# The goal here is to catch mmap'ed IO (reads) that won't be in the
+# strace file. The algorithm is to look for mmaps in the strace file,
+# use the files tha are mmap'ed to search in the ftraces to pick up
+# tracepoints from there, and merge those with the straces.
+# The output of this function is a set of parsed_input_trace.<pid>
+# files, that can then be compiled into .wl files
+merge_compile()
+{
+    for stracefile in trace.*
+    do
+	if [ $stracefile == trace.begin ] || [ $stracefile == trace.tar ];
+	then
+	    continue
+	fi
+	# Get the pid from the strace filename (pid is the extension)
+	pid=${stracefile##*.}
+	process_strace $stracefile foo.$pid
+	if ! [ -s foo.$pid ]; then
+	    rm foo.$pid
+	    continue
+	fi
+	#
+	# If we have matching strace and ftrace files, then look for mmaps in
+	# the strace pluck the corresponding entries for the mmap (mmaped IO)
+	# from the ftrace and merge them into the strace
+	#
+	if [ -f fstrace.$pid ]; then
+	    fgrep mmap foo.$pid > bar
+	    if [ -s bar ]; then
+		# Get all the unique mmap'ed filenames from the strace
+		awk '{ print $7 }' bar | sed 's/^[^<]*<//g' | sed 's/>,//g' > mapped_files
+		# Pluck all the lines from the ftrace corresponding to the mmaps
+		cat /dev/null > footemp
+		for j in `sort mapped_files | uniq`
+		do
+		    # Merge the readpage(s) traces from the ftrace into strace
+		    # for this mmaped file.
+		    grep -w $j fstrace.$pid > foobar
+		    if [ $? == 0 ]; then
+			sort foo.$pid foobar >> footemp
+		    fi
+		    rm foobar
+		done
+		rm mapped_files
+		if [ -s footemp ]; then
+		    mv footemp parsed_input_trace.$pid
+		else
+		    mv foo.$pid parsed_input_trace.$pid
+		fi
+	    else
+		mv foo.$pid parsed_input_trace.$pid
+	    fi
+	    rm bar
+	else
+	    mv foo.$pid parsed_input_trace.$pid
+	fi
+	echo compiling parsed_input_trace.$pid
+	compile_ioshark parsed_input_trace.$pid $pid.wl
+	rm parsed_input_trace.$pid
+	rm -f foo.$pid
+    done
+}
+
+catch_sigint()
+{
+    echo "signal INT received, killing streaming trace capture"
+    ps_line=`ps -ef | grep trace_pipe | grep adb `
+    if [ $? == 0 ]; then
+        echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+	kill `echo $ps_line | awk '{print $2}' `
+    fi
+    ps_line=`ps -ef | grep strace | grep adb `
+    if [ $? == 0 ]; then
+        echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+	kill `echo $ps_line | awk '{print $2}' `
+    fi
+}
+
+enable_tracepoints()
+{
+    adb shell "echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable"
+    adb shell "echo 1 > /sys/kernel/debug/tracing/tracing_on"
+}
+
+disable_tracepoints()
+{
+    adb shell "echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable"
+    adb shell "echo 0 > /sys/kernel/debug/tracing/tracing_on"
+}
+
+kill_traces()
+{
+    ps_line=`ps -ef | grep trace_pipe | grep adb `
+    if [ $? == 0 ]; then
+        echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+	kill `echo $ps_line | awk '{print $2}' `
+    fi
+    ps_line=`ps -ef | grep strace | grep adb `
+    if [ $? == 0 ]; then
+        echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
+	kill `echo $ps_line | awk '{print $2}' `
+    fi
+}
+
+catch_sigint()
+{
+    echo "signal INT received, killing streaming trace capture"
+    kill_traces
+}
+
+# main() starts here
+
+adb root && adb wait-for-device
+
+enable_tracepoints
+
+trap 'catch_sigint' INT
+
+adb shell 'ps' | grep zygote > zygote_pids
+fgrep -v grep zygote_pids > bar
+mv bar zygote_pids
+pid1=`grep -w zygote zygote_pids | awk '{print $2}' `
+pid2=`grep -w zygote64 zygote_pids | awk '{print $2}' `
+rm -f zygote_pids
+
+adb shell "date +%s > /data/local/tmp/trace.begin ; strace -p $pid1,$pid2 -o /data/local/tmp/trace -q -qq -f -ff -y -ttt -e trace=mmap2,read,write,pread64,pwrite64,fsync,fdatasync,openat,close,lseek,_llseek" &
+adb shell "cat /sys/kernel/debug/tracing/trace_pipe" > fstrace &
+
+echo "^C this when done with the test"
+
+wait
+
+adb shell 'monkey -p com.android.alarmclock -p com.android.chrome -p com.android.calculator -p com.android.calendar -p com.google.android.calendar -p com.google.android.camera -p com.android.contacts -p com.google.android.gm -p com.android.im -p com.android.launcher -p com.google.android.apps.maps -p com.android.mms -p com.google.android.music -p com.android.phone -p com.google.android.youtube -p com.android.email -p com.google.android.voicesearch -c android.intent.category.LAUNCHER --throttle 200 --ignore-security-exceptions --ignore-crashes --ignore-timeouts -v -v -v 25000'
+
+kill_traces
+
+disable_tracepoints
+
+rm -f trace.*
+rm -f fstrace.*
+rm -f *.wl
+rm -f parsed*
+
+# Get the tracefiles from the device
+adb shell 'cd /data/local/tmp ; tar cvf trace.tar trace.*'
+adb pull /data/local/tmp/trace.tar
+tar xf trace.tar
+
+# Pre-process the ftrace file
+prep_fstrace fstrace
+# Merge the ftrace file(s) with the strace files
+merge_compile
+
+# tar up the .wl files just created
+tar cf wl.tar ioshark_filenames *.wl
diff --git a/ioshark/collect-straces.sh b/ioshark/collect-straces.sh
new file mode 100755
index 0000000..21a3939
--- /dev/null
+++ b/ioshark/collect-straces.sh
@@ -0,0 +1,97 @@
+# 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.
+
+#!/bin/sh
+
+# When signal is received, the stracer will get killed
+# Call this (just to make sure anyway)
+kill_strace() {
+    ps_line=`ps -ef | grep strace | grep adb `
+    if [ $? == 0 ]; then
+        echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s \
+$i " "; print s}' `
+        kill `echo $ps_line | awk '{print $2}' `
+    fi
+}
+
+catch_sigint()
+{
+    echo "signal INT received, killing streaming trace capture"
+    kill_strace
+}
+
+compile_tracefiles()
+{
+    for i in trace.*
+    do
+	if [ $i != trace.begin ] && [ $i != trace.tar ];
+	then
+	    egrep '\/system\/|\/data\/|\/vendor\/' $i > bar
+# parse out /sys/devices/system/...
+	    egrep -v '\/sys\/devices\/system\/' bar > bar0
+	    mv bar0 bar
+	    fgrep -v '= -1'	bar > foo
+	    rm bar
+	    # begin_time is seconds since epoch
+	    begin_time=`cat trace.begin`
+	    # replace seconds since epoch with SECONDS SINCE BOOT in the
+	    # strace files
+	    awk -v begin="$begin_time" '{ printf "%f strace ", $1 - begin; $1=""; print $0}' foo > bar
+	    if [ -s bar ]
+	    then
+		echo parsing $i
+		pid=${i##*.}
+		compile_ioshark bar $pid.wl
+		rm -f bar
+	    else
+		rm -f $i bar
+	    fi
+	fi
+    done
+}
+
+# main() starts here
+
+adb root && adb wait-for-device
+
+adb shell 'ps' | grep zygote > zygote_pids
+
+fgrep -v grep zygote_pids > bar
+mv bar zygote_pids
+pid1=`grep -w zygote zygote_pids | awk '{print $2}' `
+pid2=`grep -w zygote64 zygote_pids | awk '{print $2}' `
+rm -f zygote_pids
+
+trap 'catch_sigint' INT
+
+echo "^C this script once you finish running your test"
+
+adb shell "date +%s > /data/local/tmp/trace.begin ; strace -p $pid1,$pid2 -o /data/local/tmp/trace -q -qq -f -ff -y -ttt -e trace=mmap2,read,write,pread64,pwrite64,fsync,fdatasync,openat,close,lseek,_llseek"
+
+# Remove any remnant tracefiles first
+rm -f trace.*
+
+# Get the tracefiles from the device
+adb shell 'cd /data/local/tmp ; tar cvf trace.tar trace.*'
+adb pull /data/local/tmp/trace.tar
+
+# Extract the tracefiles from the device
+rm -f *.wl
+tar xf trace.tar
+
+# Compile the tracefiles
+compile_tracefiles
+
+# tar up the .wl files just created
+rm -f wl.tar
+tar cf wl.tar ioshark_filenames *.wl
diff --git a/ioshark/compile-only.sh b/ioshark/compile-only.sh
new file mode 100644
index 0000000..634b474
--- /dev/null
+++ b/ioshark/compile-only.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+
+# This function just re-writes the timestamp of the strace entries to be
+# seconds.usecs since boot. To match the timestamping of ftrace (so we can
+# merge them later).
+process_strace()
+{
+    strace=$1
+    # parse in data/system/vendor and parse out /sys/devices/system/...
+    egrep '\/system\/|\/data\/|\/vendor\/' $strace | egrep -v '\/sys\/devices\/system\/' > bar
+    fgrep -v '= -1' bar > foo
+    mv foo bar
+    # begin_time is seconds since epoch
+    begin_time=`cat trace.begin`
+    # replace seconds since epoch with SECONDS SINCE BOOT in the
+    # strace files
+    awk -v begin="$begin_time" '{ printf "%f strace ", $1 - begin; $1=""; print $0}' bar > $2
+    rm bar
+}
+
+#
+# This function processes the ftrace file, removing the fields that we don't care
+# about, breaks up the ftrace file into one file per pid.
+# Input : One single fstrace file.
+# Output : Multiple fstrace.pid files.
+prep_fstrace()
+{
+    # Remove leading junk
+    fgrep android_fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+    sed 's/android_fs_dataread_start/read/' foo > bar1
+    mv bar1 bar
+    # First column is timestamp SECONDS SINCE BOOT
+    awk '{ print $2, "ftrace", $3, $5, $7, $9, $13 }' bar > foo
+    rm bar
+    # Get all the uniq pids
+    awk '{print $7}' foo | sort | uniq > pidlist
+    for i in `cat pidlist`
+    do
+	awk -v pid=$i '{ if (pid == $7) print $0}' foo > fstrace.$i
+    done
+    rm pidlist
+    rm foo
+}
+
+# Merge straces and ftraces.
+# The goal here is to catch mmap'ed IO (reads) that won't be in the
+# strace file. The algorithm is to look for mmaps in the strace file,
+# use the files tha are mmap'ed to search in the ftraces to pick up
+# tracepoints from there, and merge those with the straces.
+# The output of this function is a set of parsed_input_trace.<pid>
+# files, that can then be compiled into .wl files
+merge_compile()
+{
+    for stracefile in trace.*
+    do
+	if [ $stracefile == trace.begin ] || [ $stracefile == trace.tar ];
+	then
+	    continue
+	fi
+	# Get the pid from the strace filename (pid is the extension)
+	pid=${stracefile##*.}
+	process_strace $stracefile foo.$pid
+	if ! [ -s foo.$pid ]; then
+	    rm foo.$pid
+	    continue
+	fi
+	#
+	# If we have matching strace and ftrace files, then look for mmaps in
+	# the strace pluck the corresponding entries for the mmap (mmaped IO)
+	# from the ftrace and merge them into the strace
+	#
+	if [ -f fstrace.$pid ]; then
+	    fgrep mmap foo.$pid > bar
+	    if [ -s bar ]; then
+		# Get all the unique mmap'ed filenames from the strace
+		awk '{ print $7 }' bar | sed 's/^[^<]*<//g' | sed 's/>,//g' > mapped_files
+		# Pluck all the lines from the ftrace corresponding to the mmaps
+		cat /dev/null > footemp
+		for j in `sort mapped_files | uniq`
+		do
+		    # Merge the readpage(s) traces from the ftrace into strace
+		    # for this mmaped file.
+		    grep -w $j fstrace.$pid > foobar
+		    if [ $? == 0 ]; then
+			sort foo.$pid foobar >> footemp
+		    fi
+		    rm foobar
+		done
+		rm mapped_files
+		if [ -s footemp ]; then
+		    mv footemp parsed_input_trace.$pid
+		else
+		    mv foo.$pid parsed_input_trace.$pid
+		fi
+	    else
+		mv foo.$pid parsed_input_trace.$pid
+	    fi
+	    rm bar
+	else
+	    mv foo.$pid parsed_input_trace.$pid
+	fi
+	echo compiling parsed_input_trace.$pid
+	compile_ioshark parsed_input_trace.$pid $pid.wl
+	rm parsed_input_trace.$pid
+	rm -f foo.$pid
+    done
+}
+
+# main() starts here
+
+rm -f *.wl
+rm -f parsed*
+rm ioshark_filenames
+
+# Pre-process the ftrace file
+prep_fstrace fstrace
+# Merge the ftrace file(s) with the strace files
+merge_compile
+
+# tar up the .wl files just created
+tar cf wl-test.tar ioshark_filenames *.wl
diff --git a/ioshark/compile_ioshark.c b/ioshark/compile_ioshark.c
new file mode 100644
index 0000000..f35d595
--- /dev/null
+++ b/ioshark/compile_ioshark.c
@@ -0,0 +1,687 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "ioshark.h"
+#include "compile_ioshark.h"
+
+char *progname;
+
+char in_buf[2048];
+
+struct flags_map_s {
+	char *flag_str;
+	int flag;
+};
+
+#define ARRAY_SIZE(a)	(sizeof(a) / sizeof(a[0]))
+
+struct flags_map_s open_flags_map[] = {
+	{ "O_RDONLY", O_RDONLY },
+	{ "O_WRONLY", O_WRONLY },
+	{ "O_RDWR", O_RDWR },
+	{ "O_CREAT", O_CREAT },
+	{ "O_SYNC", O_SYNC },
+	{ "O_TRUNC", O_TRUNC },
+	{ "O_EXCL", O_EXCL },
+	{ "O_APPEND", O_APPEND },
+	{ "O_NOATIME", O_NOATIME },
+	{ "O_ASYNC", O_ASYNC },
+	{ "O_CLOEXEC", O_CLOEXEC },
+	{ "O_DIRECT", O_DIRECT },
+	{ "O_DIRECTORY", O_DIRECTORY },
+	{ "O_LARGEFILE", O_LARGEFILE },
+	{ "O_NOCTTY", O_NOCTTY },
+	{ "O_NOFOLLOW", O_NOFOLLOW },
+	{ "O_NONBLOCK", O_NONBLOCK },
+	{ "O_NDELAY", O_NDELAY },
+	{ "O_PATH", O_PATH }
+};
+
+struct flags_map_s lseek_action_map[] = {
+	{ "SEEK_SET", SEEK_SET },
+	{ "SEEK_CUR", SEEK_CUR },
+	{ "SEEK_END", SEEK_END }
+};
+
+struct flags_map_s fileop_map[] = {
+	{ "lseek", IOSHARK_LSEEK },
+	{ "_llseek", IOSHARK_LLSEEK },
+	{ "pread64", IOSHARK_PREAD64 },
+	{ "pwrite64", IOSHARK_PWRITE64 },
+	{ "read", IOSHARK_READ },
+	{ "write", IOSHARK_WRITE },
+	{ "mmap", IOSHARK_MMAP },
+	{ "mmap2", IOSHARK_MMAP2 },
+	{ "openat", IOSHARK_OPEN },
+	{ "fsync", IOSHARK_FSYNC },
+	{ "fdatasync", IOSHARK_FDATASYNC },
+	{ "close", IOSHARK_CLOSE },
+	{ "ftrace", IOSHARK_MAPPED_PREAD }
+};
+
+struct in_mem_file_op {
+	struct ioshark_file_operation disk_file_op;
+	struct in_mem_file_op *next;
+};
+
+struct in_mem_file_op *in_mem_file_op_head = NULL, *in_mem_file_op_tail = NULL;
+
+void usage(void)
+{
+	fprintf(stderr, "%s in_file out_file\n", progname);
+}
+
+void
+init_prev_time(struct timeval *tv)
+{
+	tv->tv_sec = tv->tv_usec = 0;
+}
+
+/*
+ * delta ts is the time delta from the previous IO in this tracefile.
+ */
+static u_int64_t
+get_delta_ts(char *buf, struct timeval *prev)
+{
+	struct timeval op_tv, tv_res;
+
+	sscanf(buf, "%lu.%lu", &op_tv.tv_sec, &op_tv.tv_usec);
+	/* First item */
+	if (prev->tv_sec == 0 && prev->tv_usec == 0)
+		tv_res.tv_sec = tv_res.tv_usec = 0;
+	else
+		timersub(&op_tv, prev, &tv_res);
+	*prev = op_tv;
+	return (tv_res.tv_usec + (tv_res.tv_sec * 1000000));
+}
+
+void
+get_tracetype(char *buf, char *trace_type)
+{
+	char *s, *s2;
+
+	*trace_type = '\0';
+	s = strchr(buf, ' ');
+	if (s == NULL) {
+		fprintf(stderr,
+			"%s Malformed Trace Type ? %s\n",
+			progname, __func__);
+		exit(EXIT_FAILURE);
+	}
+	while (*s == ' ')
+		s++;
+	if (sscanf(s, "%s", trace_type) != 1) {
+		fprintf(stderr,
+			"%s Malformed Trace Type ? %s\n",
+			progname, __func__);
+		exit(EXIT_FAILURE);
+	}
+	if (strcmp(trace_type, "strace") != 0 &&
+	    strcmp(trace_type, "ftrace") != 0) {
+		fprintf(stderr,
+			"%s Unknown/Missing Trace Type (has to be strace|ftrace) %s\n",
+			progname, __func__);
+		exit(EXIT_FAILURE);
+	}
+	/*
+	 * Remove the keyword "strace"/"ftrace" from the buffer
+	 */
+	s2 = strchr(s, ' ');
+	if (s2 == NULL) {
+		fprintf(stderr,
+			"%s Malformed Trace Type ? %s\n",
+			progname, __func__);
+		exit(EXIT_FAILURE);
+	}
+	while (*s2 == ' ')
+		s2++;
+	if (*s2  == '\0') {
+		/*
+		 * Premature end of input record
+		 */
+		fprintf(stderr,
+			"%s Mal-formed strace/ftrace record %s:%s\n",
+			progname, __func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	/* strcpy() expects non-overlapping buffers, but bcopy doesn't */
+	bcopy(s2, s, strlen(s2) + 1);
+}
+
+void
+get_pathname(char *buf, char *pathname, enum file_op file_op)
+{
+	char *s, *s2, save;
+
+	if (file_op == IOSHARK_MAPPED_PREAD) {
+		s = strchr(buf, '/');
+		if (s == NULL) {
+			fprintf(stderr, "%s: Malformed line: %s\n",
+				__func__, buf);
+			exit(EXIT_FAILURE);
+		}
+		s2 = strchr(s, ' ');
+		if (s2 == NULL) {
+			fprintf(stderr, "%s: Malformed line: %s\n",
+				__func__, buf);
+			exit(EXIT_FAILURE);
+		}
+	} else {
+		if (file_op == IOSHARK_OPEN)
+			s = strchr(buf, '"');
+		else
+			s = strchr(buf, '<');
+		if (s == NULL) {
+			fprintf(stderr, "%s: Malformed line: %s\n",
+				__func__, buf);
+			exit(EXIT_FAILURE);
+		}
+		s += 1;
+		if (file_op == IOSHARK_OPEN)
+			s2 = strchr(s, '"');
+		else
+			s2 = strchr(s, '>');
+		if (s2 == NULL) {
+			fprintf(stderr, "%s: Malformed line: %s\n",
+				__func__, buf);
+			exit(EXIT_FAILURE);
+		}
+	}
+	save = *s2;
+	*s2 = '\0';
+	strcpy(pathname, s);
+	*s2 = save;
+}
+
+void
+get_syscall(char *buf, char *syscall)
+{
+	char *s, *s2;
+
+	s = strchr(buf, ' ');
+	if (s == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s += 1;
+	s2 = strchr(s, '(');
+	if (s2 == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	*s2 = '\0';
+	strcpy(syscall, s);
+	*s2 = '(';
+}
+
+void
+get_mmap_offset_len_prot(char *buf, int *prot, off_t *offset, u_int64_t *len)
+{
+	char *s, *s1;
+	int i;
+	char protstr[128];
+
+	s = strchr(buf, ',');
+	if (s == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s += 2;
+	sscanf(s, "%ju", len);
+	s1 = strchr(s, ',');
+	if (s1 == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s1 += 2;
+	s = strchr(s1, ',');
+	if (s == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	*s = '\0';
+	strcpy(protstr, s1);
+	*prot = 0;
+	if (strstr(protstr, "PROT_READ"))
+		*prot |= IOSHARK_PROT_READ;
+	if (strstr(protstr, "PROT_WRITE"))
+		*prot |= IOSHARK_PROT_WRITE;
+	*s = ',';
+	s += 2;
+	for (i = 0 ; i < 2 ; i++) {
+		s = strchr(s, ',');
+		if (s == NULL) {
+			fprintf(stderr, "%s: Malformed line: %s\n",
+				__func__, buf);
+			exit(EXIT_FAILURE);
+		}
+		s += 2;
+	}
+	sscanf(s, "%jx", offset);
+}
+
+void
+get_lseek_offset_action(char *buf, enum file_op op,
+			off_t *offset, char *action)
+{
+	char *s, *s2;
+
+	s = strchr(buf, ',');
+	if (s == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s += 2;
+	sscanf(s, "%ju", offset);
+	s = strchr(s, ',');
+	if (s == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s += 2;
+	if (op == IOSHARK_LLSEEK) {
+		s = strchr(s, ',');
+		if (s == NULL) {
+			fprintf(stderr, "%s: Malformed line: %s\n",
+				__func__, buf);
+			exit(EXIT_FAILURE);
+		}
+		s += 2;
+	}
+	s2 = strchr(s, ')');
+	if (s2 == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	*s2 = '\0';
+	strcpy(action, s);
+	*s2 = ')';
+}
+
+void
+get_rw_len(char *buf,
+	   u_int64_t *len)
+{
+	char *s_len;
+
+	s_len = strrchr(buf, ',');
+	if (s_len == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	sscanf(s_len + 2, "%ju", len);
+}
+
+void
+get_prw64_offset_len(char *buf,
+		     off_t *offset,
+		     u_int64_t *len)
+{
+	char *s_offset, *s_len;
+
+	s_offset = strrchr(buf, ',');
+	if (s_offset == NULL) {
+		fprintf(stderr, "%s: Malformed line 1: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	*s_offset = '\0';
+	s_len = strrchr(buf, ',');
+	if (s_len == NULL) {
+		fprintf(stderr, "%s: Malformed line 2: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	*s_offset = ',';
+	sscanf(s_len + 2, "%ju", len);
+	sscanf(s_offset + 2, "%ju", offset);
+}
+
+
+void
+get_ftrace_offset_len(char *buf,
+		      off_t *offset,
+		      u_int64_t *len)
+{
+	char *s_offset;
+
+	s_offset = strchr(buf, '/');
+	if (s_offset == NULL) {
+		fprintf(stderr, "%s: Malformed line 1: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s_offset = strchr(s_offset, ' ');
+	if (s_offset == NULL) {
+		fprintf(stderr, "%s: Malformed line 2: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	while (*s_offset == ' ')
+		s_offset++;
+	if (sscanf(s_offset, "%ju %ju", offset, len) != 2) {
+		fprintf(stderr, "%s: Malformed line 3: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+}
+
+void
+get_openat_flags_mode(char *buf, char *flags, mode_t *mode)
+{
+	char *s, *s2, lookfor;
+
+	s = strchr(buf, ',');
+	if (s == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s += 2;
+	s = strchr(s, ',');
+	if (s == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	s += 2;
+	if (strstr(s, "O_CREAT") == NULL)
+		lookfor = ')';
+	else
+		lookfor = ',';
+	s2 = strchr(s, lookfor);
+	if (s2 == NULL) {
+		fprintf(stderr, "%s: Malformed line: %s\n",
+			__func__, buf);
+		exit(EXIT_FAILURE);
+	}
+	*s2 = '\0';
+	strcpy(flags, s);
+	*s2 = lookfor;
+	if (strstr(s, "O_CREAT") != NULL) {
+		s = s2 + 2;
+		s2 = strchr(s, ')');
+		if (s2 == NULL) {
+			fprintf(stderr, "%s: Malformed line: %s\n",
+				__func__, buf);
+			exit(EXIT_FAILURE);
+		}
+		*s2 = '\0';
+		sscanf(s, "%o", mode);
+		*s2 = ')';
+	}
+}
+
+int
+lookup_map(char *s, struct flags_map_s *flags_map, int maplen)
+{
+	int found = 0, flag = 0;
+	int i;
+
+	while (isspace(*s))
+		s++;
+	for (i = 0 ; i < maplen ; i++) {
+		if (strcmp(flags_map[i].flag_str, s) == 0) {
+			flag = flags_map[i].flag;
+			found = 1;
+			break;
+		}
+	}
+	if (found == 0) {
+		fprintf(stderr, "%s: Unknown syscall %s\n",
+			__func__, s);
+		exit(1);
+	}
+	return flag;
+}
+
+int
+map_open_flags(char *s)
+{
+	int flags = 0;
+	char *s1;
+
+	while ((s1 = strchr(s, '|'))) {
+		*s1 = '\0';
+		flags |= lookup_map(s, open_flags_map,
+				    ARRAY_SIZE(open_flags_map));
+		*s1 = '|';
+		s = s1 + 1;
+	}
+	/* Last option */
+	flags |= lookup_map(s, open_flags_map,
+			    ARRAY_SIZE(open_flags_map));
+	return flags;
+}
+
+int
+map_lseek_action(char *s)
+{
+	int flags = 0;
+	char *s1;
+
+	while ((s1 = strchr(s, '|'))) {
+		*s1 = '\0';
+		flags |= lookup_map(s, lseek_action_map,
+				    ARRAY_SIZE(lseek_action_map));
+		*s1 = '|';
+		s = s1 + 1;
+	}
+	/* Last option */
+	flags |= lookup_map(s, lseek_action_map,
+			    ARRAY_SIZE(lseek_action_map));
+	return flags;
+}
+
+enum file_op
+map_syscall(char *syscall)
+{
+	return lookup_map(syscall, fileop_map,
+			  ARRAY_SIZE(fileop_map));
+}
+
+/*
+ * For each tracefile, we first create in-memory structures, then once
+ * we've processed each tracefile completely, we write out the in-memory
+ * structures and free them.
+ */
+int main(int argc, char **argv)
+{
+	FILE *fp;
+	char path[512];
+	char syscall[512];
+	char lseek_action_str[512];
+	char *s;
+	char open_flags_str[64];
+	void *db_node;
+	int num_io_operations = 0;
+	struct ioshark_header header;
+	struct ioshark_file_operation *disk_file_op;
+	struct in_mem_file_op *in_mem_fop;
+	struct stat st;
+	char *infile, *outfile;
+	struct timeval prev_time;
+	char trace_type[64];
+
+	progname = argv[0];
+	if (argc != 3) {
+		usage();
+		exit(EXIT_FAILURE);
+	}
+	infile = argv[1];
+	outfile = argv[2];
+	if (stat(infile, &st) < 0) {
+		fprintf(stderr, "%s Can't stat %s\n",
+			progname, infile);
+		exit(EXIT_FAILURE);
+	}
+	if (st.st_size == 0) {
+		fprintf(stderr, "%s Empty file %s\n",
+			progname, infile);
+		exit(EXIT_FAILURE);
+	}
+	init_prev_time(&prev_time);
+	init_filename_cache();
+	fp = fopen(infile, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "%s Can't open %s\n",
+			progname, infile);
+		exit(EXIT_FAILURE);
+	}
+	while (fgets(in_buf, 2048, fp)) {
+		s = in_buf;
+		while (isspace(*s))
+			s++;
+		in_mem_fop = malloc(sizeof(struct in_mem_file_op));
+		disk_file_op = &in_mem_fop->disk_file_op;
+		disk_file_op->delta_us = get_delta_ts(s, &prev_time);
+		get_tracetype(s, trace_type);
+		if (strcmp(trace_type, "strace") == 0) {
+			get_syscall(s, syscall);
+			disk_file_op->file_op = map_syscall(syscall);
+		} else
+			disk_file_op->file_op = map_syscall("ftrace");
+		get_pathname(s, path, disk_file_op->file_op);
+		db_node = files_db_add(path);
+		disk_file_op->fileno = files_db_get_fileno(db_node);
+		switch (disk_file_op->file_op) {
+		case IOSHARK_LLSEEK:
+		case IOSHARK_LSEEK:
+			get_lseek_offset_action(s,
+					disk_file_op->file_op,
+					&disk_file_op->lseek_offset,
+					lseek_action_str);
+			disk_file_op->lseek_action =
+				map_lseek_action(lseek_action_str);
+			if (disk_file_op->lseek_action == SEEK_SET)
+				files_db_update_size(db_node,
+						     disk_file_op->lseek_offset);
+			break;
+		case IOSHARK_PREAD64:
+		case IOSHARK_PWRITE64:
+			get_prw64_offset_len(s,
+					     &disk_file_op->prw_offset,
+					     (u_int64_t *)&disk_file_op->prw_len);
+			files_db_update_size(db_node,
+					     disk_file_op->prw_offset +
+					     disk_file_op->prw_len);
+			break;
+		case IOSHARK_READ:
+		case IOSHARK_WRITE:
+			get_rw_len(s, (u_int64_t *)&disk_file_op->rw_len);
+			files_db_add_to_size(db_node,
+					     disk_file_op->rw_len);
+			break;
+		case IOSHARK_MMAP:
+		case IOSHARK_MMAP2:
+			get_mmap_offset_len_prot(s,
+				    &disk_file_op->mmap_prot,
+				    &disk_file_op->mmap_offset,
+				    (u_int64_t *)&disk_file_op->mmap_len);
+			files_db_update_size(db_node,
+				     disk_file_op->mmap_offset +
+				     disk_file_op->mmap_len);
+			break;
+		case IOSHARK_OPEN:
+			disk_file_op->open_mode = 0;
+			get_openat_flags_mode(s, open_flags_str,
+				      &disk_file_op->open_mode);
+			disk_file_op->open_flags =
+				map_open_flags(open_flags_str);
+			break;
+		case IOSHARK_FSYNC:
+		case IOSHARK_FDATASYNC:
+			break;
+		case IOSHARK_CLOSE:
+			break;
+		case IOSHARK_MAPPED_PREAD:
+			/* Convert a mmap'ed read into a PREAD64 */
+			disk_file_op->file_op = IOSHARK_PREAD64;
+			get_ftrace_offset_len(s,
+					      &disk_file_op->prw_offset,
+					      (u_int64_t *)&disk_file_op->prw_len);
+			files_db_update_size(db_node,
+					     disk_file_op->prw_offset +
+					     disk_file_op->prw_len);
+			break;
+		default:
+			break;
+		}
+		/* Chain at the end */
+		num_io_operations++;
+		in_mem_fop->next = NULL;
+		if (in_mem_file_op_head == NULL) {
+			in_mem_file_op_tail = in_mem_fop;
+			in_mem_file_op_head = in_mem_fop;
+		} else {
+			in_mem_file_op_tail->next = in_mem_fop;
+			in_mem_file_op_tail = in_mem_fop;
+		}
+	}
+	fclose(fp);
+	/*
+	 * Now we can write everything out to the output tracefile.
+	 */
+	fp = fopen(outfile, "w+");
+	if (fp == NULL) {
+		fprintf(stderr, "%s Can't open trace.outfile\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	header.num_io_operations = num_io_operations;
+	header.num_files = files_db_get_total_obj();
+	if (fwrite(&header, sizeof(struct ioshark_header), 1, fp) != 1) {
+		fprintf(stderr, "%s Write error trace.outfile\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	files_db_write_objects(fp);
+	while (in_mem_file_op_head != NULL) {
+		struct in_mem_file_op *temp;
+
+		disk_file_op = &in_mem_file_op_head->disk_file_op;
+		if (fwrite(disk_file_op,
+			   sizeof(struct ioshark_file_operation), 1, fp) != 1) {
+			fprintf(stderr, "%s Write error trace.outfile\n",
+				progname);
+			exit(EXIT_FAILURE);
+		}
+		temp = in_mem_file_op_head;
+		in_mem_file_op_head = in_mem_file_op_head->next;
+		free(temp);
+	}
+	store_filename_cache();
+}
diff --git a/ioshark/compile_ioshark.h b/ioshark/compile_ioshark.h
new file mode 100644
index 0000000..9e11bd2
--- /dev/null
+++ b/ioshark/compile_ioshark.h
@@ -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.
+ */
+
+#define FILE_DB_HASHSIZE	8192
+
+struct files_db_s {
+	char *filename;
+	int fileno;
+	struct files_db_s *next;
+	size_t	size;
+	int	global_filename_ix;
+};
+
+/* Lifted from Wikipedia Jenkins Hash function page */
+static inline u_int32_t
+jenkins_one_at_a_time_hash(char *key, size_t len)
+{
+	u_int32_t hash, i;
+
+	for(hash = i = 0; i < len; ++i) {
+		hash += key[i];
+		hash += (hash << 10);
+		hash ^= (hash >> 6);
+	}
+	hash += (hash << 3);
+	hash ^= (hash >> 11);
+	hash += (hash << 15);
+	return hash;
+}
+
+static inline void
+files_db_update_size(void *node, u_int64_t new_size)
+{
+	struct files_db_s *db_node = (struct files_db_s *)node;
+
+	if (db_node->size < new_size)
+		db_node->size = new_size;
+}
+
+static inline void
+files_db_add_to_size(void *node, u_int64_t size_incr)
+{
+	((struct files_db_s *)node)->size += size_incr;
+}
+
+static inline int
+files_db_get_fileno(void *node)
+{
+	return (((struct files_db_s *)node)->fileno);
+}
+
+static inline char *
+files_db_get_filename(void *node)
+{
+	return (((struct files_db_s *)node)->filename);
+}
+
+
+void *files_db_create_handle(void);
+void files_db_write_objects(FILE *fp);
+void *files_db_add(char *filename);
+void *files_db_lookup(char *filename);
+int files_db_get_total_obj(void);
+void init_filename_cache(void);
+void store_filename_cache(void);
+
+
+
diff --git a/ioshark/compile_ioshark_subr.c b/ioshark/compile_ioshark_subr.c
new file mode 100644
index 0000000..5cc07c6
--- /dev/null
+++ b/ioshark/compile_ioshark_subr.c
@@ -0,0 +1,217 @@
+/*
+ * 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 <fcntl.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include "ioshark.h"
+#include "compile_ioshark.h"
+
+extern char *progname;
+
+static struct files_db_s *files_db_buckets[FILE_DB_HASHSIZE];
+static int current_fileno = 1;
+static int num_objects = 0;
+
+static int filename_cache_lookup(char *filename);;
+
+void
+files_db_write_objects(FILE *fp)
+{
+	int i;
+	struct ioshark_file_state st;
+
+	for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+		struct files_db_s *db_node, *s;
+
+		db_node = files_db_buckets[i];
+		while (db_node != NULL) {
+			st.fileno = db_node->fileno;
+			st.size = db_node->size;
+			st.global_filename_ix =
+				db_node->global_filename_ix;
+			if (fwrite(&st, sizeof(st), 1, fp) != 1) {
+				fprintf(stderr,
+					"%s Write error trace.outfile\n",
+					progname);
+				exit(EXIT_FAILURE);
+			}
+			s = db_node;
+			db_node = db_node->next;
+			free(s->filename);
+			free(s);
+		}
+	}
+}
+
+void *files_db_lookup(char *pathname)
+{
+	u_int32_t hash;
+	struct files_db_s *db_node;
+
+	hash = jenkins_one_at_a_time_hash(pathname, strlen(pathname));
+	hash %= FILE_DB_HASHSIZE;
+	db_node = files_db_buckets[hash];
+	while (db_node != NULL) {
+		if (strcmp(db_node->filename, pathname) == 0)
+			break;
+		db_node = db_node->next;
+	}
+	return db_node;
+}
+
+void *files_db_add(char *filename)
+{
+	u_int32_t hash;
+	struct files_db_s *db_node;
+
+	if ((db_node = files_db_lookup(filename)))
+		return db_node;
+	hash = jenkins_one_at_a_time_hash(filename, strlen(filename));
+	hash %= FILE_DB_HASHSIZE;
+	db_node = malloc(sizeof(struct files_db_s));
+	db_node->filename = strdup(filename);
+	db_node->global_filename_ix =
+		filename_cache_lookup(filename);
+	db_node->fileno = current_fileno++;
+	db_node->next = files_db_buckets[hash];
+	db_node->size = 0;
+	files_db_buckets[hash] = db_node;
+	num_objects++;
+	return db_node;
+}
+
+int
+files_db_get_total_obj(void)
+{
+	return num_objects;
+}
+
+static struct ioshark_filename_struct *filename_cache;
+static int filename_cache_num_entries;
+static int filename_cache_size;
+
+void
+init_filename_cache(void)
+{
+	static FILE *filename_cache_fp;
+	struct stat st;
+	int file_exists = 1;
+
+	if (stat("ioshark_filenames", &st) < 0) {
+		if (errno != ENOENT) {
+			fprintf(stderr, "%s Can't stat ioshark_filenames file\n",
+				progname);
+			exit(EXIT_FAILURE);
+		} else {
+			file_exists = 0;
+			filename_cache_num_entries = 0;
+		}
+	} else {
+		filename_cache_num_entries = st.st_size /
+			sizeof(struct ioshark_filename_struct);
+	}
+	if (file_exists) {
+		filename_cache_fp = fopen("ioshark_filenames", "r");
+		if (filename_cache_fp == NULL) {
+			fprintf(stderr, "%s Cannot open ioshark_filenames file\n",
+				progname);
+			exit(EXIT_FAILURE);
+		}
+	}
+	/* Preallocate a fixed size of entries */
+	filename_cache_size = filename_cache_num_entries + 1024;
+	filename_cache = calloc(filename_cache_size,
+				sizeof(struct ioshark_filename_struct));
+	if (filename_cache == NULL) {
+		fprintf(stderr, "%s Can't allocate memory - this is fatal\n",
+			__func__);
+		exit(EXIT_FAILURE);
+	}
+	if (fread(filename_cache,
+		  sizeof(struct ioshark_filename_struct),
+		  filename_cache_num_entries,
+		  filename_cache_fp) != (size_t)filename_cache_num_entries) {
+		fprintf(stderr, "%s Can't read ioshark_filenames file\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	if (file_exists)
+		fclose(filename_cache_fp);
+}
+
+static int
+filename_cache_lookup(char *filename)
+{
+	int ret;
+	int i;
+
+	for (i = 0 ; i < filename_cache_num_entries ; i++) {
+		if (strcmp(filename_cache[i].path, filename) == 0)
+			return i;
+	}
+	if (filename_cache_num_entries >= filename_cache_size) {
+		int newsize;
+
+		/* reallocate the filename cache up first */
+		filename_cache_size += 1024;
+		newsize = filename_cache_size *
+			sizeof(struct ioshark_filename_struct);
+		filename_cache = realloc(filename_cache, newsize);
+		if (filename_cache == NULL) {
+			fprintf(stderr,
+				"%s Can't allocate memory - this is fatal\n",
+				__func__);
+			exit(EXIT_FAILURE);
+		}
+	}
+	strcpy(filename_cache[filename_cache_num_entries].path,
+	       filename);
+	ret = filename_cache_num_entries;
+	filename_cache_num_entries++;
+	return ret;
+}
+
+void
+store_filename_cache(void)
+{
+	static FILE *filename_cache_fp;
+
+	filename_cache_fp = fopen("ioshark_filenames", "w+");
+	if (filename_cache_fp == NULL) {
+		fprintf(stderr, "%s Cannot open ioshark_filenames file\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	if (fwrite(filename_cache,
+		   sizeof(struct ioshark_filename_struct),
+		   filename_cache_num_entries,
+		   filename_cache_fp) != (size_t)filename_cache_num_entries) {
+		fprintf(stderr, "%s Can't read ioshark_filenames file\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	fclose(filename_cache_fp);
+	free(filename_cache);
+}
+
+
+
diff --git a/ioshark/dump_ioshark_filenames.c b/ioshark/dump_ioshark_filenames.c
new file mode 100644
index 0000000..c082c27
--- /dev/null
+++ b/ioshark/dump_ioshark_filenames.c
@@ -0,0 +1,80 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "ioshark.h"
+
+/*
+ * Real simple utility that just extracts and dumps the IOshark filenames
+ * one per line from the ioshark_filenames file. Useful in debugging.
+ */
+int
+main(int argc __attribute__((unused)), char **argv)
+{
+	char *progname;
+	static FILE *filename_cache_fp;
+	struct stat st;
+	struct ioshark_filename_struct *filename_cache;
+	int filename_cache_num_entries;
+	size_t filename_cache_size;
+	int i;
+
+	progname = argv[0];
+	if (stat("ioshark_filenames", &st) < 0) {
+		fprintf(stderr, "%s Can't stat ioshark_filenames file\n",
+			progname);
+			exit(EXIT_FAILURE);
+	}
+	filename_cache_num_entries = st.st_size /
+		sizeof(struct ioshark_filename_struct);
+	filename_cache_fp = fopen("ioshark_filenames", "r");
+	if (filename_cache_fp == NULL) {
+		fprintf(stderr, "%s Cannot open ioshark_filenames file\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	/* Preallocate a fixed size of entries */
+	filename_cache_size = filename_cache_num_entries + 1024;
+	filename_cache = calloc(filename_cache_size,
+				sizeof(struct ioshark_filename_struct));
+	if (filename_cache == NULL) {
+		fprintf(stderr, "%s Can't allocate memory - this is fatal\n",
+			__func__);
+		exit(EXIT_FAILURE);
+	}
+	if (fread(filename_cache,
+		  sizeof(struct ioshark_filename_struct),
+		  filename_cache_num_entries,
+		  filename_cache_fp) != (size_t)filename_cache_num_entries) {
+		fprintf(stderr, "%s Can't read ioshark_filenames file\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+       for (i = 0 ; i < filename_cache_num_entries ; i++) {
+	       printf("%s\n", filename_cache[i].path);
+       }
+       free(filename_cache);
+       fclose(filename_cache_fp);
+}
diff --git a/ioshark/ioshark.h b/ioshark/ioshark.h
new file mode 100644
index 0000000..fd9caca
--- /dev/null
+++ b/ioshark/ioshark.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+/*
+ * Format of the parsed workload files.
+ * 1) Header
+ * 2) Table of the entries, each entry describes 1 file
+ * 3) Table of IO operations to perform on the files
+ */
+
+/*
+ * The parsed workload file starts off with the header, which
+ * contains the count of the total # of files that are operated on.
+ * and the total number of IO operations.
+ */
+struct ioshark_header {
+	int	num_files;
+	int	num_io_operations;
+};
+
+/*
+ * After the header, we have a table of #files entries. Each entry
+ * in this table describes 1 file, indexed by fileno and with the
+ * specified size.
+ * Before the tests starts, these files are pre-created.
+ */
+struct ioshark_file_state {
+	int	fileno;	/* 1..num_files, with files name ioshark.<fileno> */
+	size_t	size;
+	int	global_filename_ix;
+};
+
+enum file_op {
+	IOSHARK_LSEEK = 0,
+	IOSHARK_LLSEEK,
+	IOSHARK_PREAD64,
+	IOSHARK_PWRITE64,
+	IOSHARK_READ,
+	IOSHARK_WRITE,
+	IOSHARK_MMAP,
+	IOSHARK_MMAP2,
+	IOSHARK_OPEN,
+	IOSHARK_FSYNC,
+	IOSHARK_FDATASYNC,
+	IOSHARK_CLOSE,
+	IOSHARK_MAPPED_PREAD,
+	IOSHARK_MAPPED_PWRITE,
+	IOSHARK_MAX_FILE_OP
+};
+
+/* mmap prot flags */
+#define IOSHARK_PROT_READ	0x1
+#define IOSHARK_PROT_WRITE	0x2
+
+/*
+ * Next we have the table of IO operatiosn to perform. Each
+ * IO operation is described by this entry.
+ */
+struct ioshark_file_operation {
+	/* delta us between previous file op and this */
+	u_int64_t		delta_us;
+	enum file_op		file_op;
+	int			fileno;
+	union {
+		struct lseek_args {
+#define lseek_offset	u.lseek_a.offset
+#define lseek_action	u.lseek_a.action
+			off_t	offset;
+			int action;
+		} lseek_a;
+		struct prw_args {
+#define prw_offset	u.prw_a.offset
+#define prw_len		u.prw_a.len
+			off_t	offset;
+			size_t	len;
+		} prw_a;
+#define rw_len		u.rw_a.len
+		struct rw_args {
+			size_t	len;
+		} rw_a;
+#define mmap_offset	u.mmap_a.offset
+#define mmap_len	u.mmap_a.len
+#define mmap_prot	u.mmap_a.prot
+		struct mmap_args {
+			off_t	offset;
+			size_t	len;
+			int	prot;
+	} mmap_a;
+#define open_flags	u.open_a.flags
+#define open_mode	u.open_a.mode
+		struct open_args {
+			int	flags;
+			mode_t	mode;
+		} open_a;
+	} u;
+};
+
+#define MAX_IOSHARK_PATHLEN	512
+
+/*
+ * Global table of all fileames
+ */
+struct ioshark_filename_struct
+{
+	char path[MAX_IOSHARK_PATHLEN];
+};
diff --git a/ioshark/ioshark_bench.c b/ioshark/ioshark_bench.c
new file mode 100644
index 0000000..f44e73e
--- /dev/null
+++ b/ioshark/ioshark_bench.c
@@ -0,0 +1,862 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+#include <pthread.h>
+#include <sys/statfs.h>
+#include <sys/resource.h>
+#include "ioshark.h"
+#define IOSHARK_MAIN
+#include "ioshark_bench.h"
+
+/*
+ * Note on "quick" mode where we do reads on existing /system,
+ * /vendor and other files in ro partitions, instead of creating
+ * them. The ioshark compiler builds up a table of all the files
+ * in /system, /vendor and other ro partitions. For files in this
+ * list, the benchmark skips the pre-creation of these files and
+ * reads them directly.
+ * The code relevant to this is in *filename_cache*.
+ */
+
+char *progname;
+
+#define MAX_INPUT_FILES		8192
+#define MAX_THREADS		8192
+
+struct thread_state_s {
+	char *filename;
+	FILE *fp;
+	int num_files;
+	void *db_handle;
+};
+
+struct thread_state_s thread_state[MAX_INPUT_FILES];
+int num_input_files = 0;
+int next_input_file;
+
+pthread_t tid[MAX_THREADS];
+
+/*
+ * Global options
+ */
+int do_delay = 0;
+int verbose = 0;
+int summary_mode = 0;
+int quick_mode = 0;
+
+#if 0
+static long gettid()
+{
+        return syscall(__NR_gettid);
+}
+#endif
+
+void usage()
+{
+	fprintf(stderr, "%s [-d preserve_delays] [-n num_iterations] [-t num_threads] -q -v | -s <list of parsed input files>\n",
+		progname);
+	fprintf(stderr, "%s -s, -v are mutually exclusive\n",
+		progname);
+	exit(EXIT_FAILURE);
+}
+
+pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER;
+struct timeval aggregate_file_create_time;
+struct timeval debug_file_create_time;
+struct timeval aggregate_file_remove_time;
+struct timeval aggregate_IO_time;
+struct timeval aggregate_delay_time;
+
+u_int64_t aggr_op_counts[IOSHARK_MAX_FILE_OP];
+struct rw_bytes_s aggr_io_rw_bytes;
+struct rw_bytes_s aggr_create_rw_bytes;
+
+/*
+ * Locking needed here because aggregate_delay_time is updated
+ * from multiple threads concurrently.
+ */
+static void
+update_time(struct timeval *aggr_time,
+	    struct timeval *delta_time)
+{
+	struct timeval tmp;
+
+	pthread_mutex_lock(&time_mutex);
+	timeradd(aggr_time, delta_time, &tmp);
+	*aggr_time = tmp;
+	pthread_mutex_unlock(&time_mutex);
+}
+
+static void
+update_op_counts(u_int64_t *op_counts)
+{
+	int i;
+
+	pthread_mutex_lock(&stats_mutex);
+	for (i = IOSHARK_LSEEK ; i < IOSHARK_MAX_FILE_OP ; i++)
+		aggr_op_counts[i] += op_counts[i];
+	pthread_mutex_unlock(&stats_mutex);
+}
+
+static void
+update_byte_counts(struct rw_bytes_s *dest, struct rw_bytes_s *delta)
+{
+	pthread_mutex_lock(&stats_mutex);
+	dest->bytes_read += delta->bytes_read;
+	dest->bytes_written += delta->bytes_written;
+	pthread_mutex_unlock(&stats_mutex);
+}
+
+static int work_next_file;
+static int work_num_files;
+
+void
+init_work(int next_file, int num_files)
+{
+	pthread_mutex_lock(&work_mutex);
+	work_next_file = next_file;
+	work_num_files = work_next_file + num_files;
+	pthread_mutex_unlock(&work_mutex);
+}
+
+/* Dole out the next file to work on to the thread */
+static struct thread_state_s *
+get_work()
+{
+	struct thread_state_s *work = NULL;
+
+	pthread_mutex_lock(&work_mutex);
+	if (work_next_file < work_num_files)
+		work = &thread_state[work_next_file++];
+	pthread_mutex_unlock(&work_mutex);
+	return work;
+}
+
+static void
+create_files(struct thread_state_s *state)
+{
+	int i;
+	struct ioshark_file_state file_state;
+	char path[MAX_IOSHARK_PATHLEN];
+	void *db_node;
+	struct rw_bytes_s rw_bytes;
+	char *filename;
+	int readonly;
+
+	memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
+	for (i = 0 ; i < state->num_files ; i++) {
+		if (fread(&file_state, sizeof(struct ioshark_file_state),
+			  1, state->fp) != 1) {
+			fprintf(stderr, "%s read error tracefile\n",
+				progname);
+			exit(EXIT_FAILURE);
+		}
+		/*
+		 * Check to see if the file is in a readonly partition,
+		 * in which case, we don't have to pre-create the file
+		 * we can just read the existing file.
+		 */
+		filename =
+			get_ro_filename(file_state.global_filename_ix);
+		if (quick_mode)
+			assert(filename != NULL);
+		if (quick_mode == 0 ||
+		    is_readonly_mount(filename, file_state.size) == 0) {
+			sprintf(path, "file.%d.%d",
+				(int)(state - thread_state),
+				file_state.fileno);
+			create_file(path, file_state.size,
+				    &rw_bytes);
+			filename = path;
+			readonly = 0;
+		} else {
+			readonly = 1;
+		}
+		db_node = files_db_add_byfileno(state->db_handle,
+						file_state.fileno,
+						readonly);
+		files_db_update_size(db_node, file_state.size);
+		files_db_update_filename(db_node, filename);
+	}
+	update_byte_counts(&aggr_create_rw_bytes, &rw_bytes);
+}
+
+static void
+do_one_io(void *db_node,
+	  struct ioshark_file_operation *file_op,
+	  u_int64_t *op_counts,
+	  struct rw_bytes_s *rw_bytes,
+	  char **bufp, int *buflen)
+{
+	assert(file_op->file_op < IOSHARK_MAX_FILE_OP);
+	op_counts[file_op->file_op]++;
+	switch (file_op->file_op) {
+	int ret;
+	char *p;
+	int fd;
+
+	case IOSHARK_LSEEK:
+	case IOSHARK_LLSEEK:
+		ret = lseek(files_db_get_fd(db_node),
+			    file_op->lseek_offset,
+			    file_op->lseek_action);
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s: lseek(%s %lu %d) returned error %d\n",
+				progname, files_db_get_filename(db_node),
+				file_op->lseek_offset,
+				file_op->lseek_action, errno);
+			exit(EXIT_FAILURE);
+		}
+		break;
+	case IOSHARK_PREAD64:
+		p = get_buf(bufp, buflen, file_op->prw_len, 0);
+		ret = pread(files_db_get_fd(db_node), p,
+			    file_op->prw_len, file_op->prw_offset);
+		rw_bytes->bytes_read += file_op->prw_len;
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s: pread(%s %zu %lu) error %d\n",
+				progname,
+				files_db_get_filename(db_node),
+				file_op->prw_len,
+				file_op->prw_offset, errno);
+			exit(EXIT_FAILURE);
+		}
+		break;
+	case IOSHARK_PWRITE64:
+		p = get_buf(bufp, buflen, file_op->prw_len, 1);
+		ret = pwrite(files_db_get_fd(db_node), p,
+			     file_op->prw_len, file_op->prw_offset);
+		rw_bytes->bytes_written += file_op->prw_len;
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s: pwrite(%s %zu %lu) error %d\n",
+				progname,
+				files_db_get_filename(db_node),
+				file_op->prw_len,
+				file_op->prw_offset, errno);
+			exit(EXIT_FAILURE);
+		}
+		break;
+	case IOSHARK_READ:
+		p = get_buf(bufp, buflen, file_op->rw_len, 0);
+		ret = read(files_db_get_fd(db_node), p,
+			   file_op->rw_len);
+		rw_bytes->bytes_read += file_op->rw_len;
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s: read(%s %zu) error %d\n",
+				progname,
+				files_db_get_filename(db_node),
+				file_op->rw_len,
+				errno);
+			exit(EXIT_FAILURE);
+		}
+		break;
+	case IOSHARK_WRITE:
+		p = get_buf(bufp, buflen, file_op->rw_len, 1);
+		ret = write(files_db_get_fd(db_node), p,
+			    file_op->rw_len);
+		rw_bytes->bytes_written += file_op->rw_len;
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s: write(%s %zu) error %d\n",
+				progname,
+				files_db_get_filename(db_node),
+				file_op->rw_len,
+				errno);
+			exit(EXIT_FAILURE);
+		}
+		break;
+	case IOSHARK_MMAP:
+	case IOSHARK_MMAP2:
+		ioshark_handle_mmap(db_node, file_op,
+				    bufp, buflen, op_counts,
+				    rw_bytes);
+		break;
+	case IOSHARK_OPEN:
+		if (file_op->open_flags & O_CREAT) {
+			fd = open(files_db_get_filename(db_node),
+				  file_op->open_flags,
+				  file_op->open_mode);
+			if (fd < 0) {
+				/*
+				 * EEXIST error acceptable, others are fatal.
+				 * Although we failed to O_CREAT the file (O_EXCL)
+				 * We will force an open of the file before any
+				 * IO.
+				 */
+				if (errno == EEXIST) {
+					return;
+				} else {
+					fprintf(stderr,
+						"%s: O_CREAT open(%s %x %o) error %d\n",
+						progname,
+						files_db_get_filename(db_node),
+						file_op->open_flags,
+						file_op->open_mode, errno);
+					exit(EXIT_FAILURE);
+				}
+			}
+		} else {
+			fd = open(files_db_get_filename(db_node),
+				  file_op->open_flags);
+			if (fd < 0) {
+				if (file_op->open_flags & O_DIRECTORY) {
+					/* O_DIRECTORY open()s should fail */
+					return;
+				} else {
+					fprintf(stderr,
+						"%s: open(%s %x) error %d\n",
+						progname,
+						files_db_get_filename(db_node),
+						file_op->open_flags,
+						errno);
+					exit(EXIT_FAILURE);
+				}
+			}
+		}
+		files_db_close_fd(db_node);
+		files_db_update_fd(db_node, fd);
+		break;
+	case IOSHARK_FSYNC:
+	case IOSHARK_FDATASYNC:
+		if (file_op->file_op == IOSHARK_FSYNC) {
+			ret = fsync(files_db_get_fd(db_node));
+			if (ret < 0) {
+				fprintf(stderr,
+					"%s: fsync(%s) error %d\n",
+					progname,
+					files_db_get_filename(db_node),
+					errno);
+				exit(EXIT_FAILURE);
+			}
+		} else {
+			ret = fdatasync(files_db_get_fd(db_node));
+			if (ret < 0) {
+				fprintf(stderr,
+					"%s: fdatasync(%s) error %d\n",
+					progname,
+					files_db_get_filename(db_node),
+					errno);
+				exit(EXIT_FAILURE);
+			}
+		}
+		break;
+	case IOSHARK_CLOSE:
+		ret = close(files_db_get_fd(db_node));
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s: close(%s) error %d\n",
+				progname,
+				files_db_get_filename(db_node), errno);
+			exit(EXIT_FAILURE);
+		}
+		files_db_update_fd(db_node, -1);
+		break;
+	default:
+		fprintf(stderr, "%s: unknown FILE_OP %d\n",
+			progname, file_op->file_op);
+		exit(EXIT_FAILURE);
+		break;
+	}
+}
+
+static void
+do_io(struct thread_state_s *state)
+{
+	void *db_node;
+	struct ioshark_header header;
+	struct ioshark_file_operation file_op;
+	int fd;
+	int i;
+	char *buf = NULL;
+	int buflen = 0;
+	struct timeval total_delay_time;
+	u_int64_t op_counts[IOSHARK_MAX_FILE_OP];
+	struct rw_bytes_s rw_bytes;
+
+	rewind(state->fp);
+	if (fread(&header, sizeof(struct ioshark_header), 1, state->fp) != 1) {
+		fprintf(stderr, "%s read error %s\n",
+			progname, state->filename);
+		exit(EXIT_FAILURE);
+	}
+	/*
+	 * First open and pre-create all the files. Indexed by fileno.
+	 */
+	timerclear(&total_delay_time);
+	memset(&rw_bytes, 0, sizeof(struct rw_bytes_s));
+	memset(op_counts, 0, sizeof(op_counts));
+	fseek(state->fp,
+	      sizeof(struct ioshark_header) +
+	      header.num_files * sizeof(struct ioshark_file_state),
+	      SEEK_SET);
+	/*
+	 * Loop over all the IOs, and launch each
+	 */
+	for (i = 0 ; i < header.num_io_operations ; i++) {
+		if (fread(&file_op, sizeof(struct ioshark_file_operation),
+			  1, state->fp) != 1) {
+			fprintf(stderr, "%s read error trace.outfile\n",
+				progname);
+			exit(EXIT_FAILURE);
+		}
+		if (do_delay) {
+			struct timeval start;
+
+			(void)gettimeofday(&start, (struct timezone *)NULL);
+			usleep(file_op.delta_us);
+			update_delta_time(&start, &total_delay_time);
+		}
+		db_node = files_db_lookup_byfileno(state->db_handle,
+						   file_op.fileno);
+		if (db_node == NULL) {
+			fprintf(stderr,
+				"%s Can't lookup fileno %d, fatal error\n",
+				progname, file_op.fileno);
+			exit(EXIT_FAILURE);
+		}
+		if (file_op.file_op != IOSHARK_OPEN &&
+		    files_db_get_fd(db_node) == -1) {
+			int openflags;
+
+			/*
+			 * This is a hack to workaround the fact that we did not
+			 * see an open() for this file until now. open() the
+			 * file O_RDWR, so that we can perform the IO.
+			 */
+			if (files_db_readonly(db_node))
+				openflags = O_RDONLY;
+			else
+				openflags = O_RDWR;
+			fd = open(files_db_get_filename(db_node),
+				  openflags);
+			if (fd < 0) {
+				fprintf(stderr, "%s: open(%s %x) error %d\n",
+					progname,
+					files_db_get_filename(db_node),
+					openflags,
+					errno);
+				exit(EXIT_FAILURE);
+			}
+			files_db_update_fd(db_node, fd);
+		}
+		do_one_io(db_node, &file_op,
+			  op_counts, &rw_bytes, &buf, &buflen);
+	}
+	files_db_fsync_discard_files(state->db_handle);
+	files_db_close_files(state->db_handle);
+	update_time(&aggregate_delay_time, &total_delay_time);
+	update_op_counts(op_counts);
+	update_byte_counts(&aggr_io_rw_bytes, &rw_bytes);
+}
+
+void *
+io_thread(void *unused __attribute__((unused)))
+{
+	struct thread_state_s *state;
+
+	srand(gettid());
+	while ((state = get_work()))
+		do_io(state);
+	pthread_exit(NULL);
+        return(NULL);
+}
+
+static void
+do_create(struct thread_state_s *state)
+{
+	struct ioshark_header header;
+
+	if (fread(&header, sizeof(struct ioshark_header), 1, state->fp) != 1) {
+		fprintf(stderr, "%s read error %s\n",
+			progname, state->filename);
+		exit(EXIT_FAILURE);
+	}
+	state->num_files = header.num_files;
+	state->db_handle = files_db_create_handle();
+	create_files(state);
+}
+
+void *
+create_files_thread(void *unused __attribute__((unused)))
+{
+	struct thread_state_s *state;
+
+	while ((state = get_work()))
+		do_create(state);
+	pthread_exit(NULL);
+	return(NULL);
+}
+
+int
+get_start_end(int *start_ix)
+{
+	int i, j, ret_numfiles;
+	u_int64_t free_fs_bytes;
+	char *infile;
+	FILE *fp;
+	struct ioshark_header header;
+	struct ioshark_file_state file_state;
+	struct statfs fsstat;
+	static int fssize_clamp_next_index = 0;
+	static int chunk = 0;
+
+	if (fssize_clamp_next_index == num_input_files)
+		return 0;
+	if (statfs("/data/local/tmp", &fsstat) < 0) {
+		fprintf(stderr, "%s: Can't statfs /data/local/tmp\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	free_fs_bytes = (fsstat.f_bavail * fsstat.f_bsize) * 9 /10;
+	for (i = fssize_clamp_next_index; i < num_input_files; i++) {
+		infile = thread_state[i].filename;
+		fp = fopen(infile, "r");
+		if (fp == NULL) {
+			fprintf(stderr, "%s: Can't open %s\n",
+				progname, infile);
+			exit(EXIT_FAILURE);
+		}
+		if (fread(&header, sizeof(struct ioshark_header),
+			  1, fp) != 1) {
+			fprintf(stderr, "%s read error %s\n",
+				progname, infile);
+			exit(EXIT_FAILURE);
+		}
+		for (j = 0 ; j < header.num_files ; j++) {
+			if (fread(&file_state, sizeof(struct ioshark_file_state),
+				  1, fp) != 1) {
+				fprintf(stderr, "%s read error tracefile\n",
+					progname);
+				exit(EXIT_FAILURE);
+			}
+			if (quick_mode == 0 ||
+			    !is_readonly_mount(
+				    get_ro_filename(file_state.global_filename_ix),
+				    file_state.size)) {
+				if (file_state.size > free_fs_bytes) {
+					fclose(fp);
+					goto out;
+				}
+				free_fs_bytes -= file_state.size;
+			}
+		}
+		fclose(fp);
+	}
+out:
+	if (verbose) {
+		if (chunk > 0 || i < num_input_files) {
+			printf("Breaking up input files, Chunk %d: %d to %d\n",
+			       chunk++, fssize_clamp_next_index, i - 1);
+		} else {
+			printf("Entire Dataset fits start = %d to %d, free_bytes = %ju\n",
+			       fssize_clamp_next_index,
+			       i - fssize_clamp_next_index,
+			       free_fs_bytes);
+		}
+	}
+	*start_ix = fssize_clamp_next_index;
+	ret_numfiles = i - fssize_clamp_next_index;
+	fssize_clamp_next_index = i;
+	return ret_numfiles;
+}
+
+int
+ioshark_pthread_create(pthread_t *tidp, void *(*start_routine)(void *))
+{
+	pthread_attr_t attr;
+
+	pthread_attr_init(&attr);
+	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
+	pthread_attr_setstacksize(&attr, (size_t)(1024*1024));
+	return pthread_create(tidp, &attr, start_routine, (void *)NULL);
+}
+
+void
+wait_for_threads(int num_threads)
+{
+	int i;
+
+	for (i = 0; i < num_threads; i++) {
+		pthread_join(tid[i], NULL);
+		tid[i] = 0;
+	}
+}
+
+#define IOSHARK_FD_LIM		8192
+
+static void
+sizeup_fd_limits(void)
+{
+	struct rlimit r;
+
+	getrlimit(RLIMIT_NOFILE, &r);
+	if (r.rlim_cur >= IOSHARK_FD_LIM)
+		/* cur limit already at what we want */
+		return;
+	/*
+	 * Size up both the Max and Cur to IOSHARK_FD_LIM.
+	 * If we are not running as root, this will fail,
+	 * catch that below and exit.
+	 */
+	if (r.rlim_max < IOSHARK_FD_LIM)
+		r.rlim_max = IOSHARK_FD_LIM;
+	r.rlim_cur = IOSHARK_FD_LIM;
+	if (setrlimit(RLIMIT_NOFILE, &r) < 0) {
+		fprintf(stderr, "%s: Can't setrlimit (RLIMIT_NOFILE, 8192)\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	getrlimit(RLIMIT_NOFILE, &r);
+	if (r.rlim_cur < IOSHARK_FD_LIM) {
+		fprintf(stderr, "%s: Can't setrlimit up to 8192\n",
+			progname);
+		fprintf(stderr, "%s: Running as root ?\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	int i;
+	FILE *fp;
+	struct stat st;
+	char *infile;
+	int num_threads = 0;
+	int num_iterations = 1;
+	int c;
+	int num_files, start_file;
+	struct thread_state_s *state;
+
+	progname = argv[0];
+        while ((c = getopt(argc, argv, "dn:st:qv")) != EOF) {
+                switch (c) {
+                case 'd':
+			do_delay = 1;
+			break;
+                case 'n':
+			num_iterations = atoi(optarg);
+			break;
+                case 's':
+			/* Non-verbose summary mode for nightly runs */
+			summary_mode = 1;
+			break;
+                case 't':
+			num_threads = atoi(optarg);
+			break;
+                case 'q':
+			/*
+			 * If quick mode is enabled, then we won't
+			 * pre-create files that we are doing IO on that
+			 * live in readonly partitions (/system, /vendor etc)
+			 */
+			quick_mode = 1;
+			break;
+                case 'v':
+			verbose = 1;
+			break;
+ 	        default:
+			usage();
+		}
+	}
+
+	if ((verbose + summary_mode) == 2)
+		usage();
+
+	if (num_threads > MAX_THREADS)
+		usage();
+
+	if (optind == argc)
+                usage();
+
+	sizeup_fd_limits();
+
+	for (i = optind; i < argc; i++) {
+		infile = argv[i];
+		if (stat(infile, &st) < 0) {
+			fprintf(stderr, "%s: Can't stat %s\n",
+				progname, infile);
+			exit(EXIT_FAILURE);
+		}
+		if (st.st_size == 0) {
+			fprintf(stderr, "%s: Empty file %s\n",
+				progname, infile);
+			continue;
+		}
+		fp = fopen(infile, "r");
+		if (fp == NULL) {
+			fprintf(stderr, "%s: Can't open %s\n",
+				progname, infile);
+			continue;
+		}
+		thread_state[num_input_files].filename = infile;
+		thread_state[num_input_files].fp = fp;
+		num_input_files++;
+	}
+
+	if (num_input_files == 0) {
+		exit(EXIT_SUCCESS);
+	}
+	if (verbose) {
+		printf("Total Input Files = %d\n", num_input_files);
+		printf("Num Iterations = %d\n", num_iterations);
+	}
+	timerclear(&aggregate_file_create_time);
+	timerclear(&aggregate_file_remove_time);
+	timerclear(&aggregate_IO_time);
+
+	if (quick_mode)
+		init_filename_cache();
+
+	capture_util_state_before();
+
+	/*
+	 * We pre-create the files that we need once and then we
+	 * loop around N times doing IOs on the pre-created files.
+	 *
+	 * get_start_end() breaks up the total work here to make sure
+	 * that all the files we need to pre-create fit into the
+	 * available space in /data/local/tmp (hardcoded for now).
+	 *
+	 * If it won't fit, then we do several sweeps.
+	 */
+	while ((num_files = get_start_end(&start_file))) {
+		struct timeval time_for_pass;
+
+		/* Create files once */
+		if (!summary_mode)
+			printf("Doing Pre-creation of Files\n");
+		if (quick_mode && !summary_mode)
+			printf("Skipping Pre-creation of read-only Files\n");
+		if (num_threads == 0 || num_threads > num_files)
+			num_threads = num_files;
+		(void)system("echo 3 > /proc/sys/vm/drop_caches");
+		init_work(start_file, num_files);
+		(void)gettimeofday(&time_for_pass,
+				   (struct timezone *)NULL);
+		for (i = 0; i < num_threads; i++) {
+			if (ioshark_pthread_create(&(tid[i]),
+						   create_files_thread)) {
+				fprintf(stderr,
+					"%s: Can't create creator thread %d\n",
+					progname, i);
+				exit(EXIT_FAILURE);
+			}
+		}
+		wait_for_threads(num_threads);
+		update_delta_time(&time_for_pass, &aggregate_file_create_time);
+		/* Do the IOs N times */
+		for (i = 0 ; i < num_iterations ; i++) {
+			(void)system("echo 3 > /proc/sys/vm/drop_caches");
+			if (!summary_mode) {
+				if (num_iterations > 1)
+					printf("Starting Test. Iteration %d...\n",
+					       i);
+				else
+					printf("Starting Test...\n");
+			}
+			init_work(start_file, num_files);
+			(void)gettimeofday(&time_for_pass,
+					   (struct timezone *)NULL);
+			for (c = 0; c < num_threads; c++) {
+				if (ioshark_pthread_create(&(tid[c]),
+							   io_thread)) {
+					fprintf(stderr,
+						"%s: Can't create thread %d\n",
+						progname, c);
+					exit(EXIT_FAILURE);
+				}
+			}
+			wait_for_threads(num_threads);
+			update_delta_time(&time_for_pass,
+					  &aggregate_IO_time);
+		}
+
+		/*
+		 * We are done with the N iterations of IO.
+		 * Destroy the files we pre-created.
+		 */
+		init_work(start_file, num_files);
+		while ((state = get_work())) {
+			struct timeval start;
+
+			(void)gettimeofday(&start, (struct timezone *)NULL);
+			files_db_unlink_files(state->db_handle);
+			update_delta_time(&start, &aggregate_file_remove_time);
+			files_db_free_memory(state->db_handle);
+		}
+	}
+	if (!summary_mode) {
+		printf("Total Creation time = %ju.%ju (msecs.usecs)\n",
+		       get_msecs(&aggregate_file_create_time),
+		       get_usecs(&aggregate_file_create_time));
+		printf("Total Remove time = %ju.%ju (msecs.usecs)\n",
+		       get_msecs(&aggregate_file_remove_time),
+		       get_usecs(&aggregate_file_remove_time));
+		if (do_delay)
+			printf("Total delay time = %ju.%ju (msecs.usecs)\n",
+			       get_msecs(&aggregate_delay_time),
+			       get_usecs(&aggregate_delay_time));
+		printf("Total Test (IO) time = %ju.%ju (msecs.usecs)\n",
+		       get_msecs(&aggregate_IO_time),
+		       get_usecs(&aggregate_IO_time));
+		if (verbose)
+			print_bytes("Upfront File Creation bytes",
+				    &aggr_create_rw_bytes);
+		print_bytes("Total Test (IO) bytes", &aggr_io_rw_bytes);
+		if (verbose)
+			print_op_stats(aggr_op_counts);
+		report_cpu_disk_util();
+	} else {
+		printf("%ju.%ju ",
+		       get_msecs(&aggregate_file_create_time),
+		       get_usecs(&aggregate_file_create_time));
+		printf("%ju.%ju ",
+		       get_msecs(&aggregate_file_remove_time),
+		       get_usecs(&aggregate_file_remove_time));
+		if (do_delay)
+			printf("%ju.%ju ",
+			       get_msecs(&aggregate_delay_time),
+			       get_usecs(&aggregate_delay_time));
+		printf("%ju.%ju ",
+		       get_msecs(&aggregate_IO_time),
+		       get_usecs(&aggregate_IO_time));
+		print_bytes(NULL, &aggr_io_rw_bytes);
+		report_cpu_disk_util();
+		printf("\n");
+	}
+	if (quick_mode)
+		free_filename_cache();
+}
diff --git a/ioshark/ioshark_bench.h b/ioshark/ioshark_bench.h
new file mode 100644
index 0000000..d0d0962
--- /dev/null
+++ b/ioshark/ioshark_bench.h
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#ifdef IOSHARK_MAIN
+const char *IO_op[] = {
+	"LSEEK",
+	"LLSEEK",
+	"PREAD64",
+	"PWRITE64",
+	"READ",
+	"WRITE",
+	"MMAP",
+	"MMAP2",
+	"OPEN",
+	"FSYNC",
+	"FDATASYNC",
+	"CLOSE",
+	"MAPPED_PREAD",
+	"MAPPED_PWRITE",
+	"MAX_FILE_OP"
+};
+#endif
+
+#define MAX(A, B)	((A) > (B) ? (A) : (B))
+#define MIN(A, B)	((A) < (B) ? (A) : (B))
+
+#define MINBUFLEN	(16*1024)
+
+#define FILE_DB_HASHSIZE	8192
+
+struct files_db_s {
+	char *filename;
+	int fileno;
+	size_t	size;
+	int fd;
+	int readonly;
+	int debug_open_flags;
+	struct files_db_s *next;
+};
+
+struct files_db_handle {
+	struct files_db_s *files_db_buckets[FILE_DB_HASHSIZE];
+};
+
+struct IO_operation_s {
+	char *IO_op;
+};
+
+struct rw_bytes_s {
+	u_int64_t bytes_read;
+	u_int64_t bytes_written;
+};
+
+static inline void
+files_db_update_size(void *node, u_int64_t new_size)
+{
+	struct files_db_s *db_node = (struct files_db_s *)node;
+
+	if (db_node->size < new_size)
+		db_node->size = new_size;
+}
+
+static inline void
+files_db_update_filename(void *node, char *filename)
+{
+	((struct files_db_s *)node)->filename = strdup(filename);
+}
+
+static inline int
+files_db_get_fileno(void *node)
+{
+	return (((struct files_db_s *)node)->fileno);
+}
+
+static inline int
+files_db_get_fd(void *node)
+{
+	return (((struct files_db_s *)node)->fd);
+}
+
+static inline char *
+files_db_get_filename(void *node)
+{
+	return (((struct files_db_s *)node)->filename);
+}
+
+static inline int
+files_db_readonly(void *node)
+{
+	return (((struct files_db_s *)node)->readonly);
+}
+
+static inline u_int64_t
+get_msecs(struct timeval *tv)
+{
+	return ((tv->tv_sec * 1000) + (tv->tv_usec / 1000));
+}
+
+static inline u_int64_t
+get_usecs(struct timeval *tv)
+{
+	return (tv->tv_usec % 1000);
+}
+
+static inline void
+update_delta_time(struct timeval *start,
+		  struct timeval *destination)
+{
+	struct timeval res, finish;
+
+	(void)gettimeofday(&finish, (struct timezone *)NULL);
+	timersub(&finish, start, &res);
+	timeradd(destination, &res, &finish);
+	*destination = finish;
+}
+
+void *files_db_create_handle(void);
+void *files_db_lookup_byfileno(void *handle, int fileno);
+void *files_db_add_byfileno(void *handle, int fileno, int readonly);
+void files_db_update_fd(void *node, int fd);
+void files_db_unlink_files(void *db_handle);
+void files_db_close_files(void *handle);
+void files_db_close_fd(void *node);
+void files_db_free_memory(void *handle);
+void create_file(char *path, size_t size,
+		 struct rw_bytes_s *rw_bytes);
+char *get_buf(char **buf, int *buflen, int len, int do_fill);
+void files_db_fsync_discard_files(void *handle);
+void print_op_stats(u_int64_t *op_counts);
+void print_bytes(char *desc, struct rw_bytes_s *rw_bytes);
+void ioshark_handle_mmap(void *db_node,
+			 struct ioshark_file_operation *file_op,
+			 char **bufp, int *buflen, u_int64_t *op_counts,
+			 struct rw_bytes_s *rw_bytes);
+void capture_util_state_before(void);
+void report_cpu_disk_util(void);
+
+char *get_ro_filename(int ix);
+void init_filename_cache(void);
+void free_filename_cache(void);
+int is_readonly_mount(char *filename, size_t size);
diff --git a/ioshark/ioshark_bench_mmap.c b/ioshark/ioshark_bench_mmap.c
new file mode 100644
index 0000000..55d7060
--- /dev/null
+++ b/ioshark/ioshark_bench_mmap.c
@@ -0,0 +1,201 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+#include "ioshark.h"
+#include "ioshark_bench.h"
+
+/*
+ * The purpose of this code is to convert mmap() calls into
+ * a mix of (semi)-random reads and writes.
+ * PROT_READ => 4KB/8KB/16KB random reads.
+ * PROT_WRITE => adds 4KB random writes.
+ */
+
+extern char *progname;
+
+#define IOSHARK_MAX_MMAP_IOLEN	(16*1024)
+
+#define MMAP_ENTS		16
+
+struct mmap_io_ent_tab_s {
+	off_t offset;
+	size_t len;
+};
+
+struct mmap_io_ent_s {
+	int				num_entries;
+	struct mmap_io_ent_tab_s	table[MMAP_ENTS + 1];
+	size_t				resid;
+};
+
+static void
+setup_mmap_io_state(struct mmap_io_ent_s *mio,
+		    size_t total_len, off_t offset)
+{
+	int slice;
+
+	memset(mio, 0, sizeof(struct mmap_io_ent_s));
+	mio->resid = total_len;
+	slice = MAX(IOSHARK_MAX_MMAP_IOLEN,
+		    total_len / MMAP_ENTS);
+	while (total_len > 0) {
+		assert(mio->num_entries < MMAP_ENTS + 1);
+		mio->table[mio->num_entries].offset = offset;
+		mio->table[mio->num_entries].len =
+			MIN((u_int64_t)total_len, (u_int64_t)slice);
+		total_len -= mio->table[mio->num_entries].len;
+		offset += mio->table[mio->num_entries].len;
+		mio->num_entries++;
+	}
+}
+
+static size_t
+mmap_getnext_off_len(struct mmap_io_ent_s *mio,
+		     off_t *offset)
+{
+	int i;
+	int find_rand_index[MMAP_ENTS + 1];
+	int rand_index_len = 0;
+	size_t iolength;
+
+	if (mio->resid == 0)
+		return 0;
+	/* Pick a slot with residual length > 0 at random first */
+	for (i = 0 ; i < MMAP_ENTS + 1 ; i++) {
+		if (mio->table[i].len > 0)
+			find_rand_index[rand_index_len++] = i;
+	}
+	i = find_rand_index[rand() % rand_index_len];
+	/* Randomize iolength 4K-16K */
+	iolength = ((rand() % 4) + 1) * 4096;
+	iolength = MIN(mio->table[i].len, iolength);
+	*offset = mio->table[i].offset;
+	mio->table[i].offset += iolength;
+	mio->table[i].len -= iolength;
+	mio->resid -= iolength;
+	return iolength;
+}
+
+static void
+mmap_do_io(void *db_node, int prot, off_t offset, size_t len,
+	   char **bufp, int *buflen, u_int64_t *op_counts,
+	   struct rw_bytes_s *rw_bytes)
+{
+	char *p;
+	int ret;
+
+	if (!(prot & IOSHARK_PROT_WRITE)) {
+		/* Only preads */
+		p = get_buf(bufp, buflen, len, 0);
+		ret = pread(files_db_get_fd(db_node),
+			    p, len, offset);
+		rw_bytes->bytes_read += len;
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s: mapped pread(%s %zu %lu) error fd=%d %s\n",
+				progname, files_db_get_filename(db_node),
+				len, offset, files_db_get_fd(db_node),
+				strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+		op_counts[IOSHARK_MAPPED_PREAD]++;
+	} else {
+		/* 50-50 R/W */
+		if ((rand() % 2) == 1) {
+			p = get_buf(bufp, buflen, len, 1);
+			ret = pwrite(files_db_get_fd(db_node),
+				     p, len, offset);
+			rw_bytes->bytes_written += len;
+			if (ret < 0) {
+#if 0
+				fprintf(stderr,
+					"%s: mapped pwrite failed, file unwriteable ? open_flags=%x\n",
+					progname,
+					fcntl(files_db_get_fd(db_node), F_GETFL));
+				exit(EXIT_FAILURE);
+#endif
+			} else
+				op_counts[IOSHARK_MAPPED_PWRITE]++;
+		} else {
+			p = get_buf(bufp, buflen, len, 0);
+			ret = pread(files_db_get_fd(db_node),
+				    p, len, offset);
+			rw_bytes->bytes_read += len;
+			if (ret < 0) {
+				fprintf(stderr,
+				"%s: mapped pread(%s %zu %lu) error fd=%d %s\n",
+					progname, files_db_get_filename(db_node),
+					len,
+					offset, files_db_get_fd(db_node),
+					strerror(errno));
+				exit(EXIT_FAILURE);
+			}
+			op_counts[IOSHARK_MAPPED_PREAD]++;
+		}
+	}
+}
+
+void
+ioshark_handle_mmap(void *db_node,
+		    struct ioshark_file_operation *file_op,
+		    char **bufp, int *buflen, u_int64_t *op_counts,
+		    struct rw_bytes_s *rw_bytes)
+{
+	off_t offset = file_op->mmap_offset;
+	size_t len = file_op->mmap_len;
+	int prot = file_op->mmap_prot;
+	struct mmap_io_ent_s mio;
+	struct stat statbuf;
+
+	if (fstat(files_db_get_fd(db_node), &statbuf) < 0) {
+		fprintf(stderr,
+			"%s: fstat failure %s\n",
+			__func__, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	/*
+	 * The size of the file better accomodate offset + len
+	 * Else there is an issue with pre-creation
+	 */
+	assert(offset + len <= statbuf.st_size);
+	if (len <= IOSHARK_MAX_MMAP_IOLEN) {
+		mmap_do_io(db_node, prot, offset, len,
+			   bufp, buflen, op_counts,
+			   rw_bytes);
+		return;
+	}
+	setup_mmap_io_state(&mio, len, offset);
+	assert(mio.num_entries > 0);
+	while ((len = mmap_getnext_off_len(&mio, &offset))) {
+		assert((offset + len) <=
+		       (file_op->mmap_offset + file_op->mmap_len));
+		mmap_do_io(db_node, prot, offset, len, bufp, buflen,
+			   op_counts, rw_bytes);
+	}
+}
diff --git a/ioshark/ioshark_bench_subr.c b/ioshark/ioshark_bench_subr.c
new file mode 100644
index 0000000..4280a5a
--- /dev/null
+++ b/ioshark/ioshark_bench_subr.c
@@ -0,0 +1,644 @@
+/*
+ * 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/vfs.h>
+#include <sys/statvfs.h>
+#include <sys/mman.h>
+#include "ioshark.h"
+#include "ioshark_bench.h"
+
+extern char *progname;
+extern int verbose, summary_mode;
+
+void *
+files_db_create_handle(void)
+{
+	struct files_db_handle *h;
+	int i;
+
+	h = malloc(sizeof(struct files_db_handle));
+	for (i = 0 ; i < FILE_DB_HASHSIZE ; i++)
+		h->files_db_buckets[i] = NULL;
+	return h;
+}
+
+void *files_db_lookup_byfileno(void *handle, int fileno)
+{
+	u_int32_t	hash;
+	struct files_db_handle *h = (struct files_db_handle *)handle;
+	struct files_db_s *db_node;
+
+	hash = fileno % FILE_DB_HASHSIZE;
+	db_node = h->files_db_buckets[hash];
+	while (db_node != NULL) {
+		if (db_node->fileno == fileno)
+			break;
+		db_node = db_node->next;
+	}
+	return db_node;
+}
+
+void *files_db_add_byfileno(void *handle, int fileno, int readonly)
+{
+	u_int32_t	hash = fileno % FILE_DB_HASHSIZE;
+	struct files_db_handle *h = (struct files_db_handle *)handle;
+	struct files_db_s *db_node;
+
+	db_node = (struct files_db_s *)
+		files_db_lookup_byfileno(handle, fileno);
+	if (db_node == NULL) {
+		db_node = malloc(sizeof(struct files_db_s));
+		db_node->fileno = fileno;
+		db_node->filename = NULL;
+		db_node->readonly = readonly;
+		db_node->size = 0;
+		db_node->fd = -1;
+		db_node->next = h->files_db_buckets[hash];
+		h->files_db_buckets[hash] = db_node;
+	} else {
+		fprintf(stderr,
+			"%s: Node to be added already exists fileno = %d\n\n",
+			__func__, fileno);
+		exit(EXIT_FAILURE);
+	}
+	return db_node;
+}
+
+void
+files_db_fsync_discard_files(void *handle)
+{
+	struct files_db_handle *h = (struct files_db_handle *)handle;
+	struct files_db_s *db_node;
+	int i;
+
+	for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+		db_node = h->files_db_buckets[i];
+		while (db_node != NULL) {
+			int do_close = 0;
+
+			if (db_node->fd == -1) {
+				int fd;
+				int openflags;
+
+				/*n
+				 * File was closed, let's open it so we can
+				 * fsync and fadvise(DONTNEED) it.
+				 */
+				do_close = 1;
+				if (files_db_readonly(db_node))
+					openflags = O_RDONLY;
+				else
+					openflags = O_RDWR;
+				fd = open(files_db_get_filename(db_node),
+					  openflags);
+				if (fd < 0) {
+					fprintf(stderr,
+						"%s: open(%s %x) error %d\n",
+						progname, db_node->filename,
+						openflags,
+						errno);
+					exit(EXIT_FAILURE);
+				}
+				db_node->fd = fd;
+			}
+			if (!db_node->readonly && fsync(db_node->fd) < 0) {
+				fprintf(stderr, "%s: Cannot fsync %s\n",
+					__func__, db_node->filename);
+				exit(1);
+			}
+			if (posix_fadvise(db_node->fd, 0, 0,
+					  POSIX_FADV_DONTNEED) < 0) {
+				fprintf(stderr,
+					"%s: Cannot fadvise(DONTNEED) %s\n",
+					__func__, db_node->filename);
+				exit(1);
+			}
+			if (do_close) {
+				close(db_node->fd);
+				db_node->fd = -1;
+			}
+			db_node = db_node->next;
+		}
+	}
+}
+
+void
+files_db_update_fd(void *node, int fd)
+{
+	struct files_db_s *db_node = (struct files_db_s *)node;
+
+	db_node->fd = fd;
+}
+
+void
+files_db_close_fd(void *node)
+{
+	struct files_db_s *db_node = (struct files_db_s *)node;
+
+	if (db_node->fd != -1)
+		close(db_node->fd);
+	db_node->fd = -1;
+}
+
+void
+files_db_close_files(void *handle)
+{
+	struct files_db_handle *h = (struct files_db_handle *)handle;
+	struct files_db_s *db_node;
+	int i;
+
+	for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+		db_node = h->files_db_buckets[i];
+		while (db_node != NULL) {
+			if ((db_node->fd != -1) && close(db_node->fd) < 0) {
+				fprintf(stderr, "%s: Cannot close %s\n",
+					__func__, db_node->filename);
+				exit(1);
+			}
+			db_node->fd = -1;
+			db_node = db_node->next;
+		}
+	}
+}
+
+void
+files_db_unlink_files(void *handle)
+{
+	struct files_db_handle *h = (struct files_db_handle *)handle;
+	struct files_db_s *db_node;
+	int i;
+
+	for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+		db_node = h->files_db_buckets[i];
+		while (db_node != NULL) {
+			if ((db_node->fd != -1) && close(db_node->fd) < 0) {
+				fprintf(stderr, "%s: Cannot close %s\n",
+					__func__, db_node->filename);
+				exit(1);
+			}
+			db_node->fd = -1;
+			if (is_readonly_mount(db_node->filename, db_node->size) == 0) {
+				if (unlink(db_node->filename) < 0) {
+					fprintf(stderr, "%s: Cannot unlink %s:%s\n",
+						__func__, db_node->filename,
+						strerror(errno));
+					exit(EXIT_FAILURE);
+				}
+			}
+			db_node = db_node->next;
+		}
+	}
+}
+
+void
+files_db_free_memory(void *handle)
+{
+	struct files_db_handle *h = (struct files_db_handle *)handle;
+	struct files_db_s *db_node, *tmp;
+	int i;
+
+	for (i = 0 ; i < FILE_DB_HASHSIZE ; i++) {
+		db_node = h->files_db_buckets[i];
+		while (db_node != NULL) {
+			tmp = db_node;
+			db_node = db_node->next;
+			free(tmp->filename);
+			free(tmp);
+		}
+	}
+	free(h);
+}
+
+char *
+get_buf(char **buf, int *buflen, int len, int do_fill __attribute__((unused)))
+{
+	if (len == 0 && *buf == NULL) {
+		/*
+		 * If we ever get a zero len
+		 * request, start with MINBUFLEN
+		 */
+		if (*buf == NULL)
+			len = MINBUFLEN / 2;
+	}
+	if (*buflen < len) {
+		*buflen = MAX(MINBUFLEN, len * 2);
+		if (*buf)
+			free(*buf);
+		*buf = malloc(*buflen);
+		if (do_fill) {
+			u_int32_t *s;
+			int count;
+
+			s = (u_int32_t *)*buf;
+			count = *buflen / sizeof(u_int32_t);
+			while (count > 0) {
+				*s++ = rand();
+				count--;
+			}
+		}
+	}
+	assert(*buf != NULL);
+	return *buf;
+}
+
+void
+create_file(char *path, size_t size, struct rw_bytes_s *rw_bytes)
+{
+	int fd, n;
+	char *buf = NULL;
+	int buflen = 0;
+
+	fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+	if (fd < 0) {
+		fprintf(stderr, "%s Cannot create file %s, error = %d\n",
+			progname, path, errno);
+		exit(EXIT_FAILURE);
+	}
+	while (size > 0) {
+		n = MIN(size, MINBUFLEN);
+		buf = get_buf(&buf, &buflen, n, 1);
+		if (write(fd, buf, n) < n) {
+			fprintf(stderr,
+				"%s Cannot write file %s, error = %d\n",
+				progname, path, errno);
+			exit(EXIT_FAILURE);
+		}
+		rw_bytes->bytes_written += n;
+		size -= n;
+	}
+	if (fsync(fd) < 0) {
+		fprintf(stderr, "%s Cannot fsync file %s, error = %d\n",
+			progname, path, errno);
+		exit(EXIT_FAILURE);
+	}
+	if (posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) < 0) {
+		fprintf(stderr,
+			"%s Cannot fadvise(DONTNEED) file %s, error = %d\n",
+			progname, path, errno);
+		exit(EXIT_FAILURE);
+	}
+	close(fd);
+}
+
+void
+print_op_stats(u_int64_t *op_counts)
+{
+	int i;
+	extern char *IO_op[];
+
+	printf("IO Operation counts :\n");
+	for (i = IOSHARK_LSEEK ; i < IOSHARK_MAX_FILE_OP ; i++) {
+		printf("%s: %ju\n",
+		       IO_op[i], op_counts[i]);
+	}
+}
+
+void
+print_bytes(char *desc, struct rw_bytes_s *rw_bytes)
+{
+	if (!summary_mode)
+		printf("%s: Reads = %dMB, Writes = %dMB\n",
+		       desc,
+		       (int)(rw_bytes->bytes_read / (1024 * 1024)),
+		       (int)(rw_bytes->bytes_written / (1024 * 1024)));
+	else
+		printf("%d %d ",
+		       (int)(rw_bytes->bytes_read / (1024 * 1024)),
+		       (int)(rw_bytes->bytes_written / (1024 * 1024)));
+}
+
+struct cpu_disk_util_stats {
+	/* CPU util */
+	u_int64_t user_cpu_ticks;
+	u_int64_t nice_cpu_ticks;
+	u_int64_t system_cpu_ticks;
+	u_int64_t idle_cpu_ticks;
+	u_int64_t iowait_cpu_ticks;
+	u_int64_t hardirq_cpu_ticks;
+	u_int64_t softirq_cpu_ticks;
+	/* disk util */
+	unsigned long long uptime;
+	unsigned int tot_ticks;
+	unsigned long rd_ios;
+	unsigned long wr_ios;
+	unsigned long rd_sec;
+	unsigned long wr_sec;
+};
+
+static struct cpu_disk_util_stats before;
+static struct cpu_disk_util_stats after;
+
+#define BUFSIZE		8192
+
+static int hz;
+
+static void
+get_HZ(void)
+{
+	if ((hz = sysconf(_SC_CLK_TCK)) == -1)
+		exit(1);
+}
+
+#if 0
+static int num_cores;
+
+static void
+get_cores(void)
+{
+	if ((num_cores = sysconf(_SC_NPROCESSORS_ONLN)) == -1)
+		exit(1);
+}
+#endif
+
+static void
+get_blockdev_name(char *bdev)
+{
+	char dev_name[BUFSIZE];
+	FILE *cmd;
+
+	cmd = popen("getprop ro.product.name", "r");
+	if (cmd == NULL) {
+		fprintf(stderr, "%s: Cannot popen getprop\n",
+			progname);
+		exit(1);
+	}
+	if (fgets(dev_name, BUFSIZE, cmd) == NULL) {
+		fprintf(stderr,
+			"%s: Bad output from getprop ro.product.name\n",
+			progname);
+		exit(1);
+	}
+	pclose(cmd);
+	/* strncmp needed because of the trailing '\n' */
+	if (strncmp(dev_name, "bullhead", strlen("bullhead")) == 0 ||
+	    strncmp(dev_name, "angler", strlen("angler")) == 0 ||
+	    strncmp(dev_name, "shamu", strlen("shamu")) == 0) {
+		strcpy(bdev, "mmcblk0");
+	} else if (strncmp(dev_name, "marlin", strlen("marlin")) == 0 ||
+		   strncmp(dev_name, "sailfish", strlen("sailfish")) == 0) {
+		strcpy(bdev, "sda");
+	} else {
+		fprintf(stderr,
+			"%s: Unknown device %s\n",
+			progname, dev_name);
+		exit(1);
+	}
+}
+
+static void
+read_disk_util_state(struct cpu_disk_util_stats *state)
+{
+	FILE *fp;
+        char line[BUFSIZE], dev_name[BUFSIZE];
+        unsigned int major, minor;
+	unsigned int ios_pgr;
+	unsigned int rq_ticks;
+	unsigned int wr_ticks;
+	unsigned long rd_ticks;
+	unsigned long rd_merges;
+	unsigned long wr_merges;
+	unsigned long up_sec, up_cent;
+	char blockdev_name[BUFSIZE];
+
+	/* Read and parse /proc/uptime */
+	fp = fopen("/proc/uptime", "r");
+	if (fgets(line, sizeof(line), fp) == NULL) {
+		fprintf(stderr, "%s: Cannot read /proc/uptime\n",
+			progname);
+		exit(1);
+	}
+	fclose(fp);
+	sscanf(line, "%lu.%lu", &up_sec, &up_cent);
+	state->uptime = (unsigned long long) up_sec * hz +
+		(unsigned long long) up_cent * hz / 100;
+
+	/* Read and parse /proc/diskstats */
+	get_blockdev_name(blockdev_name);
+	fp = fopen("/proc/diskstats", "r");
+	while (fgets(line, sizeof(line), fp)) {
+		sscanf(line,
+		       "%u %u %s %lu %lu %lu %lu %lu %lu %lu %u %u %u %u",
+		       &major, &minor, dev_name,
+		       &state->rd_ios, &rd_merges, &state->rd_sec,
+		       &rd_ticks, &state->wr_ios, &wr_merges,
+		       &state->wr_sec, &wr_ticks,
+		       &ios_pgr, &state->tot_ticks, &rq_ticks);
+                if (strcmp(dev_name, blockdev_name) == 0) {
+			/*
+			 * tot_ticks is "number of milliseconds spent
+			 * doing I/Os". Look at Documentation/iostats.txt.
+			 * Or at genhd.c:diskstats_show(), which calls
+			 * jiffies_to_msecs() on this field before printing
+			 * it. Convert this to hz, so we can do all our math
+			 * in ticks.
+			 */
+			state->tot_ticks /= 1000; /* to seconds */
+			state->tot_ticks *= hz;   /* to hz	*/
+			fclose(fp);
+			return;
+		}
+	}
+        fprintf(stderr, "%s: Did not find device sda in /proc/diskstats\n",
+		progname);
+	exit(1);
+}
+
+static void
+read_cpu_util_state(struct cpu_disk_util_stats *state)
+{
+	FILE *fp;
+	char line[BUFSIZE], cpu[BUFSIZE];
+
+	/* Read and parse /proc/stat */
+	fp = fopen("/proc/stat", "r");
+	if (fgets(line, sizeof(line), fp) == NULL) {
+		fprintf(stderr, "%s: Cannot read /proc/stat\n",
+			progname);
+		exit(1);
+	}
+	fclose(fp);
+	sscanf(line, "%s %ju %ju %ju %ju %ju %ju %ju",
+	       cpu,
+	       &state->user_cpu_ticks,
+	       &state->nice_cpu_ticks,
+	       &state->system_cpu_ticks,
+	       &state->idle_cpu_ticks,
+	       &state->iowait_cpu_ticks,
+	       &state->hardirq_cpu_ticks,
+	       &state->softirq_cpu_ticks);
+}
+
+void
+capture_util_state_before(void)
+{
+	get_HZ();
+	read_disk_util_state(&before);
+	read_cpu_util_state(&before);
+}
+
+void
+report_cpu_disk_util(void)
+{
+        double disk_util, cpu_util;
+	u_int64_t tot1, tot2, delta1, delta2;
+
+	read_disk_util_state(&after);
+	read_cpu_util_state(&after);
+	/* CPU Util */
+	tot2 = after.user_cpu_ticks + after.nice_cpu_ticks +
+		after.system_cpu_ticks + after.hardirq_cpu_ticks +
+		after.softirq_cpu_ticks;
+	tot1 = before.user_cpu_ticks + before.nice_cpu_ticks +
+		before.system_cpu_ticks + before.hardirq_cpu_ticks +
+		before.softirq_cpu_ticks;
+	delta1 = tot2 - tot1;
+	tot2 += after.iowait_cpu_ticks + after.idle_cpu_ticks;
+	tot1 += before.iowait_cpu_ticks + before.idle_cpu_ticks;
+	delta2 = tot2 - tot1;
+	cpu_util = delta1 * 100.0 / delta2;
+	if (!summary_mode)
+		printf("CPU util = %.2f%%\n", cpu_util);
+	else
+		printf("%.2f ", cpu_util);
+	/* Next compute system (incl irq/softirq) and user cpu util */
+	delta1 = (after.user_cpu_ticks + after.nice_cpu_ticks) -
+		(before.user_cpu_ticks + before.nice_cpu_ticks);
+	cpu_util = delta1 * 100.0 / delta2;
+	if (!summary_mode)
+		printf("User CPU util = %.2f%%\n", cpu_util);
+	else
+		printf("%.2f ", cpu_util);
+	delta1 = (after.system_cpu_ticks + after.hardirq_cpu_ticks +
+		  after.softirq_cpu_ticks) -
+		(before.system_cpu_ticks + before.hardirq_cpu_ticks +
+		 before.softirq_cpu_ticks);
+	cpu_util = delta1 * 100.0 / delta2;
+	if (!summary_mode)
+		printf("System CPU util = %.2f%%\n", cpu_util);
+	else
+		printf("%.2f ", cpu_util);
+	/* Disk Util */
+	disk_util = (after.tot_ticks - before.tot_ticks) * 100.0 /
+		(after.uptime - before.uptime);
+	if (verbose) {
+		printf("Reads : nr_ios %lu, MB read %lu\n",
+	       (after.rd_ios - before.rd_ios),
+	       (after.rd_sec - before.rd_sec) / 2048);
+		printf("Writes : nr_ios %lu, MB written %lu\n",
+	       (after.wr_ios - before.wr_ios),
+		       (after.wr_sec - before.wr_sec) / 2048);
+	}
+	if (!summary_mode)
+		printf("Disk util = %.2f%%\n", disk_util);
+	else
+		printf("%.2f", disk_util);
+}
+
+
+static struct ioshark_filename_struct *filename_cache;
+static int filename_cache_num_entries;
+
+char *
+get_ro_filename(int ix)
+{
+	if (ix >= filename_cache_num_entries)
+		return NULL;
+	return filename_cache[ix].path;
+}
+
+void
+init_filename_cache(void)
+{
+	int fd;
+	struct stat st;
+
+	fd = open("ioshark_filenames", O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr, "%s Can't open ioshark_filenames file\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	if (fstat(fd, &st) < 0) {
+		fprintf(stderr, "%s Can't fstat ioshark_filenames file\n",
+			progname);
+		exit(EXIT_FAILURE);
+	}
+	filename_cache_num_entries = st.st_size /
+		sizeof(struct ioshark_filename_struct);
+	filename_cache = mmap(NULL, st.st_size, PROT_READ,
+			      MAP_SHARED | MAP_LOCKED | MAP_POPULATE,
+			      fd, 0);
+	if (filename_cache == MAP_FAILED) {
+		fprintf(stderr, "%s Can't fstat ioshark_filenames file: %s\n",
+			progname, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	close(fd);
+}
+
+void
+free_filename_cache(void)
+{
+	size_t mmap_size;
+
+	mmap_size = filename_cache_num_entries *
+		sizeof(struct ioshark_filename_struct);
+	munmap(filename_cache, mmap_size);
+}
+
+/*
+ * Is the passed in filename a regular file ? (eg. not a directory).
+ * Second, is it in a read-only partition ?
+ */
+int
+is_readonly_mount(char *filename, size_t size)
+{
+	struct statfs statfsbuf;
+	struct stat statbuf;
+
+	if (stat(filename, &statbuf) < 0) {
+		/* File possibly deleted */
+		return 0;
+	}
+	if (!S_ISREG(statbuf.st_mode)) {
+		/* Is it a regular file ? */
+		return 0;
+	}
+	if ((size_t)statbuf.st_size < size) {
+		/* Size of existing file is smaller than we expect */
+		return 0;
+	}
+	if (statfs(filename, &statfsbuf) < 0) {
+		/* This shouldn't happen */
+		return 0;
+	}
+	if ((statfsbuf.f_flags & ST_RDONLY) == 0)
+		return 0;
+	else
+		return 1;
+}
diff --git a/ioshark/monkey-strace+fstrace.tgz b/ioshark/monkey-strace+fstrace.tgz
new file mode 100644
index 0000000..6f02301
--- /dev/null
+++ b/ioshark/monkey-strace+fstrace.tgz
Binary files differ
diff --git a/ioshark/monkeystracebig.tgz b/ioshark/monkeystracebig.tgz
new file mode 100644
index 0000000..e2ab7bc
--- /dev/null
+++ b/ioshark/monkeystracebig.tgz
Binary files differ
diff --git a/ioshark/monkeytracebig.tar.gz b/ioshark/monkeytracebig.tar.gz
new file mode 100644
index 0000000..2ff7047
--- /dev/null
+++ b/ioshark/monkeytracebig.tar.gz
Binary files differ
diff --git a/ioshark/wl.tar b/ioshark/wl.tar
new file mode 100644
index 0000000..a004ad1
--- /dev/null
+++ b/ioshark/wl.tar
Binary files differ
diff --git a/libpagemap/Android.bp b/libpagemap/Android.bp
index 976385c..e06caa6 100644
--- a/libpagemap/Android.bp
+++ b/libpagemap/Android.bp
@@ -15,6 +15,9 @@
 cc_library {
     name: "libpagemap",
     vendor_available: true,
+    vndk: {
+        enabled: true,
+    },
     srcs: [
         "pm_kernel.c",
         "pm_process.c",
diff --git a/librank/librank.c b/librank/librank.c
index a525f23..f017c9d 100644
--- a/librank/librank.c
+++ b/librank/librank.c
@@ -91,7 +91,7 @@
             return libraries[i];
     }
 
-    if (libraries_count >= libraries_size) {
+    if (libraries_size && libraries_count >= libraries_size) {
         libraries = realloc(libraries, 2 * libraries_size * sizeof(struct library_info *));
         if (!libraries) {
             fprintf(stderr, "Couldn't resize libraries array: %s\n", strerror(errno));
@@ -133,7 +133,7 @@
             return library->mappings[i];
     }
 
-    if (library->mappings_count >= library->mappings_size) {
+    if (library->mappings_size && library->mappings_count >= library->mappings_size) {
         library->mappings = realloc(library->mappings,
             2 * library->mappings_size * sizeof(struct mapping_info*));
         if (!library->mappings) {
@@ -419,7 +419,7 @@
         fflush(stdout);
     }
 
-    return 0;
+    exit(0);
 }
 
 static void usage(char *myname) {
diff --git a/memcpy-perf/memcpy-perf.cpp b/memcpy-perf/memcpy-perf.cpp
index 20d060b..2dfd900 100644
--- a/memcpy-perf/memcpy-perf.cpp
+++ b/memcpy-perf/memcpy-perf.cpp
@@ -7,14 +7,20 @@
 #include <memory>
 #include <cmath>
 #include <string>
+#include <thread>
+
+#define CACHE_HIT_SIZE 1 << 17
 
 using namespace std;
 
-const size_t size_start = 64;
-const size_t size_end = 16 * (1ull << 20);
-const size_t samples = 2048;
+size_t size_start = 64;
+size_t size_end = 16 * (1ull << 20);
+size_t samples = 2048;
 size_t size_per_test = 64 * (1ull << 20);
 size_t tot_sum = 0;
+size_t delay = 0;
+float speed = 0;
+bool dummy = false;
 
 void __attribute__((noinline)) memcpy_noinline(void *dst, void *src, size_t size);
 void __attribute__((noinline)) memset_noinline(void *dst, int value, size_t size);
@@ -26,21 +32,64 @@
     SumBench,
 };
 
+static void usage(char* p) {
+    printf("Usage: %s <test> <options>\n"
+           "<test> is one of the following:\n"
+           "  --memcpy\n"
+           "  --memset\n"
+           "  --sum\n"
+           "<options> are optional and apply to all tests:\n"
+           "  --dummy\n"
+           "    Simulates cpu-only load of a test. Guaranteed to use L2\n"
+           "    instead.  Not supported on --sum test.\n"
+           "  --delay DELAY_DIVISOR\n"
+           "  --start START_SIZE_MB\n"
+           "    --end END_SIZE_MB (requires start, optional)\n"
+           "  --samples NUM_SAMPLES\n"
+           , p);
+}
+
 int main(int argc, char *argv[])
 {
-    BenchType type;
+    BenchType type = MemcpyBench;
     if (argc <= 1) {
-        cerr << "memcpy_perf [--memcpy|--memset|--sum]" << endl;
+        usage(argv[0]);
         return 0;
     }
-    if (string(argv[1]) == string("--memcpy")) {
-        type = MemcpyBench;
-    } else if (string(argv[1]) == string("--memset")) {
-        type = MemsetBench;
-    } else if (string(argv[1]) == string("--sum")) {
-        type = SumBench;
-    } else {
-        type = MemcpyBench;
+    for (int i = 1; i < argc; i++) {
+      if (string(argv[i]) == string("--memcpy")) {
+         type = MemcpyBench;
+      } else if (string(argv[i]) == string("--memset")) {
+         type = MemsetBench;
+      } else if (string(argv[i]) == string("--sum")) {
+         type = SumBench;
+      } else if (string(argv[i]) == string("--dummy")) {
+         dummy = true;
+      } else if (i + 1 < argc) {
+          if (string(argv[i]) == string("--delay")) {
+             delay = atoi(argv[++i]);
+          } else if (string(argv[i]) == string("--start")) {
+             size_start = atoi(argv[++i]) * (1ull << 20);
+             size_end = size_start;
+          } else if (string(argv[i]) == string("--end")) {
+             size_t end = atoi(argv[++i]) * (1ull << 20);
+             if (end > size_start && i > 3
+                 && string(argv[i-3]) == string("--start")) {
+                 size_end = end;
+             } else {
+                 printf("Cannot specify --end without --start.\n");
+                 return 0;
+             }
+          } else if (string(argv[i]) == string("--samples")) {
+             samples = atoi(argv[++i]);
+          } else {
+             printf("Unknown argument %s\n", argv[i]);
+             return 0;
+          }
+       } else {
+          printf("The %s option requires a single argument.\n", argv[i]);
+          return 0;
+       }
     }
 
     unique_ptr<uint8_t[]> src(new uint8_t[size_end]);
@@ -54,8 +103,10 @@
     //cout << "src: " << (uintptr_t)src.get() << endl;
     //cout << "dst: " <<  (uintptr_t)dst.get() << endl;
 
-    for (double cur_pow = start_pow; cur_pow <= end_pow; cur_pow += pow_inc) {
-        chrono::time_point<chrono::high_resolution_clock> copy_start, copy_end;
+    for (double cur_pow = start_pow; cur_pow <= end_pow && samples > 0;
+            cur_pow += pow_inc) {
+        chrono::time_point<chrono::high_resolution_clock>
+            copy_start, copy_end, pre_wait;
 
         size_t cur_size = (size_t)pow(10.0, cur_pow);
         size_t iter_per_size = size_per_test / cur_size;
@@ -65,9 +116,21 @@
             case MemsetBench: {
                 memcpy_noinline(src.get(), dst.get(), cur_size);
                 memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+                size_t hit_size = CACHE_HIT_SIZE;
                 copy_start = chrono::high_resolution_clock::now();
                 for (int i = 0; i < iter_per_size; i++) {
-                    memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+                    if (!dummy) {
+                        memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+                    } else {
+                        while (hit_size < cur_size) {
+                            memset_noinline
+                                (dst.get(), 0xdeadbeef, CACHE_HIT_SIZE);
+                            hit_size += 1 << 17;
+                        }
+                    }
+                    if (delay != 0)
+                        this_thread::sleep_for(chrono
+                            ::nanoseconds(size_per_test / delay));
                 }
                 copy_end = chrono::high_resolution_clock::now();
                 break;
@@ -75,9 +138,21 @@
             case MemcpyBench: {
                 memcpy_noinline(dst.get(), src.get(), cur_size);
                 memcpy_noinline(src.get(), dst.get(), cur_size);
+                size_t hit_size = CACHE_HIT_SIZE;
                 copy_start = chrono::high_resolution_clock::now();
                 for (int i = 0; i < iter_per_size; i++) {
-                    memcpy_noinline(dst.get(), src.get(), cur_size);
+                    if (!dummy) {
+                        memcpy_noinline(dst.get(), src.get(), cur_size);
+                    } else {
+                        while (hit_size < cur_size) {
+                            memcpy_noinline
+                                (dst.get(), src.get(), CACHE_HIT_SIZE);
+                            hit_size += CACHE_HIT_SIZE;
+                        }
+                    }
+                    if (delay != 0)
+                        this_thread::sleep_for(chrono
+                            ::nanoseconds(size_per_test / delay));
                 }
                 copy_end = chrono::high_resolution_clock::now();
                 break;
@@ -88,6 +163,9 @@
                 copy_start = chrono::high_resolution_clock::now();
                 for (int i = 0; i < iter_per_size; i++) {
                     s += sum(src.get(), cur_size);
+                    if (delay != 0)
+                        this_thread::sleep_for(chrono
+                            ::nanoseconds(size_per_test / delay));
                 }
                 copy_end = chrono::high_resolution_clock::now();
                 tot_sum += s;
@@ -95,11 +173,18 @@
             }
         }
 
+        samples--;
         double ns_per_copy = chrono::duration_cast<chrono::nanoseconds>(copy_end - copy_start).count() / double(iter_per_size);
         double gb_per_sec = ((double)cur_size / (1ull<<30)) / (ns_per_copy / 1.0E9);
         if (type == MemcpyBench)
             gb_per_sec *= 2.0;
-        cout << "size: " << cur_size << ", perf: " << gb_per_sec << "GB/s, iter: " << iter_per_size << endl;
+        double percent_waiting = 0;
+        if (delay != 0) {
+            percent_waiting = (size_per_test / delay) / ns_per_copy * 100;
+        }
+        cout << "size: " << cur_size << ", perf: " << gb_per_sec
+             << "GB/s, iter: " << iter_per_size << ", \% time spent waiting: "
+             << percent_waiting << endl;
     }
     return 0;
 }
diff --git a/perfprofd/Android.bp b/perfprofd/Android.bp
index f05f324..2c07ca1 100644
--- a/perfprofd/Android.bp
+++ b/perfprofd/Android.bp
@@ -63,7 +63,7 @@
         "libcutils"
     ],
     system_shared_libs: [
-        "libc",
+        "libc", "libdl",
     ],
     cppflags: perfprofd_cppflags,
 
diff --git a/preopt2cachename/preopt2cachename.cpp b/preopt2cachename/preopt2cachename.cpp
index dfdc63f..f9a12ff 100644
--- a/preopt2cachename/preopt2cachename.cpp
+++ b/preopt2cachename/preopt2cachename.cpp
@@ -24,37 +24,38 @@
 #endif
 
 static const char* kDalvikCacheDir = "/data/dalvik-cache/";
-static const char* kCacheSuffix = "@classes.dex";
+static const char* kOdexCacheSuffix = "@classes.dex";
+static const char* kVdexCacheSuffix = "@classes.vdex";
 
-// Returns the ISA extracted from the odex_file_location.
-// odex_file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.odex for all
-// functions. We return an empty string "" in error cases.
-static std::string ExtractISA(const std::string& odex_file_location) {
-  std::vector<std::string> split_file_location = android::base::Split(odex_file_location, "/");
+// Returns the ISA extracted from the file_location.
+// file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.{odex,vdex}
+// for all functions. We return an empty string "" in error cases.
+static std::string ExtractISA(const std::string& file_location) {
+  std::vector<std::string> split_file_location = android::base::Split(file_location, "/");
   if (split_file_location.size() <= 1) {
     return "";
   } else if (split_file_location.size() != 7) {
-    LOG(WARNING) << "Unexpected length for odex-file-location. We expected 7 segments but found "
+    LOG(WARNING) << "Unexpected length for file-location. We expected 7 segments but found "
                  << split_file_location.size();
   }
   return split_file_location[split_file_location.size() - 2];
 }
 
-// Returns the apk name extracted from the odex_file_location.
-// odex_file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.odex. We return
-// the final <app_name> with the .odex replaced with .apk.
-static std::string ExtractAPKName(const std::string& odex_file_location) {
+// Returns the apk name extracted from the file_location.
+// file_location is formatted like /system/app/<app_name>/oat/<isa>/<app_name>.{odex,vdex}.
+// We return the final <app_name> with the .{odex,vdex} replaced with .apk.
+static std::string ExtractAPKName(const std::string& file_location) {
   // Find and copy filename.
-  size_t file_location_start = odex_file_location.rfind('/');
+  size_t file_location_start = file_location.rfind('/');
   if (file_location_start == std::string::npos) {
     return "";
   }
-  size_t ext_start = odex_file_location.rfind('.');
+  size_t ext_start = file_location.rfind('.');
   if (ext_start == std::string::npos || ext_start < file_location_start) {
     return "";
   }
-  std::string apk_name = odex_file_location.substr(file_location_start + 1,
-                                                   ext_start - file_location_start);
+  std::string apk_name = file_location.substr(file_location_start + 1,
+                                              ext_start - file_location_start);
 
   // Replace extension with .apk.
   apk_name += "apk";
@@ -62,18 +63,18 @@
 }
 
 // The cache file name is /data/dalvik-cache/<isa>/ prior to this function
-static bool OdexFilenameToCacheFile(const std::string& odex_file_location,
-                                    /*in-out*/std::string& cache_file) {
-  // Skip the first '/' in odex_file_location.
-  size_t initial_position = odex_file_location[0] == '/' ? 1 : 0;
-  size_t apk_position = odex_file_location.find("/oat", initial_position);
+static bool SystemBFilenameToCacheFile(const std::string& file_location,
+                                       /*in-out*/std::string& cache_file) {
+  // Skip the first '/' in file_location.
+  size_t initial_position = file_location[0] == '/' ? 1 : 0;
+  size_t apk_position = file_location.find("/oat", initial_position);
   if (apk_position == std::string::npos) {
     LOG(ERROR) << "Unable to find oat directory!";
     return false;
   }
 
   size_t cache_file_position = cache_file.size();
-  cache_file += odex_file_location.substr(initial_position, apk_position);
+  cache_file += file_location.substr(initial_position, apk_position);
   // '/' -> '@' up to where the apk would be.
   cache_file_position = cache_file.find('/', cache_file_position);
   while (cache_file_position != std::string::npos) {
@@ -82,28 +83,33 @@
   }
 
   // Add <apk_name>.
-  std::string apk_name = ExtractAPKName(odex_file_location);
+  std::string apk_name = ExtractAPKName(file_location);
   if (apk_name.empty()) {
-    LOG(ERROR) << "Unable to determine apk name from odex file name '" << odex_file_location << "'";
+    LOG(ERROR) << "Unable to determine apk name from file name '" << file_location << "'";
     return false;
   }
   cache_file += apk_name;
-  cache_file += kCacheSuffix;
+  if (file_location.size() >= 5 &&
+      file_location.substr(file_location.size() - 5) == std::string(".vdex")) {
+    cache_file += kVdexCacheSuffix;
+  } else {
+    cache_file += kOdexCacheSuffix;
+  }
   return true;
 }
 
-// Do the overall transformation from odex_file_location to output_file_location. Prior to this the
+// Do the overall transformation from file_location to output_file_location. Prior to this the
 // output_file_location is empty.
-static bool OdexToCacheFile(std::string& odex_file_location,
-                            /*out*/std::string& output_file_location) {
-  std::string isa = ExtractISA(odex_file_location);
+static bool SystemBFileToCacheFile(const std::string& file_location,
+                                   /*out*/std::string& output_file_location) {
+  std::string isa = ExtractISA(file_location);
   if (isa.empty()) {
-    LOG(ERROR) << "Unable to determine isa for odex file '" << odex_file_location << "', skipping";
+    LOG(ERROR) << "Unable to determine isa for file '" << file_location << "', skipping";
     return false;
   }
   output_file_location += isa;
   output_file_location += '/';
-  return OdexFilenameToCacheFile(odex_file_location, output_file_location);
+  return SystemBFilenameToCacheFile(file_location, output_file_location);
 }
 
 // This program is used to determine where in the /data directory the runtime will search for an
@@ -115,9 +121,9 @@
     LOG(ERROR) << "usage: preopt2cachename preopt-location";
     return 2;
   }
-  std::string odex_file_location(argv[1]);
+  std::string file_location(argv[1]);
   std::string output_file_location(kDalvikCacheDir);
-  if (!OdexToCacheFile(odex_file_location, output_file_location)) {
+  if (!SystemBFileToCacheFile(file_location, output_file_location)) {
     return 1;
   } else {
     std::cout << output_file_location;
diff --git a/procrank/procrank.cpp b/procrank/procrank.cpp
index 36a2043..b8ebd31 100644
--- a/procrank/procrank.cpp
+++ b/procrank/procrank.cpp
@@ -18,7 +18,6 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
-#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
diff --git a/puncture_fs/puncture_fs.c b/puncture_fs/puncture_fs.c
index e9d08dc..dbb4efc 100644
--- a/puncture_fs/puncture_fs.c
+++ b/puncture_fs/puncture_fs.c
@@ -163,6 +163,10 @@
                 (int) (100.0 * starting_max / total_size));
         hole_max = get_random_num(starting_max, ending_max);
 
+	do {
+		hole_max = get_random_num(starting_max, ending_max);
+	} while (hole_max == starting_max);
+
         create_unique_file(stay_dir,
                            hole_max - starting_max,
                            file_id++,
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index b36c28c..665d348 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -492,4 +492,36 @@
 LOCAL_MULTILIB := both
 include $(BUILD_HOST_NATIVE_TEST)
 
+
+# simpleperf_script.zip (for release in ndk)
+# ============================================================
+SIMPLEPERF_SCRIPT_LIST := \
+    $(filter-out scripts/update.py,$(call all-named-files-under,*.py,scripts)) \
+    scripts/inferno.sh \
+    scripts/inferno.bat \
+    scripts/inferno/inferno.b64 \
+    scripts/inferno/jqueryui/LICENSE.txt \
+    $(call all-named-files-under,*.js,scripts) \
+    $(call all-named-files-under,*.css,scripts) \
+    $(call all-named-files-under,*,doc) \
+    $(call all-named-files-under,app-profiling.apk,demo) \
+    $(call all-named-files-under,*.so,demo) \
+    $(call all-cpp-files-under,demo) \
+    $(call all-java-files-under,demo) \
+    $(call all-named-files-under,*.kt,demo) \
+    testdata/perf_with_symbols.data \
+    testdata/perf_with_trace_offcpu.data
+
+SIMPLEPERF_SCRIPT_LIST := $(addprefix -f $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST))
+
+SIMPLEPERF_SCRIPT_PATH := \
+    $(call intermediates-dir-for,PACKAGING,simplerperf_script,HOST)/simpleperf_script.zip
+
+$(SIMPLEPERF_SCRIPT_PATH) : $(SOONG_ZIP)
+	$(hide) $(SOONG_ZIP) -d -o $@ -C system/extras/simpleperf $(SIMPLEPERF_SCRIPT_LIST)
+
+sdk: $(SIMPLEPERF_SCRIPT_PATH)
+
+$(call dist-for-goals,sdk,$(SIMPLEPERF_SCRIPT_PATH))
+
 include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index dcd7bf4..919b62a 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -92,7 +92,10 @@
   return true;
 }
 
-static const std::string GetFeatureName(int feature);
+static const std::string GetFeatureNameOrUnknown(int feature) {
+  std::string name = GetFeatureName(feature);
+  return name.empty() ? android::base::StringPrintf("unknown_feature(%d)", feature) : name;
+}
 
 void DumpRecordCommand::DumpFileHeader() {
   const FileHeader& header = record_file_reader_->FileHeader();
@@ -127,38 +130,10 @@
     }
   }
   for (auto& feature : features) {
-    printf("feature: %s\n", GetFeatureName(feature).c_str());
+    printf("feature: %s\n", GetFeatureNameOrUnknown(feature).c_str());
   }
 }
 
-static const std::string GetFeatureName(int feature) {
-  static std::map<int, std::string> feature_name_map = {
-      {FEAT_TRACING_DATA, "tracing_data"},
-      {FEAT_BUILD_ID, "build_id"},
-      {FEAT_HOSTNAME, "hostname"},
-      {FEAT_OSRELEASE, "osrelease"},
-      {FEAT_VERSION, "version"},
-      {FEAT_ARCH, "arch"},
-      {FEAT_NRCPUS, "nrcpus"},
-      {FEAT_CPUDESC, "cpudesc"},
-      {FEAT_CPUID, "cpuid"},
-      {FEAT_TOTAL_MEM, "total_mem"},
-      {FEAT_CMDLINE, "cmdline"},
-      {FEAT_EVENT_DESC, "event_desc"},
-      {FEAT_CPU_TOPOLOGY, "cpu_topology"},
-      {FEAT_NUMA_TOPOLOGY, "numa_topology"},
-      {FEAT_BRANCH_STACK, "branch_stack"},
-      {FEAT_PMU_MAPPINGS, "pmu_mappings"},
-      {FEAT_GROUP_DESC, "group_desc"},
-      {FEAT_FILE, "file"},
-      {FEAT_META_INFO, "meta_info"},
-  };
-  auto it = feature_name_map.find(feature);
-  if (it != feature_name_map.end()) {
-    return it->second;
-  }
-  return android::base::StringPrintf("unknown_feature(%d)", feature);
-}
 
 void DumpRecordCommand::DumpAttrSection() {
   std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
@@ -189,7 +164,7 @@
     int feature = pair.first;
     const auto& section = pair.second;
     printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n",
-           GetFeatureName(feature).c_str(), section.offset, section.size);
+           GetFeatureNameOrUnknown(feature).c_str(), section.offset, section.size);
     if (feature == FEAT_BUILD_ID) {
       std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature();
       for (auto& r : records) {
diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp
index 7199814..3b22bde 100644
--- a/simpleperf/cmd_list.cpp
+++ b/simpleperf/cmd_list.cpp
@@ -165,6 +165,9 @@
   if (IsDumpingRegsForTracepointEventsSupported()) {
     printf("trace-offcpu\n");
   }
+  if (IsSettingClockIdSupported()) {
+    printf("set-clockid\n");
+  }
 }
 
 void RegisterListCommand() {
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index 487e219..d83b210 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -31,6 +31,9 @@
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <android-base/test_utils.h>
+#if defined(__ANDROID__)
+#include <android-base/properties.h>
+#endif
 
 #include "command.h"
 #include "dwarf_unwind.h"
@@ -59,6 +62,13 @@
     {"ind_call", PERF_SAMPLE_BRANCH_IND_CALL},
 };
 
+static std::unordered_map<std::string, int> clockid_map = {
+    {"realtime", CLOCK_REALTIME},
+    {"monotonic", CLOCK_MONOTONIC},
+    {"monotonic_raw", CLOCK_MONOTONIC_RAW},
+    {"boottime", CLOCK_BOOTTIME},
+};
+
 // The max size of records dumped by kernel is 65535, and dump stack size
 // should be a multiply of 8, so MAX_DUMP_STACK_SIZE is 65528.
 constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
@@ -108,7 +118,7 @@
 "-f freq      Set event sample frequency. It means recording at most [freq]\n"
 "             samples every second. For non-tracepoint events, the default\n"
 "             option is -f 4000. A -f/-c option affects all event types\n"
-"             following it until meeting another -f/-c option. For example,"
+"             following it until meeting another -f/-c option. For example,\n"
 "             for \"-f 1000 cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
 "             has sample freq 1000, sched:sched_switch event has sample period 1.\n"
 "-c count     Set event sample period. It means recording one sample when\n"
@@ -119,6 +129,9 @@
 "             frame as the method to parse call graph in stack.\n"
 "             Default is dwarf,65528.\n"
 "-g           Same as '--call-graph dwarf'.\n"
+"--clockid clock_id      Generate timestamps of samples using selected clock.\n"
+"                        Possible values are: realtime, monotonic,\n"
+"                        monotonic_raw, boottime, perf. Default is perf.\n"
 "--cpu cpu_item1,cpu_item2,...\n"
 "             Collect samples only on the selected cpus. cpu_item can be cpu\n"
 "             number like 1, or cpu range like 0-3.\n"
@@ -155,6 +168,8 @@
 "               will be unwound while recording by default. But it may lose\n"
 "               records as stacking unwinding can be time consuming. Use this\n"
 "               option to unwind the user's stack after recording.\n"
+"--exit-with-parent            Stop recording when the process starting\n"
+"                              simpleperf dies.\n"
 "--start_profiling_fd fd_no    After starting profiling, write \"STARTED\" to\n"
 "                              <fd_no>, then close <fd_no>.\n"
 "--symfs <dir>    Look for files with symbols relative to this directory.\n"
@@ -178,6 +193,7 @@
         duration_in_sec_(0),
         can_dump_kernel_symbols_(true),
         dump_symbols_(true),
+        clockid_("perf"),
         event_selection_set_(false),
         mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)),
         record_filename_("perf.data"),
@@ -188,8 +204,6 @@
         in_app_context_(false),
         trace_offcpu_(false),
         exclude_kernel_callchain_(false) {
-    // Stop profiling if parent exits.
-    prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
     // If we run `adb shell simpleperf record xxx` and stop profiling by ctrl-c, adb closes
     // sockets connecting simpleperf. After that, simpleperf will receive SIGPIPE when writing
     // to stdout/stderr, which is a problem when we use '--app' option. So ignore SIGPIPE to
@@ -234,6 +248,7 @@
   double duration_in_sec_;
   bool can_dump_kernel_symbols_;
   bool dump_symbols_;
+  std::string clockid_;
   std::vector<int> cpus_;
   EventSelectionSet event_selection_set_;
 
@@ -322,8 +337,8 @@
     } else if (!app_package_name_.empty()) {
       // If app process is not created, wait for it. This allows simpleperf starts before
       // app process. In this way, we can have a better support of app start-up time profiling.
-      int pid = WaitForAppProcess(app_package_name_);
-      event_selection_set_.AddMonitoredProcesses({pid});
+      std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
+      event_selection_set_.AddMonitoredProcesses(pids);
     } else {
       LOG(ERROR)
           << "No threads to monitor. Try `simpleperf help record` for help";
@@ -495,6 +510,21 @@
                    << args[i];
         return false;
       }
+    } else if (args[i] == "--clockid") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (args[i] != "perf") {
+        if (!IsSettingClockIdSupported()) {
+          LOG(ERROR) << "Setting clockid is not supported by the kernel.";
+          return false;
+        }
+        if (clockid_map.find(args[i]) == clockid_map.end()) {
+          LOG(ERROR) << "Invalid clockid: " << args[i];
+          return false;
+        }
+      }
+      clockid_ = args[i];
     } else if (args[i] == "--cpu") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -525,6 +555,8 @@
           wait_setting_speed_event_groups_.push_back(group_id);
         }
       }
+    } else if (args[i] == "--exit-with-parent") {
+      prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
     } else if (args[i] == "-g") {
       fp_callchain_sampling_ = false;
       dwarf_callchain_sampling_ = true;
@@ -714,6 +746,9 @@
     }
   }
   event_selection_set_.SetInherit(child_inherit_);
+  if (clockid_ != "perf") {
+    event_selection_set_.SetClockId(clockid_map[clockid_]);
+  }
   return true;
 }
 
@@ -1180,6 +1215,13 @@
   // By storing event types information in perf.data, the readers of perf.data have the same
   // understanding of event types, even if they are on another machine.
   info_map["event_type_info"] = ScopedEventTypes::BuildString(event_selection_set_.GetEvents());
+#if defined(__ANDROID__)
+  info_map["product_props"] = android::base::StringPrintf("%s:%s:%s",
+                                  android::base::GetProperty("ro.product.manufacturer", "").c_str(),
+                                  android::base::GetProperty("ro.product.model", "").c_str(),
+                                  android::base::GetProperty("ro.product.name", "").c_str());
+#endif
+  info_map["clockid"] = clockid_;
   return record_file_writer_->WriteMetaInfoFeature(info_map);
 }
 
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 4873230..b0c1862 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -16,6 +16,8 @@
 
 #include <gtest/gtest.h>
 
+#include <stdio.h>
+#include <stdlib.h>
 #include <unistd.h>
 
 #include <android-base/file.h>
@@ -191,7 +193,31 @@
   TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "--call-graph", "fp"})));
 }
 
+bool IsInNativeAbi() {
+  static int in_native_abi = -1;
+  if (in_native_abi == -1) {
+    FILE* fp = popen("uname -m", "re");
+    char buf[40];
+    memset(buf, '\0', sizeof(buf));
+    fgets(buf, sizeof(buf), fp);
+    pclose(fp);
+    std::string s = buf;
+    in_native_abi = 1;
+    if (GetBuildArch() == ARCH_X86_32 || GetBuildArch() == ARCH_X86_64) {
+      if (s.find("86") == std::string::npos) {
+        in_native_abi = 0;
+      }
+    } else if (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64) {
+      if (s.find("arm") == std::string::npos && s.find("aarch64") == std::string::npos) {
+        in_native_abi = 0;
+      }
+    }
+  }
+  return in_native_abi == 1;
+}
+
 TEST(record_cmd, dwarf_callchain_sampling) {
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(1, &workloads);
@@ -203,17 +229,20 @@
 }
 
 TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
   TEST_IN_ROOT(RunRecordCmd({"-a", "--call-graph", "dwarf"}));
 }
 
 TEST(record_cmd, no_unwind_option) {
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
   ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--no-unwind"}));
   ASSERT_FALSE(RunRecordCmd({"--no-unwind"}));
 }
 
 TEST(record_cmd, post_unwind_option) {
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(1, &workloads);
@@ -332,6 +361,7 @@
   ASSERT_TRUE(RunRecordCmd({"--no-dump-symbols"}, tmpfile.path));
   CheckDsoSymbolRecords(tmpfile.path, false, &success);
   ASSERT_TRUE(success);
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(1, &workloads);
@@ -454,6 +484,9 @@
   std::unordered_map<std::string, std::string> info_map;
   ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
   ASSERT_NE(info_map.find("simpleperf_version"), info_map.end());
+#if defined(__ANDROID__)
+  ASSERT_NE(info_map.find("product_props"), info_map.end());
+#endif
 }
 
 // See http://b/63135835.
@@ -468,6 +501,7 @@
 
 TEST(record_cmd, dump_regs_for_tracepoint_events) {
   TEST_REQUIRE_HOST_ROOT();
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   // Check if the kernel can dump registers for tracepoint events.
   // If not, probably a kernel patch below is missing:
   // "5b09a094f2 arm64: perf: Fix callchain parse error with kernel tracepoint events"
@@ -477,6 +511,7 @@
 TEST(record_cmd, trace_offcpu_option) {
   // On linux host, we need root privilege to read tracepoint events.
   TEST_REQUIRE_HOST_ROOT();
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   TemporaryFile tmpfile;
   ASSERT_TRUE(RunRecordCmd({"--trace-offcpu", "-f", "1000"}, tmpfile.path));
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
@@ -486,3 +521,21 @@
   ASSERT_EQ(info_map["trace_offcpu"], "true");
   CheckEventType(tmpfile.path, "sched:sched_switch", 1u, 0u);
 }
+
+TEST(record_cmd, exit_with_parent_option) {
+  ASSERT_TRUE(RunRecordCmd({"--exit-with-parent"}));
+}
+
+TEST(record_cmd, clockid_option) {
+  if (!IsSettingClockIdSupported()) {
+    ASSERT_FALSE(RunRecordCmd({"--clockid", "monotonic"}));
+  } else {
+    TemporaryFile tmpfile;
+    ASSERT_TRUE(RunRecordCmd({"--clockid", "monotonic"}, tmpfile.path));
+    std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+    ASSERT_TRUE(reader);
+    std::unordered_map<std::string, std::string> info_map;
+    ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
+    ASSERT_EQ(info_map["clockid"], "monotonic");
+  }
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index f274114..f6ab6f8 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -478,7 +478,7 @@
   bool found = false;
   for (auto& line : lines) {
     if (line.find("SleepFunction") != std::string::npos) {
-      ASSERT_NE(line.find("46.29%"), std::string::npos);
+      ASSERT_NE(line.find("38.77%"), std::string::npos);
       found = true;
       break;
     }
@@ -494,6 +494,7 @@
 }
 
 TEST_F(ReportCommandTest, dwarf_callgraph) {
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(1, &workloads);
@@ -523,6 +524,7 @@
 
 TEST_F(ReportCommandTest, exclude_kernel_callchain) {
   TEST_REQUIRE_HOST_ROOT();
+  OMIT_TEST_ON_NON_NATIVE_ABIS();
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(1, &workloads);
   std::string pid = std::to_string(workloads[0]->GetPid());
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index a9d5036..ddd83f7 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -386,8 +386,8 @@
       event_selection_set_.AddMonitoredProcesses({workload->GetPid()});
       event_selection_set_.SetEnableOnExec(true);
     } else if (!app_package_name_.empty()) {
-      int pid = WaitForAppProcess(app_package_name_);
-      event_selection_set_.AddMonitoredProcesses({pid});
+      std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
+      event_selection_set_.AddMonitoredProcesses(pids);
     } else {
       LOG(ERROR)
           << "No threads to monitor. Try `simpleperf help stat` for help\n";
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 3cdb4eb..e8c722d 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -24,6 +24,7 @@
 
 #include "command.h"
 #include "environment.h"
+#include "event_selection_set.h"
 #include "get_test_data.h"
 #include "test_util.h"
 
@@ -175,3 +176,17 @@
   while (tid == 0);
   ASSERT_TRUE(StatCmd()->Run({"-t", std::to_string(tid), "--in-app"}));
 }
+
+TEST(stat_cmd, sample_speed_should_be_zero) {
+  EventSelectionSet set(true);
+  ASSERT_TRUE(set.AddEventType("cpu-cycles"));
+  set.AddMonitoredProcesses({getpid()});
+  ASSERT_TRUE(set.OpenEventFiles({-1}));
+  std::vector<EventAttrWithId> attrs = set.GetEventAttrWithId();
+  ASSERT_GT(attrs.size(), 0u);
+  for (auto& attr : attrs) {
+    ASSERT_EQ(attr.attr->sample_period, 0u);
+    ASSERT_EQ(attr.attr->sample_freq, 0u);
+    ASSERT_EQ(attr.attr->freq, 0u);
+  }
+}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index 034163f..0d071ab 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -98,8 +98,15 @@
 
 CommandRegister command_register;
 
+static void StderrLogger(android::base::LogId, android::base::LogSeverity severity,
+                         const char*, const char* file, unsigned int line, const char* message) {
+  static const char log_characters[] = "VDIWEFF";
+  char severity_char = log_characters[severity];
+  fprintf(stderr, "simpleperf %c %s:%u] %s\n", severity_char, file, line, message);
+}
+
 bool RunSimpleperfCmd(int argc, char** argv) {
-  android::base::InitLogging(argv, android::base::StderrLogger);
+  android::base::InitLogging(argv, StderrLogger);
   std::vector<std::string> args;
   android::base::LogSeverity log_severity = android::base::INFO;
 
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index 16e0e5c..8227761 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -19,7 +19,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #if defined(__BIONIC__)
-#include <sys/system_properties.h>
+#include <android-base/properties.h>
 #endif
 
 #include <atomic>
@@ -59,25 +59,22 @@
 
  private:
   bool IsMpdecisionRunning() {
-    char value[PROP_VALUE_MAX];
-    int len = __system_property_get("init.svc.mpdecision", value);
-    if (len == 0 || (len > 0 && strstr(value, "stopped") != nullptr)) {
+    std::string value = android::base::GetProperty("init.svc.mpdecision", "");
+    if (value.empty() || value.find("stopped") != std::string::npos) {
       return false;
     }
     return true;
   }
 
   void DisableMpdecision() {
-    int ret = __system_property_set("ctl.stop", "mpdecision");
-    CHECK_EQ(0, ret);
+    CHECK(android::base::SetProperty("ctl.stop", "mpdecision"));
     // Need to wait until mpdecision is actually stopped.
     std::this_thread::sleep_for(std::chrono::milliseconds(500));
     CHECK(!IsMpdecisionRunning());
   }
 
   void EnableMpdecision() {
-    int ret = __system_property_set("ctl.start", "mpdecision");
-    CHECK_EQ(0, ret);
+    CHECK(android::base::SetProperty("ctl.start", "mpdecision"));
     std::this_thread::sleep_for(std::chrono::milliseconds(500));
     CHECK(IsMpdecisionRunning());
   }
@@ -199,13 +196,24 @@
 struct CpuToggleThreadArg {
   int toggle_cpu;
   std::atomic<bool> end_flag;
+  std::atomic<bool> cpu_hotplug_failed;
+
+  CpuToggleThreadArg(int cpu)
+      : toggle_cpu(cpu), end_flag(false), cpu_hotplug_failed(false) {
+  }
 };
 
 static void CpuToggleThread(CpuToggleThreadArg* arg) {
   while (!arg->end_flag) {
-    CHECK(SetCpuOnline(arg->toggle_cpu, true));
+    if (!SetCpuOnline(arg->toggle_cpu, true)) {
+      arg->cpu_hotplug_failed = true;
+      break;
+    }
     std::this_thread::sleep_for(cpu_hotplug_interval);
-    CHECK(SetCpuOnline(arg->toggle_cpu, false));
+    if (!SetCpuOnline(arg->toggle_cpu, false)) {
+      arg->cpu_hotplug_failed = true;
+      break;
+    }
     std::this_thread::sleep_for(cpu_hotplug_interval);
   }
 }
@@ -223,9 +231,7 @@
   if (!FindAHotpluggableCpu(&test_cpu)) {
     return;
   }
-  CpuToggleThreadArg cpu_toggle_arg;
-  cpu_toggle_arg.toggle_cpu = test_cpu;
-  cpu_toggle_arg.end_flag = false;
+  CpuToggleThreadArg cpu_toggle_arg(test_cpu);
   std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
 
   std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
@@ -240,7 +246,7 @@
   auto report_step = std::chrono::seconds(15);
   size_t iterations = 0;
 
-  while (cur_time < end_time) {
+  while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) {
     if (cur_time + report_step < std::chrono::steady_clock::now()) {
       // Report test time.
       auto diff = std::chrono::duration_cast<std::chrono::seconds>(
@@ -261,6 +267,9 @@
       GTEST_LOG_(INFO) << "Test offline while recording for " << iterations << " times.";
     }
   }
+  if (cpu_toggle_arg.cpu_hotplug_failed) {
+    GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure.";
+  }
   cpu_toggle_arg.end_flag = true;
   cpu_toggle_thread.join();
 }
@@ -278,9 +287,7 @@
   if (!FindAHotpluggableCpu(&test_cpu)) {
     return;
   }
-  CpuToggleThreadArg cpu_toggle_arg;
-  cpu_toggle_arg.toggle_cpu = test_cpu;
-  cpu_toggle_arg.end_flag = false;
+  CpuToggleThreadArg cpu_toggle_arg(test_cpu);
   std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
 
   std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
@@ -295,7 +302,7 @@
   auto report_step = std::chrono::seconds(15);
   size_t iterations = 0;
 
-  while (cur_time < end_time) {
+  while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) {
     if (cur_time + report_step < std::chrono::steady_clock::now()) {
       // Report test time.
       auto diff = std::chrono::duration_cast<std::chrono::seconds>(
@@ -319,6 +326,9 @@
       GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations << " times.";
     }
   }
+  if (cpu_toggle_arg.cpu_hotplug_failed) {
+    GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure.";
+  }
   cpu_toggle_arg.end_flag = true;
   cpu_toggle_thread.join();
 }
@@ -350,9 +360,7 @@
   if (!FindAHotpluggableCpu(&test_cpu)) {
     return;
   }
-  CpuToggleThreadArg cpu_toggle_arg;
-  cpu_toggle_arg.toggle_cpu = test_cpu;
-  cpu_toggle_arg.end_flag = false;
+  CpuToggleThreadArg cpu_toggle_arg(test_cpu);
   std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
 
   // Start cpu spinner.
@@ -378,7 +386,7 @@
   auto report_step = std::chrono::seconds(15);
   size_t iterations = 0;
 
-  while (cur_time < end_time) {
+  while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) {
     if (cur_time + report_step < std::chrono::steady_clock::now()) {
       auto diff = std::chrono::duration_cast<std::chrono::seconds>(
           std::chrono::steady_clock::now() - start_time);
@@ -403,13 +411,17 @@
       GTEST_LOG_(INFO) << "Test offline while user process profiling for " << iterations << " times.";
     }
   }
+  if (cpu_toggle_arg.cpu_hotplug_failed) {
+    GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure.";
+  }
   cpu_toggle_arg.end_flag = true;
   cpu_toggle_thread.join();
   cpu_spin_arg.end_flag = true;
   cpu_spin_thread.join();
   // Check if the cpu-cycle event is still available on test_cpu.
-  ASSERT_TRUE(SetCpuOnline(test_cpu, true));
-  ASSERT_TRUE(EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, true) != nullptr);
+  if (SetCpuOnline(test_cpu, true)) {
+    ASSERT_TRUE(EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, true) != nullptr);
+  }
 }
 
 // http://b/19863147.
@@ -433,10 +445,14 @@
   const size_t TEST_ITERATION_COUNT = 10u;
   for (size_t i = 0; i < TEST_ITERATION_COUNT; ++i) {
     int record_cpu = 0;
-    ASSERT_TRUE(SetCpuOnline(test_cpu, true));
+    if (!SetCpuOnline(test_cpu, true)) {
+      break;
+    }
     std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
     ASSERT_TRUE(event_fd != nullptr);
-    ASSERT_TRUE(SetCpuOnline(test_cpu, false));
+    if (!SetCpuOnline(test_cpu, false)) {
+      break;
+    }
     event_fd = nullptr;
     event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
     ASSERT_TRUE(event_fd != nullptr);
diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md
index 2bba043..2c5f2b3 100644
--- a/simpleperf/demo/README.md
+++ b/simpleperf/demo/README.md
@@ -10,8 +10,8 @@
 ## Introduction
 
 Simpleperf is a native profiler used on Android platform. It can be used to profile Android
-applications. It's document is at [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md).
-Instructions of preparing your Android application for profiling are [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md#Android-application-profiling).
+applications. It's document is at [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md).
+Instructions of preparing your Android application for profiling are [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md#Android-application-profiling).
 This directory is to show examples of using simpleperf to profile Android applications. The
 meaning of each directory is as below:
 
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk
index 0254bc0..f297046 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
index c611102..f42ec17 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
@@ -17,9 +17,12 @@
             </intent-filter>
         </activity>
         <activity android:name=".SleepActivity"
-            android:exported="true">
+            android:exported="true" />
+        <activity android:name=".MultiProcessActivity"
+            android:exported="true" />
 
-        </activity>
+        <service android:name=".MultiProcessService"
+            android:process=":multiprocess_service" />
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
new file mode 100644
index 0000000..de698ec
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
@@ -0,0 +1,78 @@
+package com.example.simpleperf.simpleperfexamplepurejava;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class MultiProcessActivity extends AppCompatActivity {
+    public static final String TAG = "MultiProcessActivity";
+
+    Messenger mService = null;
+    boolean mBound;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_multi_process);
+
+        bindService(new Intent(this, MultiProcessService.class), mConnection,
+                Context.BIND_AUTO_CREATE);
+        createBusyThread();
+    }
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+            mService = new Messenger(iBinder);
+            mBound = true;
+            Message message = new Message();
+            message.what = MultiProcessService.MSG_START_BUSY_THREAD;
+            try {
+                mService.send(message);
+            } catch (RemoteException e) {
+                Log.d(TAG, e.toString());
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    void createBusyThread() {
+        new Thread(new Runnable() {
+            volatile int i = 0;
+
+            @Override
+            public void run() {
+                while (true) {
+                    i = callFunction(i);
+                }
+            }
+
+            private int callFunction(int a) {
+                return a+1;
+            }
+        }, "BusyThread").start();
+    }
+}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
new file mode 100644
index 0000000..2fd4d57
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
@@ -0,0 +1,50 @@
+package com.example.simpleperf.simpleperfexamplepurejava;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+
+public class MultiProcessService extends Service {
+    public static final int MSG_START_BUSY_THREAD = 1;
+
+    public MultiProcessService() {
+    }
+
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START_BUSY_THREAD:
+                    createBusyThread();
+            }
+            super.handleMessage(msg);
+        }
+    }
+
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    void createBusyThread() {
+        new Thread(new Runnable() {
+            volatile int i = 0;
+
+            @Override
+            public void run() {
+                while (true) {
+                    i = callFunction(i);
+                }
+            }
+
+            private int callFunction(int a) {
+                return a+1;
+            }
+        }, "BusyService").start();
+    }
+}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
index 1aa4458..4a09b1a 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
@@ -9,7 +9,7 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="Hello World!"
+        android:text="MainActivity"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent"
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
new file mode 100644
index 0000000..f97b72e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.example.simpleperf.simpleperfexamplepurejava.MultiProcessActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="MultiProcessActivity"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
index e2274ca..f732f77 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
@@ -6,4 +6,13 @@
     android:layout_height="match_parent"
     tools:context="com.example.simpleperf.simpleperfexamplepurejava.SleepActivity">
 
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="SleepActivity"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
 </android.support.constraint.ConstraintLayout>
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index 078d56b..db58485 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -6,7 +6,7 @@
 and above.
 
 Simpleperf is part of the Android Open Source Project. The source code is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/).
-The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/README.md).
+The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md).
 Bugs and feature requests can be submitted at http://github.com/android-ndk/ndk/issues.
 
 
@@ -28,6 +28,8 @@
     - [Record and report call graph](#record-and-report-call-graph)
     - [Visualize profiling data](#visualize-profiling-data)
     - [Annotate source code](#annotate-source-code)
+    - [Trace offcpu time](#trace-offcpu-time)
+    - [Profile from launch of an application](#profile-from-launch-of-an-application)
 - [Answers to common issues](#answers-to-common-issues)
     - [Why we suggest profiling on android >= N devices](#why-we-suggest-profiling-on-android-n-devices)
 
@@ -93,7 +95,7 @@
 
 `annotate.py` is used to annotate source files based on profiling data.
 
-`app_profiler.py` is used to profile Android applications.
+`app_profiler.py` is used to profile Android applications and native programs.
 
 `binary_cache_builder.py` is used to pull libraries from Android devices.
 
@@ -103,6 +105,8 @@
 
 `report_sample.py` is used to generate flamegraph.
 
+`run_simpleperf_on_device.py` is a simple wrapper to run simpleperf on device.
+
 `simpleperf_report_lib.py` provides a python interface for parsing profiling data.
 
 
@@ -760,7 +764,7 @@
 
 On Windows platform:
 
-    $ ./inferno.bat -sc --symfs binary_cache
+    $ inferno.bat -sc --symfs binary_cache
 
 Remove `--symfs binary_cache` if you selected not to collect binaries when
 using `app_profiler.py`.
@@ -819,6 +823,73 @@
     /* acc_p: 99.966552%, p: 83.628188%        */                    i = callFunction(i);
 
 
+### Trace offcpu time
+
+Simpleperf is a cpu profiler, it generates samples for a thread only when it is
+running on a cpu. However, sometimes we want to find out where time of a thread
+is spent, whether it is running on cpu, preempted by other threads, doing I/O
+work, or waiting for some events. To support this, we added the --trace-offcpu
+option in the simpleperf record command. When --trace-offcpu is used, simpleperf
+generates a sample when a running thread is scheduled out, so we know the
+callstack of a thread when it is scheduled out. And when reporting a perf.data
+generated with --trace-offcpu, we use timestamp to the next sample
+(instead of event counts from the previous sample) as the weight of current
+sample. As a result, we can get a callgraph based on timestamp, including both
+on cpu time and off cpu time.
+
+trace-offcpu is implemented using sched:sched_switch tracepoint event, which
+may not work well on old kernels. But it is guaranteed to be supported on
+devices after Android O MR1. We can check whether trace-offcpu is supported as
+below.
+
+    $ python run_simpleperf_on_device.py list --show-features
+    dwarf-based-call-graph
+    trace-offcpu
+
+If trace-offcpu is supported, it will be shown in the feature list.
+Then we can try it. Below is an example without using --trace-offcpu option.
+
+    $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava \
+      -a .SleepActivity -r "-g -e cpu-cycles:u --duration 10"
+    $ ./inferno.sh -sc
+
+![flamegraph sample](./without_trace_offcpu.png)
+
+In the graph, all time is taken by RunFunction(), and sleep time is ignored.
+But if we add --trace-offcpu option, the graph is changed as below.
+
+    $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava \
+      -a .SleepActivity -r "-g -e cpu-cycles:u --trace-offcpu --duration 10"
+    $ ./inferno.sh -sc
+
+![flamegraph sample](./trace_offcpu.png)
+
+As shown in the graph, half time is spent in RunFunction(), and half time is
+spent in SleepFunction(). It includes both on cpu time and off cpu time.
+
+### Profile from launch of an application
+
+Sometimes we want to profile the launch-time of an application. To support this,
+we added the --app option in the simpleperf record command. The --app option
+sets the package name of the Android application to profile. If the app is not
+already running, the simpleperf record command will poll for the app process in
+a loop with an interval of 1ms. So to profile from launch of an application,
+we can first start simpleperf record with --app, then start the app.
+Below is an example.
+
+    $ adb shell /data/local/tmp/simpleperf record -g \
+    --app com.example.simpleperf.simpleperfexamplepurejava --duration 1 \
+    -o /data/local/tmp/perf.data
+    # Start the app manually or using the `am` command.
+
+To make it convenient to use, app_profiler.py combines these in the
+--profile_from_launch option. Below is an example.
+
+    $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava \
+      -a .MainActivity --arch arm64 -r "-g -e cpu-cycles:u --duration 1" \
+      --profile_from_launch
+
+
 ## Answers to common issues
 
 ### Why we suggest profiling on Android >= N devices?
diff --git a/simpleperf/doc/inferno.md b/simpleperf/doc/inferno.md
index 2f33e8d..bfe280a 100644
--- a/simpleperf/doc/inferno.md
+++ b/simpleperf/doc/inferno.md
@@ -52,7 +52,7 @@
 Open a terminal and from `simpleperf/scripts` directory type:
 ```
 ./inferno.sh  (on Linux/Mac)
-./inferno.bat (on Windows)
+inferno.bat (on Windows)
 ```
 
 Inferno will collect data, process them and automatically open your web browser
diff --git a/simpleperf/doc/trace_offcpu.png b/simpleperf/doc/trace_offcpu.png
new file mode 100644
index 0000000..6af7f4b
--- /dev/null
+++ b/simpleperf/doc/trace_offcpu.png
Binary files differ
diff --git a/simpleperf/doc/without_trace_offcpu.png b/simpleperf/doc/without_trace_offcpu.png
new file mode 100644
index 0000000..e7a8380
--- /dev/null
+++ b/simpleperf/doc/without_trace_offcpu.png
Binary files differ
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 4f202bc..9080640 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -153,7 +153,8 @@
       min_vaddr_(std::numeric_limits<uint64_t>::max()),
       is_loaded_(false),
       dump_id_(UINT_MAX),
-      symbol_dump_id_(0) {
+      symbol_dump_id_(0),
+      symbol_warning_loglevel_(android::base::WARNING) {
   if (type_ == DSO_KERNEL) {
     min_vaddr_ = 0;
   }
@@ -287,6 +288,8 @@
     // dumped_symbols,  so later we can merge them with symbols read from file system.
     dumped_symbols = std::move(symbols_);
     symbols_.clear();
+    // Don't warn missing symbol table if we have dumped symbols in perf.data.
+    symbol_warning_loglevel_ = android::base::DEBUG;
   }
   bool result = false;
   switch (type_) {
@@ -354,11 +357,10 @@
       return true;
     }
     // Lacking symbol table isn't considered as an error but worth reporting.
-    LOG(WARNING) << filename << " doesn't contain symbol table";
+    LOG(symbol_warning_loglevel_) << filename << " doesn't contain symbol table";
     return true;
   } else {
-    LOG(WARNING) << "failed to read symbols from " << filename
-                 << ": " << result;
+    LOG(symbol_warning_loglevel_) << "failed to read symbols from " << filename << ": " << result;
     return false;
   }
 }
@@ -380,7 +382,7 @@
       }
     }
     if (all_zero) {
-      LOG(WARNING)
+      LOG(symbol_warning_loglevel_)
           << "Symbol addresses in /proc/kallsyms on device are all zero. "
              "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
       symbols_.clear();
@@ -396,8 +398,8 @@
       }
       bool match = (build_id == real_build_id);
       if (!match) {
-        LOG(WARNING) << "failed to read symbols from /proc/kallsyms: Build id "
-                     << "mismatch";
+        LOG(symbol_warning_loglevel_) << "failed to read symbols from /proc/kallsyms: Build id "
+                                      << "mismatch";
         return false;
       }
     }
@@ -417,8 +419,8 @@
       }
     }
     if (all_zero) {
-      LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. "
-                      "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
+      LOG(symbol_warning_loglevel_) << "Symbol addresses in /proc/kallsyms are all zero. "
+                                       "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
       symbols_.clear();
       return false;
     }
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index f41b140..cb0e51d 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -22,6 +22,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <android-base/logging.h>
 #include <android-base/test_utils.h>
 
 #include "build_id.h"
@@ -182,6 +183,7 @@
   uint32_t dump_id_;
   // Used to assign dump_id for symbols in current dso.
   uint32_t symbol_dump_id_;
+  android::base::LogSeverity symbol_warning_loglevel_;
 };
 
 const char* DsoTypeToString(DsoType dso_type);
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 79280d1..e238ffb 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -35,7 +35,7 @@
 #include <procinfo/process.h>
 
 #if defined(__ANDROID__)
-#include <sys/system_properties.h>
+#include <android-base/properties.h>
 #endif
 
 #include "event_type.h"
@@ -383,22 +383,22 @@
     return true;
   }
 #if defined(__ANDROID__)
-  const char* prop_name = "security.perf_harden";
-  char prop_value[PROP_VALUE_MAX];
-  if (__system_property_get(prop_name, prop_value) <= 0) {
+  const std::string prop_name = "security.perf_harden";
+  std::string prop_value = android::base::GetProperty(prop_name, "");
+  if (prop_value.empty()) {
     // can't do anything if there is no such property.
     return true;
   }
-  if (strcmp(prop_value, "0") == 0) {
+  if (prop_value == "0") {
     return true;
   }
   // Try to enable perf_event_paranoid by setprop security.perf_harden=0.
-  if (__system_property_set(prop_name, "0") == 0) {
+  if (android::base::SetProperty(prop_name, "0")) {
     sleep(1);
     if (can_read_paranoid && ReadPerfEventParanoid(&limit_level) && limit_level <= 1) {
       return true;
     }
-    if (__system_property_get(prop_name, prop_value) > 0 && strcmp(prop_value, "0") == 0) {
+    if (android::base::GetProperty(prop_name, "") == "0") {
       return true;
     }
   }
@@ -522,7 +522,23 @@
   Dso::SetVdsoFile(std::move(tmpfile), sizeof(size_t) == sizeof(uint64_t));
 }
 
-int WaitForAppProcess(const std::string& package_name) {
+static bool HasOpenedAppApkFile(int pid) {
+  std::string fd_path = "/proc/" + std::to_string(pid) + "/fd/";
+  std::vector<std::string> files = GetEntriesInDir(fd_path);
+  for (const auto& file : files) {
+    std::string real_path;
+    if (!android::base::Readlink(fd_path + file, &real_path)) {
+      continue;
+    }
+    if (real_path.find("app") != std::string::npos && real_path.find(".apk") != std::string::npos) {
+      return true;
+    }
+  }
+  return false;
+}
+
+std::set<pid_t> WaitForAppProcesses(const std::string& package_name) {
+  std::set<pid_t> result;
   size_t loop_count = 0;
   while (true) {
     std::vector<pid_t> pids = GetAllProcesses();
@@ -532,13 +548,36 @@
         // Maybe we don't have permission to read it.
         continue;
       }
-      cmdline = android::base::Basename(cmdline);
-      if (cmdline == package_name) {
-        if (loop_count > 0u) {
-          LOG(INFO) << "Got process " << pid << " for package " << package_name;
-        }
-        return pid;
+      std::string process_name = android::base::Basename(cmdline);
+      // The app may have multiple processes, with process name like
+      // com.google.android.googlequicksearchbox:search.
+      size_t split_pos = process_name.find(':');
+      if (split_pos != std::string::npos) {
+        process_name = process_name.substr(0, split_pos);
       }
+      if (process_name != package_name) {
+        continue;
+      }
+      // If a debuggable app with wrap.sh runs on Android O, the app will be started with
+      // logwrapper as below:
+      // 1. Zygote forks a child process, rename it to package_name.
+      // 2. The child process execute sh, which starts a child process running
+      //    /system/bin/logwrapper.
+      // 3. logwrapper starts a child process running sh, which interprets wrap.sh.
+      // 4. wrap.sh starts a child process running the app.
+      // The problem here is we want to profile the process started in step 4, but sometimes we
+      // run into the process started in step 1. To solve it, we can check if the process has
+      // opened an apk file in some app dirs.
+      if (!HasOpenedAppApkFile(pid)) {
+        continue;
+      }
+      if (loop_count > 0u) {
+        LOG(INFO) << "Got process " << pid << " for package " << package_name;
+      }
+      result.insert(pid);
+    }
+    if (!result.empty()) {
+      return result;
     }
     if (++loop_count == 1u) {
       LOG(INFO) << "Waiting for process of app " << package_name;
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 52d0a7c..0f12146 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -92,7 +92,7 @@
 ArchType GetMachineArch();
 void PrepareVdsoFile();
 
-int WaitForAppProcess(const std::string& package_name);
+std::set<pid_t> WaitForAppProcesses(const std::string& package_name);
 bool RunInAppContext(const std::string& app_package_name, const std::string& cmd,
                      const std::vector<std::string>& args, size_t workload_args_size,
                      const std::string& output_filepath, bool need_tracepoint_events);
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index e0af586..ebd3477 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -103,6 +103,19 @@
   return false;
 }
 
+bool IsSettingClockIdSupported() {
+  const EventType* type = FindEventTypeByName("cpu-cycles");
+  if (type == nullptr) {
+    return false;
+  }
+  // Check if the kernel supports setting clockid, which was added in kernel 4.0. Just check with
+  // one clockid is enough. Because all needed clockids were supported before kernel 4.0.
+  perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
+  attr.use_clockid = 1;
+  attr.clockid = CLOCK_MONOTONIC;
+  return IsEventAttrSupported(attr);
+}
+
 bool EventSelectionSet::BuildAndCheckEventSelection(
     const std::string& event_name, EventSelection* selection) {
   std::unique_ptr<EventTypeAndModifier> event_type = ParseEventType(event_name);
@@ -128,13 +141,15 @@
   selection->event_attr.exclude_host = event_type->exclude_host;
   selection->event_attr.exclude_guest = event_type->exclude_guest;
   selection->event_attr.precise_ip = event_type->precise_ip;
-  if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) {
-    selection->event_attr.freq = 0;
-    selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
-  } else {
-    selection->event_attr.freq = 1;
-    selection->event_attr.sample_freq =
-        AdjustSampleFrequency(DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+  if (!for_stat_cmd_) {
+    if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) {
+      selection->event_attr.freq = 0;
+      selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
+    } else {
+      selection->event_attr.freq = 1;
+      selection->event_attr.sample_freq =
+          AdjustSampleFrequency(DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+    }
   }
   if (!IsEventAttrSupported(selection->event_attr)) {
     LOG(ERROR) << "Event type '" << event_type->name
@@ -370,6 +385,15 @@
   }
 }
 
+void EventSelectionSet::SetClockId(int clock_id) {
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      selection.event_attr.use_clockid = 1;
+      selection.event_attr.clockid = clock_id;
+    }
+  }
+}
+
 bool EventSelectionSet::NeedKernelSymbol() const {
   for (const auto& group : groups_) {
     for (const auto& selection : group) {
@@ -402,7 +426,7 @@
   EventFd* group_fd = nullptr;
   for (auto& selection : group) {
     std::unique_ptr<EventFd> event_fd =
-        EventFd::OpenEventFile(selection.event_attr, tid, cpu, group_fd);
+        EventFd::OpenEventFile(selection.event_attr, tid, cpu, group_fd, false);
     if (event_fd != nullptr) {
       LOG(VERBOSE) << "OpenEventFile for " << event_fd->Name();
       event_fds.push_back(std::move(event_fd));
@@ -462,24 +486,25 @@
       }
     } else {
       for (const auto& pair : process_map) {
+        size_t success_count = 0;
+        std::string failed_event_type;
         for (const auto& tid : pair.second) {
-          size_t success_cpu_count = 0;
-          std::string failed_event_type;
           for (const auto& cpu : cpus) {
             if (OpenEventFilesOnGroup(group, tid, cpu, &failed_event_type)) {
-              success_cpu_count++;
+              success_count++;
             }
           }
-          // As the online cpus can be enabled or disabled at runtime, we may not
-          // open event file for all cpus successfully. But we should open at
-          // least one cpu successfully.
-          if (success_cpu_count == 0) {
-            PLOG(ERROR) << "failed to open perf event file for event_type "
-                        << failed_event_type << " for "
-                        << (tid == -1 ? "all threads" : "thread " + std::to_string(tid))
-                        << " on all cpus";
-            return false;
-          }
+        }
+        // We can't guarantee to open perf event file successfully for each thread on each cpu.
+        // Because threads may exit between PrepareThreads() and OpenEventFilesOnGroup(), and
+        // cpus may be offlined between GetOnlineCpus() and OpenEventFilesOnGroup().
+        // So we only check that we can at least monitor one thread for each process.
+        if (success_count == 0) {
+          PLOG(ERROR) << "failed to open perf event file for event_type "
+                      << failed_event_type << " for "
+                      << (pair.first == -1 ? "all threads"
+                                           : "threads in process " + std::to_string(pair.first));
+          return false;
         }
       }
     }
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index cc972be..7397c6f 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -101,6 +101,7 @@
   void EnableFpCallChainSampling();
   bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);
   void SetInherit(bool enable);
+  void SetClockId(int clock_id);
   bool NeedKernelSymbol() const;
 
   void AddMonitoredProcesses(const std::set<pid_t>& processes) {
@@ -201,5 +202,6 @@
 bool IsBranchSamplingSupported();
 bool IsDwarfCallChainSamplingSupported();
 bool IsDumpingRegsForTracepointEventsSupported();
+bool IsSettingClockIdSupported();
 
 #endif  // SIMPLE_PERF_EVENT_SELECTION_SET_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 42519d5..599bb42 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -26,7 +26,7 @@
 #include <ziparchive/zip_archive.h>
 
 #if defined(__ANDROID__)
-#include <sys/system_properties.h>
+#include <android-base/properties.h>
 #endif
 
 #include "command.h"
@@ -106,26 +106,26 @@
 class ScopedEnablingPerf {
  public:
   ScopedEnablingPerf() {
-    memset(prop_value_, '\0', sizeof(prop_value_));
-    __system_property_get("security.perf_harden", prop_value_);
+    prop_value_ = android::base::GetProperty("security.perf_harden", "");
     SetProp("0");
   }
 
   ~ScopedEnablingPerf() {
-    if (strlen(prop_value_) != 0) {
+    if (!prop_value_.empty()) {
       SetProp(prop_value_);
     }
   }
 
  private:
-  void SetProp(const char* value) {
-    __system_property_set("security.perf_harden", value);
+  void SetProp(const std::string& value) {
+    android::base::SetProperty("security.perf_harden", value);
+
     // Sleep one second to wait for security.perf_harden changing
     // /proc/sys/kernel/perf_event_paranoid.
     sleep(1);
   }
 
-  char prop_value_[PROP_VALUE_MAX];
+  std::string prop_value_;
 };
 
 class ScopedWorkloadExecutable {
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
index f9ed6f3..1ddaf00 100644
--- a/simpleperf/record_file_format.h
+++ b/simpleperf/record_file_format.h
@@ -17,6 +17,8 @@
 #ifndef SIMPLE_PERF_RECORD_FILE_FORMAT_H_
 #define SIMPLE_PERF_RECORD_FILE_FORMAT_H_
 
+#include <string>
+
 #include "perf_event.h"
 
 /*
@@ -91,6 +93,9 @@
   FEAT_MAX_NUM = 256,
 };
 
+std::string GetFeatureName(int feature_id);
+int GetFeatureId(const std::string& feature_name);
+
 struct SectionDesc {
   uint64_t offset;
   uint64_t size;
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index 67f35be..38a4b2d 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -29,6 +29,46 @@
 
 using namespace PerfFileFormat;
 
+namespace PerfFileFormat {
+
+static const std::map<int, std::string> feature_name_map = {
+    {FEAT_TRACING_DATA, "tracing_data"},
+    {FEAT_BUILD_ID, "build_id"},
+    {FEAT_HOSTNAME, "hostname"},
+    {FEAT_OSRELEASE, "osrelease"},
+    {FEAT_VERSION, "version"},
+    {FEAT_ARCH, "arch"},
+    {FEAT_NRCPUS, "nrcpus"},
+    {FEAT_CPUDESC, "cpudesc"},
+    {FEAT_CPUID, "cpuid"},
+    {FEAT_TOTAL_MEM, "total_mem"},
+    {FEAT_CMDLINE, "cmdline"},
+    {FEAT_EVENT_DESC, "event_desc"},
+    {FEAT_CPU_TOPOLOGY, "cpu_topology"},
+    {FEAT_NUMA_TOPOLOGY, "numa_topology"},
+    {FEAT_BRANCH_STACK, "branch_stack"},
+    {FEAT_PMU_MAPPINGS, "pmu_mappings"},
+    {FEAT_GROUP_DESC, "group_desc"},
+    {FEAT_FILE, "file"},
+    {FEAT_META_INFO, "meta_info"},
+};
+
+std::string GetFeatureName(int feature_id) {
+  auto it = feature_name_map.find(feature_id);
+  return it == feature_name_map.end() ? "" : it->second;
+}
+
+int GetFeatureId(const std::string& feature_name) {
+  for (auto& pair : feature_name_map) {
+    if (pair.second == feature_name) {
+      return pair.first;
+    }
+  }
+  return -1;
+}
+
+} // namespace PerfFileFormat
+
 std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::string& filename) {
   std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE;
   FILE* fp = fopen(filename.c_str(), mode.c_str());
@@ -203,6 +243,20 @@
     }
     if (record->type() == SIMPLE_PERF_RECORD_EVENT_ID) {
       ProcessEventIdRecord(*static_cast<EventIdRecord*>(record.get()));
+    } else if (record->type() == PERF_RECORD_SAMPLE) {
+      SampleRecord* r = static_cast<SampleRecord*>(record.get());
+      // Although we have removed ip == 0 callchains when recording dwarf based callgraph,
+      // stack frame based callgraph can also generate ip == 0 callchains. Remove them here
+      // to avoid caller's effort.
+      if (r->sample_type & PERF_SAMPLE_CALLCHAIN) {
+        size_t i;
+        for (i = 0; i < r->callchain_data.ip_nr; ++i) {
+          if (r->callchain_data.ips[i] == 0) {
+            break;
+          }
+        }
+        r->callchain_data.ip_nr = i;
+      }
     }
     if (sorted) {
       record_cache_->Push(std::move(record));
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index 79a2103..88bb4d7 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -72,9 +72,9 @@
   CallChainEntry* entries;
 };
 
-struct MetaInfoEntry {
-  const char* key;
-  const char* value;
+struct FeatureSection {
+  const char* data;
+  uint32_t data_size;
 };
 
 // Create a new instance,
@@ -96,7 +96,7 @@
 CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) EXPORT;
 
 const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) EXPORT;
-MetaInfoEntry* GetNextMetaInfo(ReportLib* report_lib) EXPORT;
+FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) EXPORT;
 }
 
 struct EventAttrWithName {
@@ -120,7 +120,6 @@
         current_thread_(nullptr),
         update_flag_(0),
         trace_offcpu_(false) {
-    current_meta_info_.key = current_meta_info_.value = nullptr;
   }
 
   bool SetLogSeverity(const char* log_level);
@@ -142,7 +141,7 @@
   CallChain* GetCallChainOfCurrentSample();
 
   const char* GetBuildIdForPath(const char* path);
-  MetaInfoEntry* GetNextMetaInfo();
+  FeatureSection* GetFeatureSection(const char* feature_name);
 
  private:
   Sample* GetCurrentSample();
@@ -164,12 +163,11 @@
   std::string build_id_string_;
   int update_flag_;
   std::vector<EventAttrWithName> event_attrs_;
-
-  std::unordered_map<std::string, std::string> meta_info_map_;
-  MetaInfoEntry current_meta_info_;
   std::unique_ptr<ScopedEventTypes> scoped_event_types_;
   bool trace_offcpu_;
   std::unordered_map<pid_t, std::unique_ptr<SampleRecord>> next_sample_cache_;
+  FeatureSection feature_section_;
+  std::vector<char> feature_section_data_;
 };
 
 bool ReportLib::SetLogSeverity(const char* log_level) {
@@ -200,16 +198,17 @@
       return false;
     }
     record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+    std::unordered_map<std::string, std::string> meta_info_map;
     if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO) &&
-        !record_file_reader_->ReadMetaInfoFeature(&meta_info_map_)) {
+        !record_file_reader_->ReadMetaInfoFeature(&meta_info_map)) {
       return false;
     }
-    auto it = meta_info_map_.find("event_type_info");
-    if (it != meta_info_map_.end()) {
+    auto it = meta_info_map.find("event_type_info");
+    if (it != meta_info_map.end()) {
       scoped_event_types_.reset(new ScopedEventTypes(it->second));
     }
-    it = meta_info_map_.find("trace_offcpu");
-    if (it != meta_info_map_.end()) {
+    it = meta_info_map.find("trace_offcpu");
+    if (it != meta_info_map.end()) {
       trace_offcpu_ = it->second == "true";
     }
   }
@@ -387,21 +386,17 @@
   return build_id_string_.c_str();
 }
 
-MetaInfoEntry* ReportLib::GetNextMetaInfo() {
+FeatureSection* ReportLib::GetFeatureSection(const char* feature_name) {
   if (!OpenRecordFileIfNecessary()) {
     return nullptr;
   }
-  auto it = meta_info_map_.begin();
-  if (current_meta_info_.key != nullptr) {
-    it = meta_info_map_.find(current_meta_info_.key);
-    ++it;
-  }
-  if (it == meta_info_map_.end()) {
+  int feature = PerfFileFormat::GetFeatureId(feature_name);
+  if (feature == -1 || !record_file_reader_->ReadFeatureSection(feature, &feature_section_data_)) {
     return nullptr;
   }
-  current_meta_info_.key = it->first.c_str();
-  current_meta_info_.value = it->second.c_str();
-  return &current_meta_info_;
+  feature_section_.data = feature_section_data_.data();
+  feature_section_.data_size = feature_section_data_.size();
+  return &feature_section_;
 }
 
 // Exported methods working with a client created instance
@@ -453,6 +448,6 @@
   return report_lib->GetBuildIdForPath(path);
 }
 
-MetaInfoEntry* GetNextMetaInfo(ReportLib* report_lib) {
-  return report_lib->GetNextMetaInfo();
+FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) {
+  return report_lib->GetFeatureSection(feature_name);
 }
diff --git a/simpleperf/scripts/Android.mk b/simpleperf/scripts/Android.mk
deleted file mode 100644
index e068bd4..0000000
--- a/simpleperf/scripts/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-LOCAL_PATH := $(call my-dir)
-
-SIMPLEPERF_SCRIPT_LIST := $(wildcard $(LOCAL_PATH)/*.py $(LOCAL_PATH)/*.config) \
-                          $(LOCAL_PATH)/../doc \
-                          $(LOCAL_PATH)/../demo \
-                          $(LOCAL_PATH)/../testdata/perf_with_symbols.data \
-                          $(LOCAL_PATH)/../testdata/perf_with_trace_offcpu.data
-
-SIMPLEPERF_SCRIPT_LIST := $(filter-out $(LOCAL_PATH)/update.py,$(SIMPLEPERF_SCRIPT_LIST))
-
-$(HOST_OUT_EXECUTABLES)/simpleperf_script.zip : $(SIMPLEPERF_SCRIPT_LIST)
-	zip -r - $^ >$@
-
-SIMPLEPERF_SCRIPT_LIST :=
-
-sdk: $(HOST_OUT_EXECUTABLES)/simpleperf_script.zip
-
-$(call dist-for-goals,sdk,$(HOST_OUT_EXECUTABLES)/simpleperf_script.zip)
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index 0de735f..c089bbe 100644
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -25,6 +25,7 @@
 import copy
 import os
 import os.path
+import re
 import shutil
 import subprocess
 import sys
@@ -53,12 +54,13 @@
         self.app_program = self.config['app_package_name'] or self.config['native_program']
         self.app_pid = None
         self.has_symfs_on_device = False
+        self.record_subproc = None
 
 
     def check_config(self, config):
         config_names = ['app_package_name', 'native_program', 'cmd', 'native_lib_dir',
                         'apk_file_path', 'recompile_app', 'launch_activity', 'launch_inst_test',
-                        'record_options', 'perf_data_path']
+                        'record_options', 'perf_data_path', 'profile_from_launch', 'app_arch']
         for name in config_names:
             if name not in config:
                 log_exit('config [%s] is missing' % name)
@@ -83,6 +85,13 @@
             if not config['launch_activity'] and not config['launch_inst_test']:
                 # If recompile app, the app needs to be restarted to take effect.
                 config['launch_activity'] = '.MainActivity'
+        if config['profile_from_launch']:
+            if not config['app_package_name']:
+                log_exit('-p needs to be set to profile from launch.')
+            if not config['launch_activity']:
+                log_exit('-a needs to be set to profile from launch.')
+            if not config['app_arch']:
+                log_exit('--arch needs to be set to profile from launch.')
 
 
     def profile(self):
@@ -101,27 +110,16 @@
         self._recompile_app()
         self._restart_app()
         self._get_app_environment()
-        self._download_simpleperf()
-        self._download_native_libs()
+        if not self.config['profile_from_launch']:
+            self._download_simpleperf()
+            self._download_native_libs()
 
 
     def _get_device_environment(self):
         self.is_root_device = self.adb.switch_to_root()
-
-        # Get android version.
-        build_version = self.adb.get_property('ro.build.version.release')
-        if build_version:
-            if not build_version[0].isdigit():
-                c = build_version[0].upper()
-                if c < 'L':
-                    self.android_version = 0
-                else:
-                    self.android_version = ord(c) - ord('L') + 5
-            else:
-                strs = build_version.split('.')
-                if strs:
-                    self.android_version = int(strs[0])
-
+        self.android_version = self.adb.get_android_version()
+        if self.android_version < 7:
+            log_warning("app_profiler.py is not tested prior Android N, please switch to use cmdline interface.")
         self.device_arch = self.adb.get_device_arch()
 
 
@@ -164,11 +162,14 @@
             else:
                 self.config['launch_activity'] = '.MainActivity'
 
-        pid = self._find_app_process()
-        if pid is not None:
-            self.run_in_app_dir(['kill', '-9', str(pid)])
+        self.adb.check_run(['shell', 'am', 'force-stop', self.config['app_package_name']])
+        while self._find_app_process():
             time.sleep(1)
 
+        if self.config['profile_from_launch']:
+            self._download_simpleperf()
+            self.start_profiling()
+
         if self.config['launch_activity']:
             activity = self.config['app_package_name'] + '/' + self.config['launch_activity']
             result = self.adb.run(['shell', 'am', 'start', '-n', activity])
@@ -182,8 +183,8 @@
                 log_exit("Can't start instrumentation test  %s" % self.config['launch_inst_test'])
 
         for i in range(10):
-            pid = self._find_app_process()
-            if pid is not None:
+            self.app_pid = self._find_app_process()
+            if self.app_pid is not None:
                 return
             time.sleep(1)
             log_info('Wait for the app process for %d seconds' % (i + 1))
@@ -191,20 +192,46 @@
 
 
     def _find_app_process(self):
-        # On Android >= N, pidof is available. Otherwise, we can use ps.
-        if self.android_version >= 7:
+        if not self.config['app_package_name'] and self.android_version >= 7:
             result, output = self.adb.run_and_return_output(['shell', 'pidof', self.app_program])
             return int(output) if result else None
-        result, output = self.adb.run_and_return_output(['shell', 'ps'], log_output=False)
+        ps_args = ['ps', '-e', '-o', 'PID,NAME'] if self.android_version >= 8 else ['ps']
+        result, output = self.adb.run_and_return_output(['shell'] + ps_args, log_output=False)
         if not result:
             return None
         for line in output.split('\n'):
             strs = line.split()
-            if len(strs) > 2 and self.app_program in strs[-1]:
-                return int(strs[1])
+            if len(strs) < 2:
+                continue
+            process_name = strs[-1]
+            if self.config['app_package_name']:
+                # This is to match process names in multiprocess apps.
+                process_name = process_name.split(':')[0]
+            if process_name == self.app_program:
+                pid = int(strs[0] if self.android_version >= 8 else strs[1])
+                # If a debuggable app with wrap.sh runs on Android O, the app will be started with
+                # logwrapper as below:
+                # 1. Zygote forks a child process, rename it to package_name.
+                # 2. The child process execute sh, which starts a child process running
+                # /system/bin/logwrapper.
+                # 3. logwrapper starts a child process running sh, which interprets wrap.sh.
+                # 4. wrap.sh starts a child process running the app.
+                # The problem here is we want to profile the process started in step 4, but
+                # sometimes we run into the process started in step 1. To solve it, we can check
+                # if the process has opened an apk file in some app dirs.
+                if self.android_version >= 8 and self.config['app_package_name'] and (
+                    not self._has_opened_apk_file(pid)):
+                    continue
+                return pid
         return None
 
 
+    def _has_opened_apk_file(self, pid):
+        result, output = self.run_in_app_dir(['ls -l /proc/%d/fd' % pid],
+                                             check_result=False, log_output=False)
+        return result and re.search(r'app.*\.apk', output)
+
+
     def _get_app_environment(self):
         if not self.config['cmd']:
             if self.app_pid is None:
@@ -252,7 +279,7 @@
                 searched_lib[item] = True
                 # Use '/' as path separator as item comes from android environment.
                 filename = item[item.rfind('/') + 1:]
-                dirname = '/data/local/tmp' + item[:item.rfind('/')]
+                dirname = '/data/local/tmp/native_libs' + item[:item.rfind('/')]
                 path = filename_dict.get(filename)
                 if path is None:
                     continue
@@ -283,28 +310,40 @@
 
 
     def start_and_wait_profiling(self):
-        subproc = None
+        if self.record_subproc is None:
+            self.start_profiling()
+        self.wait_profiling()
+
+
+    def wait_profiling(self):
         returncode = None
         try:
-            args = ['/data/local/tmp/simpleperf', 'record', self.config['record_options'],
-                    '-o', '/data/local/tmp/perf.data']
-            if self.config['app_package_name']:
-                args += ['--app', self.config['app_package_name']]
-            elif self.config['native_program']:
-                args += ['-p', str(self.app_pid)]
-            elif self.config['cmd']:
-                args.append(self.config['cmd'])
-            if self.has_symfs_on_device:
-                args += ['--symfs', '/data/local/tmp/native_libs']
-            adb_args = [self.adb.adb_path, 'shell'] + args
-            log_debug('run adb cmd: %s' % adb_args)
-            subproc = subprocess.Popen(adb_args)
-            returncode = subproc.wait()
+            returncode = self.record_subproc.wait()
         except KeyboardInterrupt:
-            if subproc:
-                self.stop_profiling()
-                returncode = 0
-        log_debug('run adb cmd: %s [result %s]' % (adb_args, returncode == 0))
+            self.stop_profiling()
+            self.record_subproc = None
+            # Don't check return value of record_subproc. Because record_subproc also
+            # receives Ctrl-C, and always returns non-zero.
+            returncode = 0
+        log_debug('profiling result [%s]' % (returncode == 0))
+        if returncode != 0:
+            log_exit('Failed to record profiling data.')
+
+
+    def start_profiling(self):
+        args = ['/data/local/tmp/simpleperf', 'record', self.config['record_options'],
+                '-o', '/data/local/tmp/perf.data']
+        if self.config['app_package_name']:
+            args += ['--app', self.config['app_package_name']]
+        elif self.config['native_program']:
+            args += ['-p', str(self.app_pid)]
+        elif self.config['cmd']:
+            args.append(self.config['cmd'])
+        if self.has_symfs_on_device:
+            args += ['--symfs', '/data/local/tmp/native_libs']
+        adb_args = [self.adb.adb_path, 'shell'] + args
+        log_debug('run adb cmd: %s' % adb_args)
+        self.record_subproc = subprocess.Popen(adb_args)
 
 
     def stop_profiling(self):
@@ -370,11 +409,11 @@
 """When profiling an Android app, we need the apk file to recompile the app on
 Android version <= M.""")
     parser.add_argument('-a', '--activity', help=
-"""When profiling an Android app, start an activity before profiling. It can be used to profile
-the startup time of an activity.""")
+"""When profiling an Android app, start an activity before profiling.
+It restarts the app if the app is already running.""")
     parser.add_argument('-t', '--test', help=
 """When profiling an Android app, start an instrumentation test before profiling.
-It can be used to profile an instrumentation test.""")
+It restarts the app if the app is already running.""")
     parser.add_argument('--arch', help=
 """Select which arch the app is running on, possible values are:
 arm, arm64, x86, x86_64. If not set, the script will try to detect it.""")
@@ -385,6 +424,12 @@
     parser.add_argument('-nb', '--skip_collect_binaries', action='store_true', help=
 """By default we collect binaries used in profiling data from device to
 binary_cache directory. It can be used to annotate source code. This option skips it.""")
+    parser.add_argument('--profile_from_launch', action='store_true', help=
+"""Profile an activity from initial launch. It should be used with -p, -a, and --arch options.
+Normally we run in the following order: restart the app, detect the architecture of the app,
+download simpleperf and native libs with debug info on device, and start simpleperf record.
+But with --profile_from_launch option, we change the order as below: kill the app if it is
+already running, download simpleperf on device, start simpleperf record, and start the app.""")
     parser.add_argument('--disable_adb_root', action='store_true', help=
 """Force adb to run in non root mode.""")
     args = parser.parse_args()
@@ -403,6 +448,7 @@
     config['record_options'] = args.record_options
     config['perf_data_path'] = args.perf_data_path
     config['collect_binaries'] = not args.skip_collect_binaries
+    config['profile_from_launch'] = args.profile_from_launch
     config['disable_adb_root'] = args.disable_adb_root
 
     profiler = AppProfiler(config)
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index 7fe6cd2..c4d0fce 100755
--- a/simpleperf/scripts/bin/android/arm/simpleperf
+++ b/simpleperf/scripts/bin/android/arm/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/arm64/simpleperf b/simpleperf/scripts/bin/android/arm64/simpleperf
index ef80a48..9519f68 100755
--- a/simpleperf/scripts/bin/android/arm64/simpleperf
+++ b/simpleperf/scripts/bin/android/arm64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/x86/simpleperf b/simpleperf/scripts/bin/android/x86/simpleperf
index 4d881bd..b7c753a 100755
--- a/simpleperf/scripts/bin/android/x86/simpleperf
+++ b/simpleperf/scripts/bin/android/x86/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/x86_64/simpleperf b/simpleperf/scripts/bin/android/x86_64/simpleperf
index 4fc509e..069370b 100755
--- a/simpleperf/scripts/bin/android/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/android/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib b/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib
index dc7492f..07ff964 100755
--- a/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib
+++ b/simpleperf/scripts/bin/darwin/x86/libsimpleperf_report.dylib
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86/simpleperf b/simpleperf/scripts/bin/darwin/x86/simpleperf
index 2eba694..61fd1fe 100755
--- a/simpleperf/scripts/bin/darwin/x86/simpleperf
+++ b/simpleperf/scripts/bin/darwin/x86/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
index 508a881..f70d7e7 100755
--- a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
+++ b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86_64/simpleperf b/simpleperf/scripts/bin/darwin/x86_64/simpleperf
index a6046ef..1af4ecf 100755
--- a/simpleperf/scripts/bin/darwin/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/darwin/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so b/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so
index 5f98789..8d3ea66 100755
--- a/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so
+++ b/simpleperf/scripts/bin/linux/x86/libsimpleperf_report.so
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86/simpleperf b/simpleperf/scripts/bin/linux/x86/simpleperf
index cbbbdc4..fc76b5a 100755
--- a/simpleperf/scripts/bin/linux/x86/simpleperf
+++ b/simpleperf/scripts/bin/linux/x86/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
index a8ce067..9e57247 100755
--- a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
+++ b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86_64/simpleperf b/simpleperf/scripts/bin/linux/x86_64/simpleperf
index 56b60a5..69b0cf9 100755
--- a/simpleperf/scripts/bin/linux/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/linux/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
index 3f56fa3..3a37be9 100755
--- a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
+++ b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/simpleperf.exe b/simpleperf/scripts/bin/windows/x86/simpleperf.exe
index 5de283c..cce47bf 100755
--- a/simpleperf/scripts/bin/windows/x86/simpleperf.exe
+++ b/simpleperf/scripts/bin/windows/x86/simpleperf.exe
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
index ee2e690..bbe6de6 100755
--- a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
+++ b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
index 5de283c..cce47bf 100755
--- a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
+++ b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
Binary files differ
diff --git a/simpleperf/scripts/inferno.bat b/simpleperf/scripts/inferno.bat
index 3021d50..5818f98 100644
--- a/simpleperf/scripts/inferno.bat
+++ b/simpleperf/scripts/inferno.bat
@@ -1 +1,2 @@
-python -m inferno.inferno %
+set PYTHONPATH=%PYTHONPATH%;%~dp0
+python -m inferno.inferno %*
diff --git a/simpleperf/scripts/inferno.sh b/simpleperf/scripts/inferno.sh
index 8ba5097..d30ee31 100755
--- a/simpleperf/scripts/inferno.sh
+++ b/simpleperf/scripts/inferno.sh
@@ -1,2 +1,4 @@
 #!/bin/bash
+SCRIPTPATH=$(dirname "$0")
+export PYTHONPATH=$SCRIPTPATH:$PYTHONPATH
 python -m inferno.inferno "$@"
diff --git a/simpleperf/scripts/inferno/adb.py b/simpleperf/scripts/inferno/adb.py
deleted file mode 100644
index be0a41e..0000000
--- a/simpleperf/scripts/inferno/adb.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import subprocess
-import abc
-import os
-
-BIN_PATH = "bin/android/%s/simpleperf"
-
-class Abi:
-    ARM    = 1
-    ARM_64 = 2
-    X86    = 3
-    X86_64 = 4
-
-    def __init__(self):
-        pass
-
-class Adb:
-
-    def __init__(self):
-        pass
-
-
-    def delete_previous_data(self):
-        err = subprocess.call(["adb", "shell", "rm", "-f", "/data/local/tmp/perf.data"])
-
-
-    def get_process_pid(self, process_name):
-        piof_output = subprocess.check_output(["adb", "shell", "pidof", process_name])
-        try:
-            process_id = int(piof_output)
-        except ValueError:
-            process_id = 0
-        return process_id
-
-
-    def pull_data(self):
-        err = subprocess.call(["adb", "pull", "/data/local/tmp/perf.data", "."])
-        return err
-
-
-    @abc.abstractmethod
-    def collect_data(self, simpleperf_command):
-        raise NotImplementedError("%s.collect_data(str) is not implemented!" % self.__class__.__name__)
-
-
-    def get_props(self):
-        props = {}
-        output = subprocess.check_output(["adb", "shell", "getprop"])
-        lines = output.split("\n")
-        for line in lines:
-            tokens = line.split(": ")
-            if len(tokens) < 2:
-                continue
-            key = tokens[0].replace("[", "").replace("]", "")
-            value = tokens[1].replace("[", "").replace("]", "")
-            props[key] = value
-        return props
-
-    def parse_abi(self, str):
-        if str.find("arm64") != -1:
-            return Abi.ARM_64
-        if str.find("arm") != -1:
-            return Abi.ARM
-        if str.find("x86_64") != -1:
-            return Abi.X86_64
-        if str.find("x86") != -1:
-            return Abi.X86
-        return Abi.ARM_64
-
-    def get_exec_path(self, abi):
-        folder_name = "arm64"
-        if abi == Abi.ARM:
-            folder_name = "arm"
-        if abi == Abi.X86:
-            folder_name = "x86"
-        if abi == Abi.X86_64:
-            folder_name = "x86_64"
-        return os.path.join(os.path.dirname(__file__), BIN_PATH % folder_name)
-
-    def push_simpleperf_binary(self):
-        # Detect the ABI of the device
-        props = self.get_props()
-        abi_raw = props["ro.product.cpu.abi"]
-        abi = self.parse_abi(abi_raw)
-        exec_path = self.get_exec_path(abi)
-
-        # Push simpleperf to the device
-        print "Pushing local '%s' to device." % exec_path
-        subprocess.call(["adb", "push", exec_path, "/data/local/tmp/simpleperf"])
diff --git a/simpleperf/scripts/inferno/adb_non_root.py b/simpleperf/scripts/inferno/adb_non_root.py
deleted file mode 100644
index f187c28..0000000
--- a/simpleperf/scripts/inferno/adb_non_root.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from adb import Adb
-import subprocess
-import time
-
-class AdbNonRoot(Adb):
-    # If adb cannot run as root, there is still a way to collect data but it is much more complicated.
-    # 1. Identify the platform abi, use getprop:  ro.product.cpu.abi
-    # 2. Push the precompiled scripts/bin/android/[ABI]/simpleperf to device /data/local/tmp/simpleperf
-    # 4. Use run-as to copy /data/local/tmp/simplerperf -> /apps/installation_path/simpleperf
-    # 5. Use run-as to run: /apps/installation_path/simpleperf -p APP_PID -o /apps/installation_path/perf.data
-    # 6. Use run-as fork+pipe trick to copy /apps/installation_path/perf.data to /data/local/tmp/perf.data
-    def collect_data(self, process):
-
-        if not process.args.skip_push_binary:
-          self.push_simpleperf_binary()
-
-        # Copy simpleperf to the data
-        subprocess.check_output(["adb", "shell", "run-as %s" % process.name, "cp", "/data/local/tmp/simpleperf", "."])
-
-        # Patch command to run with path to data folder where simpleperf was written.
-        process.cmd = process.cmd.replace("/data/local/tmp/perf.data", "./perf.data")
-
-        # Start collecting samples.
-        process.cmd = ("run-as %s " % process.name) + process.cmd
-        subprocess.call(["adb", "shell", process.cmd])
-
-        # Wait sampling_duration+1.5 seconds.
-        time.sleep(int(process.args.capture_duration) + 1)
-
-        # Move data to a location where shell user can read it.
-        subprocess.call(["adb", "shell", "run-as %s cat perf.data | tee /data/local/tmp/perf.data >/dev/null" % (process.name)])
-
-        return True
diff --git a/simpleperf/scripts/inferno/adb_root.py b/simpleperf/scripts/inferno/adb_root.py
deleted file mode 100644
index 4958643..0000000
--- a/simpleperf/scripts/inferno/adb_root.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from adb import Adb
-import subprocess
-
-class AdbRoot(Adb):
-    def collect_data(self, process):
-        if not process.args.skip_push_binary:
-            self.push_simpleperf_binary()
-        subprocess.call(["adb", "shell", "cd /data/local/tmp; " + process.cmd])
-        return True
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/data_types.py b/simpleperf/scripts/inferno/data_types.py
index 5341d23..4f07ba7 100644
--- a/simpleperf/scripts/inferno/data_types.py
+++ b/simpleperf/scripts/inferno/data_types.py
@@ -14,25 +14,31 @@
 # limitations under the License.
 #
 
+
 class CallSite:
+
     def __init__(self, ip, method, dso):
         self.ip = ip
         self.method = method
         self.dso = dso
 
 
-
 class Thread:
-    def __init__(self, tid):
-        self.tid = tid
-        self.samples = []
-        self.flamegraph = {}
-        self.num_samples = 0
 
+    def __init__(self, tid, pid):
+        self.tid = tid
+        self.pid = pid
+        self.name = ""
+        self.samples = []
+        self.flamegraph = FlameGraphCallSite("root", "", 0)
+        self.num_samples = 0
+        self.event_count = 0
 
     def add_callchain(self, callchain, symbol, sample):
-        chain = []
+        self.name = sample.thread_comm
         self.num_samples += 1
+        self.event_count += sample.period
+        chain = []
         for j in range(callchain.nr):
             entry = callchain.entries[callchain.nr - j - 1]
             if entry.ip == 0:
@@ -40,75 +46,68 @@
             chain.append(CallSite(entry.ip, entry.symbol.symbol_name, entry.symbol.dso_name))
 
         chain.append(CallSite(sample.ip, symbol.symbol_name, symbol.dso_name))
-        self.samples.append(chain)
-
-
-    def collapse_flamegraph(self):
-        flamegraph = FlameGraphCallSite("root", "")
-        flamegraph.id = 0 # This is used for wasd navigation, 0 = not a valid target.
-        self.flamegraph = flamegraph
-        for sample in self.samples:
-            flamegraph = self.flamegraph
-            for callsite in sample:
-               flamegraph = flamegraph.get_callsite(callsite.method, callsite.dso)
-
-        # Populate root note.
-        for node in self.flamegraph.callsites:
-            self.flamegraph.num_samples += node.num_samples
+        self.flamegraph.add_callchain(chain, sample.period)
 
 
 class Process:
+
     def __init__(self, name, pid):
         self.name = name
         self.pid = pid
         self.threads = {}
         self.cmd = ""
         self.props = {}
-        self.args = None
         self.num_samples = 0
 
-    def get_thread(self, tid):
-        if (tid not in self.threads.keys()):
-            self.threads[tid] = Thread(tid)
+    def get_thread(self, tid, pid):
+        if tid not in self.threads.keys():
+            self.threads[tid] = Thread(tid, pid)
         return self.threads[tid]
 
-CALLSITE_COUNTER = 0
-def get_callsite_id():
-    global CALLSITE_COUNTER
-    CALLSITE_COUNTER += 1
-    toReturn = CALLSITE_COUNTER
-    return toReturn
-
 
 class FlameGraphCallSite:
 
-    def __init__(self, method, dso):
-        self.callsites = []
+    callsite_counter = 0
+    @classmethod
+    def _get_next_callsite_id(cls):
+        cls.callsite_counter += 1
+        return cls.callsite_counter
+
+    def __init__(self, method, dso, id):
+        self.children = []
         self.method = method
         self.dso = dso
-        self.num_samples = 0
-        self.offset = 0 # Offset allows position nodes in different branches.
-        self.id = get_callsite_id()
+        self.event_count = 0
+        self.offset = 0  # Offset allows position nodes in different branches.
+        self.id = id
 
+    def weight(self):
+        return float(self.event_count)
 
-    def get_callsite(self, name, dso):
-        for c in self.callsites:
-            if c.equivalent(name, dso):
-                c.num_samples += 1
+    def add_callchain(self, chain, event_count):
+        self.event_count += event_count
+        current = self
+        for callsite in chain:
+            current = current._get_child(callsite)
+            current.event_count += event_count
+
+    def _get_child(self, callsite):
+        for c in self.children:
+            if c._equivalent(callsite.method, callsite.dso):
                 return c
-        callsite = FlameGraphCallSite(name, dso)
-        callsite.num_samples = 1
-        self.callsites.append(callsite)
-        return callsite
+        new_child = FlameGraphCallSite(callsite.method, callsite.dso, self._get_next_callsite_id())
+        self.children.append(new_child)
+        return new_child
 
-    def equivalent(self, method, dso):
+    def _equivalent(self, method, dso):
         return self.method == method and self.dso == dso
 
-
     def get_max_depth(self):
-        max = 0
-        for c in self.callsites:
-            depth = c.get_max_depth()
-            if depth > max:
-                max = depth
-        return max +1
\ No newline at end of file
+        return max([c.get_max_depth() for c in self.children]) + 1 if self.children else 1
+
+    def generate_offset(self, start_offset):
+        self.offset = start_offset
+        child_offset = start_offset
+        for child in self.children:
+            child_offset = child.generate_offset(child_offset)
+        return self.offset + self.event_count
diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py
index a6d950c..5060a35 100644
--- a/simpleperf/scripts/inferno/inferno.py
+++ b/simpleperf/scripts/inferno/inferno.py
@@ -29,89 +29,55 @@
 
 """
 
-from simpleperf_report_lib import ReportLib
 import argparse
-from data_types import *
-from svg_renderer import *
 import datetime
 import os
 import subprocess
+import sys
 import webbrowser
-from adb_non_root import AdbNonRoot
-from adb_root import AdbRoot
 
-def create_process(adb_client, args):
-    """ Retrieves target process pid and create a process contained.
+scripts_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+sys.path.append(scripts_path)
+from simpleperf_report_lib import ReportLib
+from utils import log_exit, log_info, AdbHelper
 
-    :param args: Argument as parsed by argparse
-    :return: Process objectk
-    """
-    process_id = adb_client.get_process_pid(args.process_name)
-    process = Process(args.process_name, process_id)
-    return process
+from data_types import *
+from svg_renderer import *
 
 
-def collect_data(adb_client, process):
-    """ Start simpleperf on device and collect data. Pull perf.data into cwd.
-
-    :param process:  Process object
-    :return: Populated Process object
-    """
-
-    if process.args.dwarf_unwinding:
-        unwinding_parameter = "-g"
-        print "Unwinding with dwarf."
+def collect_data(args):
+    app_profiler_args = [sys.executable, os.path.join(scripts_path, "app_profiler.py"), "-nb"]
+    if args.app:
+        app_profiler_args += ["-p", args.app]
+    elif args.native_program:
+        app_profiler_args += ["-np", args.native_program]
     else:
-        unwinding_parameter = "--call-graph fp"
-        print "Unwinding with frame pointers."
-
-    # Check whether sampling will be frequency based or event based.
-    sampling_parameter = "-f %s" % process.args.sample_frequency
-    if process.args.events:
-        tokens = process.args.events.split(" ")
+        log_exit("Please set profiling target with -p or -np option.")
+    if args.skip_recompile:
+        app_profiler_args.append("-nc")
+    if args.disable_adb_root:
+        app_profiler_args.append("--disable_adb_root")
+    record_arg_str = ""
+    if args.dwarf_unwinding:
+        record_arg_str += "-g "
+    else:
+        record_arg_str += "--call-graph fp "
+    if args.events:
+        tokens = args.events.split()
         if len(tokens) == 2:
             num_events = tokens[0]
             event_name = tokens[1]
-            sampling_parameter = "-c %s -e '%s'" % (num_events, event_name)
+            record_arg_str += "-c %s -e %s " % (num_events, event_name)
         else:
-            print "Event format string not recognized. Expected \"requency event_name\"."
-            print "Got : [" + ",".join(tokens) + "]"
-            return False
-        print "Using event sampling (%s)." % sampling_parameter
+            log_exit("Event format string of -e option cann't be recognized.")
+        log_info("Using event sampling (-c %s -e %s)." % (num_events, event_name))
     else:
-        print "Using frequency sampling (%s)." % sampling_parameter
-
-    process.cmd = "./simpleperf record \
-    -o /data/local/tmp/perf.data \
-    %s \
-    -p %s \
-    --duration %s \
-    %s" % (
-        unwinding_parameter,
-        process.pid,
-        process.args.capture_duration,
-        sampling_parameter)
-
-    print("Process '%s' PID = %d" % (process.name, process.pid))
-
-    if process.args.skip_collection:
-       print("Skipping data collection, expecting perf.data in folder")
-       return True
-
-    print("Sampling for %s seconds..." % process.args.capture_duration)
-
-
-    adb_client.delete_previous_data()
-
-    success = adb_client.collect_data(process)
-    if not success:
-        return False
-
-    err = adb_client.pull_data()
-    if err:
-        return False
-
-    return True
+        record_arg_str += "-f %d " % args.sample_frequency
+        log_info("Using frequency sampling (-f %d)." % args.sample_frequency)
+    record_arg_str += "--duration %d " % args.capture_duration
+    app_profiler_args += ["-r", record_arg_str]
+    returncode = subprocess.call(app_profiler_args)
+    return returncode == 0
 
 
 def parse_samples(process, args):
@@ -124,12 +90,19 @@
     lib = ReportLib()
 
     lib.ShowIpForUnknownSymbol()
-    if symfs_dir is not None:
+    if symfs_dir:
         lib.SetSymfs(symfs_dir)
-    if record_file is not None:
+    if record_file:
         lib.SetRecordFile(record_file)
-    if kallsyms_file is not None:
+    if kallsyms_file:
         lib.SetKallsymsFile(kallsyms_file)
+    process.cmd = lib.GetRecordCmd()
+    product_props = lib.MetaInfo().get("product_props")
+    if product_props:
+        tuple = product_props.split(':')
+        process.props['ro.product.manufacturer'] = tuple[0]
+        process.props['ro.product.model'] = tuple[1]
+        process.props['ro.product.name'] = tuple[2]
 
     while True:
         sample = lib.GetNextSample()
@@ -138,20 +111,16 @@
             break
         symbol = lib.GetSymbolOfCurrentSample()
         callchain = lib.GetCallChainOfCurrentSample()
-        process.get_thread(sample.tid).add_callchain(callchain, symbol, sample)
+        process.get_thread(sample.tid, sample.pid).add_callchain(callchain, symbol, sample)
         process.num_samples += 1
 
-    print("Parsed %s callchains." % process.num_samples)
+    if process.pid == 0:
+        main_threads = [thread for thread in process.threads.values() if thread.tid == thread.pid]
+        if main_threads:
+            process.name = main_threads[0].name
+            process.pid = main_threads[0].pid
 
-
-def collapse_callgraphs(process):
-    """
-    For each thread, collapse all callgraph into one flamegraph.
-    :param process:  Process object
-    :return: None
-    """
-    for _, thread in process.threads.items():
-        thread.collapse_flamegraph()
+    log_info("Parsed %s callchains." % process.num_samples)
 
 
 def get_local_asset_content(local_path):
@@ -160,149 +129,174 @@
     :param local_path: str, filename of local asset
     :return: str, the content of local_path
     """
-    f = open(os.path.join(os.path.dirname(__file__), local_path), 'r')
-    content = f.read()
-    f.close()
-    return content
+    with open(os.path.join(os.path.dirname(__file__), local_path), 'r') as f:
+        return f.read()
 
 
-def output_report(process):
+def output_report(process, args):
     """
     Generates a HTML report representing the result of simpleperf sampling as flamegraph
     :param process: Process object
     :return: str, absolute path to the file
     """
-    f = open('report.html', 'w')
+    f = open(args.report_path, 'w')
     filepath = os.path.realpath(f.name)
     f.write("<html>")
+    f.write("<head>")
+    f.write("""<style type="text/css">""")
+    f.write(get_local_asset_content(os.path.join("jqueryui", "jquery-ui.min.css")))
+    f.write("</style>")
+    f.write("</head>")
     f.write("<body style='font-family: Monospace;' onload='init()'>")
-    f.write('<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;} </style>')
+    f.write("""<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;}
+            </style>""")
     f.write('<style type="text/css"> .t:hover { cursor:pointer; } </style>')
     f.write('<img height="180" alt = "Embedded Image" src ="data')
     f.write(get_local_asset_content("inferno.b64"))
     f.write('"/>')
-    f.write("<div style='display:inline-block;'> \
-    <font size='8'>\
-    Inferno Flamegraph Report</font><br/><br/> \
-    Process : %s (%d)<br/>\
-    Date&nbsp;&nbsp;&nbsp;&nbsp;: %s<br/>\
-    Threads : %d <br/>\
-    Samples : %d</br>\
-    Duration: %s seconds<br/>\
-    Machine : %s (%s) by %s<br/>\
-    Capture : %s<br/><br/></div>"
-            % (
-                process.name,process.pid,
-                datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"),
-                len(process.threads),
-                process.num_samples,
-                process.args.capture_duration,
-                process.props["ro.product.model"], process.props["ro.product.name"],
-                process.props["ro.product.manufacturer"],
-                process.cmd))
-    f.write("<br/><br/><div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>")
-    f.write(get_local_asset_content("script.js"))
+    process_entry = ("Process : %s (%d)<br/>" % (process.name, process.pid)) if process.pid else ""
+    # TODO: collect capture duration info from perf.data.
+    duration_entry = ("Duration: %s seconds<br/>" % args.capture_duration
+                      ) if args.capture_duration else ""
+    f.write("""<div style='display:inline-block;'>
+                  <font size='8'>
+                  Inferno Flamegraph Report</font><br/><br/>
+                  %s
+                  Date&nbsp;&nbsp;&nbsp;&nbsp;: %s<br/>
+                  Threads : %d <br/>
+                  Samples : %d</br>
+                  %s""" % (
+        process_entry,
+        datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"),
+        len(process.threads),
+        process.num_samples,
+        duration_entry))
+    if 'ro.product.model' in process.props:
+        f.write(
+            "Machine : %s (%s) by %s<br/>" %
+            (process.props["ro.product.model"],
+             process.props["ro.product.name"],
+             process.props["ro.product.manufacturer"]))
+    if process.cmd:
+        f.write("Capture : %s<br/><br/>" % process.cmd)
+    f.write("</div>")
+    f.write("""<br/><br/>
+            <div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>""")
+    f.write("<script>")
+    f.write(get_local_asset_content(os.path.join("jqueryui", "jquery-3.2.1.min.js")))
+    f.write(get_local_asset_content(os.path.join("jqueryui", "jquery-ui.min.js")))
+    f.write("</script>")
+    f.write("<script>%s</script>" % get_local_asset_content("script.js"))
 
     # Output tid == pid Thread first.
-    main_thread = [x for _, x in process.threads.items() if x.tid == process.pid]
+    main_thread = [x for x in process.threads.values() if x.tid == process.pid]
     for thread in main_thread:
-        f.write("<br/><br/><b>Main Thread %d (%d samples):</b><br/>\n\n\n\n" % (thread.tid, thread.num_samples))
-        renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width)
+        f.write("<br/><br/><b>Main Thread %d (%s) (%d samples):</b><br/>\n\n\n\n" % (
+                thread.tid, thread.name, thread.num_samples))
+        renderSVG(thread.flamegraph, f, args.color, args.svg_width)
 
-    other_threads = [x for _, x in process.threads.items() if x.tid != process.pid]
+    other_threads = [x for x in process.threads.values() if x.tid != process.pid]
     for thread in other_threads:
-        f.write("<br/><br/><b>Thread %d (%d samples):</b><br/>\n\n\n\n" % (thread.tid, thread.num_samples))
-        renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width)
+        f.write("<br/><br/><b>Thread %d (%s) (%d samples):</b><br/>\n\n\n\n" % (
+                thread.tid, thread.name, thread.num_samples))
+        renderSVG(thread.flamegraph, f, args.color, args.svg_width)
 
     f.write("</body>")
     f.write("</html>")
     f.close()
     return "file://" + filepath
 
-def generate_flamegraph_offsets(flamegraph):
-    rover = flamegraph.offset
-    for callsite in flamegraph.callsites:
-        callsite.offset = rover
-        rover += callsite.num_samples
-        generate_flamegraph_offsets(callsite)
-
 
 def generate_threads_offsets(process):
-    for _, thread in process.threads.items():
-        generate_flamegraph_offsets(thread.flamegraph)
+    for thread in process.threads.values():
+       thread.flamegraph.generate_offset(0)
 
 
-def collect_machine_info(adb_client, process):
-    process.props = adb_client.get_props()
+def collect_machine_info(process):
+    adb = AdbHelper()
+    process.props = {}
+    process.props['ro.product.model'] = adb.get_property('ro.product.model')
+    process.props['ro.product.name'] = adb.get_property('ro.product.name')
+    process.props['ro.product.manufacturer'] = adb.get_property('ro.product.manufacturer')
 
 
-def setup_adb():
-    err = subprocess.call(["adb", "root"])
-    if err == 0:
-        return AdbRoot()
-    else:
-        return AdbNonRoot()
-
 def open_report_in_browser(report_path):
-    # Try to open the report with Chrome
-    browser_key = ""
-    for key, value in webbrowser._browsers.items():
-        if key.find("chrome") != -1:
-           browser_key = key
-    browser = webbrowser.get(browser_key)
-    browser.open(report_path, new=0, autoraise=True)
-
+    try:
+        # Try to open the report with Chrome
+        browser_key = ""
+        for key, value in webbrowser._browsers.items():
+            if key.find("chrome") != -1:
+                browser_key = key
+        browser = webbrowser.get(browser_key)
+        browser.open(report_path, new=0, autoraise=True)
+    except:
+        # webbrowser.get() doesn't work well on darwin/windows.
+        webbrowser.open_new_tab(report_path)
 
 
 def main():
 
     parser = argparse.ArgumentParser(description='Report samples in perf.data.')
-    parser.add_argument('--symfs', help='Set the path to find binaries with symbols and debug info.')
+    parser.add_argument('--symfs', help="""Set the path to find binaries with symbols and debug
+                        info.""")
     parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.')
     parser.add_argument('--record_file', default='perf.data', help='Default is perf.data.')
-    parser.add_argument('-t', '--capture_duration', default=10, help='Capture duration in seconds.')
-    parser.add_argument('-p', '--process_name', default='surfaceflinger', help='Default is surfaceflinger.')
+    parser.add_argument('-t', '--capture_duration', type=int, default=10,
+                        help="""Capture duration in seconds.""")
+    parser.add_argument('-p', '--app', help="""Profile an Android app, given the package name.
+                        Like -p com.example.android.myapp.""")
+    parser.add_argument('-np', '--native_program', default="surfaceflinger",
+                        help="""Profile a native program. The program should be running on the
+                        device. Like -np surfaceflinger.""")
     parser.add_argument('-c', '--color', default='hot', choices=['hot', 'dso', 'legacy'],
-                        help='Color theme: hot=percentage of samples, dso=callsite DSO name, legacy=brendan style')
-    parser.add_argument('-sc','--skip_collection', default=False, help='Skip data collection', action="store_true")
-    parser.add_argument('-f', '--sample_frequency', default=6000, help='Sample frequency')
+                        help="""Color theme: hot=percentage of samples, dso=callsite DSO name,
+                        legacy=brendan style""")
+    parser.add_argument('-sc', '--skip_collection', default=False, help='Skip data collection',
+                        action="store_true")
+    parser.add_argument('-nc', '--skip_recompile', action='store_true', help="""When profiling
+                        an Android app, by default we recompile java bytecode to native
+                        instructions to profile java code. It takes some time. You can skip it
+                        if the code has been compiled or you don't need to profile java code.""")
+    parser.add_argument('-f', '--sample_frequency', type=int, default=6000, help='Sample frequency')
     parser.add_argument('-w', '--svg_width', type=int, default=1124)
-    parser.add_argument('-sb', '--skip_push_binary', help='Skip pushing simpleperf before profiling',
-                        default=False, action="store_true")
-    parser.add_argument('-du', '--dwarf_unwinding', help='Perform unwinding using dwarf instead of fp.',
-                        default=False, action='store_true')
-    parser.add_argument('-e', '--events',
-                        help='Sample based on event occurences instead of frequency. '
-                             'Format expected is "event_counts event_name". e.g: "10000 cpu-cyles". A few examples of \
-                              nmames: cpu-cycles, cache-references, cache-misses, branch-instructions, branch-misses',
+    parser.add_argument(
+        '-du',
+        '--dwarf_unwinding',
+        help='Perform unwinding using dwarf instead of fp.',
+        default=False,
+        action='store_true')
+    parser.add_argument('-e', '--events', help="""Sample based on event occurences instead of
+                        frequency. Format expected is "event_counts event_name".
+                        e.g: "10000 cpu-cyles". A few examples of event_name: cpu-cycles,
+                        cache-references, cache-misses, branch-instructions, branch-misses""",
                         default="")
+    parser.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run in non
+                        root mode.""")
+    parser.add_argument('-o', '--report_path', default='report.html', help="Set report path.")
     args = parser.parse_args()
+    process = Process("", 0)
 
-    # Since we may attempt to sample privileged process, let's try to be root.
-    adb_client = setup_adb()
+    if not args.skip_collection:
+        process.name = args.app or args.native_program
+        log_info("Starting data collection stage for process '%s'." % process.name)
+        if not collect_data(args):
+            log_exit("Unable to collect data.")
+        result, output = AdbHelper().run_and_return_output(['shell', 'pidof', process.name])
+        if result:
+            try:
+                process.pid = int(output)
+            except:
+                process.pid = 0
+        collect_machine_info(process)
+    else:
+        args.capture_duration = 0
 
-    # Create a process object
-    process = create_process(adb_client, args)
-    if process.pid == 0:
-        print("Unable to retrive pid for process '%s'. Terminating." % process.name)
-        return
-    process.args = args
-
-
-    print("Starting data collection stage for process '%s'." % args.process_name)
-    success = collect_data(adb_client, process)
-    if not success:
-        print "Unable to collect data"
-        return
-
-    collect_machine_info(adb_client, process)
     parse_samples(process, args)
-    collapse_callgraphs(process)
     generate_threads_offsets(process)
-    report_path = output_report(process)
+    report_path = output_report(process, args)
     open_report_in_browser(report_path)
 
-    print "Report generated at '%s'." % report_path
+    log_info("Report generated at '%s'." % report_path)
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/simpleperf/scripts/inferno/jqueryui/LICENSE.txt b/simpleperf/scripts/inferno/jqueryui/LICENSE.txt
new file mode 100644
index 0000000..4819e54
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/LICENSE.txt
@@ -0,0 +1,43 @@
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/jquery/jquery-ui
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code contained within the demos directory.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+All files located in the node_modules and external directories are
+externally maintained libraries used by this software which have their
+own licenses; we recommend you read them, as their terms may differ from
+the terms above.
diff --git a/simpleperf/scripts/inferno/jqueryui/jquery-3.2.1.min.js b/simpleperf/scripts/inferno/jqueryui/jquery-3.2.1.min.js
new file mode 100644
index 0000000..644d35e
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/jquery-3.2.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,N,e),g(f,c,O,e)):(f++,j.call(a,g(f,c,N,e),g(f,c,O,e),g(f,c,N,c.notifyWith))):(d!==N&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S),
+a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")([a-z%]*)$","i"),ca=["Top","Right","Bottom","Left"],da=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},ea=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function fa(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&ba.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ga={};function ha(a){var b,c=a.ownerDocument,d=a.nodeName,e=ga[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),ga[d]=e,e)}function ia(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=W.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&da(d)&&(e[f]=ha(d))):"none"!==c&&(e[f]="none",W.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ia(this,!0)},hide:function(){return ia(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){da(this)?r(this).show():r(this).hide()})}});var ja=/^(?:checkbox|radio)$/i,ka=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c<d;c++)W.set(a[c],"globalEval",!b||W.get(b[c],"globalEval"))}var pa=/<|&#?\w+;/;function qa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(pa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ka.exec(f)||["",""])[1].toLowerCase(),i=ma[h]||ma._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==xa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===xa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&B(this,"input"))return this.click(),!1},_default:function(a){return B(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?va:wa,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:wa,isPropagationStopped:wa,isImmediatePropagationStopped:wa,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=va,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=va,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=va,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&sa.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&ta.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return ya(this,a,b,c,d)},one:function(a,b,c,d){return ya(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=wa),this.each(function(){r.event.remove(this,a,c,b)})}});var za=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/<script|<style|<link/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,Ca=/^true\/(.*)/,Da=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}X.hasData(a)&&(h=X.access(a),i=r.extend({},h),X.set(b,i))}}function Ia(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ja.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ja(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,na(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Ga),l=0;l<i;l++)j=h[l],la.test(j.type||"")&&!W.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Da,""),k))}return a}function Ka(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(na(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&oa(na(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(za,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d<e;d++)Ia(f[d],g[d]);if(b)if(c)for(f=f||na(a),g=g||na(h),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);else Ha(a,h);return g=na(h,"script"),g.length>0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(na(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ja(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(na(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var La=/^margin/,Ma=new RegExp("^("+aa+")(?!px)[a-z%]+$","i"),Na=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",ra.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,ra.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Oa(a,b,c){var d,e,f,g,h=a.style;return c=c||Na(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&Ma.test(g)&&La.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Pa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Qa=/^(none|table(?!-c[ea]).+)/,Ra=/^--/,Sa={position:"absolute",visibility:"hidden",display:"block"},Ta={letterSpacing:"0",fontWeight:"400"},Ua=["Webkit","Moz","ms"],Va=d.createElement("div").style;function Wa(a){if(a in Va)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ua.length;while(c--)if(a=Ua[c]+b,a in Va)return a}function Xa(a){var b=r.cssProps[a];return b||(b=r.cssProps[a]=Wa(a)||a),b}function Ya(a,b,c){var d=ba.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Za(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ca[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ca[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ca[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ca[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ca[f]+"Width",!0,e)));return g}function $a(a,b,c){var d,e=Na(a),f=Oa(a,b,e),g="border-box"===r.css(a,"boxSizing",!1,e);return Ma.test(f)?f:(d=g&&(o.boxSizingReliable()||f===a.style[b]),"auto"===f&&(f=a["offset"+b[0].toUpperCase()+b.slice(1)]),f=parseFloat(f)||0,f+Za(a,b,c||(g?"border":"content"),d,e)+"px")}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Oa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=Ra.test(b),j=a.style;return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:j[b]:(f=typeof c,"string"===f&&(e=ba.exec(c))&&e[1]&&(c=fa(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(j[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i?j.setProperty(b,c):j[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b),i=Ra.test(b);return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Oa(a,b,d)),"normal"===e&&b in Ta&&(e=Ta[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Qa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?$a(a,b,d):ea(a,Sa,function(){return $a(a,b,d)})},set:function(a,c,d){var e,f=d&&Na(a),g=d&&Za(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=ba.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Ya(a,c,g)}}}),r.cssHooks.marginLeft=Pa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Oa(a,"marginLeft"))||a.getBoundingClientRect().left-ea(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ca[d]+b]=f[d]||f[d-2]||f[0];return e}},La.test(a)||(r.cssHooks[a+b].set=Ya)}),r.fn.extend({css:function(a,b){return T(this,function(a,b,c){var d,e,f={},g=0;if(Array.isArray(b)){for(d=Na(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&da(a),q=W.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],cb.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=W.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ia([a],!0),j=a.style.display||j,k=r.css(a,"display"),ia([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=W.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ia([a],!0),m.done(function(){p||ia([a]),W.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=hb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],Array.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=kb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=ab||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(i||h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:ab||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);f<g;f++)if(d=kb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,hb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j}r.Animation=r.extend(kb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return fa(c.elem,a,ba.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(L);for(var c,d=0,e=a.length;d<e;d++)c=a[d],kb.tweeners[c]=kb.tweeners[c]||[],kb.tweeners[c].unshift(b)},prefilters:[ib],prefilter:function(a,b){b?kb.prefilters.unshift(a):kb.prefilters.push(a)}}),r.speed=function(a,b,c){var d=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off?d.duration=0:"number"!=typeof d.duration&&(d.duration in r.fx.speeds?d.duration=r.fx.speeds[d.duration]:d.duration=r.fx.speeds._default),null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){r.isFunction(d.old)&&d.old.call(this),d.queue&&r.dequeue(this,d.queue)},d},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(da).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=kb(this,r.extend({},a),f);(e||W.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=W.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&db.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=W.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),r.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(ab=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),ab=void 0},r.fx.timer=function(a){r.timers.push(a),r.fx.start()},r.fx.interval=13,r.fx.start=function(){bb||(bb=!0,eb())},r.fx.stop=function(){bb=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var lb,mb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return T(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),
+null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!B(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Tb=[],Ub=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Tb.pop()||r.expando+"_"+ub++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Ub.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ub.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Ub,"$1"+e):b.jsonp!==!1&&(b.url+=(vb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Tb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=C.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=qa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=pb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length},r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),b=f.ownerDocument,c=b.documentElement,e=b.defaultView,{top:d.top+e.pageYOffset-c.clientTop,left:d.left+e.pageXOffset-c.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),B(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||ra})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return T(this,function(a,d,e){var f;return r.isWindow(a)?f=a:9===a.nodeType&&(f=a.defaultView),void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Pa(o.pixelPosition,function(a,c){if(c)return c=Oa(a,b),Ma.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return T(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.holdReady=function(a){a?r.readyWait++:r.ready(!0)},r.isArray=Array.isArray,r.parseJSON=JSON.parse,r.nodeName=B,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Vb=a.jQuery,Wb=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Wb),b&&a.jQuery===r&&(a.jQuery=Vb),r},b||(a.jQuery=a.$=r),r});
diff --git a/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.css b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.css
new file mode 100644
index 0000000..22ca1d3
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.css
@@ -0,0 +1,7 @@
+/*! jQuery UI - v1.12.1 - 2017-08-18
+* http://jqueryui.com
+* Includes: core.css, resizable.css, theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666}
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.js b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.js
new file mode 100644
index 0000000..a26b9c0
--- /dev/null
+++ b/simpleperf/scripts/inferno/jqueryui/jquery-ui.min.js
@@ -0,0 +1,6 @@
+/*! jQuery UI - v1.12.1 - 2017-08-18
+* http://jqueryui.com
+* Includes: widget.js, disable-selection.js, widgets/resizable.js, widgets/mouse.js
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1";var e=0,i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},l=e.split(".")[0];e=e.split(".")[1];var h=l+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][h.toLowerCase()]=function(e){return!!t.data(e,h)},t[l]=t[l]||{},n=t[l][e],o=t[l][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:l,widgetName:e,widgetFullName:h}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var s,n,o=i.call(arguments,1),a=0,r=o.length;r>a;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}}),t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var s=!1;t(document).on("mouseup",function(){s=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,n=1===e.which,o="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return n&&!o&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;o.length>n;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}},t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;default:}},_setupHandles:function(){var e,i,s,n,o,a=this.options,r=this;if(this.handles=a.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;s.length>i;i++)e=t.trim(s[i]),n="ui-resizable-"+e,o=t("<div>"),this._addClass(o,"ui-resizable-handle "+n),o.css({zIndex:a.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(o);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:r._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){r.resizing||(this.className&&(o=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),r.axis=o&&o[1]?o[1]:"se")}),a.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(e){var i,s,n,o=this.options,a=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),o.containment&&(i+=t(o.containment).scrollLeft()||0,s+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===n?this.axis+"-resize":n),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,n=this.originalMousePosition,o=this.axis,a=e.pageX-n.left||0,r=e.pageY-n.top||0,l=this._change[o];return this._updatePrevProperties(),l?(i=l.apply(this,[e,a,r]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,l,h=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,l=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,h.animate||this.element.css(t.extend(a,{top:l,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!h.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,n,o,a=this.options;o={minWidth:this._isNumber(a.minWidth)?a.minWidth:0,maxWidth:this._isNumber(a.maxWidth)?a.maxWidth:1/0,minHeight:this._isNumber(a.minHeight)?a.minHeight:0,maxHeight:this._isNumber(a.maxHeight)?a.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,s=o.minWidth/this.aspectRatio,i=o.maxHeight*this.aspectRatio,n=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),s>o.minHeight&&(o.minHeight=s),o.maxWidth>i&&(o.maxWidth=i),o.maxHeight>n&&(o.maxHeight=n)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,n=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,o=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,l=this.originalPosition.top+this.originalSize.height,h=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&h&&(t.left=r-e.minWidth),s&&h&&(t.left=r-e.maxWidth),a&&c&&(t.top=l-e.minHeight),n&&c&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];4>e;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;this._proportionallyResizeElements.length>e;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,l={width:i.size.width-r,height:i.size.height-a},h=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(l,c&&h?{top:c,left:h}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,n,o,a,r,l=t(this).resizable("instance"),h=l.options,c=l.element,u=h.containment,d=u instanceof t?u.get(0):/parent/.test(u)?c.parent().get(0):u;d&&(l.containerElement=t(d),/document/.test(u)||u===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(d),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=l._num(e.css("padding"+s))}),l.containerOffset=e.offset(),l.containerPosition=e.position(),l.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=l.containerOffset,n=l.containerSize.height,o=l.containerSize.width,a=l._hasScroll(d,"left")?d.scrollWidth:o,r=l._hasScroll(d)?d.scrollHeight:n,l.parentData={element:d,left:s.left,top:s.top,width:a,height:r}))},resize:function(e){var i,s,n,o,a=t(this).resizable("instance"),r=a.options,l=a.containerOffset,h=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement,p=!0;d[0]!==document&&/static/.test(d.css("position"))&&(u=l),h.left<(a._helper?l.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-l.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio,p=!1),a.position.left=r.helper?l.left:0),h.top<(a._helper?l.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-l.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio,p=!1),a.position.top=a._helper?l.top:0),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o?(a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top):(a.offset.left=a.element.offset().left,a.offset.top=a.element.offset().top),i=Math.abs(a.sizeDiff.width+(a._helper?a.offset.left-u.left:a.offset.left-l.left)),s=Math.abs(a.sizeDiff.height+(a._helper?a.offset.top-u.top:a.offset.top-l.top)),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio,p=!1)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio,p=!1)),p||(a.position.left=a.prevPosition.left,a.position.top=a.prevPosition.top,a.size.width=a.prevSize.width,a.size.height=a.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),l=a.outerWidth()-e.sizeDiff.width,h=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:l,height:h}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:l,height:h})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0};t(n.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),n={},o=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(s[e]||0)+(r[e]||0);i&&i>=0&&(n[e]=i||null)}),e.css(n)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),t.uiBackCompat!==!1&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,n=i.size,o=i.originalSize,a=i.originalPosition,r=i.axis,l="number"==typeof s.grid?[s.grid,s.grid]:s.grid,h=l[0]||1,c=l[1]||1,u=Math.round((n.width-o.width)/h)*h,d=Math.round((n.height-o.height)/c)*c,p=o.width+u,f=o.height+d,g=s.maxWidth&&p>s.maxWidth,m=s.maxHeight&&f>s.maxHeight,_=s.minWidth&&s.minWidth>p,v=s.minHeight&&s.minHeight>f;s.grid=l,_&&(p+=h),v&&(f+=c),g&&(p-=h),m&&(f-=c),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=a.top-d):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=a.left-u):((0>=f-c||0>=p-h)&&(e=i._getPaddingPlusBorderDimensions(this)),f-c>0?(i.size.height=f,i.position.top=a.top-d):(f=c-e.height,i.size.height=f,i.position.top=a.top+o.height-f),p-h>0?(i.size.width=p,i.position.left=a.left-u):(p=h-e.width,i.size.width=p,i.position.left=a.left+o.width-p))}}),t.ui.resizable});
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/script.js b/simpleperf/scripts/inferno/script.js
index 6a81d73..834504a 100644
--- a/simpleperf/scripts/inferno/script.js
+++ b/simpleperf/scripts/inferno/script.js
@@ -1,146 +1,142 @@
-<script type="text/ecmascript">
+'use strict';
+
 function init() {
-  var x = document.getElementsByTagName("svg")
-  for (i = 0; i < x.length; i=i+1) {
+  let x = document.getElementsByTagName('svg');
+  for (let i = 0; i < x.length; i++) {
       createZoomHistoryStack(x[i]);
   }
 }
 
 // Create a stack add the root svg element in it.
 function createZoomHistoryStack(svgElement) {
-  stack = [];
-  svgElement.zoomStack = stack;
-  stack.push(svgElement.getElementById(svgElement.attributes["rootid"].value))
+  svgElement.zoomStack = [svgElement.getElementById(svgElement.attributes['rootid'].value)];
 }
 
 function dumpStack(svgElement) {
   // Disable (enable for debugging)
-  return
+  return;
   stack = svgElement.zoomStack;
-  for (i=0 ; i < stack.length; i++) {
-    title = stack[i].getElementsByTagName("title")[0];
-    console.log("[" +i+ "]-" + title.textContent)
+  for (i=0; i < stack.length; i++) {
+    let title = stack[i].getElementsByTagName('title')[0];
+    console.log('[' +i+ ']-' + title.textContent);
   }
 }
 
-function adjust_node_text_size(x) {
-  title = x.getElementsByTagName("title")[0];
-  text = x.getElementsByTagName("text")[0];
-  rect = x.getElementsByTagName("rect")[0];
+function adjust_node_text_size(x, svgWidth) {
+  let title = x.getElementsByTagName('title')[0];
+  let text = x.getElementsByTagName('text')[0];
+  let rect = x.getElementsByTagName('rect')[0];
 
-  width = parseFloat(rect.attributes["width"].value);
+  let width = parseFloat(rect.attributes['width'].value) * svgWidth * 0.01;
 
   // Don't even bother trying to find a best fit. The area is too small.
-  if (width < 25) {
-      text.textContent = "";
+  if (width < 28) {
+      text.textContent = '';
       return;
   }
   // Remove dso and #samples which are here only for mouseover purposes.
-  methodName = title.textContent.substring(0, title.textContent.indexOf("|"));
+  let methodName = title.textContent.split(' | ')[0];
 
-  var numCharacters;
-  for (numCharacters=methodName.length; numCharacters>4; numCharacters--) {
+  let numCharacters;
+  for (numCharacters = methodName.length; numCharacters > 4; numCharacters--) {
      // Avoid reflow by using hard-coded estimate instead of text.getSubStringLength(0, numCharacters)
      // if (text.getSubStringLength(0, numCharacters) <= width) {
      if (numCharacters * 7.5 <= width) {
-       break ;
+       break;
      }
   }
 
   if (numCharacters == methodName.length) {
     text.textContent = methodName;
-    return
+    return;
   }
 
-  text.textContent = methodName.substring(0, numCharacters-2) + "..";
+  text.textContent = methodName.substring(0, numCharacters-2) + '..';
  }
 
 function adjust_text_size(svgElement) {
-  var x = svgElement.getElementsByTagName("g");
-  var i;
-  for (i=0 ; i < x.length ; i=i+1) {
-    adjust_node_text_size(x[i])
+  let svgWidth = $(svgElement).parent().width();
+  let x = svgElement.getElementsByTagName('g');
+  for (let i = 0; i < x.length; i++) {
+    adjust_node_text_size(x[i], svgWidth);
   }
 }
 
 function zoom(e) {
-  svgElement = e.ownerSVGElement
-  zoomStack = svgElement.zoomStack;
+  let svgElement = e.ownerSVGElement;
+  let zoomStack = svgElement.zoomStack;
   zoomStack.push(e);
-  displayFromElement(e)
+  displayFromElement(e);
   select(e);
-  dumpStack(e.ownerSVGElement);
+  dumpStack(svgElement);
 
   // Show zoom out button.
-  svgElement.getElementById("zoom_rect").style.display = "block";
-  svgElement.getElementById("zoom_text").style.display = "block";
+  svgElement.getElementById('zoom_rect').style.display = 'block';
+  svgElement.getElementById('zoom_text').style.display = 'block';
 }
 
 function displayFromElement(e) {
-  var clicked_rect = e.getElementsByTagName("rect")[0];
-  var clicked_origin_x = clicked_rect.attributes["ox"].value;
-  var clicked_origin_y = clicked_rect.attributes["oy"].value;
-  var clicked_origin_width = clicked_rect.attributes["owidth"].value;
+  let clicked_rect = e.getElementsByTagName('rect')[0];
+  let clicked_origin_x = clicked_rect.attributes['ox'].value;
+  let clicked_origin_y = clicked_rect.attributes['oy'].value;
+  let clicked_origin_width = clicked_rect.attributes['owidth'].value;
 
 
-  var svgBox = e.ownerSVGElement.getBoundingClientRect();
-  var svgBoxHeight = svgBox.height
-  var svgBoxWidth = svgBox.width
-  var scaleFactor = svgBoxWidth/clicked_origin_width;
+  let svgBox = e.ownerSVGElement.getBoundingClientRect();
+  let svgBoxHeight = svgBox.height;
+  let svgBoxWidth = 100;
+  let scaleFactor = svgBoxWidth / clicked_origin_width;
 
-  var callsites = e.ownerSVGElement.getElementsByTagName("g");
-  var i;
-  for (i = 0; i < callsites.length; i=i+1) {
-    text = callsites[i].getElementsByTagName("text")[0];
-    rect = callsites[i].getElementsByTagName("rect")[0];
+  let callsites = e.ownerSVGElement.getElementsByTagName('g');
+  for (let i = 0; i < callsites.length; i++) {
+    let text = callsites[i].getElementsByTagName('text')[0];
+    let rect = callsites[i].getElementsByTagName('rect')[0];
 
-    rect_o_x = rect.attributes["ox"].value
-    rect_o_y = parseFloat(rect.attributes["oy"].value)
+    let rect_o_x = parseFloat(rect.attributes['ox'].value);
+    let rect_o_y = parseFloat(rect.attributes['oy'].value);
 
     // Avoid multiple forced reflow by hiding nodes.
     if (rect_o_y > clicked_origin_y) {
-     rect.style.display = "none"
-     text.style.display = "none"
-     continue;
-    } else {
-     rect.style.display = "block"
-     text.style.display = "block"
+      rect.style.display = 'none';
+      text.style.display = 'none';
+      continue;
     }
+    rect.style.display = 'block';
+    text.style.display = 'block';
 
-    rect.attributes["x"].value = newrec_x = (rect_o_x - clicked_origin_x) * scaleFactor ;
-    rect.attributes["y"].value = newrec_y = rect_o_y + (svgBoxHeight - clicked_origin_y - 17 -2);
+    let newrec_x = rect.attributes['x'].value = (rect_o_x - clicked_origin_x) * scaleFactor + "%";
+    let newrec_y = rect.attributes['y'].value = rect_o_y + (svgBoxHeight - clicked_origin_y
+                                                            - 17 - 2);
 
-    text.attributes["y"].value = newrec_y + 12;
-    text.attributes["x"].value = newrec_x + 4;
+    text.attributes['y'].value = newrec_y + 12;
+    text.attributes['x'].value = newrec_x;
 
-    rect.attributes["width"].value = rect.attributes["owidth"].value * scaleFactor;
+    rect.attributes['width'].value = (rect.attributes['owidth'].value * scaleFactor) + "%";
   }
 
   adjust_text_size(e.ownerSVGElement);
-
 }
 
 function unzoom(e) {
-
-  var svgOwner = e.ownerSVGElement;
-  stack = svgOwner.zoomStack;
+  let svgOwner = e.ownerSVGElement;
+  let stack = svgOwner.zoomStack;
 
   // Unhighlight whatever was selected.
-  if (selected != null)
-    selected.classList.remove("s")
-
+  if (selected) {
+    selected.classList.remove('s');
+  }
 
   // Stack management: Never remove the last element which is the flamegraph root.
   if (stack.length > 1) {
-    previouslySelected = stack.pop();
+    let previouslySelected = stack.pop();
     select(previouslySelected);
   }
-  nextElement = stack[stack.length-1] // stack.peek()
+  let nextElement = stack[stack.length-1];
 
   // Hide zoom out button.
-  if (stack.length==1) {
-    svgOwner.getElementById("zoom_rect").style.display = "none";
-    svgOwner.getElementById("zoom_text").style.display = "none";
+  if (stack.length == 1) {
+    svgOwner.getElementById('zoom_rect').style.display = 'none';
+    svgOwner.getElementById('zoom_text').style.display = 'none';
   }
 
   displayFromElement(nextElement);
@@ -148,97 +144,105 @@
 }
 
 function search(e) {
-  var term = prompt("Search for:", "");
+  let term = prompt('Search for:', '');
+  let svgOwner = e.ownerSVGElement;
+  let callsites = e.ownerSVGElement.getElementsByTagName('g');
 
-  var svgOwner = e.ownerSVGElement
-  var callsites = e.ownerSVGElement.getElementsByTagName("g");
-
-  if (term == null || term == "") {
-    for (i = 0; i < callsites.length; i=i+1) {
-      rect = callsites[i].getElementsByTagName("rect")[0];
-      rect.attributes["fill"].value = rect.attributes["ofill"].value;
+  if (!term) {
+    for (let i = 0; i < callsites.length; i++) {
+      let rect = callsites[i].getElementsByTagName('rect')[0];
+      rect.attributes['fill'].value = rect.attributes['ofill'].value;
     }
     return;
   }
 
-  for (i = 0; i < callsites.length; i=i+1) {
-    title = callsites[i].getElementsByTagName("title")[0];
-    rect = callsites[i].getElementsByTagName("rect")[0];
+  for (let i = 0; i < callsites.length; i++) {
+    let title = callsites[i].getElementsByTagName('title')[0];
+    let rect = callsites[i].getElementsByTagName('rect')[0];
     if (title.textContent.indexOf(term) != -1) {
-      rect.attributes["fill"].value = "rgb(230,100,230)";
+      rect.attributes['fill'].value = 'rgb(230,100,230)';
     } else {
-      rect.attributes["fill"].value = rect.attributes["ofill"].value;
+      rect.attributes['fill'].value = rect.attributes['ofill'].value;
     }
   }
 }
 
-var selected;
+let selected;
 document.onkeydown = function handle_keyboard_input(e) {
-  if (selected == null)
+  if (!selected) {
      return;
+  }
 
-  title = selected.getElementsByTagName("title")[0];
-  nav = selected.attributes["nav"].value.split(",")
-  navigation_index = -1
+  let title = selected.getElementsByTagName('title')[0];
+  let nav = selected.attributes['nav'].value.split(',');
+  let navigation_index;
   switch (e.keyCode) {
-     //case 38: // ARROW UP
-     case 87 : navigation_index = 0;break; //W
+     // case 38: // ARROW UP
+     case 87: navigation_index = 0; break; // W
 
-     //case 32 : // ARROW LEFT
-     case 65 : navigation_index = 1;break; //A
+     // case 32 : // ARROW LEFT
+     case 65: navigation_index = 1; break; // A
 
      // case 43: // ARROW DOWN
-     case 68 : navigation_index = 3;break; // S
+     case 68: navigation_index = 3; break; // S
 
      // case 39: // ARROW RIGHT
-     case 83 : navigation_index = 2;break; // D
+     case 83: navigation_index = 2; break; // D
 
-     case 32 : zoom(selected); return false; break; // SPACE
+     case 32: zoom(selected); return false; // SPACE
 
      case 8: // BACKSPACE
           unzoom(selected); return false;
      default: return true;
   }
 
-  if (nav[navigation_index] == "0")
+  if (nav[navigation_index] == '0') {
     return false;
+  }
 
-  target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]);
-  select(target_element)
+  let target_element = selected.ownerSVGElement.getElementById(nav[navigation_index]);
+  select(target_element);
   return false;
-}
+};
 
 function select(e) {
-  if (selected != null)
-    selected.classList.remove("s")
+  if (selected) {
+    selected.classList.remove('s');
+  }
   selected = e;
-  selected.classList.add("s")
+  selected.classList.add('s');
 
   // Update info bar
-  titleElement = selected.getElementsByTagName("title")[0];
-  text = titleElement.textContent;
+  let titleElement = selected.getElementsByTagName('title')[0];
+  let text = titleElement.textContent;
 
   // Parse title
-  method_and_info = text.split(" | ");
-  methodName = method_and_info[0];
-  info =  method_and_info[1]
+  let method_and_info = text.split(' | ');
+  let methodName = method_and_info[0];
+  let info = method_and_info[1];
 
   // Parse info
   // '/system/lib64/libhwbinder.so (4 samples: 0.28%)'
-  var regexp = /(.*) \(.* ([0-9**\.[0-9]*%)\)/g;
-  match = regexp.exec(info);
+  let regexp = /(.*) \(.* ([0-9**\.[0-9]*%)\)/g;
+  let match = regexp.exec(info);
   if (match.length > 2) {
-    percentage = match[2]
+    let percentage = match[2];
     // Write percentage
-    percentageTextElement = selected.ownerSVGElement.getElementById("percent_text")
-    percentageTextElement.textContent = percentage
-    //console.log("'" + percentage + "'")
+    let percentageTextElement = selected.ownerSVGElement.getElementById('percent_text');
+    percentageTextElement.textContent = percentage;
+    // console.log("'" + percentage + "'")
   }
 
   // Set fields
-  barTextElement = selected.ownerSVGElement.getElementById("info_text")
-  barTextElement.textContent = methodName
+  let barTextElement = selected.ownerSVGElement.getElementById('info_text');
+  barTextElement.textContent = methodName;
 }
 
-
-</script>
\ No newline at end of file
+$(document).ready(function() {
+  $(".flamegraph_block").resizable({
+    handles: "e",
+    resize: function(event, ui) {
+      adjust_text_size(ui.element.find("svg")[0]);
+    }
+  });
+});
\ No newline at end of file
diff --git a/simpleperf/scripts/inferno/svg_renderer.py b/simpleperf/scripts/inferno/svg_renderer.py
index 00f2d75..3950572 100644
--- a/simpleperf/scripts/inferno/svg_renderer.py
+++ b/simpleperf/scripts/inferno/svg_renderer.py
@@ -22,36 +22,37 @@
 
 
 def hash_to_float(string):
-    return hash(string) / float(sys.maxint)
+    return hash(string) / float(sys.maxsize)
 
-def getLegacyColor(method) :
+
+def getLegacyColor(method):
     r = 175 + int(50 * hash_to_float(reversed(method)))
     g = 60 + int(180 * hash_to_float(method))
-    b = 60 +int(55 * hash_to_float(reversed(method)))
-    return (r,g,b)
+    b = 60 + int(55 * hash_to_float(reversed(method)))
+    return (r, g, b)
 
 
-def getDSOColor(method) :
+def getDSOColor(method):
     r = 170 + int(80 * hash_to_float(reversed(method)))
-    g = 180 +int(70 * hash_to_float((method)))
+    g = 180 + int(70 * hash_to_float((method)))
     b = 170 + int(80 * hash_to_float(reversed(method)))
-    return (r,g,b)
+    return (r, g, b)
 
 
-def getHeatColor(callsite, num_samples) :
-    r = 245 + 10* (1- float(callsite.num_samples)/ num_samples)
-    g = 110 + 105* (1-float(callsite.num_samples)/ num_samples)
+def getHeatColor(callsite, total_weight):
+    r = 245 + 10 * (1 - callsite.weight() / total_weight)
+    g = 110 + 105 * (1 - callsite.weight() / total_weight)
     b = 100
-    return (r,g,b)
+    return (r, g, b)
 
 
-def createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav):
-    x = float(callsite.offset)/float(num_samples)*SVG_CANVAS_WIDTH
-    y = height - (depth * SVG_NODE_HEIGHT) - SVG_NODE_HEIGHT
-    width = float(callsite.num_samples) /float(num_samples) * SVG_CANVAS_WIDTH
+def createSVGNode(callsite, depth, f, total_weight, height, color_scheme, nav):
+    x = float(callsite.offset) / total_weight * 100
+    y = height - (depth + 1) * SVG_NODE_HEIGHT
+    width = callsite.weight() / total_weight * 100
 
     method = callsite.method.replace(">", "&gt;").replace("<", "&lt;")
-    if (width <= 0) :
+    if width <= 0:
         return
 
     if color_scheme == "dso":
@@ -59,111 +60,125 @@
     elif color_scheme == "legacy":
         r, g, b = getLegacyColor(method)
     else:
-        r, g, b = getHeatColor(callsite, num_samples)
+        r, g, b = getHeatColor(callsite, total_weight)
 
-
-
-    r_border = (r - 50)
-    if r_border < 0:
-        r_border = 0
-
-    g_border = (g - 50)
-    if g_border < 0:
-        g_border = 0
-
-    b_border = (b - 50)
-    if (b_border < 0):
-        b_border = 0
+    r_border, g_border, b_border = [max(0, color - 50) for color in [r, g, b]]
 
     f.write(
-    '<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s"> \n\
-        <title>%s | %s (%d samples: %3.2f%%)</title>\n \
-        <rect x="%f" y="%f" ox="%f" oy="%f" width="%f" owidth="%f" height="15.0" ofill="rgb(%d,%d,%d)" \
-        fill="rgb(%d,%d,%d)" style="stroke:rgb(%d,%d,%d)"/>\n \
-        <text x="%f" y="%f" font-size="%d" font-family="Monospace"></text>\n \
-    </g>\n' % (callsite.id, ','.join(str(x) for x in nav),
-               method, callsite.dso, callsite.num_samples, callsite.num_samples/float(num_samples) * 100,
-               x, y, x, y, width , width, r, g, b, r, g, b, r_border, g_border, b_border,
-               x+2, y+12, FONT_SIZE))
+        """<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s">
+        <title>%s | %s (%.0f events: %3.2f%%)</title>
+        <rect x="%f%%" y="%f" ox="%f" oy="%f" width="%f%%" owidth="%f" height="15.0"
+        ofill="rgb(%d,%d,%d)" fill="rgb(%d,%d,%d)" style="stroke:rgb(%d,%d,%d)"/>
+        <text x="%f%%" y="%f" font-size="%d" font-family="Monospace"></text>
+        </g>""" %
+        (callsite.id,
+         ','.join(str(x) for x in nav),
+         method,
+         callsite.dso,
+         callsite.weight(),
+         callsite.weight() / total_weight * 100,
+         x,
+         y,
+         x,
+         y,
+         width,
+         width,
+         r,
+         g,
+         b,
+         r,
+         g,
+         b,
+         r_border,
+         g_border,
+         b_border,
+         x,
+         y + 12,
+         FONT_SIZE))
 
 
-def renderSVGNodes(flamegraph, depth, f, num_samples, height, color_scheme):
-    for i, callsite in enumerate(flamegraph.callsites):
+def renderSVGNodes(flamegraph, depth, f, total_weight, height, color_scheme):
+    for i, child in enumerate(flamegraph.children):
         # Prebuild navigation target for wasd
 
         if i == 0:
             left_index = 0
         else:
-            left_index = flamegraph.callsites[i-1].id
+            left_index = flamegraph.children[i - 1].id
 
-        if i == len(flamegraph.callsites)-1:
+        if i == len(flamegraph.children) - 1:
             right_index = 0
         else:
-            right_index = flamegraph.callsites[i+1].id
+            right_index = flamegraph.children[i + 1].id
 
-
-        up_index = 0
-        max_up = 0
-        for upcallsite in callsite.callsites:
-            if upcallsite.num_samples > max_up:
-                max_up = upcallsite.num_samples
-                up_index = upcallsite.id
+        up_index = max(child.children, key=lambda x: x.weight()).id if child.children else 0
 
         # up, left, down, right
-        nav = [up_index, left_index,flamegraph.id,right_index]
+        nav = [up_index, left_index, flamegraph.id, right_index]
 
-        createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav)
+        createSVGNode(child, depth, f, total_weight, height, color_scheme, nav)
         # Recurse down
-        renderSVGNodes(callsite, depth+1, f, num_samples, height, color_scheme)
+        renderSVGNodes(child, depth + 1, f, total_weight, height, color_scheme)
+
 
 def renderSearchNode(f):
     f.write(
-       '<rect id="search_rect"  style="stroke:rgb(0,0,0);" onclick="search(this);" class="t" rx="10" ry="10" \
-       x="%d" y="10" width="80" height="30" fill="rgb(255,255,255)""/> \
-        <text id="search_text"  class="t" x="%d" y="30"    onclick="search(this);">Search</text>\n'
-       % (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80)
-    )
+        """<rect id="search_rect"  style="stroke:rgb(0,0,0);" onclick="search(this);" class="t"
+        rx="10" ry="10" x="%d" y="10" width="80" height="30" fill="rgb(255,255,255)""/>
+        <text id="search_text"  class="t" x="%d" y="30"    onclick="search(this);">Search</text>
+        """ % (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80))
 
 
 def renderUnzoomNode(f):
     f.write(
-        '<rect id="zoom_rect" style="display:none;stroke:rgb(0,0,0);" class="t" onclick="unzoom(this);" \
-        rx="10" ry="10" x="10" y="10" width="80" height="30" fill="rgb(255,255,255)"/> \
-         <text id="zoom_text" style="display:none;" class="t" x="19" y="30"     \
-         onclick="unzoom(this);">Zoom out</text>\n'
+        """<rect id="zoom_rect" style="display:none;stroke:rgb(0,0,0);" class="t"
+        onclick="unzoom(this);" rx="10" ry="10" x="10" y="10" width="80" height="30"
+        fill="rgb(255,255,255)"/>
+         <text id="zoom_text" style="display:none;" class="t" x="19" y="30"
+         onclick="unzoom(this);">Zoom out</text>"""
     )
 
+
 def renderInfoNode(f):
     f.write(
-        '<clipPath id="info_clip_path"> <rect id="info_rect" style="stroke:rgb(0,0,0);" \
-        rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/></clipPath> \
-        <rect id="info_rect" style="stroke:rgb(0,0,0);" \
-        rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/> \
-         <text clip-path="url(#info_clip_path)" id="info_text" x="128" y="30"></text>\n' % (SVG_CANVAS_WIDTH - 335, SVG_CANVAS_WIDTH - 325)
+        """<clipPath id="info_clip_path"> <rect id="info_rect" style="stroke:rgb(0,0,0);"
+        rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/>
+        </clipPath>
+        <rect id="info_rect" style="stroke:rgb(0,0,0);"
+        rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/>
+         <text clip-path="url(#info_clip_path)" id="info_text" x="128" y="30"></text>
+         """ % (SVG_CANVAS_WIDTH - 335, SVG_CANVAS_WIDTH - 325)
     )
 
+
 def renderPercentNode(f):
     f.write(
-        '<rect id="percent_rect" style="stroke:rgb(0,0,0);" \
-        rx="10" ry="10" x="%d" y="10" width="82" height="30" fill="rgb(255,255,255)"/> \
-         <text  id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text>\n' % (SVG_CANVAS_WIDTH - (95 * 2),SVG_CANVAS_WIDTH - (125))
+        """<rect id="percent_rect" style="stroke:rgb(0,0,0);"
+        rx="10" ry="10" x="%d" y="10" width="82" height="30" fill="rgb(255,255,255)"/>
+         <text  id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text>
+         """ % (SVG_CANVAS_WIDTH - (95 * 2), SVG_CANVAS_WIDTH - (125))
     )
 
 
 def renderSVG(flamegraph, f, color_scheme, width):
     global SVG_CANVAS_WIDTH
     SVG_CANVAS_WIDTH = width
-    height = (flamegraph.get_max_depth() + 2 )* SVG_NODE_HEIGHT
-    f.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" \
-    width="%d" height="%d" style="border: 1px solid black;" \
-    onload="adjust_text_size(this);" rootid="%d">\n' % (SVG_CANVAS_WIDTH, height, flamegraph.callsites[0].id))
-    f.write('<defs > <linearGradient id="background_gradiant" y1="0" y2="1" x1="0" x2="0" > \
-    <stop stop-color="#eeeeee" offset="5%" /> <stop stop-color="#efefb1" offset="90%" /> </linearGradient> </defs>')
-    f.write('<rect x="0.0" y="0" width="%d" height="%d" fill="url(#background_gradiant)"  />' % \
-            (SVG_CANVAS_WIDTH, height))
-    renderSVGNodes(flamegraph, 0, f, flamegraph.num_samples, height, color_scheme)
+    height = (flamegraph.get_max_depth() + 2) * SVG_NODE_HEIGHT
+    f.write("""<div class="flamegraph_block" style="width:%dpx; height:%dpx;">
+            """ % (SVG_CANVAS_WIDTH, height))
+    f.write("""<svg xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
+    width="100%%" height="100%%" style="border: 1px solid black;"
+    onload="adjust_text_size(this);" rootid="%d">
+    """ % (flamegraph.children[0].id))
+    f.write("""<defs > <linearGradient id="background_gradiant" y1="0" y2="1" x1="0" x2="0" >
+    <stop stop-color="#eeeeee" offset="5%" /> <stop stop-color="#efefb1" offset="90%" />
+    </linearGradient> </defs>""")
+    f.write("""<rect x="0.0" y="0" width="100%" height="100%" fill="url(#background_gradiant)" />
+            """)
+    renderSVGNodes(flamegraph, 0, f, flamegraph.weight(), height, color_scheme)
     renderSearchNode(f)
     renderUnzoomNode(f)
     renderInfoNode(f)
     renderPercentNode(f)
-    f.write("</svg><br/>\n\n")
\ No newline at end of file
+    f.write("</svg></div><br/>\n\n")
diff --git a/simpleperf/scripts/run_simpleperf_on_device.py b/simpleperf/scripts/run_simpleperf_on_device.py
new file mode 100644
index 0000000..37155bc
--- /dev/null
+++ b/simpleperf/scripts/run_simpleperf_on_device.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 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.
+#
+
+"""run_simpleperf_on_device.py:
+    It downloads simpleperf to /data/local/tmp on device, and run it with all given arguments.
+    It saves the time downloading simpleperf and using `adb shell` directly.
+"""
+import subprocess
+import sys
+from utils import *
+
+def main():
+    disable_debug_log()
+    adb = AdbHelper()
+    device_arch = adb.get_device_arch()
+    simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
+    adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
+    adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+    shell_cmd = 'cd /data/local/tmp && ./simpleperf ' + ' '.join(sys.argv[1:])
+    sys.exit(subprocess.call([adb.adb_path, 'shell', shell_cmd]))
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 67fdfa3..385df2f 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -83,9 +83,9 @@
                 ('entries', ct.POINTER(CallChainEntryStructure))]
 
 
-class MetaInfoEntryStructure(ct.Structure):
-    _fields_ = [('key', ct.c_char_p),
-                ('value', ct.c_char_p)]
+class FeatureSectionStructure(ct.Structure):
+    _fields_ = [('data', ct.POINTER(ct.c_char)),
+                ('data_size', ct.c_uint32)]
 
 
 # convert char_p to str for python3.
@@ -160,8 +160,8 @@
             CallChainStructure)
         self._GetBuildIdForPathFunc = self._lib.GetBuildIdForPath
         self._GetBuildIdForPathFunc.restype = ct.c_char_p
-        self._GetNextMetaInfoFunc = self._lib.GetNextMetaInfo
-        self._GetNextMetaInfoFunc.restype = ct.POINTER(MetaInfoEntryStructure)
+        self._GetFeatureSection = self._lib.GetFeatureSection
+        self._GetFeatureSection.restype = ct.POINTER(FeatureSectionStructure)
         self._instance = self._CreateReportLibFunc()
         assert(not _is_null(self._instance))
 
@@ -241,15 +241,52 @@
         assert(not _is_null(build_id))
         return _char_pt_to_str(build_id)
 
+    def GetRecordCmd(self):
+        if hasattr(self, "record_cmd"):
+            return self.record_cmd
+        self.record_cmd = None
+        feature_data = self._GetFeatureSection(self.getInstance(), _char_pt("cmdline"))
+        if not _is_null(feature_data):
+            void_p = ct.cast(feature_data[0].data, ct.c_void_p)
+            data_size = feature_data[0].data_size
+            arg_count = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
+            void_p.value += 4
+            args = []
+            for i in range(arg_count):
+                str_len = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
+                void_p.value += 4
+                char_p = ct.cast(void_p, ct.POINTER(ct.c_char))
+                current_str = ""
+                for j in range(str_len):
+                    c = bytes_to_str(char_p[j])
+                    if c != '\0':
+                        current_str += c
+                if ' ' in current_str:
+                    current_str = '"' + current_str + '"'
+                args.append(current_str)
+                void_p.value += str_len
+            self.record_cmd = " ".join(args)
+        return self.record_cmd
+
+
     def MetaInfo(self):
         if self.meta_info is None:
             self.meta_info = {}
-            while True:
-                entry = self._GetNextMetaInfoFunc(self.getInstance())
-                if _is_null(entry): break
-                key = _char_pt_to_str(entry[0].key)
-                value = _char_pt_to_str(entry[0].value)
-                self.meta_info[key] = value
+            feature_data = self._GetFeatureSection(self.getInstance(), _char_pt("meta_info"))
+            if not _is_null(feature_data):
+                str_list = []
+                data = feature_data[0].data
+                data_size = feature_data[0].data_size
+                current_str = ""
+                for i in range(data_size):
+                    c = bytes_to_str(data[i])
+                    if c != '\0':
+                        current_str += c
+                    else:
+                        str_list.append(current_str)
+                        current_str = ""
+                for i in range(0, len(str_list), 2):
+                    self.meta_info[str_list[i]] = str_list[i + 1]
         return self.meta_info
 
     def getInstance(self):
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index a4f2ebc..e75a328 100644
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -51,6 +51,8 @@
 except:
     has_google_protobuf = False
 
+inferno_script = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh")
+
 support_trace_offcpu = None
 
 def is_trace_offcpu_supported():
@@ -66,7 +68,6 @@
         support_trace_offcpu = 'trace-offcpu' in output
     return support_trace_offcpu
 
-
 def build_testdata():
     """ Collect testdata from ../testdata and ../demo. """
     from_testdata_path = os.path.join('..', 'testdata')
@@ -85,7 +86,27 @@
     for demo in copy_demo_list:
         shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo))
 
-class TestExampleBase(unittest.TestCase):
+
+class TestBase(unittest.TestCase):
+    def run_cmd(self, args, return_output=False):
+        if args[0].endswith('.py'):
+            args = [sys.executable] + args
+        use_shell = args[0].endswith('.bat')
+        try:
+            if not return_output:
+                returncode = subprocess.call(args, shell=use_shell)
+            else:
+                subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell)
+                (output_data, _) = subproc.communicate()
+                returncode = subproc.returncode
+        except:
+            returncode = None
+        self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
+        if return_output:
+            return output_data
+
+
+class TestExampleBase(TestBase):
     @classmethod
     def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
         cls.adb = AdbHelper(enable_switch_to_root=adb_root)
@@ -100,6 +121,9 @@
             log_fatal("can't find app-profiling.apk under " + cls.example_path)
         cls.package_name = package_name
         cls.activity_name = activity_name
+        cls.abi = "arm64"
+        if abi and abi != "arm64" and abi.find("arm") != -1:
+            cls.abi = "arm"
         args = ["install", "-r"]
         if abi:
             args += ["--abi", abi]
@@ -114,37 +138,25 @@
 
     @classmethod
     def tearDownClass(cls):
+        if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful():
+            return
         if hasattr(cls, 'package_name'):
             cls.adb.check_run(["uninstall", cls.package_name])
-
-    @classmethod
-    def cleanupTestFiles(cls):
         remove("binary_cache")
         remove("annotated_files")
         remove("perf.data")
         remove("report.txt")
         remove("pprof.profile")
 
-    def run_cmd(self, args, return_output=False):
-        args = [sys.executable] + args
-        try:
-            if not return_output:
-                returncode = subprocess.call(args)
-            else:
-                subproc = subprocess.Popen(args, stdout=subprocess.PIPE)
-                (output_data, _) = subproc.communicate()
-                returncode = subproc.returncode
-        except:
-            returncode = None
-        self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
-        if return_output:
-            return output_data
+    def run(self, result=None):
+        self.__class__.test_result = result
+        super(TestBase, self).run(result)
 
     def run_app_profiler(self, record_arg = "-g --duration 3 -e cpu-cycles:u",
                          build_binary_cache=True, skip_compile=False, start_activity=True,
-                         native_lib_dir=None):
+                         native_lib_dir=None, profile_from_launch=False, add_arch=False):
         args = ["app_profiler.py", "--app", self.package_name, "--apk", self.apk_path,
-                "-a", self.activity_name, "-r", record_arg, "-o", "perf.data"]
+                "-r", record_arg, "-o", "perf.data"]
         if not build_binary_cache:
             args.append("-nb")
         if skip_compile or self.__class__.compiled:
@@ -153,6 +165,10 @@
             args += ["-a", self.activity_name]
         if native_lib_dir:
             args += ["-lib", native_lib_dir]
+        if profile_from_launch:
+            args.append("--profile_from_launch")
+        if add_arch:
+            args += ["--arch", self.abi]
         if not self.adb_root:
             args.append("--disable_adb_root")
         self.run_cmd(args)
@@ -209,6 +225,22 @@
                             fulfilled[i] = True
         self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled)
 
+    def check_inferno_report_html(self, check_entries, file="report.html"):
+        self.check_exist(file=file)
+        with open(file, 'r') as fh:
+            data = fh.read()
+        fulfilled = [False for x in check_entries]
+        for line in data.split('\n'):
+            # each entry is a (function_name, min_percentage) pair.
+            for i, entry in enumerate(check_entries):
+                if fulfilled[i] or line.find(entry[0]) == -1:
+                    continue
+                m = re.search(r'(\d+\.\d+)%', line)
+                if m and float(m.group(1)) >= entry[1]:
+                    fulfilled[i] = True
+                    break
+        self.assertEqual(fulfilled, [True for x in check_entries])
+
     def common_test_app_profiler(self):
         self.run_cmd(["app_profiler.py", "-h"])
         remove("binary_cache")
@@ -224,7 +256,6 @@
         self.run_app_profiler(skip_compile=True)
         self.run_app_profiler(start_activity=False)
 
-
     def common_test_report(self):
         self.run_cmd(["report.py", "-h"])
         self.run_app_profiler(build_binary_cache=False)
@@ -247,7 +278,7 @@
         self.run_cmd(["report_sample.py"])
         output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
         self.check_strings_in_content(output, check_strings)
-        self.run_app_profiler(record_arg="-g --duration 3 -e cpu-cycles:u, --no-dump-symbols")
+        self.run_app_profiler(record_arg="-g --duration 3 -e cpu-cycles:u --no-dump-symbols")
         output = self.run_cmd(["report_sample.py", "--symfs", "binary_cache"], return_output=True)
         self.check_strings_in_content(output, check_strings)
 
@@ -274,6 +305,18 @@
         self.check_strings_in_content(output, check_strings_without_lines +
                                               ["has_line_numbers: False"])
 
+    def common_test_inferno(self):
+        self.run_cmd([inferno_script, "-h"])
+        remove("perf.data")
+        append_args = [] if self.adb_root else ["--disable_adb_root"]
+        self.run_cmd([inferno_script, "-p", self.package_name, "-t", "3"] + append_args)
+        self.check_exist(file="perf.data")
+        self.run_cmd([inferno_script, "-p", self.package_name, "-f", "1000", "-du", "-t", "1",
+                      "-nc"] + append_args)
+        self.run_cmd([inferno_script, "-p", self.package_name, "-e", "100000 cpu-cycles",
+                      "-t", "1", "-nc"] + append_args)
+        self.run_cmd([inferno_script, "-sc"])
+
 
 class TestExamplePureJava(TestExampleBase):
     @classmethod
@@ -285,11 +328,36 @@
     def test_app_profiler(self):
         self.common_test_app_profiler()
 
+    def test_app_profiler_profile_from_launch(self):
+        self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt",
+            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
+             "__start_thread"])
+
+    def test_app_profiler_multiprocesses(self):
+        self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
+        self.adb.check_run(['shell', 'am', 'start', '-n',
+                            self.package_name + '/.MultiProcessActivity'])
+        # Wait until both MultiProcessActivity and MultiProcessService set up.
+        time.sleep(3)
+        self.run_app_profiler(skip_compile=True, start_activity=False)
+        self.run_cmd(["report.py", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"])
+
     def test_app_profiler_with_ctrl_c(self):
         if is_windows():
             return
+        # `adb root` and `adb unroot` may consumes more time than 3 sec. So
+        # do it in advance to make sure ctrl-c happens when recording.
+        if self.adb_root:
+            self.adb.switch_to_root()
+        else:
+            self.adb._unroot()
         args = [sys.executable, "app_profiler.py", "--app", self.package_name,
                 "-r", "--duration 10000", "-nc"]
+        if not self.adb_root:
+            args.append("--disable_adb_root")
         subproc = subprocess.Popen(args)
         time.sleep(3)
 
@@ -313,7 +381,7 @@
             [("MainActivity.java", 80, 80),
              ("run", 80, 0),
              ("callFunction", 0, 0),
-             ("line 24", 80, 0)])
+             ("line 23", 80, 0)])
 
     def test_report_sample(self):
         self.common_test_report_sample(
@@ -328,6 +396,28 @@
             check_strings_without_lines=
                 ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()"])
 
+    def test_inferno(self):
+        self.common_test_inferno()
+        self.run_app_profiler()
+        self.run_cmd([inferno_script, "-sc"])
+        self.check_inferno_report_html(
+            [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)])
+        self.run_cmd([inferno_script, "-sc", "-o", "report2.html"])
+        self.check_inferno_report_html(
+            [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)],
+            "report2.html")
+        remove("report2.html")
+
+    def test_inferno_in_another_dir(self):
+        test_dir = 'inferno_testdir'
+        saved_dir = os.getcwd()
+        remove(test_dir)
+        os.mkdir(test_dir)
+        os.chdir(test_dir)
+        self.run_cmd([inferno_script])
+        os.chdir(saved_dir)
+        remove(test_dir)
+
 
 class TestExamplePureJavaRoot(TestExampleBase):
     @classmethod
@@ -368,6 +458,13 @@
              ("SleepFunction", 20, 0),
              ("line 24", 20, 0),
              ("line 32", 20, 0)])
+        self.run_cmd([inferno_script, "-sc"])
+        self.check_inferno_report_html(
+            [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run() ', 80),
+             ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()',
+              20),
+             ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)',
+              20)])
 
 
 class TestExampleWithNative(TestExampleBase):
@@ -382,6 +479,13 @@
         remove("binary_cache")
         self.run_app_profiler(native_lib_dir=self.example_path)
 
+    def test_app_profiler_profile_from_launch(self):
+        self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt",
+            ["BusyLoopThread",
+             "__start_thread"])
+
     def test_report(self):
         self.common_test_report()
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
@@ -411,6 +515,12 @@
             check_strings_without_lines=
                 ["BusyLoopThread"])
 
+    def test_inferno(self):
+        self.common_test_inferno()
+        self.run_app_profiler()
+        self.run_cmd([inferno_script, "-sc"])
+        self.check_inferno_report_html([('BusyLoopThread', 20)])
+
 
 class TestExampleWithNativeRoot(TestExampleBase):
     @classmethod
@@ -452,6 +562,10 @@
              ("SleepFunction", 20, 0),
              ("line 73", 20, 0),
              ("line 83", 20, 0)])
+        self.run_cmd([inferno_script, "-sc"])
+        self.check_inferno_report_html([('SleepThread', 80),
+                                        ('RunFunction', 20),
+                                        ('SleepFunction', 20)])
 
 
 class TestExampleWithNativeJniCall(TestExampleBase):
@@ -480,6 +594,7 @@
              ("line 26", 20, 0),
              ("native-lib.cpp", 10, 0),
              ("line 40", 10, 0)])
+        self.run_cmd([inferno_script, "-sc"])
 
 
 class TestExampleWithNativeForceArm(TestExampleWithNative):
@@ -520,6 +635,13 @@
     def test_app_profiler(self):
         self.common_test_app_profiler()
 
+    def test_app_profiler_profile_from_launch(self):
+        self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt",
+            ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
+             "__start_thread"])
+
     def test_report(self):
         self.common_test_report()
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
@@ -551,6 +673,14 @@
             check_strings_without_lines=
                 ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()"])
 
+    def test_inferno(self):
+        self.common_test_inferno()
+        self.run_app_profiler()
+        self.run_cmd([inferno_script, "-sc"])
+        self.check_inferno_report_html(
+            [('com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()',
+              80)])
+
 
 class TestExampleOfKotlinRoot(TestExampleBase):
     @classmethod
@@ -591,6 +721,14 @@
              ("SleepFunction", 20, 0),
              ("line 24", 20, 0),
              ("line 32", 20, 0)])
+        self.run_cmd([inferno_script, "-sc"])
+        self.check_inferno_report_html(
+            [('void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()',
+              80),
+             ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()',
+              20),
+             ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)',
+              20)])
 
 
 class TestProfilingNativeProgram(TestExampleBase):
@@ -617,6 +755,16 @@
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
 
 
+class TestProfilingNativeProgram(TestExampleBase):
+    def test_smoke(self):
+        adb = AdbHelper()
+        if adb.switch_to_root():
+            self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"])
+            self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+            self.run_cmd([inferno_script, "-sc"])
+            self.run_cmd([inferno_script, "-np", "surfaceflinger"])
+
+
 class TestReportLib(unittest.TestCase):
     def setUp(self):
         self.report_lib = ReportLib()
@@ -659,10 +807,11 @@
     def test_meta_info(self):
         self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
         meta_info = self.report_lib.MetaInfo()
-        self.assertEqual(meta_info["simpleperf_version"], "1.65f91c7ed862")
+        self.assertTrue("simpleperf_version" in meta_info)
         self.assertEqual(meta_info["system_wide_collection"], "false")
         self.assertEqual(meta_info["trace_offcpu"], "true")
         self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47")
+        self.assertTrue("product_props" in meta_info)
 
     def test_event_name_from_meta_info(self):
         self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
@@ -672,6 +821,11 @@
         self.assertTrue('sched:sched_switch' in event_names)
         self.assertTrue('cpu-cycles' in event_names)
 
+    def test_record_cmd(self):
+        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
+        self.assertEqual(self.report_lib.GetRecordCmd(),
+                         "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g ./simpleperf_runtest_run_and_sleep64")
+
     def test_offcpu(self):
         self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
         total_period = 0
@@ -689,14 +843,21 @@
                     sleep_function_period += sample.period
                     break
         sleep_percentage = float(sleep_function_period) / total_period
-        self.assertAlmostEqual(sleep_percentage, 0.4629, delta=0.0001)
+        self.assertGreater(sleep_percentage, 0.30)
+
+
+class TestRunSimpleperfOnDevice(TestBase):
+    def test_smoke(self):
+        self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features'])
 
 
 def main():
+    os.chdir(get_script_dir())
     build_testdata()
-    test_program = unittest.main(failfast=True, exit=False)
-    if test_program.result.wasSuccessful():
-        TestExampleBase.cleanupTestFiles()
+    if AdbHelper().get_android_version() < 7:
+        log_info("Skip tests on Android version < N.")
+        sys.exit(0)
+    unittest.main(failfast=True)
 
 if __name__ == '__main__':
     main()
\ No newline at end of file
diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py
index 0570cfd..77f9f72 100644
--- a/simpleperf/scripts/utils.py
+++ b/simpleperf/scripts/utils.py
@@ -59,6 +59,9 @@
 def log_exit(msg):
     sys.exit(msg)
 
+def disable_debug_log():
+    logging.getLogger().setLevel(logging.WARN)
+
 def str_to_bytes(str):
     if not is_python3():
         return str
@@ -262,6 +265,21 @@
         log_fatal('unsupported architecture: %s' % output.strip())
 
 
+    def get_android_version(self):
+        build_version = self.get_property('ro.build.version.release')
+        android_version = 0
+        if build_version:
+            if not build_version[0].isdigit():
+                c = build_version[0].upper()
+                if c.isupper() and c >= 'L':
+                    android_version = ord(c) - ord('L') + 5
+            else:
+                strs = build_version.split('.')
+                if strs:
+                    android_version = int(strs[0])
+        return android_version
+
+
 def flatten_arg_list(arg_list):
     res = []
     if arg_list:
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index bb061ba..f5fb590 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -46,3 +46,13 @@
 #else
 #define TEST_REQUIRE_HOST_ROOT()  if (!IsRoot()) return
 #endif
+
+bool IsInNativeAbi();
+// Used to skip tests not supposed to run on non-native ABIs.
+#define OMIT_TEST_ON_NON_NATIVE_ABIS()  \
+  do { \
+    if (!IsInNativeAbi()) { \
+      GTEST_LOG_(INFO) << "Skip this test as it only runs on native ABIs."; \
+      return; \
+    } \
+  } while (0)
diff --git a/simpleperf/testdata/perf_with_trace_offcpu.data b/simpleperf/testdata/perf_with_trace_offcpu.data
index 6d0c5e0..7d81414 100644
--- a/simpleperf/testdata/perf_with_trace_offcpu.data
+++ b/simpleperf/testdata/perf_with_trace_offcpu.data
Binary files differ
diff --git a/su/su.cpp b/su/su.cpp
index ee1526e..f3e4ff0 100644
--- a/su/su.cpp
+++ b/su/su.cpp
@@ -19,7 +19,6 @@
 #include <getopt.h>
 #include <paths.h>
 #include <pwd.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
diff --git a/tests/bootloader/Android.mk b/tests/bootloader/Android.mk
new file mode 100644
index 0000000..ab311fd
--- /dev/null
+++ b/tests/bootloader/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH := $(call my-dir)
+
+# Build a module that has all of the python files as its LOCAL_PICKUP_FILES.
+# Since no action needs to be taken to compile the python source, just
+# use BUILD_PHONY_PACKAGE to give us a target to execute.
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootloader_unit_test
+LOCAL_MODULE_TAGS := tests
+
+bootloader_py_files := $(call find-subdir-files, *.py)
+
+bootloader_zip_prefix := $(TARGET_OUT_DATA)/py_bootloader
+bootloader_zip_path := $(bootloader_zip_prefix)/nativetest/py_bootloader
+
+GEN := $(addprefix $(bootloader_zip_path)/, $(bootloader_py_files))
+$(GEN) : PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN) : PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN) : $(bootloader_zip_path)/% : $(LOCAL_PATH)/%
+	$(transform-generated-source)
+
+LOCAL_PICKUP_FILES := $(bootloader_zip_prefix)/nativetest
+LOCAL_ADDITIONAL_DEPENDENCIES := $(GEN)
+
+include $(BUILD_PHONY_PACKAGE)
diff --git a/tests/icachetest/Android.mk b/tests/icachetest/Android.mk
index 132efd3..9874ffd 100644
--- a/tests/icachetest/Android.mk
+++ b/tests/icachetest/Android.mk
@@ -2,7 +2,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:= icache_main.c icache.S icache2.S
+LOCAL_SRC_FILES:= icache_main.cpp Profiler.cpp icache.S
 
 LOCAL_SHARED_LIBRARIES := libc
 
@@ -12,4 +12,6 @@
 
 LOCAL_MODULE_TARGET_ARCH := arm
 
+LOCAL_CFLAGS += -Wall -Werror
+
 include $(BUILD_EXECUTABLE)
diff --git a/tests/icachetest/Profiler.cpp b/tests/icachetest/Profiler.cpp
new file mode 100644
index 0000000..792cf43
--- /dev/null
+++ b/tests/icachetest/Profiler.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 "Profiler.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <iostream>
+
+#if defined(__linux__)
+
+#include <sys/syscall.h>
+
+#ifdef __ARM_ARCH
+    enum ARMv8PmuPerfTypes{
+        // Common micro-architecture events
+        ARMV8_PMUV3_PERFCTR_L1_ICACHE_REFILL    = 0x01,
+        ARMV8_PMUV3_PERFCTR_L1_ICACHE_ACCESS    = 0x14,
+        ARMV8_PMUV3_PERFCTR_L2_CACHE_ACCESS     = 0x16,
+        ARMV8_PMUV3_PERFCTR_L2_CACHE_REFILL     = 0x17,
+        ARMV8_PMUV3_PERFCTR_L2_CACHE_WB         = 0x18,
+    };
+#endif
+
+static int perf_event_open(struct perf_event_attr* hw_event, pid_t pid,
+        int cpu, int group_fd, unsigned long flags) {
+    return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
+}
+
+#endif // __linux__
+
+namespace utils {
+
+Profiler& Profiler::get() noexcept {
+    static Profiler sProfiler;
+    return sProfiler;
+}
+
+Profiler::Profiler() noexcept {
+    std::uninitialized_fill(mCountersFd.begin(), mCountersFd.end(), -1);
+    Profiler::resetEvents(EV_CPU_CYCLES | EV_L1D_RATES | EV_BPU_RATES);
+}
+
+Profiler::~Profiler() noexcept {
+    for (int fd : mCountersFd) {
+        if (fd >= 0) {
+            close(fd);
+        }
+    }
+}
+
+uint32_t Profiler::resetEvents(uint32_t eventMask) noexcept {
+    // close all counters
+    for (int& fd : mCountersFd) {
+        if (fd >= 0) {
+            close(fd);
+            fd = -1;
+        }
+    }
+    mEnabledEvents = 0;
+
+#if defined(__linux__)
+
+    struct perf_event_attr pe;
+    memset(&pe, 0, sizeof(struct perf_event_attr));
+    pe.type = PERF_TYPE_HARDWARE;
+    pe.size = sizeof(struct perf_event_attr);
+    pe.config = PERF_COUNT_HW_INSTRUCTIONS;
+    pe.disabled = 1;
+    pe.exclude_kernel = 1;
+    pe.exclude_hv = 1;
+    pe.read_format = PERF_FORMAT_GROUP |
+                     PERF_FORMAT_ID |
+                     PERF_FORMAT_TOTAL_TIME_ENABLED |
+                     PERF_FORMAT_TOTAL_TIME_RUNNING;
+
+    uint8_t count = 0;
+    int fd = perf_event_open(&pe, 0, -1, -1, 0);
+    if (fd >= 0) {
+        const int groupFd = fd;
+        mIds[INSTRUCTIONS] = count++;
+        mCountersFd[INSTRUCTIONS] = fd;
+
+        pe.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
+
+        if (eventMask & EV_CPU_CYCLES) {
+            pe.type = PERF_TYPE_HARDWARE;
+            pe.config = PERF_COUNT_HW_CPU_CYCLES;
+            mCountersFd[CPU_CYCLES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[CPU_CYCLES] > 0) {
+                mIds[CPU_CYCLES] = count++;
+                mEnabledEvents |= EV_CPU_CYCLES;
+            }
+        }
+
+        if (eventMask & EV_L1D_REFS) {
+            pe.type = PERF_TYPE_HARDWARE;
+            pe.config = PERF_COUNT_HW_CACHE_REFERENCES;
+            mCountersFd[DCACHE_REFS] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[DCACHE_REFS] > 0) {
+                mIds[DCACHE_REFS] = count++;
+                mEnabledEvents |= EV_L1D_REFS;
+            }
+        }
+
+        if (eventMask & EV_L1D_MISSES) {
+            pe.type = PERF_TYPE_HARDWARE;
+            pe.config = PERF_COUNT_HW_CACHE_MISSES;
+            mCountersFd[DCACHE_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[DCACHE_MISSES] > 0) {
+                mIds[DCACHE_MISSES] = count++;
+                mEnabledEvents |= EV_L1D_MISSES;
+            }
+        }
+    
+        if (eventMask & EV_BPU_REFS) {
+            pe.type = PERF_TYPE_HARDWARE;
+            pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
+            mCountersFd[BRANCHES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[BRANCHES] > 0) {
+                mIds[BRANCHES] = count++;
+                mEnabledEvents |= EV_BPU_REFS;
+            }
+        }
+    
+        if (eventMask & EV_BPU_MISSES) {
+            pe.type = PERF_TYPE_HARDWARE;
+            pe.config = PERF_COUNT_HW_BRANCH_MISSES;
+            mCountersFd[BRANCH_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[BRANCH_MISSES] > 0) {
+                mIds[BRANCH_MISSES] = count++;
+                mEnabledEvents |= EV_BPU_MISSES;
+            }
+        }
+    
+#ifdef __ARM_ARCH
+        if (eventMask & EV_L1I_REFS) {
+            pe.type = PERF_TYPE_RAW;
+            pe.config = ARMV8_PMUV3_PERFCTR_L1_ICACHE_ACCESS;
+            mCountersFd[ICACHE_REFS] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[ICACHE_REFS] > 0) {
+                mIds[ICACHE_REFS] = count++;
+                mEnabledEvents |= EV_L1I_REFS;
+            }
+        }
+
+        if (eventMask & EV_L1I_MISSES) {
+            pe.type = PERF_TYPE_RAW;
+            pe.config = ARMV8_PMUV3_PERFCTR_L1_ICACHE_REFILL;
+            mCountersFd[ICACHE_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[ICACHE_MISSES] > 0) {
+                mIds[ICACHE_MISSES] = count++;
+                mEnabledEvents |= EV_L1I_MISSES;
+            }
+        }
+#else
+        if (eventMask & EV_L1I_REFS) {
+            pe.type = PERF_TYPE_HW_CACHE;
+            pe.config = PERF_COUNT_HW_CACHE_L1I | 
+                (PERF_COUNT_HW_CACHE_OP_READ<<8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS<<16);
+            mCountersFd[ICACHE_REFS] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[ICACHE_REFS] > 0) {
+                mIds[ICACHE_REFS] = count++;
+                mEnabledEvents |= EV_L1I_REFS;
+            }
+        }
+
+        if (eventMask & EV_L1I_MISSES) {
+            pe.type = PERF_TYPE_HW_CACHE;
+            pe.config = PERF_COUNT_HW_CACHE_L1I | 
+                (PERF_COUNT_HW_CACHE_OP_READ<<8) | (PERF_COUNT_HW_CACHE_RESULT_MISS<<16);
+            mCountersFd[ICACHE_MISSES] = perf_event_open(&pe, 0, -1, groupFd, 0);
+            if (mCountersFd[ICACHE_MISSES] > 0) {
+                mIds[ICACHE_MISSES] = count++;
+                mEnabledEvents |= EV_L1I_MISSES;
+            }
+        }
+#endif
+    }
+#endif // __linux__
+    return mEnabledEvents;
+}
+
+} // namespace utils
diff --git a/tests/icachetest/Profiler.h b/tests/icachetest/Profiler.h
new file mode 100644
index 0000000..a36cab3
--- /dev/null
+++ b/tests/icachetest/Profiler.h
@@ -0,0 +1,229 @@
+/*
+ * 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 TNT_UTILS_PROFILER_H
+#define TNT_UTILS_PROFILER_H
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <array>
+#include <chrono>
+
+#if defined(__linux__)
+#   include <unistd.h>
+#   include <sys/ioctl.h>
+#   include <linux/perf_event.h>
+#endif
+
+namespace utils {
+
+class Profiler {
+    enum {
+        INSTRUCTIONS    = 0,   // must be zero
+        CPU_CYCLES      = 1,
+        DCACHE_REFS     = 2,
+        DCACHE_MISSES   = 3,
+        BRANCHES        = 4,
+        BRANCH_MISSES   = 5,
+        ICACHE_REFS     = 6,
+        ICACHE_MISSES   = 7,
+
+        // Must be last one
+        EVENT_COUNT
+    };
+
+public:
+
+    enum {
+        EV_CPU_CYCLES = 1 << CPU_CYCLES,
+        EV_L1D_REFS   = 1 << DCACHE_REFS,
+        EV_L1D_MISSES = 1 << DCACHE_MISSES,
+        EV_BPU_REFS   = 1 << BRANCHES,
+        EV_BPU_MISSES = 1 << BRANCH_MISSES,
+        EV_L1I_REFS   = 1 << ICACHE_REFS,
+        EV_L1I_MISSES = 1 << ICACHE_MISSES,
+        // helpers
+        EV_L1D_RATES = EV_L1D_REFS | EV_L1D_MISSES,
+        EV_L1I_RATES = EV_L1I_REFS | EV_L1I_MISSES,
+        EV_BPU_RATES = EV_BPU_REFS | EV_BPU_MISSES,
+    };
+
+    static Profiler& get() noexcept;
+
+
+    Profiler(const Profiler& rhs) = delete;
+    Profiler(Profiler&& rhs) = delete;
+    Profiler& operator=(const Profiler& rhs) = delete;
+    Profiler& operator=(Profiler&& rhs) = delete;
+
+    // selects which events are enabled. 
+    // By Default: EV_CPU_CYCLES | EV_L1D_RATES | EV_BPU_RATES
+    uint32_t resetEvents(uint32_t eventMask) noexcept;
+
+    uint32_t getEnabledEvents() const noexcept { return mEnabledEvents; }
+
+    // could return false if performance counters are not supported/enabled
+    bool isValid() const { return mCountersFd[0] >= 0; }
+
+    class Counters {
+        friend class Profiler;
+        uint64_t nr;
+        uint64_t time_enabled;
+        uint64_t time_running;
+        struct {
+            uint64_t value;
+            uint64_t id;
+        } counters[Profiler::EVENT_COUNT];
+
+        friend Counters operator-(Counters lhs, const Counters& rhs) noexcept {
+            lhs.nr -= rhs.nr;
+            lhs.time_enabled -= rhs.time_enabled;
+            lhs.time_running -= rhs.time_running;
+            for (size_t i=0 ; i<EVENT_COUNT ; ++i) {
+                lhs.counters[i].value -= rhs.counters[i].value;
+            }
+            return lhs;
+        }
+
+    public:
+        uint64_t getInstructions() const        { return counters[INSTRUCTIONS].value; }
+        uint64_t getCpuCycles() const           { return counters[CPU_CYCLES].value; }
+        uint64_t getL1DReferences() const       { return counters[DCACHE_REFS].value; }
+        uint64_t getL1DMisses() const           { return counters[DCACHE_MISSES].value; }
+        uint64_t getL1IReferences() const       { return counters[ICACHE_REFS].value; }
+        uint64_t getL1IMisses() const           { return counters[ICACHE_MISSES].value; }
+        uint64_t getBranchInstructions() const  { return counters[BRANCHES].value; }
+        uint64_t getBranchMisses() const        { return counters[BRANCH_MISSES].value; }
+
+        std::chrono::duration<uint64_t, std::nano> getWallTime() const {
+            return std::chrono::duration<uint64_t, std::nano>(time_enabled);
+        }
+
+        std::chrono::duration<uint64_t, std::nano> getRunningTime() const {
+            return std::chrono::duration<uint64_t, std::nano>(time_running);
+        }
+
+        double getIPC() const noexcept {
+            uint64_t cpuCycles = getCpuCycles();
+            uint64_t instructions = getInstructions();
+            return double(instructions) / double(cpuCycles);
+        }
+
+        double getCPI() const noexcept {
+            uint64_t cpuCycles = getCpuCycles();
+            uint64_t instructions = getInstructions();
+            return double(cpuCycles) / double(instructions);
+        }
+
+        double getL1DMissRate() const noexcept {
+            uint64_t cacheReferences = getL1DReferences();
+            uint64_t cacheMisses = getL1DMisses();
+            return double(cacheMisses) / double(cacheReferences);
+        }
+
+        double getL1DHitRate() const noexcept {
+            return 1.0 - getL1DMissRate();
+        }
+
+        double getL1IMissRate() const noexcept {
+            uint64_t cacheReferences = getL1IReferences();
+            uint64_t cacheMisses = getL1IMisses();
+            return double(cacheMisses) / double(cacheReferences);
+        }
+
+        double getL1IHitRate() const noexcept {
+            return 1.0 - getL1IMissRate();
+        }
+
+        double getBranchMissRate() const noexcept {
+            uint64_t branchReferences = getBranchInstructions();
+            uint64_t branchMisses = getBranchMisses();
+            return double(branchMisses) / double(branchReferences);
+        }
+
+        double getBranchHitRate() const noexcept {
+            return 1.0 - getBranchMissRate();
+        }
+
+        double getMPKI(uint64_t misses) const noexcept {
+            return (misses * 1000.0) / getInstructions();
+        }
+
+    };
+
+#if defined(__linux__)
+
+    void reset() noexcept {
+        int fd = mCountersFd[0];
+        ioctl(fd, PERF_EVENT_IOC_RESET,  PERF_IOC_FLAG_GROUP);
+    }
+
+    void start() noexcept {
+        int fd = mCountersFd[0];
+        ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
+    }
+
+    void stop() noexcept {
+        int fd = mCountersFd[0];
+        ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+    }
+
+    void readCounters(Counters* outCounters) noexcept {
+        Counters counters;
+        ssize_t n = read(mCountersFd[0], &counters, sizeof(Counters));
+        memset(outCounters, 0, sizeof(Counters));
+        if (n > 0) {
+            outCounters->nr = counters.nr;
+            outCounters->time_enabled = counters.time_enabled;
+            outCounters->time_running = counters.time_running;
+            for (size_t i=0 ; i<size_t(EVENT_COUNT) ; i++) {
+                if (mCountersFd[i] >= 0) {
+                    outCounters->counters[i] = counters.counters[mIds[i]];
+                }
+            }
+        }
+    }
+
+#else // !__linux__
+
+    void reset() noexcept { }
+    void start() noexcept { }
+    void stop() noexcept { }
+    void readCounters(Counters* counters) noexcept { }
+
+#endif // __linux__
+
+    bool hasBranchRates() const noexcept {
+        return (mCountersFd[BRANCHES] >= 0) && (mCountersFd[BRANCH_MISSES] >= 0);
+    }
+
+    bool hasICacheRates() const noexcept {
+        return (mCountersFd[ICACHE_REFS] >= 0) && (mCountersFd[ICACHE_MISSES] >= 0);
+    }
+
+private:
+    Profiler() noexcept;
+    ~Profiler() noexcept;
+
+    std::array<uint8_t, EVENT_COUNT> mIds;
+    std::array<int, EVENT_COUNT> mCountersFd;
+    uint32_t mEnabledEvents = 0;
+};
+
+} // namespace utils
+
+#endif // TNT_UTILS_PROFILER_H
diff --git a/tests/icachetest/icache.S b/tests/icachetest/icache.S
index fbe8fa7..e82895d 100644
--- a/tests/icachetest/icache.S
+++ b/tests/icachetest/icache.S
@@ -19,6 +19,14 @@
         mov     r0, r0                 ; \
         mov     r0, r0                 ; \
         mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
+        mov     r0, r0                 ; \
         beq     end_loop               ; \
         mov     r0, r0                 ; \
 
@@ -37,6 +45,14 @@
         mov     r0, r0
         mov     r0, r0
         mov     r0, r0
+        mov     r0, r0
+        mov     r0, r0
+        mov     r0, r0
+        mov     r0, r0
+        mov     r0, r0
+        mov     r0, r0
+        mov     r0, r0
+        mov     r0, r0
 
 end_loop:
 		subs      r0, r0, r1
diff --git a/tests/icachetest/icache2.S b/tests/icachetest/icache2.S
deleted file mode 100644
index 2a204ce..0000000
--- a/tests/icachetest/icache2.S
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- *  icache.s
- *  
- *
- *  Copyright 2005 The Android Open Source Project
- *
- */
-
-    .text
-    .align
-    
-    .global icache_test2
-    .type icache_test2, %function
-
-#define LOOP                             \
-        mov     r0, r0                 ; \
-        mov     r0, r0                 ; \
-        mov     r0, r0                 ; \
-        mov     r0, r0                 ; \
-        mov     r0, r0                 ; \
-        mov     r0, r0                 ; \
-        mov     r0, r0                 ; \
-        mov     r0, r0                 ;
-
-
-    /*
-     * r0 = loop_count
-     * r1 = step
-     * r2 = mask
-     */
-
-icache_test2:
-end_loop:
-        
-        /* each loop iteration is one cache line 
-           repeat this block 2048 times... */
-
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-        LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP LOOP 
-    
-        subs    r0, r0, #1
-        bgt     end_loop
-        bx      lr
-
-        
diff --git a/tests/icachetest/icache_main.c b/tests/icachetest/icache_main.c
deleted file mode 100644
index 93f36d4..0000000
--- a/tests/icachetest/icache_main.c
+++ /dev/null
@@ -1,34 +0,0 @@
-#include <stdio.h>
-#include <sys/time.h>
-
-extern void icache_test(long count, long step);
-extern void icache_test2(long count);
-
-int main() 
-{
-    printf("[bytes]\t[us]\n");
-
-    struct timeval now, tm;
-    long long t;
-    long MBs;
-    long i;
-    long step = 32;
-    for (i=0 ; step<=2048 ; i++, step+=32) 
-    {
-        long value;
-        gettimeofday(&now, 0);
-        icache_test(0x800000L, step);
-        gettimeofday(&tm, 0);
-        t = (tm.tv_sec*1000000LL+tm.tv_usec) - (now.tv_sec*1000000LL+now.tv_usec);
-        printf("%6ld\t%lld\n", step*32, t);
-    }
-
-    gettimeofday(&now, 0);
-    icache_test2(0x800000L / 2048);
-    gettimeofday(&tm, 0);
-    t = (tm.tv_sec*1000000LL+tm.tv_usec) - (now.tv_sec*1000000LL+now.tv_usec);
-    MBs = (8388608LL*32*1000000) / (t * (1024*1024));
-    printf("\n%6lld us\t%ld MB/s\n", t, MBs);
-    
-    return 0;
-}
diff --git a/tests/icachetest/icache_main.cpp b/tests/icachetest/icache_main.cpp
new file mode 100644
index 0000000..d5aeda0
--- /dev/null
+++ b/tests/icachetest/icache_main.cpp
@@ -0,0 +1,124 @@
+#include <stdio.h>
+#include <sys/time.h>
+#include <getopt.h>
+
+#include <thread>
+#include <iostream>
+#include <iomanip>
+
+#include <sched.h>
+
+#include "Profiler.h"
+
+extern "C" void icache_test(long count, long step);
+
+static constexpr size_t MAX_CODE_SIZE = 128*1024;
+static constexpr size_t CACHE_LINE_SIZE = 64;
+static constexpr size_t MAX_ITERATIONS_COUNT = MAX_CODE_SIZE / CACHE_LINE_SIZE;
+static constexpr size_t REPETITIONS = 0x800000L;
+
+
+using namespace utils;
+
+static cpu_set_t g_cpu_set;
+
+static void printUsage(char* name) {
+    std::string exec_name(name);
+    std::string usage(
+            "ICACHE is a command-line tool for testing the L1 instruction cache performance.\n"
+            "(Make sure security.perf_harden is set to 0)\n\n"
+            "Usages:\n"
+            "    ICACHE [options]\n"
+            "\n"
+            "Options:\n"
+            "   --help, -h\n"
+            "       print this message\n\n"
+            "   --affinity=N, -a N\n"
+            "       Specify which CPU the test should run on.\n\n"
+    );
+    const std::string from("ICACHE");
+    for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
+         usage.replace(pos, from.length(), exec_name);
+    }
+    printf("%s", usage.c_str());
+}
+
+static int handleCommandLineArgments(int argc, char* argv[]) {
+    static constexpr const char* OPTSTR = "ha:";
+    static const struct option OPTIONS[] = {
+            { "help",                 no_argument, 0, 'h' },
+            { "affinity",       required_argument, 0, 'a' },
+            { 0, 0, 0, 0 }  // termination of the option list
+    };
+    int opt;
+    int option_index = 0;
+    while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
+        std::string arg(optarg ? optarg : "");
+        switch (opt) {
+            default:
+            case 'h':
+                printUsage(argv[0]);
+                exit(0);
+                break;
+            case 'a':
+                size_t cpu = std::stoi(arg);
+                if (cpu < std::thread::hardware_concurrency()) {
+                    CPU_SET(cpu, &g_cpu_set);
+                } else {
+                    std::cerr << "N must be < " << std::thread::hardware_concurrency() << std::endl;
+                    exit(0);
+                }
+                break;
+        }
+    }
+    return optind;
+}
+
+int main(int argc, char* argv[]) {
+    CPU_ZERO(&g_cpu_set);
+
+    [[maybe_unused]] int option_index = handleCommandLineArgments(argc, argv);
+    [[maybe_unused]] int num_args = argc - option_index;
+
+    if (CPU_COUNT(&g_cpu_set)) {
+        sched_setaffinity(gettid(), sizeof(g_cpu_set), &g_cpu_set);
+    }
+
+    Profiler& profiler = Profiler::get();
+    profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_L1I_RATES);
+
+    if (!profiler.isValid()) {
+        fprintf(stderr, "performance counters not enabled. try \"setprop security.perf_harden 0\"\n");
+        exit(0);
+    }
+
+    size_t const stepInBytes = 1024;    // 1 KiB steps
+    size_t const step = stepInBytes / CACHE_LINE_SIZE;
+
+    std::cout << std::fixed << std::setprecision(2);
+
+    printf("[KiB]\t[cyc]\t[refs]\t[MPKI]\t[ns]\n");
+
+    Profiler::Counters counters;
+
+    for (size_t i=step ; i <= MAX_ITERATIONS_COUNT ; i += step) {
+        profiler.reset();
+
+        auto now = std::chrono::steady_clock::now();
+        profiler.start();
+        icache_test(REPETITIONS, i);
+        profiler.stop();
+        auto duration = std::chrono::steady_clock::now() - now;
+
+        profiler.readCounters(&counters);
+
+        std::cout << ((i*CACHE_LINE_SIZE)/1024) << "\t"
+            << counters.getCpuCycles()/double(REPETITIONS) << "\t"
+            << counters.getL1IReferences()/double(REPETITIONS) << "\t"
+            << counters.getMPKI(counters.getL1IMisses()) << "\t"
+            << duration.count()/double(REPETITIONS) << "\t"
+            << std::endl;
+    }
+
+    return 0;
+}
diff --git a/tests/kernel.config/Android.mk b/tests/kernel.config/Android.mk
index af68c05..0c87107 100644
--- a/tests/kernel.config/Android.mk
+++ b/tests/kernel.config/Android.mk
@@ -15,7 +15,6 @@
 
 # Required Tests
 cts_src_files := \
-    aslr_test.cpp \
     logger_test.cpp \
     multicast_test.cpp \
     nfs_test.cpp \
@@ -23,8 +22,10 @@
     sysvipc_test.cpp \
 
 # Required plus Recommended Tests
+# TODO: move aslr_test.cpp back to cts_src_files b/36888825
 test_src_files := \
     $(cts_src_files) \
+    aslr_test.cpp \
     aslr_rec_test.cpp \
     mmc_max_speed_test.cpp \
 
diff --git a/tests/kernel.config/AndroidTest.xml b/tests/kernel.config/AndroidTest.xml
index 4fe3192..df7aa49 100644
--- a/tests/kernel.config/AndroidTest.xml
+++ b/tests/kernel.config/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Kernel Config test cases">
+    <option name="config-descriptor:metadata" key="component" value="systems" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CtsKernelConfigTestCases->/data/local/tmp/CtsKernelConfigTestCases" />
@@ -22,5 +23,8 @@
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="CtsKernelConfigTestCases" />
+        <!-- Make sure there is some data in the pstore -->
+        <option name="before-test-cmd" value="echo HELLOWORLD >/dev/pmsg0" />
+        <option name="reboot-before-test" value="true" />
     </test>
 </configuration>
diff --git a/tests/sdcard/sdcard_perf_test.cpp b/tests/sdcard/sdcard_perf_test.cpp
index c93c52b..7efa650 100644
--- a/tests/sdcard/sdcard_perf_test.cpp
+++ b/tests/sdcard/sdcard_perf_test.cpp
@@ -132,7 +132,7 @@
            "  -s --size:        Size in kbytes of the data.\n"
            "  -S --chunk-size:  Size of a chunk. Default to size ie 1 chunk.\n"
            "                    Data will be written/read using that chunk size.\n"
-           "  -D --depth:       Depth of directory tree to create for traversal.\n",
+           "  -D --depth:       Depth of directory tree to create for traversal.\n"
            "  -i --iterations:  Number of time a process should carry its task.\n"
            "  -p --procnb:      Number of processes to use.\n"
            "  -d --dump:        Print the raw timing on stdout.\n"
diff --git a/tests/workloads/pwrsummary.sh b/tests/workloads/pwrsummary.sh
index 3d3aeb8..c527b54 100755
--- a/tests/workloads/pwrsummary.sh
+++ b/tests/workloads/pwrsummary.sh
@@ -99,7 +99,7 @@
 	# 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
+	# stdbuf -o0 adb shell /data/local/tmp/hwuimacro shadowgrid2 400 | stdbuf -o0 ./hwuitestfilter.sh  | tee t.csv
 	sed -e 's/ns//' -e 's/[\(\)%]/ /g' | awk '
 	BEGIN { startTime=0; lastTime=0; }
 	/^Stats since:/ {
diff --git a/tests/workloads/pwrtest.sh b/tests/workloads/pwrtest.sh
index 39f7b11..fd5d825 100755
--- a/tests/workloads/pwrtest.sh
+++ b/tests/workloads/pwrtest.sh
@@ -94,16 +94,16 @@
 
 case $DEVICE in
 (shamu|hammerhead)
-	HWUITEST=hwuitest
+	HWUIMACRO=hwuimacro
 	onSwipe="700 1847 700 400 50"
 	;;
 (*)
-	HWUITEST=hwuitest64
+	HWUIMACRO=hwuimacro64
 	onSwipe="500 1200 500 550 150"
 	;;
 esac
 
-scripts="defs.sh systemapps.sh recentfling.sh youtube.sh chromefling.sh $HWUITEST"
+scripts="defs.sh systemapps.sh recentfling.sh youtube.sh chromefling.sh"
 
 if ! $MONSOON >/dev/null 2>&1; then
 	echo $MONSOON must be in your PATH >&2
@@ -253,6 +253,7 @@
 
 echo Copying $scripts to device $devdir...
 copy_files
+adb shell ln -s /data/benchmarktest/hwuimacro/$HWUIMACRO $devdir/$HWUIMACRO
 tests=""
 
 # measure background power
@@ -332,9 +333,9 @@
 if [ $shadowgrid2Time -gt 0 ]; then
 	airplane_mode on
 	echo $(date) Test 4 : shadowgrid2 for $shadowgrid2Time minutes
-	start_job "./$HWUITEST shadowgrid2 100000"
+	start_job "./$HWUIMACRO --onscreen shadowgrid2 100000"
 	run_test shadowgrid2 $shadowgrid2Time
-	cleanup_job shadowgrid2 $HWUITEST
+	cleanup_job shadowgrid2 $HWUIMACRO
 	airplane_mode off
 	date
 	tests="$tests shadowgrid2"
diff --git a/verity/build_verity_tree.cpp b/verity/build_verity_tree.cpp
index c50e449..69c761d 100644
--- a/verity/build_verity_tree.cpp
+++ b/verity/build_verity_tree.cpp
@@ -10,11 +10,11 @@
 #include <fcntl.h>
 #include <inttypes.h>
 #include <limits.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <vector>
 
 #include <android-base/file.h>
 
@@ -126,7 +126,7 @@
 {
     char *data_filename;
     char *verity_filename;
-    unsigned char *salt = NULL;
+    std::vector<unsigned char> salt;
     size_t salt_size = 0;
     bool sparse = false;
     size_t block_size = 4096;
@@ -150,12 +150,8 @@
 
         switch (c) {
         case 'a':
-            salt_size = strlen(optarg);
-            salt = new unsigned char[salt_size]();
-            if (salt == NULL) {
-                FATAL("failed to allocate memory for salt\n");
-            }
-            memcpy(salt, optarg, salt_size);
+            salt.clear();
+            salt.insert(salt.end(), optarg, &optarg[strlen(optarg)]);
             break;
         case 'A': {
                 BIGNUM *bn = NULL;
@@ -163,11 +159,8 @@
                     FATAL("failed to convert salt from hex\n");
                 }
                 salt_size = BN_num_bytes(bn);
-                salt = new unsigned char[salt_size]();
-                if (salt == NULL) {
-                    FATAL("failed to allocate memory for salt\n");
-                }
-                if((size_t)BN_bn2bin(bn, salt) != salt_size) {
+                salt.resize(salt_size);
+                if((size_t)BN_bn2bin(bn, salt.data()) != salt_size) {
                     FATAL("failed to convert salt to bytes\n");
                 }
             }
@@ -214,19 +207,16 @@
     size_t hash_size = EVP_MD_size(md);
     assert(hash_size * 2 < block_size);
 
-    if (!salt || !salt_size) {
+    if (salt.data() || !salt_size) {
         salt_size = hash_size;
-        salt = new unsigned char[salt_size];
-        if (salt == NULL) {
-            FATAL("failed to allocate memory for salt\n");
-        }
+        salt.resize(salt_size);
 
         int random_fd = open("/dev/urandom", O_RDONLY);
         if (random_fd < 0) {
             FATAL("failed to open /dev/urandom\n");
         }
 
-        ssize_t ret = read(random_fd, salt, salt_size);
+        ssize_t ret = read(random_fd, salt.data(), salt_size);
         if (ret != (ssize_t)salt_size) {
             FATAL("failed to read %zu bytes from /dev/urandom: %zd %d\n", salt_size, ret, errno);
         }
@@ -310,14 +300,14 @@
     unsigned char zero_block_hash[hash_size];
     unsigned char zero_block[block_size];
     memset(zero_block, 0, block_size);
-    hash_block(md, zero_block, block_size, salt, salt_size, zero_block_hash, NULL);
+    hash_block(md, zero_block, block_size, salt.data(), salt_size, zero_block_hash, NULL);
 
     unsigned char root_hash[hash_size];
     verity_tree_levels[levels] = root_hash;
 
     struct sparse_hash_ctx ctx;
     ctx.hashes = verity_tree_levels[0];
-    ctx.salt = salt;
+    ctx.salt = salt.data();
     ctx.salt_size = salt_size;
     ctx.hash_size = hash_size;
     ctx.block_size = block_size;
@@ -334,7 +324,7 @@
         hash_blocks(md,
                 verity_tree_levels[i], verity_tree_level_blocks[i] * block_size,
                 verity_tree_levels[i + 1], &out_size,
-                salt, salt_size, block_size);
+                salt.data(), salt_size, block_size);
           if (i < levels - 1) {
               assert(div_round_up(out_size, block_size) == verity_tree_level_blocks[i + 1]);
           } else {
@@ -347,7 +337,7 @@
     }
     printf(" ");
     for (size_t i = 0; i < salt_size; i++) {
-        printf("%02x", salt[i]);
+        printf("%02x", salt.data()[i]);
     }
     printf("\n");
 
@@ -363,5 +353,4 @@
     delete[] verity_tree_levels;
     delete[] verity_tree_level_blocks;
     delete[] verity_tree;
-    delete[] salt;
 }
diff --git a/verity/fec/image.cpp b/verity/fec/image.cpp
index 509b102..861ca60 100644
--- a/verity/fec/image.cpp
+++ b/verity/fec/image.cpp
@@ -28,7 +28,6 @@
 #include <getopt.h>
 #include <openssl/sha.h>
 #include <pthread.h>
-#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
diff --git a/verity/fec/main.cpp b/verity/fec/main.cpp
index 93f1ec2..6063a66 100644
--- a/verity/fec/main.cpp
+++ b/verity/fec/main.cpp
@@ -25,7 +25,6 @@
 #include <getopt.h>
 #include <fcntl.h>
 #include <pthread.h>
-#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>