OSDN Git Service

am a0370d7b: Merge "Implement iotop"
authorColin Cross <ccross@android.com>
Sat, 5 Sep 2015 19:38:42 +0000 (19:38 +0000)
committerAndroid Git Automerger <android-git-automerger@android.com>
Sat, 5 Sep 2015 19:38:42 +0000 (19:38 +0000)
* commit 'a0370d7bfea440286c3b4df3253e632377ecc326':
  Implement iotop

19 files changed:
ext4_utils/canned_fs_config.c
ext4_utils/canned_fs_config.h
ext4_utils/ext4_crypt_init_extensions.cpp
ext4_utils/ext4_utils.h
ext4_utils/make_ext4fs.c
ext4_utils/make_ext4fs_main.c
ext4_utils/mkuserimg.sh
f2fs_utils/f2fs_sparseblock.c
simpleperf/workload.cpp
squashfs_utils/mksquashfsimage.sh
tests/ext4/Android.mk
tests/workloads/atrace-uncompress.py [new file with mode: 0644]
tests/workloads/capture.sh [new file with mode: 0755]
tests/workloads/defs.sh [new file with mode: 0755]
tests/workloads/feedly-chrome.sh [new file with mode: 0755]
tests/workloads/recentfling.sh [new file with mode: 0755]
tests/workloads/systemapps.sh [new file with mode: 0755]
verity/Utils.java
verity/VerityVerifier.java

index 69646b1..089a8df 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <inttypes.h>
 #include <stdio.h>
 #include <string.h>
 #include <errno.h>
@@ -80,7 +81,9 @@ int load_canned_fs_config(const char* fn) {
        return 0;
 }
 
-void canned_fs_config(const char* path, int dir,
+static const int kDebugCannedFsConfig = 0;
+
+void canned_fs_config(const char* path, int dir, const char* target_out_path,
                                          unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities) {
        Path key;
        key.path = path+1;   // canned paths lack the leading '/'
@@ -94,16 +97,20 @@ void canned_fs_config(const char* path, int dir,
        *mode = p->mode;
        *capabilities = p->capabilities;
 
-#if 0
-       // for debugging, run the built-in fs_config and compare the results.
-
-       unsigned c_uid, c_gid, c_mode;
-       uint64_t c_capabilities;
-       fs_config(path, dir, &c_uid, &c_gid, &c_mode, &c_capabilities);
-
-       if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid);
-       if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid);
-       if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode);
-       if (c_capabilities != *capabilities) printf("%s capabilities %llx %llx\n", path, *capabilities, c_capabilities);
-#endif
+       if (kDebugCannedFsConfig) {
+               // for debugging, run the built-in fs_config and compare the results.
+
+               unsigned c_uid, c_gid, c_mode;
+               uint64_t c_capabilities;
+               fs_config(path, dir, target_out_path, &c_uid, &c_gid, &c_mode, &c_capabilities);
+
+               if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid);
+               if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid);
+               if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode);
+               if (c_capabilities != *capabilities)
+                       printf("%s capabilities %" PRIx64 " %" PRIx64 "\n",
+                               path,
+                               *capabilities,
+                               c_capabilities);
+        }
 }
index aec923b..d9f51ca 100644 (file)
@@ -20,7 +20,7 @@
 #include <inttypes.h>
 
 int load_canned_fs_config(const char* fn);
-void canned_fs_config(const char* path, int dir,
+void canned_fs_config(const char* path, int dir, const char* target_out_path,
                       unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities);
 
 #endif
index fc54a2f..aab7cbf 100644 (file)
@@ -27,7 +27,7 @@ static std::string vold_command(std::string const& command)
     int sock = -1;
 
     while (true) {
-        sock = socket_local_client("vold",
+        sock = socket_local_client("cryptd",
                                    ANDROID_SOCKET_NAMESPACE_RESERVED,
                                    SOCK_STREAM);
         if (sock >= 0) {
@@ -61,18 +61,19 @@ static std::string vold_command(std::string const& command)
 
     struct pollfd poll_sock = {sock, POLLIN, 0};
 
-    int rc = poll(&poll_sock, 1, vold_command_timeout_ms);
+    int rc = TEMP_FAILURE_RETRY(poll(&poll_sock, 1, vold_command_timeout_ms));
     if (rc < 0) {
         KLOG_ERROR(TAG, "Error in poll %s\n", strerror(errno));
         return "";
     }
+
     if (!(poll_sock.revents & POLLIN)) {
         KLOG_ERROR(TAG, "Timeout\n");
         return "";
     }
     char buffer[4096];
     memset(buffer, 0, sizeof(buffer));
-    rc = read(sock, buffer, sizeof(buffer));
+    rc = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
     if (rc <= 0) {
         if (rc == 0) {
             KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
index 8cc8c2c..0159dbe 100644 (file)
@@ -152,12 +152,12 @@ u64 parse_num(const char *arg);
 void ext4_parse_sb_info(struct ext4_super_block *sb);
 u16 ext4_crc16(u16 crc_in, const void *buf, int size);
 
-typedef void (*fs_config_func_t)(const char *path, int dir, unsigned *uid, unsigned *gid,
-               unsigned *mode, uint64_t *capabilities);
+typedef void (*fs_config_func_t)(const char *path, int dir, const char *target_out_path,
+        unsigned *uid, unsigned *gid, unsigned *mode, uint64_t *capabilities);
 
 struct selabel_handle;
 
-int make_ext4fs_internal(int fd, const char *directory,
+int make_ext4fs_internal(int fd, const char *directory, const char *_target_out_directory,
                                                 const char *mountpoint, fs_config_func_t fs_config_func, int gzip,
                                                 int sparse, int crc, int wipe, int real_uuid,
                                                 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
index 52c3208..b4ebbce 100644 (file)
@@ -123,7 +123,7 @@ static u32 build_default_directory_structure(const char *dir_path,
    that does not exist on disk (e.g. lost+found).
    dir_path is an absolute path, with trailing slash, to the same directory
    if the image were mounted at the specified mount point */
-static u32 build_directory_structure(const char *full_path, const char *dir_path,
+static u32 build_directory_structure(const char *full_path, const char *dir_path, const char *target_out_path,
                u32 dir_inode, fs_config_func_t fs_config_func,
                struct selabel_handle *sehnd, int verbose, time_t fixed_time)
 {
@@ -201,7 +201,7 @@ static u32 build_directory_structure(const char *full_path, const char *dir_path
                        unsigned int uid = 0;
                        unsigned int gid = 0;
                        int dir = S_ISDIR(stat.st_mode);
-                       fs_config_func(dentries[i].path, dir, &uid, &gid, &mode, &capabilities);
+                       fs_config_func(dentries[i].path, dir, target_out_path, &uid, &gid, &mode, &capabilities);
                        dentries[i].mode = mode;
                        dentries[i].uid = uid;
                        dentries[i].gid = gid;
@@ -285,8 +285,8 @@ static u32 build_directory_structure(const char *full_path, const char *dir_path
                        ret = asprintf(&subdir_dir_path, "%s/", dentries[i].path);
                        if (ret < 0)
                                critical_error_errno("asprintf");
-                       entry_inode = build_directory_structure(subdir_full_path,
-                                       subdir_dir_path, inode, fs_config_func, sehnd, verbose, fixed_time);
+                       entry_inode = build_directory_structure(subdir_full_path, subdir_dir_path, target_out_path,
+                                       inode, fs_config_func, sehnd, verbose, fixed_time);
                        free(subdir_full_path);
                        free(subdir_dir_path);
                } else if (dentries[i].file_type == EXT4_FT_SYMLINK) {
@@ -404,7 +404,7 @@ int make_ext4fs_sparse_fd(int fd, long long len,
        reset_ext4fs_info();
        info.len = len;
 
-       return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, 0, sehnd, 0, -1, NULL);
+       return make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 1, 0, 0, 0, sehnd, 0, -1, NULL);
 }
 
 int make_ext4fs(const char *filename, long long len,
@@ -422,7 +422,7 @@ int make_ext4fs(const char *filename, long long len,
                return EXIT_FAILURE;
        }
 
-       status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, 0, sehnd, 0, -1, NULL);
+       status = make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 0, 0, 1, 0, sehnd, 0, -1, NULL);
        close(fd);
 
        return status;
@@ -488,7 +488,7 @@ static char *canonicalize_rel_slashes(const char *str)
        return canonicalize_slashes(str, false);
 }
 
-int make_ext4fs_internal(int fd, const char *_directory,
+int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out_directory,
                                                 const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
                                                 int sparse, int crc, int wipe, int real_uuid,
                                                 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
@@ -498,6 +498,7 @@ int make_ext4fs_internal(int fd, const char *_directory,
        u16 root_mode;
        char *mountpoint;
        char *directory = NULL;
+       char *target_out_directory = NULL;
 
        if (setjmp(setjmp_env))
                return EXIT_FAILURE; /* Handle a call to longjmp() */
@@ -519,6 +520,10 @@ int make_ext4fs_internal(int fd, const char *_directory,
                directory = canonicalize_rel_slashes(_directory);
        }
 
+       if (_target_out_directory) {
+               target_out_directory = canonicalize_rel_slashes(_target_out_directory);
+       }
+
        if (info.len <= 0)
                info.len = get_file_size(fd);
 
@@ -607,7 +612,7 @@ int make_ext4fs_internal(int fd, const char *_directory,
        root_inode_num = build_default_directory_structure(mountpoint, sehnd);
 #else
        if (directory)
-               root_inode_num = build_directory_structure(directory, mountpoint, 0,
+               root_inode_num = build_directory_structure(directory, mountpoint, target_out_directory, 0,
                        fs_config_func, sehnd, verbose, fixed_time);
        else
                root_inode_num = build_default_directory_structure(mountpoint, sehnd);
index f28e1b2..03872db 100644 (file)
@@ -57,7 +57,7 @@ static void usage(char *path)
        fprintf(stderr, "    [ -L <label> ] [ -f ] [ -a <android mountpoint> ] [ -u ]\n");
        fprintf(stderr, "    [ -S file_contexts ] [ -C fs_config ] [ -T timestamp ]\n");
        fprintf(stderr, "    [ -z | -s ] [ -w ] [ -c ] [ -J ] [ -v ] [ -B <block_list_file> ]\n");
-       fprintf(stderr, "    <filename> [<directory>]\n");
+       fprintf(stderr, "    <filename> [[<directory>] <target_out_directory>]\n");
 }
 
 int main(int argc, char **argv)
@@ -65,6 +65,7 @@ int main(int argc, char **argv)
        int opt;
        const char *filename = NULL;
        const char *directory = NULL;
+       const char *target_out_directory = NULL;
        char *mountpoint = NULL;
        fs_config_func_t fs_config_func = NULL;
        const char *fs_config_file = NULL;
@@ -216,6 +217,9 @@ int main(int argc, char **argv)
        if (optind < argc)
                directory = argv[optind++];
 
+       if (optind < argc)
+               target_out_directory = argv[optind++];
+
        if (optind < argc) {
                fprintf(stderr, "Unexpected argument: %s\n", argv[optind]);
                usage(argv[0]);
@@ -232,7 +236,7 @@ int main(int argc, char **argv)
                fd = STDOUT_FILENO;
        }
 
-       exitcode = make_ext4fs_internal(fd, directory, mountpoint, fs_config_func, gzip,
+       exitcode = make_ext4fs_internal(fd, directory, target_out_directory, mountpoint, fs_config_func, gzip,
                sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time, block_list_file);
        close(fd);
        if (block_list_file)
index 3a6006e..8667013 100755 (executable)
@@ -6,7 +6,7 @@ function usage() {
 cat<<EOT
 Usage:
 mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>]
-             [-T TIMESTAMP] [-C FS_CONFIG] [-B BLOCK_LIST_FILE] [-L LABEL] [FILE_CONTEXTS]
+             [-T TIMESTAMP] [-C FS_CONFIG] [-D PRODUCT_OUT] [-B BLOCK_LIST_FILE] [-L LABEL] [FILE_CONTEXTS]
 EOT
 }
 
@@ -55,6 +55,12 @@ if [[ "$1" == "-C" ]]; then
   shift; shift
 fi
 
+PRODUCT_OUT=
+if [[ "$1" == "-D" ]]; then
+  PRODUCT_OUT=$2
+  shift; shift
+fi
+
 BLOCK_LIST=
 if [[ "$1" == "-B" ]]; then
   BLOCK_LIST=$2
@@ -98,7 +104,7 @@ if [ -n "$LABEL" ]; then
   OPT="$OPT -L $LABEL"
 fi
 
-MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE $JOURNAL_FLAGS -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR"
+MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE $JOURNAL_FLAGS -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR $PRODUCT_OUT"
 echo $MAKE_EXT4FS_CMD
 $MAKE_EXT4FS_CMD
 if [ $? -ne 0 ]; then
index 950628c..e39a61f 100644 (file)
@@ -262,13 +262,13 @@ int get_valid_checkpoint_info(int fd, struct f2fs_super_block *sb, struct f2fs_c
 
     struct f2fs_checkpoint *cp1, *cp2, *cur_cp;
     int cur_cp_no;
-    unsigned long blk_size;// = 1<<le32_to_cpu(info->sb->log_blocksize);
+    unsigned long blk_size;
     unsigned long long cp1_version = 0, cp2_version = 0;
     unsigned long long cp1_start_blk_no;
     unsigned long long cp2_start_blk_no;
     u32 bmp_size;
 
-    blk_size = 1U<<le32_to_cpu(sb->log_blocksize);
+    blk_size = 1U << le32_to_cpu(sb->log_blocksize);
 
     /*
      * Find valid cp by reading both packs and finding most recent one.
@@ -489,7 +489,8 @@ int run_on_used_blocks(u64 startblock, struct f2fs_info *info, int (*func)(u64 p
     u64 block;
     unsigned int used, found, started = 0, i;
 
-    for (block=startblock; block<info->total_blocks; block++) {
+    block = startblock;
+    while (block < info->total_blocks) {
         /* TODO: Save only relevant portions of metadata */
         if (block < info->main_blkaddr) {
             if (func(block, data)) {
@@ -512,17 +513,24 @@ int run_on_used_blocks(u64 startblock, struct f2fs_info *info, int (*func)(u64 p
 
             /* get SIT entry from SIT section */
             if (!found) {
-                sit_block_num_cur = segnum/SIT_ENTRY_PER_BLOCK;
+                sit_block_num_cur = segnum / SIT_ENTRY_PER_BLOCK;
                 sit_entry = &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK];
             }
 
             block_offset = (block - info->main_blkaddr) % info->blocks_per_segment;
 
+            if (block_offset == 0 && GET_SIT_VBLOCKS(sit_entry) == 0) {
+                block += info->blocks_per_segment;
+                continue;
+            }
+
             used = f2fs_test_bit(block_offset, (char *)sit_entry->valid_map);
             if(used)
                 if (func(block, data))
                     return -1;
         }
+
+        block++;
     }
     return 0;
 }
@@ -548,7 +556,7 @@ int copy_used(u64 pos, void *data)
 {
     struct privdata *d = data;
     char *buf;
-    int pdone = (pos*100)/d->info->total_blocks;
+    int pdone = (pos * 100) / d->info->total_blocks;
     if (pdone > d->done) {
         d->done = pdone;
         printf("Done with %d percent\n", d->done);
@@ -562,7 +570,7 @@ int copy_used(u64 pos, void *data)
     }
 
     off64_t ret;
-    ret = lseek64(d->outfd, pos*F2FS_BLKSIZE, SEEK_SET);
+    ret = lseek64(d->outfd, pos * F2FS_BLKSIZE, SEEK_SET);
     if (ret < 0) {
         SLOGE("failed to seek\n");
         return ret;
index 9138afa..6f07cda 100644 (file)
@@ -108,10 +108,11 @@ static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd,
     TEMP_FAILURE_RETRY(write(exec_child_fd, &exec_child_failed, 1));
     close(exec_child_fd);
     errno = saved_errno;
-    PLOG(FATAL) << "execvp(" << argv[0] << ") failed";
+    PLOG(ERROR) << "execvp(" << argv[0] << ") failed";
   } else {
-    PLOG(FATAL) << "child process failed to receive start_signal, nread = " << nread;
+    PLOG(DEBUG) << "child process failed to receive start_signal, nread = " << nread;
   }
+  exit(1);
 }
 
 bool Workload::Start() {
index 260a0fd..1bc2b83 100755 (executable)
@@ -5,7 +5,7 @@
 function usage() {
 cat<<EOT
 Usage:
-${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-c FILE_CONTEXTS] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT]
+${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-d PRODUCT_OUT] [-c FILE_CONTEXTS] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT]
 EOT
 }
 
@@ -36,6 +36,12 @@ if [[ "$1" == "-m" ]]; then
     shift; shift
 fi
 
+PRODUCT_OUT=
+if [[ "$1" == "-d" ]]; then
+    PRODUCT_OUT=$2
+    shift; shift
+fi
+
 FILE_CONTEXTS=
 if [[ "$1" == "-c" ]]; then
     FILE_CONTEXTS=$2
@@ -65,6 +71,9 @@ OPT=""
 if [ -n "$MOUNT_POINT" ]; then
   OPT="$OPT -mount-point $MOUNT_POINT"
 fi
+if [ -n "$PRODUCT_OUT" ]; then
+  OPT="$OPT -product-out $PRODUCT_OUT"
+fi
 if [ -n "$FILE_CONTEXTS" ]; then
   OPT="$OPT -context-file $FILE_CONTEXTS"
 fi
index e471ef9..1ccc4ef 100644 (file)
@@ -1,11 +1,15 @@
 # Copyright 2012 The Android Open Source Project
 
+local_target_dir := $(TARGET_OUT_DATA)/local/tmp
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= rand_emmc_perf.c
 LOCAL_MODULE:= rand_emmc_perf
+LOCAL_MODULE_STEM_32:= rand_emmc_perf
+LOCAL_MODULE_STEM_64:= rand_emmc_perf64
 LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(local_target_dir)
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_STATIC_LIBRARIES := libm libc
 
diff --git a/tests/workloads/atrace-uncompress.py b/tests/workloads/atrace-uncompress.py
new file mode 100644 (file)
index 0000000..5efb698
--- /dev/null
@@ -0,0 +1,35 @@
+#
+# Uncompress a file generated via atrace -z
+#
+# Usage: python atrace-uncompress.py infile > outfile
+#
+import sys, zlib
+
+def main():
+
+       if len(sys.argv) != 2:
+               print >> sys.stderr, ('Usage: %s inputfile' % sys.argv[0])
+               sys.exit(1)
+
+       infile = open(sys.argv[1], "rb")
+       out = infile.read()
+       parts = out.split('\nTRACE:', 1)
+
+       data = ''.join(parts[1])
+
+       # Remove CR characters
+       if data.startswith('\r\n'):
+               data = data.replace('\r\n', '\n')
+
+       # Skip the initial newline.
+       data = data[1:]
+
+       if not data:
+               print >> sys.stderr, ('No trace data found')
+               sys.exit(1)
+
+       out = zlib.decompress(data)
+       print(out)
+
+if __name__ == '__main__':
+       main()
diff --git a/tests/workloads/capture.sh b/tests/workloads/capture.sh
new file mode 100755 (executable)
index 0000000..3b2f446
--- /dev/null
@@ -0,0 +1,74 @@
+# Capture and display input events and coordinates
+#
+# Usage: ./capture.sh
+#
+
+# do a throw-away adb in case the server is out-of-date
+adb devices -l 2>&1 >/dev/null
+
+while [ $# -gt 0 ]
+do
+       case "$1" in
+       (-d) DEVICE=$2; shift;;
+       (*)
+               echo Unknown option $1
+               exit 1;;
+       esac
+       shift
+done
+
+if [ "$DEVICE" = "" ]; then
+       devInfo=$(adb devices -l | grep -v ^List | head -1)
+       set -- $devInfo
+       echo devInfo=$devInfo
+       DEVICE=$(echo $4 | sed 's/product://')
+fi
+
+function convert {
+       in=$1
+       max=$2
+       scale=$3
+       if [ $max -eq 0 ]; then
+               echo $in
+       else
+               ((out=in*scale/max))
+               echo $out
+       fi
+}
+
+
+case $DEVICE in
+(shamu|hammerhead|bullhead)
+       # no scaling necessary
+       xmax=0
+       ymax=0;;
+(volantis)
+       xmax=3060
+       xscale=1500
+       ymax=2304
+       yscale=1950;;
+(*)
+       echo "Error: No display information available for $DEVICE"
+       exit 1;;
+esac
+
+echo Capturing input for $DEVICE...
+stdbuf -o0 adb shell getevent -t |
+       stdbuf -o0 grep "event.: 0003" |
+       stdbuf -o0 grep "0003 003[0156a9]" |
+       stdbuf -o0 tr ':[]\r' ' ' | while read line
+do
+       set -- $line
+       code=$4
+       value=$((16#$5))
+       case $code in
+       (0035) x=$(convert $value $xmax $xscale);;
+       (0036) y=$(convert $value $ymax $yscale);;
+       (0030) tag="majorTouch";;
+       (0031) tag="minorTouch";;
+       (003a) tag="pressure";;
+       (0039) tag="trackingId";;
+       (--) echo unknown code=$code;;
+       esac
+       printf "%-10s %-4d  %-4d\n" $tag $x $y
+done
diff --git a/tests/workloads/defs.sh b/tests/workloads/defs.sh
new file mode 100755 (executable)
index 0000000..a2b7138
--- /dev/null
@@ -0,0 +1,442 @@
+# functions and definitions for workload automation scripts
+#
+# See recentfling.sh, systemapps.sh, and other scripts that use
+# these definitions.
+#
+
+dflttracecategories="gfx input view am rs power sched freq idle load memreclaim"
+dfltAppList="gmail hangouts chrome youtube camera photos play maps calendar earth calculator sheets docs home"
+generateActivities=0
+
+# default activities. Can dynamically generate with -g.
+gmailActivity='com.google.android.gm/com.google.android.gm.ConversationListActivityGmail'
+hangoutsActivity='com.google.android.talk/com.google.android.talk.SigningInActivity'
+chromeActivity='com.android.chrome/_not_used'
+youtubeActivity='com.google.android.youtube/com.google.android.apps.youtube.app.WatchWhileActivity'
+cameraActivity='com.google.android.GoogleCamera/com.android.camera.CameraActivity'
+playActivity='com.android.vending/com.google.android.finsky.activities.MainActivity'
+feedlyActivity='com.devhd.feedly/com.devhd.feedly.Main'
+photosActivity='com.google.android.apps.plus/com.google.android.apps.photos.phone.PhotosHomeActivity'
+mapsActivity='com.google.android.apps.maps/com.google.android.maps.MapsActivity'
+calendarActivity='com.google.android.calendar/com.android.calendar.AllInOneActivity'
+earthActivity='com.google.earth/com.google.earth.EarthActivity'
+calculatorActivity='com.android.calculator2/com.android.calculator2.Calculator'
+sheetsActivity='com.google.android.apps.docs.editors.sheets/com.google.android.apps.docs.app.NewMainProxyActivity'
+docsActivity='com.google.android.apps.docs.editors.docs/com.google.android.apps.docs.app.NewMainProxyActivity'
+operaActivity='com.opera.mini.native/com.opera.mini.android.Browser'
+firefoxActivity='org.mozilla.firefox/org.mozilla.firefox.App'
+homeActivity='com.google.android.googlequicksearchbox/com.google.android.launcher.GEL'
+
+function showUsage {
+       echo "$0: unrecognized option: $1"
+       echo; echo "Usage: $0 [options]"
+       echo "-e : stop on error"
+       echo "-i iterations"
+       echo "-n : keep trace files"
+       echo "-o output file"
+       echo "-s device : adb device"
+       echo "-t trace categories"
+       echo "-g : generate activity strings"
+}
+
+DEVICE=unknown
+
+# handle args
+while [ $# -gt 0 ]
+do
+       case "$1" in
+       (-d) DEVICE=$2; shift;;
+       (-e) stoponerror=1;;
+       (-n) savetmpfiles=1;;
+       (-t) tracecategories=$2; shift;;
+       (-i) iterations=$2; shift;;
+       (-o) output=$2; shift;;
+       (-v) verbose=1;;
+       (-nz) compress=0;;
+       (-s) deviceName=$2; shift;;
+       (-g) generateActivities=1;;
+       (--) ;;
+       (*)
+               chk1=$(functions 2>/dev/null)
+               chk2=$(typeset -F 2>/dev/null)
+
+               if echo $chk1 $chk2 | grep -q processLocalOption; then
+                       if ! processLocalOption "$1" "$2"; then
+                               shift
+                       fi
+               else
+                       showUsage $1
+                       exit 1
+               fi;;
+       esac
+       shift
+done
+
+# check if running on a device
+if ls /etc/* 2>/dev/null | grep -q android.hardware; then
+       ADB=""
+       compress=0
+       isOnDevice=1
+else
+       # do a throw-away adb in case the server is out-of-date
+       adb devices -l 2>&1 >/dev/null
+
+       if [ -z "$deviceName" ]; then
+               devInfo=$(adb devices -l | grep -v ^List | head -1)
+       else
+               devInfo=$(adb devices -l | grep $deviceName)
+       fi
+       set -- $devInfo
+       if [ -z $1 ]; then
+               echo Error: could not find device $deviceName
+               exit 1
+       fi
+       deviceName=$1
+       ADB="adb -s $deviceName shell "
+       DEVICE=$(echo $4 | sed 's/product://')
+       isOnDevice=0
+fi
+
+# default values if not set by options or calling script
+appList=${appList:=$dfltAppList}
+savetmpfiles=${savetmpfiles:=0}
+stoponerror=${stoponerror:=0}
+verbose=${verbose:=0}
+compress=${compress:=1}
+iterations=${iterations:=5}
+tracecategories=${tracecategories:=$dflttracecategories}
+ADB=${ADB:=""}
+output=${output:="./out"}
+
+# clear the output file
+> $output
+
+# ADB commands
+AM_FORCE_START="${ADB}am start -W -S"
+AM_START="${ADB}am start -W"
+AM_START_NOWAIT="${ADB}am start"
+AM_STOP="${ADB}am force-stop"
+AM_LIST="${ADB}am stack list"
+WHO="${ADB}whoami"
+INPUT="${ADB}input"
+PS="${ADB}ps"
+
+function vout {
+       # debug output enabled by -v
+       if [ $verbose -gt 0 ]; then
+           echo DEBUG: $* >&2
+           echo DEBUG: $* >&2 >> $output
+       fi
+}
+
+function findtimestamp {
+       # extract timestamp from atrace log entry
+       while [ "$2" != "" -a "$2" != "tracing_mark_write" ]
+       do
+               shift
+       done
+       echo $1
+}
+
+function computeTimeDiff {
+       # Compute time diff given: startSeconds startNs endSeconds endNS
+
+       # strip leading zeros
+       startS=$(expr 0 + $1)
+       endS=$(expr 0 + $3)
+       if [ "$2" = N ]; then
+               startNs=0
+               endNs=0
+       else
+               startNs=$(expr 0 + $2)
+               endNs=$(expr 0 + $4)
+       fi
+
+       ((startMs=startS*1000 + startNs/1000000))
+       ((endMs=endS*1000 + endNs/1000000))
+       ((diff=endMs-startMs))
+       echo $diff
+}
+
+function log2msec {
+       in=$1
+       in=${in:=0.0}
+       set -- $(echo $in | tr . " ")
+       # shell addition via (( )) doesn't like leading zeroes in msecs
+       # field so remove leading zeroes
+       msecfield=$(expr 0 + $2)
+
+       ((msec=$1*1000000+msecfield))
+       ((msec=msec/1000))
+       echo $msec
+}
+
+function getStartTime {
+       # extract event indicating beginning of start sequence
+       # a) look for a "launching" event indicating start from scratch
+       # b) look for another activity getting a pause event
+       _app=$1
+       traceout=$2
+       ret=0
+       s=$(grep "Binder.*tracing_mark_write.*launching" $traceout 2>/dev/null | head -1| tr [\(\)\[\]\r:] " ")
+       if [ -z "$s" ]; then
+               s=$(grep activityPause $traceout | head -1 2>/dev/null| tr [\(\)\[\]\r:] " ")
+       else
+               vout $_app was restarted!
+               ret=1
+       fi
+       vout STARTLOG: $s
+       log2msec $(findtimestamp $s)
+       return $ret
+}
+
+function getEndTime {
+       # extract event indicating end of start sequence. We use the
+       # first surfaceflinger event associated with the target activity
+       _app=$1
+       traceout=$2
+       f=$(grep "surfaceflinger.*tracing_mark_write.*$_app" $traceout 2>/dev/null |
+               grep -v Starting | head -1 | tr [\(\)\[\]\r:] " ")
+       if [ -z "$f" ]; then
+               # Hmm. sf symbols may not be there... get the pid
+               pid=$(${ADB}pidof /system/bin/surfaceflinger | tr "[\r]" "[ ]")
+               f=$(grep "           <...>-$pid.*tracing_mark_write.*$_app" $traceout 2>/dev/null |
+                       grep -v Starting | head -1 | tr [\(\)\[\]\r:] " ")
+       fi
+       vout ENDLOG: $f
+       log2msec $(findtimestamp $f)
+}
+
+function resetJankyFrames {
+       _gfxapp=$1
+       _gfxapp=${app:="com.android.systemui"}
+       ${ADB}dumpsys gfxinfo $_gfxapp reset 2>&1 >/dev/null
+}
+
+function getJankyFrames {
+       _gfxapp=$1
+       _gfxapp=${_gfxapp:="com.android.systemui"}
+
+       # Note: no awk or sed on devices so have to do this
+       # purely with bash
+       total=0
+       janky=0
+       latency=0
+       ${ADB}dumpsys gfxinfo $_gfxapp | tr "\r" " " | egrep "9[059]th| frames" | while read line
+       do
+               if echo $line | grep -q "Total frames"; then
+                       set -- $line
+                       total=$4
+               elif echo $line | grep -q "Janky frames"; then
+                       set -- $line
+                       janky=$3
+               elif echo $line | grep -q "90th"; then
+                       set -- $(echo $line | tr m " ")
+                       l90=$3
+               elif echo $line | grep -q "95th"; then
+                       set -- $(echo $line | tr m " ")
+                       l95=$3
+               elif echo $line | grep -q "99th"; then
+                       set -- $(echo $line | tr m " ")
+                       l99=$3
+                       echo $total $janky $l90 $l95 $l99
+                       break
+               fi
+       done
+}
+
+function checkForDirectReclaim {
+       # look for any reclaim events in atrace output
+       _app=$1
+       traceout=$2
+       if grep -qi reclaim $traceout; then
+          return 1
+       fi
+       return 0
+}
+
+function startInstramentation {
+       # Called at beginning of loop. Turn on instramentation like atrace
+       vout start instramentation $(date)
+       echo =============================== >> $output
+       echo Before iteration >> $output
+       echo =============================== >> $output
+       ${ADB}cat /proc/meminfo 2>&1 >> $output
+       ${ADB}dumpsys meminfo 2>&1 >> $output
+       if [ "$user" = root ]; then
+               vout ${ADB}atrace -b 32768 --async_start $tracecategories
+               ${ADB}atrace -b 32768 --async_start $tracecategories >> $output
+               echo >> $output
+       fi
+}
+
+function stopInstramentation {
+       if [ "$user" = root ]; then
+               vout ${ADB}atrace --async_stop
+               ${ADB}atrace --async_stop > /dev/null
+       fi
+}
+
+function stopAndDumpInstramentation {
+       # Called at beginning of loop. Turn on instramentation like atrace
+       vout stop instramentation $(date)
+       echo =============================== >> $output
+       echo After iteration >> $output
+       echo =============================== >> $output
+       ${ADB}cat /proc/meminfo 2>&1 >> $output
+       ${ADB}dumpsys meminfo 2>&1 >> $output
+       if [ "$user" = root ]; then
+               traceout=$1
+               traceout=${traceout:=$output}
+               echo =============================== >> $traceout
+               echo TRACE >> $traceout
+               echo =============================== >> $traceout
+               if [ $compress -gt 0 ]; then
+                       tmpTrace=./tmptrace.$$
+                       UNCOMPRESS=$CMDDIR/atrace-uncompress.py
+                       > $tmpTrace
+                       zarg="-z"
+                       ${ADB}atrace -z -b 32768 --async_dump >> $tmpTrace
+                       python $UNCOMPRESS $tmpTrace >> $traceout
+                       rm -f $tmpTrace
+               else
+                       ${ADB}atrace $zarg -b 32768 --async_dump >> $traceout
+               fi
+               vout ${ADB}atrace $zarg --async_dump
+               vout ${ADB}atrace --async_stop
+               ${ADB}atrace --async_stop > /dev/null
+       fi
+}
+
+function getActivityName {
+       cmd="actName=\$${1}Activity"
+       eval $cmd
+       echo $actName
+}
+
+function getPackageName {
+       set -- $(getActivityName $1 | tr "[/]" "[ ]")
+       echo $1
+}
+
+function startActivityFromPackage {
+       if [ "$1" = home ]; then
+               doKeyevent HOME
+               echo 0
+               return 0
+       fi
+       vout $AM_START_NOWAIT -p "$(getPackageName $1)" -c android.intent.category.LAUNCHER -a android.intent.action.MAIN
+       $AM_START_NOWAIT -p "$(getPackageName $1)" -c android.intent.category.LAUNCHER -a android.intent.action.MAIN 2>&1
+       echo 0
+}
+
+function startActivity {
+       if [ "$1" = home ]; then
+               doKeyevent HOME
+               echo 0
+               return 0
+       elif [ "$1" = chrome ]; then
+               if [ "$DEVICE" = volantis ]; then
+                       vout $AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com
+                       $AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com > /dev/null
+                       set -- 0 0
+               else
+                       vout $AM_START -p "$(getPackageName $1)" http://www.theverge.com
+                       set -- $($AM_START -p "$(getPackageName $1)" http://www.theverge.com | grep ThisTime)
+               fi
+       else
+               vout $AM_START "$(getActivityName $1)"
+               set -- $($AM_START "$(getActivityName $1)" | grep ThisTime)
+       fi
+       echo $2 | tr "[\r]" "[\n]"
+}
+
+function forceStartActivity {
+       if [ "$1" = chrome ]; then
+               vout $AM_START -p "$(getPackageName $1)" http://www.theverge.com
+               set -- $($AM_FORCE_START -p "$(getPackageName $1)" http://www.theverge.com | grep ThisTime)
+       else
+               vout $AM_FORCE_START "$(getActivityName $1)"
+               set -- $($AM_FORCE_START "$(getActivityName $1)" | grep ThisTime)
+       fi
+       echo $2 | tr "[\r]" "[\n]"
+}
+
+function checkActivity {
+       # requires root
+       actName="$(getActivityName $1)"
+       $AM_LIST | grep $actName
+}
+
+#function stopActivity {
+#    vout $AM_STOP $(getActivityName $1)
+#    $AM_STOP $(getActivityName $1)
+#}
+
+function doSwipe {
+       vout ${ADB}input swipe $*
+       ${ADB}input swipe $*
+}
+
+function doTap {
+       vout ${ADB}input tap $*
+       ${ADB}input tap $*
+}
+
+function doKeyevent {
+       vout $INPUT keyevent $*
+       $INPUT keyevent $*
+}
+
+function checkIsRunning {
+       p=$1
+       shift
+       if ! $PS | grep $p | grep -qv grep; then
+          handleError $*: $p is not running
+          exit 1
+       fi
+}
+
+function checkStartTime {
+       vout checkStartTime $1 v $2
+       if [ -z "$2" ]; then
+           echo false
+           return 2
+       fi
+       if [ "$1" -gt "$2" ]; then
+           echo false
+           return 1
+       fi
+       echo true
+       return 0
+}
+
+function handleError {
+       echo Error: $*
+       stopAndDumpInstramentation
+       if [ $stoponerror -gt 0 ]; then
+               exit 1
+       fi
+}
+
+user=root
+if ${ADB}ls /data 2>/dev/null | grep -q "Permission denied"; then
+       user=shell
+fi
+vout User is $user
+
+if [ $generateActivities -gt 0  ]; then
+       if [ $isOnDevice -gt 0 ]; then
+               echo Error: cannot generate activity list when run on device
+               exit 1
+       fi
+       echo Generating activities...
+       for app in $appList
+       do
+               startActivityFromPackage $app 2>&1 > /dev/null
+               act=$(${ADB}am stack list | grep $(getPackageName $app) | sed -e 's/\r//' | head -1 | awk '{ print $2; }')
+               eval "${app}Activity=$act"
+               echo "ACTIVITY: $app --> $(getActivityName $app)"
+       done
+fi
+
diff --git a/tests/workloads/feedly-chrome.sh b/tests/workloads/feedly-chrome.sh
new file mode 100755 (executable)
index 0000000..4c7002f
--- /dev/null
@@ -0,0 +1,111 @@
+# Script to automate the following sequence:
+# - Open Feedly
+# - Open an article
+# - Scroll to bottome
+# - Open the same article in Chrome
+# - Scroll the article
+# - Back to Feely (should still be in memory)
+# - Home screen
+# ---- repeat ----
+#
+# Currently works on volantis only (verticle orientation)
+#
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case "$DEVICE" in
+(volantis)
+       echo volantis...
+       feedlyArticle="500 700"
+       feedlyOptions="1480 100"
+       feedlyBrowserSelect="1350 650"
+       feedlyArticleSwipeUp="700 700 700 50 50"
+       feedlyArticleSwipeDown="700 200 700 700 50"
+       chromeSwipe="700 700 700 50 50"
+       ;;
+(shamu|*)
+       echo shamu...
+       feedlyArticle="676 500"
+       feedlyOptions="1327 207"
+       feedlyBrowserSelect="1278 1191"
+       feedlyArticleSwipeUp="700 1847 700 400 50"
+       feedlyArticleSwipeDown="700 400 700 1847 50"
+       chromeSwipe="700 1847 700 400 50"
+       ;;
+(hammerhead|*)
+       echo "Error: No feedly screen geometry information available for $DEVICE"
+       exit 1;;
+esac
+
+feedlySwitchToTime=600
+
+# start feedly, if not installed, error out
+t=$(forceStartActivity feedly)
+checkIsRunning feedly "initial start of feedly"
+echo Feedly start time = ${t}ms
+
+# start chrome, if not installed, error out
+t=$(forceStartActivity chrome)
+checkIsRunning chrome "initial start of chrome"
+echo Chrome start time = ${t}ms
+sleep 1
+
+feedlyStartTimes=0
+
+cur=1
+while [ $cur -le $iterations ]
+do
+       echo =======================================
+       echo Iteration $cur of $iterations
+       echo =======================================
+       startInstramentation
+       t=$(startActivity feedly)
+       if [ $(checkStartTime "$t" $feedlySwitchToTime) != true ]; then
+               handleError Feedly took too long to start: $t v $feedlySwitchToTime: $?
+               # for now, not fatal
+               # exit 1
+       fi
+       sleep 2
+       ((feedlyStartTimes=feedlyStartTimes+t))
+       echo feedly started in ${t}ms
+       checkIsRunning chrome "switch back to feedly"
+       checkIsRunning googlequicksearchbox "switch back to feedly"
+
+       # click on first article
+       doTap $feedlyArticle
+       sleep 2
+
+       # scroll through article
+       doSwipe $feedlyArticleSwipeUp
+       sleep 5
+       checkIsRunning chrome "feedly swipe"
+       checkIsRunning googlequicksearchbox "feedly swipe"
+
+       # scroll back to top
+       doSwipe $feedlyArticleSwipeDown
+       sleep 2
+
+       # switch to chrome
+       # 1. click on menu bar
+       doTap $feedlyOptions
+       sleep 1
+       # 2. click on browser
+       doTap $feedlyBrowserSelect
+       sleep 10
+
+       checkIsRunning feedly "switch to chrome"
+       checkIsRunning googlequicksearchbox "switch to chrome"
+
+       # Now we're back in chrome, swipe to bottom of article
+       doSwipe $chromeSwipe
+       sleep 2
+       checkIsRunning feedly "swiped chrome"
+       stopInstramentation
+       ((cur=cur+1))
+done
+((feedlyAve=feedlyStartTimes/iterations))
+echo Avg start times: feedly: ${feedlyAve}ms
+
+doKeyevent HOME
diff --git a/tests/workloads/recentfling.sh b/tests/workloads/recentfling.sh
new file mode 100755 (executable)
index 0000000..092c8d9
--- /dev/null
@@ -0,0 +1,150 @@
+#
+# Script to start a set of apps, switch to recents and fling it back and forth.
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+# Works for volantis, shamu, and hammerhead. Can be pushed and executed on
+# the device.
+#
+iterations=10
+startapps=1
+capturesystrace=0
+
+function processLocalOption {
+       ret=0
+       case "$1" in
+       (-N) startapps=0;;
+       (-A) unset appList;;
+       (-L) appList=$2; shift; ret=1;;
+       (-T) capturesystrace=1;;
+       (*)
+               echo "$0: unrecognized option: $1"
+               echo; echo "Usage: $0 [options]"
+               echo "-A : use all known applications"
+               echo "-L applist : list of applications"
+               echo "   default: $appList"
+               echo "-N : no app startups, just fling"
+               echo "-g : generate activity strings"
+               echo "-i iterations"
+               echo "-T : capture systrace on each iteration"
+               exit 1;;
+       esac
+       return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(shamu|hammerhead)
+       flingtime=300
+       downCount=2
+       upCount=6
+       UP="70 400 70 100 $flingtime"
+       DOWN="70 100 70 400 $flingtime";;
+(bullhead)
+       flingtime=200
+       downCount=5
+       upCount=5
+       UP="500 1200 500 550 $flingtime"
+       DOWN="500 550 500 1200 $flingtime";;
+(volantis)
+       flingtime=400
+       downCount=5
+       upCount=6
+       UP="70 400 70 70 $flingtime"
+       DOWN="70 70 70 400 $flingtime";;
+(*)
+       echo "Error: No display information available for $DEVICE"
+       exit 1;;
+esac
+
+doKeyevent HOME
+if [ $startapps -gt 0 ]; then
+
+       # start a bunch of apps
+       for app in $appList
+       do
+               echo Starting $app ...
+               t=$(startActivity $app)
+       done
+fi
+
+function swipe {
+       count=0
+       while [ $count -lt $2 ]
+       do
+               doSwipe $1
+               ((count=count+1))
+       done
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+echo Fling recents...
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames
+
+while [ $cur -le $iterations ]
+do
+       if [ $capturesystrace -gt 0 ]; then
+               ${ADB}atrace --async_start -z -c -b 16000 freq gfx view idle sched
+       fi
+       doKeyevent APP_SWITCH
+       sleep 0.5
+       swipe "$DOWN" $downCount
+       sleep 1
+       swipe "$UP" $upCount
+       sleep 1
+       swipe "$DOWN" $downCount
+       sleep 1
+       swipe "$UP" $upCount
+       sleep 1
+       if [ $capturesystrace -gt 0 ]; then
+               ${ADB}atrace --async_dump -z -c -b 16000 freq gfx view idle sched > trace.${cur}.out
+       fi
+       doKeyevent HOME
+       sleep 0.5
+
+       set -- $(getJankyFrames)
+       totalDiff=$1
+       jankyDiff=$2
+       latency90=$3
+       latency95=$4
+       latency99=$5
+       if [ ${totalDiff:=0} -eq 0 ]; then
+               echo Error: could not read frame info with \"dumpsys gfxinfo\"
+               exit 1
+       fi
+
+       ((frameSum=frameSum+totalDiff))
+       ((jankSum=jankSum+jankyDiff))
+       ((latency90Sum=latency90Sum+latency90))
+       ((latency95Sum=latency95Sum+latency95))
+       ((latency99Sum=latency99Sum+latency99))
+       if [ "$totalDiff" -eq 0 ]; then
+               echo Error: no frames detected. Is the display off?
+               exit 1
+       fi
+       ((jankPct=jankyDiff*100/totalDiff))
+       resetJankyFrames
+
+       echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+       ((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)
diff --git a/tests/workloads/systemapps.sh b/tests/workloads/systemapps.sh
new file mode 100755 (executable)
index 0000000..a263e7d
--- /dev/null
@@ -0,0 +1,264 @@
+# Script to start a set of apps in order and then in each iteration
+# switch the focus to each one. For each iteration, the time to start
+# the app is reported as measured using atrace events and via am ThisTime.
+# The output also reports if applications are restarted (eg, killed by
+# LMK since previous iteration) or if there were any direct reclaim
+# events.
+#
+# Variation: the "-T" option skips all of the atrace instramentation and
+# attempts to start the apps as quickly as possible.
+#
+# Example 1: start all default apps. 2 iterations
+#
+# ./systemapps.sh -i 2
+#
+# Example 2: just start chrome, feedly, and the home screen in a loop
+#
+# ./systemapps.sh -L "chrome feedly home" -i 5
+#
+# Example 3: just start the default apps as quickly as possible
+#
+# ./systemapps.sh -T
+#
+# Other options are described below.
+#
+iterations=1
+tracecategories="gfx view am input memreclaim"
+totaltimetest=0
+forcecoldstart=0
+waitTime=3.0
+
+appList="gmail hangouts chrome youtube play home"
+
+function processLocalOption {
+       ret=0
+       case "$1" in
+       (-A) unset appList;;
+       (-F) forcecoldstart=1;;
+       (-L) appList=$2; shift; ret=1;;
+       (-T) totaltimetest=1;;
+       (-W) waitTime=$2; shift; ret=1;;
+       (*)
+               echo "$0: unrecognized option: $1"
+               echo; echo "Usage: $0 [options]"
+               echo "-A : use all known applications"
+               echo "-F : force cold-start for all apps"
+               echo "-L applist : list of applications"
+               echo "   default: $appList"
+               echo "-T : total time to start all apps"
+               echo "-W : time to wait between apps"
+               echo "-g : generate activity strings"
+               echo "-i iterations"
+               echo "-n : keep trace files"
+               echo "-o output file"
+               echo "-s : stop on error"
+               echo "-t trace categories"
+               exit 1;;
+       esac
+       return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+tmpTraceOutBase=./tmptrace
+
+if [ $user !=  "root" -a $totaltimetest -eq 0 ]; then
+       handleError Must be root on device
+       exit 1
+fi
+doKeyevent HOME
+
+function computeStats {
+       label=$1
+       t=$2
+       restart=$3
+       reclaim=$4
+       frames=$5
+       janks=$6
+       l90=$7
+       l95=$8
+       l99=$9
+       curMax=$(eval "echo \$${label}max")
+       curMax=${curMax:=0}
+       curMin=$(eval "echo \$${label}min")
+       curMin=${curMin:=100000}
+       curSum=$(eval "echo \$${label}sum")
+       curSum=${curSum:=0}
+       curRestart=$(eval "echo \$${label}restart")
+       curRestart=${curRestart:=0}
+       curReclaim=$(eval "echo \$${label}reclaim")
+       curReclaim=${curReclaim:=0}
+       curFrames=$(eval "echo \$${label}frames")
+       curFrames=${curFrames:=0}
+       curJanks=$(eval "echo \$${label}janks")
+       curJanks=${curJanks:=0}
+       cur90=$(eval "echo \$${label}90")
+       cur90=${cur90:=0}
+       cur95=$(eval "echo \$${label}95")
+       cur95=${cur95:=0}
+       cur99=$(eval "echo \$${label}99")
+       cur99=${cur99:=0}
+       if [ $curMax -lt $t ]; then
+               eval "${label}max=$t"
+       fi
+       if [ $curMin -gt $t ]; then
+               eval "${label}min=$t"
+       fi
+       ((curSum=curSum+t))
+       eval "${label}sum=$curSum"
+
+       ((curRestart=curRestart+${restart:=0}))
+       eval "${label}restart=$curRestart"
+       ((curReclaim=curReclaim+${reclaim:=0}))
+       eval "${label}reclaim=$curReclaim"
+       ((curFrames=curFrames+${frames:=0}))
+       eval "${label}frames=$curFrames"
+       ((curJanks=curJanks+${janks:=0}))
+       eval "${label}janks=$curJanks"
+       ((cur90=cur90+${l90:=0}))
+       eval "${label}90=$cur90"
+       ((cur95=cur95+${l95:=0}))
+       eval "${label}95=$cur95"
+       ((cur99=cur99+${l99:=0}))
+       eval "${label}99=$cur99"
+}
+function getStats {
+       label=$1
+       echo $(eval "echo \$${label}max") $(eval "echo \$${label}min") $(eval "echo \$${label}sum") \
+               $(eval "echo \$${label}restart") $(eval "echo \$${label}reclaim") \
+               $(eval "echo \$${label}frames") $(eval "echo \$${label}janks") \
+               $(eval "echo \$${label}90") $(eval "echo \$${label}95") $(eval "echo \$${label}99")
+}
+
+cur=1
+totaltime=0
+startTimestamp=$(date +"%s %N")
+
+while [ $cur -le $iterations ]
+do
+       if [ $iterations -gt 1 ]; then
+               echo =========================================
+               echo Iteration $cur of $iterations
+               echo =========================================
+       fi
+       if [ $iterations -gt 1 -o $cur -eq 1 ]; then
+               if [ $totaltimetest -eq 0 ]; then
+                       printf "%-6s    %7s(ms)  %6s(ms) %s %s %s     %s\n" App  Time AmTime Restart DirReclaim Jank Latency
+               fi
+       fi
+
+       appnum=-1
+       for app in $appList
+       do
+               vout Starting $app...
+               ((appnum=appnum+1))
+               loopTimestamp=$(date +"%s %N")
+               resetJankyFrames
+               resetJankyFrames $(getPackageName $app)
+               if [ $totaltimetest -eq 0 ]; then
+                       tmpTraceOut="$tmpTraceOutBase-$app.out"
+                       >$tmpTraceOut
+                       startInstramentation
+               else
+                       if [ $appnum -eq 0 ]; then
+                               printf "%-8s %5s(ms) %3s(ms) %s      %s\n" App Start Iter Jank Latency
+                       fi
+               fi
+               if [ $forcecoldstart -eq 0 ]; then
+                       t=$(startActivity $app)
+               else
+                       t=$(forceStartActivity $app)
+               fi
+
+               # let app finish drawing before checking janks
+               sleep $waitTime
+               set -- $(getJankyFrames $(getPackageName $app))
+               frames=$1
+               janks=$2
+               l90=$3
+               l95=$4
+               l99=$5
+               set -- $(getJankyFrames)
+               systemFrames=$1
+               systemJanks=$2
+               s90=$3
+               s95=$4
+               s99=$5
+               ((frames=frames+systemFrames))
+               ((janks=janks+systemJanks))
+               ((l90=l90+s90))
+               ((l95=l95+s95))
+               ((l99=l99+s99))
+
+               loopEndTimestamp=$(date +"%s %N")
+               diffTime=$(computeTimeDiff $loopTimestamp $loopEndTimestamp)
+
+               if [ $frames -eq 0 ]; then
+                       janks=0
+                       jankPct=0
+               else
+                       ((jankPct=100*janks/frames))
+               fi
+               if [ $totaltimetest -gt 0 ]; then
+                       # Note: using %f since %d doesn't work correctly
+                       # when running on lollipop
+                       printf "%-10s %5.0f   %5.0f    %4.0f(%2.0f%%) %2.0f/%2.0f/%2.0f\n" $app $t $diffTime $janks $jankPct $l90 $l95 $l99
+                       ((totaltime=totaltime+t))
+                       continue
+               else
+                       stopAndDumpInstramentation $tmpTraceOut
+                       actName=$(getActivityName $app)
+                       pkgName=$(getPackageName $app)
+                       stime=$(getStartTime $actName $tmpTraceOut)
+                       relaunch=$?
+                       etime=$(getEndTime $pkgName $tmpTraceOut)
+                       ((tdiff=$etime-$stime))
+                       if [ $etime -eq 0 -o $stime -eq 0 ]; then
+                               handleError $app : could not compute start time stime=$stime  etime=$etime
+                               # use AmTime so statistics make sense
+                               tdiff=$t
+                       fi
+                       checkForDirectReclaim $actName $tmpTraceOut
+                       directReclaim=$?
+
+                       printf "%-12s %5d     %5d     %5d    %5d    %5d(%d%%) %d/%d/%d\n" "$app" "$tdiff" "$t" "$relaunch" "$directReclaim" "$janks" "$jankPct" $l90 $l95 $l99
+                       computeStats "$app" "$tdiff" "$relaunch" "$directReclaim" "$frames" "$janks" $l90 $l95 $l99
+
+                       if [ $savetmpfiles -eq 0 ]; then
+                               rm -f $tmpTraceOut
+                       fi
+               fi
+       done
+       ((cur=cur+1))
+done
+endTimestamp=$(date +"%s %N")
+diffTime=$(computeTimeDiff $startTimestamp $endTimestamp)
+if [ $totaltimetest -gt 0 ]; then
+       printf "%-10s %5.0f   %5.0f\n" TOTAL $totaltime $diffTime
+fi
+
+if [ $iterations -gt 1 -a $totaltimetest -eq 0 ]; then
+       echo
+       echo =========================================
+       printf "Stats after $iterations iterations:\n"
+       echo =========================================
+       printf "%-6s    %7s(ms) %6s(ms) %6s(ms)    %s    %s %s     %s\n" App Max Ave Min Restart DirReclaim Jank Latency
+       for app in $appList
+       do
+               set -- $(getStats $app)
+               sum=$3
+               ((ave=sum/iterations))
+               frames=$6
+               janks=$7
+               l90=$8
+               l95=$9
+               l99=${10}
+               ((ave90=l90/iterations))
+               ((ave95=l95/iterations))
+               ((ave99=l99/iterations))
+               ((jankPct=100*janks/frames))
+               printf "%-12s %5d      %5d      %5d      %5d      %5d     %5d(%d%%) %d/%d/%d\n" $app $1 $ave $2 $4 $5 $janks $jankPct $ave90 $ave95 $ave99
+       done
+fi
index 3576e3b..937c206 100644 (file)
@@ -35,6 +35,8 @@ import java.security.Signature;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.ECPrivateKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.InvalidKeySpecException;
@@ -52,6 +54,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.util.encoders.Base64;
 
 public class Utils {
@@ -63,10 +66,16 @@ public class Utils {
         ID_TO_ALG = new HashMap<String, String>();
         ALG_TO_ID = new HashMap<String, String>();
 
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
         ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
         ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
         ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
 
+        ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
+        ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
+        ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
         ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
         ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
         ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
@@ -208,15 +217,36 @@ public class Utils {
         }
     }
 
-    private static String getSignatureAlgorithm(Key key) {
-        if ("RSA".equals(key.getAlgorithm())) {
+    private static String getSignatureAlgorithm(Key key) throws Exception {
+        if ("EC".equals(key.getAlgorithm())) {
+            int curveSize;
+            KeyFactory factory = KeyFactory.getInstance("EC");
+
+            if (key instanceof PublicKey) {
+                ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
+                curveSize = spec.getParams().getCurve().getField().getFieldSize();
+            } else if (key instanceof PrivateKey) {
+                ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
+                curveSize = spec.getParams().getCurve().getField().getFieldSize();
+            } else {
+                throw new InvalidKeySpecException();
+            }
+
+            if (curveSize <= 256) {
+                return "SHA256withECDSA";
+            } else if (curveSize <= 384) {
+                return "SHA384withECDSA";
+            } else {
+                return "SHA512withECDSA";
+            }
+        } else if ("RSA".equals(key.getAlgorithm())) {
             return "SHA256withRSA";
         } else {
             throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
         }
     }
 
-    static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) {
+    static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
         String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
 
         if (id == null) {
index 5c9d7d2..6b3f49e 100644 (file)
@@ -20,31 +20,83 @@ import java.io.File;
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.lang.Math;
 import java.lang.Process;
 import java.lang.Runtime;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
 import java.security.PublicKey;
-import java.security.PrivateKey;
 import java.security.Security;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.xml.bind.DatatypeConverter;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 public class VerityVerifier {
 
+    private ArrayList<Integer> hashBlocksLevel;
+    private byte[] hashTree;
+    private byte[] rootHash;
+    private byte[] salt;
+    private byte[] signature;
+    private byte[] table;
+    private File image;
+    private int blockSize;
+    private int hashBlockSize;
+    private int hashOffsetForData;
+    private int hashSize;
+    private int hashTreeSize;
+    private long hashStart;
+    private long imageSize;
+    private MessageDigest digest;
+
     private static final int EXT4_SB_MAGIC = 0xEF53;
     private static final int EXT4_SB_OFFSET = 0x400;
     private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38;
     private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18;
     private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4;
     private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150;
+    private static final int MINCRYPT_OFFSET_MODULUS = 0x8;
+    private static final int MINCRYPT_OFFSET_EXPONENT = 0x208;
+    private static final int MINCRYPT_MODULUS_SIZE = 0x100;
+    private static final int MINCRYPT_EXPONENT_SIZE = 0x4;
+    private static final int VERITY_FIELDS = 10;
     private static final int VERITY_MAGIC = 0xB001B001;
     private static final int VERITY_SIGNATURE_SIZE = 256;
     private static final int VERITY_VERSION = 0;
 
+    public VerityVerifier(String fname) throws Exception {
+        digest = MessageDigest.getInstance("SHA-256");
+        hashSize = digest.getDigestLength();
+        hashBlocksLevel = new ArrayList<Integer>();
+        hashTreeSize = -1;
+        openImage(fname);
+        readVerityData();
+    }
+
+    /**
+     * Reverses the order of bytes in a byte array
+     * @param value Byte array to reverse
+     */
+    private static byte[] reverse(byte[] value) {
+        for (int i = 0; i < value.length / 2; i++) {
+            byte tmp = value[i];
+            value[i] = value[value.length - i - 1];
+            value[value.length - i - 1] = tmp;
+        }
+
+        return value;
+    }
+
     /**
      * Converts a 4-byte little endian value to a Java integer
      * @param value Little endian integer to convert
      */
-     public static int fromle(int value) {
+    private static int fromle(int value) {
         byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
         return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
     }
@@ -53,28 +105,51 @@ public class VerityVerifier {
      * Converts a 2-byte little endian value to Java a integer
      * @param value Little endian short to convert
      */
-     public static int fromle(short value) {
+    private static int fromle(short value) {
         return fromle(value << 16);
     }
 
     /**
+     * Reads a 2048-bit RSA public key saved in mincrypt format, and returns
+     * a Java PublicKey for it.
+     * @param fname Name of the mincrypt public key file
+     */
+    private static PublicKey getMincryptPublicKey(String fname) throws Exception {
+        try (RandomAccessFile key = new RandomAccessFile(fname, "r")) {
+            byte[] binaryMod = new byte[MINCRYPT_MODULUS_SIZE];
+            byte[] binaryExp = new byte[MINCRYPT_EXPONENT_SIZE];
+
+            key.seek(MINCRYPT_OFFSET_MODULUS);
+            key.readFully(binaryMod);
+
+            key.seek(MINCRYPT_OFFSET_EXPONENT);
+            key.readFully(binaryExp);
+
+            BigInteger modulus  = new BigInteger(1, reverse(binaryMod));
+            BigInteger exponent = new BigInteger(1, reverse(binaryExp));
+
+            RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
+            KeyFactory factory = KeyFactory.getInstance("RSA");
+            return factory.generatePublic(spec);
+        }
+    }
+
+    /**
      * Unsparses a sparse image into a temporary file and returns a
      * handle to the file
      * @param fname Path to a sparse image file
      */
-     public static RandomAccessFile openImage(String fname) throws Exception {
-        File tmp = File.createTempFile("system", ".raw");
-        tmp.deleteOnExit();
+     private void openImage(String fname) throws Exception {
+        image = File.createTempFile("system", ".raw");
+        image.deleteOnExit();
 
         Process p = Runtime.getRuntime().exec("simg2img " + fname +
-                            " " + tmp.getAbsoluteFile());
+                            " " + image.getAbsoluteFile());
 
         p.waitFor();
         if (p.exitValue() != 0) {
             throw new IllegalArgumentException("Invalid image: failed to unsparse");
         }
-
-        return new RandomAccessFile(tmp, "r");
     }
 
     /**
@@ -106,56 +181,234 @@ public class VerityVerifier {
     }
 
     /**
-     * Reads and validates verity metadata, and check the signature against the
+     * Calculates the size of the verity hash tree based on the image size
+     */
+    private int calculateHashTreeSize() {
+        if (hashTreeSize > 0) {
+            return hashTreeSize;
+        }
+
+        int totalBlocks = 0;
+        int hashes = (int) (imageSize / blockSize);
+
+        hashBlocksLevel.clear();
+
+        do {
+            hashBlocksLevel.add(0, hashes);
+
+            int hashBlocks =
+                (int) Math.ceil((double) hashes * hashSize / hashBlockSize);
+
+            totalBlocks += hashBlocks;
+
+            hashes = hashBlocks;
+        } while (hashes > 1);
+
+        hashTreeSize = totalBlocks * hashBlockSize;
+        return hashTreeSize;
+    }
+
+    /**
+     * Parses the verity mapping table and reads the hash tree from
+     * the image file
+     * @param img Handle to the image file
+     * @param table Verity mapping table
+     */
+    private void readHashTree(RandomAccessFile img, byte[] table)
+            throws Exception {
+        String tableStr = new String(table);
+        String[] fields = tableStr.split(" ");
+
+        if (fields.length != VERITY_FIELDS) {
+            throw new IllegalArgumentException("Invalid image: unexpected number of fields "
+                    + "in verity mapping table (" + fields.length + ")");
+        }
+
+        String hashVersion = fields[0];
+
+        if (!"1".equals(hashVersion)) {
+            throw new IllegalArgumentException("Invalid image: unsupported hash format");
+        }
+
+        String alg = fields[7];
+
+        if (!"sha256".equals(alg)) {
+            throw new IllegalArgumentException("Invalid image: unsupported hash algorithm");
+        }
+
+        blockSize = Integer.parseInt(fields[3]);
+        hashBlockSize = Integer.parseInt(fields[4]);
+
+        int blocks = Integer.parseInt(fields[5]);
+        int start = Integer.parseInt(fields[6]);
+
+        if (imageSize != (long) blocks * blockSize) {
+            throw new IllegalArgumentException("Invalid image: size mismatch in mapping "
+                    + "table");
+        }
+
+        rootHash = DatatypeConverter.parseHexBinary(fields[8]);
+        salt = DatatypeConverter.parseHexBinary(fields[9]);
+
+        hashStart = (long) start * blockSize;
+        img.seek(hashStart);
+
+        int treeSize = calculateHashTreeSize();
+
+        hashTree = new byte[treeSize];
+        img.readFully(hashTree);
+    }
+
+    /**
+     * Reads verity data from the image file
+     */
+    private void readVerityData() throws Exception {
+        try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+            imageSize = getMetadataPosition(img);
+            img.seek(imageSize);
+
+            int magic = fromle(img.readInt());
+
+            if (magic != VERITY_MAGIC) {
+                throw new IllegalArgumentException("Invalid image: verity metadata not found");
+            }
+
+            int version = fromle(img.readInt());
+
+            if (version != VERITY_VERSION) {
+                throw new IllegalArgumentException("Invalid image: unknown metadata version");
+            }
+
+            signature = new byte[VERITY_SIGNATURE_SIZE];
+            img.readFully(signature);
+
+            int tableSize = fromle(img.readInt());
+
+            table = new byte[tableSize];
+            img.readFully(table);
+
+            readHashTree(img, table);
+        }
+    }
+
+    /**
+     * Reads and validates verity metadata, and checks the signature against the
      * given public key
-     * @param img File handle to the image file
      * @param key Public key to use for signature verification
      */
-    public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
+    public boolean verifyMetaData(PublicKey key)
             throws Exception {
-        img.seek(getMetadataPosition(img));
-        int magic = fromle(img.readInt());
+       return Utils.verify(key, table, signature,
+                   Utils.getSignatureAlgorithmIdentifier(key));
+    }
+
+    /**
+     * Hashes a block of data using a salt and checks of the results are expected
+     * @param hash The expected hash value
+     * @param data The data block to check
+     */
+    private boolean checkBlock(byte[] hash, byte[] data) {
+        digest.reset();
+        digest.update(salt);
+        digest.update(data);
+        return Arrays.equals(hash, digest.digest());
+    }
+
+    /**
+     * Verifies the root hash and the first N-1 levels of the hash tree
+     */
+    private boolean verifyHashTree() throws Exception {
+        int hashOffset = 0;
+        int dataOffset = hashBlockSize;
 
-        if (magic != VERITY_MAGIC) {
-            throw new IllegalArgumentException("Invalid image: verity metadata not found");
+        if (!checkBlock(rootHash, Arrays.copyOfRange(hashTree, 0, hashBlockSize))) {
+            System.err.println("Root hash mismatch");
+            return false;
         }
 
-        int version = fromle(img.readInt());
+        for (int level = 0; level < hashBlocksLevel.size() - 1; level++) {
+            int blocks = hashBlocksLevel.get(level);
+
+            for (int i = 0; i < blocks; i++) {
+                byte[] hashBlock = Arrays.copyOfRange(hashTree,
+                        hashOffset + i * hashSize,
+                        hashOffset + i * hashSize + hashSize);
+
+                byte[] dataBlock = Arrays.copyOfRange(hashTree,
+                        dataOffset + i * hashBlockSize,
+                        dataOffset + i * hashBlockSize + hashBlockSize);
 
-        if (version != VERITY_VERSION) {
-            throw new IllegalArgumentException("Invalid image: unknown metadata version");
+                if (!checkBlock(hashBlock, dataBlock)) {
+                    System.err.printf("Hash mismatch at tree level %d, block %d\n", level, i);
+                    return false;
+                }
+            }
+
+            hashOffset = dataOffset;
+            hashOffsetForData = dataOffset;
+            dataOffset += blocks * hashBlockSize;
         }
 
-        byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
-        img.readFully(signature);
+        return true;
+    }
 
-        int tableSize = fromle(img.readInt());
+    /**
+     * Validates the image against the hash tree
+     */
+    public boolean verifyData() throws Exception {
+        if (!verifyHashTree()) {
+            return false;
+        }
 
-        byte[] table = new byte[tableSize];
-        img.readFully(table);
+        try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+            byte[] dataBlock = new byte[blockSize];
+            int hashOffset = hashOffsetForData;
 
-        return Utils.verify(key, table, signature,
-                   Utils.getSignatureAlgorithmIdentifier(key));
+            for (int i = 0; (long) i * blockSize < imageSize; i++) {
+                byte[] hashBlock = Arrays.copyOfRange(hashTree,
+                        hashOffset + i * hashSize,
+                        hashOffset + i * hashSize + hashSize);
+
+                img.readFully(dataBlock);
+
+                if (!checkBlock(hashBlock, dataBlock)) {
+                    System.err.printf("Hash mismatch at block %d\n", i);
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Verifies the integrity of the image and the verity metadata
+     * @param key Public key to use for signature verification
+     */
+    public boolean verify(PublicKey key) throws Exception {
+        return (verifyMetaData(key) && verifyData());
     }
 
     public static void main(String[] args) throws Exception {
-        if (args.length != 2) {
-            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
+        Security.addProvider(new BouncyCastleProvider());
+        PublicKey key = null;
+
+        if (args.length == 3 && "-mincrypt".equals(args[1])) {
+            key = getMincryptPublicKey(args[2]);
+        } else if (args.length == 2) {
+            X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+            key = cert.getPublicKey();
+        } else {
+            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem> | -mincrypt <mincrypt_key>");
             System.exit(1);
         }
 
-        Security.addProvider(new BouncyCastleProvider());
-
-        X509Certificate cert = Utils.loadPEMCertificate(args[1]);
-        PublicKey key = cert.getPublicKey();
-        RandomAccessFile img = openImage(args[0]);
+        VerityVerifier verifier = new VerityVerifier(args[0]);
 
         try {
-            if (verifyMetaData(img, key)) {
+            if (verifier.verify(key)) {
                 System.err.println("Signature is VALID");
                 System.exit(0);
-            } else {
-                System.err.println("Signature is INVALID");
             }
         } catch (Exception e) {
             e.printStackTrace(System.err);