Snap for 10453563 from d6f259b4285f74ed240a6392a7c2b2812ed1320f to mainline-os-statsd-release

Change-Id: I107502bfd40d4606845db6f2c4e78a17ef5063ea
diff --git a/app-launcher/Android.bp b/app-launcher/Android.bp
index 3fbc88c..f00dcb6 100644
--- a/app-launcher/Android.bp
+++ b/app-launcher/Android.bp
@@ -31,7 +31,7 @@
 
 sh_binary_host {
     name: "app-launcher",
-    src: "app-launcher",
+    src: "app-launcher.sh",
     required: [
         "computestats",
         "computestatsf",
diff --git a/app-launcher/app-launcher b/app-launcher/app-launcher.sh
similarity index 100%
rename from app-launcher/app-launcher
rename to app-launcher/app-launcher.sh
diff --git a/bootctl/Android.bp b/bootctl/Android.bp
index f63871c..106ff86 100644
--- a/bootctl/Android.bp
+++ b/bootctl/Android.bp
@@ -30,6 +30,8 @@
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
         "android.hardware.boot@1.2",
+        "android.hardware.boot-V1-ndk",
+        "libboot_control_client",
         "libhidlbase",
         "libutils",
     ],
diff --git a/bootctl/bootctl.cpp b/bootctl/bootctl.cpp
index 8ead010..1bf9181 100644
--- a/bootctl/bootctl.cpp
+++ b/bootctl/bootctl.cpp
@@ -17,27 +17,19 @@
 #include <optional>
 #include <sstream>
 
+#include <BootControlClient.h>
 #include <android/hardware/boot/1.2/IBootControl.h>
 #include <sysexits.h>
 
 using android::sp;
 
-using android::hardware::hidl_string;
-using android::hardware::Return;
+using aidl::android::hardware::boot::MergeStatus;
 
-using android::hardware::boot::V1_0::BoolResult;
-using android::hardware::boot::V1_0::CommandResult;
-using android::hardware::boot::V1_0::Slot;
-using android::hardware::boot::V1_1::IBootControl;
-using android::hardware::boot::V1_1::MergeStatus;
+using android::hal::BootControlClient;
+using android::hal::BootControlVersion;
+using android::hal::CommandResult;
 
-namespace V1_0 = android::hardware::boot::V1_0;
-namespace V1_1 = android::hardware::boot::V1_1;
-namespace V1_2 = android::hardware::boot::V1_2;
-
-enum BootCtlVersion { BOOTCTL_V1_0, BOOTCTL_V1_1, BOOTCTL_V1_2 };
-
-static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char* argv[]) {
+static void usage(FILE* where, BootControlVersion bootVersion, int /* argc */, char* argv[]) {
     fprintf(where,
             "%s - command-line wrapper for the boot HAL.\n"
             "\n"
@@ -56,7 +48,7 @@
             "  is-slot-marked-successful SLOT - Returns 0 only if SLOT is marked GOOD.\n"
             "  get-suffix SLOT                - Prints suffix for SLOT.\n",
             argv[0], argv[0]);
-    if (bootVersion >= BOOTCTL_V1_1) {
+    if (bootVersion >= BootControlVersion::BOOTCTL_V1_1) {
         fprintf(where,
                 "  set-snapshot-merge-status STAT - Sets whether a snapshot-merge of any dynamic\n"
                 "                                   partition is in progress. Valid STAT values\n"
@@ -69,32 +61,39 @@
             "SLOT parameter is the zero-based slot-number.\n");
 }
 
-static int do_hal_info(const sp<V1_0::IBootControl> module) {
-    module->interfaceDescriptor([&](const auto& descriptor) {
-        fprintf(stdout, "HAL Version: %s\n", descriptor.c_str());
-    });
+static constexpr auto ToString(BootControlVersion ver) {
+    switch (ver) {
+        case BootControlVersion::BOOTCTL_V1_0:
+            return "android.hardware.boot@1.0::IBootControl";
+        case BootControlVersion::BOOTCTL_V1_1:
+            return "android.hardware.boot@1.1::IBootControl";
+        case BootControlVersion::BOOTCTL_V1_2:
+            return "android.hardware.boot@1.2::IBootControl";
+        case BootControlVersion::BOOTCTL_AIDL:
+            return "android.hardware.boot@aidl::IBootControl";
+    }
+}
+
+static int do_hal_info(const BootControlClient* module) {
+    fprintf(stdout, "HAL Version: %s\n", ToString(module->GetVersion()));
     return EX_OK;
 }
 
-static int do_get_number_slots(sp<V1_0::IBootControl> module) {
-    uint32_t numSlots = module->getNumberSlots();
+static int do_get_number_slots(BootControlClient* module) {
+    auto numSlots = module->GetNumSlots();
     fprintf(stdout, "%u\n", numSlots);
     return EX_OK;
 }
 
-static int do_get_current_slot(sp<V1_0::IBootControl> module) {
-    Slot curSlot = module->getCurrentSlot();
+static int do_get_current_slot(BootControlClient* module) {
+    auto curSlot = module->GetCurrentSlot();
     fprintf(stdout, "%u\n", curSlot);
     return EX_OK;
 }
 
-static std::function<void(CommandResult)> generate_callback(CommandResult* crp) {
-    return [=](CommandResult cr) { *crp = cr; };
-}
-
-static int handle_return(const Return<void>& ret, CommandResult cr, const char* errStr) {
-    if (!ret.isOk()) {
-        fprintf(stderr, errStr, ret.description().c_str());
+static int handle_return(CommandResult cr, const char* errStr) {
+    if (!cr.IsOk()) {
+        fprintf(stderr, errStr, cr.errMsg.c_str());
         return EX_SOFTWARE;
     } else if (!cr.success) {
         fprintf(stderr, errStr, cr.errMsg.c_str());
@@ -103,51 +102,48 @@
     return EX_OK;
 }
 
-static int do_mark_boot_successful(sp<V1_0::IBootControl> module) {
-    CommandResult cr;
-    Return<void> ret = module->markBootSuccessful(generate_callback(&cr));
-    return handle_return(ret, cr, "Error marking as having booted successfully: %s\n");
+static int do_mark_boot_successful(BootControlClient* module) {
+    auto ret = module->MarkBootSuccessful();
+    return handle_return(ret, "Error marking as having booted successfully: %s\n");
 }
 
-static int do_get_active_boot_slot(sp<V1_2::IBootControl> module) {
-    uint32_t slot = module->getActiveBootSlot();
+static int do_get_active_boot_slot(BootControlClient* module) {
+    uint32_t slot = module->GetActiveBootSlot();
     fprintf(stdout, "%u\n", slot);
     return EX_OK;
 }
 
-static int do_set_active_boot_slot(sp<V1_0::IBootControl> module, Slot slot_number) {
-    CommandResult cr;
-    Return<void> ret = module->setActiveBootSlot(slot_number, generate_callback(&cr));
-    return handle_return(ret, cr, "Error setting active boot slot: %s\n");
+static int do_set_active_boot_slot(BootControlClient* module, int32_t slot_number) {
+    const auto cr = module->SetActiveBootSlot(slot_number);
+    return handle_return(cr, "Error setting active boot slot: %s\n");
 }
 
-static int do_set_slot_as_unbootable(sp<V1_0::IBootControl> module, Slot slot_number) {
-    CommandResult cr;
-    Return<void> ret = module->setSlotAsUnbootable(slot_number, generate_callback(&cr));
-    return handle_return(ret, cr, "Error setting slot as unbootable: %s\n");
+static int do_set_slot_as_unbootable(BootControlClient* module, int32_t slot_number) {
+    const auto cr = module->MarkSlotUnbootable(slot_number);
+    return handle_return(cr, "Error setting slot as unbootable: %s\n");
 }
 
-static int handle_return(const Return<BoolResult>& ret, const char* errStr) {
-    if (!ret.isOk()) {
-        fprintf(stderr, errStr, ret.description().c_str());
+static int handle_return(const std::optional<bool>& ret, const char* errStr) {
+    if (!ret.has_value()) {
+        fprintf(stderr, errStr, "");
         return EX_SOFTWARE;
-    } else if (ret == BoolResult::INVALID_SLOT) {
-        fprintf(stderr, errStr, "Invalid slot");
-        return EX_SOFTWARE;
-    } else if (ret == BoolResult::TRUE) {
+    }
+    if (ret.value()) {
+        printf("%d\n", ret.value());
         return EX_OK;
     }
+    printf("%d\n", ret.value());
     return EX_SOFTWARE;
 }
 
-static int do_is_slot_bootable(sp<V1_0::IBootControl> module, Slot slot_number) {
-    Return<BoolResult> ret = module->isSlotBootable(slot_number);
-    return handle_return(ret, "Error calling isSlotBootable(): %s\n");
+static int do_is_slot_bootable(BootControlClient* module, int32_t slot_number) {
+    const auto ret = module->IsSlotBootable(slot_number);
+    return handle_return(ret, "Error calling isSlotBootable()\n");
 }
 
-static int do_is_slot_marked_successful(sp<V1_0::IBootControl> module, Slot slot_number) {
-    Return<BoolResult> ret = module->isSlotMarkedSuccessful(slot_number);
-    return handle_return(ret, "Error calling isSlotMarkedSuccessful(): %s\n");
+static int do_is_slot_marked_successful(BootControlClient* module, int32_t slot_number) {
+    const auto ret = module->IsSlotMarkedSuccessful(slot_number);
+    return handle_return(ret, "Error calling isSlotMarkedSuccessful()\n");
 }
 
 std::optional<MergeStatus> stringToMergeStatus(const std::string& status) {
@@ -159,7 +155,7 @@
     return {};
 }
 
-static int do_set_snapshot_merge_status(sp<V1_1::IBootControl> module, BootCtlVersion bootVersion,
+static int do_set_snapshot_merge_status(BootControlClient* module, BootControlVersion bootVersion,
                                         int argc, char* argv[]) {
     if (argc != 3) {
         usage(stderr, bootVersion, argc, argv);
@@ -174,10 +170,8 @@
         return -1;
     }
 
-    if (!module->setSnapshotMergeStatus(status.value())) {
-        return EX_SOFTWARE;
-    }
-    return EX_OK;
+    const auto ret = module->SetSnapshotMergeStatus(status.value());
+    return handle_return(ret, "Failed to set snapshot merge status: %s\n");
 }
 
 std::ostream& operator<<(std::ostream& os, MergeStatus state) {
@@ -197,7 +191,7 @@
     }
 }
 
-static int do_get_snapshot_merge_status(sp<V1_1::IBootControl> module) {
+static int do_get_snapshot_merge_status(BootControlClient* module) {
     MergeStatus ret = module->getSnapshotMergeStatus();
     std::stringstream ss;
     ss << ret;
@@ -205,19 +199,17 @@
     return EX_OK;
 }
 
-static int do_get_suffix(sp<V1_0::IBootControl> module, Slot slot_number) {
-    std::function<void(hidl_string)> cb = [](hidl_string suffix) {
-        fprintf(stdout, "%s\n", suffix.c_str());
-    };
-    Return<void> ret = module->getSuffix(slot_number, cb);
-    if (!ret.isOk()) {
-        fprintf(stderr, "Error calling getSuffix(): %s\n", ret.description().c_str());
+static int do_get_suffix(BootControlClient* module, int32_t slot_number) {
+    const auto ret = module->GetSuffix(slot_number);
+    if (ret.empty()) {
+        fprintf(stderr, "Error calling getSuffix()\n");
         return EX_SOFTWARE;
     }
+    printf("%s\n", ret.c_str());
     return EX_OK;
 }
 
-static uint32_t parse_slot(BootCtlVersion bootVersion, int pos, int argc, char* argv[]) {
+static uint32_t parse_slot(BootControlVersion bootVersion, int pos, int argc, char* argv[]) {
     if (pos > argc - 1) {
         usage(stderr, bootVersion, argc, argv);
         exit(EX_USAGE);
@@ -234,25 +226,12 @@
 }
 
 int main(int argc, char* argv[]) {
-    sp<V1_0::IBootControl> v1_0_module;
-    sp<V1_1::IBootControl> v1_1_module;
-    sp<V1_2::IBootControl> v1_2_module;
-    BootCtlVersion bootVersion = BOOTCTL_V1_0;
-
-    v1_0_module = V1_0::IBootControl::getService();
-    if (v1_0_module == nullptr) {
-        fprintf(stderr, "Error getting bootctrl v1.0 module.\n");
+    const auto client = android::hal::BootControlClient::WaitForService();
+    if (client == nullptr) {
+        fprintf(stderr, "Failed to get bootctl module.\n");
         return EX_SOFTWARE;
     }
-    v1_1_module = V1_1::IBootControl::castFrom(v1_0_module);
-    if (v1_1_module != nullptr) {
-        bootVersion = BOOTCTL_V1_1;
-    }
-
-    v1_2_module = V1_2::IBootControl::castFrom(v1_0_module);
-    if (v1_2_module != nullptr) {
-        bootVersion = BOOTCTL_V1_2;
-    }
+    const auto bootVersion = client->GetVersion();
 
     if (argc < 2) {
         usage(stderr, bootVersion, argc, argv);
@@ -261,46 +240,46 @@
 
     // Functions present from version 1.0
     if (strcmp(argv[1], "hal-info") == 0) {
-        return do_hal_info(v1_0_module);
+        return do_hal_info(client.get());
     } else if (strcmp(argv[1], "get-number-slots") == 0) {
-        return do_get_number_slots(v1_0_module);
+        return do_get_number_slots(client.get());
     } else if (strcmp(argv[1], "get-current-slot") == 0) {
-        return do_get_current_slot(v1_0_module);
+        return do_get_current_slot(client.get());
     } else if (strcmp(argv[1], "mark-boot-successful") == 0) {
-        return do_mark_boot_successful(v1_0_module);
+        return do_mark_boot_successful(client.get());
     } else if (strcmp(argv[1], "set-active-boot-slot") == 0) {
-        return do_set_active_boot_slot(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+        return do_set_active_boot_slot(client.get(), parse_slot(bootVersion, 2, argc, argv));
     } else if (strcmp(argv[1], "set-slot-as-unbootable") == 0) {
-        return do_set_slot_as_unbootable(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+        return do_set_slot_as_unbootable(client.get(), parse_slot(bootVersion, 2, argc, argv));
     } else if (strcmp(argv[1], "is-slot-bootable") == 0) {
-        return do_is_slot_bootable(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+        return do_is_slot_bootable(client.get(), parse_slot(bootVersion, 2, argc, argv));
     } else if (strcmp(argv[1], "is-slot-marked-successful") == 0) {
-        return do_is_slot_marked_successful(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+        return do_is_slot_marked_successful(client.get(), parse_slot(bootVersion, 2, argc, argv));
     } else if (strcmp(argv[1], "get-suffix") == 0) {
-        return do_get_suffix(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+        return do_get_suffix(client.get(), parse_slot(bootVersion, 2, argc, argv));
     }
 
     // Functions present from version 1.1
     if (strcmp(argv[1], "set-snapshot-merge-status") == 0 ||
         strcmp(argv[1], "get-snapshot-merge-status") == 0) {
-        if (v1_1_module == nullptr) {
+        if (bootVersion < BootControlVersion::BOOTCTL_V1_1) {
             fprintf(stderr, "Error getting bootctrl v1.1 module.\n");
             return EX_SOFTWARE;
         }
         if (strcmp(argv[1], "set-snapshot-merge-status") == 0) {
-            return do_set_snapshot_merge_status(v1_1_module, bootVersion, argc, argv);
+            return do_set_snapshot_merge_status(client.get(), bootVersion, argc, argv);
         } else if (strcmp(argv[1], "get-snapshot-merge-status") == 0) {
-            return do_get_snapshot_merge_status(v1_1_module);
+            return do_get_snapshot_merge_status(client.get());
         }
     }
 
     if (strcmp(argv[1], "get-active-boot-slot") == 0) {
-        if (v1_2_module == nullptr) {
+        if (bootVersion < BootControlVersion::BOOTCTL_V1_2) {
             fprintf(stderr, "Error getting bootctrl v1.2 module.\n");
             return EX_SOFTWARE;
         }
 
-        return do_get_active_boot_slot(v1_2_module);
+        return do_get_active_boot_slot(client.get());
     }
 
     // Parameter not matched, print usage
diff --git a/boottime_tools/bootanalyze/bootanalyze.py b/boottime_tools/bootanalyze/bootanalyze.py
index 5bed9f2..2b47a89 100755
--- a/boottime_tools/bootanalyze/bootanalyze.py
+++ b/boottime_tools/bootanalyze/bootanalyze.py
@@ -16,7 +16,7 @@
 #
 """Tool to analyze logcat and dmesg logs.
 
-bootanalyze read logcat and dmesg loga and determines key points for boot.
+bootanalyze read logcat and dmesg logs and determines key points for boot.
 """
 
 import argparse
@@ -28,12 +28,11 @@
 import re
 import select
 import subprocess
-import sys
 import time
 import threading
 import yaml
 
-from datetime import datetime, date
+from datetime import datetime
 
 TIME_DMESG = r"\[\s*(\d+\.\d+)\]"
 TIME_LOGCAT = r"[0-9]+\.?[0-9]*"
@@ -41,7 +40,9 @@
 BOOT_ANIM_END_TIME_KEY = "BootAnimEnd"
 KERNEL_BOOT_COMPLETE = "BootComplete_kernel"
 LOGCAT_BOOT_COMPLETE = "BootComplete"
+CARWATCHDOG_BOOT_COMPLETE = "CarWatchdogBootupProfilingComplete"
 LAUNCHER_START = "LauncherStart"
+CARWATCHDOG_DUMP_COMMAND = "adb shell dumpsys android.automotive.watchdog.ICarWatchdog/default"
 BOOT_TIME_TOO_BIG = 200.0
 MAX_RETRIES = 5
 DEBUG = False
@@ -50,6 +51,7 @@
 TIMING_THRESHOLD = 5.0
 BOOT_PROP = r"\[ro\.boottime\.([^\]]+)\]:\s+\[(\d+)\]"
 BOOTLOADER_TIME_PROP = r"\[ro\.boot\.boottime\]:\s+\[([^\]]+)\]"
+CARWATCHDOG_PARSER_CMD = 'perf_stats_parser'
 
 max_wait_time = BOOT_TIME_TOO_BIG
 
@@ -84,7 +86,7 @@
       value = float(kv[1])
       components_to_monitor[key] = value
 
-  cfg = yaml.load(args.config, Loader=yaml.FullLoader)
+  cfg = yaml.load(args.config, Loader=yaml.SafeLoader)
 
   if args.stressfs:
     if run_adb_cmd('install -r -g ' + args.stressfs) != 0:
@@ -102,12 +104,18 @@
   if DEBUG_PATTERN:
     print("search event:{} timing event:{}".format(search_events_pattern, timing_events_pattern))
 
+  now = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
+  boot_chart_file_path_prefix = "bootchart-" + now
+  systrace_file_path_prefix = "systrace-" + now
+
+  if args.output:
+    boot_chart_file_path_prefix = args.output + '/' + boot_chart_file_path_prefix
+    systrace_file_path_prefix = args.output + '/' + systrace_file_path_prefix
+
   data_points = {}
   kernel_timing_points = collections.OrderedDict()
   logcat_timing_points = collections.OrderedDict()
   boottime_points = collections.OrderedDict()
-  boot_chart_file_name_prefix = "bootchart-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
-  systrace_file_name_prefix = "systrace-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
   shutdown_event_all = collections.OrderedDict()
   shutdown_timing_event_all = collections.OrderedDict()
   for it in range(0, args.iterate):
@@ -141,11 +149,16 @@
       # Processing error
       print("Failed to collect valid samples for run {0}".format(it))
       continue
+
     if args.bootchart:
-      grab_bootchart(boot_chart_file_name_prefix + "_run_" + str(it))
+      grab_bootchart(boot_chart_file_path_prefix + "_run_" + str(it))
 
     if args.systrace:
-      grab_systrace(systrace_file_name_prefix + "_run_" + str(it))
+      grab_systrace(systrace_file_path_prefix + "_run_" + str(it))
+
+    if args.carwatchdog:
+      grab_carwatchdog_bootstats(args.output)
+
     for k, v in processing_data.items():
       if k not in data_points:
         data_points[k] = []
@@ -332,6 +345,8 @@
   logcat_stop_events = [LOGCAT_BOOT_COMPLETE, LAUNCHER_START]
   if args.fs_check:
     logcat_stop_events.append("FsStat")
+  if args.carwatchdog:
+    logcat_stop_events.append(CARWATCHDOG_BOOT_COMPLETE)
   logcat_events, logcat_timing_events = collect_events(
     search_events_pattern, ADB_CMD + ' logcat -b all -v epoch', timings_pattern,\
     logcat_stop_events, False)
@@ -504,6 +519,8 @@
   parser.add_argument('-r', '--reboot', dest='reboot',
                       action='store_true',
                       help='reboot device for measurement', )
+  parser.add_argument('-o', '--output', dest='output', type=str,
+                      help='Output directory where results are stored')
   parser.add_argument('-c', '--config', dest='config',
                       default='config.yaml', type=argparse.FileType('r'),
                       help='config file for the tool', )
@@ -543,6 +560,8 @@
   parser.add_argument('-y', '--systrace', dest='systrace',
                       action='store_true',
                       help='collect systrace from the device. kernel trace should be already enabled', )
+  parser.add_argument('-W', '--carwatchdog', dest='carwatchdog', action='store_true',
+                      help='collect carwatchdog boot stats')
   parser.add_argument('-G', '--buffersize', dest='buffersize', action='store', type=str,
                       default=None,
                       help='set logcat buffersize')
@@ -677,6 +696,15 @@
       log_timeout(time_left, stop_events, events, timing_events)
       break
     polled_events = read_poll.poll(time_left * 1000.0)
+    # adb logcat subprocess is auto-terminated when the adb connection is lost.
+    # Thus, check for the subprocess return code and reconnect to the device if
+    # needed. Otherwise, the logcat events cannot be polled completely.
+    if process.poll() is not None:
+      print("adb might be disconnected?\nRetrying to connect.")
+      run_adb_cmd('wait-for-device')
+      print(" reconnected")
+      init = True
+      continue
     if len(polled_events) == 0:
       log_timeout(time_left, stop_events, events, timing_events)
       break
@@ -830,14 +858,21 @@
     if run_adb_cmd('logcat -G {}'.format(adb_buffersize)) != 0:
       debug('Fail to set logcat buffer size as {}'.format(adb_buffersize))
 
-def run_adb_cmd(cmd):
-  return subprocess.call(ADB_CMD + ' ' + cmd, shell=True)
+'''
+Runs adb command. If do_return_result is true then output of command is
+returned otherwise an empty string is returned.
+'''
+def run_adb_cmd(cmd, do_return_result=False):
+  if do_return_result:
+    return subprocess.check_output(ADB_CMD + ' ' + cmd, shell=True).decode('utf-8', 'ignore').strip()
+  subprocess.call(ADB_CMD + ' ' + cmd, shell=True)
+  return ""
 
-def run_adb_shell_cmd(cmd):
-  return subprocess.call(ADB_CMD + ' shell ' + cmd, shell=True)
+def run_adb_shell_cmd(cmd, do_return_result=False):
+  return run_adb_cmd('shell ' + cmd, do_return_result)
 
-def run_adb_shell_cmd_as_root(cmd):
-  return subprocess.call(ADB_CMD + ' shell su root ' + cmd, shell=True)
+def run_adb_shell_cmd_as_root(cmd, do_return_result=False):
+  return run_adb_shell_cmd('su root ' + cmd, do_return_result)
 
 def logcat_time_func(offset_year):
   def f(date_str):
@@ -856,21 +891,95 @@
   variance = sq_diffs_sum / items_count
   return math.sqrt(variance)
 
-def grab_bootchart(boot_chart_file_name):
-  subprocess.call("$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh", shell=True)
-  print("Saving boot chart as " + boot_chart_file_name + ".tgz")
-  subprocess.call('cp /tmp/android-bootchart/bootchart.tgz ./' + boot_chart_file_name + '.tgz',\
+def grab_bootchart(boot_chart_file_path):
+  subprocess.run("$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh", shell=True,
+                 stdout=subprocess.DEVNULL)
+  print("Saving boot chart as " + boot_chart_file_path + ".tgz")
+  subprocess.call('cp /tmp/android-bootchart/bootchart.tgz ' + boot_chart_file_path + '.tgz', \
                   shell=True)
-  subprocess.call('cp ./bootchart.png ./' + boot_chart_file_name + '.png', shell=True)
+  subprocess.call('cp ./bootchart.png ' + boot_chart_file_path + '.png', shell=True)
 
-def grab_systrace(systrace_file_name):
-  trace_file = systrace_file_name + "_trace.txt"
+def grab_systrace(systrace_file_path_prefix):
+  trace_file = systrace_file_path_prefix + "_trace.txt"
   with open(trace_file, 'w') as f:
     f.write("TRACE:\n")
   run_adb_shell_cmd_as_root("cat /d/tracing/trace >> " + trace_file)
-  html_file = systrace_file_name + ".html"
+  html_file = systrace_file_path_prefix + ".html"
   subprocess.call("$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=" + trace_file + " -o " +\
                   html_file, shell=True)
 
+def capture_build_info(out_dir, build_info_file_name):
+  fingerprint = run_adb_shell_cmd('getprop ro.build.fingerprint', True)
+  brand = run_adb_shell_cmd('getprop ro.product.brand', True)
+  product = run_adb_shell_cmd('getprop ro.product.name', True)
+  device = run_adb_shell_cmd('getprop ro.product.device', True)
+  version_release = run_adb_shell_cmd('getprop ro.build.version.release', True)
+  id = run_adb_shell_cmd('getprop ro.build.id', True)
+  version_incremental = run_adb_shell_cmd('getprop ro.build.version.incremental', True)
+  type = run_adb_shell_cmd('getprop ro.build.type', True)
+  tags = run_adb_shell_cmd('getprop ro.build.tags', True)
+  sdk = run_adb_shell_cmd('getprop ro.build.version.sdk', True)
+  platform_minor = run_adb_shell_cmd('getprop ro.android.car.version.platform_minor', True)
+  codename = run_adb_shell_cmd('getprop ro.build.version.codename', True)
+  carwatchdog_collection_interval = run_adb_shell_cmd('getprop ro.carwatchdog.system_event_collection_interval', True)
+  carwatchdog_post_event_duration = run_adb_shell_cmd('getprop ro.carwatchdog.post_system_event_duration', True)
+  carwatchdog_top_n_category = run_adb_shell_cmd('getprop ro.carwatchdog.top_n_stats_per_category', True)
+  carwatchdog_top_n_subcategory = run_adb_shell_cmd('getprop ro.carwatchdog.top_n_stats_per_subcategory', True)
+
+  # TODO: Change file format to JSON to avoid custom parser
+  build_info = []
+  build_info.append('Build information: ')
+  build_info.append('-' * 20)
+  build_info.append('fingerprint: ' + fingerprint)
+  build_info.append('brand: ' + brand)
+  build_info.append('product: ' + product)
+  build_info.append('device: ' + device)
+  build_info.append('version.release: ' + version_release)
+  build_info.append('id: ' + id)
+  build_info.append('version.incremental: ' + version_incremental)
+  build_info.append('type: ' + type)
+  build_info.append('tags: ' + tags)
+  build_info.append('sdk: ' + sdk)
+  build_info.append('platform minor version: ' + platform_minor)
+  build_info.append('codename: ' + codename)
+  build_info.append('carwatchdog collection interval (s): ' + carwatchdog_collection_interval)
+  build_info.append('carwatchdog post event duration (s): ' + carwatchdog_post_event_duration)
+  build_info.append('carwatchdog top N packages: ' + carwatchdog_top_n_category)
+  build_info.append('carwatchdog top N processes: ' + carwatchdog_top_n_subcategory)
+
+  build_info_str = '\n'.join(build_info)
+
+  with open(out_dir + '/' + build_info_file_name, 'w') as f:
+    f.write(build_info_str)
+
+def generate_proto(dump_file, build_info_file, out_proto_file):
+  subprocess.run("{} -f {} -b {} -d {}".format(CARWATCHDOG_PARSER_CMD,
+                                               dump_file,
+                                               build_info_file,
+                                               out_proto_file),
+                  shell=True, stdout=subprocess.DEVNULL)
+
+def grab_carwatchdog_bootstats(result_dir):
+  carwatchdog_state = run_adb_shell_cmd_as_root('getprop init.svc.carwatchdogd', True)
+  if carwatchdog_state != "running":
+    print('carwatchdog (-d) flag set but CarWatchdog is not running on device')
+    return
+  elif not result_dir:
+    print('carwatchdog needs the output directory to be specified.')
+    return
+  print("Capturing carwatchdog stats")
+  build_info_file_name = "device_build_info.txt"
+  capture_build_info(result_dir, build_info_file_name)
+
+  # Capture CW dump and save dump to txt
+  dump_file_name = result_dir + '/carwatchdog_dump.txt'
+  subprocess.call(CARWATCHDOG_DUMP_COMMAND + " > " + dump_file_name, shell=True)
+
+  # Generate proto from dump
+  build_info_file_path = result_dir + '/' + build_info_file_name
+  out_proto_file_path = result_dir + '/carwatchdog_perf_stats_out.pb'
+  generate_proto(dump_file_name, build_info_file_path, out_proto_file_path)
+
+
 if __name__ == '__main__':
   main()
diff --git a/boottime_tools/bootanalyze/bootanalyze.sh b/boottime_tools/bootanalyze/bootanalyze.sh
index 7fce2e1..bada465 100755
--- a/boottime_tools/bootanalyze/bootanalyze.sh
+++ b/boottime_tools/bootanalyze/bootanalyze.sh
@@ -16,13 +16,18 @@
 
 readme() {
     echo '
-Analyze boot-time & bootchart
+Analyze boot-time
 e.g.
 ANDROID_BUILD_TOP="$PWD" \
 CONFIG_YMAL="$ANDROID_BUILD_TOP/system/extras/boottime_tools/bootanalyze/config.yaml" \
     LOOPS=3 \
-    RESULTS_DIR="$ANDROID_BUILD_TOP/bootAnalyzeResults" \
-    $PWD/system/extras/boottime_tools/bootanalyze/bootanalyze.sh
+    RESULTS_DIR="$PWD/bootAnalyzeResults" \
+    $ANDROID_BUILD_TOP/system/extras/boottime_tools/bootanalyze/bootanalyze.sh
+
+Flags:
+-a : Uses "adb reboot" (instead of "adb shell su root svc power reboot") command to reboot
+-b : If set grabs bootchart
+-w : If set grabs carwatchdog perf stats
 '
     exit
 }
@@ -48,6 +53,29 @@
 echo "RESULTS_DIR=$RESULTS_DIR"
 mkdir -p $RESULTS_DIR
 
+ADB_REBOOT_FLAG=""
+BOOTCHART_FLAG=""
+CARWATCHDOG_FLAG=""
+
+while getopts 'abw' OPTION; do
+  case "$OPTION" in
+    a)
+      ADB_REBOOT_FLAG="-a"
+      ;;
+    b)
+      BOOTCHART_FLAG="-b"
+      ;;
+    w)
+      CARWATCHDOG_FLAG="-W"
+      ;;
+    ?)
+      echo 'Error: Invalid flag set'
+      readme
+      ;;
+  esac
+done
+shift "$(($OPTIND -1))"
+
 
 adb shell 'touch /data/bootchart/enabled'
 
@@ -55,15 +83,25 @@
 	LOOPS=1
 fi
 echo "Analyzing boot-time for LOOPS=$LOOPS"
+BOOTCHART_TGZ="/tmp/android-bootchart/bootchart.tgz"
 START=1
 
-SLEEP_SEC=30
+SLEEP_SEC=20
 for (( l=$START; l<=$LOOPS; l++ )); do
-    echo -n "Loop: $l"
+    echo "Loop: $l"
     SECONDS=0
-    $SCRIPT_DIR/bootanalyze.py -c $CONFIG_YMAL -G 4M -r -b > "$RESULTS_DIR/boot$l.txt"
+    mkdir $RESULTS_DIR/$l
+    $SCRIPT_DIR/bootanalyze.py -c $CONFIG_YMAL -G 4M -r \
+        $ADB_REBOOT_FLAG $BOOTCHART_FLAG $CARWATCHDOG_FLAG \
+        -o "$RESULTS_DIR/$l" 1> "$RESULTS_DIR/$l/boot.txt"
+    if [[ $? -ne 0 ]]; then
+        echo "bootanalyze.py failed"
+        exit 1
+    fi
     echo "$SECONDS sec."
-    cp /tmp/android-bootchart/bootchart.tgz "$RESULTS_DIR/bootchart$l.tgz"
+    if [ -f "$BOOTCHART_TGZ" ]; then
+        cp $BOOTCHART_TGZ "$RESULTS_DIR/$l/bootchart.tgz"
+    fi
     echo "Sleep for $SLEEP_SEC sec."
     sleep $SLEEP_SEC
 done
diff --git a/boottime_tools/bootanalyze/bugreport_anayze.py b/boottime_tools/bootanalyze/bugreport_anayze.py
index 3ea7552..07c8b8d 100644
--- a/boottime_tools/bootanalyze/bugreport_anayze.py
+++ b/boottime_tools/bootanalyze/bugreport_anayze.py
@@ -138,7 +138,7 @@
     self.re_log_start = re.compile(LOG_START_PATTERN)
     self.re_log_end = re.compile(LOG_END_PATTERN)
     self.f = bugreport_file
-    cfg = yaml.load(config_file, Loader=yaml.FullLoader)
+    cfg = yaml.load(config_file, Loader=yaml.SafeLoader)
     self.event_patterns = {key: re.compile(pattern)
                          for key, pattern in cfg['events'].iteritems()}
     self.timing_patterns = {key: re.compile(pattern)
diff --git a/boottime_tools/bootanalyze/config.yaml b/boottime_tools/bootanalyze/config.yaml
index 83c1bcd..a41cfad 100644
--- a/boottime_tools/bootanalyze/config.yaml
+++ b/boottime_tools/bootanalyze/config.yaml
@@ -63,6 +63,7 @@
   BootComplete_kernel: processing action \(sys\.boot_completed=1\)
   LauncherStart: START.*HOME.*(NexusLauncherActivity|GEL|LensPickerTrampolineActivity|SetupWizard|CarLauncher|launcher.*Launcher)
   FsStat: fs_stat, partition:userdata stat:(0x\S+)
+  CarWatchdogBootupProfilingComplete: Switching to PERIODIC_COLLECTION and PERIODIC_MONITOR
 shutdown_events:
   ShutdownStart: ShutdownThread:\sNotifying thread to start shutdown
   ShutdownBroadcast: ShutdownThread:\sSending shutdown broadcast
diff --git a/boottime_tools/bootio/bootio_collector.cpp b/boottime_tools/bootio/bootio_collector.cpp
index dc13525..037a3e2 100644
--- a/boottime_tools/bootio/bootio_collector.cpp
+++ b/boottime_tools/bootio/bootio_collector.cpp
@@ -66,7 +66,12 @@
 int ReadIo(char *filename, AppSample *sample) {
     FILE *file;
     char line[MAX_LINE];
-    unsigned int rchar, wchar, syscr, syscw, readbytes, writebytes;
+    unsigned int rchar = 0;
+    unsigned int wchar = 0;
+    unsigned int syscr = 0;
+    unsigned int syscw = 0;
+    unsigned int readbytes = 0;
+    unsigned int writebytes = 0;
 
     file = fopen(filename, "r");
     if (!file) return 1;
@@ -289,17 +294,17 @@
                    cpuLoad);
             isFirstSample = false;
         }
-        printf("-----------------------------------------------------------------------------\n");
+        if (!newerSample) {
+            LOG(ERROR) << "newerSample is null";
+        } else {
+            printf("-----------------------------------------------------------------------------"
+                   "\n");
 #define NUMBER "%-13" PRId64
-        printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n",
+            printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n",
 #undef NUMBER
-               "Total",
-               newerSample->rchar(),
-               newerSample->wchar(),
-               newerSample->syscr(),
-               newerSample->syscw(),
-               newerSample->readbytes(),
-               newerSample->writebytes());
+                   "Total", newerSample->rchar(), newerSample->wchar(), newerSample->syscr(),
+                   newerSample->syscw(), newerSample->readbytes(), newerSample->writebytes());
+        }
     }
     printf("\nAggregations\n%-10s%-13s%-13s%-13s\n",
            "Total",
diff --git a/checkpoint_gc/checkpoint_gc.sh b/checkpoint_gc/checkpoint_gc.sh
index b308046..10514b5 100644
--- a/checkpoint_gc/checkpoint_gc.sh
+++ b/checkpoint_gc/checkpoint_gc.sh
@@ -28,6 +28,15 @@
 TIME=0
 MAX_TIME=1200
 
+# GC_URGENT_MID, will fall back to GC_URGENT_HIGH if unsupported
+GC_TYPE=3
+
+# If we fall back, start off with less impactful GC
+# To avoid long wait time, ramp up over time
+GC_SLEEP_MAX=150
+GC_SLEEP_MIN=50
+GC_SLEEP_STEP=5
+
 # We only need to run this if we're using f2fs
 if [ ! -f /dev/sys/fs/by-name/userdata/gc_urgent ]; then
   exit 0
@@ -47,13 +56,31 @@
 fi
 
 log -pi -t checkpoint_gc Turning on GC for userdata
-echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent || exit 1
+
+read OLD_SLEEP < /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time || \
+  { log -pw -t checkpoint_gc Cannot read gc_urgent_sleep_time; exit 1; }
+GC_SLEEP=${GC_SLEEP_MAX}
+echo ${GC_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time || \
+  { log -pw -t checkpoint_gc Cannot set gc_urgent_sleep_time; exit 1; }
+
+
+echo ${GC_TYPE} > /dev/sys/fs/by-name/userdata/gc_urgent \
+  || { GC_TYPE=1; log -pi -t checkpoint_gc GC_URGENT_MID not supported, using GC_URGENT_HIGH; }
+
+if [ ${GC_TYPE} -eq 1 ]; then
+  echo ${GC_TYPE} > /dev/sys/fs/by-name/userdata/gc_urgent || \
+    { echo ${OLD_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time; \
+    log -pw -t checkpoint_gc Failed to set gc_urgent; exit 1; }
+else
+  # GC MID will wait for background I/O, so no need to start small
+  GC_SLEEP=${GC_SLEEP_MIN}
+fi
 
 
 CURRENT=${START}
 TODO=$((${START}-${THRESHOLD}))
 while [ ${CURRENT} -gt ${THRESHOLD} ]; do
-  log -pi -t checkpoint_gc ${METRIC}:${CURRENT} \(threshold:${THRESHOLD}\)
+  log -pi -t checkpoint_gc ${METRIC}:${CURRENT} \(threshold:${THRESHOLD}\) mode:${GC_TYPE} GC_SLEEP:${GC_SLEEP}
   PROGRESS=`echo "(${START}-${CURRENT})/${TODO}"|bc -l`
   if [[ $PROGRESS == -* ]]; then
       PROGRESS=0
@@ -67,17 +94,22 @@
   sleep ${SLEEP}
   TIME=$((${TIME}+${SLEEP}))
   if [ ${TIME} -gt ${MAX_TIME} ]; then
-    log -pi -t checkpoint_gc Timed out with gc threshold not met.
+    log -pw -t checkpoint_gc Timed out with gc threshold not met.
     break
   fi
+  if [ ${GC_SLEEP} -gt ${GC_SLEEP_MIN} ]; then
+    GC_SLEEP=$((${GC_SLEEP}-${GC_SLEEP_STEP}))
+  fi
   # In case someone turns it off behind our back
-  echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent
+  echo ${GC_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time
+  echo ${GC_TYPE} > /dev/sys/fs/by-name/userdata/gc_urgent
 done
 
 # It could be a while before the system reboots for the update...
 # Leaving on low level GC can help ensure the boot for ota is faster
 # If powerhints decides to turn it off, we'll just rely on normal GC
-log -pi -t checkpoint_gc Leaving on low GC for userdata
+log -pi -t checkpoint_gc Leaving on GC_URGENT_LOW for userdata
+echo ${OLD_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time
 echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent
 sync
 
diff --git a/cppreopts/fstab.postinstall b/cppreopts/fstab.postinstall
index 81272eb..6802bb3 100644
--- a/cppreopts/fstab.postinstall
+++ b/cppreopts/fstab.postinstall
@@ -18,4 +18,9 @@
 
 #<src>                    <mnt_point>  <type> <mnt_flags and options> <fs_mgr_flags>
 system                    /postinstall ext4   ro,nosuid,nodev,noexec  slotselect_other,logical
+system                    /postinstall erofs  ro,nosuid,nodev,noexec  slotselect_other,logical
 /dev/block/by-name/system /postinstall ext4   ro,nosuid,nodev,noexec  slotselect_other
+/dev/block/by-name/system /postinstall erofs  ro,nosuid,nodev,noexec  slotselect_other
+/dev/block/mapper/system /postinstall ext4   ro,nosuid,nodev,noexec  slotselect_other
+/dev/block/mapper/system /postinstall erofs  ro,nosuid,nodev,noexec  slotselect_other
+
diff --git a/cpu_loads/Android.bp b/cpu_loads/Android.bp
new file mode 100644
index 0000000..f61aa32
--- /dev/null
+++ b/cpu_loads/Android.bp
@@ -0,0 +1,116 @@
+//
+// 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.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "memcpy",
+    srcs: ["memcpy.cpp"],
+    cppflags: [
+        "-g",
+        "-O0",
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-sign-compare",
+    ],
+}
+
+cc_binary {
+    name: "memcpy-16kb",
+    srcs: ["memcpy-16kb.cpp"],
+    cppflags: [
+        "-g",
+        "-O0",
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-sign-compare",
+    ],
+}
+
+cc_binary {
+    name: "memcpy-2048kb",
+    srcs: ["memcpy-2048kb.cpp"],
+    cppflags: [
+        "-g",
+        "-O0",
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-sign-compare",
+    ],
+}
+
+cc_binary {
+    name: "memcpy-byte",
+    srcs: ["memcpy-byte.cpp"],
+    cppflags: [
+        "-g",
+        "-O0",
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-sign-compare",
+    ],
+}
+
+cc_binary {
+    name: "while-true",
+    srcs: ["while-true.cpp"],
+    cppflags: [
+        "-g",
+        "-O0",
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-sign-compare",
+    ],
+}
+
+cc_binary {
+    name: "simd",
+    srcs: ["simd.cpp"],
+    header_libs: [
+        "libeigen",
+    ],
+    cppflags: [
+        "-O2",
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-sign-compare",
+    ],
+}
+
+cc_binary {
+    name: "pss",
+    srcs: ["pss.cpp"],
+    shared_libs: [
+        "libmeminfo",
+        "libbase",
+    ],
+    cppflags: [
+        "-g",
+        "-O0",
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-sign-compare",
+    ],
+}
diff --git a/cpu_loads/README.md b/cpu_loads/README.md
new file mode 100644
index 0000000..7f22379
--- /dev/null
+++ b/cpu_loads/README.md
@@ -0,0 +1,46 @@
+# CPU test loads
+
+These are a collection of simple workloads designed to induce various levels of power consumption on the CPU and memory subsystems of an SOC. All of these workloads run in an infinite loop and are designed to be measured across a fixed duration. They are not benchmarks and provide no information about the performance of various cores; they are only designed to generate different amounts of load for the purposes of measuring power consumption.
+
+## Workloads
+
+- `simd` is a large double-precision matrix multiplication using Eigen
+- `memcpy` copies a 1GB buffer to a second 1GB buffer using bionic `memcpy`
+- `memcpy-16kb` copies a 16KB buffer to a second 16KB buffer using bionic `memcpy`
+- `memcpy-2048kb` copies a 2048KB buffer to a second 2048KB buffer using bionic `memcpy`
+- `memcpy-byte` copies a 1GB buffer to a second 1GB buffer using byte assignment
+- `while-true` stalls at a `while (true);`, which becomes an unconditional branch to the same instruction
+- `pss` allocates a 1GB buffer and repeatedly measures the process's PSS
+
+## Usage
+
+1. Build the tests for a given device with `mm`.
+2. Push the tests to the device; usually this is something like
+
+```
+adb push out/target/product/<device target>/system/bin/simd /data/local/tmp
+```
+
+3. Prepare the device to run the test. This usually means stopping the framework, locking a sustainable CPU frequency, and moving the shell to a cpuset containing only a single core. For example:
+
+```
+stop
+mkdir /dev/cpuset/cpu7
+echo 0 > /dev/cpuset/cpu7/mems
+echo 7 > /dev/cpuset/cpu7/cpus
+echo $$ > /dev/cpuset/cpu7/cgroup.procs
+
+cat /sys/devices/system/cpu/cpu7/cpufreq/scaling_available_frequencies
+# 500000 851000 984000 1106000 1277000 1426000 1582000 1745000 1826000 2048000 2188000 2252000 2401000 2507000 2630000 2704000 2802000 2850000
+echo 1826000 > /sys/devices/system/cpu/cpu7/cpufreq/scaling_min_freq
+echo 1826000 > /sys/devices/system/cpu/cpu7/cpufreq/scaling_max_freq
+```
+
+4. Run the tests on the device; there are no arguments.
+5. Measure power somehow. On a device with ODPM capabilities, this could be something like
+
+```
+dumpsys android.hardware.power.stats.IPowerStats/default | tail -27 && sleep 120 && killall memcpy-2048kb && echo "done" && dumpsys android.hardware.power.stats.IPowerStats/default | tail -27
+```
+
+from a separate `adb shell` to the shell running the test. Alternately, a breakout board with per-rail measurements or a separate battery monitor could be used.
diff --git a/cpu_loads/memcpy-16kb.cpp b/cpu_loads/memcpy-16kb.cpp
new file mode 100644
index 0000000..2b082b5
--- /dev/null
+++ b/cpu_loads/memcpy-16kb.cpp
@@ -0,0 +1,32 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (16 * 1024)
+
+int main(int, char**) {
+    void* src = malloc(BUFFER_SIZE);
+    for (size_t i = 0; i < BUFFER_SIZE; i++) {
+        ((char*)src)[i] = (char)i;
+    }
+    void* dst = malloc(BUFFER_SIZE);
+    while (true) {
+        memcpy(dst, src, BUFFER_SIZE);
+    }
+    ((char*)dst)[0] = 0;
+    return 0;
+}
diff --git a/cpu_loads/memcpy-2048kb.cpp b/cpu_loads/memcpy-2048kb.cpp
new file mode 100644
index 0000000..6954d31
--- /dev/null
+++ b/cpu_loads/memcpy-2048kb.cpp
@@ -0,0 +1,32 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (2 * 1024 * 1024)
+
+int main(int, char**) {
+    void* src = malloc(BUFFER_SIZE);
+    for (size_t i = 0; i < BUFFER_SIZE; i++) {
+        ((char*)src)[i] = (char)i;
+    }
+    void* dst = malloc(BUFFER_SIZE);
+    while (true) {
+        memcpy(dst, src, BUFFER_SIZE);
+    }
+    ((char*)dst)[0] = 0;
+    return 0;
+}
diff --git a/cpu_loads/memcpy-byte.cpp b/cpu_loads/memcpy-byte.cpp
new file mode 100644
index 0000000..70293cb
--- /dev/null
+++ b/cpu_loads/memcpy-byte.cpp
@@ -0,0 +1,33 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+    volatile void* src = malloc(BUFFER_SIZE);
+    for (size_t i = 0; i < BUFFER_SIZE; i++) {
+        ((char*)src)[i] = (char)i;
+    }
+    volatile void* dst = malloc(BUFFER_SIZE);
+    while (true) {
+        for (size_t i = 0; i < BUFFER_SIZE; i++) {
+            ((char*)dst)[i] = ((char*)src)[i];
+        }
+    }
+    return 0;
+}
diff --git a/cpu_loads/memcpy.cpp b/cpu_loads/memcpy.cpp
new file mode 100644
index 0000000..38027a2
--- /dev/null
+++ b/cpu_loads/memcpy.cpp
@@ -0,0 +1,32 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+    void* src = malloc(BUFFER_SIZE);
+    for (size_t i = 0; i < BUFFER_SIZE; i++) {
+        ((char*)src)[i] = (char)i;
+    }
+    void* dst = malloc(BUFFER_SIZE);
+    while (true) {
+        memcpy(dst, src, BUFFER_SIZE);
+    }
+    ((char*)dst)[0] = 0;
+    return 0;
+}
diff --git a/cpu_loads/pss.cpp b/cpu_loads/pss.cpp
new file mode 100644
index 0000000..1cc8145
--- /dev/null
+++ b/cpu_loads/pss.cpp
@@ -0,0 +1,40 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <meminfo/procmeminfo.h>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+    // waste a bunch of memory
+    void* src = malloc(BUFFER_SIZE);
+    for (size_t i = 0; i < BUFFER_SIZE; i++) {
+        ((char*)src)[i] = (char)i;
+    }
+    void* dst = malloc(BUFFER_SIZE);
+    memcpy(dst, src, BUFFER_SIZE);
+
+    uint64_t pss;
+    // should always return true
+    std::string pid_path = android::base::StringPrintf("/proc/%d/smaps", getpid());
+    while (android::meminfo::SmapsOrRollupPssFromFile(pid_path, &pss))
+        ;
+
+    return 0;
+}
diff --git a/cpu_loads/simd.cpp b/cpu_loads/simd.cpp
new file mode 100644
index 0000000..3add21f
--- /dev/null
+++ b/cpu_loads/simd.cpp
@@ -0,0 +1,44 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#define EIGEN_RUNTIME_NO_MALLOC
+
+#include <Eigen/Dense>
+
+using namespace std;
+
+int main(int, char**) {
+    Eigen::MatrixXd a(8192, 8192);
+    Eigen::MatrixXd b(8192, 8192);
+    Eigen::MatrixXd c(8192, 8192);
+
+    for (int i = 0; i < 8192; i++) {
+        for (int j = 0; j < 8192; j++) {
+            a(i, j) = 1 + i * j;
+            b(i, j) = 2 + i * j;
+            c(i, j) = 3 + i * j;
+        }
+    }
+
+    cout << "starting" << endl;
+    while (true) {
+        a.noalias() += (b * c);
+        b(1, 5) += 5.0;
+        c(5, 1) -= 5.0;
+    }
+
+    return 0;
+}
diff --git a/cpu_loads/while-true.cpp b/cpu_loads/while-true.cpp
new file mode 100644
index 0000000..ac795c4
--- /dev/null
+++ b/cpu_loads/while-true.cpp
@@ -0,0 +1,33 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+    volatile void* src = malloc(BUFFER_SIZE);
+    for (size_t i = 0; i < BUFFER_SIZE; i++) {
+        ((char*)src)[i] = (char)i;
+    }
+    volatile void* dst = malloc(BUFFER_SIZE);
+
+    while (true)
+        ;
+
+    ((char*)dst)[0] = 0;
+    return 0;
+}
diff --git a/ext4_utils/Android.bp b/ext4_utils/Android.bp
index ba2c8ac..b28e84f 100644
--- a/ext4_utils/Android.bp
+++ b/ext4_utils/Android.bp
@@ -32,6 +32,7 @@
     cflags: [
         "-Werror",
         "-fno-strict-aliasing",
+        "-D_FILE_OFFSET_BITS=64",
     ],
     export_include_dirs: ["include"],
     shared_libs: [
diff --git a/ext4_utils/Android.mk b/ext4_utils/Android.mk
deleted file mode 100644
index b55613f..0000000
--- a/ext4_utils/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2010 The Android Open Source Project
-
-LOCAL_PATH:= $(call my-dir)
-
-#
-# -- All host/targets excluding windows
-#
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := mke2fs.conf
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_IS_HOST_MODULE := true
-include $(BUILD_PREBUILT)
diff --git a/ext4_utils/ext4_utils.cpp b/ext4_utils/ext4_utils.cpp
index 632d829..dde7590 100644
--- a/ext4_utils/ext4_utils.cpp
+++ b/ext4_utils/ext4_utils.cpp
@@ -83,12 +83,9 @@
 
 /* Function to read the primary superblock */
 void read_sb(int fd, struct ext4_super_block* sb) {
-    off64_t ret;
+    if (lseek(fd, 1024, SEEK_SET) < 0) critical_error_errno("failed to seek to superblock");
 
-    ret = lseek64(fd, 1024, SEEK_SET);
-    if (ret < 0) critical_error_errno("failed to seek to superblock");
-
-    ret = read(fd, sb, sizeof(*sb));
+    ssize_t ret = read(fd, sb, sizeof(*sb));
     if (ret < 0) critical_error_errno("failed to read superblock");
     if (ret != sizeof(*sb)) critical_error("failed to read all of superblock");
 }
@@ -144,7 +141,8 @@
         aux_info.sb = aux_info.sb_block;
 
     /* Alloc an array to hold the pointers to the backup superblocks */
-    aux_info.backup_sb = (struct ext4_super_block**)calloc(aux_info.groups, sizeof(char*));
+    aux_info.backup_sb =
+            (struct ext4_super_block**)calloc(aux_info.groups, sizeof(struct ext4_super_block*));
 
     if (!aux_info.sb) critical_error_errno("calloc");
 
@@ -276,17 +274,17 @@
 }
 
 int read_ext(int fd, int verbose) {
-    off64_t ret;
+    off_t ret;
     struct ext4_super_block sb;
 
     read_sb(fd, &sb);
 
     ext4_parse_sb_info(&sb);
 
-    ret = lseek64(fd, info.len, SEEK_SET);
+    ret = lseek(fd, info.len, SEEK_SET);
     if (ret < 0) critical_error_errno("failed to seek to end of input image");
 
-    ret = lseek64(fd, info.block_size * (aux_info.first_data_block + 1), SEEK_SET);
+    ret = lseek(fd, info.block_size * (aux_info.first_data_block + 1), SEEK_SET);
     if (ret < 0) critical_error_errno("failed to seek to block group descriptors");
 
     read_block_group_descriptors(fd);
diff --git a/ext4_utils/include/ext4_utils/ext4.h b/ext4_utils/include/ext4_utils/ext4.h
index 54e5d86..df1ba57 100644
--- a/ext4_utils/include/ext4_utils/ext4.h
+++ b/ext4_utils/include/ext4_utils/ext4.h
@@ -526,6 +526,7 @@
 #define EXT4_FEATURE_COMPAT_EXT_ATTR 0x0008
 #define EXT4_FEATURE_COMPAT_RESIZE_INODE 0x0010
 #define EXT4_FEATURE_COMPAT_DIR_INDEX 0x0020
+#define EXT4_FEATURE_COMPAT_STABLE_INODES 0x0800
 
 #define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
 #define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002
diff --git a/ext4_utils/include/ext4_utils/ext4_utils.h b/ext4_utils/include/ext4_utils/ext4_utils.h
index 48f3ee7..d6bef68 100644
--- a/ext4_utils/include/ext4_utils/ext4_utils.h
+++ b/ext4_utils/include/ext4_utils/ext4_utils.h
@@ -21,11 +21,6 @@
 extern "C" {
 #endif
 
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#define _FILE_OFFSET_BITS 64
-#define _LARGEFILE64_SOURCE 1
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -38,13 +33,6 @@
 #include <string.h>
 #include <sys/types.h>
 
-#if defined(__APPLE__) && defined(__MACH__)
-#define lseek64 lseek
-#define ftruncate64 ftruncate
-#define mmap64 mmap
-#define off64_t off_t
-#endif
-
 #include "ext4_sb.h"
 
 extern int force;
diff --git a/f2fs_utils/Android.bp b/f2fs_utils/Android.bp
index dc74d6d..e27e28c 100644
--- a/f2fs_utils/Android.bp
+++ b/f2fs_utils/Android.bp
@@ -30,6 +30,7 @@
 
     include_dirs: [
         "external/f2fs-tools/include",
+        "bionic/libc",
     ],
 
     export_include_dirs: ["."],
@@ -48,16 +49,15 @@
 
     include_dirs: [
         "external/f2fs-tools/include",
+        "bionic/libc",
     ],
 }
 
-sh_binary {
-    name: "mkf2fsuserimg.sh",
-
+sh_binary_host {
+    name: "mkf2fsuserimg",
     src: "mkf2fsuserimg.sh",
     required: [
         "make_f2fs",
         "sload_f2fs",
     ],
-    host_supported: true,
 }
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index df59d7c..234ea7e 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -26,7 +26,7 @@
 #define D_DISP_u64(ptr, member)                                                 \
     do {                                                                        \
         SLOGV("%-30s"                                                           \
-              "\t\t[0x%#016llx : %llu]\n",                                      \
+              "\t\t[0x%#016" PRIx64 " : %" PRIu64 "]\n",                          \
               #member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member)); \
     } while (0);
 
diff --git a/f2fs_utils/mkf2fsuserimg.sh b/f2fs_utils/mkf2fsuserimg.sh
index 59f9eea..e95a0c5 100755
--- a/f2fs_utils/mkf2fsuserimg.sh
+++ b/f2fs_utils/mkf2fsuserimg.sh
@@ -163,13 +163,14 @@
 
   SLOAD_F2FS_CMD="sload_f2fs $SLOAD_OPTS $OUTPUT_FILE"
   echo $SLOAD_F2FS_CMD
-  MB_SIZE=`$SLOAD_F2FS_CMD | grep "Max image size" | awk '{print $5}'`
+  SLOAD_LOG=`$SLOAD_F2FS_CMD`
   # allow 1: Filesystem errors corrected
   ret=$?
   if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then
     rm -f $OUTPUT_FILE
     exit 4
   fi
+  MB_SIZE=`echo "$SLOAD_LOG" | grep "Max image size" | awk '{print $5}'`
   SIZE=$(((MB_SIZE + 6) * 1024 * 1024))
 }
 
diff --git a/ioblame/androidFsParser.py b/ioblame/androidFsParser.py
index 6ea38dc..c47b6f1 100644
--- a/ioblame/androidFsParser.py
+++ b/ioblame/androidFsParser.py
@@ -14,16 +14,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-"""Trace parser for android_fs traces."""
+"""Trace parser for f2fs traces."""
 
 import collections
 import re
 
-# ex) bt_stack_manage-21277   [000] ....  5879.043608: android_fs_datawrite_start: entry_name /misc/bluedroid/bt_config.bak.new, offset 0, bytes 408, cmdline bt_stack_manage, pid 21277, i_size 0, ino 9103
-RE_WRITE_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+android_fs_datawrite_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
+# ex) bt_stack_manage-21277   [000] ....  5879.043608: f2fs_datawrite_start: entry_name /misc/bluedroid/bt_config.bak.new, offset 0, bytes 408, cmdline bt_stack_manage, pid 21277, i_size 0, ino 9103
+RE_WRITE_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+f2fs_datawrite_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
 
-# ex)        dumpsys-21321   [001] ....  5877.599324: android_fs_dataread_start: entry_name /system/lib64/libbinder.so, offset 311296, bytes 4096, cmdline dumpsys, pid 21321, i_size 848848, ino 2397
-RE_READ_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+android_fs_dataread_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
+# ex)        dumpsys-21321   [001] ....  5877.599324: f2fs_dataread_start: entry_name /system/lib64/libbinder.so, offset 311296, bytes 4096, cmdline dumpsys, pid 21321, i_size 848848, ino 2397
+RE_READ_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+f2fs_dataread_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
 
 MIN_PID_BYTES = 1024 * 1024  # 1 MiB
 SMALL_FILE_BYTES = 1024  # 1 KiB
diff --git a/ioblame/ioblame.py b/ioblame/ioblame.py
index e00a5ec..d69dda4 100644
--- a/ioblame/ioblame.py
+++ b/ioblame/ioblame.py
@@ -60,7 +60,7 @@
 
 def init_arguments():
   parser = argparse.ArgumentParser(
-      description="Collect and process android_fs traces")
+      description="Collect and process f2fs traces")
   parser.add_argument(
       "-s",
       "--serial",
@@ -73,14 +73,14 @@
       default=False,
       action="store_true",
       dest="traceReads",
-      help="Trace android_fs_dataread_start")
+      help="Trace f2fs_dataread_start")
   parser.add_argument(
       "-w",
       "--trace_writes",
       default=False,
       action="store_true",
       dest="traceWrites",
-      help="Trace android_fs_datawrite_start")
+      help="Trace f2fs_datawrite_start")
   parser.add_argument(
       "-d",
       "--trace_duration",
@@ -187,7 +187,7 @@
   # This is a good point to check if the Android FS tracepoints are enabled in the
   # kernel or not
   isTraceEnabled = run_adb_shell_cmd(
-      "'if [ -d /sys/kernel/tracing/events/android_fs ]; then echo 0; else echo 1; fi'"
+      "'if [ -d /sys/kernel/tracing/events/f2fs ]; then echo 0; else echo 1; fi'"
   )
 
   if isTraceEnabled == 0:
@@ -200,12 +200,12 @@
 
   if shouldTraceReads:
     run_adb_shell_cmd(
-        "'echo 1 > /sys/kernel/tracing/events/android_fs/android_fs_dataread_start/enable'"
+        "'echo 1 > /sys/kernel/tracing/events/f2fs/f2fs_dataread_start/enable'"
     )
 
   if shouldTraceWrites:
     run_adb_shell_cmd(
-        "'echo 1 > /sys/kernel/tracing/events/android_fs/android_fs_datawrite_start/enable'"
+        "'echo 1 > /sys/kernel/tracing/events/f2fs/f2fs_datawrite_start/enable'"
     )
 
   run_adb_shell_cmd("'echo 1 > /sys/kernel/tracing/tracing_on'")
@@ -214,12 +214,12 @@
 def clear_tracing(shouldTraceReads, shouldTraceWrites):
   if shouldTraceReads:
     run_adb_shell_cmd(
-        "'echo 0 > /sys/kernel/tracing/events/android_fs/android_fs_dataread_start/enable'"
+        "'echo 0 > /sys/kernel/tracing/events/f2fs/f2fs_dataread_start/enable'"
     )
 
   if shouldTraceWrites:
     run_adb_shell_cmd(
-        "'echo 0 > /sys/kernel/tracing/events/android_fs/android_fs_datawrite_start/enable'"
+        "'echo 0 > /sys/kernel/tracing/events/f2fs/f2fs_datawrite_start/enable'"
     )
 
   run_adb_shell_cmd("'echo 0 > /sys/kernel/tracing/tracing_on'")
@@ -227,7 +227,7 @@
 
 def start_streaming_trace(traceFile):
   return run_bg_adb_shell_cmd(
-      "'cat /sys/kernel/tracing/trace_pipe | grep -e android_fs_data -e android_fs_writepages'\
+      "'cat /sys/kernel/tracing/trace_pipe | grep -e f2fs_data -e f2fs_writepages'\
       > {}".format(traceFile))
 
 
diff --git a/ioblame/ioblame.sh b/ioblame/ioblame.sh
index d6943e9..7b6d646 100755
--- a/ioblame/ioblame.sh
+++ b/ioblame/ioblame.sh
@@ -157,7 +157,7 @@
 clean_up_tracepoints() {
     # This is a good point to check if the Android FS tracepoints are enabled in the
     # kernel or not
-    tracepoint_exists=`adb shell 'if [ -d /sys/kernel/debug/tracing/events/android_fs ]; then echo 0; else echo 1; fi' `
+    tracepoint_exists=`adb shell 'if [ -d /sys/kernel/debug/tracing/events/f2fs ]; then echo 0; else echo 1; fi' `
     if [ $tracepoint_exists == 1 ]; then
 	echo "Android FS tracepoints not enabled in kernel. Exiting..."
 	exit 1
@@ -165,14 +165,14 @@
     adb shell 'echo 0 > /sys/kernel/debug/tracing/tracing_on'
     adb shell 'echo 0 > /sys/kernel/debug/tracing/trace'
     if [ $trace_reads == true ]; then
-	adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable'
+	adb shell 'echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_dataread_start/enable'
     fi
     if [ $trace_writes == true ]; then
-	adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_datawrite_start/enable'
+	adb shell 'echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_datawrite_start/enable'
     fi
     if [ $f2fs_fs == 1 ] ; then
 	if [ $trace_writepages == true ]; then
-	    adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_writepages/enable'
+	    adb shell 'echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_writepages/enable'
 	fi
     fi
     adb shell 'echo 1 > /sys/kernel/debug/tracing/tracing_on'
@@ -197,14 +197,14 @@
 copyout_trace() {
     streamtrace_end
     if [ $trace_reads == true ]; then
-	adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable'
+	adb shell 'echo 0 > /sys/kernel/debug/tracing/events/f2fs/f2fs_dataread_start/enable'
     fi
     if [ $trace_writes == true ]; then
-	adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_datawrite_start/enable'
+	adb shell 'echo 0 > /sys/kernel/debug/tracing/events/f2fs/f2fs_datawrite_start/enable'
     fi
     if [ $f2fs_fs == 1 ] ; then
 	if [ $trace_writepages == true ]; then
-	    adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_writepages/enable'
+	    adb shell 'echo 0 > /sys/kernel/debug/tracing/events/f2fs/f2fs_writepages/enable'
 	fi
     fi
     adb shell 'echo 0 > /sys/kernel/debug/tracing/tracing_on'
@@ -218,11 +218,11 @@
 }
 
 prep_tracefile_rd() {
-    prep_tracefile_common android_fs_dataread
+    prep_tracefile_common f2fs_dataread
     # Strip away unnecessary stuff so we can compute latencies easily
-    fgrep android_fs_dataread_start $infile > foo0
-    # Throw away everything upto and including android_fs_dataread:
-    cat foo0 | sed -n -e 's/^.*android_fs_dataread_start //p' > foo1
+    fgrep f2fs_dataread_start $infile > foo0
+    # Throw away everything upto and including f2fs_dataread:
+    cat foo0 | sed -n -e 's/^.*f2fs_dataread_start //p' > foo1
     mv foo1 $infile
     # At this stage, $infile should the following format :
     # entry_name <filename> offset <offset> bytes <bytes> cmdline <cmdline> pid <pid> i_size <i_size> ino <ino>
@@ -230,9 +230,9 @@
 }
 
 prep_tracefile_writepages() {
-    prep_tracefile_common android_fs_writepages
-    # Throw away everything up to and including android_fs_writepages_start:
-    cat $infile | sed -n -e 's/^.*android_fs_writepages //p' > foo1
+    prep_tracefile_common f2fs_writepages
+    # Throw away everything up to and including f2fs_writepages_start:
+    cat $infile | sed -n -e 's/^.*f2fs_writepages //p' > foo1
     mv foo1 $infile
     # At this stage, $infile should the following format :
     # entry_name <filename> bytes <bytes> ino <ino>
@@ -241,10 +241,10 @@
 # Latencies not supported for Writes. 'Write End' is just when the data has been
 # written back to page cache.
 prep_tracefile_wr() {
-    prep_tracefile_common android_fs_datawrite
-    fgrep android_fs_datawrite_start $infile > foo0
-    # Throw away everything upto and including android_fs_datawrite:
-    cat foo0 | sed -n -e 's/^.*android_fs_datawrite_start //p' > foo1
+    prep_tracefile_common f2fs_datawrite
+    fgrep f2fs_datawrite_start $infile > foo0
+    # Throw away everything upto and including f2fs_datawrite:
+    cat foo0 | sed -n -e 's/^.*f2fs_datawrite_start //p' > foo1
     mv foo1 $infile
     # At this stage, $infile should the following format :
     # entry_name <filename> offset <offset> bytes <bytes> cmdline <cmdline> pid <pid> i_size <i_size> ino <ino>
diff --git a/ioshark/collect-straces-ftraces.sh b/ioshark/collect-straces-ftraces.sh
index 6e83a24..3590f80 100644
--- a/ioshark/collect-straces-ftraces.sh
+++ b/ioshark/collect-straces-ftraces.sh
@@ -26,9 +26,9 @@
 prep_fstrace()
 {
     # Remove leading junk
-    fgrep android_fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+    fgrep f2fs_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
+    sed 's/f2fs_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
@@ -125,13 +125,13 @@
 
 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/events/f2fs/f2fs_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/events/f2fs/f2fs_dataread_start/enable"
     adb shell "echo 0 > /sys/kernel/debug/tracing/tracing_on"
 }
 
diff --git a/ioshark/compile-only.sh b/ioshark/compile-only.sh
index 7a6b3cd..ac70344 100644
--- a/ioshark/compile-only.sh
+++ b/ioshark/compile-only.sh
@@ -26,8 +26,8 @@
 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
+    fgrep f2fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+    sed 's/f2fs_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
diff --git a/libfec/fec_read.cpp b/libfec/fec_read.cpp
index 889f990..28a7c89 100644
--- a/libfec/fec_read.cpp
+++ b/libfec/fec_read.cpp
@@ -325,8 +325,12 @@
 
         /* copy raw data without error correction */
         if (!raw_pread(f->fd, data, FEC_BLOCKSIZE, curr_offset)) {
-            error("failed to read: %s", strerror(errno));
-            return -1;
+            if (errno == EIO) {
+                warn("I/O error encounter when reading, attempting to recover using fec");
+            } else {
+                error("failed to read: %s", strerror(errno));
+                return -1;
+            }
         }
 
         if (likely(f->hashtree().check_block_hash_with_index(curr, data))) {
diff --git a/libfscrypt/fscrypt.cpp b/libfscrypt/fscrypt.cpp
index f6e97f1..174ceca 100644
--- a/libfscrypt/fscrypt.cpp
+++ b/libfscrypt/fscrypt.cpp
@@ -61,6 +61,7 @@
         {"aes-256-cts"s, FSCRYPT_MODE_AES_256_CTS},
         {"aes-256-heh"s, FSCRYPT_MODE_AES_256_HEH},
         {"adiantum"s, FSCRYPT_MODE_ADIANTUM},
+        {"aes-256-hctr2"s, FSCRYPT_MODE_AES_256_HCTR2},
 };
 
 static bool LookupModeByName(const std::vector<struct ModeLookupEntry>& modes,
@@ -85,7 +86,8 @@
     return false;
 }
 
-bool fscrypt_is_native() {
+// Returns true if FBE (File Based Encryption) is enabled.
+bool IsFbeEnabled() {
     char value[PROPERTY_VALUE_MAX];
     property_get("ro.crypto.type", value, "none");
     return !strcmp(value, "file");
diff --git a/libfscrypt/include/fscrypt/fscrypt.h b/libfscrypt/include/fscrypt/fscrypt.h
index f3779d0..11f3711 100644
--- a/libfscrypt/include/fscrypt/fscrypt.h
+++ b/libfscrypt/include/fscrypt/fscrypt.h
@@ -19,7 +19,7 @@
 
 #include <string>
 
-bool fscrypt_is_native();
+bool IsFbeEnabled();
 
 static const char* fscrypt_unencrypted_folder = "/unencrypted";
 static const char* fscrypt_key_ref = "/unencrypted/ref";
diff --git a/libfscrypt/tests/fscrypt_test.cpp b/libfscrypt/tests/fscrypt_test.cpp
index 70eb178..0cd7995 100644
--- a/libfscrypt/tests/fscrypt_test.cpp
+++ b/libfscrypt/tests/fscrypt_test.cpp
@@ -176,6 +176,14 @@
     EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:v2:foo", &dummy_options));
     EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:blah", &dummy_options));
     EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:vblah", &dummy_options));
+
+    {
+        TEST_STRING(34, ":aes-256-hctr2", "aes-256-xts:aes-256-hctr2:v2");
+        EXPECT_EQ(2, options.version);
+        EXPECT_EQ(FSCRYPT_MODE_AES_256_XTS, options.contents_mode);
+        EXPECT_EQ(FSCRYPT_MODE_AES_256_HCTR2, options.filenames_mode);
+        EXPECT_EQ(FSCRYPT_POLICY_FLAGS_PAD_16, options.flags);
+    }
 }
 
 TEST(fscrypt, ComparePolicies) {
diff --git a/libjsonpb/parse/jsonpb.cpp b/libjsonpb/parse/jsonpb.cpp
index 3a042e7..e65a39d 100644
--- a/libjsonpb/parse/jsonpb.cpp
+++ b/libjsonpb/parse/jsonpb.cpp
@@ -48,7 +48,7 @@
                                    &json, options);
 
   if (!status.ok()) {
-    return MakeError<std::string>(status.error_message().as_string());
+    return MakeError<std::string>(status.message().as_string());
   }
   return ErrorOr<std::string>(std::move(json));
 }
@@ -61,7 +61,7 @@
   std::string binary;
   auto status = JsonToBinaryString(resolver.get(), GetTypeUrl(*message), content, &binary);
   if (!status.ok()) {
-    return MakeError<std::monostate>(status.error_message().as_string());
+    return MakeError<std::monostate>(status.message().as_string());
   }
   if (!message->ParseFromString(binary)) {
     return MakeError<std::monostate>("Fail to parse.");
diff --git a/memory_replay/Alloc.cpp b/memory_replay/Alloc.cpp
index af94ee5..a624710 100644
--- a/memory_replay/Alloc.cpp
+++ b/memory_replay/Alloc.cpp
@@ -14,72 +14,15 @@
  * limitations under the License.
  */
 
-#include <err.h>
-#include <inttypes.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <unistd.h>
 
-#include <string>
-
 #include "Alloc.h"
+#include "AllocParser.h"
 #include "Pointers.h"
 #include "Utils.h"
 
-void AllocGetData(const std::string& line, AllocEntry* entry) {
-  int line_pos = 0;
-  char name[128];
-  // All lines have this format:
-  //   TID: ALLOCATION_TYPE POINTER
-  // where
-  //   TID is the thread id of the thread doing the operation.
-  //   ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done
-  //   POINTER is the hex value of the actual pointer
-  if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry->tid, name, &entry->ptr, &line_pos) !=
-      3) {
-    errx(1, "File Error: Failed to process %s", line.c_str());
-  }
-  const char* line_end = &line[line_pos];
-  std::string type(name);
-  if (type == "malloc") {
-    // Format:
-    //   TID: malloc POINTER SIZE_OF_ALLOCATION
-    if (sscanf(line_end, "%zu", &entry->size) != 1) {
-      errx(1, "File Error: Failed to read malloc data %s", line.c_str());
-    }
-    entry->type = MALLOC;
-  } else if (type == "free") {
-    // Format:
-    //   TID: free POINTER
-    entry->type = FREE;
-  } else if (type == "calloc") {
-    // Format:
-    //   TID: calloc POINTER ITEM_COUNT ITEM_SIZE
-    if (sscanf(line_end, "%" SCNd64 " %zu", &entry->u.n_elements, &entry->size) != 2) {
-      errx(1, "File Error: Failed to read calloc data %s", line.c_str());
-    }
-    entry->type = CALLOC;
-  } else if (type == "realloc") {
-    // Format:
-    //   TID: calloc POINTER NEW_SIZE OLD_POINTER
-    if (sscanf(line_end, "%" SCNx64 " %zu", &entry->u.old_ptr, &entry->size) != 2) {
-      errx(1, "File Error: Failed to read realloc data %s", line.c_str());
-    }
-    entry->type = REALLOC;
-  } else if (type == "memalign") {
-    // Format:
-    //   TID: memalign POINTER ALIGNMENT SIZE
-    if (sscanf(line_end, "%" SCNd64 " %zu", &entry->u.align, &entry->size) != 2) {
-      errx(1, "File Error: Failed to read memalign data %s", line.c_str());
-    }
-    entry->type = MEMALIGN;
-  } else if (type == "thread_done") {
-    entry->type = THREAD_DONE;
-  } else {
-    errx(1, "File Error: Unknown type %s", type.c_str());
-  }
-}
-
 bool AllocDoesFree(const AllocEntry& entry) {
   switch (entry.type) {
     case MALLOC:
diff --git a/memory_replay/Alloc.h b/memory_replay/Alloc.h
index d590fcb..f4dcc83 100644
--- a/memory_replay/Alloc.h
+++ b/memory_replay/Alloc.h
@@ -16,34 +16,11 @@
 
 #pragma once
 
-#include <string>
+#include "AllocParser.h"
 
 // Forward Declarations.
 class Pointers;
 
-enum AllocEnum : uint8_t {
-  MALLOC = 0,
-  CALLOC,
-  MEMALIGN,
-  REALLOC,
-  FREE,
-  THREAD_DONE,
-};
-
-struct AllocEntry {
-  pid_t tid;
-  AllocEnum type;
-  uint64_t ptr = 0;
-  size_t size = 0;
-  union {
-    uint64_t old_ptr = 0;
-    uint64_t n_elements;
-    uint64_t align;
-  } u;
-};
-
-void AllocGetData(const std::string& line, AllocEntry* entry);
-
 bool AllocDoesFree(const AllocEntry& entry);
 
 uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers);
diff --git a/memory_replay/AllocParser.cpp b/memory_replay/AllocParser.cpp
new file mode 100644
index 0000000..ac6664a
--- /dev/null
+++ b/memory_replay/AllocParser.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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 <err.h>
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "AllocParser.h"
+
+#include <iostream>
+
+void AllocGetData(const std::string& line, AllocEntry* entry) {
+    int op_prefix_pos = 0;
+    char name[128];
+    // All lines have this format:
+    //   TID: ALLOCATION_TYPE POINTER
+    // where
+    //   TID is the thread id of the thread doing the operation.
+    //   ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done
+    //   POINTER is the hex value of the actual pointer
+    if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry->tid, name, &entry->ptr,
+               &op_prefix_pos) != 3) {
+        errx(1, "File Error: Failed to process %s", line.c_str());
+    }
+    std::string type(name);
+    if (type == "thread_done") {
+        entry->type = THREAD_DONE;
+    } else {
+        int args_offset = 0;
+        const char* args_beg = &line[op_prefix_pos];
+        if (type == "malloc") {
+            // Format:
+            //   TID: malloc POINTER SIZE_OF_ALLOCATION
+            if (sscanf(args_beg, "%zu%n", &entry->size, &args_offset) != 1) {
+                errx(1, "File Error: Failed to read malloc data %s", line.c_str());
+            }
+            entry->type = MALLOC;
+        } else if (type == "free") {
+            // Format:
+            //   TID: free POINTER
+            entry->type = FREE;
+        } else if (type == "calloc") {
+            // Format:
+            //   TID: calloc POINTER ITEM_COUNT ITEM_SIZE
+            if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry->u.n_elements, &entry->size,
+                       &args_offset) != 2) {
+                errx(1, "File Error: Failed to read calloc data %s", line.c_str());
+            }
+            entry->type = CALLOC;
+        } else if (type == "realloc") {
+            // Format:
+            //   TID: realloc POINTER OLD_POINTER NEW_SIZE
+            if (sscanf(args_beg, "%" SCNx64 " %zu%n", &entry->u.old_ptr, &entry->size,
+                       &args_offset) != 2) {
+                errx(1, "File Error: Failed to read realloc data %s", line.c_str());
+            }
+            entry->type = REALLOC;
+        } else if (type == "memalign") {
+            // Format:
+            //   TID: memalign POINTER ALIGNMENT SIZE
+            if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry->u.align, &entry->size,
+                       &args_offset) != 2) {
+                errx(1, "File Error: Failed to read memalign data %s", line.c_str());
+            }
+            entry->type = MEMALIGN;
+        } else {
+            errx(1, "File Error: Unknown type %s", type.c_str());
+        }
+
+        const char* timestamps_beg = &args_beg[args_offset];
+
+        // Timestamps come after the alloc args if present, for example,
+        //   TID: malloc POINTER SIZE_OF_ALLOCATION START_TIME END_TIME
+        int n_match = sscanf(timestamps_beg, "%" SCNd64 " %" SCNd64, &entry->st, &entry->et);
+        if (n_match != EOF && n_match != 2) {
+            errx(1, "File Error: Failed to read timestamps %s", line.c_str());
+        }
+    }
+}
diff --git a/memory_replay/AllocParser.h b/memory_replay/AllocParser.h
new file mode 100644
index 0000000..e58be48
--- /dev/null
+++ b/memory_replay/AllocParser.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <string>
+
+enum AllocEnum : uint8_t {
+    MALLOC = 0,
+    CALLOC,
+    MEMALIGN,
+    REALLOC,
+    FREE,
+    THREAD_DONE,
+};
+
+struct AllocEntry {
+    pid_t tid;
+    AllocEnum type;
+    uint64_t ptr = 0;
+    size_t size = 0;
+    union {
+        uint64_t old_ptr = 0;
+        uint64_t n_elements;
+        uint64_t align;
+    } u;
+    uint64_t st = 0;
+    uint64_t et = 0;
+};
+
+void AllocGetData(const std::string& line, AllocEntry* entry);
diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp
index 8fb9dbc..c7de1fe 100644
--- a/memory_replay/Android.bp
+++ b/memory_replay/Android.bp
@@ -44,6 +44,17 @@
     compile_multilib: "both",
 }
 
+cc_library_static {
+    name: "liballoc_parser",
+    host_supported: true,
+    defaults: ["memory_flag_defaults"],
+
+    export_include_dirs: ["."],
+    srcs: [
+        "AllocParser.cpp",
+    ],
+}
+
 cc_defaults {
     name: "memory_replay_defaults",
     defaults: ["memory_flag_defaults"],
@@ -63,7 +74,7 @@
     ],
 
     static_libs: [
-        "libasync_safe",
+        "liballoc_parser",
     ],
 }
 
@@ -126,6 +137,10 @@
         "libziparchive",
     ],
 
+    static_libs: [
+        "liballoc_parser",
+    ],
+
     data: [
         "traces/*.zip",
     ],
diff --git a/memory_replay/File.cpp b/memory_replay/File.cpp
index 8bcd904..e44c500 100644
--- a/memory_replay/File.cpp
+++ b/memory_replay/File.cpp
@@ -29,6 +29,7 @@
 #include <ziparchive/zip_archive.h>
 
 #include "Alloc.h"
+#include "AllocParser.h"
 #include "File.h"
 
 std::string ZipGetContents(const char* filename) {
diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
index 3439a29..8493b68 100644
--- a/memory_replay/NativeInfo.cpp
+++ b/memory_replay/NativeInfo.cpp
@@ -27,23 +27,12 @@
 #include <unistd.h>
 
 #include <android-base/unique_fd.h>
-#include <async_safe/log.h>
 
 #include "NativeInfo.h"
 
-void NativePrintf(const char* fmt, ...) {
-  va_list args;
-  va_start(args, fmt);
-  char buffer[512];
-  int buffer_len = async_safe_format_buffer_va_list(buffer, sizeof(buffer), fmt, args);
-  va_end(args);
-
-  (void)write(STDOUT_FILENO, buffer, buffer_len);
-}
-
 void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor) {
   uint64_t hundreds = ((((value % divisor) * 1000) / divisor) + 5) / 10;
-  async_safe_format_buffer(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds);
+  snprintf(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds);
 }
 
 // This function is not re-entrant since it uses a static buffer for
@@ -114,7 +103,7 @@
   // Avoid any allocations, so use special non-allocating printfs.
   char buffer[256];
   NativeFormatFloat(buffer, sizeof(buffer), rss_bytes, 1024 * 1024);
-  NativePrintf("%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer);
+  dprintf(STDOUT_FILENO, "%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer);
   NativeFormatFloat(buffer, sizeof(buffer), va_bytes, 1024 * 1024);
-  NativePrintf("%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer);
+  dprintf(STDOUT_FILENO, "%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer);
 }
diff --git a/memory_replay/NativeInfo.h b/memory_replay/NativeInfo.h
index c91eec2..a33db02 100644
--- a/memory_replay/NativeInfo.h
+++ b/memory_replay/NativeInfo.h
@@ -20,8 +20,5 @@
 
 void NativePrintInfo(const char* preamble);
 
-// Does not support any floating point specifiers.
-void NativePrintf(const char* fmt, ...) __printflike(1, 2);
-
 // Fill buffer as if %0.2f was chosen for value / divisor.
 void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor);
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
index e610305..2dd66bb 100644
--- a/memory_replay/main.cpp
+++ b/memory_replay/main.cpp
@@ -78,15 +78,15 @@
   Pointers pointers(max_allocs);
   Threads threads(&pointers, max_threads);
 
-  NativePrintf("Maximum threads available:   %zu\n", threads.max_threads());
-  NativePrintf("Maximum allocations in dump: %zu\n", max_allocs);
-  NativePrintf("Total pointers available:    %zu\n\n", pointers.max_pointers());
+  dprintf(STDOUT_FILENO, "Maximum threads available:   %zu\n", threads.max_threads());
+  dprintf(STDOUT_FILENO, "Maximum allocations in dump: %zu\n", max_allocs);
+  dprintf(STDOUT_FILENO, "Total pointers available:    %zu\n\n", pointers.max_pointers());
 
   NativePrintInfo("Initial ");
 
   for (size_t i = 0; i < num_entries; i++) {
     if (((i + 1) % 100000) == 0) {
-      NativePrintf("  At line %zu:\n", i + 1);
+      dprintf(STDOUT_FILENO, "  At line %zu:\n", i + 1);
       NativePrintInfo("    ");
     }
     const AllocEntry& entry = entries[i];
@@ -139,7 +139,7 @@
   char buffer[256];
   uint64_t total_nsecs = threads.total_time_nsecs();
   NativeFormatFloat(buffer, sizeof(buffer), total_nsecs, 1000000000);
-  NativePrintf("Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
+  dprintf(STDOUT_FILENO, "Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
 }
 
 int main(int argc, char** argv) {
@@ -161,13 +161,13 @@
   }
 
 #if defined(__LP64__)
-  NativePrintf("64 bit environment.\n");
+  dprintf(STDOUT_FILENO, "64 bit environment.\n");
 #else
-  NativePrintf("32 bit environment.\n");
+  dprintf(STDOUT_FILENO, "32 bit environment.\n");
 #endif
 
 #if defined(__BIONIC__)
-  NativePrintf("Setting decay time to 1\n");
+  dprintf(STDOUT_FILENO, "Setting decay time to 1\n");
   mallopt(M_DECAY_TIME, 1);
 #endif
 
@@ -180,7 +180,7 @@
   size_t num_entries;
   GetUnwindInfo(argv[1], &entries, &num_entries);
 
-  NativePrintf("Processing: %s\n", argv[1]);
+  dprintf(STDOUT_FILENO, "Processing: %s\n", argv[1]);
 
   ProcessDump(entries, num_entries, max_threads);
 
diff --git a/memtrack/Android.bp b/memtrack/Android.bp
index d04c6ff..2611029 100644
--- a/memtrack/Android.bp
+++ b/memtrack/Android.bp
@@ -53,17 +53,8 @@
 
     static_executable: true,
     static_libs: [
-        "libc",
         "liblog",
-        "libc++abi",
-        "libdl",
     ],
 
     stl: "libc++_static",
-
-    // Bug: 18389563 - Today, libc++_static and libgcc have duplicate sybols for
-    // __aeabi_uidiv(). Allowing multiple definitions lets the build proceed, but
-    // updating compiler-rt to be a superset of libgcc will allow this WAR to be
-    // removed.
-    ldflags: ["-Wl,-z,muldefs"],
 }
diff --git a/module_ndk_libs/libnativehelper/Android.bp b/module_ndk_libs/libnativehelper/Android.bp
index 4304373..e5c6326 100644
--- a/module_ndk_libs/libnativehelper/Android.bp
+++ b/module_ndk_libs/libnativehelper/Android.bp
@@ -55,4 +55,8 @@
     name: "libnativehelper",
     symbol_file: "libnativehelper.map.txt",
     first_version: "S",
+    export_header_libs: [
+        "libnativehelper_ndk_headers",
+        "ndk_jni.h",
+    ],
 }
diff --git a/mtectrl/.clang-format b/mtectrl/.clang-format
new file mode 120000
index 0000000..242a033
--- /dev/null
+++ b/mtectrl/.clang-format
@@ -0,0 +1 @@
+../../core/.clang-format-2
\ No newline at end of file
diff --git a/mtectrl/Android.bp b/mtectrl/Android.bp
index bc84f8c..423eec2 100644
--- a/mtectrl/Android.bp
+++ b/mtectrl/Android.bp
@@ -25,3 +25,26 @@
   ],
   init_rc: ["mtectrl.rc"],
 }
+
+cc_test {
+  name: "mtectrl_test",
+  srcs: ["mtectrl_test.cc"],
+  test_suites: ["general-tests"],
+  // shell cannot use /system/bin/mtectrl
+  require_root: true,
+  shared_libs: [
+    "libbootloader_message",
+    "libbase",
+  ],
+  static_libs: [
+    "libgmock",
+  ]
+}
+
+java_test_host {
+    name: "mtectrl_end_to_end_test",
+    libs: ["tradefed"],
+    static_libs: ["frameworks-base-hostutils", "cts-install-lib-host"],
+    srcs:  ["src/com/android/tests/mtectrl/MtectrlEndToEndTest.java"],
+    test_suites: ["general-tests"],
+}
diff --git a/mtectrl/TEST_MAPPING b/mtectrl/TEST_MAPPING
new file mode 100644
index 0000000..23c2e9d
--- /dev/null
+++ b/mtectrl/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "mtectrl_test"
+    }
+  ],
+  "hwasan-presubmit": [
+    {
+      "name": "mtectrl_test"
+    }
+  ]
+}
diff --git a/mtectrl/mtectrl.cc b/mtectrl/mtectrl.cc
index 0738f9e..bea12a6 100644
--- a/mtectrl/mtectrl.cc
+++ b/mtectrl/mtectrl.cc
@@ -14,43 +14,237 @@
  * limitations under the License.
  */
 
+#include <getopt.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <bootloader_message/bootloader_message.h>
 
+#include <functional>
 #include <iostream>
 
-int main(int argc, char** argv) {
-  if (argc != 2) {
-    std::cerr
-        << "Usage: " << argv[0]
-        << " [none|memtag|memtag_once|memtag_kernel|memtag_kernel_once]\n";
-    return 1;
+void AddItem(std::string* s, const char* item) {
+  if (!s->empty()) *s += ",";
+  *s += item;
+}
+
+bool CheckAndUnset(uint32_t& mode, uint32_t mask) {
+  bool is_set = mode & mask;
+  mode &= ~mask;
+  return is_set;
+}
+
+bool UpdateProp(const char* prop_name, const misc_memtag_message& m) {
+  uint32_t mode = m.memtag_mode;
+  std::string prop_str;
+  if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG)) AddItem(&prop_str, "memtag");
+  if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_ONCE)) AddItem(&prop_str, "memtag-once");
+  if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_KERNEL)) AddItem(&prop_str, "memtag-kernel");
+  if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE))
+    AddItem(&prop_str, "memtag-kernel-once");
+  if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_OFF)) AddItem(&prop_str, "memtag-off");
+  if (android::base::GetProperty(prop_name, "") != prop_str)
+    android::base::SetProperty(prop_name, prop_str);
+  if (mode) {
+    LOG(ERROR) << "MTE mode in misc message contained unknown bits: " << mode
+               << ". Ignoring and setting " << prop_name << " to " << prop_str;
   }
-  std::string value = argv[1];
-  misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
-                           .magic = MISC_MEMTAG_MAGIC_HEADER};
+  return mode == 0;
+}
+
+void PrintUsage(const char* progname) {
+  std::cerr
+      << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+         "!!! YOU PROBABLY DO NOT NEED TO USE THIS                    !!!\n"
+         "!!! USE THE `arm64.memtag.bootctl` SYSTEM PROPERTY INSTEAD. !!!\n"
+         "!!! This program is an implementation detail that is used   !!!\n"
+         "!!! by the system to apply MTE settings.                    !!!\n"
+         "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+         "\n"
+      << "USAGE: " << progname
+      << "\n"
+         "      [-s PROPERTY_NAME]\n"
+         "      [none,][memtag,][memtag-once,][memtag-kernel,][memtag-kernel-once,][memtag-off,]\n"
+         "      [default|force_on|force_off]\n"
+         "      [-t PATH_TO_FAKE_MISC_PARTITION]\n"
+
+         "OPTIONS:\n"
+         "  -s PROPERTY_NAME\n"
+         "      Sets the system property 'PROPERTY_NAME' to the new MTE mode (if provided), or to\n"
+         "      the current value from the /misc partition.\n"
+         "  [none,][memtag,][memtag-once,][memtag-kernel,][memtag-kernel-once,][memtag-off,]\n"
+         "      A set of MTE options to be applied, if provided. Multiple options may be\n"
+         "      specified as a ','-delimited list, e.g. 'memtag,memtag-kernel'.\n"
+         "      The options are described below:\n"
+         "        - none: default settings for MTE for the product will be applied on next\n"
+         "                reboot.\n"
+         "        - memtag: MTE is persistently enabled in userspace upon the next reboot.\n"
+         "        - memtag-once: MTE is enabled in userspace, only for the next reboot.\n"
+         "        - memtag-kernel: MTE is persistently enabled in the kernel upon the next \n"
+         "                         reboot.\n"
+         "        - memtag-kernel-once: MTE is enabled in the kernel, only for the next reboot.\n"
+         "        - memtag-off: MTE is persistently disabled in both userspace and kernel upon \n"
+         "                      the next reboot.\n"
+         "  [default|force_on|force_off]\n"
+         "      An alternative method of configuring the MTE options to be applied, if provided.\n"
+         "      This control is generally to be used by device_config only, and it overwrites\n"
+         "      the previously described settings that are expected to be utilized by the user.\n"
+         "      The options are described below:\n"
+         "        - default: This flag is not overwriting the MTE mode, and so the setting\n"
+         "                   should be inherited from the userspace controls (if present), or the\n"
+         "                   default value from the bootloader's ROM.\n"
+         "        - force_on: MTE is persistently enabled in userspace, overwriting the userspace\n"
+         "                    setting.\n"
+         "        - force_off: MTE is persistently disabled in userspace and the kernel, \n"
+         "                     overwriting the userspace setting.\n";
+}
+
+int StringToMode(const char* value) {
+  int memtag_mode = 0;
   for (const auto& field : android::base::Split(value, ",")) {
     if (field == "memtag") {
-      m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG;
+      memtag_mode |= MISC_MEMTAG_MODE_MEMTAG;
     } else if (field == "memtag-once") {
-      m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_ONCE;
+      memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_ONCE;
     } else if (field == "memtag-kernel") {
-      m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL;
+      memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL;
     } else if (field == "memtag-kernel-once") {
-      m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE;
+      memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE;
+    } else if (field == "memtag-off") {
+      memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_OFF;
     } else if (field != "none") {
-      LOG(ERROR) << "Unknown value for arm64.memtag.bootctl: " << field;
-      return 1;
+      LOG(ERROR) << "Unknown value for mode: " << field;
+      return -1;
     }
   }
+  return memtag_mode;
+}
+
+bool HandleOverride(const std::string& override_value, misc_memtag_message* m) {
+  if (override_value == "force_off") {
+    // If the force_off override is active, only allow MEMTAG_MODE_MEMTAG_ONCE.
+    m->memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_OFF;
+    m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG;
+  } else if (override_value == "force_on") {
+    m->memtag_mode |= MISC_MEMTAG_MODE_MEMTAG;
+    m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG_OFF;
+  } else if (!override_value.empty() && override_value != "default") {
+    return false;
+  }
+  return true;
+}
+
+int main(int argc, char** argv) {
+  const char* set_prop = nullptr;
+  int opt;
+  std::function<bool(misc_memtag_message*, std::string*)> read_memtag_message =
+      ReadMiscMemtagMessage;
+  std::function<bool(const misc_memtag_message&, std::string*)> write_memtag_message =
+      WriteMiscMemtagMessage;
+
+  android::base::unique_fd fake_partition_fd;
+  while ((opt = getopt(argc, argv, "s:t:")) != -1) {
+    switch (opt) {
+      case 's':
+        // Set property in argument to state of misc partition. If given by
+        // itself, sets the property to the current state. We do this on device
+        // boot,
+        //
+        // Otherwise, applies new state and then sets property to newly applied
+        // state.
+        set_prop = optarg;
+        break;
+      case 't': {
+        // Use different fake misc partition for testing.
+        const char* filename = optarg;
+        fake_partition_fd.reset(open(filename, O_RDWR | O_CLOEXEC));
+        int raw_fd = fake_partition_fd.get();
+        CHECK_NE(raw_fd, -1);
+        CHECK_NE(ftruncate(raw_fd, sizeof(misc_memtag_message)), -1);
+        read_memtag_message = [raw_fd](misc_memtag_message* m, std::string*) {
+          CHECK(android::base::ReadFully(raw_fd, m, sizeof(*m)));
+          return true;
+        };
+        write_memtag_message = [raw_fd](const misc_memtag_message& m, std::string*) {
+          CHECK(android::base::WriteFully(raw_fd, &m, sizeof(m)));
+          return true;
+        };
+        break;
+      }
+      default:
+        PrintUsage(argv[0]);
+        return 1;
+    }
+  }
+
+  const char* value = optind < argc ? argv[optind++] : nullptr;
+  const char* override_value = optind < argc ? argv[optind++] : nullptr;
+
+  if (optind != argc) {  // Unknown argument.
+    PrintUsage(argv[0]);
+    return 1;
+  }
+
+  if (!value && set_prop) {
+    // -s <property> is given on its own. This means we want to read the state
+    // of the misc partition into the property.
+    std::string err;
+    misc_memtag_message m = {};
+    if (!read_memtag_message(&m, &err)) {
+      LOG(ERROR) << "Failed to read memtag message: " << err;
+      return 1;
+    }
+    if (m.magic != MISC_MEMTAG_MAGIC_HEADER || m.version != MISC_MEMTAG_MESSAGE_VERSION) {
+      // This should not fail by construction.
+      CHECK(UpdateProp(set_prop, {}));
+      // This is an expected case, as the partition gets initialized to all zero.
+      return 0;
+    }
+    // Unlike above, setting the system property here can fail if the misc partition
+    // was corrupted by another program (e.g. the bootloader).
+    return UpdateProp(set_prop, m) ? 0 : 1;
+  }
+
+  if (!value) {
+    PrintUsage(argv[0]);
+    return 1;
+  }
+
+  misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
+                           .magic = MISC_MEMTAG_MAGIC_HEADER};
+  int memtag_mode = StringToMode(value);
+  bool valid_value = memtag_mode != -1;
+  m.memtag_mode = valid_value ? memtag_mode : 0;
+
+  bool valid_override = true;
+  if (override_value) valid_override = HandleOverride(override_value, &m);
+
+  if (!valid_value && !valid_override) {
+    return 1;
+  }
   std::string err;
-  if (!WriteMiscMemtagMessage(m, &err)) {
-    LOG(ERROR) << "Failed to apply arm64.memtag.bootctl: " << value << ". "
-               << err;
+  if (!write_memtag_message(m, &err)) {
+    LOG(ERROR) << "Failed to apply mode: " << value << ", override: " << override_value << err;
     return 1;
   } else {
-    LOG(INFO) << "Applied arm64.memtag.bootctl: " << value;
-    return 0;
+    const char* parse_error = "";
+    const char* verb = "Applied";
+    if (!valid_value) {
+      parse_error = " (invalid mode)";
+      verb = "Partially applied";
+    } else if (!valid_override) {
+      // else if because we bail out if both are false above.
+      parse_error = " (invalid override)";
+      verb = "Partially applied";
+    }
+    LOG(INFO) << verb << " mode: " << value << ", "
+              << "override: " << (override_value ? override_value : "") << parse_error;
+    // Because all the bits in memtag_mode were set above, this should never fail.
+    if (set_prop) CHECK(UpdateProp(set_prop, m));
+    return !valid_value || !valid_override;
   }
 }
diff --git a/mtectrl/mtectrl.rc b/mtectrl/mtectrl.rc
index de04237..a474cdc 100644
--- a/mtectrl/mtectrl.rc
+++ b/mtectrl/mtectrl.rc
@@ -13,4 +13,15 @@
 # limitations under the License.
 
 on property:arm64.memtag.bootctl=*
-  exec -- /system/bin/mtectrl ${arm64.memtag.bootctl}
+  exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
+
+on property:persist.device_config.runtime_native_boot.bootloader_override=*
+  exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
+
+# adbd gets initialized in init, so run before that. this makes sure that the
+# user does not change the value before we initialize it
+on early-init && property:ro.arm64.memtag.bootctl_supported=1
+  exec -- /system/bin/mtectrl -s arm64.memtag.bootctl
+
+on shutdown && property:ro.arm64.memtag.bootctl_supported=1
+  exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
diff --git a/mtectrl/mtectrl_test.cc b/mtectrl/mtectrl_test.cc
new file mode 100644
index 0000000..5fe77f8
--- /dev/null
+++ b/mtectrl/mtectrl_test.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <bootloader_message/bootloader_message.h>
+
+namespace {
+using ::testing::StartsWith;
+
+int mtectrl(const char* arg) {
+  std::string cmd = "mtectrl -t /data/local/tmp/misc_memtag ";
+  cmd += arg;
+  return system(cmd.c_str());
+}
+
+std::string GetMisc() {
+  std::string data;
+  CHECK(android::base::ReadFileToString("/data/local/tmp/misc_memtag", &data, false));
+  return data;
+}
+
+std::string TestProperty() {
+  return android::base::GetProperty("arm64.memtag.test_bootctl", "");
+}
+}  // namespace
+
+class MteCtrlTest : public ::testing::Test {
+  void SetUp() override {
+    // Empty fake misc partition.
+    int fd = creat("/data/local/tmp/misc_memtag", 0600);
+    CHECK(fd != -1);
+    CHECK(ftruncate(fd, sizeof(misc_memtag_message)) != -1);
+    close(fd);
+    android::base::SetProperty("arm64.memtag.test_bootctl", "INVALID");
+  }
+  void TearDown() override {
+    CHECK(unlink("/data/local/tmp/misc_memtag") == 0);
+  }
+};
+
+TEST_F(MteCtrlTest, invalid) {
+  EXPECT_NE(mtectrl("memtag-invalid"), 0);
+  EXPECT_NE(mtectrl("memtag override-invalid"), 0);
+}
+
+TEST_F(MteCtrlTest, set_once) {
+  ASSERT_EQ(mtectrl("memtag-once"), 0);
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x02"));
+}
+
+TEST_F(MteCtrlTest, set_once_kernel) {
+  ASSERT_EQ(mtectrl("memtag-once,memtag-kernel"), 0);
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x06"));
+}
+
+TEST_F(MteCtrlTest, set_memtag) {
+  ASSERT_EQ(mtectrl("memtag"), 0);
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+}
+
+TEST_F(MteCtrlTest, set_memtag_force_off) {
+  ASSERT_EQ(mtectrl("memtag force_off"), 0);
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x10"));
+}
+
+TEST_F(MteCtrlTest, read_memtag) {
+  ASSERT_EQ(mtectrl("memtag"), 0);
+  ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+  EXPECT_EQ(TestProperty(), "memtag");
+}
+
+TEST_F(MteCtrlTest, read_invalid_memtag_message) {
+  misc_memtag_message m = {.version = 1, .magic = 0xffff, .memtag_mode = MISC_MEMTAG_MODE_MEMTAG};
+  std::string m_str(reinterpret_cast<char*>(&m), sizeof(m));
+  android::base::WriteStringToFile(m_str, "/data/local/tmp/misc_memtag");
+  ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+  EXPECT_EQ(TestProperty(), "");
+}
+
+TEST_F(MteCtrlTest, read_invalid_memtag_mode) {
+  misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
+                           .magic = MISC_MEMTAG_MAGIC_HEADER,
+                           .memtag_mode = MISC_MEMTAG_MODE_MEMTAG | 1u << 31};
+  std::string m_str(reinterpret_cast<char*>(&m), sizeof(m));
+  android::base::WriteStringToFile(m_str, "/data/local/tmp/misc_memtag");
+  ASSERT_NE(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+  EXPECT_EQ(TestProperty(), "memtag");
+}
+
+TEST_F(MteCtrlTest, set_read_memtag) {
+  ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl memtag"), 0);
+  EXPECT_EQ(TestProperty(), "memtag");
+}
+
+TEST_F(MteCtrlTest, set_read_force_off) {
+  ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl memtag,memtag-once force_off"), 0);
+  EXPECT_EQ(TestProperty(), "memtag-once,memtag-off");
+}
+
+TEST_F(MteCtrlTest, override) {
+  ASSERT_EQ(mtectrl("memtag"), 0);
+  ASSERT_EQ(mtectrl("memtag-once"), 0);
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x02"));
+}
+
+TEST_F(MteCtrlTest, read_empty) {
+  ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+  EXPECT_EQ(TestProperty(), "");
+}
+
+TEST_F(MteCtrlTest, force_off_invalid_mode) {
+  mtectrl("-s arm64.memtag.test_bootctl memtag-invalid force_off");
+  EXPECT_EQ(TestProperty(), "memtag-off");
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x10"));
+}
+
+TEST_F(MteCtrlTest, force_on_invalid_mode) {
+  mtectrl("-s arm64.memtag.test_bootctl memtag-invalid force_on");
+  EXPECT_EQ(TestProperty(), "memtag");
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+}
+
+TEST_F(MteCtrlTest, mode_invalid_override) {
+  mtectrl("-s arm64.memtag.test_bootctl memtag force_invalid");
+  EXPECT_EQ(TestProperty(), "memtag");
+  EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+}
diff --git a/mtectrl/src/com/android/tests/mtectrl/MtectrlEndToEndTest.java b/mtectrl/src/com/android/tests/mtectrl/MtectrlEndToEndTest.java
new file mode 100644
index 0000000..fd3d440
--- /dev/null
+++ b/mtectrl/src/com/android/tests/mtectrl/MtectrlEndToEndTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.tests.mtectrl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assume.assumeThat;
+
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// Test the protocol described in
+// https://source.android.com/docs/security/test/memory-safety/bootloader-support.
+// This will reboot the device multiple times, which is perfectly normal.
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MtectrlEndToEndTest extends BaseHostJUnit4Test {
+    private static String mPreviousState = null;
+
+    @BeforeClassWithInfo
+    public static void setUp(TestInformation testInfo) throws Exception {
+        assumeThat(
+                testInfo.getDevice().getProperty("ro.arm64.memtag.bootctl_supported"),
+                equalTo("1"));
+        mPreviousState = testInfo.getDevice().getProperty("arm64.memtag.bootctl");
+        if (mPreviousState == null) {
+            mPreviousState = "";
+        }
+    }
+
+    @AfterClassWithInfo
+    public static void tearDown(TestInformation testInfo) throws Exception {
+        if (mPreviousState != null) {
+            testInfo.getDevice().setProperty("arm64.memtag.bootctl", mPreviousState);
+            testInfo.getDevice().reboot();
+        }
+    }
+
+    @Test
+    public void testMemtagOnce() throws Exception {
+        getDevice().setProperty("arm64.memtag.bootctl", "memtag-once");
+        getDevice().reboot();
+        assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isAnyOf("", "none", null);
+        assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+        getDevice().reboot();
+        assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isAnyOf("", "none", null);
+        assertThat(getDevice().pullFileContents("/proc/cpuinfo")).doesNotContain("mte");
+    }
+
+    @Test
+    public void testMemtag() throws Exception {
+        getDevice().setProperty("arm64.memtag.bootctl", "memtag");
+        getDevice().reboot();
+        assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+        assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+        getDevice().reboot();
+        assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+        assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+    }
+
+    @Test
+    public void testBoth() throws Exception {
+        getDevice().setProperty("arm64.memtag.bootctl", "memtag,memtag-once");
+        getDevice().reboot();
+        assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+        assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+        getDevice().reboot();
+        assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+        assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+    }
+}
diff --git a/partition_tools/dynamic_partitions_device_info.proto b/partition_tools/dynamic_partitions_device_info.proto
index e53b40e..8800dac 100644
--- a/partition_tools/dynamic_partitions_device_info.proto
+++ b/partition_tools/dynamic_partitions_device_info.proto
@@ -25,7 +25,7 @@
     bool enabled = 1;
     bool retrofit = 2;
 
-    // Next: 7
+    // Next: 8
     message Partition {
         string name = 1;
         string group_name = 2 [json_name = "group_name"];
@@ -36,6 +36,8 @@
         uint64 fs_size = 5 [json_name = "fs_size"];
         /** Used space of the filesystem. */
         uint64 fs_used = 6 [json_name = "fs_used"];
+        /**  Name of the filesystem. */
+        string fs_type = 7 [json_name = "fs_type"];
     }
     repeated Partition partitions = 3;
 
diff --git a/partition_tools/lpadd.cc b/partition_tools/lpadd.cc
index 7374e77..b2b1c74 100644
--- a/partition_tools/lpadd.cc
+++ b/partition_tools/lpadd.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <getopt.h>
+#include <string.h>
 #include <sysexits.h>
 #include <unistd.h>
 
diff --git a/partition_tools/lpdump.cc b/partition_tools/lpdump.cc
index 047b5ee..9768294 100644
--- a/partition_tools/lpdump.cc
+++ b/partition_tools/lpdump.cc
@@ -229,6 +229,13 @@
             partition_proto->set_is_dynamic(false);
         }
         partition_proto->set_fs_size((uint64_t)vst.f_blocks * vst.f_frsize);
+
+        if (!entry.fs_type.empty()) {
+            partition_proto->set_fs_type(entry.fs_type);
+        } else {
+            partition_proto->set_fs_type("UNKNOWN");
+        }
+
         if (vst.f_bavail <= vst.f_blocks) {
             partition_proto->set_fs_used((uint64_t)(vst.f_blocks - vst.f_bavail) * vst.f_frsize);
         }
diff --git a/partition_tools/lpmake.cc b/partition_tools/lpmake.cc
index e6c4e53..16dfec5 100644
--- a/partition_tools/lpmake.cc
+++ b/partition_tools/lpmake.cc
@@ -17,9 +17,11 @@
 #include <getopt.h>
 #include <inttypes.h>
 #include <stdio.h>
+#include <stdlib.h>
 #ifndef WIN32
 #include <sysexits.h>
 #endif
+#include <unistd.h>
 
 #include <algorithm>
 #include <memory>
@@ -27,6 +29,7 @@
 #include <android-base/parseint.h>
 #include <android-base/result.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <liblp/builder.h>
 #include <liblp/liblp.h>
 
@@ -35,12 +38,15 @@
 
 using android::base::Error;
 using android::base::Result;
+using android::base::unique_fd;
 
 #ifdef WIN32
 static constexpr int EX_OK = 0;
 static constexpr int EX_USAGE = 1;
 static constexpr int EX_SOFTWARE = 2;
 static constexpr int EX_CANTCREAT = 3;
+#else
+static constexpr int O_BINARY = 0;
 #endif
 
 /* Prints program usage to |where|. */
@@ -165,16 +171,47 @@
 };
 
 static uint64_t CalculateBlockDeviceSize(uint32_t alignment, uint32_t metadata_size,
+                                         uint32_t metadata_slots,
                                          const std::vector<PartitionInfo>& partitions) {
-    uint64_t ret = std::max(alignment, LP_PARTITION_RESERVED_BYTES +
-                                               (LP_METADATA_GEOMETRY_SIZE + metadata_size) * 2) +
-                   partitions.size() * alignment;
+    uint64_t ret = LP_PARTITION_RESERVED_BYTES;
+    ret += LP_METADATA_GEOMETRY_SIZE * 2;
+
+    // Each metadata slot has a primary and backup copy.
+    ret += metadata_slots * metadata_size * 2;
+
+    if (alignment) {
+        uint64_t remainder = ret % alignment;
+        uint64_t to_add = alignment - remainder;
+        if (to_add > std::numeric_limits<uint64_t>::max() - ret) {
+            return 0;
+        }
+        ret += to_add;
+    }
+
+    ret += partitions.size() * alignment;
     for (const auto& partition_info : partitions) {
         ret += partition_info.size;
     }
     return ret;
 }
 
+static bool GetFileSize(const std::string& path, uint64_t* size) {
+    unique_fd fd(open(path.c_str(), O_RDONLY | O_BINARY));
+    if (fd < 0) {
+        fprintf(stderr, "Could not open file: %s: %s\n", path.c_str(), strerror(errno));
+        return false;
+    }
+
+    auto offs = lseek(fd.get(), 0, SEEK_END);
+    if (offs < 0) {
+        fprintf(stderr, "Failed to seek file: %s: %s\n", path.c_str(), strerror(errno));
+        return false;
+    }
+
+    *size = offs;
+    return true;
+}
+
 int main(int argc, char* argv[]) {
     struct option options[] = {
         { "device-size", required_argument, nullptr, (int)Option::kDeviceSize },
@@ -182,7 +219,7 @@
         { "metadata-slots", required_argument, nullptr, (int)Option::kMetadataSlots },
         { "partition", required_argument, nullptr, (int)Option::kPartition },
         { "output", required_argument, nullptr, (int)Option::kOutput },
-        { "help", no_argument, nullptr, (int)Option::kOutput },
+        { "help", no_argument, nullptr, (int)Option::kHelp },
         { "alignment-offset", required_argument, nullptr, (int)Option::kAlignmentOffset },
         { "alignment", required_argument, nullptr, (int)Option::kAlignment },
         { "sparse", no_argument, nullptr, (int)Option::kSparse },
@@ -347,7 +384,12 @@
     }
 
     if (auto_blockdevice_size) {
-        blockdevice_size = CalculateBlockDeviceSize(alignment, metadata_size, partitions);
+        blockdevice_size =
+                CalculateBlockDeviceSize(alignment, metadata_size, metadata_slots, partitions);
+        if (!blockdevice_size) {
+            fprintf(stderr, "Invalid block device parameters.\n");
+            return EX_USAGE;
+        }
     }
 
     // Must specify a block device via the old method (--device-size etc) or
@@ -425,13 +467,21 @@
         }
     }
 
-    for (const auto& partition_info : partitions) {
+    for (auto& partition_info : partitions) {
         Partition* partition = builder->AddPartition(partition_info.name, partition_info.group_name,
                                                      partition_info.attribute_flags);
         if (!partition) {
             fprintf(stderr, "Could not add partition: %s\n", partition_info.name.c_str());
             return EX_SOFTWARE;
         }
+        if (!partition_info.size) {
+            // Deduce the size automatically.
+            if (auto iter = images.find(partition_info.name); iter != images.end()) {
+                if (!GetFileSize(iter->second, &partition_info.size)) {
+                    return EX_SOFTWARE;
+                }
+            }
+        }
         if (!builder->ResizePartition(partition, partition_info.size)) {
             fprintf(stderr, "Not enough space on device for partition %s with size %" PRIu64 "\n",
                     partition_info.name.c_str(), partition_info.size);
diff --git a/partition_tools/lpunpack.cc b/partition_tools/lpunpack.cc
index 1f870c5..b215c58 100644
--- a/partition_tools/lpunpack.cc
+++ b/partition_tools/lpunpack.cc
@@ -34,11 +34,12 @@
 
 using namespace android::fs_mgr;
 using android::base::unique_fd;
+using android::base::borrowed_fd;
 using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
 
 class ImageExtractor final {
   public:
-    ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata,
+    ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
                    std::unordered_set<std::string>&& partitions, const std::string& output_dir);
 
     bool Extract();
@@ -48,7 +49,7 @@
     bool ExtractPartition(const LpMetadataPartition* partition);
     bool ExtractExtent(const LpMetadataExtent& extent, int output_fd);
 
-    unique_fd image_fd_;
+    std::vector<unique_fd> image_fds_;
     std::unique_ptr<LpMetadata> metadata_;
     std::unordered_set<std::string> partitions_;
     std::string output_dir_;
@@ -59,16 +60,15 @@
 // file format.
 class SparseWriter final {
   public:
-    SparseWriter(int output_fd, int image_fd, uint32_t block_size);
+    SparseWriter(borrowed_fd output_fd, uint32_t block_size);
 
-    bool WriteExtent(const LpMetadataExtent& extent);
+    bool WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent);
     bool Finish();
 
   private:
     bool WriteBlock(const uint8_t* data);
 
-    int output_fd_;
-    int image_fd_;
+    borrowed_fd output_fd_;
     uint32_t block_size_;
     off_t hole_size_ = 0;
 };
@@ -81,7 +81,14 @@
             "Usage:\n"
             "  %s [options...] SUPER_IMAGE [OUTPUT_DIR]\n"
             "\n"
+            "The SUPER_IMAGE argument is mandatory and expected to contain\n"
+            "the metadata. Additional super images are referenced with '-i' as needed to extract\n"
+            "the desired partition[s].\n"
+            "Default OUTPUT_DIR is '.'.\n"
+            "\n"
             "Options:\n"
+            "  -i, --image=IMAGE        Use the given file as an additional super image.\n"
+            "                           This can be specified multiple times.\n"
             "  -p, --partition=NAME     Extract the named partition. This can\n"
             "                           be specified multiple times.\n"
             "  -S, --slot=NUM           Slot number (default is 0).\n",
@@ -92,6 +99,7 @@
 int main(int argc, char* argv[]) {
     // clang-format off
     struct option options[] = {
+        { "image",      required_argument,  nullptr, 'i' },
         { "partition",  required_argument,  nullptr, 'p' },
         { "slot",       required_argument,  nullptr, 'S' },
         { nullptr,      0,                  nullptr, 0 },
@@ -100,6 +108,7 @@
 
     uint32_t slot_num = 0;
     std::unordered_set<std::string> partitions;
+    std::vector<std::string> image_files;
 
     int rv, index;
     while ((rv = getopt_long_only(argc, argv, "+p:sh", options, &index)) != -1) {
@@ -116,6 +125,9 @@
                     return usage(argc, argv);
                 }
                 break;
+            case 'i':
+                image_files.push_back(optarg);
+                break;
             case 'p':
                 partitions.emplace(optarg);
                 break;
@@ -126,56 +138,66 @@
         std::cerr << "Missing super image argument.\n";
         return usage(argc, argv);
     }
-    std::string super_path = argv[optind++];
+    image_files.emplace(image_files.begin(), argv[optind++]);
 
     std::string output_dir = ".";
     if (optind + 1 <= argc) {
         output_dir = argv[optind++];
     }
 
-    if (optind < argc) {
-        std::cerr << "Unrecognized command-line arguments.\n";
-        return usage(argc, argv);
-    }
+    std::unique_ptr<LpMetadata> metadata;
+    std::vector<unique_fd> fds;
 
-    // Done reading arguments; open super.img. PartitionOpener will decorate
-    // relative paths with /dev/block/by-name, so get an absolute path here.
-    std::string abs_super_path;
-    if (!android::base::Realpath(super_path, &abs_super_path)) {
-        std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
-        return EX_OSERR;
-    }
+    for (std::size_t index = 0; index < image_files.size(); ++index) {
+        std::string super_path = image_files[index];
 
-    unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
-    if (fd < 0) {
-        std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
-        return EX_OSERR;
-    }
+        // Done reading arguments; open super.img. PartitionOpener will decorate
+        // relative paths with /dev/block/by-name, so get an absolute path here.
+        std::string abs_super_path;
+        if (!android::base::Realpath(super_path, &abs_super_path)) {
+            std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
+            return EX_OSERR;
+        }
 
-    auto metadata = ReadMetadata(abs_super_path, slot_num);
-    if (!metadata) {
+        unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
+        if (fd < 0) {
+            std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
+            return EX_OSERR;
+        }
+
         SparsePtr ptr(sparse_file_import(fd, false, false), sparse_file_destroy);
         if (ptr) {
-            std::cerr << "This image appears to be a sparse image. It must be "
-                         "unsparsed to be"
-                      << " unpacked.\n";
+            std::cerr << "The image file '"
+                      << super_path
+                      << "' appears to be a sparse image. It must be unsparsed to be unpacked.\n";
             return EX_USAGE;
         }
-        std::cerr << "Image does not appear to be in super-partition format.\n";
-        return EX_USAGE;
+
+        if (!metadata) {
+            metadata = ReadMetadata(abs_super_path, slot_num);
+            if (!metadata) {
+                std::cerr << "Could not read metadata from the super image file '"
+                          << super_path
+                          << "'.\n";
+                return EX_USAGE;
+            }
+        }
+
+        fds.emplace_back(std::move(fd));
     }
 
-    ImageExtractor extractor(std::move(fd), std::move(metadata), std::move(partitions), output_dir);
+    // Now do actual extraction.
+    ImageExtractor extractor(std::move(fds), std::move(metadata), std::move(partitions), output_dir);
     if (!extractor.Extract()) {
         return EX_SOFTWARE;
     }
     return EX_OK;
 }
 
-ImageExtractor::ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata,
+ImageExtractor::ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
                                std::unordered_set<std::string>&& partitions,
                                const std::string& output_dir)
-    : image_fd_(std::move(image_fd)),
+    : image_fds_(std::move(image_fds)),
       metadata_(std::move(metadata)),
       partitions_(std::move(partitions)),
       output_dir_(output_dir) {}
@@ -186,6 +208,7 @@
     }
 
     for (const auto& [name, info] : partition_map_) {
+        std::cout << "Attempting to extract partition '" << name << "'...\n";
         if (!ExtractPartition(info)) {
             return false;
         }
@@ -217,13 +240,14 @@
     for (uint32_t i = 0; i < partition->num_extents; i++) {
         uint32_t index = partition->first_extent_index + i;
         const LpMetadataExtent& extent = metadata_->extents[index];
+        std::cout << "  Dealing with extent " << i << " from target source " << extent.target_source << "...\n";
 
         if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
             std::cerr << "Unsupported target type in extent: " << extent.target_type << "\n";
             return false;
         }
-        if (extent.target_source != 0) {
-            std::cerr << "Split super devices are not supported.\n";
+        if (extent.target_source >= image_fds_.size()) {
+            std::cerr << "Insufficient number of super images passed, need at least " << extent.target_source + 1 << ".\n";
             return false;
         }
         total_size += extent.num_sectors * LP_SECTOR_SIZE;
@@ -237,28 +261,28 @@
         return false;
     }
 
-    SparseWriter writer(output_fd, image_fd_, metadata_->geometry.logical_block_size);
+    SparseWriter writer(output_fd, metadata_->geometry.logical_block_size);
 
     // Extract each extent into output_fd.
     for (uint32_t i = 0; i < partition->num_extents; i++) {
         uint32_t index = partition->first_extent_index + i;
         const LpMetadataExtent& extent = metadata_->extents[index];
 
-        if (!writer.WriteExtent(extent)) {
+        if (!writer.WriteExtent(image_fds_[extent.target_source], extent)) {
             return false;
         }
     }
     return writer.Finish();
 }
 
-SparseWriter::SparseWriter(int output_fd, int image_fd, uint32_t block_size)
-    : output_fd_(output_fd), image_fd_(image_fd), block_size_(block_size) {}
+SparseWriter::SparseWriter(borrowed_fd output_fd, uint32_t block_size)
+    : output_fd_(output_fd), block_size_(block_size) {}
 
-bool SparseWriter::WriteExtent(const LpMetadataExtent& extent) {
+bool SparseWriter::WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent) {
     auto buffer = std::make_unique<uint8_t[]>(block_size_);
 
     off_t super_offset = extent.target_data * LP_SECTOR_SIZE;
-    if (lseek(image_fd_, super_offset, SEEK_SET) < 0) {
+    if (lseek(image_fd.get(), super_offset, SEEK_SET) < 0) {
         std::cerr << "image lseek failed: " << strerror(errno) << "\n";
         return false;
     }
@@ -269,7 +293,7 @@
             std::cerr << "extent is not block-aligned\n";
             return false;
         }
-        if (!android::base::ReadFully(image_fd_, buffer.get(), block_size_)) {
+        if (!android::base::ReadFully(image_fd, buffer.get(), block_size_)) {
             std::cerr << "read failed: " << strerror(errno) << "\n";
             return false;
         }
@@ -297,7 +321,7 @@
     }
 
     if (hole_size_) {
-        if (lseek(output_fd_, hole_size_, SEEK_CUR) < 0) {
+        if (lseek(output_fd_.get(), hole_size_, SEEK_CUR) < 0) {
             std::cerr << "lseek failed: " << strerror(errno) << "\n";
             return false;
         }
@@ -312,12 +336,12 @@
 
 bool SparseWriter::Finish() {
     if (hole_size_) {
-        off_t offset = lseek(output_fd_, 0, SEEK_CUR);
+        off_t offset = lseek(output_fd_.get(), 0, SEEK_CUR);
         if (offset < 0) {
             std::cerr << "lseek failed: " << strerror(errno) << "\n";
             return false;
         }
-        if (ftruncate(output_fd_, offset + hole_size_) < 0) {
+        if (ftruncate(output_fd_.get(), offset + hole_size_) < 0) {
             std::cerr << "ftruncate failed: " << strerror(errno) << "\n";
             return false;
         }
diff --git a/perf_tools/Android.bp b/perf_tools/Android.bp
new file mode 100644
index 0000000..26b9351
--- /dev/null
+++ b/perf_tools/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2023 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.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_binary_host {
+    name: "progress_report",
+    srcs: ["progress_report.py"],
+    libs: [ "report_proto",],
+    version: {
+    	     py3: { embedded_launcher: true },
+    },
+    main: "progress_report.py",
+}
+
+python_library_host {
+    name: "report_proto",
+    srcs: ["*.proto"],
+    proto: {
+        canonical_path_from_root: false,
+    },
+}
diff --git a/perf_tools/bats/lcan.py b/perf_tools/bats/lcan.py
new file mode 100755
index 0000000..cfe381a
--- /dev/null
+++ b/perf_tools/bats/lcan.py
@@ -0,0 +1,692 @@
+#!/usr/bin/python3
+# -------------------------------------------
+# logcat analysis
+# -------------------------------------------
+from ast import keyword
+from curses import keyname
+import argparse
+import os
+from string import digits
+from sbtaTools import TextFile
+import datetime
+import re
+import shlex
+import glob
+
+class LCItem:
+	def __init__(self, lCTimeProcessor):
+		self.dateTime = 0
+		self.relativeTime = 0
+		self.key = ""
+		self.moduleName = ""
+		self.keyword = ""
+		self.valueMsec = 0
+		self.lCTimeProcessor = lCTimeProcessor
+		self.lines = []
+
+	def set(self, dateTime, moduleName, keyText, valueMsec):
+		try:
+			self.dateTime = dateTime
+			self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+			self.moduleName = moduleName
+			self.keyword = keyText
+			self.key = moduleName+":" + keyText
+			self.valueMsec = valueMsec
+		except Exception as e:
+			errLine = "LCItem:set() ERROR Failed: " + str(e)
+			assert False, errLine
+
+	def copy(self, otherLCItem):
+		self.dateTime = otherLCItem.dataTime
+		self.relativeTime = otherLCItem.relativeTime
+		self.key = otherLCItem.key
+		self.moduleName = otherLCItem.moduleName
+		self.keyword = otherLCItem.keyword
+		self.valueMsec = otherLCItem.valueMsec
+		self.lCTimeProcessor = otherLCItem.lcTimeProcessor
+		self.lines = otherLCItem.lines
+
+	def appendLine(self, line):
+		self.lines.append(line)
+
+	def keyEqual(self, otherItem):
+		if self.key != otherItem.key:
+			return False
+		return True
+
+	def add(self, otherItem):
+		assert(self.key==otherItem.key)
+		self.lines.extend(otherItem.lines)
+		self.valueMsec = self.valueMsec + otherItem.valueMsec
+		return True
+
+	def addValue(self, otherLCItem):
+		if self.key=="":
+			self.copy(otherLCItem)
+		else:
+			assert(self.key==otherLCItem.key)
+			self.valueMsec = self.valueMsec + otherLCItem.valueMsec
+		return True
+
+	def divideValue(self, number):	# scaler divide
+		self.valueMsec = self.valueMsec / number
+		return True
+
+	def key(self):
+		return self.key
+
+	def print(self):
+		#systemTime = self.lCTimeProcessor.toSystemTime(self.dateTime)
+		#relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+		newTimeString = str(self.relativeTime)
+		if (len(self.lines)>0):
+			print("{} {}: {} {:.4f} - {}".format(newTimeString, self.moduleName, self.keyword, self.valueMsec, self.lines[0]))
+		else:
+			print("{} {}: {} {:.4f} -".format(newTimeString, self.moduleName, self.keyword, self.valueMsec))
+
+	def printLines(self, prefix, min):
+		if (len(self.lines)<min):
+			return
+		for line in self.lines:
+			print("     {}{}".format(prefix, line))
+
+	def findModuleName(self, lineTextWords):
+		colonIndex = -1
+		try:
+			colonIndex = lineTextWords.index(":")
+			# address case of colon with no space
+			moduleName = lineTextWords[colonIndex-1]
+		except:
+			moduleName = ""
+		if colonIndex==-1:
+			for word in reversed(lineTextWords):
+				index = word.find(":")
+				if index!=-1:
+					moduleName = word[:index]
+					break
+		moduleName = moduleName.strip()
+		return colonIndex, moduleName
+
+	def parseLineWithTook(self, line):
+		maxLineLength = 100
+		stage = 0
+		try:
+			words = line.split("  ")
+			dataTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+			if line.find("took to complete") != -1:
+				stage = 1
+				tookIndex = line.find(" took to complete:")
+				uptoEnd= line[:tookIndex]
+				lineTextWords = uptoEnd.split()
+				colonIndex, moduleName = self.findModuleName(lineTextWords)
+				keyword  = " ".join([lineTextWords[6]])
+				value = re.findall(r'\d+', line[tookIndex:])[-1]
+				value = float(value)
+
+			elif line.find("took") != -1:
+				stage = 2
+				tookIndex = line.find(" took")
+				uptoEnd= line[:tookIndex]
+				uptoBracket = uptoEnd.rfind("(")
+				if uptoBracket != -1:
+					uptoEnd = uptoEnd[:uptoBracket]
+				#uptoEnd = uptoEnd.replace(":", "")
+				lineTextWords = shlex.split(uptoEnd)
+				colonIndex, moduleName = self.findModuleName(lineTextWords)
+				# if there is colon only take words after it
+				if colonIndex!=-1:
+					lineTextWords = lineTextWords[colonIndex+1:]
+				numWords = len(lineTextWords)
+				keyword = ""
+				stage = 3
+				try:
+					for i in range(max(numWords-3, 0), numWords, 1):
+						keyword  = keyword + " " + lineTextWords[i]
+				except Exception as e:
+					errLine = "LCItem:parseLineWithTook() ERROR Failed to parse1: " + str(e)
+					print(errLine)
+					assert False, errLine
+
+				# reduce length
+				keyword = keyword[:maxLineLength]
+				keyword = keyword.strip()
+				# using regex expression to replace all numbers
+				keyword = re.sub(r'\d', "_", keyword)
+				value = 0
+				stage = 4
+				try:
+					multplier = 1
+					tookSubstring = line[tookIndex:]
+					secondsIndex = tookSubstring.find("seconds")
+					msIndex = tookSubstring.find("ms")
+					if (secondsIndex!=-1):
+						tookSubstring = tookSubstring[:secondsIndex]
+						multiplier = 1000
+					elif msIndex != -1:
+						tookSubstring = tookSubstring[:msIndex]
+					else:
+						# known exception
+						if tookSubstring.find("properties")==-1:
+							errLine = "LCItem:parseLineWithTook() ERROR invalid took in substring 1B {}".format(line)
+							print(errLine)
+							assert False, errLine
+							return False
+
+					values = re.findall(r'[\d\.\d]+', tookSubstring)
+					while "." in values:
+						values.remove(".")
+					value = float(values[-1])
+					if line.find("seconds") != -1:
+						value = value * multiplier
+				except Exception as e:
+					errLine = "LCItem:parseLineWithTook() ERROR Failed to parse2: " + str(e)
+					print(errLine)
+					assert False, errLine
+				stage = 5
+
+			else:
+				return False
+
+			stage = 6
+			self.set(dataTimeO, moduleName, keyword, value)
+			stage = 7
+			self.lines.append(line)
+
+			return True
+
+		except Exception as e:
+			errLine = "LCItem:parseLineWithTook() ERROR Failed to parse3:" + str(e)
+			print(errLine, stage)
+			assert False, errLine
+
+	def parseLine(self, line):
+		try:
+			words = line.split("  ")
+			dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+			if (dateTimeO!=None):
+				#lcItem = LCItem(self.lCTimeProcessor)
+				newLine = line[19:].rstrip()
+				self.set(dateTimeO, "", newLine, 0)
+				#self.print()
+				return
+			else:
+				return None
+
+		except Exception as e:
+			errLine = "LCItem:parseLine() ERROR Failed to parse3:" + str(e)
+			print(errLine)
+			assert False, errLine
+
+	def find(self, keyword):
+		if self.key.find(keyword)!=-1:
+			return True
+		for line in self.lines:
+			if line.find(keyword)!=-1:
+				return True
+
+	def createLogLine(self):
+		line = ""
+		msecs = self.dateTime.strftime("%f")
+		timeString = self.dateTime.strftime("%m-%d %H:%M:%S.")
+		#timeString = timeString + msecs[]
+		return line
+
+class LCItemSet:
+	def __init__(self, item1, item2):
+		self.item1 = item1
+		self.item2 = item2
+		if (item1.key != "" and item2.key != ""):
+			assert(item1.key == item2.key)
+		if (item1.key!=""):
+			self.key = item1.key
+		else:
+			self.key = item2.key
+		self.diff = item2.valueMsec - item1.valueMsec
+
+	def __gt__(self, other):
+			if(self.diff>other.diff):
+				return True
+			else:
+				return False
+
+	def add(item):
+		assert(False)
+
+	def print(self, min, printAll):
+		self.diff = self.item2.valueMsec - self.item1.valueMsec
+		if abs(self.diff)<min:
+			return
+		flag = "12"
+		if self.item1.key=="":
+			flag = "-2"
+
+		if self.item2.key=="":
+			flag = "1-"
+
+		print("{}, {}, {}, {}, {}".format(self.key, self.item1.valueMsec, self.item2.valueMsec, self.diff, flag))
+		if printAll:
+			self.item1.printLines("1> ", 1)
+			self.item2.printLines("2> ", 1)
+
+class LCItemMap:
+	def __init__(self):
+		self.map = {}
+
+	def put(self, newItem):
+		item = self.map.get(newItem.key)
+		if item==None:
+			self.map[newItem.key] = newItem
+		else:
+			item.add(newItem)
+
+	def print(self):
+		for key in self.map:
+			self.map[key].print()
+
+	def find(self, keyword):
+		lCItems = []
+		for index, lCItem in self.map:
+			if lCItem.find(keyword):
+				lCItems.append(lCItem)
+		return lCItems
+
+	def addValues(self, other):
+		for index, item in other.map.items():
+			if item.key in self.map:
+				self.map[item.key].addValue(item)
+			else:
+				self.map[item.key] = item
+
+	def divideValue(self, number):
+		for index, item in self.map:
+			item.divideValue(number)
+
+class LCItemSetMap:
+	def __init__(self):
+		self.map = {}
+
+	def put(self, itemSet):
+		item = self.map.get(itemSet.key)
+		if item==None:
+			self.map[itemSet.key] = itemSet
+		else:
+			item.add(itemSet)
+
+	def printSorted(self, printAll):
+		a = sorted(self.map.items(), key=lambda x: (x[1], x[0]), reverse=True)
+		cumDif = 0
+		print("Key, Value1, Value2, diff")
+		for item in a:
+			item[1].print(1, printAll)
+			cumDif = cumDif + item[1].diff
+		print("CUMULATIVE DIFF: {}".format(cumDif))
+
+class LCTimeProcessor:
+	def __init__(self):
+		self.firstKernelTimeStamp = 0
+		self.lastKernelTimeStamp = 0
+		self.firstSystemTimesStamp = 0
+		self.lastTimeStamp = 0
+		self.zeroRelativeTime = 0
+		today = datetime.datetime.now()
+		year = str(today.year)
+		self.currentYear = year[-2:] # 2022/2023
+
+	def parseTimeStamp(self, line):
+		try:
+			if len(line)<19:
+				return None
+			currentYear = self.currentYear	# 22
+			words = line.split("  ")
+			timeString = words[0]
+			#timeString = re.sub("[^0-9: -.]", "", timeString)
+			timeString = timeString.strip()
+			timeString = timeString[:18]
+			timeString = currentYear + "-" + timeString
+			dataTimeO = datetime.datetime.strptime(timeString, "%Y-%m-%d %H:%M:%S.%f")
+			return dataTimeO
+		except Exception as e:
+			# If no time stamp on this line
+			if line.find("beginning of")!=-1:
+				return None
+			errLine = "LCItem:parseTimeStamp() ERROR Failed to parse:" + str(e)
+			print(errLine)
+			assert False, errLine
+			return None
+
+
+	def process(self, line):
+		timeStamp = self.parseTimeStamp(line)
+		if timeStamp==None:
+			return False
+
+		if self.firstKernelTimeStamp==0:
+			self.firstKernelTimeStamp = timeStamp
+		else:
+			if timeStamp < self.firstKernelTimeStamp:
+				return False
+
+			timeChange = timeStamp - self.lastTimeStamp
+			if (timeChange.total_seconds() > 68*5):
+				if self.firstSystemTimesStamp ==0:
+					self.firstSystemTimesStamp = timeStamp
+					self.lastKernelTimeStamp = self.lastTimeStamp
+					self.zeroRelativeTime = self.toSystemTime(self.firstKernelTimeStamp)
+
+		self.lastTimeStamp = timeStamp
+		return True
+
+	def toSystemTime(self, timeStamp):
+		try:
+			# if no systemTime is found, it must all be system time
+			if self.firstSystemTimesStamp==0:
+				self.firstSystemTimesStamp = self.firstKernelTimeStamp
+				self.lastKernelTimeStamp = self.lastTimeStamp
+				self.zeroRelativeTime = self.firstKernelTimeStamp
+				return timeStamp
+			if timeStamp >= self.firstSystemTimesStamp:
+				return timeStamp
+			else:
+				timeChange = timeStamp - self.lastKernelTimeStamp
+				systemTime = self.firstSystemTimesStamp + timeChange
+				return systemTime
+		except Exception as e:
+			errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
+			print(errLine)
+			assert False, errLine
+
+	def toRelativeTime(self, timeStamp):
+		systemTime = self.toSystemTime(timeStamp)
+		relativeTime = systemTime - self.zeroRelativeTime
+		return relativeTime
+
+		if timeStamp< self.firstSystemTimesStamp:
+			timeChange = timeStamp - self.lastKernelTimeStamp
+			systemTime = self.firstSystemTimesStamp + timeChange
+			return systemTime
+		else:
+			return timeStamp
+
+	def toString(self, timeStamp):
+		return timeStamp.strftime("%Y-%m-%d %H:%M:%S.%f")
+
+class LCLogLine:
+	def __init__(self, lCTimeProcessor):
+		self.dateTime = 0
+		self.relativeTime = 0
+		self.lineText = ""
+		self.lCTimeProcessor = lCTimeProcessor
+
+	def set(self, dateTime, lineText):
+		self.dateTime = dateTime
+		self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+		self.lineText = lineText
+
+	def print(self):
+		newTimeString = str(self.relativeTime)
+		print("{}{}".format(newTimeString, self.lineText))
+
+	def parseLine(self, line):
+		try:
+			dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+			if (dateTimeO!=None):
+				lineText = line[19:].rstrip()
+				self.set(dateTimeO, lineText)
+				return
+			else:
+				return None
+
+		except Exception as e:
+			errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
+			print(errLine)
+			assert False, errLine
+
+	def find(self, word):
+		if (self.lineText.find(word)!=-1):
+			return True
+		else:
+			return False
+
+	def findAll(self, words):
+		for word in words:
+			if (self.lineText.find(word)==-1):
+				return False
+		return True
+
+class LCLogFile(TextFile):
+	priorTimeStamp = 0.0
+	def __init__(self, _fileName = ""):
+		super(LCLogFile, self).__init__(_fileName)
+		self.linesWithTook = []
+		self.linesWithTookToComplete = []
+		self.linesWithoutTookToComplete = []
+		self.firstKernelTimeStamp = 0
+		self.lastKernelTimeStamp = 0
+		self.firstSystemTimesStamp = 0
+		self.lastTimeStamp = 0
+		self.lCTimeProcessor = LCTimeProcessor()
+		self.dumpLinesBeforeBeginning()
+
+	def dumpLinesBeforeBeginning(self):
+		# start from --------- beginning of kernel
+		beginningFound = False
+		_lines = []
+		for line in self.lines:
+			if beginningFound==True:
+				_lines.append(line)
+				self.lCTimeProcessor.process(line)
+
+			elif line.find("beginning of kernel") != -1:
+				beginningFound = True
+
+		self.lines = _lines
+
+
+	def scanTook(self):
+		lCItemMap = LCItemMap()
+		foundBeginning = False
+		for line in self.lines:
+			# start at beginning
+			if not foundBeginning:
+				if line.find("beginning of kernel=1") != -1:
+					foundBeginning = True
+					continue
+
+			# stop if boot complete
+			if line.find("sys.boot_completed=1") != -1:
+				break
+
+			if line.find("took") != -1:
+				self.linesWithTook.append(line.rstrip())
+
+		for line in self.linesWithTook:
+			lCItem = LCItem(self.lCTimeProcessor)
+			if lCItem.parseLineWithTook(line)==True:
+				lCItemMap.put(lCItem)
+
+		return lCItemMap
+
+	def print(self, numItems=None):
+		self.scanTook()
+
+	def convert(self, numItems=None):
+		lcLogLines = []
+		for line in self.lines:
+			lcLogLine = LCLogLine(self.lCTimeProcessor)
+			lcLogLine.parseLine(line)
+			lcLogLines.append(lcLogLine)
+		return lcLogLines
+'''
+	def createLCFile(self, fileName):
+		# create LCTimeProcessor
+		# create LCItem
+		# create LCLogLine
+		# write LCLogLine to file
+'''
+class ScanFile:
+	def __init__(self):
+		self.fileName = "none"
+
+	def scanKeyWords(self, fileName):
+		print("Scanning {}".format(fileName))
+		cmd = "grep \"apexd: wait for '\/dev\/loop-control'\" {}".format(fileName)
+		x = os.system(cmd)
+		cmd = "grep \"Service 'apexd-bootstrap\" {}".format(fileName)
+		x = os.system(cmd)
+		cmd = "grep apexd.status=activated {}".format(fileName)
+		x = os.system(cmd)
+		cmd = "grep \"Service 'bpfloader'\" {}".format(fileName)
+		x = os.system(cmd)
+		cmd = "grep \"sys.boot_completed=1\" {} | head -n 1".format(fileName)
+		x = os.system(cmd)
+
+	def scanTook(self, fileName):
+		lCLogFile = LCLogFile(fileName)
+		lCItemMap = lCLogFile.scanTook()
+
+	def convert(self, fileName):
+		lCLogFile = LCLogFile(fileName)
+		lcItems = lCLogFile.convert()
+		for lcItem in lcItems:
+			lcItem.print()
+
+	def phases(self, fileName):
+		keywordFile = TextFile("keywords")
+		#keywords = ['init first', 'init second', "Starting phase 200", "boot_completed"]
+
+		lCLogFile = LCLogFile(fileName)
+		keywordSets = []
+		for line in keywordFile.lines:
+			line = line.strip()
+			keywordSet = line.split(", ")
+			keywordSets.append(keywordSet)
+
+		lcLogLines = lCLogFile.convert()
+		for keywordSet in keywordSets:
+			for lcLogLine in lcLogLines:
+				if lcLogLine.findAll(keywordSet)==True:
+					lcLogLine.print()
+					break
+
+class Compare:
+	def __init__(self):
+		self.fileName = "none"
+
+	def compareLCItemMaps(self, lCItemMap1, lCItemMap2):
+		lCItemSetMap = LCItemSetMap()
+
+		for item1key in lCItemMap1.map:
+			found = False
+			for item2key in lCItemMap2.map:
+				if item2key==item1key:
+					lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
+					lCItemSetMap.put(lcItemSet)
+					found = True
+					break
+			# if item1Key is not in ItemMap2, add a null item
+			if found==False:
+				lCTimeProcessor = LCTimeProcessor()
+				nullLCItem = LCItem(lCTimeProcessor)
+				lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
+				lCItemSetMap.put(lcItemSet)
+				found = True
+
+		lCItemSetMap.printSorted(printAll)
+		return lCItemSetMap
+
+	def compareFiles(self, fileName1, fileName2, printAll):
+		print("---------------------------------------------------------------")
+		print("lcan.py -cmp {} {}".format(fileName1, fileName2))
+		print("---------------------------------------------------------------")
+		lCLogFile1 = LCLogFile(fileName1)
+		lCItemMap1 = lCLogFile1.scanTook()
+		lCLogFile2 = LCLogFile(fileName2)
+		lCItemMap2 = lCLogFile2.scanTook()
+
+		lCItemSetMap = LCItemSetMap()
+
+		for item1key in lCItemMap1.map:
+			found = False
+			for item2key in lCItemMap2.map:
+				if item2key==item1key:
+					lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
+					lCItemSetMap.put(lcItemSet)
+					found = True
+					break
+			# if item1Key is not in ItemMap2, add a null item
+			if found==False:
+				lCTimeProcessor = LCTimeProcessor()
+				nullLCItem = LCItem(lCTimeProcessor)
+				lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
+				lCItemSetMap.put(lcItemSet)
+				found = True
+
+		lCItemSetMap.printSorted(printAll)
+		return lCItemSetMap
+
+	def getAverageOfDir(self, buildId):
+		#get average values for build1
+		dirList = glob.glob("{}/LC-{}*.txt".format(buildId, buildId))
+		numFiles = len(dirList)
+		#iterate in numerical order
+		lCItemMapS = LCItemMap()
+		for index in range(numFiles):
+			fileName = "{}/LC-{}-{}.txt".format(buildId, buildId, index)
+		#for index, fileName in enumerate(dirList):
+			lCLogFile = LCLogFile(fileName)
+			lCItemMap = lCLogFile.scanTook()
+			lCItemMapS.addValues(lCItemMap)
+		lCItemMapS.divideValue(numFiles)
+		return lCItemMapS
+
+	def compareDirs(self, buildId1, buildId2, printAll):
+		print("---------------------------------------------------------------")
+		print("lcan.py -cmpd {} {} {}".format(buildId1, buildId2, printAll))
+		print("---------------------------------------------------------------")
+
+		#get average values for build1
+		lCItemMap1 = self.getAverageOfDir(buildId1)
+		lCItemMap2 = self.getAverageOfDir(buildId2)
+		self.compareLCItemMaps(self, lCItemMap1, lCItemMap2)
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-c", nargs=1, metavar=('<fileName>'), help="convert Logcat output to start from boot with converted timeStamps")
+parser.add_argument("-k", nargs=1, metavar=('<fileName>'), help="summary on keywords")
+parser.add_argument("-a", nargs=1, metavar=('<fileName>'), help="analyze file")
+parser.add_argument("-cmp", nargs=3, metavar=('<fileName1>', '<fileName2>', '<brief/all>'), help="compare logcat files")
+parser.add_argument("-cmpd", nargs=3, metavar=('<dirName1>', '<dirName2>', '<brief/all>'), help="compare logcat files")
+parser.add_argument("-p", nargs=1, metavar=('<fileName1>'), help="phase report on log files")
+args = parser.parse_args()
+
+if args.k!=None:
+	scanFile = ScanFile()
+	scanFile.scanKeyWords(args.k[0])
+
+if args.a!=None:
+	scanFile = ScanFile()
+	scanFile.scanTook(args.a[0])
+
+if args.c!=None:
+	scanFile = ScanFile()
+	scanFile.convert(args.c[0])
+
+if args.p!=None:
+	scanFile = ScanFile()
+	scanFile.phases(args.p[0])
+
+if args.cmp!=None:
+	printAll = False
+	compare = Compare()
+	if (len(args.cmp)>2):
+		if (args.cmp[2].find("all")!=-1):
+			printAll = True
+	compare.compareFiles(args.cmp[0], args.cmp[1], printAll)
+
+if args.cmpd!=None:
+	printAll = False
+	compare = Compare()
+	if (len(args.cmpd)>2):
+		if (args.cmpd[2].find("all")!=-1):
+			printAll = True
+	compare.compareDirs(args.cmpd[0], args.cmpd[1], printAll)
diff --git a/perf_tools/config.yaml b/perf_tools/config.yaml
new file mode 100644
index 0000000..4372ce2
--- /dev/null
+++ b/perf_tools/config.yaml
@@ -0,0 +1,15 @@
+# This file is used to store the keywords to be extracted
+# from the logcat.
+---
+- boot_progress_start
+- boot_progress_preload_start
+- boot_progress_preload_end
+- boot_progress_system_run
+- boot_progress_pms_start
+- boot_progress_pms_system_scan_start
+- boot_progress_pms_data_scan_start
+- boot_progress_pms_scan_end
+- boot_progress_pms_ready
+- boot_progress_ams_ready
+- boot_progress_enable_screen
+- car_helper_boot_phase
diff --git a/perf_tools/parse_timestamp.py b/perf_tools/parse_timestamp.py
new file mode 100644
index 0000000..bfac3f7
--- /dev/null
+++ b/perf_tools/parse_timestamp.py
@@ -0,0 +1,55 @@
+import sys
+import os
+from datetime import datetime
+
+# Usage:
+# replace_timestamp.py input.txt output.txt timestamp_string
+#
+# Description:
+# Replace timestamp in the input.txt with the difference timestamp to timestamp_string.
+#
+# Example: replace_timestamp.py input.txt output.txt "01-28 18:12:30.339".
+#
+def main():
+    filepath = sys.argv[1]
+    if not os.path.isfile(filepath):
+        print("File path {} does not exist. Exiting...".format(filepath))
+        sys.exit()
+
+    output_filepath = sys.argv[2]
+
+    timestamp_str = sys.argv[3]
+    date_time_obj = datetime.strptime(timestamp_str, '%m-%d %H:%M:%S.%f')
+
+    output_fp = open(output_filepath, 'w')
+    i = 1
+    with open(filepath, 'r', errors = 'ignore') as fp:
+        for line in fp:
+            newline = replace_timestamp_abs(line, timestamp_str, date_time_obj)
+            output_fp.write(newline)
+            i = i + 1
+    fp.close()
+    output_fp.close()
+
+
+def replace_timestamp_abs(line, timestamp_str, date_time_obj0):
+    if line[:5] != timestamp_str[:5]:
+        return line
+
+    index = line.find(" ", 6)
+    if index <= 0:
+        return line
+    substr0 = line[:index]
+    substr1 = line[index:]
+
+    try:
+        date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f')
+    except ValueError:
+        return line
+
+    date_time_delta = date_time_obj - date_time_obj0
+    date_time_delta_str = str(date_time_delta)
+    return date_time_delta_str + substr1
+
+if __name__ == '__main__':
+    main()
diff --git a/perf_tools/parse_timing.py b/perf_tools/parse_timing.py
new file mode 100644
index 0000000..1ba2314
--- /dev/null
+++ b/perf_tools/parse_timing.py
@@ -0,0 +1,197 @@
+import sys
+import os
+from datetime import datetime
+
+# Usage:
+# python3 parse_timing.py logcat.txt "08-23 23:10:32.555" 10 200
+#
+# Description: extract events and timing in the log that start from timestamp "08-23 23:10:32.555"
+# till 10 seconds
+#
+# Usage:
+# python3 parse_timing.py logcat1.txt logcat2.txt 10 ts1 ts1 200
+#
+# Description: report the timing that the differences are bigger than 200
+#
+# Example:
+# python3 log_processing/parse_timing.py 8976224/logcat.txt 8879724/logcat.txt
+# "08-23 23:10:32.555" "07-29 06:39:06.254" 200
+def main():
+   if len(sys.argv) == 5:
+      process_one_log()
+   elif len(sys.argv) == 6:
+      compair_two_log()
+   else:
+      print("wrong number of arguments")
+
+def compair_two_log():
+   filepath1 = sys.argv[1]
+   print(filepath1)
+   if not os.path.isfile(filepath1):
+       print("File path {} does not exist. Exiting...".format(filepath1))
+       sys.exit()
+
+   filepath2 = sys.argv[2]
+   print(filepath2)
+   if not os.path.isfile(filepath2):
+       print("File path {} does not exist. Exiting...".format(filepath2))
+       sys.exit()
+
+   ts1 = datetime.strptime(sys.argv[3], '%m-%d %H:%M:%S.%f')
+   ts2 = datetime.strptime(sys.argv[4], '%m-%d %H:%M:%S.%f')
+   duration = float(sys.argv[5])*1000
+
+   # 1: took to complete 1000ms
+   # 2: took 33ms
+   # 3: took 33 ms or took 0.3 seconds
+   file1_events = [{}, {}, {}]
+   file2_events = [{}, {}, {}]
+
+   extract_events(filepath1, file1_events, ts1, duration)
+   extract_events(filepath2, file2_events, ts2, duration)
+
+
+   sum_events_timing(file1_events)
+   sum_events_timing(file2_events)
+
+   sum_all_events_timing_diff(file1_events, file2_events)
+
+   sys.exit()
+
+
+def process_one_log():
+   filepath = sys.argv[1]
+   print(filepath)
+   if not os.path.isfile(filepath):
+       print("File path {} does not exist. Exiting...".format(filepath))
+       sys.exit()
+
+   # 1: took to complete 1000ms
+   # 2: took 33ms
+   # 3: took 33 ms or took 0.3 seconds
+   events = [{}, {}, {}]
+   ts = datetime.strptime(sys.argv[2], '%m-%d %H:%M:%S.%f')
+   duration = float(sys.argv[3])*1000
+   extract_events(filepath, events, ts, duration)
+
+   timing = float(sys.argv[3])
+   print_sorted_all_events(events, timing)
+
+   sys.exit()
+
+def print_sorted_all_events(file_events, timing):
+   for i in range(len(file_events)):
+      print_sorted_events(file_events[i], timing)
+
+def print_sorted_events(events, timing):
+   for word in sorted(events, key=events.get, reverse=True):
+      if (events[word]) > timing:
+         print("event:{} \ttiming:{} ".format(word, events[word]))
+
+def sum_events_timing(events):
+   total_sum = 0;
+   for i in range(len(events)):
+      sum = 0
+      print("start summary for type {}".format(i))
+      for event in events[i]:
+         sum += events[i][event]
+         #print("event {} timing {} ".format(event, events[i][event]))
+      print("sum events type {} {} : timing {}".format(i, len(events), sum))
+      total_sum += sum
+   print("sum all type events timing {}\n".format(total_sum))
+
+def sum_events_timing_diff(type, file1_events, file2_events):
+   sum_diff = 0
+   max_diff = 0
+   regression_events = {}
+   print("start summary for type {}".format(type))
+   for event in file1_events:
+      val = file2_events.get(event)
+      if val != None:
+         diff = file1_events[event] - val
+         if diff > 100 and val > 100:
+            # print("regression event {} \t{}: {} \t{}: {} \tdiff: {}"
+            #      .format(event, "case1", file1_events[event], "case2", val, diff))
+            regression_events[event] = diff
+            sum_diff += diff
+            max_diff = max(max_diff, diff)
+   print("\nsummary for timing type {} sum_diff {} max_diff {}".format(type, sum_diff, max_diff))
+   print_events(regression_events, file1_events, file2_events)
+
+def sum_all_events_timing_diff(file1_events, file2_events):
+   for i in range(len(file1_events)):
+      sum_events_timing_diff(i, file1_events[i], file2_events[i])
+
+def print_events(events, file1_events, file2_events):
+   for word in sorted(events, key=events.get, reverse=True):
+      if (events[word]) > 10:
+          print("{} \tdiff {} \t{} \t{}".format(word, events[word],file1_events[word], file2_events[word]))
+
+def find_took(words):
+   for i in range(len(words)):
+      if words[i] == 'took' or words[i] == "took:":
+         return i
+
+def extract_time(line, events):
+   if not "took" in line:
+      return
+
+   # 1: took to complete 1000ms
+   # 2: took 33ms
+   # 3: took 33 ms or took 0.3 seconds
+   words = line.strip().split(' ')
+   i = find_took(words)
+   index = 0;
+   str1 = " "
+   key = str1.join(words[8:i])
+
+   if words[i+1] == 'to' and words[i+2] == 'complete:':
+      index = 0;
+      val = float(words[i+3][:-2]);
+   elif words[i+1][-2:] == 'ms':
+      index = 1
+      val = float(words[i+1][:-2]);
+   elif len(words) > i+2:
+      index = 2
+      if words[i+2] == 'seconds':
+         val = float(words[i+1])*1000;
+      elif words[i+2] == 'ms':
+         val = float(words[i+1])
+      else:
+         return True
+
+   # print("index: {}  key: {} val: {}".format(index, key, val));
+
+   if events[index].get(key) == None:
+      events[index][key] = val
+      return True
+   else:
+      # print("duplicate key: " + key + " line: " + line)
+      return True
+
+def check_time_range(line, ts, duration):
+   index = line.find(" ", 6)
+   if index <= 0:
+      return False
+
+   try:
+      current_time = datetime.strptime(line[:index], '%m-%d %H:%M:%S.%f')
+   except ValueError:
+      return False
+
+   deltatime = current_time - ts
+   if deltatime.total_seconds()*1000 < 0 or deltatime.total_seconds() > duration:
+      return False
+   return True
+
+def extract_events(filepath, events, ts, duration):
+   with open(filepath, errors='ignore') as fp:
+      for line in fp:
+         if check_time_range(line, ts, duration) == False:
+            continue
+         if extract_time(line, events) == False:
+            return
+
+
+if __name__ == '__main__':
+    main()
diff --git a/perf_tools/progress_report.py b/perf_tools/progress_report.py
new file mode 100755
index 0000000..f6a1c43
--- /dev/null
+++ b/perf_tools/progress_report.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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.
+#
+
+import argparse
+from datetime import datetime
+import yaml
+import os
+import report_pb2
+import sys
+import traceback
+
+# Usage: python3 progress_report.py --logcat logcat.txt --config config.yaml --output_dir report_dir
+#
+# logcat.txt should contain the "boot_progress_start" and "boot_progress_enable_screen"".
+# config.yaml contains all the keywords to be extracted.
+# report_dir will contain three generated files:
+#
+# timestamp_log.txt: contains the same content as logcat.txt, but the timestamp is replaced
+# with relative time with boot_progress_start time.
+#
+# report_proto.txt: contains the report for the events related to the keywords.
+#
+# report.txt: contains logcat messages corresponding to the events captured in report_proto.txt
+
+def init_arguments():
+    parser = argparse.ArgumentParser(
+        prog = 'progrocess_report.py',
+        description='Extract timing information and generate a report.')
+    parser.add_argument(
+        '--logcat', type=str, required=True,
+        help = 'logcat file name')
+    parser.add_argument(
+        '--config', type=str, required=True,
+        help = 'configuration file for keywords')
+    parser.add_argument(
+        '--output_dir', type= str, required=True,
+        help = 'directory name to store the generated files')
+    return parser.parse_args()
+
+# Find boot_progress_start line and boot_progress_enable_screen find the time difference
+# return the start time string
+def find_boot_progress_start_end(fp):
+    start = ""
+    end = ""
+    for line in fp:
+        if "boot_progress_start" in line:
+            start = line
+        if "boot_progress_enable_screen" in line and len(start):
+            end = line
+            break
+
+    missing_error = ""
+    if start == "":
+        missing_error = "******logcat file missing boot_progress_start\n"
+    elif end == "":
+        missing_error +=  "******logcat file missing boot_progress_end "
+    if missing_error != "":
+        sys.exit("Missing required message in the logcat:\n" + missing_error)
+    return [start, end]
+
+# TODO(b/262259622): passing a tuple of (startDate, endDate)
+def replace_timestamp_abs(line, timestamp_str, date_time_obj0):
+    index = line.find(" ", 6)
+    if index <= 0:
+        return line
+    substr0 = line[:index]
+    substr1 = line[index:]
+
+    try:
+        date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f')
+    except ValueError:
+        return line
+
+    date_time_delta = date_time_obj - date_time_obj0
+    date_time_delta_str = str(date_time_delta)
+    return date_time_delta_str + substr1
+
+def in_time_range(start, end, line):
+    try:
+        current_time = datetime.strptime(line[:18], '%m-%d %H:%M:%S.%f')
+    except ValueError:
+        return False
+
+    if current_time >= start and current_time <= end:
+        return True
+
+    return False
+
+# Here is an example of event we would like extract:
+# 09-15 16:04:15.655  root   991   991 I boot_progress_preload_start: 5440
+# for each event, it is a tuple of(timestamp, event_name, timing)
+def extract_event(line, keywords):
+    words = line.split(" ")
+    for keyword in keywords:
+        if keyword in words[-2]:
+            return (words[0], words[-2], words[-1])
+    return ()
+
+def write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp,
+                      report_proto_fp):
+    start_timestamp_obj = datetime.strptime(timestamps[0][:18], '%m-%d %H:%M:%S.%f')
+    end_timestamp_obj = datetime.strptime(timestamps[1][:18], '%m-%d %H:%M:%S.%f')
+    report = report_pb2.Report()
+    for line in logcat_fp:
+        ts_fixed_line = replace_timestamp_abs(line, timestamps[0][:18], start_timestamp_obj)
+        timestamp_fixed_logcat_fp.write(ts_fixed_line)
+        if in_time_range(start_timestamp_obj, end_timestamp_obj, line):
+            event = extract_event(ts_fixed_line, keywords)
+            if len(event) == 0:
+                continue
+
+            report_fp.write(ts_fixed_line)
+            record = report.record.add()
+            record.timestamp = event[0]
+            record.event = event[1]
+            record.timing = int(event[2])
+    report_proto_fp.write(str(report))
+
+def main():
+    args = init_arguments()
+
+    keywords = []
+    with open(args.config, 'r') as file:
+        keywords = yaml.safe_load(file)
+
+    if not os.path.isdir(args.output_dir):
+        os.mkdir(args.output_dir)
+    timestamp_fixed_logcat_fp = open(os.path.join(args.output_dir, "timestamp_fixed_log.txt"), 'w')
+    report_fp = open(os.path.join(args.output_dir, "report.txt"), 'w')
+    report_proto_fp = open(os.path.join(args.output_dir,  "report_proto.txt"), 'w')
+    try:
+        with open(args.logcat, 'r', errors = 'ignore') as logcat_fp:
+            timestamps = find_boot_progress_start_end(logcat_fp)
+            logcat_fp.seek(0)
+            write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp, report_proto_fp)
+    except Exception as e:
+        traceresult = traceback.format_exc()
+        print("Caught an exception: {}".format(traceback.format_exc()))
+
+    timestamp_fixed_logcat_fp.close()
+    report_fp.close()
+    report_proto_fp.close()
+
+if __name__ == '__main__':
+    main()
diff --git a/perf_tools/report.proto b/perf_tools/report.proto
new file mode 100644
index 0000000..fb9e839
--- /dev/null
+++ b/perf_tools/report.proto
@@ -0,0 +1,21 @@
+syntax = "proto2";
+
+package report;
+
+message Keyword {
+  required string value = 1;
+}
+
+message Keywords {
+  repeated Keyword keyword = 1;
+}
+
+message Record {
+  required string timestamp = 1;
+  required string event = 2;
+  required int64 timing = 3;
+}
+
+message Report {
+  repeated Record record = 1;
+}
diff --git a/perf_tools/sbtpull.py b/perf_tools/sbtpull.py
new file mode 100755
index 0000000..c6af55c
--- /dev/null
+++ b/perf_tools/sbtpull.py
@@ -0,0 +1,562 @@
+#!/usr/bin/python3
+#from calendar import c
+import sys
+import os
+import copy
+import argparse
+import statistics
+import glob
+import subprocess
+import re
+import time
+
+from string import digits
+
+class LogLine:
+	remove_digits = str.maketrans('', '', digits)
+	def __init__(self):
+		self.lineNum = 0
+		self.timeStamp = 0
+		self.delta = 0
+		self.deltaDiff = 0
+		self.text = "none"
+		self.textKey = "none"
+
+	def parse(self, index, line, priorTimeStamp):
+		_line = line.strip()
+		words = _line.split("]", 1)
+		timeString = words[0].strip(" [")
+		self.lineNum = index
+		self.timeStamp = float(timeString)
+		self.delta = self.timeStamp - priorTimeStamp
+		self.text = words[1][:150]
+		self.textKey = self.text.translate(self.remove_digits)
+		priorTimeStamp = self.timeStamp
+		return self
+
+	def getTextKey(self):
+		textKey = self.textKey
+		return textKey
+
+	def print(self):
+		print("I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text))
+
+	def toString(self):
+		return "I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text)
+
+def sortByDelta(item):
+	return item.delta
+
+def sortByTimeStamp(item):
+	return item.timeStamp
+
+class LogLineListStats:
+	def __init__(self):
+		self.numItems = 0
+		self.firstTimeStamp = 0
+		self.lastTimeStamp = 0
+		self.deltaSum = 0
+		self.deltaDiffSum = 0
+		self.status = "unknown"
+		self.name = "unknown"
+
+	def print(self):
+		print("Name {:25} NumItems {:4d} FirstTimeStamp {:.3f}, lastTimeStamp {:.3f}, deltaTime {:.3f} deltaSum {:.3f}, deltaDiffSum {:.3f} Status {}".format(self.name, self.numItems, self.firstTimeStamp, self.lastTimeStamp, (self.lastTimeStamp - self.firstTimeStamp), self.deltaSum, self.deltaDiffSum, self.status))
+
+	def add(self, _other):
+		if (_other.firstTimeStamp< self.firstTimeStamp):
+				self.firstTimeStamp = _other.firstTimeStamp
+
+		if (_other.lastTimeStamp > self.lastTimeStamp):
+			self.lastTimeStamp = _other.lastTimeStamp
+		self.deltaSum += _other.deltaSum
+
+
+# ------------------------------------------------------
+
+class LogLineList:
+
+	def __init__(self, _name= ""):
+		self.list = []
+		self.name = _name
+
+	def clear(self):
+		self.list.clear()
+
+	def append(self, item):
+		self.list.append(item)
+
+	def print(self, numItems=None):
+		printLineNum = 0
+		timeStart = 0
+		sumDelta = 0
+		sumDeltaDiff = 0
+		print("List: {}", self.name)
+		for item in self.list:
+			if (timeStart==0):
+				timeStart = item.timeStamp
+			timeOffset = item.timeStamp - timeStart
+			sumDelta += item.delta
+			sumDeltaDiff += item.deltaDiff
+			printLineNum += 1
+			printLine = "{:4d} {:.4f} {: .4f} ({: .4f}) | {} ".format(printLineNum, timeOffset, sumDelta, sumDeltaDiff, item.toString())
+			print(printLine)
+			if (numItems!=None):
+				numItems -= 1
+				if (numItems<=0):
+					break
+
+	def find(self, word):
+		itemList = []
+		for item in self.list:
+			if item.text.find(word) != -1:
+				itemList.append(item)
+		return itemList
+	def findFirst(self, word):
+		itemList = self.find(word)
+		if (itemList!=None):
+			if (len(itemList)>0):
+				return itemList[0]
+		return None
+
+	def findTextKey(self, textKey):
+		itemList = []
+		for item in self.list:
+			if item.getTextKey()==textKey:
+				itemList.append(item)
+		if (len(itemList)==0):
+			return None
+		return itemList[0]
+
+	def findItem(self, item):
+		textKey = item.getTextKey()
+		return self.findTextKey(textKey)
+
+	def findExactItem(self, item):
+		text = item.text
+		return self.find(text)
+
+	def filter(self, startKeyWord, endKeyWord, delta=0):
+		resultsList = LogLineList()
+		startTime = self.findFirst(startKeyWord).timeStamp
+		endTime = self.findFirst(endKeyWord).timeStamp
+		for item in self.list:
+			if ((item.timeStamp >= startTime) and (item.timeStamp<=endTime)):
+				if (item.timeStamp == startTime):
+					item.delta = 0
+				if ((item.delta > delta) or ((item.timeStamp == startTime))):
+					resultsList.append(item)
+		resultsList.name = self.name
+		return resultsList
+
+
+	def findCommon(self, otherList):
+		commonList = LogLineList()
+		commonList.name = self.name + "_common"
+		notCommonList = LogLineList()
+		notCommonList.name = self.name + "_notCommon"
+		numFoundItems = 0
+		numNotFoundItems = 0
+		for item in self.list:
+			dm1 = otherList.findExactItem(item)
+			_item = copy.deepcopy(item)
+			if dm1!=None:
+				commonList.append(_item)
+				numFoundItems += 1
+			else:
+				notCommonList.append(_item)
+				numNotFoundItems += 1
+		print("FindCommon {} {} {} {}".format(len(self.list), len(otherList.list), numFoundItems, numNotFoundItems  ))
+		return commonList, notCommonList
+
+	def difference(self, otherList):
+		diffList = LogLineList()
+		diffList.name = otherList.name + "Diff"
+		for item in self.list:
+			thisItem = copy.deepcopy(item)
+			otherItem = otherList.findItem(item)
+			if (item.text.find("EXT4-fs (sda11): recovery complete")!=-1):
+				print("here")
+			if otherItem==None:
+				print("LogLineList::difference() !Error, other does not have {}".format(item.text))
+			else:
+				thisItem.deltaDiff = otherItem.delta - item.delta
+
+			diffList.append(thisItem)
+		return diffList
+
+	def analyze(self, checkPeriod = True, includeFirst = True):
+		numItems = 0
+		firstTimeStamp = 0
+		firstDelta = 0
+		lastTimeStamp = 0
+		deltaSum = 0
+		deltaDiffSum = 0
+		for item in self.list:
+			numItems += 1
+			deltaSum += item.delta
+			deltaDiffSum += item.deltaDiff
+			if firstTimeStamp==0:
+				firstTimeStamp = item.timeStamp
+				firstDelta = item.delta
+				deltaSum = 0
+				deltaDiffSum = 0
+			if (item.timeStamp<firstTimeStamp):
+				firstTimeStamp = item.timeStamp
+				firstDelta = item.delta
+
+			if (item.timeStamp > lastTimeStamp):
+				lastTimeStamp = item.timeStamp
+		timePeriod = lastTimeStamp - firstTimeStamp
+		status = "pass"
+		if (checkPeriod):
+			diff = timePeriod - deltaSum
+			if (abs(diff)>0.0001):
+				print("LogLineList::Analyze() {} ERROR! TimePeriod:{}, CumulativeDelta: {} ".format(self.name, timePeriod, deltaSum))
+				status = "ERROR"
+		logLineListStats = LogLineListStats()
+		logLineListStats.numItems = numItems
+		logLineListStats.firstTimeStamp = firstTimeStamp
+		logLineListStats.lastTimeStamp = lastTimeStamp
+		logLineListStats.deltaSum = deltaSum
+		logLineListStats.deltaDiffSum = deltaDiffSum
+		logLineListStats.status = status
+		logLineListStats.name = self.name
+		return logLineListStats
+
+	def addList(self, otherList):
+		self.list.extend(otherList.list)
+		self.list = sorted(self.list, key=sortByTimeStamp)
+
+
+class LogFile:
+	priorTimeStamp = 0.0
+	def __init__(self, _fileName = ""):
+		self.logLineList = LogLineList()
+		if (_fileName!=""):
+			self.load(_fileName)
+
+	def loadLines(self, lines):
+		logLineList = LogLineList()
+		for index, line in enumerate(lines):
+			logLine = LogLine().parse(index, line, self.priorTimeStamp)
+			self.priorTimeStamp = logLine.timeStamp
+			logLineList.append(logLine)
+		return logLineList
+
+	def load(self, _fileName):
+		self.name = _fileName
+		try:
+			file = open(_fileName, 'r')
+			lines = file.readlines()
+			self.logLineList = self.loadLines(lines)
+			file.close()
+		except:
+			print("Error, file '{}' does not exist".format(self.name))
+
+	def print(self, numItems=None):
+		self.logLineList.print(numItems)
+
+# -----------------------------------------------------
+
+class MetricSet:
+	def __init__(self, _names):
+		self.columnNames = _names
+		self.rowColArray = []
+		self.rowSum = []
+		self.rowMax = []
+		self.rowMin = []
+		self.rowStd = []
+		self.rowMedian = []
+		for col in self.columnNames:
+			self.rowSum.append(0)
+			self.rowMax.append(0)
+			self.rowMin.append(sys.maxsize)
+			self.rowStd.append(0)
+			self.rowMedian.append(0)
+
+	def appendSet(self, values):
+		self.rowColArray.append(values)
+
+	def print(self):
+		print("{}".format("  Line#"), end='')
+		for words in self.columnNames:
+			print(", '{}'".format(words), end='')
+		print("")
+
+		for row, values in enumerate(self.rowColArray):
+			print("{:6d}".format(row), end='')
+			for col, value in enumerate(values):
+				print(", {:.3f}".format(value), end='')
+			print("")
+
+		print("{}".format("   MAX"), end='')
+		for value in self.rowMax:
+			print(", {:.3f}".format(value), end='')
+		print("")
+
+
+		print("{}".format("   AVE"), end='')
+		for value in self.rowSum:
+			print(", {:.3f}".format(value), end='')
+		print("")
+
+		print("{}".format("   MIN"), end='')
+		for value in self.rowMin:
+			print(", {:2.3f}".format(value), end='')
+		print("")
+
+		print("{}".format("   STD"), end='')
+		for value in self.rowStd:
+			print(", {:2.3f}".format(value), end='')
+		print("")
+
+		print("{}".format("MEDIAN"), end='')
+		for value in self.rowMedian:
+			print(", {:2.3f}".format(value), end='')
+		print("")
+
+	def analyze(self):
+		stdCols = []
+		numCols = len(self.columnNames)
+		numRows = len(self.rowColArray)
+		for col in range(numCols):
+			stdCols.append([])
+
+		# compute sum
+		for row, values in enumerate(self.rowColArray):
+			for col, value in enumerate(values):
+				self.rowSum[col] += value
+				if value > self.rowMax[col]:
+					self.rowMax[col] = value
+				if value < self.rowMin[col]:
+					self.rowMin[col] = value
+
+		# compute std
+		for col in range(numCols):
+			for row in range(numRows):
+				try:
+					val = self.rowColArray[row][col]
+					stdCols[col].append(val)
+				except IndexError:
+					i = 3
+		for col, colList in enumerate(stdCols):
+			stdValue = 0
+			if (len(colList)>0):
+				stdValue = statistics.pstdev(colList)
+				stdMedian = statistics.median(colList)
+			self.rowStd[col] = stdValue
+			self.rowMedian[col] = stdMedian
+
+		#compute average
+		for col, value in enumerate(self.rowSum):
+			if numRows > 0:
+				self.rowSum[col] = self.rowSum[col] / numRows
+			else:
+				self.rowSum[col] = 0
+
+class AnalyzeFile:
+	initFirstTime = 0
+	initSecondTime = 0
+
+	def __init__(self, _fileName, _keyWords = ["init first", "init second", "boot_completed"]):
+		self.fileName = _fileName
+		self.logFile = LogFile(_fileName)
+		self.workingList = []
+		self.keyWords = _keyWords
+
+	def report(self):
+		print("-----------------------")
+		print("Reporting on '{}'".format(self.fileName))
+		for word in self.keyWords:
+			item = self.logFile.logLineList.findFirst(word)
+			item.print()
+		print("-----------------------")
+
+	def getMetrics(self, metricsSet):
+		values = []
+		for word in self.keyWords:
+			item = self.logFile.logLineList.findFirst(word)
+			if item is not None:
+				values.append(item.timeStamp)
+			else:
+				print("Did not find {} ".format(word))
+		metricsSet.appendSet(values)
+
+	def keyWordReport(self, keyword):
+		numItems = 0
+		cumd = 0
+		items = self.logFile.logLineList.find(keyword)
+		for item in items:
+			item.print()
+			numItems += 1
+			cumd += item.delta
+		print("Num {} found = {}, Sum delay = {:.2f} ".format(keyword, numItems, cumd))
+
+		for item in items:
+			lineKeywords = item.text.split(" ")
+			if (len(lineKeywords)>2):
+				if lineKeywords[2] == "Service":
+					tookIndex = item.text.find("took")
+					if (tookIndex!=None):
+						tookTime = item.text[tookIndex:tookIndex+10]
+						print("{} took {}".format(lineKeywords[3], tookTime))
+
+
+class Analyzer:
+	def __init__(self):
+		self.fileName = []
+
+	def rebootAndRunCmdToFile(self, fileNamePrefix, msgPrefix, Cmd, numTimes, startIndex):
+		captured = False
+		error = False
+		filenameNum = ""
+		for i in range(numTimes):
+			postfix = str(i+startIndex)
+			filenameNum = fileNamePrefix + "-" + postfix + ".txt"
+			print(msgPrefix + " to {}".format(filenameNum))
+			# try 5 times to capure status 'boot_completed'
+			for i in range(5):
+				captured = False
+				rebootCmd = "adb shell su root reboot"
+				fullCmd = Cmd + " > {}".format(filenameNum)
+				x = os.system(rebootCmd)
+				if (x!=0):
+					print("Error")
+					error = True
+					break
+				time.sleep(45)
+				x = os.system(fullCmd)
+				if (x!=0):
+					print("Error")
+					error = True
+					break
+				# check for boot complete
+				try:
+					checkBootComplete = "grep boot_complete {}".format(filenameNum)
+					output = subprocess.check_output(checkBootComplete, shell=True)
+					captured = True
+					break
+				except:
+					captured = False
+					print("trying again for {}".format(filenameNum))
+			if not captured:
+				print("ERROR - failed to capture {}".format(filenameNum))
+		if error:
+			os.system("rm {}".format(filenameNum))
+		return captured
+
+	def getBuildID(self):
+		buildIDCmd = "adb shell su root getprop ro.build.version.incremental"
+		buildString = subprocess.check_output(buildIDCmd, shell = True)
+		numberList = re.findall(r'\d+', buildString.decode('ISO-8859-1') )
+		if (numberList==None): return 0
+		if (len(numberList)==0): return 0
+		buildID = numberList[0]
+		return buildID
+
+	def pullDmesgLogs(self, BuildID, numTimes, startIndex):
+		fileNamePrefix = BuildID
+		msgPrefix = "Pulling Kernel dmesg logs"
+		cmd = "adb shell su root dmesg"
+		return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
+
+	def pullLogcatLogs(self, BuildID, numTimes, startIndex):
+		fileNamePrefix = "LC-"+BuildID
+		msgPrefix = "Pulling Kernel Logcat"
+		cmd = "adb logcat -b all -d"
+		return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
+
+	def runBootAnalyze(self, filename, numTimes, startIndex):
+		ABT = os.environ["ANDROID_BUILD_TOP"]
+		if (len(ABT)<=0):
+			print("ERROR - ANDROID_BUILD_TOP not set")
+		BAFILE = "BA-" + filename + "-" + str(numTimes + startIndex) + ".txt"
+		BACmd = ABT + "/system/extras/boottime_tools/bootanalyze/bootanalyze.py -c " + ABT + "/system/extras/boottime_tools/bootanalyze/config.yaml -n 20 -r -t > " + BAFILE
+		print(BACmd)
+		x = os.system(BACmd)
+		if (x!=0):
+			print("ERROR running bootanalze")
+			return False
+		return True
+
+	def pullAll(self):
+		BuildID = self.getBuildID()
+		Cmd = "adb bugreport bugreport-{}".format(BuildID)
+		print(Cmd)
+		x = os.system(Cmd)
+		if (x!=0):
+			print("ERROR Pulling all data")
+			return False
+		self.pullDmesgLogs(BuildID, 20, 0)
+		self.pullLogcatLogs(BuildID, 2, 0)
+		self.runBootAnalyze(BuildID, 20, 0)
+		self.summaryReportOnDmesgLogFiles(BuildID, 20)
+
+	def summaryReportOnDmesgLogFiles(self, BuildID, numFiles):
+		metricKeyWords = ["init first", "init second", "boot_completed"]
+		metricSet = MetricSet(metricKeyWords)
+		print("Summary report on log files with build ID {}".format(BuildID))
+		dirList = glob.glob("{}*.txt".format(BuildID))
+		numFilesAnalyzed = 0
+		for index, file in enumerate(dirList):
+			analyzeFile = AnalyzeFile(file, metricKeyWords)
+			#check it's a kernel log file
+			item = analyzeFile.logFile.logLineList.findFirst("build.fingerprint")
+			if (item!=None):
+				#check if it has the correct build ID
+				if (item.text.find(BuildID)==-1):
+					continue
+			else:
+				print("BuildID {} not found in file {} fingerprint {}".format(BuildID, file, item))
+				continue
+			analyzeFile.getMetrics(metricSet)
+			numFilesAnalyzed += 1
+			if ((index+1)>=numFiles):
+				break
+		if (numFilesAnalyzed>0):
+			metricSet.analyze()
+			metricSet.print()
+		else:
+			print("No files criteria {}* and build.fingerprint with {}".format(BuildID, BuildID))
+
+	def rename(self, BuildID1, BuildID2, fileType):
+		print("Summary report on log files with build ID {}".format(BuildID1))
+		dirList = glob.glob("*{}*".format(BuildID1))
+		for index, file in enumerate(dirList):
+			findRes = file.find(BuildID1)
+			if (findRes!=-1):
+				newFile = file.replace(BuildID1, BuildID2, 1)
+				newFile += fileType
+				os.system("mv {} {}".format(file, newFile))
+
+
+parser = argparse.ArgumentParser(description='pull all data files from seahawk and run dmesg summary report. The data files will be prefixed with the build ID')
+
+parser.add_argument("-plc", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'),  help="pull logcat numTimes from seahawk")
+parser.add_argument("-pdm", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'),  help="pull dmesg logs numTimes from seahawk")
+parser.add_argument("-pba", nargs=2, metavar=('<BuildID>', '<numTimes>'),  help="pull bootanalyze numTimes from seahawk")
+parser.add_argument("-rd", nargs=2, metavar=('<BuildID>', '<numFiles>'),  help="summary report on <numFiles> dmesg log files named <BuildID>-*.txt in current directory")
+parser.add_argument("-pA", action='store_true', help="pull all data from seahawk a default number of times")
+parser.add_argument("-t", nargs="*", help="test - do not use")
+args = parser.parse_args()
+
+
+if args.pdm!=None:
+	Analyzer().pullDmesgLogs(args.pdm[0], int(args.pdm[1]), int(args.pdm[2]))
+
+if args.plc!=None:
+	Analyzer().pullLogcatLogs(args.plc[0], int(args.plc[1]), int(args.plc[2]))
+
+if args.pba!=None:
+	Analyzer().runBootAnalyze(args.pba[0], int(args.pba[1]), 0)
+
+if args.pA!=None:
+	Analyzer().pullAll()
+
+if args.rd!=None:
+	Analyzer().summaryReportOnDmesgLogFiles(args.rd[0], int(args.rd[1]))
+
+if args.t!=None:
+	Analyzer().getBuildID()
+
diff --git a/power_profile/camera_avg/LICENSE b/power_profile/camera_avg/LICENSE
index 4f22946..35cf914 100644
--- a/power_profile/camera_avg/LICENSE
+++ b/power_profile/camera_avg/LICENSE
@@ -1,6 +1,3 @@
-Apache License
---------------
-
                            Version 2.0, January 2004
                         http://www.apache.org/licenses/
 
@@ -201,447 +198,3 @@
    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.
-
-All image and audio files (including *.png, *.jpg, *.svg, *.mp3, *.wav 
-and *.ogg) are licensed under the CC-BY-NC license. All other files are 
-licensed under the Apache 2 license.
-
-CC-BY-NC License
-----------------
-
-Attribution-NonCommercial-ShareAlike 4.0 International
-
-=======================================================================
-
-Creative Commons Corporation ("Creative Commons") is not a law firm and
-does not provide legal services or legal advice. Distribution of
-Creative Commons public licenses does not create a lawyer-client or
-other relationship. Creative Commons makes its licenses and related
-information available on an "as-is" basis. Creative Commons gives no
-warranties regarding its licenses, any material licensed under their
-terms and conditions, or any related information. Creative Commons
-disclaims all liability for damages resulting from their use to the
-fullest extent possible.
-
-Using Creative Commons Public Licenses
-
-Creative Commons public licenses provide a standard set of terms and
-conditions that creators and other rights holders may use to share
-original works of authorship and other material subject to copyright
-and certain other rights specified in the public license below. The
-following considerations are for informational purposes only, are not
-exhaustive, and do not form part of our licenses.
-
-     Considerations for licensors: Our public licenses are
-     intended for use by those authorized to give the public
-     permission to use material in ways otherwise restricted by
-     copyright and certain other rights. Our licenses are
-     irrevocable. Licensors should read and understand the terms
-     and conditions of the license they choose before applying it.
-     Licensors should also secure all rights necessary before
-     applying our licenses so that the public can reuse the
-     material as expected. Licensors should clearly mark any
-     material not subject to the license. This includes other CC-
-     licensed material, or material used under an exception or
-     limitation to copyright. More considerations for licensors:
-	wiki.creativecommons.org/Considerations_for_licensors
-
-     Considerations for the public: By using one of our public
-     licenses, a licensor grants the public permission to use the
-     licensed material under specified terms and conditions. If
-     the licensor's permission is not necessary for any reason--for
-     example, because of any applicable exception or limitation to
-     copyright--then that use is not regulated by the license. Our
-     licenses grant only permissions under copyright and certain
-     other rights that a licensor has authority to grant. Use of
-     the licensed material may still be restricted for other
-     reasons, including because others have copyright or other
-     rights in the material. A licensor may make special requests,
-     such as asking that all changes be marked or described.
-     Although not required by our licenses, you are encouraged to
-     respect those requests where reasonable. More_considerations
-     for the public: 
-	wiki.creativecommons.org/Considerations_for_licensees
-
-=======================================================================
-
-Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
-Public License
-
-By exercising the Licensed Rights (defined below), You accept and agree
-to be bound by the terms and conditions of this Creative Commons
-Attribution-NonCommercial-ShareAlike 4.0 International Public License
-("Public License"). To the extent this Public License may be
-interpreted as a contract, You are granted the Licensed Rights in
-consideration of Your acceptance of these terms and conditions, and the
-Licensor grants You such rights in consideration of benefits the
-Licensor receives from making the Licensed Material available under
-these terms and conditions.
-
-
-Section 1 -- Definitions.
-
-  a. Adapted Material means material subject to Copyright and Similar
-     Rights that is derived from or based upon the Licensed Material
-     and in which the Licensed Material is translated, altered,
-     arranged, transformed, or otherwise modified in a manner requiring
-     permission under the Copyright and Similar Rights held by the
-     Licensor. For purposes of this Public License, where the Licensed
-     Material is a musical work, performance, or sound recording,
-     Adapted Material is always produced where the Licensed Material is
-     synched in timed relation with a moving image.
-
-  b. Adapter's License means the license You apply to Your Copyright
-     and Similar Rights in Your contributions to Adapted Material in
-     accordance with the terms and conditions of this Public License.
-
-  c. BY-NC-SA Compatible License means a license listed at
-     creativecommons.org/compatiblelicenses, approved by Creative
-     Commons as essentially the equivalent of this Public License.
-
-  d. Copyright and Similar Rights means copyright and/or similar rights
-     closely related to copyright including, without limitation,
-     performance, broadcast, sound recording, and Sui Generis Database
-     Rights, without regard to how the rights are labeled or
-     categorized. For purposes of this Public License, the rights
-     specified in Section 2(b)(1)-(2) are not Copyright and Similar
-     Rights.
-
-  e. Effective Technological Measures means those measures that, in the
-     absence of proper authority, may not be circumvented under laws
-     fulfilling obligations under Article 11 of the WIPO Copyright
-     Treaty adopted on December 20, 1996, and/or similar international
-     agreements.
-
-  f. Exceptions and Limitations means fair use, fair dealing, and/or
-     any other exception or limitation to Copyright and Similar Rights
-     that applies to Your use of the Licensed Material.
-
-  g. License Elements means the license attributes listed in the name
-     of a Creative Commons Public License. The License Elements of this
-     Public License are Attribution, NonCommercial, and ShareAlike.
-
-  h. Licensed Material means the artistic or literary work, database,
-     or other material to which the Licensor applied this Public
-     License.
-
-  i. Licensed Rights means the rights granted to You subject to the
-     terms and conditions of this Public License, which are limited to
-     all Copyright and Similar Rights that apply to Your use of the
-     Licensed Material and that the Licensor has authority to license.
-
-  j. Licensor means the individual(s) or entity(ies) granting rights
-     under this Public License.
-
-  k. NonCommercial means not primarily intended for or directed towards
-     commercial advantage or monetary compensation. For purposes of
-     this Public License, the exchange of the Licensed Material for
-     other material subject to Copyright and Similar Rights by digital
-     file-sharing or similar means is NonCommercial provided there is
-     no payment of monetary compensation in connection with the
-     exchange.
-
-  l. Share means to provide material to the public by any means or
-     process that requires permission under the Licensed Rights, such
-     as reproduction, public display, public performance, distribution,
-     dissemination, communication, or importation, and to make material
-     available to the public including in ways that members of the
-     public may access the material from a place and at a time
-     individually chosen by them.
-
-  m. Sui Generis Database Rights means rights other than copyright
-     resulting from Directive 96/9/EC of the European Parliament and of
-     the Council of 11 March 1996 on the legal protection of databases,
-     as amended and/or succeeded, as well as other essentially
-     equivalent rights anywhere in the world.
-
-  n. You means the individual or entity exercising the Licensed Rights
-     under this Public License. Your has a corresponding meaning.
-
-
-Section 2 -- Scope.
-
-  a. License grant.
-
-       1. Subject to the terms and conditions of this Public License,
-          the Licensor hereby grants You a worldwide, royalty-free,
-          non-sublicensable, non-exclusive, irrevocable license to
-          exercise the Licensed Rights in the Licensed Material to:
-
-            a. reproduce and Share the Licensed Material, in whole or
-               in part, for NonCommercial purposes only; and
-
-            b. produce, reproduce, and Share Adapted Material for
-               NonCommercial purposes only.
-
-       2. Exceptions and Limitations. For the avoidance of doubt, where
-          Exceptions and Limitations apply to Your use, this Public
-          License does not apply, and You do not need to comply with
-          its terms and conditions.
-
-       3. Term. The term of this Public License is specified in Section
-          6(a).
-
-       4. Media and formats; technical modifications allowed. The
-          Licensor authorizes You to exercise the Licensed Rights in
-          all media and formats whether now known or hereafter created,
-          and to make technical modifications necessary to do so. The
-          Licensor waives and/or agrees not to assert any right or
-          authority to forbid You from making technical modifications
-          necessary to exercise the Licensed Rights, including
-          technical modifications necessary to circumvent Effective
-          Technological Measures. For purposes of this Public License,
-          simply making modifications authorized by this Section 2(a)
-          (4) never produces Adapted Material.
-
-       5. Downstream recipients.
-
-            a. Offer from the Licensor -- Licensed Material. Every
-               recipient of the Licensed Material automatically
-               receives an offer from the Licensor to exercise the
-               Licensed Rights under the terms and conditions of this
-               Public License.
-
-            b. Additional offer from the Licensor -- Adapted Material.
-               Every recipient of Adapted Material from You
-               automatically receives an offer from the Licensor to
-               exercise the Licensed Rights in the Adapted Material
-               under the conditions of the Adapter's License You apply.
-
-            c. No downstream restrictions. You may not offer or impose
-               any additional or different terms or conditions on, or
-               apply any Effective Technological Measures to, the
-               Licensed Material if doing so restricts exercise of the
-               Licensed Rights by any recipient of the Licensed
-               Material.
-
-       6. No endorsement. Nothing in this Public License constitutes or
-          may be construed as permission to assert or imply that You
-          are, or that Your use of the Licensed Material is, connected
-          with, or sponsored, endorsed, or granted official status by,
-          the Licensor or others designated to receive attribution as
-          provided in Section 3(a)(1)(A)(i).
-
-  b. Other rights.
-
-       1. Moral rights, such as the right of integrity, are not
-          licensed under this Public License, nor are publicity,
-          privacy, and/or other similar personality rights; however, to
-          the extent possible, the Licensor waives and/or agrees not to
-          assert any such rights held by the Licensor to the limited
-          extent necessary to allow You to exercise the Licensed
-          Rights, but not otherwise.
-
-       2. Patent and trademark rights are not licensed under this
-          Public License.
-
-       3. To the extent possible, the Licensor waives any right to
-          collect royalties from You for the exercise of the Licensed
-          Rights, whether directly or through a collecting society
-          under any voluntary or waivable statutory or compulsory
-          licensing scheme. In all other cases the Licensor expressly
-          reserves any right to collect such royalties, including when
-          the Licensed Material is used other than for NonCommercial
-          purposes.
-
-
-Section 3 -- License Conditions.
-
-Your exercise of the Licensed Rights is expressly made subject to the
-following conditions.
-
-  a. Attribution.
-
-       1. If You Share the Licensed Material (including in modified
-          form), You must:
-
-            a. retain the following if it is supplied by the Licensor
-               with the Licensed Material:
-
-                 i. identification of the creator(s) of the Licensed
-                    Material and any others designated to receive
-                    attribution, in any reasonable manner requested by
-                    the Licensor (including by pseudonym if
-                    designated);
-
-                ii. a copyright notice;
-
-               iii. a notice that refers to this Public License;
-
-                iv. a notice that refers to the disclaimer of
-                    warranties;
-
-                 v. a URI or hyperlink to the Licensed Material to the
-                    extent reasonably practicable;
-
-            b. indicate if You modified the Licensed Material and
-               retain an indication of any previous modifications; and
-
-            c. indicate the Licensed Material is licensed under this
-               Public License, and include the text of, or the URI or
-               hyperlink to, this Public License.
-
-       2. You may satisfy the conditions in Section 3(a)(1) in any
-          reasonable manner based on the medium, means, and context in
-          which You Share the Licensed Material. For example, it may be
-          reasonable to satisfy the conditions by providing a URI or
-          hyperlink to a resource that includes the required
-          information.
-       3. If requested by the Licensor, You must remove any of the
-          information required by Section 3(a)(1)(A) to the extent
-          reasonably practicable.
-
-  b. ShareAlike.
-
-     In addition to the conditions in Section 3(a), if You Share
-     Adapted Material You produce, the following conditions also apply.
-
-       1. The Adapter's License You apply must be a Creative Commons
-          license with the same License Elements, this version or
-          later, or a BY-NC-SA Compatible License.
-
-       2. You must include the text of, or the URI or hyperlink to, the
-          Adapter's License You apply. You may satisfy this condition
-          in any reasonable manner based on the medium, means, and
-          context in which You Share Adapted Material.
-
-       3. You may not offer or impose any additional or different terms
-          or conditions on, or apply any Effective Technological
-          Measures to, Adapted Material that restrict exercise of the
-          rights granted under the Adapter's License You apply.
-
-
-Section 4 -- Sui Generis Database Rights.
-
-Where the Licensed Rights include Sui Generis Database Rights that
-apply to Your use of the Licensed Material:
-
-  a. for the avoidance of doubt, Section 2(a)(1) grants You the right
-     to extract, reuse, reproduce, and Share all or a substantial
-     portion of the contents of the database for NonCommercial purposes
-     only;
-
-  b. if You include all or a substantial portion of the database
-     contents in a database in which You have Sui Generis Database
-     Rights, then the database in which You have Sui Generis Database
-     Rights (but not its individual contents) is Adapted Material,
-     including for purposes of Section 3(b); and
-
-  c. You must comply with the conditions in Section 3(a) if You Share
-     all or a substantial portion of the contents of the database.
-
-For the avoidance of doubt, this Section 4 supplements and does not
-replace Your obligations under this Public License where the Licensed
-Rights include other Copyright and Similar Rights.
-
-
-Section 5 -- Disclaimer of Warranties and Limitation of Liability.
-
-  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
-     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
-     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
-     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
-     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
-     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
-     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
-     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
-     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
-     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
-
-  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
-     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
-     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
-     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
-     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
-     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
-     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
-     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
-     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
-
-  c. The disclaimer of warranties and limitation of liability provided
-     above shall be interpreted in a manner that, to the extent
-     possible, most closely approximates an absolute disclaimer and
-     waiver of all liability.
-
-
-Section 6 -- Term and Termination.
-
-  a. This Public License applies for the term of the Copyright and
-     Similar Rights licensed here. However, if You fail to comply with
-     this Public License, then Your rights under this Public License
-     terminate automatically.
-
-  b. Where Your right to use the Licensed Material has terminated under
-     Section 6(a), it reinstates:
-
-       1. automatically as of the date the violation is cured, provided
-          it is cured within 30 days of Your discovery of the
-          violation; or
-
-       2. upon express reinstatement by the Licensor.
-
-     For the avoidance of doubt, this Section 6(b) does not affect any
-     right the Licensor may have to seek remedies for Your violations
-     of this Public License.
-
-  c. For the avoidance of doubt, the Licensor may also offer the
-     Licensed Material under separate terms or conditions or stop
-     distributing the Licensed Material at any time; however, doing so
-     will not terminate this Public License.
-
-  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
-     License.
-
-
-Section 7 -- Other Terms and Conditions.
-
-  a. The Licensor shall not be bound by any additional or different
-     terms or conditions communicated by You unless expressly agreed.
-
-  b. Any arrangements, understandings, or agreements regarding the
-     Licensed Material not stated herein are separate from and
-     independent of the terms and conditions of this Public License.
-
-
-Section 8 -- Interpretation.
-
-  a. For the avoidance of doubt, this Public License does not, and
-     shall not be interpreted to, reduce, limit, restrict, or impose
-     conditions on any use of the Licensed Material that could lawfully
-     be made without permission under this Public License.
-
-  b. To the extent possible, if any provision of this Public License is
-     deemed unenforceable, it shall be automatically reformed to the
-     minimum extent necessary to make it enforceable. If the provision
-     cannot be reformed, it shall be severed from this Public License
-     without affecting the enforceability of the remaining terms and
-     conditions.
-
-  c. No term or condition of this Public License will be waived and no
-     failure to comply consented to unless expressly agreed to by the
-     Licensor.
-
-  d. Nothing in this Public License constitutes or may be interpreted
-     as a limitation upon, or waiver of, any privileges and immunities
-     that apply to the Licensor or You, including from the legal
-     processes of any jurisdiction or authority.
-
-=======================================================================
-
-Creative Commons is not a party to its public licenses.
-Notwithstanding, Creative Commons may elect to apply one of its public
-licenses to material it publishes and in those instances will be
-considered the "Licensor." Except for the limited purpose of indicating
-that material is shared under a Creative Commons public license or as
-otherwise permitted by the Creative Commons policies published at
-creativecommons.org/policies, Creative Commons does not authorize the
-use of the trademark "Creative Commons" or any other trademark or logo
-of Creative Commons without its prior written consent including,
-without limitation, in connection with any unauthorized modifications
-to any of its public licenses or any other arrangements,
-understandings, or agreements concerning use of licensed material. For
-the avoidance of doubt, this paragraph does not form part of the public
-licenses.
-
-Creative Commons may be contacted at creativecommons.org.
-
diff --git a/profcollectd/Android.bp b/profcollectd/Android.bp
index 122e4f6..787ca22 100644
--- a/profcollectd/Android.bp
+++ b/profcollectd/Android.bp
@@ -31,9 +31,21 @@
     ],
 }
 
+rust_defaults {
+    name: "profcollectd_defaults",
+    arch: {
+        riscv64: {
+            // libprofcollectd doesn't build for riscv64
+            enabled: false,
+        },
+    },
+}
+
 rust_binary {
     name: "profcollectctl",
 
+    defaults: ["profcollectd_defaults"],
+
     srcs: ["profcollectctl.rs"],
 
     rustlibs: [
@@ -45,6 +57,8 @@
 rust_binary {
     name: "profcollectd",
 
+    defaults: ["profcollectd_defaults"],
+
     srcs: ["profcollectd.rs"],
 
     rustlibs: [
diff --git a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
index bfc2444..0769930 100644
--- a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
+++ b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
@@ -24,7 +24,8 @@
     void terminate();
     void trace_once(@utf8InCpp String tag);
     void process();
-    @utf8InCpp String report();
+    /** -1 if there is no usageSetting */
+    @utf8InCpp String report(int usageSetting);
     @utf8InCpp String get_supported_provider();
     void registerProviderStatusCallback(IProviderStatusCallback cb);
 }
diff --git a/profcollectd/libprofcollectd/config.rs b/profcollectd/libprofcollectd/config.rs
index d68f02e..2888aab 100644
--- a/profcollectd/libprofcollectd/config.rs
+++ b/profcollectd/libprofcollectd/config.rs
@@ -40,6 +40,7 @@
     pub static ref REPORT_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/report/");
     pub static ref CONFIG_FILE: &'static Path =
         Path::new("/data/misc/profcollectd/output/config.json");
+    pub static ref LOG_FILE: &'static Path = Path::new("/data/misc/profcollectd/output/trace.log");
 }
 
 /// Dynamic configs, stored in config.json.
@@ -151,7 +152,7 @@
         read_dir(path)?
             .filter_map(|e| e.ok())
             .map(|e| e.path())
-            .filter(|e| e.is_file())
+            .filter(|e| e.is_file() && e != *LOG_FILE)
             .try_for_each(remove_file)?;
         Ok(())
     }
diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs
index da178f2..c0e12e3 100644
--- a/profcollectd/libprofcollectd/lib.rs
+++ b/profcollectd/libprofcollectd/lib.rs
@@ -114,7 +114,7 @@
 
 /// Process traces and report profile.
 pub fn report() -> Result<String> {
-    Ok(get_profcollectd_service()?.report()?)
+    Ok(get_profcollectd_service()?.report(report::NO_USAGE_SETTING)?)
 }
 
 /// Clear all local data.
diff --git a/profcollectd/libprofcollectd/logging_trace_provider.rs b/profcollectd/libprofcollectd/logging_trace_provider.rs
index fda4c66..d9fd35e 100644
--- a/profcollectd/libprofcollectd/logging_trace_provider.rs
+++ b/profcollectd/libprofcollectd/logging_trace_provider.rs
@@ -36,7 +36,7 @@
         true
     }
 
-    fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) {
+    fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str) {
         let trace_file = trace_provider::get_path(trace_dir, tag, LOGGING_TRACEFILE_EXTENSION);
 
         log::info!(
@@ -51,6 +51,9 @@
         log::info!("Process event triggered");
         Ok(())
     }
+
+    fn set_log_file(&self, filename: &Path) {}
+    fn reset_log_file(&self) {}
 }
 
 impl LoggingTraceProvider {
diff --git a/profcollectd/libprofcollectd/report.rs b/profcollectd/libprofcollectd/report.rs
index 69dff0c..cbce1d6 100644
--- a/profcollectd/libprofcollectd/report.rs
+++ b/profcollectd/libprofcollectd/report.rs
@@ -32,11 +32,18 @@
 
 use crate::config::Config;
 
+pub const NO_USAGE_SETTING: i32 = -1;
+
 lazy_static! {
     pub static ref UUID_CONTEXT: Context = Context::new(0);
 }
 
-pub fn pack_report(profile: &Path, report: &Path, config: &Config) -> Result<String> {
+pub fn pack_report(
+    profile: &Path,
+    report: &Path,
+    config: &Config,
+    usage_setting: i32,
+) -> Result<String> {
     let mut report = PathBuf::from(report);
     let report_filename = get_report_filename(&config.node_id)?;
     report.push(&report_filename);
@@ -67,9 +74,14 @@
             let mut f = File::open(e)?;
             let mut buffer = Vec::new();
             f.read_to_end(&mut buffer)?;
-            zip.write_all(&*buffer)?;
+            zip.write_all(&buffer)?;
             Ok(())
         })?;
+
+    if usage_setting != NO_USAGE_SETTING {
+        zip.start_file("usage_setting", options)?;
+        zip.write_all(usage_setting.to_string().as_bytes())?;
+    }
     zip.finish()?;
 
     Ok(report_filename)
@@ -79,14 +91,17 @@
     let since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
     let ts =
         Timestamp::from_unix(&*UUID_CONTEXT, since_epoch.as_secs(), since_epoch.subsec_nanos());
-    let uuid = Uuid::new_v1(ts, node_id.as_bytes())?;
+    let uuid = Uuid::new_v1(
+        ts,
+        node_id.as_bytes().try_into().expect("Invalid number of bytes in V1 UUID"),
+    );
     Ok(uuid.to_string())
 }
 
 /// Get report creation timestamp through its filename (version 1 UUID).
 pub fn get_report_ts(filename: &str) -> Result<SystemTime> {
     let uuid_ts = Uuid::parse_str(filename)?
-        .to_timestamp()
+        .get_timestamp()
         .ok_or_else(|| anyhow!("filename is not a valid V1 UUID."))?
         .to_unix();
     Ok(SystemTime::UNIX_EPOCH + Duration::new(uuid_ts.0, uuid_ts.1))
diff --git a/profcollectd/libprofcollectd/scheduler.rs b/profcollectd/libprofcollectd/scheduler.rs
index f58c499..5558f58 100644
--- a/profcollectd/libprofcollectd/scheduler.rs
+++ b/profcollectd/libprofcollectd/scheduler.rs
@@ -25,7 +25,7 @@
 use std::thread;
 use std::time::{Duration, Instant};
 
-use crate::config::{Config, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR};
+use crate::config::{Config, LOG_FILE, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR};
 use crate::trace_provider::{self, TraceProvider};
 use anyhow::{anyhow, ensure, Context, Result};
 
@@ -41,6 +41,7 @@
 impl Scheduler {
     pub fn new() -> Result<Self> {
         let p = trace_provider::get_trace_provider()?;
+        p.lock().map_err(|e| anyhow!(e.to_string()))?.set_log_file(&LOG_FILE);
         Ok(Scheduler {
             termination_ch: None,
             trace_provider: p,
@@ -68,11 +69,12 @@
                     Ok(_) => break,
                     Err(_) => {
                         // Did not receive a termination signal, initiate trace event.
-                        if check_space_limit(*TRACE_OUTPUT_DIR, &config).unwrap() {
+                        if check_space_limit(&TRACE_OUTPUT_DIR, &config).unwrap() {
                             trace_provider.lock().unwrap().trace(
                                 &TRACE_OUTPUT_DIR,
                                 "periodic",
                                 &config.sampling_period,
+                                &config.binary_filter,
                             );
                         }
                     }
@@ -94,8 +96,13 @@
 
     pub fn one_shot(&self, config: &Config, tag: &str) -> Result<()> {
         let trace_provider = self.trace_provider.clone();
-        if check_space_limit(*TRACE_OUTPUT_DIR, config)? {
-            trace_provider.lock().unwrap().trace(&TRACE_OUTPUT_DIR, tag, &config.sampling_period);
+        if check_space_limit(&TRACE_OUTPUT_DIR, config)? {
+            trace_provider.lock().unwrap().trace(
+                &TRACE_OUTPUT_DIR,
+                tag,
+                &config.sampling_period,
+                &config.binary_filter,
+            );
         }
         Ok(())
     }
@@ -158,6 +165,17 @@
             }
         });
     }
+
+    pub fn clear_trace_log(&self) -> Result<()> {
+        let provider = self.trace_provider.lock().map_err(|e| anyhow!(e.to_string()))?;
+        provider.reset_log_file();
+        let mut result = Ok(());
+        if LOG_FILE.exists() {
+            result = fs::remove_file(*LOG_FILE).map_err(|e| anyhow!(e));
+        }
+        provider.set_log_file(&LOG_FILE);
+        result
+    }
 }
 
 /// Run if space usage is under limit.
diff --git a/profcollectd/libprofcollectd/service.rs b/profcollectd/libprofcollectd/service.rs
index 3f33802..3188888 100644
--- a/profcollectd/libprofcollectd/service.rs
+++ b/profcollectd/libprofcollectd/service.rs
@@ -79,11 +79,11 @@
             .context("Failed to process profiles.")
             .map_err(err_to_binder_status)
     }
-    fn report(&self) -> BinderResult<String> {
+    fn report(&self, usage_setting: i32) -> BinderResult<String> {
         self.process()?;
 
         let lock = &mut *self.lock();
-        pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config)
+        pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config, usage_setting)
             .context("Failed to create profile report.")
             .map_err(err_to_binder_status)
     }
@@ -131,7 +131,8 @@
             log::info!("Config change detected, resetting profcollect.");
             clear_data()?;
 
-            write(*CONFIG_FILE, &new_config.to_string())?;
+            write(*CONFIG_FILE, new_config.to_string())?;
+            new_scheduler.clear_trace_log()?;
         }
 
         // Clear profile reports out of rentention period.
diff --git a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
index f25f5ff..2239a18 100644
--- a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
+++ b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
@@ -38,14 +38,17 @@
         simpleperf_profcollect::has_device_support()
     }
 
-    fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) {
+    fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str) {
         let trace_file = trace_provider::get_path(trace_dir, tag, ETM_TRACEFILE_EXTENSION);
+        // Record ETM data for kernel space only when it's not filtered out by binary_filter. So we
+        // can get more ETM data for user space when ETM data for kernel space isn't needed.
+        let record_scope = if binary_filter.contains("kernel") {
+            simpleperf_profcollect::RecordScope::BOTH
+        } else {
+            simpleperf_profcollect::RecordScope::USERSPACE
+        };
 
-        simpleperf_profcollect::record(
-            &*trace_file,
-            sampling_period,
-            simpleperf_profcollect::RecordScope::BOTH,
-        );
+        simpleperf_profcollect::record(&trace_file, sampling_period, binary_filter, record_scope);
     }
 
     fn process(&self, trace_dir: &Path, profile_dir: &Path, binary_filter: &str) -> Result<()> {
@@ -76,6 +79,14 @@
             .filter(is_etm_extension)
             .try_for_each(process_trace_file)
     }
+
+    fn set_log_file(&self, filename: &Path) {
+        simpleperf_profcollect::set_log_file(filename);
+    }
+
+    fn reset_log_file(&self) {
+        simpleperf_profcollect::reset_log_file();
+    }
 }
 
 impl SimpleperfEtmTraceProvider {
diff --git a/profcollectd/libprofcollectd/trace_provider.rs b/profcollectd/libprofcollectd/trace_provider.rs
index 1305919..133fa5c 100644
--- a/profcollectd/libprofcollectd/trace_provider.rs
+++ b/profcollectd/libprofcollectd/trace_provider.rs
@@ -30,8 +30,10 @@
 pub trait TraceProvider {
     fn get_name(&self) -> &'static str;
     fn is_ready(&self) -> bool;
-    fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration);
+    fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str);
     fn process(&self, trace_dir: &Path, profile_dir: &Path, binary_filter: &str) -> Result<()>;
+    fn set_log_file(&self, filename: &Path);
+    fn reset_log_file(&self);
 }
 
 pub fn get_trace_provider() -> Result<Arc<Mutex<dyn TraceProvider + Send>>> {
diff --git a/puncture_fs/Android.bp b/puncture_fs/Android.bp
deleted file mode 100644
index aef7ba4..0000000
--- a/puncture_fs/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2014 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.
-
-package {
-    default_applicable_licenses: ["system_extras_puncture_fs_license"],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
-    name: "system_extras_puncture_fs_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-    ],
-    license_text: [
-        "NOTICE",
-    ],
-}
-
-cc_binary {
-    name: "puncture_fs",
-
-    srcs: ["puncture_fs.cpp"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    shared_libs: [
-        "libc",
-        "liblog",
-        "liblogwrap",
-    ],
-}
diff --git a/puncture_fs/NOTICE b/puncture_fs/NOTICE
deleted file mode 100644
index 316b4eb..0000000
--- a/puncture_fs/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2014, The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/puncture_fs/puncture_fs.cpp b/puncture_fs/puncture_fs.cpp
deleted file mode 100644
index 9c01e3b..0000000
--- a/puncture_fs/puncture_fs.cpp
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2014 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 <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <getopt.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <logwrap/logwrap.h>
-#include <sys/stat.h>
-#include <sys/statvfs.h>
-#include <utils/Log.h>
-
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
-
-#define MAX_IO_WRITE_CHUNK_SIZE 0x100000
-
-#ifndef min
-#define min(a,b) ((a) < (b) ? (a) : (b))
-#endif
-
-typedef unsigned long u64;
-
-static void usage(const char * const progname) {
-    fprintf(stderr,
-            "Usage: %s [-s <seed>] -h <hole size in bytes> -t <total hole size in bytes> "
-            "path\n",
-            progname);
-}
-
-static u64 get_free_space(const char * const path) {
-    struct statvfs s;
-
-    if (statvfs(path, &s) < 0) {
-        fprintf(stderr, "\nerrno: %d. Failed to get free disk space on %s\n",
-                errno, path);
-        return 0;
-    } else {
-        return (u64)s.f_bsize * (u64)s.f_bfree;
-    }
-}
-
-static u64 get_random_num(const u64 start, const u64 end) {
-    if (end - start <= 0)
-        return start;
-    assert(RAND_MAX >= 0x7FFFFFFF);
-    if ((end - start) > 0x7FFFFFFF)
-        return start + (((u64)random() << 31) | (u64)random()) % (end - start);
-    return start + (random() % (end - start));
-}
-
-static char get_random_char() {
-    return 'A' + random() % ('Z' - 'A');
-}
-
-static bool create_unique_file(const char * const dir_path, const u64 size,
-                               const u64 id, char * const base,
-                               const u64 base_length) {
-    u64 length = 0;
-    int fd;
-    char file_path[FILENAME_MAX];
-    bool ret = true;
-
-    base[random() % min(base_length, size)] = get_random_char();
-
-    sprintf(file_path, "%s/file_%lu", dir_path, id);
-    fd = open(file_path, O_WRONLY | O_CREAT | O_SYNC, 0777);
-    if (fd < 0) {
-        // We suppress ENOSPC erros as that is common as we approach the
-        // last few MBs of the fs as we don't account for the size of the newly
-        // added meta data after the initial free space computation.
-        if (errno != 28) {
-            fprintf(stderr, "\nerrno: %d. Failed to create %s\n", errno, file_path);
-        }
-        return false;
-    }
-    while (length + base_length < size) {
-        if (write(fd, base, base_length) < 0) {
-            if (errno != 28) {
-                fprintf(stderr, "\nerrno: %d. Failed to write %lu bytes to %s\n",
-                        errno, base_length, file_path);
-            }
-            ret = false;
-            goto done;
-        }
-        length += base_length;
-    }
-    if (write(fd, base, size - length) < 0) {
-        if (errno != 28) {
-            fprintf(stderr, "\nerrno: %d. Failed to write last %lu bytes to %s\n",
-                    errno, size - length, file_path);
-        }
-        ret = false;
-        goto done;
-    }
-done:
-    if (close(fd) < 0) {
-        fprintf(stderr, "\nFailed to close %s\n", file_path);
-        ret = false;
-    }
-    return ret;
-}
-
-static bool create_unique_dir(char *dir, const char * const root_path) {
-    char random_string[15];
-    int i;
-
-    for (i = 0; i < 14; ++i) {
-        random_string[i] = get_random_char();
-    }
-    random_string[14] = '\0';
-
-    sprintf(dir, "%s/%s", root_path, random_string);
-
-    if (mkdir(dir, 0777) < 0) {
-        fprintf(stderr, "\nerrno: %d. Failed to create %s\n", errno, dir);
-        return false;
-    }
-    return true;
-}
-
-static bool puncture_fs (const char * const path, const u64 total_size,
-                         const u64 hole_size, const u64 total_hole_size) {
-    u64 increments = (hole_size * total_size) / total_hole_size;
-    u64 hole_max;
-    u64 starting_max = 0;
-    u64 ending_max = increments;
-    char stay_dir[FILENAME_MAX], delete_dir[FILENAME_MAX];
-    const char* rm_bin_argv[] = { "/system/bin/rm", "-rf", ""};
-    u64 file_id = 1;
-    char *base_file_data;
-    u64 i = 0;
-
-    if (!create_unique_dir(stay_dir, path) ||
-        !create_unique_dir(delete_dir, path)) {
-        return false;
-    }
-
-    base_file_data = (char*) malloc(MAX_IO_WRITE_CHUNK_SIZE);
-    for (i = 0; i < MAX_IO_WRITE_CHUNK_SIZE; ++i) {
-        base_file_data[i] = get_random_char();
-    }
-    fprintf(stderr, "\n");
-    while (ending_max <= total_size) {
-        fprintf(stderr, "\rSTAGE 1/2: %d%% Complete",
-                (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++,
-                           base_file_data,
-                           MAX_IO_WRITE_CHUNK_SIZE);
-        create_unique_file(delete_dir,
-                           hole_size,
-                           file_id++,
-                           base_file_data,
-                           MAX_IO_WRITE_CHUNK_SIZE);
-
-        starting_max = hole_max + hole_size;
-        ending_max += increments;
-    }
-    create_unique_file(stay_dir,
-                       (ending_max - increments - starting_max),
-                       file_id++,
-                       base_file_data,
-                       MAX_IO_WRITE_CHUNK_SIZE);
-    fprintf(stderr, "\rSTAGE 1/2: 100%% Complete\n");
-    fprintf(stderr, "\rSTAGE 2/2: 0%% Complete");
-    free(base_file_data);
-    rm_bin_argv[2] = delete_dir;
-    if (logwrap_fork_execvp(ARRAY_SIZE(rm_bin_argv), rm_bin_argv, nullptr,
-                            false, LOG_KLOG, false, nullptr) < 0) {
-        fprintf(stderr, "\nFailed to delete %s\n", rm_bin_argv[2]);
-        return false;
-    }
-    fprintf(stderr, "\rSTAGE 2/2: 100%% Complete\n");
-    return true;
-}
-
-int main (const int argc, char ** const argv) {
-    int opt;
-    int mandatory_opt;
-    char *path = NULL;
-    int seed = time(NULL);
-
-    u64 total_size = 0;
-    u64 hole_size = 0;
-    u64 total_hole_size = 0;
-
-    mandatory_opt = 2;
-    while ((opt = getopt(argc, argv, "s:h:t:")) != -1) {
-        switch(opt) {
-            case 's':
-                seed = atoi(optarg);
-                break;
-            case 'h':
-                hole_size = atoll(optarg);
-                mandatory_opt--;
-                break;
-            case 't':
-                total_hole_size = atoll(optarg);
-                mandatory_opt--;
-                break;
-            default:
-                usage(argv[0]);
-                exit(EXIT_FAILURE);
-        }
-    }
-    if (mandatory_opt) {
-        usage(argv[0]);
-        exit(EXIT_FAILURE);
-    }
-    if (optind >= argc) {
-        fprintf(stderr, "\nExpected path name after options.\n");
-        usage(argv[0]);
-        exit(EXIT_FAILURE);
-    }
-    path = argv[optind++];
-
-    if (optind < argc) {
-        fprintf(stderr, "\nUnexpected argument: %s\n", argv[optind]);
-        usage(argv[0]);
-        exit(EXIT_FAILURE);
-    }
-
-    srandom(seed);
-    fprintf(stderr, "\nRandom seed is: %d\n", seed);
-
-    total_size = get_free_space(path);
-    if (!total_size) {
-        exit(EXIT_FAILURE);
-    }
-    if (total_size < total_hole_size || total_hole_size < hole_size) {
-        fprintf(stderr, "\nInvalid sizes: total available size should be "
-                        "larger than total hole size which is larger than "
-                        "hole size\n");
-        exit(EXIT_FAILURE);
-    }
-
-    if (!puncture_fs(path, total_size, hole_size, total_hole_size)) {
-        exit(EXIT_FAILURE);
-    }
-    return 0;
-}
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index 5894393..475213b 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -65,6 +65,11 @@
             local_include_dirs: ["nonlinux_support/include"],
         },
     },
+    arch: {
+        riscv64: {
+            enabled: false,
+        },
+    },
 }
 
 // linked as a separate library because using OpenCSD headers needs to enable exception
@@ -98,6 +103,20 @@
     },
 }
 
+// Build regex support in a separate library to catch std::regex_error exception.
+cc_library_static {
+    name: "libsimpleperf_regex",
+    host_supported: true,
+    srcs: ["RegEx.cpp"],
+    cppflags: ["-fexceptions"],
+    static_libs: ["libbase"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
+}
+
 cc_defaults {
     name: "simpleperf_static_libs",
     defaults: [
@@ -107,6 +126,7 @@
     host_supported: true,
     static_libs: [
         "libsimpleperf_etm_decoder",
+        "libsimpleperf_regex",
         "libbase",
         "liblog",
         "libutils",
@@ -121,11 +141,7 @@
                 "libprocinfo",
                 "libevent",
                 "libc++fs",
-            ],
-        },
-        android: {
-            static_libs: [
-                "libc",
+                "librustc_demangle_static",
             ],
         },
         linux_glibc_x86_64: {
@@ -156,6 +172,7 @@
     ],
     static_libs: [
         "libsimpleperf_etm_decoder",
+        "libsimpleperf_regex",
         "libopencsd_decoder",
     ],
     target: {
@@ -170,6 +187,7 @@
             static_libs: [
                 "libc++fs",
                 "libdexfile_support",
+                "librustc_demangle_static",
             ],
             runtime_libs: [
                 "libdexfile", // libdexfile_support dependency
@@ -224,6 +242,7 @@
         "command.cpp",
         "dso.cpp",
         "etm_branch_list.proto",
+        "ETMBranchListFile.cpp",
         "event_attr.cpp",
         "event_type.cpp",
         "kallsyms.cpp",
@@ -389,6 +408,9 @@
     target: {
         android: {
             static_executable: true,
+            static_libs: [
+                "libc",
+            ],
         },
         android_arm: {
             dist: {
@@ -576,6 +598,7 @@
         "read_elf_test.cpp",
         "read_symbol_map_test.cpp",
         "RecordFilter_test.cpp",
+        "RegEx_test.cpp",
         "record_file_test.cpp",
         "record_test.cpp",
         "report_utils_test.cpp",
@@ -684,6 +707,7 @@
     whole_static_libs: [
         "libgmock",
         "libsimpleperf",
+        "libsimpleperf_regex",
     ],
 }
 
@@ -709,19 +733,6 @@
     },
 }
 
-python_library_host {
-    name: "simpleperf_report_lib",
-    srcs: [
-        "scripts/simpleperf_report_lib.py",
-        "scripts/simpleperf_utils.py",
-    ],
-    data: [
-        "scripts/bin/darwin/x86_64/libsimpleperf_report.dylib",
-        "scripts/bin/linux/x86_64/libsimpleperf_report.so",
-        "scripts/bin/windows/**/*.dll",
-    ],
-}
-
 filegroup {
     name: "system-extras-simpleperf-testdata",
     srcs: ["CtsSimpleperfTestCases_testdata/**/*"],
@@ -730,7 +741,7 @@
 cc_fuzz {
     name: "libsimpleperf_report_fuzzer",
     defaults: [
-        "simpleperf_shared_libs",
+        "simpleperf_static_libs",
     ],
     host_supported: true,
     srcs: [
@@ -738,17 +749,15 @@
     ],
     static_libs: [
         "libsimpleperf",
-        "libLLVMObject",
-        "libLLVMBitReader",
-        "libLLVMMC",
-        "libLLVMMCParser",
-        "libLLVMCore",
-        "libLLVMSupport",
     ],
     target: {
+        linux: {
+            // Fuzzer may not be able to load libdexfile. So statically link it.
+            static_libs: ["libdexfile_static"],
+        },
         windows: {
             enabled: false,
         },
     },
-    corpus: ["testdata/*.data"],
+    corpus: ["testdata/**/*.data"],
 }
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index 954c28c..5c443e9 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -17,7 +17,10 @@
 
 # simpleperf_script.zip (for release in ndk)
 # ============================================================
-SIMPLEPERF_SCRIPT_LIST := \
+SIMPLEPERF_SCRIPT_FILE_LIST := \
+    $(call all-named-files-under,*.proto,.)
+
+SIMPLEPERF_SCRIPT_DIR_LIST := \
     app_api \
     doc \
     demo \
@@ -25,13 +28,15 @@
     scripts \
     testdata
 
-SIMPLEPERF_SCRIPT_LIST := $(addprefix -D $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST))
+SIMPLEPERF_SCRIPT_FILE_LIST := $(addprefix -f $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_FILE_LIST))
+SIMPLEPERF_SCRIPT_DIR_LIST := $(addprefix -D $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_DIR_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)
+	$(hide) $(SOONG_ZIP) -d -o $@ -C system/extras/simpleperf $(SIMPLEPERF_SCRIPT_FILE_LIST) \
+    $(SIMPLEPERF_SCRIPT_DIR_LIST)
 
 $(call declare-1p-target,$(SIMPLEPERF_SCRIPT_PATH),system/extras)
 
diff --git a/simpleperf/ETMBranchListFile.cpp b/simpleperf/ETMBranchListFile.cpp
new file mode 100644
index 0000000..c92cf8c
--- /dev/null
+++ b/simpleperf/ETMBranchListFile.cpp
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2023 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 "ETMBranchListFile.h"
+
+#include "ETMDecoder.h"
+#include "system/extras/simpleperf/etm_branch_list.pb.h"
+
+namespace simpleperf {
+
+static constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
+
+std::string BranchToProtoString(const std::vector<bool>& branch) {
+  size_t bytes = (branch.size() + 7) / 8;
+  std::string res(bytes, '\0');
+  for (size_t i = 0; i < branch.size(); i++) {
+    if (branch[i]) {
+      res[i >> 3] |= 1 << (i & 7);
+    }
+  }
+  return res;
+}
+
+std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) {
+  std::vector<bool> branch(bit_size, false);
+  for (size_t i = 0; i < bit_size; i++) {
+    if (s[i >> 3] & (1 << (i & 7))) {
+      branch[i] = true;
+    }
+  }
+  return branch;
+}
+
+static std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) {
+  switch (dso_type) {
+    case DSO_ELF_FILE:
+      return proto::ETMBranchList_Binary::ELF_FILE;
+    case DSO_KERNEL:
+      return proto::ETMBranchList_Binary::KERNEL;
+    case DSO_KERNEL_MODULE:
+      return proto::ETMBranchList_Binary::KERNEL_MODULE;
+    default:
+      LOG(ERROR) << "unexpected dso type " << dso_type;
+      return std::nullopt;
+  }
+}
+
+bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::string& s) {
+  proto::ETMBranchList branch_list_proto;
+  branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+  std::vector<char> branch_buf;
+  for (const auto& p : binary_map) {
+    const BinaryKey& key = p.first;
+    const BranchListBinaryInfo& binary = p.second;
+    auto binary_proto = branch_list_proto.add_binaries();
+
+    binary_proto->set_path(key.path);
+    if (!key.build_id.IsEmpty()) {
+      binary_proto->set_build_id(key.build_id.ToString().substr(2));
+    }
+    auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
+    if (!opt_binary_type.has_value()) {
+      return false;
+    }
+    binary_proto->set_type(opt_binary_type.value());
+
+    for (const auto& addr_p : binary.branch_map) {
+      auto addr_proto = binary_proto->add_addrs();
+      addr_proto->set_addr(addr_p.first);
+
+      for (const auto& branch_p : addr_p.second) {
+        const std::vector<bool>& branch = branch_p.first;
+        auto branch_proto = addr_proto->add_branches();
+
+        branch_proto->set_branch(BranchToProtoString(branch));
+        branch_proto->set_branch_size(branch.size());
+        branch_proto->set_count(branch_p.second);
+      }
+    }
+
+    if (binary.dso_type == DSO_KERNEL) {
+      binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
+    }
+  }
+  if (!branch_list_proto.SerializeToString(&s)) {
+    LOG(ERROR) << "failed to serialize branch list binary map";
+    return false;
+  }
+  return true;
+}
+
+static std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) {
+  switch (binary_type) {
+    case proto::ETMBranchList_Binary::ELF_FILE:
+      return DSO_ELF_FILE;
+    case proto::ETMBranchList_Binary::KERNEL:
+      return DSO_KERNEL;
+    case proto::ETMBranchList_Binary::KERNEL_MODULE:
+      return DSO_KERNEL_MODULE;
+    default:
+      LOG(ERROR) << "unexpected binary type " << binary_type;
+      return std::nullopt;
+  }
+}
+
+static UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
+  UnorderedBranchMap branch_map;
+  for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
+    const auto& addr_proto = binary_proto.addrs(i);
+    auto& b_map = branch_map[addr_proto.addr()];
+    for (size_t j = 0; j < addr_proto.branches_size(); j++) {
+      const auto& branch_proto = addr_proto.branches(j);
+      std::vector<bool> branch =
+          ProtoStringToBranch(branch_proto.branch(), branch_proto.branch_size());
+      b_map[branch] = branch_proto.count();
+    }
+  }
+  return branch_map;
+}
+
+bool StringToBranchListBinaryMap(const std::string& s, BranchListBinaryMap& binary_map) {
+  proto::ETMBranchList branch_list_proto;
+  if (!branch_list_proto.ParseFromString(s)) {
+    PLOG(ERROR) << "failed to read ETMBranchList msg";
+    return false;
+  }
+  if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
+    PLOG(ERROR) << "not in format etm_branch_list.proto";
+    return false;
+  }
+
+  for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
+    const auto& binary_proto = branch_list_proto.binaries(i);
+    BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
+    if (binary_proto.has_kernel_info()) {
+      key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
+    }
+    BranchListBinaryInfo& binary = binary_map[key];
+    auto dso_type = ToDsoType(binary_proto.type());
+    if (!dso_type) {
+      LOG(ERROR) << "invalid binary type " << binary_proto.type();
+      return false;
+    }
+    binary.dso_type = dso_type.value();
+    binary.branch_map = BuildUnorderedBranchMap(binary_proto);
+  }
+  return true;
+}
+
+class ETMThreadTreeWhenRecording : public ETMThreadTree {
+ public:
+  ETMThreadTreeWhenRecording(bool dump_maps_from_proc)
+      : dump_maps_from_proc_(dump_maps_from_proc) {}
+
+  ThreadTree& GetThreadTree() { return thread_tree_; }
+  void ExcludePid(pid_t pid) { exclude_pid_ = pid; }
+
+  const ThreadEntry* FindThread(int tid) override {
+    const ThreadEntry* thread = thread_tree_.FindThread(tid);
+    if (thread == nullptr) {
+      if (dump_maps_from_proc_) {
+        thread = FindThreadFromProc(tid);
+      }
+      if (thread == nullptr) {
+        return nullptr;
+      }
+    }
+    if (exclude_pid_ && exclude_pid_ == thread->pid) {
+      return nullptr;
+    }
+
+    if (dump_maps_from_proc_) {
+      DumpMapsFromProc(thread->pid);
+    }
+    return thread;
+  }
+
+  void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+  const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+  const ThreadEntry* FindThreadFromProc(int tid) {
+    std::string comm;
+    pid_t pid;
+    if (ReadThreadNameAndPid(tid, &comm, &pid)) {
+      thread_tree_.SetThreadName(pid, tid, comm);
+      return thread_tree_.FindThread(tid);
+    }
+    return nullptr;
+  }
+
+  void DumpMapsFromProc(int pid) {
+    if (dumped_processes_.count(pid) == 0) {
+      dumped_processes_.insert(pid);
+      std::vector<ThreadMmap> maps;
+      if (GetThreadMmapsInProcess(pid, &maps)) {
+        for (const auto& map : maps) {
+          thread_tree_.AddThreadMap(pid, pid, map.start_addr, map.len, map.pgoff, map.name);
+        }
+      }
+    }
+  }
+
+  ThreadTree thread_tree_;
+  bool dump_maps_from_proc_;
+  std::unordered_set<int> dumped_processes_;
+  std::optional<pid_t> exclude_pid_;
+};
+
+class ETMBranchListGeneratorImpl : public ETMBranchListGenerator {
+ public:
+  ETMBranchListGeneratorImpl(bool dump_maps_from_proc)
+      : thread_tree_(dump_maps_from_proc), binary_filter_(nullptr) {}
+
+  void SetExcludePid(pid_t pid) override { thread_tree_.ExcludePid(pid); }
+  void SetBinaryFilter(const RegEx* binary_name_regex) override {
+    binary_filter_.SetRegex(binary_name_regex);
+  }
+
+  bool ProcessRecord(const Record& r, bool& consumed) override;
+  BranchListBinaryMap GetBranchListBinaryMap() override;
+
+ private:
+  struct AuxRecordData {
+    uint64_t start;
+    uint64_t end;
+    bool formatted;
+    AuxRecordData(uint64_t start, uint64_t end, bool formatted)
+        : start(start), end(end), formatted(formatted) {}
+  };
+
+  struct PerCpuData {
+    std::vector<uint8_t> aux_data;
+    uint64_t data_offset = 0;
+    std::queue<AuxRecordData> aux_records;
+  };
+
+  bool ProcessAuxRecord(const AuxRecord& r);
+  bool ProcessAuxTraceRecord(const AuxTraceRecord& r);
+  void ProcessBranchList(const ETMBranchList& branch_list);
+
+  ETMThreadTreeWhenRecording thread_tree_;
+  uint64_t kernel_map_start_addr_ = 0;
+  BinaryFilter binary_filter_;
+  std::map<uint32_t, PerCpuData> cpu_map_;
+  std::unique_ptr<ETMDecoder> etm_decoder_;
+  std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_;
+};
+
+bool ETMBranchListGeneratorImpl::ProcessRecord(const Record& r, bool& consumed) {
+  consumed = true;  // No need to store any records.
+  uint32_t type = r.type();
+  if (type == PERF_RECORD_AUXTRACE_INFO) {
+    etm_decoder_ = ETMDecoder::Create(*static_cast<const AuxTraceInfoRecord*>(&r), thread_tree_);
+    if (!etm_decoder_) {
+      return false;
+    }
+    etm_decoder_->RegisterCallback(
+        [this](const ETMBranchList& branch) { ProcessBranchList(branch); });
+    return true;
+  }
+  if (type == PERF_RECORD_AUX) {
+    return ProcessAuxRecord(*static_cast<const AuxRecord*>(&r));
+  }
+  if (type == PERF_RECORD_AUXTRACE) {
+    return ProcessAuxTraceRecord(*static_cast<const AuxTraceRecord*>(&r));
+  }
+  if (type == PERF_RECORD_MMAP && r.InKernel()) {
+    auto& mmap_r = *static_cast<const MmapRecord*>(&r);
+    if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) {
+      kernel_map_start_addr_ = mmap_r.data->addr;
+    }
+  }
+  thread_tree_.GetThreadTree().Update(r);
+  return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxRecord(const AuxRecord& r) {
+  OverflowResult result = SafeAdd(r.data->aux_offset, r.data->aux_size);
+  if (result.overflow || r.data->aux_size > SIZE_MAX) {
+    LOG(ERROR) << "invalid aux record";
+    return false;
+  }
+  size_t size = r.data->aux_size;
+  uint64_t start = r.data->aux_offset;
+  uint64_t end = result.value;
+  PerCpuData& data = cpu_map_[r.Cpu()];
+  if (start >= data.data_offset && end <= data.data_offset + data.aux_data.size()) {
+    // The ETM data is available. Process it now.
+    uint8_t* p = data.aux_data.data() + (start - data.data_offset);
+    if (!etm_decoder_) {
+      LOG(ERROR) << "ETMDecoder isn't created";
+      return false;
+    }
+    return etm_decoder_->ProcessData(p, size, !r.Unformatted(), r.Cpu());
+  }
+  // The ETM data isn't available. Put the aux record into queue.
+  data.aux_records.emplace(start, end, !r.Unformatted());
+  return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxTraceRecord(const AuxTraceRecord& r) {
+  OverflowResult result = SafeAdd(r.data->offset, r.data->aux_size);
+  if (result.overflow || r.data->aux_size > SIZE_MAX) {
+    LOG(ERROR) << "invalid auxtrace record";
+    return false;
+  }
+  size_t size = r.data->aux_size;
+  uint64_t start = r.data->offset;
+  uint64_t end = result.value;
+  PerCpuData& data = cpu_map_[r.Cpu()];
+  data.data_offset = start;
+  CHECK(r.location.addr != nullptr);
+  data.aux_data.resize(size);
+  memcpy(data.aux_data.data(), r.location.addr, size);
+
+  // Process cached aux records.
+  while (!data.aux_records.empty() && data.aux_records.front().start < end) {
+    const AuxRecordData& aux = data.aux_records.front();
+    if (aux.start >= start && aux.end <= end) {
+      uint8_t* p = data.aux_data.data() + (aux.start - start);
+      if (!etm_decoder_) {
+        LOG(ERROR) << "ETMDecoder isn't created";
+        return false;
+      }
+      if (!etm_decoder_->ProcessData(p, aux.end - aux.start, aux.formatted, r.Cpu())) {
+        return false;
+      }
+    }
+    data.aux_records.pop();
+  }
+  return true;
+}
+
+void ETMBranchListGeneratorImpl::ProcessBranchList(const ETMBranchList& branch_list) {
+  if (!binary_filter_.Filter(branch_list.dso)) {
+    return;
+  }
+  auto& branch_map = branch_list_binary_map_[branch_list.dso].branch_map;
+  ++branch_map[branch_list.addr][branch_list.branch];
+}
+
+BranchListBinaryMap ETMBranchListGeneratorImpl::GetBranchListBinaryMap() {
+  BranchListBinaryMap binary_map;
+  for (auto& p : branch_list_binary_map_) {
+    Dso* dso = p.first;
+    BranchListBinaryInfo& binary = p.second;
+    binary.dso_type = dso->type();
+    BuildId build_id;
+    GetBuildId(*dso, build_id);
+    BinaryKey key(dso->Path(), build_id);
+    if (binary.dso_type == DSO_KERNEL) {
+      if (kernel_map_start_addr_ == 0) {
+        LOG(WARNING) << "Can't convert kernel ip addresses without kernel start addr. So remove "
+                        "branches for the kernel.";
+        continue;
+      }
+      key.kernel_start_addr = kernel_map_start_addr_;
+    }
+    binary_map[key] = std::move(binary);
+  }
+  return binary_map;
+}
+
+std::unique_ptr<ETMBranchListGenerator> ETMBranchListGenerator::Create(bool dump_maps_from_proc) {
+  return std::unique_ptr<ETMBranchListGenerator>(
+      new ETMBranchListGeneratorImpl(dump_maps_from_proc));
+}
+
+ETMBranchListGenerator::~ETMBranchListGenerator() {}
+
+}  // namespace simpleperf
diff --git a/simpleperf/ETMBranchListFile.h b/simpleperf/ETMBranchListFile.h
new file mode 100644
index 0000000..fd595c5
--- /dev/null
+++ b/simpleperf/ETMBranchListFile.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include "ETMDecoder.h"
+#include "RegEx.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+namespace simpleperf {
+
+// When processing binary info in an input file, the binaries are identified by their path.
+// But this isn't sufficient when merging binary info from multiple input files. Because
+// binaries for the same path may be changed between generating input files. So after processing
+// each input file, we create BinaryKeys to identify binaries, which consider path, build_id and
+// kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in BranchListBinaryInfo
+// are interpreted for vmlinux.
+struct BinaryKey {
+  std::string path;
+  BuildId build_id;
+  uint64_t kernel_start_addr = 0;
+
+  BinaryKey() {}
+
+  BinaryKey(const std::string& path, BuildId build_id) : path(path), build_id(build_id) {}
+
+  BinaryKey(Dso* dso, uint64_t kernel_start_addr) : path(dso->Path()) {
+    build_id = Dso::FindExpectedBuildIdForPath(dso->Path());
+    if (dso->type() == DSO_KERNEL) {
+      this->kernel_start_addr = kernel_start_addr;
+    }
+  }
+
+  bool operator==(const BinaryKey& other) const {
+    return path == other.path && build_id == other.build_id &&
+           kernel_start_addr == other.kernel_start_addr;
+  }
+};
+
+struct BinaryKeyHash {
+  size_t operator()(const BinaryKey& key) const noexcept {
+    size_t seed = 0;
+    HashCombine(seed, key.path);
+    HashCombine(seed, key.build_id);
+    if (key.kernel_start_addr != 0) {
+      HashCombine(seed, key.kernel_start_addr);
+    }
+    return seed;
+  }
+};
+
+using UnorderedBranchMap =
+    std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>;
+
+struct BranchListBinaryInfo {
+  DsoType dso_type;
+  UnorderedBranchMap branch_map;
+
+  void Merge(const BranchListBinaryInfo& other) {
+    for (auto& other_p : other.branch_map) {
+      auto it = branch_map.find(other_p.first);
+      if (it == branch_map.end()) {
+        branch_map[other_p.first] = std::move(other_p.second);
+      } else {
+        auto& map2 = it->second;
+        for (auto& other_p2 : other_p.second) {
+          auto it2 = map2.find(other_p2.first);
+          if (it2 == map2.end()) {
+            map2[other_p2.first] = other_p2.second;
+          } else {
+            OverflowSafeAdd(it2->second, other_p2.second);
+          }
+        }
+      }
+    }
+  }
+
+  BranchMap GetOrderedBranchMap() const {
+    BranchMap result;
+    for (const auto& p : branch_map) {
+      uint64_t addr = p.first;
+      const auto& b_map = p.second;
+      result[addr] = std::map<std::vector<bool>, uint64_t>(b_map.begin(), b_map.end());
+    }
+    return result;
+  }
+};
+
+using BranchListBinaryMap = std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash>;
+
+bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::string& s);
+bool StringToBranchListBinaryMap(const std::string& s, BranchListBinaryMap& binary_map);
+
+class BinaryFilter {
+ public:
+  BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {}
+
+  void SetRegex(const RegEx* binary_name_regex) {
+    binary_name_regex_ = binary_name_regex;
+    dso_filter_cache_.clear();
+  }
+
+  bool Filter(Dso* dso) {
+    auto lookup = dso_filter_cache_.find(dso);
+    if (lookup != dso_filter_cache_.end()) {
+      return lookup->second;
+    }
+    bool match = Filter(dso->Path());
+    dso_filter_cache_.insert({dso, match});
+    return match;
+  }
+
+  bool Filter(const std::string& path) {
+    return binary_name_regex_ == nullptr || binary_name_regex_->Search(path);
+  }
+
+ private:
+  const RegEx* binary_name_regex_;
+  std::unordered_map<Dso*, bool> dso_filter_cache_;
+};
+
+// Convert ETM data into branch lists while recording.
+class ETMBranchListGenerator {
+ public:
+  static std::unique_ptr<ETMBranchListGenerator> Create(bool dump_maps_from_proc);
+
+  virtual ~ETMBranchListGenerator();
+  virtual void SetExcludePid(pid_t pid) = 0;
+  virtual void SetBinaryFilter(const RegEx* binary_name_regex) = 0;
+  virtual bool ProcessRecord(const Record& r, bool& consumed) = 0;
+  virtual BranchListBinaryMap GetBranchListBinaryMap() = 0;
+};
+
+// for testing
+std::string BranchToProtoString(const std::vector<bool>& branch);
+std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size);
+
+}  // namespace simpleperf
diff --git a/simpleperf/ETMBranchListFile_test.cpp b/simpleperf/ETMBranchListFile_test.cpp
new file mode 100644
index 0000000..227c68b
--- /dev/null
+++ b/simpleperf/ETMBranchListFile_test.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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 <gtest/gtest.h>
+
+#include "ETMBranchListFile.h"
+
+using namespace simpleperf;
+
+TEST(ETMBranchListFile, branch_to_proto_string) {
+  std::vector<bool> branch;
+  for (size_t i = 0; i < 100; i++) {
+    branch.push_back(i % 2 == 0);
+    std::string s = BranchToProtoString(branch);
+    for (size_t j = 0; j <= i; j++) {
+      bool b = s[j >> 3] & (1 << (j & 7));
+      ASSERT_EQ(b, branch[j]);
+    }
+    std::vector<bool> branch2 = ProtoStringToBranch(s, branch.size());
+    ASSERT_EQ(branch, branch2);
+  }
+}
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
index 62fe997..9cf4385 100644
--- a/simpleperf/ETMDecoder.cpp
+++ b/simpleperf/ETMDecoder.cpp
@@ -67,7 +67,10 @@
  public:
   ETMV4IDecodeTree()
       : error_logger_(std::bind(&ETMV4IDecodeTree::ProcessError, this, std::placeholders::_1)) {
-    frame_decoder_.Configure(OCSD_DFRMTR_FRAME_MEM_ALIGN);
+    ocsd_err_t err = frame_decoder_.Init();
+    CHECK_EQ(err, OCSD_OK);
+    err = frame_decoder_.Configure(OCSD_DFRMTR_FRAME_MEM_ALIGN);
+    CHECK_EQ(err, OCSD_OK);
     frame_decoder_.getErrLogAttachPt()->attach(&error_logger_);
   }
 
@@ -178,11 +181,9 @@
 // For each trace_id, when given an addr, find the thread and map it belongs to.
 class MapLocator : public PacketCallback {
  public:
-  MapLocator(ThreadTree& thread_tree)
+  MapLocator(ETMThreadTree& thread_tree)
       : PacketCallback(PacketCallback::MAP_LOCATOR), thread_tree_(thread_tree) {}
 
-  ThreadTree& GetThreadTree() { return thread_tree_; }
-
   // Return current thread id of a trace_id. If not available, return -1.
   pid_t GetTid(uint8_t trace_id) const { return trace_data_[trace_id].tid; }
 
@@ -242,7 +243,7 @@
     bool use_vmid = false;  // use vmid for PID
   };
 
-  ThreadTree& thread_tree_;
+  ETMThreadTree& thread_tree_;
   TraceData trace_data_[256];
 };
 
@@ -286,15 +287,22 @@
       // addr.
       if (!map->in_kernel) {
         data.buffer_map = map;
-        data.buffer = memory == nullptr ? nullptr : (memory->getBufferStart() + map->pgoff);
         data.buffer_start = map->start_addr;
         data.buffer_end = map->get_end_addr();
+        if (memory != nullptr && memory->getBufferSize() > map->pgoff &&
+            (memory->getBufferSize() - map->pgoff >= map->len)) {
+          data.buffer = memory->getBufferStart() + map->pgoff;
+        } else {
+          data.buffer = nullptr;
+        }
       }
     }
     *num_bytes = copy_size;
     return OCSD_OK;
   }
 
+  void InvalidateMemAccCache(const uint8_t cs_trace_id) override {}
+
  private:
   llvm::MemoryBuffer* GetMemoryBuffer(Dso* dso) {
     auto it = elf_map_.find(dso);
@@ -639,7 +647,11 @@
 // 2. Supports dumping data at different stages.
 class ETMDecoderImpl : public ETMDecoder {
  public:
-  ETMDecoderImpl(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+  ETMDecoderImpl(ETMThreadTree& thread_tree) : thread_tree_(thread_tree) {
+    // If the aux record for a thread is processed after it's thread exit record, we can't find
+    // the thread's maps when processing ETM data. To handle this, disable thread exit records.
+    thread_tree.DisableThreadExitRecords();
+  }
 
   void CreateDecodeTree(const AuxTraceInfoRecord& auxtrace_info) {
     uint8_t trace_id = 0;
@@ -793,7 +805,7 @@
   }
 
   // map ip address to binary path and binary offset
-  ThreadTree& thread_tree_;
+  ETMThreadTree& thread_tree_;
   // handle to build OpenCSD decoder
   ETMV4IDecodeTree decode_tree_;
   // map from cpu to trace id
@@ -830,7 +842,7 @@
 }
 
 std::unique_ptr<ETMDecoder> ETMDecoder::Create(const AuxTraceInfoRecord& auxtrace_info,
-                                               ThreadTree& thread_tree) {
+                                               ETMThreadTree& thread_tree) {
   auto decoder = std::make_unique<ETMDecoderImpl>(thread_tree);
   decoder->CreateDecodeTree(auxtrace_info);
   return std::unique_ptr<ETMDecoder>(decoder.release());
@@ -967,4 +979,4 @@
   return {};
 }
 
-}  // namespace simpleperf
\ No newline at end of file
+}  // namespace simpleperf
diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h
index 5f9daeb..e8ae7f7 100644
--- a/simpleperf/ETMDecoder.h
+++ b/simpleperf/ETMDecoder.h
@@ -61,10 +61,19 @@
   std::vector<bool> branch;
 };
 
+// ThreadTree interface used by ETMDecoder
+class ETMThreadTree {
+ public:
+  virtual ~ETMThreadTree() {}
+  virtual void DisableThreadExitRecords() = 0;
+  virtual const ThreadEntry* FindThread(int tid) = 0;
+  virtual const MapSet& GetKernelMaps() = 0;
+};
+
 class ETMDecoder {
  public:
   static std::unique_ptr<ETMDecoder> Create(const AuxTraceInfoRecord& auxtrace_info,
-                                            ThreadTree& thread_tree);
+                                            ETMThreadTree& thread_tree);
   virtual ~ETMDecoder() {}
   virtual void EnableDump(const ETMDumpOption& option) = 0;
 
diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp
index 2805894..2d4b132 100644
--- a/simpleperf/JITDebugReader.cpp
+++ b/simpleperf/JITDebugReader.cpp
@@ -47,7 +47,7 @@
 
 // If the size of a symfile is larger than EXPECTED_MAX_SYMFILE_SIZE, we don't want to read it
 // remotely.
-static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1024 * 1024u;
+static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1 * kMegabyte;
 
 // It takes about 30us-130us on Pixel (depending on the cpu frequency) to check if the descriptors
 // have been updated (most time spent in process_vm_preadv). We want to know if the JIT debug info
diff --git a/simpleperf/MapRecordReader.cpp b/simpleperf/MapRecordReader.cpp
index 8853e03..5731eb8 100644
--- a/simpleperf/MapRecordReader.cpp
+++ b/simpleperf/MapRecordReader.cpp
@@ -164,7 +164,10 @@
       PLOG(ERROR) << "fread() failed";
       return false;
     }
-    RecordHeader header(buffer.data());
+    RecordHeader header;
+    if (!header.Parse(buffer.data())) {
+      return false;
+    }
     if (buffer.size() < header.size) {
       buffer.resize(header.size);
     }
diff --git a/simpleperf/OfflineUnwinder.cpp b/simpleperf/OfflineUnwinder.cpp
index bbc488b..c847bca 100644
--- a/simpleperf/OfflineUnwinder.cpp
+++ b/simpleperf/OfflineUnwinder.cpp
@@ -27,16 +27,19 @@
 #include <unwindstack/MachineArm64.h>
 #include <unwindstack/MachineX86.h>
 #include <unwindstack/MachineX86_64.h>
+#include <unwindstack/MachineRiscv64.h>
 #include <unwindstack/Maps.h>
 #include <unwindstack/RegsArm.h>
 #include <unwindstack/RegsArm64.h>
 #include <unwindstack/RegsX86.h>
 #include <unwindstack/RegsX86_64.h>
+#include <unwindstack/RegsRiscv64.h>
 #include <unwindstack/Unwinder.h>
 #include <unwindstack/UserArm.h>
 #include <unwindstack/UserArm64.h>
 #include <unwindstack/UserX86.h>
 #include <unwindstack/UserX86_64.h>
+#include <unwindstack/UserRiscv64.h>
 
 #include "JITDebugReader.h"
 #include "OfflineUnwinder_impl.h"
@@ -140,6 +143,43 @@
       x86_64_user_regs.rip = regs.data[PERF_REG_X86_IP];
       return unwindstack::RegsX86_64::Read(&x86_64_user_regs);
     }
+    case ARCH_RISCV64: {
+      unwindstack::riscv64_user_regs riscv64_user_regs;
+      memset(&riscv64_user_regs, 0, sizeof(riscv64_user_regs));
+      riscv64_user_regs.regs[PERF_REG_RISCV_PC] = regs.data[PERF_REG_RISCV_PC];
+      riscv64_user_regs.regs[PERF_REG_RISCV_RA] = regs.data[PERF_REG_RISCV_RA];
+      riscv64_user_regs.regs[PERF_REG_RISCV_SP] = regs.data[PERF_REG_RISCV_SP];
+      riscv64_user_regs.regs[PERF_REG_RISCV_GP] = regs.data[PERF_REG_RISCV_GP];
+      riscv64_user_regs.regs[PERF_REG_RISCV_TP] = regs.data[PERF_REG_RISCV_TP];
+      riscv64_user_regs.regs[PERF_REG_RISCV_T0] = regs.data[PERF_REG_RISCV_T0];
+      riscv64_user_regs.regs[PERF_REG_RISCV_T1] = regs.data[PERF_REG_RISCV_T1];
+      riscv64_user_regs.regs[PERF_REG_RISCV_T2] = regs.data[PERF_REG_RISCV_T2];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S0] = regs.data[PERF_REG_RISCV_S0];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S1] = regs.data[PERF_REG_RISCV_S1];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A0] = regs.data[PERF_REG_RISCV_A0];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A1] = regs.data[PERF_REG_RISCV_A1];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A2] = regs.data[PERF_REG_RISCV_A2];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A3] = regs.data[PERF_REG_RISCV_A3];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A4] = regs.data[PERF_REG_RISCV_A4];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A5] = regs.data[PERF_REG_RISCV_A5];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A6] = regs.data[PERF_REG_RISCV_A6];
+      riscv64_user_regs.regs[PERF_REG_RISCV_A7] = regs.data[PERF_REG_RISCV_A7];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S2] = regs.data[PERF_REG_RISCV_S2];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S3] = regs.data[PERF_REG_RISCV_S3];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S4] = regs.data[PERF_REG_RISCV_S4];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S5] = regs.data[PERF_REG_RISCV_S5];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S6] = regs.data[PERF_REG_RISCV_S6];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S7] = regs.data[PERF_REG_RISCV_S7];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S8] = regs.data[PERF_REG_RISCV_S8];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S9] = regs.data[PERF_REG_RISCV_S9];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S10] = regs.data[PERF_REG_RISCV_S10];
+      riscv64_user_regs.regs[PERF_REG_RISCV_S11] = regs.data[PERF_REG_RISCV_S11];
+      riscv64_user_regs.regs[PERF_REG_RISCV_T3] = regs.data[PERF_REG_RISCV_T3];
+      riscv64_user_regs.regs[PERF_REG_RISCV_T4] = regs.data[PERF_REG_RISCV_T4];
+      riscv64_user_regs.regs[PERF_REG_RISCV_T5] = regs.data[PERF_REG_RISCV_T5];
+      riscv64_user_regs.regs[PERF_REG_RISCV_T6] = regs.data[PERF_REG_RISCV_T6];
+      return unwindstack::RegsRiscv64::Read(&riscv64_user_regs);
+    }
     default:
       return nullptr;
   }
diff --git a/simpleperf/ProbeEvents.cpp b/simpleperf/ProbeEvents.cpp
index 636923f..616bafe 100644
--- a/simpleperf/ProbeEvents.cpp
+++ b/simpleperf/ProbeEvents.cpp
@@ -19,7 +19,6 @@
 #include <inttypes.h>
 
 #include <memory>
-#include <regex>
 #include <string>
 
 #include <android-base/file.h>
@@ -29,6 +28,7 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
+#include "RegEx.h"
 #include "environment.h"
 #include "event_type.h"
 #include "utils.h"
@@ -55,14 +55,14 @@
 
   // Parse given name.
   event->group_name = "kprobes";
-  std::regex name_reg(R"(:([a-zA-Z_][\w_]*/)?([a-zA-Z_][\w_]*))");
-  std::smatch matches;
-  if (std::regex_search(args[0], matches, name_reg)) {
-    if (matches[1].length() > 0) {
-      event->group_name = matches[1].str();
+  auto name_reg = RegEx::Create(R"(:([a-zA-Z_][\w_]*/)?([a-zA-Z_][\w_]*))");
+  auto match = name_reg->SearchAll(args[0]);
+  if (match->IsValid()) {
+    if (match->GetField(1).length() > 0) {
+      event->group_name = match->GetField(1);
       event->group_name.pop_back();
     }
-    event->event_name = matches[2].str();
+    event->event_name = match->GetField(2);
     return true;
   }
 
@@ -88,7 +88,7 @@
     }
   }
   std::string s = StringPrintf("%c_%s_%" PRId64, probe_type, symbol.c_str(), offset);
-  event->event_name = std::regex_replace(s, std::regex(R"(\.|:)"), "_");
+  event->event_name = RegEx::Create(R"(\.|:)")->Replace(s, "_").value();
   return true;
 }
 
diff --git a/simpleperf/README.md b/simpleperf/README.md
index c1d2368..50fd243 100644
--- a/simpleperf/README.md
+++ b/simpleperf/README.md
@@ -1,6 +1,14 @@
 # Simpleperf
 
-This file is documentation for simpleperf maintainers.
+Android Studio includes a graphical front end to Simpleperf, documented in
+[Inspect CPU activity with CPU Profiler](https://developer.android.com/studio/profile/cpu-profiler).
+Most users will prefer to use that instead of using Simpleperf directly.
+
+If you prefer to use the command line, Simpleperf is a versatile command-line
+CPU profiling tool included in the NDK for Mac, Linux, and Windows.
+
+This file contains documentation for simpleperf maintainers.
+
 There is also [user documentation](doc/README.md).
 
 ## Building new prebuilts
diff --git a/simpleperf/RecordFilter.cpp b/simpleperf/RecordFilter.cpp
index 7818d32..3405428 100644
--- a/simpleperf/RecordFilter.cpp
+++ b/simpleperf/RecordFilter.cpp
@@ -248,8 +248,8 @@
 bool RecordFilter::ParseOptions(OptionValueMap& options) {
   for (bool exclude : {true, false}) {
     std::string prefix = exclude ? "--exclude-" : "--include-";
-    for (const OptionValue& value : options.PullValues(prefix + "pid")) {
-      if (auto pids = GetTidsFromString(*value.str_value, false); pids) {
+    if (auto strs = options.PullStringValues(prefix + "pid"); !strs.empty()) {
+      if (auto pids = GetPidsFromStrings(strs, false, false); pids) {
         AddPids(pids.value(), exclude);
       } else {
         return false;
@@ -263,10 +263,14 @@
       }
     }
     for (const OptionValue& value : options.PullValues(prefix + "process-name")) {
-      AddProcessNameRegex(*value.str_value, exclude);
+      if (!AddProcessNameRegex(*value.str_value, exclude)) {
+        return false;
+      }
     }
     for (const OptionValue& value : options.PullValues(prefix + "thread-name")) {
-      AddThreadNameRegex(*value.str_value, exclude);
+      if (!AddThreadNameRegex(*value.str_value, exclude)) {
+        return false;
+      }
     }
     for (const OptionValue& value : options.PullValues(prefix + "uid")) {
       if (auto uids = ParseUintVector<uint32_t>(*value.str_value); uids) {
@@ -296,16 +300,24 @@
   cond.tids.insert(tids.begin(), tids.end());
 }
 
-void RecordFilter::AddProcessNameRegex(const std::string& process_name, bool exclude) {
+bool RecordFilter::AddProcessNameRegex(const std::string& process_name, bool exclude) {
   RecordFilterCondition& cond = GetCondition(exclude);
   cond.used = true;
-  cond.process_name_regs.emplace_back(process_name, std::regex::optimize);
+  if (auto regex = RegEx::Create(process_name); regex != nullptr) {
+    cond.process_name_regs.emplace_back(std::move(regex));
+    return true;
+  }
+  return false;
 }
 
-void RecordFilter::AddThreadNameRegex(const std::string& thread_name, bool exclude) {
+bool RecordFilter::AddThreadNameRegex(const std::string& thread_name, bool exclude) {
   RecordFilterCondition& cond = GetCondition(exclude);
   cond.used = true;
-  cond.thread_name_regs.emplace_back(thread_name, std::regex::optimize);
+  if (auto regex = RegEx::Create(thread_name); regex != nullptr) {
+    cond.thread_name_regs.emplace_back(std::move(regex));
+    return true;
+  }
+  return false;
 }
 
 void RecordFilter::AddUids(const std::set<uint32_t>& uids, bool exclude) {
@@ -382,9 +394,10 @@
   return false;
 }
 
-bool RecordFilter::SearchInRegs(const std::string& s, const std::vector<std::regex>& regs) {
+bool RecordFilter::SearchInRegs(std::string_view s,
+                                const std::vector<std::unique_ptr<RegEx>>& regs) {
   for (auto& reg : regs) {
-    if (std::regex_search(s, reg)) {
+    if (reg->Search(s)) {
       return true;
     }
   }
diff --git a/simpleperf/RecordFilter.h b/simpleperf/RecordFilter.h
index 4c5ee58..cf413c1 100644
--- a/simpleperf/RecordFilter.h
+++ b/simpleperf/RecordFilter.h
@@ -19,12 +19,12 @@
 #include <sys/types.h>
 
 #include <optional>
-#include <regex>
 #include <set>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
+#include "RegEx.h"
 #include "command.h"
 #include "record.h"
 #include "thread_tree.h"
@@ -97,8 +97,8 @@
   bool used = false;
   std::set<pid_t> pids;
   std::set<pid_t> tids;
-  std::vector<std::regex> process_name_regs;
-  std::vector<std::regex> thread_name_regs;
+  std::vector<std::unique_ptr<RegEx>> process_name_regs;
+  std::vector<std::unique_ptr<RegEx>> thread_name_regs;
   std::set<uint32_t> uids;
 };
 
@@ -114,8 +114,8 @@
   bool ParseOptions(OptionValueMap& options);
   void AddPids(const std::set<pid_t>& pids, bool exclude);
   void AddTids(const std::set<pid_t>& tids, bool exclude);
-  void AddProcessNameRegex(const std::string& process_name, bool exclude);
-  void AddThreadNameRegex(const std::string& thread_name, bool exclude);
+  bool AddProcessNameRegex(const std::string& process_name, bool exclude);
+  bool AddThreadNameRegex(const std::string& thread_name, bool exclude);
   void AddUids(const std::set<uint32_t>& uids, bool exclude);
   bool SetFilterFile(const std::string& filename);
 
@@ -132,7 +132,7 @@
 
  private:
   bool CheckCondition(const SampleRecord* r, const RecordFilterCondition& condition);
-  bool SearchInRegs(const std::string& s, const std::vector<std::regex>& regs);
+  bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs);
   std::optional<uint32_t> GetUidForProcess(pid_t pid);
 
   const ThreadTree& thread_tree_;
diff --git a/simpleperf/RecordFilter_test.cpp b/simpleperf/RecordFilter_test.cpp
index 3099e45..88345ed 100644
--- a/simpleperf/RecordFilter_test.cpp
+++ b/simpleperf/RecordFilter_test.cpp
@@ -71,7 +71,7 @@
 }
 
 TEST_F(RecordFilterTest, exclude_process_name_regex) {
-  filter.AddProcessNameRegex("processA", true);
+  ASSERT_TRUE(filter.AddProcessNameRegex("processA", true));
   thread_tree.SetThreadName(1, 1, "processA1");
   thread_tree.SetThreadName(2, 2, "processB1");
   ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
@@ -79,7 +79,7 @@
 }
 
 TEST_F(RecordFilterTest, exclude_thread_name_regex) {
-  filter.AddThreadNameRegex("threadA", true);
+  ASSERT_TRUE(filter.AddThreadNameRegex("threadA", true));
   thread_tree.SetThreadName(1, 1, "processA_threadA");
   thread_tree.SetThreadName(1, 2, "processA_threadB");
   ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
@@ -109,7 +109,7 @@
 }
 
 TEST_F(RecordFilterTest, include_process_name_regex) {
-  filter.AddProcessNameRegex("processA", false);
+  ASSERT_TRUE(filter.AddProcessNameRegex("processA", false));
   thread_tree.SetThreadName(1, 1, "processA1");
   thread_tree.SetThreadName(2, 2, "processB1");
   ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
@@ -117,7 +117,7 @@
 }
 
 TEST_F(RecordFilterTest, include_thread_name_regex) {
-  filter.AddThreadNameRegex("threadA", false);
+  ASSERT_TRUE(filter.AddThreadNameRegex("threadA", false));
   thread_tree.SetThreadName(1, 1, "processA_threadA");
   thread_tree.SetThreadName(1, 2, "processA_threadB");
   ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
@@ -285,15 +285,15 @@
         filter_cmd.Run({prefix + "process-name", "processA", prefix + "process-name", "processB"}));
     auto& process_regs = filter.GetCondition(exclude).process_name_regs;
     ASSERT_EQ(process_regs.size(), 2);
-    ASSERT_TRUE(std::regex_match("processA", process_regs[0]));
-    ASSERT_TRUE(std::regex_match("processB", process_regs[1]));
+    ASSERT_TRUE(process_regs[0]->Match("processA"));
+    ASSERT_TRUE(process_regs[1]->Match("processB"));
 
     ASSERT_TRUE(
         filter_cmd.Run({prefix + "thread-name", "threadA", prefix + "thread-name", "threadB"}));
     auto& thread_regs = filter.GetCondition(exclude).thread_name_regs;
     ASSERT_EQ(thread_regs.size(), 2);
-    ASSERT_TRUE(std::regex_match("threadA", thread_regs[0]));
-    ASSERT_TRUE(std::regex_match("threadB", thread_regs[1]));
+    ASSERT_TRUE(thread_regs[0]->Match("threadA"));
+    ASSERT_TRUE(thread_regs[1]->Match("threadB"));
 
     ASSERT_TRUE(filter_cmd.Run({prefix + "uid", "1,2", prefix + "uid", "3"}));
     ASSERT_EQ(filter.GetCondition(exclude).uids, std::set<uint32_t>({1, 2, 3}));
diff --git a/simpleperf/RecordReadThread.cpp b/simpleperf/RecordReadThread.cpp
index 666be87..3e492ce 100644
--- a/simpleperf/RecordReadThread.cpp
+++ b/simpleperf/RecordReadThread.cpp
@@ -29,8 +29,8 @@
 
 namespace simpleperf {
 
-static constexpr size_t kDefaultLowBufferLevel = 10 * 1024 * 1024u;
-static constexpr size_t kDefaultCriticalBufferLevel = 5 * 1024 * 1024u;
+static constexpr size_t kDefaultLowBufferLevel = 10 * kMegabyte;
+static constexpr size_t kDefaultCriticalBufferLevel = 5 * kMegabyte;
 
 RecordBuffer::RecordBuffer(size_t buffer_size)
     : read_head_(0), write_head_(0), buffer_size_(buffer_size), buffer_(new char[buffer_size]) {}
@@ -236,6 +236,9 @@
   }
   record_buffer_low_level_ = std::min(record_buffer_size / 4, kDefaultLowBufferLevel);
   record_buffer_critical_level_ = std::min(record_buffer_size / 6, kDefaultCriticalBufferLevel);
+  LOG(VERBOSE) << "user buffer size = " << record_buffer_size
+               << ", low_level size = " << record_buffer_low_level_
+               << ", critical_level size = " << record_buffer_critical_level_;
   if (!allow_cutting_samples) {
     record_buffer_low_level_ = record_buffer_critical_level_;
   }
@@ -530,7 +533,7 @@
     if (free_size < record_buffer_critical_level_) {
       // When the free size in record buffer is below critical level, drop sample records to save
       // space for more important records (like mmap or fork records).
-      stat_.lost_samples++;
+      stat_.userspace_lost_samples++;
       return;
     }
     size_t stack_size_limit = stack_size_in_sample_record_;
@@ -577,10 +580,10 @@
           memcpy(p + pos + new_stack_size, &new_stack_size, sizeof(uint64_t));
           record_buffer_.FinishWrite();
           if (new_stack_size < dyn_stack_size) {
-            stat_.cut_stack_samples++;
+            stat_.userspace_cut_stack_samples++;
           }
         } else {
-          stat_.lost_samples++;
+          stat_.userspace_lost_samples++;
         }
         return;
       }
@@ -600,13 +603,18 @@
         // only after we have collected the aux data.
         event_fds_disabled_by_kernel_.insert(kernel_record_reader->GetEventFd());
       }
+    } else if (header.type == PERF_RECORD_LOST) {
+      LostRecord r;
+      if (r.Parse(attr_, p, p + header.size)) {
+        stat_.kernelspace_lost_records += static_cast<size_t>(r.lost);
+      }
     }
     record_buffer_.FinishWrite();
   } else {
     if (header.type == PERF_RECORD_SAMPLE) {
-      stat_.lost_samples++;
+      stat_.userspace_lost_samples++;
     } else {
-      stat_.lost_non_samples++;
+      stat_.userspace_lost_non_samples++;
     }
   }
 }
@@ -625,10 +633,11 @@
       *has_data = true;
       AuxTraceRecord auxtrace(Align(aux_size, 8), offset, event_fd->Cpu(), 0, event_fd->Cpu());
       size_t alloc_size = auxtrace.size() + auxtrace.data->aux_size;
-      if (record_buffer_.GetFreeSize() < alloc_size + record_buffer_critical_level_) {
+      char* p = nullptr;
+      if ((record_buffer_.GetFreeSize() < alloc_size + record_buffer_critical_level_) ||
+          (p = record_buffer_.AllocWriteSpace(alloc_size)) == nullptr) {
         stat_.lost_aux_data_size += aux_size;
       } else {
-        char* p = record_buffer_.AllocWriteSpace(alloc_size);
         CHECK(p != nullptr);
         MoveToBinaryFormat(auxtrace.Binary(), auxtrace.size(), p);
         MoveToBinaryFormat(buf[0], size[0], p);
diff --git a/simpleperf/RecordReadThread.h b/simpleperf/RecordReadThread.h
index 47dfbb2..658f9ea 100644
--- a/simpleperf/RecordReadThread.h
+++ b/simpleperf/RecordReadThread.h
@@ -90,9 +90,10 @@
 };
 
 struct RecordStat {
-  size_t lost_samples = 0;
-  size_t lost_non_samples = 0;
-  size_t cut_stack_samples = 0;
+  size_t kernelspace_lost_records = 0;
+  size_t userspace_lost_samples = 0;
+  size_t userspace_lost_non_samples = 0;
+  size_t userspace_cut_stack_samples = 0;
   uint64_t aux_data_size = 0;
   uint64_t lost_aux_data_size = 0;
 };
diff --git a/simpleperf/RecordReadThread_test.cpp b/simpleperf/RecordReadThread_test.cpp
index b61c9be..9917c65 100644
--- a/simpleperf/RecordReadThread_test.cpp
+++ b/simpleperf/RecordReadThread_test.cpp
@@ -77,7 +77,7 @@
   std::unique_ptr<RecordFileReader> reader =
       RecordFileReader::CreateInstance(GetTestData(PERF_DATA_NO_UNWIND));
   ASSERT_TRUE(reader);
-  RecordParser parser(*reader->AttrSection()[0].attr);
+  RecordParser parser(reader->AttrSection()[0].attr);
   auto process_record = [&](std::unique_ptr<Record> record) {
     if (record->type() == PERF_RECORD_MMAP || record->type() == PERF_RECORD_COMM ||
         record->type() == PERF_RECORD_FORK || record->type() == PERF_RECORD_SAMPLE) {
@@ -370,9 +370,9 @@
   thread.SetBufferLevels(record_buffer_size, record_buffer_size);
   read_record(r);
   ASSERT_FALSE(r);
-  ASSERT_EQ(thread.GetStat().lost_samples, 1u);
-  ASSERT_EQ(thread.GetStat().lost_non_samples, 0u);
-  ASSERT_EQ(thread.GetStat().cut_stack_samples, 1u);
+  ASSERT_EQ(thread.GetStat().userspace_lost_samples, 1u);
+  ASSERT_EQ(thread.GetStat().userspace_lost_non_samples, 0u);
+  ASSERT_EQ(thread.GetStat().userspace_cut_stack_samples, 1u);
 }
 
 // Test that the data notification exists until the RecordBuffer is empty. So we can read all
@@ -421,9 +421,9 @@
     received_samples++;
   }
   ASSERT_GT(received_samples, 0u);
-  ASSERT_GT(thread.GetStat().lost_samples, 0u);
-  ASSERT_EQ(thread.GetStat().lost_samples, total_samples - received_samples);
-  ASSERT_EQ(thread.GetStat().cut_stack_samples, 0u);
+  ASSERT_GT(thread.GetStat().userspace_lost_samples, 0u);
+  ASSERT_EQ(thread.GetStat().userspace_lost_samples, total_samples - received_samples);
+  ASSERT_EQ(thread.GetStat().userspace_cut_stack_samples, 0u);
 }
 
 TEST_F(RecordReadThreadTest, exclude_perf) {
@@ -476,16 +476,17 @@
 };
 
 TEST_F(RecordReadThreadTest, read_aux_data) {
+  ScopedEventTypes scoped_types("cs-etm,0,0");
   const EventType* type = FindEventTypeByName("cs-etm");
-  if (type == nullptr) {
-    GTEST_LOG_(INFO) << "Omit this test as cs-etm event type isn't available";
-    return;
-  }
+  ASSERT_TRUE(type != nullptr);
   std::vector<FakeAuxData> aux_data;
   aux_data.emplace_back(40, 0, '0', 0, false);   // one buffer
   aux_data.emplace_back(40, 40, '1', 0, false);  // two buffers
   aux_data.emplace_back(36, 0, '2', 4, false);   // one buffer needs padding to 8 bytes alignment
-  aux_data.emplace_back(1024, 0, '3', 0, true);  // one buffer too big to fit into RecordReadThread
+  // one buffer too big to fit in record buffer, failing at checking free size
+  aux_data.emplace_back(1024, 0, '3', 0, true);
+  // one buffer too big to fit in record buffer, failing at AllocWriteSpace()
+  aux_data.emplace_back(800, 0, '4', 0, true);
   size_t test_index = 0;
 
   auto SetBuf1 = [&](char** buf1) {
diff --git a/simpleperf/RegEx.cpp b/simpleperf/RegEx.cpp
new file mode 100644
index 0000000..97bb45b
--- /dev/null
+++ b/simpleperf/RegEx.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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 "RegEx.h"
+
+#include <regex>
+
+#include <android-base/logging.h>
+
+namespace simpleperf {
+
+RegExMatch::~RegExMatch() {}
+
+class RegExMatchImpl : public RegExMatch {
+ public:
+  RegExMatchImpl(std::string_view s, const std::regex& re)
+      : match_it_(s.data(), s.data() + s.size(), re) {}
+
+  bool IsValid() const override { return match_it_ != std::cregex_iterator(); }
+
+  std::string GetField(size_t index) const override { return match_it_->str(index); }
+
+  void MoveToNextMatch() override { ++match_it_; }
+
+ private:
+  std::cregex_iterator match_it_;
+};
+
+class RegExImpl : public RegEx {
+ public:
+  RegExImpl(std::string_view pattern)
+      : RegEx(pattern), re_(pattern_, std::regex::ECMAScript | std::regex::optimize) {}
+
+  bool Match(std::string_view s) const override {
+    return std::regex_match(s.begin(), s.end(), re_);
+  }
+  bool Search(std::string_view s) const override {
+    return std::regex_search(s.begin(), s.end(), re_);
+  }
+  std::unique_ptr<RegExMatch> SearchAll(std::string_view s) const override {
+    return std::unique_ptr<RegExMatch>(new RegExMatchImpl(s, re_));
+  }
+  std::optional<std::string> Replace(const std::string& s,
+                                     const std::string& format) const override {
+    try {
+      return {std::regex_replace(s, re_, format)};
+    } catch (std::regex_error& e) {
+      LOG(ERROR) << "regex_error: " << e.what() << ", pattern " << pattern_ << ", format "
+                 << format;
+      return std::nullopt;
+    }
+  }
+
+ private:
+  std::regex re_;
+};
+
+std::unique_ptr<RegEx> RegEx::Create(std::string_view pattern) {
+  try {
+    return std::make_unique<RegExImpl>(pattern);
+  } catch (std::regex_error& e) {
+    LOG(ERROR) << "regex_error: " << e.what() << ", pattern " << pattern;
+    return nullptr;
+  }
+}
+
+}  // namespace simpleperf
diff --git a/simpleperf/RegEx.h b/simpleperf/RegEx.h
new file mode 100644
index 0000000..c1e4f51
--- /dev/null
+++ b/simpleperf/RegEx.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace simpleperf {
+
+class RegExMatch {
+ public:
+  virtual ~RegExMatch();
+  virtual bool IsValid() const = 0;
+  virtual std::string GetField(size_t index) const = 0;
+  virtual void MoveToNextMatch() = 0;
+};
+
+// A wrapper of std::regex, converting std::regex_error exception into return value.
+class RegEx {
+ public:
+  static std::unique_ptr<RegEx> Create(std::string_view pattern);
+  virtual ~RegEx() {}
+  const std::string& GetPattern() const { return pattern_; }
+
+  virtual bool Match(std::string_view s) const = 0;
+  virtual bool Search(std::string_view s) const = 0;
+  // Always return a not-null RegExMatch. If no match, RegExMatch->IsValid() is false.
+  virtual std::unique_ptr<RegExMatch> SearchAll(std::string_view s) const = 0;
+  virtual std::optional<std::string> Replace(const std::string& s,
+                                             const std::string& format) const = 0;
+
+ protected:
+  RegEx(std::string_view pattern) : pattern_(pattern) {}
+
+  std::string pattern_;
+};
+
+}  // namespace simpleperf
diff --git a/simpleperf/RegEx_test.cpp b/simpleperf/RegEx_test.cpp
new file mode 100644
index 0000000..cbb25ae
--- /dev/null
+++ b/simpleperf/RegEx_test.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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 "RegEx.h"
+
+#include <gtest/gtest.h>
+
+using namespace simpleperf;
+
+TEST(RegEx, smoke) {
+  auto re = RegEx::Create("b+");
+  ASSERT_EQ(re->GetPattern(), "b+");
+  ASSERT_FALSE(re->Search("aaa"));
+  ASSERT_TRUE(re->Search("aba"));
+  ASSERT_FALSE(re->Match("aba"));
+  ASSERT_TRUE(re->Match("bbb"));
+
+  auto match = re->SearchAll("aaa");
+  ASSERT_FALSE(match->IsValid());
+  match = re->SearchAll("ababb");
+  ASSERT_TRUE(match->IsValid());
+  ASSERT_EQ(match->GetField(0), "b");
+  match->MoveToNextMatch();
+  ASSERT_TRUE(match->IsValid());
+  ASSERT_EQ(match->GetField(0), "bb");
+  match->MoveToNextMatch();
+  ASSERT_FALSE(match->IsValid());
+
+  ASSERT_EQ(re->Replace("ababb", "c").value(), "acac");
+}
+
+TEST(RegEx, invalid_pattern) {
+  ASSERT_TRUE(RegEx::Create("?hello") == nullptr);
+}
diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
index a95a404..aa840d8 100644
--- a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
+++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
@@ -132,7 +132,7 @@
      */
     public synchronized void startRecording(@NonNull List<String> args) {
         if (mState != State.NOT_YET_STARTED) {
-            throw new AssertionError("startRecording: session in wrong state " + mState);
+            throw new IllegalStateException("startRecording: session in wrong state " + mState);
         }
         for (String arg : args) {
             if (arg.equals("--trace-offcpu")) {
@@ -151,7 +151,7 @@
      */
     public synchronized void pauseRecording() {
         if (mState != State.STARTED) {
-            throw new AssertionError("pauseRecording: session in wrong state " + mState);
+            throw new IllegalStateException("pauseRecording: session in wrong state " + mState);
         }
         if (mTraceOffCpu) {
             throw new AssertionError(
@@ -166,7 +166,7 @@
      */
     public synchronized void resumeRecording() {
         if (mState != State.PAUSED) {
-            throw new AssertionError("resumeRecording: session in wrong state " + mState);
+            throw new IllegalStateException("resumeRecording: session in wrong state " + mState);
         }
         sendCmd("resume");
         mState = State.STARTED;
@@ -177,7 +177,7 @@
      */
     public synchronized void stopRecording() {
         if (mState != State.STARTED && mState != State.PAUSED) {
-            throw new AssertionError("stopRecording: session in wrong state " + mState);
+            throw new IllegalStateException("stopRecording: session in wrong state " + mState);
         }
         if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P + 1
                 && mSimpleperfPath.equals(SIMPLEPERF_PATH_IN_IMAGE)) {
diff --git a/simpleperf/cmd_api.cpp b/simpleperf/cmd_api.cpp
index c407edf..f3ce2bb 100644
--- a/simpleperf/cmd_api.cpp
+++ b/simpleperf/cmd_api.cpp
@@ -17,7 +17,6 @@
 #include <stdio.h>
 
 #include <memory>
-#include <regex>
 #include <string>
 #include <thread>
 #include <vector>
@@ -30,6 +29,7 @@
 #include <android-base/unique_fd.h>
 #include <ziparchive/zip_writer.h>
 
+#include "RegEx.h"
 #include "cmd_api_impl.h"
 #include "command.h"
 #include "environment.h"
@@ -126,19 +126,15 @@
     PLOG(ERROR) << "failed to run `pm list packages -U`";
     return std::nullopt;
   }
-  std::regex re(R"(package:([\w\.]+)\s+uid:(\d+))");
-  std::sregex_iterator match_it(content.begin(), content.end(), re);
-  std::sregex_iterator match_end;
-  while (match_it != match_end) {
-    std::smatch match = *match_it++;
-    std::string name = match.str(1);
+  auto re = RegEx::Create(R"(package:([\w\.]+)\s+uid:(\d+))");
+  auto match = re->SearchAll(content);
+  while (match->IsValid()) {
+    std::string name = match->GetField(1);
     uint32_t uid;
-    if (!android::base::ParseUint(match.str(2), &uid)) {
-      continue;
-    }
-    if (name == app_name_) {
+    if (name == app_name_ && android::base::ParseUint(match->GetField(2), &uid)) {
       return uid;
     }
+    match->MoveToNextMatch();
   }
   LOG(ERROR) << "failed to find package " << app_name_;
   return std::nullopt;
diff --git a/simpleperf/cmd_debug_unwind.cpp b/simpleperf/cmd_debug_unwind.cpp
index 560a183..52d3c96 100644
--- a/simpleperf/cmd_debug_unwind.cpp
+++ b/simpleperf/cmd_debug_unwind.cpp
@@ -146,7 +146,9 @@
     }
 
     // 2. Load feature sections.
-    reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+    if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+      return false;
+    }
     ScopedCurrentArch scoped_arch(
         GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH)));
     unwinder_->LoadMetaInfo(reader_->GetMetaInfoFeature());
@@ -401,12 +403,13 @@
   bool WriteMapsForSample(const SampleRecord& r) {
     ThreadEntry* thread = thread_tree_.FindThread(r.tid_data.tid);
     if (thread != nullptr && thread->maps) {
-      auto attr = reader_->AttrSection()[0].attr;
-      auto event_id = reader_->AttrSection()[0].ids[0];
+      const EventAttrIds& attrs = reader_->AttrSection();
+      const perf_event_attr& attr = attrs[0].attr;
+      uint64_t event_id = attrs[0].ids[0];
 
       for (const auto& p : thread->maps->maps) {
         const MapEntry* map = p.second;
-        Mmap2Record map_record(*attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
+        Mmap2Record map_record(attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
                                map->len, map->pgoff, map->flags, map->dso->Path(), event_id,
                                r.Timestamp());
         if (!writer_->WriteRecord(map_record)) {
@@ -423,7 +426,7 @@
     }
     std::unordered_set<int> feature_types_to_copy = {
         PerfFileFormat::FEAT_ARCH, PerfFileFormat::FEAT_CMDLINE, PerfFileFormat::FEAT_META_INFO};
-    const size_t BUFFER_SIZE = 64 * 1024;
+    const size_t BUFFER_SIZE = 64 * kKilobyte;
     std::string buffer(BUFFER_SIZE, '\0');
     for (const auto& p : reader_->FeatureSectionDescriptors()) {
       auto feat_type = p.first;
@@ -445,13 +448,17 @@
         }
       } else if (feat_type == PerfFileFormat::FEAT_FILE ||
                  feat_type == PerfFileFormat::FEAT_FILE2) {
-        size_t read_pos = 0;
+        uint64_t read_pos = 0;
         FileFeature file_feature;
-        while (reader_->ReadFileFeature(read_pos, &file_feature)) {
+        bool error = false;
+        while (reader_->ReadFileFeature(read_pos, file_feature, error)) {
           if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) {
             return false;
           }
         }
+        if (error) {
+          return false;
+        }
       } else if (feat_type == PerfFileFormat::FEAT_BUILD_ID) {
         std::vector<BuildIdRecord> build_ids = reader_->ReadBuildIdFeature();
         std::vector<BuildIdRecord> write_build_ids;
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index 142ae5c..0b8df15 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <inttypes.h>
+#include <stdint.h>
 
 #include <map>
 #include <string>
@@ -25,6 +26,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#include "ETMBranchListFile.h"
 #include "ETMDecoder.h"
 #include "command.h"
 #include "dso.h"
@@ -177,6 +179,18 @@
   return ExtractUnknownField;
 }
 
+class ETMThreadTreeForDumpCmd : public ETMThreadTree {
+ public:
+  ETMThreadTreeForDumpCmd(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+
+  void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+  const ThreadEntry* FindThread(int tid) override { return thread_tree_.FindThread(tid); }
+  const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+  ThreadTree& thread_tree_;
+};
+
 class DumpRecordCommand : public Command {
  public:
   DumpRecordCommand()
@@ -201,7 +215,7 @@
   void ProcessSampleRecord(const SampleRecord& r);
   void ProcessCallChainRecord(const CallChainRecord& r);
   SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel);
-  void ProcessTracingData(const TracingDataRecord& r);
+  bool ProcessTracingData(const TracingDataRecord& r);
   bool DumpAuxData(const AuxRecord& aux);
   bool DumpFeatureSection();
 
@@ -211,6 +225,7 @@
 
   std::unique_ptr<RecordFileReader> record_file_reader_;
   std::unique_ptr<ETMDecoder> etm_decoder_;
+  std::unique_ptr<ETMThreadTree> etm_thread_tree_;
   ThreadTree thread_tree_;
 
   std::vector<EventInfo> events_;
@@ -305,11 +320,11 @@
 }
 
 void DumpRecordCommand::DumpAttrSection() {
-  std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+  const EventAttrIds& attrs = record_file_reader_->AttrSection();
   for (size_t i = 0; i < attrs.size(); ++i) {
     const auto& attr = attrs[i];
     printf("attr %zu:\n", i + 1);
-    DumpPerfEventAttr(*attr.attr, 1);
+    DumpPerfEventAttr(attr.attr, 1);
     if (!attr.ids.empty()) {
       printf("  ids:");
       for (const auto& id : attr.ids) {
@@ -322,7 +337,9 @@
 
 bool DumpRecordCommand::DumpDataSection() {
   thread_tree_.ShowIpForUnknownSymbol();
-  record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+  if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+    return false;
+  }
 
   auto record_callback = [&](std::unique_ptr<Record> r) { return ProcessRecord(r.get()); };
   return record_file_reader_->ReadDataSection(record_callback);
@@ -341,7 +358,8 @@
       ProcessCallChainRecord(*static_cast<CallChainRecord*>(r));
       break;
     case PERF_RECORD_AUXTRACE_INFO: {
-      etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
+      etm_thread_tree_.reset(new ETMThreadTreeForDumpCmd(thread_tree_));
+      etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), *etm_thread_tree_);
       if (etm_decoder_) {
         etm_decoder_->EnableDump(etm_dump_option_);
       } else {
@@ -355,7 +373,7 @@
     }
     case PERF_RECORD_TRACING_DATA:
     case SIMPLE_PERF_RECORD_TRACING_DATA: {
-      ProcessTracingData(*static_cast<TracingDataRecord*>(r));
+      res = ProcessTracingData(*static_cast<TracingDataRecord*>(r));
       break;
     }
   }
@@ -413,29 +431,41 @@
 }
 
 bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux) {
+  if (aux.data->aux_size > SIZE_MAX) {
+    LOG(ERROR) << "invalid aux size";
+    return false;
+  }
   size_t size = aux.data->aux_size;
   if (size > 0) {
-    std::unique_ptr<uint8_t[]> data(new uint8_t[size]);
-    if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, data.get(), size)) {
+    std::vector<uint8_t> data;
+    bool error = false;
+    if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, size, data, error)) {
+      return !error;
+    }
+    if (!etm_decoder_) {
+      LOG(ERROR) << "ETMDecoder isn't created";
       return false;
     }
-    return etm_decoder_->ProcessData(data.get(), size, !aux.Unformatted(), aux.Cpu());
+    return etm_decoder_->ProcessData(data.data(), size, !aux.Unformatted(), aux.Cpu());
   }
   return true;
 }
 
-void DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
-  Tracing tracing(std::vector<char>(r.data, r.data + r.data_size));
-  std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+bool DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
+  auto tracing = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
+  if (!tracing) {
+    return false;
+  }
+  const EventAttrIds& attrs = record_file_reader_->AttrSection();
   events_.resize(attrs.size());
   for (size_t i = 0; i < attrs.size(); i++) {
     auto& attr = attrs[i].attr;
     auto& event = events_[i];
 
-    if (attr->type != PERF_TYPE_TRACEPOINT) {
+    if (attr.type != PERF_TYPE_TRACEPOINT) {
       continue;
     }
-    TracingFormat format = tracing.GetTracingFormatHavingId(attr->config);
+    TracingFormat format = tracing->GetTracingFormatHavingId(attr.config);
     event.tp_fields = format.fields;
     // Decide dump function for each field.
     for (size_t j = 0; j < event.tp_fields.size(); j++) {
@@ -444,6 +474,7 @@
       event.tp_data_size += field.elem_count * field.elem_size;
     }
   }
+  return true;
 }
 
 bool DumpRecordCommand::DumpFeatureSection() {
@@ -469,9 +500,10 @@
       PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str());
     } else if (feature == FEAT_FILE || feature == FEAT_FILE2) {
       FileFeature file;
-      size_t read_pos = 0;
+      uint64_t read_pos = 0;
+      bool error = false;
       PrintIndented(1, "file:\n");
-      while (record_file_reader_->ReadFileFeature(read_pos, &file)) {
+      while (record_file_reader_->ReadFileFeature(read_pos, file, error)) {
         PrintIndented(2, "file_path %s\n", file.path.c_str());
         PrintIndented(2, "file_type %s\n", DsoTypeToString(file.type));
         PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", file.min_vaddr);
@@ -488,6 +520,9 @@
           }
         }
       }
+      if (error) {
+        return false;
+      }
     } else if (feature == FEAT_META_INFO) {
       PrintIndented(1, "meta_info:\n");
       for (auto& pair : record_file_reader_->GetMetaInfoFeature()) {
@@ -506,6 +541,35 @@
           PrintIndented(2, "size: %" PRIu64 "\n", file.size);
         }
       }
+    } else if (feature == FEAT_ETM_BRANCH_LIST) {
+      std::string data;
+      if (!record_file_reader_->ReadFeatureSection(FEAT_ETM_BRANCH_LIST, &data)) {
+        return false;
+      }
+      BranchListBinaryMap binary_map;
+      if (!StringToBranchListBinaryMap(data, binary_map)) {
+        return false;
+      }
+      PrintIndented(1, "etm_branch_list:\n");
+      for (const auto& [key, binary] : binary_map) {
+        PrintIndented(2, "path: %s\n", key.path.c_str());
+        PrintIndented(2, "build_id: %s\n", key.build_id.ToString().c_str());
+        PrintIndented(2, "binary_type: %s\n", DsoTypeToString(binary.dso_type));
+        if (binary.dso_type == DSO_KERNEL) {
+          PrintIndented(2, "kernel_start_addr: 0x%" PRIx64 "\n", key.kernel_start_addr);
+        }
+        for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) {
+          PrintIndented(3, "addr: 0x%" PRIx64 "\n", addr);
+          for (const auto& [branch, count] : branches) {
+            std::string s = "0b";
+            for (auto it = branch.rbegin(); it != branch.rend(); ++it) {
+              s.push_back(*it ? '1' : '0');
+            }
+            PrintIndented(3, "branch: %s\n", s.c_str());
+            PrintIndented(3, "count: %" PRIu64 "\n", count);
+          }
+        }
+      }
     }
   }
   return true;
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
index b2c573d..d3f66cd 100644
--- a/simpleperf/cmd_inject.cpp
+++ b/simpleperf/cmd_inject.cpp
@@ -14,19 +14,20 @@
  * limitations under the License.
  */
 
+#include <stdint.h>
 #include <stdio.h>
 #include <unistd.h>
 
 #include <memory>
 #include <optional>
-#include <regex>
 #include <string>
 
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 
+#include "ETMBranchListFile.h"
 #include "ETMDecoder.h"
-#include "cmd_inject_impl.h"
+#include "RegEx.h"
 #include "command.h"
 #include "record_file.h"
 #include "system/extras/simpleperf/etm_branch_list.pb.h"
@@ -35,31 +36,8 @@
 
 namespace simpleperf {
 
-std::string BranchToProtoString(const std::vector<bool>& branch) {
-  size_t bytes = (branch.size() + 7) / 8;
-  std::string res(bytes, '\0');
-  for (size_t i = 0; i < branch.size(); i++) {
-    if (branch[i]) {
-      res[i >> 3] |= 1 << (i & 7);
-    }
-  }
-  return res;
-}
-
-std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) {
-  std::vector<bool> branch(bit_size, false);
-  for (size_t i = 0; i < bit_size; i++) {
-    if (s[i >> 3] & (1 << (i & 7))) {
-      branch[i] = true;
-    }
-  }
-  return branch;
-}
-
 namespace {
 
-constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
-
 using AddrPair = std::pair<uint64_t, uint64_t>;
 
 struct AddrPairHash {
@@ -76,53 +54,6 @@
   BranchList,
 };
 
-// When processing binary info in an input file, the binaries are identified by their path.
-// But this isn't sufficient when merging binary info from multiple input files. Because
-// binaries for the same path may be changed between generating input files. So after processing
-// each input file, we create BinaryKeys to identify binaries, which consider path, build_id and
-// kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in BranchListBinaryInfo
-// are interpreted for vmlinux.
-struct BinaryKey {
-  std::string path;
-  BuildId build_id;
-  uint64_t kernel_start_addr = 0;
-
-  BinaryKey() {}
-
-  BinaryKey(const std::string& path, BuildId build_id) : path(path), build_id(build_id) {}
-
-  BinaryKey(Dso* dso, uint64_t kernel_start_addr) : path(dso->Path()) {
-    build_id = Dso::FindExpectedBuildIdForPath(dso->Path());
-    if (dso->type() == DSO_KERNEL) {
-      this->kernel_start_addr = kernel_start_addr;
-    }
-  }
-
-  bool operator==(const BinaryKey& other) const {
-    return path == other.path && build_id == other.build_id &&
-           kernel_start_addr == other.kernel_start_addr;
-  }
-};
-
-struct BinaryKeyHash {
-  size_t operator()(const BinaryKey& key) const noexcept {
-    size_t seed = 0;
-    HashCombine(seed, key.path);
-    HashCombine(seed, key.build_id);
-    if (key.kernel_start_addr != 0) {
-      HashCombine(seed, key.kernel_start_addr);
-    }
-    return seed;
-  }
-};
-
-static void OverflowSafeAdd(uint64_t& dest, uint64_t add) {
-  if (__builtin_add_overflow(dest, add, &dest)) {
-    LOG(WARNING) << "Branch count overflow happened.";
-    dest = UINT64_MAX;
-  }
-}
-
 struct AutoFDOBinaryInfo {
   uint64_t first_load_segment_addr = 0;
   std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
@@ -155,78 +86,50 @@
   }
 };
 
-using UnorderedBranchMap =
-    std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>;
-
-struct BranchListBinaryInfo {
-  DsoType dso_type;
-  UnorderedBranchMap branch_map;
-
-  void Merge(const BranchListBinaryInfo& other) {
-    for (auto& other_p : other.branch_map) {
-      auto it = branch_map.find(other_p.first);
-      if (it == branch_map.end()) {
-        branch_map[other_p.first] = std::move(other_p.second);
-      } else {
-        auto& map2 = it->second;
-        for (auto& other_p2 : other_p.second) {
-          auto it2 = map2.find(other_p2.first);
-          if (it2 == map2.end()) {
-            map2[other_p2.first] = other_p2.second;
-          } else {
-            OverflowSafeAdd(it2->second, other_p2.second);
-          }
-        }
-      }
-    }
-  }
-
-  BranchMap GetOrderedBranchMap() const {
-    BranchMap result;
-    for (const auto& p : branch_map) {
-      uint64_t addr = p.first;
-      const auto& b_map = p.second;
-      result[addr] = std::map<std::vector<bool>, uint64_t>(b_map.begin(), b_map.end());
-    }
-    return result;
-  }
-};
-
 using AutoFDOBinaryCallback = std::function<void(const BinaryKey&, AutoFDOBinaryInfo&)>;
 using BranchListBinaryCallback = std::function<void(const BinaryKey&, BranchListBinaryInfo&)>;
 
-class ThreadTreeWithFilter : public ThreadTree {
+class ETMThreadTreeWithFilter : public ETMThreadTree {
  public:
   void ExcludePid(pid_t pid) { exclude_pid_ = pid; }
+  ThreadTree& GetThreadTree() { return thread_tree_; }
+  void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
 
-  ThreadEntry* FindThread(int tid) const override {
-    ThreadEntry* thread = ThreadTree::FindThread(tid);
+  const ThreadEntry* FindThread(int tid) override {
+    const ThreadEntry* thread = thread_tree_.FindThread(tid);
     if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) {
       return nullptr;
     }
     return thread;
   }
 
+  const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
  private:
+  ThreadTree thread_tree_;
   std::optional<pid_t> exclude_pid_;
 };
 
-class DsoFilter {
+class BinaryFilter {
  public:
-  DsoFilter(const std::regex& binary_name_regex) : binary_name_regex_(binary_name_regex) {}
+  BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {}
 
-  bool FilterDso(Dso* dso) {
+  bool Filter(Dso* dso) {
     auto lookup = dso_filter_cache_.find(dso);
     if (lookup != dso_filter_cache_.end()) {
       return lookup->second;
     }
-    bool match = std::regex_search(dso->Path(), binary_name_regex_);
+    bool match = Filter(dso->Path());
     dso_filter_cache_.insert({dso, match});
     return match;
   }
 
+  bool Filter(const std::string& path) {
+    return binary_name_regex_ == nullptr || binary_name_regex_->Search(path);
+  }
+
  private:
-  std::regex binary_name_regex_;
+  const RegEx* binary_name_regex_;
   std::unordered_map<Dso*, bool> dso_filter_cache_;
 };
 
@@ -247,11 +150,11 @@
 class PerfDataReader {
  public:
   PerfDataReader(const std::string& filename, bool exclude_perf, ETMDumpOption etm_dump_option,
-                 const std::regex& binary_name_regex)
+                 const RegEx* binary_name_regex)
       : filename_(filename),
         exclude_perf_(exclude_perf),
         etm_dump_option_(etm_dump_option),
-        dso_filter_(binary_name_regex) {}
+        binary_filter_(binary_name_regex) {}
 
   void SetCallback(const AutoFDOBinaryCallback& callback) { autofdo_callback_ = callback; }
   void SetCallback(const BranchListBinaryCallback& callback) { branch_list_callback_ = callback; }
@@ -261,6 +164,9 @@
     if (!record_file_reader_) {
       return false;
     }
+    if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) {
+      return ProcessETMBranchListFeature();
+    }
     if (exclude_perf_) {
       const auto& info_map = record_file_reader_->GetMetaInfoFeature();
       if (auto it = info_map.find("recording_process"); it == info_map.end()) {
@@ -275,7 +181,9 @@
         thread_tree_.ExcludePid(pid);
       }
     }
-    record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+    if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_.GetThreadTree())) {
+      return false;
+    }
     if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) {
       return false;
     }
@@ -291,8 +199,34 @@
   }
 
  private:
+  bool ProcessETMBranchListFeature() {
+    if (exclude_perf_) {
+      LOG(WARNING) << "--exclude-perf has no effect on perf.data with etm branch list";
+    }
+    if (autofdo_callback_) {
+      LOG(ERROR) << "convert to autofdo format isn't support on perf.data with etm branch list";
+      return false;
+    }
+    CHECK(branch_list_callback_);
+    std::string s;
+    if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_ETM_BRANCH_LIST, &s)) {
+      return false;
+    }
+    BranchListBinaryMap binary_map;
+    if (!StringToBranchListBinaryMap(s, binary_map)) {
+      return false;
+    }
+    for (auto& [key, binary] : binary_map) {
+      if (!binary_filter_.Filter(key.path)) {
+        continue;
+      }
+      branch_list_callback_(key, binary);
+    }
+    return true;
+  }
+
   bool ProcessRecord(Record* r) {
-    thread_tree_.Update(*r);
+    thread_tree_.GetThreadTree().Update(*r);
     if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
       etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
       if (!etm_decoder_) {
@@ -308,14 +242,19 @@
       }
     } else if (r->type() == PERF_RECORD_AUX) {
       AuxRecord* aux = static_cast<AuxRecord*>(r);
-      uint64_t aux_size = aux->data->aux_size;
+      if (aux->data->aux_size > SIZE_MAX) {
+        LOG(ERROR) << "invalid aux size";
+        return false;
+      }
+      size_t aux_size = aux->data->aux_size;
       if (aux_size > 0) {
-        if (aux_data_buffer_.size() < aux_size) {
-          aux_data_buffer_.resize(aux_size);
+        bool error = false;
+        if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset, aux_size,
+                                              aux_data_buffer_, error)) {
+          return !error;
         }
-        if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset,
-                                              aux_data_buffer_.data(), aux_size)) {
-          LOG(ERROR) << "failed to read aux data in " << filename_;
+        if (!etm_decoder_) {
+          LOG(ERROR) << "ETMDecoder isn't created";
           return false;
         }
         return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size, !aux->Unformatted(),
@@ -331,7 +270,7 @@
   }
 
   void ProcessInstrRange(const ETMInstrRange& instr_range) {
-    if (!dso_filter_.FilterDso(instr_range.dso)) {
+    if (!binary_filter_.Filter(instr_range.dso)) {
       return;
     }
 
@@ -339,7 +278,7 @@
   }
 
   void ProcessBranchList(const ETMBranchList& branch_list) {
-    if (!dso_filter_.FilterDso(branch_list.dso)) {
+    if (!binary_filter_.Filter(branch_list.dso)) {
       return;
     }
 
@@ -381,14 +320,14 @@
   const std::string filename_;
   bool exclude_perf_;
   ETMDumpOption etm_dump_option_;
-  DsoFilter dso_filter_;
+  BinaryFilter binary_filter_;
   AutoFDOBinaryCallback autofdo_callback_;
   BranchListBinaryCallback branch_list_callback_;
 
   std::vector<uint8_t> aux_data_buffer_;
   std::unique_ptr<ETMDecoder> etm_decoder_;
   std::unique_ptr<RecordFileReader> record_file_reader_;
-  ThreadTreeWithFilter thread_tree_;
+  ETMThreadTreeWithFilter thread_tree_;
   uint64_t kernel_map_start_addr_ = 0;
   // Store results for AutoFDO.
   std::unordered_map<Dso*, AutoFDOBinaryInfo> autofdo_binary_map_;
@@ -399,82 +338,34 @@
 // Read a protobuf file specified by etm_branch_list.proto, and generate BranchListBinaryInfo.
 class BranchListReader {
  public:
-  BranchListReader(const std::string& filename, const std::regex binary_name_regex)
-      : filename_(filename), binary_name_regex_(binary_name_regex) {}
+  BranchListReader(const std::string& filename, const RegEx* binary_name_regex)
+      : filename_(filename), binary_filter_(binary_name_regex) {}
 
   void SetCallback(const BranchListBinaryCallback& callback) { callback_ = callback; }
 
   bool Read() {
-    auto fd = FileHelper::OpenReadOnly(filename_);
-    if (!fd.ok()) {
-      PLOG(ERROR) << "failed to open " << filename_;
+    std::string s;
+    if (!android::base::ReadFileToString(filename_, &s)) {
+      PLOG(ERROR) << "failed to read " << filename_;
       return false;
     }
-
-    proto::ETMBranchList branch_list_proto;
-    if (!branch_list_proto.ParseFromFileDescriptor(fd)) {
-      PLOG(ERROR) << "failed to read msg from " << filename_;
+    BranchListBinaryMap binary_map;
+    if (!StringToBranchListBinaryMap(s, binary_map)) {
+      PLOG(ERROR) << "file is in wrong format: " << filename_;
       return false;
     }
-    if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
-      PLOG(ERROR) << "file not in format etm_branch_list.proto: " << filename_;
-      return false;
-    }
-
-    for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
-      const auto& binary_proto = branch_list_proto.binaries(i);
-      if (!std::regex_search(binary_proto.path(), binary_name_regex_)) {
+    for (auto& [key, binary] : binary_map) {
+      if (!binary_filter_.Filter(key.path)) {
         continue;
       }
-      BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
-      if (binary_proto.has_kernel_info()) {
-        key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
-      }
-      BranchListBinaryInfo binary;
-      auto dso_type = ToDsoType(binary_proto.type());
-      if (!dso_type) {
-        LOG(ERROR) << "invalid binary type in " << filename_;
-        return false;
-      }
-      binary.dso_type = dso_type.value();
-      binary.branch_map = BuildUnorderedBranchMap(binary_proto);
       callback_(key, binary);
     }
     return true;
   }
 
  private:
-  std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) {
-    switch (binary_type) {
-      case proto::ETMBranchList_Binary::ELF_FILE:
-        return DSO_ELF_FILE;
-      case proto::ETMBranchList_Binary::KERNEL:
-        return DSO_KERNEL;
-      case proto::ETMBranchList_Binary::KERNEL_MODULE:
-        return DSO_KERNEL_MODULE;
-      default:
-        LOG(ERROR) << "unexpected binary type " << binary_type;
-        return std::nullopt;
-    }
-  }
-
-  UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
-    UnorderedBranchMap branch_map;
-    for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
-      const auto& addr_proto = binary_proto.addrs(i);
-      auto& b_map = branch_map[addr_proto.addr()];
-      for (size_t j = 0; j < addr_proto.branches_size(); j++) {
-        const auto& branch_proto = addr_proto.branches(j);
-        std::vector<bool> branch =
-            ProtoStringToBranch(branch_proto.branch(), branch_proto.branch_size());
-        b_map[branch] = branch_proto.count();
-      }
-    }
-    return branch_map;
-  }
-
   const std::string filename_;
-  const std::regex binary_name_regex_;
+  BinaryFilter binary_filter_;
   BranchListBinaryCallback callback_;
 };
 
@@ -610,6 +501,7 @@
       }
 
       // Write the binary path in comment.
+      fprintf(output_fp.get(), "// build_id: %s\n", key.build_id.ToString().c_str());
       fprintf(output_fp.get(), "// %s\n\n", key.path.c_str());
     }
     return true;
@@ -630,84 +522,30 @@
     }
   }
 
-  std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash> binary_map;
+  BranchListBinaryMap binary_map;
 };
 
 // Write branch lists to a protobuf file specified by etm_branch_list.proto.
 class BranchListWriter {
  public:
-  bool Write(const std::string& output_filename,
-             const std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash>& binary_map) {
+  bool Write(const std::string& output_filename, const BranchListBinaryMap& binary_map) {
     // Don't produce empty output file.
     if (binary_map.empty()) {
       LOG(INFO) << "Skip empty output file.";
       unlink(output_filename.c_str());
       return true;
     }
-    std::unique_ptr<FILE, decltype(&fclose)> output_fp(fopen(output_filename.c_str(), "wb"),
-                                                       fclose);
-    if (!output_fp) {
-      PLOG(ERROR) << "failed to write to " << output_filename;
+    std::string s;
+    if (!BranchListBinaryMapToString(binary_map, s)) {
+      LOG(ERROR) << "invalid BranchListBinaryMap";
       return false;
     }
-
-    proto::ETMBranchList branch_list_proto;
-    branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
-    std::vector<char> branch_buf;
-    for (const auto& p : binary_map) {
-      const BinaryKey& key = p.first;
-      const BranchListBinaryInfo& binary = p.second;
-      auto binary_proto = branch_list_proto.add_binaries();
-
-      binary_proto->set_path(key.path);
-      if (!key.build_id.IsEmpty()) {
-        binary_proto->set_build_id(key.build_id.ToString().substr(2));
-      }
-      auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
-      if (!opt_binary_type.has_value()) {
-        return false;
-      }
-      binary_proto->set_type(opt_binary_type.value());
-
-      for (const auto& addr_p : binary.branch_map) {
-        auto addr_proto = binary_proto->add_addrs();
-        addr_proto->set_addr(addr_p.first);
-
-        for (const auto& branch_p : addr_p.second) {
-          const std::vector<bool>& branch = branch_p.first;
-          auto branch_proto = addr_proto->add_branches();
-
-          branch_proto->set_branch(BranchToProtoString(branch));
-          branch_proto->set_branch_size(branch.size());
-          branch_proto->set_count(branch_p.second);
-        }
-      }
-
-      if (binary.dso_type == DSO_KERNEL) {
-        binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
-      }
-    }
-    if (!branch_list_proto.SerializeToFileDescriptor(fileno(output_fp.get()))) {
+    if (!android::base::WriteStringToFile(s, output_filename)) {
       PLOG(ERROR) << "failed to write to " << output_filename;
       return false;
     }
     return true;
   }
-
- private:
-  std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) {
-    switch (dso_type) {
-      case DSO_ELF_FILE:
-        return proto::ETMBranchList_Binary::ELF_FILE;
-      case DSO_KERNEL:
-        return proto::ETMBranchList_Binary::KERNEL;
-      case DSO_KERNEL_MODULE:
-        return proto::ETMBranchList_Binary::KERNEL_MODULE;
-      default:
-        LOG(ERROR) << "unexpected dso type " << dso_type;
-        return std::nullopt;
-    }
-  }
 };
 
 class InjectCommand : public Command {
@@ -783,7 +621,10 @@
     }
 
     if (auto value = options.PullValue("--binary"); value) {
-      binary_name_regex_ = *value->str_value;
+      binary_name_regex_ = RegEx::Create(*value->str_value);
+      if (binary_name_regex_ == nullptr) {
+        return false;
+      }
     }
     if (auto value = options.PullValue("--dump-etm"); value) {
       if (!ParseEtmDumpOption(*value->str_value, &etm_dump_option_)) {
@@ -849,7 +690,8 @@
       autofdo_writer.AddAutoFDOBinary(key, binary);
     };
     for (const auto& input_filename : input_filenames_) {
-      PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_, binary_name_regex_);
+      PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_,
+                            binary_name_regex_.get());
       reader.SetCallback(callback);
       if (!reader.Read()) {
         return false;
@@ -864,7 +706,8 @@
       branch_list_merger.AddBranchListBinary(key, binary);
     };
     for (const auto& input_filename : input_filenames_) {
-      PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_, binary_name_regex_);
+      PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_,
+                            binary_name_regex_.get());
       reader.SetCallback(callback);
       if (!reader.Read()) {
         return false;
@@ -881,7 +724,7 @@
       branch_list_merger.AddBranchListBinary(key, binary);
     };
     for (const auto& input_filename : input_filenames_) {
-      BranchListReader reader(input_filename, binary_name_regex_);
+      BranchListReader reader(input_filename, binary_name_regex_.get());
       reader.SetCallback(callback);
       if (!reader.Read()) {
         return false;
@@ -913,7 +756,7 @@
       branch_list_merger.AddBranchListBinary(key, binary);
     };
     for (const auto& input_filename : input_filenames_) {
-      BranchListReader reader(input_filename, binary_name_regex_);
+      BranchListReader reader(input_filename, binary_name_regex_.get());
       reader.SetCallback(callback);
       if (!reader.Read()) {
         return false;
@@ -924,7 +767,7 @@
     return branch_list_writer.Write(output_filename_, branch_list_merger.binary_map);
   }
 
-  std::regex binary_name_regex_{""};  // Default to match everything.
+  std::unique_ptr<RegEx> binary_name_regex_;
   bool exclude_perf_ = false;
   std::vector<std::string> input_filenames_;
   std::string output_filename_ = "perf_inject.data";
diff --git a/simpleperf/cmd_inject_impl.h b/simpleperf/cmd_inject_impl.h
deleted file mode 100644
index 455a9d1..0000000
--- a/simpleperf/cmd_inject_impl.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-namespace simpleperf {
-
-std::string BranchToProtoString(const std::vector<bool>& branch);
-
-std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size);
-
-}  // namespace simpleperf
\ No newline at end of file
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
index c93a15d..23347b9 100644
--- a/simpleperf/cmd_inject_test.cpp
+++ b/simpleperf/cmd_inject_test.cpp
@@ -18,7 +18,6 @@
 #include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 
-#include "cmd_inject_impl.h"
 #include "command.h"
 #include "get_test_data.h"
 #include "test_util.h"
@@ -101,20 +100,6 @@
   CheckMatchingExpectedData(autofdo_data);
 }
 
-TEST(cmd_inject, branch_to_proto_string) {
-  std::vector<bool> branch;
-  for (size_t i = 0; i < 100; i++) {
-    branch.push_back(i % 2 == 0);
-    std::string s = BranchToProtoString(branch);
-    for (size_t j = 0; j <= i; j++) {
-      bool b = s[j >> 3] & (1 << (j & 7));
-      ASSERT_EQ(b, branch[j]);
-    }
-    std::vector<bool> branch2 = ProtoStringToBranch(s, branch.size());
-    ASSERT_EQ(branch, branch2);
-  }
-}
-
 TEST(cmd_inject, skip_empty_output_file) {
   TemporaryFile tmpfile;
   close(tmpfile.release());
@@ -159,7 +144,7 @@
 
 TEST(cmd_inject, multiple_input_files) {
   std::string data;
-  std::string perf_data = GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf.data");
+  std::string perf_data = GetTestData(PERF_DATA_ETM_TEST_LOOP);
   std::string perf_with_unformatted_trace =
       GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_with_unformatted_trace.data");
 
@@ -195,7 +180,6 @@
 
 TEST(cmd_inject, report_warning_when_overflow) {
   CapturedStderr capture;
-  capture.Start();
   std::vector<std::unique_ptr<TemporaryFile>> branch_list_files;
   std::vector<std::unique_ptr<TemporaryFile>> input_files;
 
@@ -232,3 +216,11 @@
   ASSERT_NE(capture.str().find(WARNING_MSG), std::string::npos);
   ASSERT_NE(autofdo_data.find("106c->1074:18446744073709551615"), std::string::npos);
 }
+
+TEST(cmd_inject, accept_missing_aux_data) {
+  // Recorded with "-e cs-etm:u --user-buffer-size 64k sleep 1".
+  std::string perf_data = GetTestData("etm/perf_with_missing_aux_data.data");
+  TemporaryFile tmpfile;
+  close(tmpfile.release());
+  ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-i", perf_data, "-o", tmpfile.path}));
+}
diff --git a/simpleperf/cmd_kmem.cpp b/simpleperf/cmd_kmem.cpp
index c756eb3..febc651 100644
--- a/simpleperf/cmd_kmem.cpp
+++ b/simpleperf/cmd_kmem.cpp
@@ -314,7 +314,7 @@
   bool ReadFeaturesFromRecordFile();
   bool ReadSampleTreeFromRecordFile();
   bool ProcessRecord(std::unique_ptr<Record> record);
-  void ProcessTracingData(const std::vector<char>& data);
+  bool ProcessTracingData(const std::vector<char>& data);
   bool PrintReport();
   void PrintReportContext(FILE* fp);
   void PrintSlabReportContext(FILE* fp);
@@ -532,10 +532,9 @@
 }
 
 void KmemCommand::ReadEventAttrsFromRecordFile() {
-  std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
-  for (const auto& attr_with_id : attrs) {
+  for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
     EventAttrWithName attr;
-    attr.attr = *attr_with_id.attr;
+    attr.attr = attr_with_id.attr;
     attr.event_ids = attr_with_id.ids;
     attr.name = GetEventNameByAttr(attr.attr);
     event_attrs_.push_back(attr);
@@ -543,7 +542,9 @@
 }
 
 bool KmemCommand::ReadFeaturesFromRecordFile() {
-  record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+  if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+    return false;
+  }
   std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
   if (!arch.empty()) {
     record_file_arch_ = GetArchType(arch);
@@ -588,18 +589,23 @@
   } else if (record->type() == PERF_RECORD_TRACING_DATA ||
              record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) {
     const auto& r = *static_cast<TracingDataRecord*>(record.get());
-    ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size));
+    if (!ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size))) {
+      return false;
+    }
   }
   return true;
 }
 
-void KmemCommand::ProcessTracingData(const std::vector<char>& data) {
-  Tracing tracing(data);
+bool KmemCommand::ProcessTracingData(const std::vector<char>& data) {
+  auto tracing = Tracing::Create(data);
+  if (!tracing) {
+    return false;
+  }
   for (auto& attr : event_attrs_) {
     if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
       uint64_t trace_event_id = attr.attr.config;
-      attr.name = tracing.GetTracingEventNameHavingId(trace_event_id);
-      TracingFormat format = tracing.GetTracingFormatHavingId(trace_event_id);
+      attr.name = tracing->GetTracingEventNameHavingId(trace_event_id);
+      TracingFormat format = tracing->GetTracingFormatHavingId(trace_event_id);
       if (use_slab_) {
         if (format.name == "kmalloc" || format.name == "kmem_cache_alloc" ||
             format.name == "kmalloc_node" || format.name == "kmem_cache_alloc_node") {
@@ -621,6 +627,7 @@
       }
     }
   }
+  return true;
 }
 
 bool KmemCommand::PrintReport() {
diff --git a/simpleperf/cmd_merge.cpp b/simpleperf/cmd_merge.cpp
index e80dd95..1bf9833 100644
--- a/simpleperf/cmd_merge.cpp
+++ b/simpleperf/cmd_merge.cpp
@@ -255,16 +255,16 @@
 
   // Check attr sections to know if recorded event types are the same.
   bool CheckAttrSection() {
-    std::vector<EventAttrWithId> attrs0 = readers_[0]->AttrSection();
+    const EventAttrIds& attrs0 = readers_[0]->AttrSection();
     for (size_t i = 1; i < readers_.size(); i++) {
-      std::vector<EventAttrWithId> attrs = readers_[i]->AttrSection();
+      const EventAttrIds& attrs = readers_[i]->AttrSection();
       if (attrs.size() != attrs0.size()) {
         LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
                    << " are not mergeable for recording different event types";
         return false;
       }
       for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
-        if (memcmp(attrs[attr_id].attr, attrs0[attr_id].attr, sizeof(perf_event_attr)) != 0) {
+        if (attrs[attr_id].attr != attrs0[attr_id].attr) {
           LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
                      << " are not mergeable for recording different event types";
           return false;
@@ -300,7 +300,7 @@
     // map event_ids in readers_[next_read_id] to event attrs. The map info is put into an
     // EventIdRecord.
     const std::unordered_map<uint64_t, size_t>& cur_map = readers_[prev_reader_id]->EventIdMap();
-    std::vector<EventAttrWithId> attrs = readers_[next_reader_id]->AttrSection();
+    const EventAttrIds& attrs = readers_[next_reader_id]->AttrSection();
     std::vector<uint64_t> event_id_data;
     for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
       for (size_t event_id : attrs[attr_id].ids) {
@@ -381,8 +381,9 @@
     // Read file features.
     for (auto& reader : readers_) {
       FileFeature file;
-      size_t read_pos = 0;
-      while (reader->ReadFileFeature(read_pos, &file)) {
+      uint64_t read_pos = 0;
+      bool error = false;
+      while (reader->ReadFileFeature(read_pos, file, error)) {
         if (files_to_drop.count(file.path) != 0) {
           continue;
         }
@@ -395,6 +396,9 @@
           files_to_drop.emplace(file.path);
         }
       }
+      if (error) {
+        return false;
+      }
     }
     // Write file features.
     for (const auto& [file_path, file] : file_map) {
diff --git a/simpleperf/cmd_monitor.cpp b/simpleperf/cmd_monitor.cpp
index d81ccfe..2ef9cbc 100644
--- a/simpleperf/cmd_monitor.cpp
+++ b/simpleperf/cmd_monitor.cpp
@@ -80,8 +80,8 @@
 // buffer size on a 8 core system. For system-wide recording, it is 8K pages *
 // 4K page_size * 8 cores = 256MB. For non system-wide recording, it is 1K pages
 // * 4K page_size * 8 cores = 64MB.
-static constexpr size_t kRecordBufferSize = 64 * 1024 * 1024;
-static constexpr size_t kSystemWideRecordBufferSize = 256 * 1024 * 1024;
+static constexpr size_t kRecordBufferSize = 64 * kMegabyte;
+static constexpr size_t kSystemWideRecordBufferSize = 256 * kMegabyte;
 
 class MonitorCommand : public Command {
  public:
@@ -252,7 +252,7 @@
 
   // Use first perf_event_attr and first event id to dump mmap and comm records.
   EventAttrWithId dumping_attr_id = event_selection_set_.GetEventAttrWithId()[0];
-  map_record_reader_.emplace(*dumping_attr_id.attr, dumping_attr_id.ids[0],
+  map_record_reader_.emplace(dumping_attr_id.attr, dumping_attr_id.ids[0],
                              event_selection_set_.RecordNotExecutableMaps());
   map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); });
 
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index e69355d..d60e2d0 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -49,6 +49,7 @@
 #include <unwindstack/Error.h>
 
 #include "CallChainJoiner.h"
+#include "ETMBranchListFile.h"
 #include "ETMRecorder.h"
 #include "IOEventLoop.h"
 #include "JITDebugReader.h"
@@ -98,23 +99,17 @@
 
 // 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;
+static constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
 
 // The max allowed pages in mapped buffer is decided by rlimit(RLIMIT_MEMLOCK).
 // Here 1024 is a desired value for pages in mapped buffer. If mapped
 // successfully, the buffer size = 1024 * 4K (page size) = 4M.
-constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
+static constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
 
 // Cache size used by CallChainJoiner to cache call chains in memory.
-constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * 1024 * 1024;
+static constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * kMegabyte;
 
-// Currently, the record buffer size in user-space is set to match the kernel buffer size on a
-// 8 core system. For system-wide recording, it is 8K pages * 4K page_size * 8 cores = 256MB.
-// For non system-wide recording, it is 1K pages * 4K page_size * 8 cores = 64MB.
-static constexpr size_t kRecordBufferSize = 64 * 1024 * 1024;
-static constexpr size_t kSystemWideRecordBufferSize = 256 * 1024 * 1024;
-
-static constexpr size_t kDefaultAuxBufferSize = 4 * 1024 * 1024;
+static constexpr size_t kDefaultAuxBufferSize = 4 * kMegabyte;
 
 // On Pixel 3, it takes about 1ms to enable ETM, and 16-40ms to disable ETM and copy 4M ETM data.
 // So make default period to 100ms.
@@ -128,6 +123,30 @@
   uint64_t post_process_time = 0;
 };
 
+std::optional<size_t> GetDefaultRecordBufferSize(bool system_wide_recording) {
+  // Currently, the record buffer size in user-space is set to match the kernel buffer size on a
+  // 8 core system. For system-wide recording, it is 8K pages * 4K page_size * 8 cores = 256MB.
+  // For non system-wide recording, it is 1K pages * 4K page_size * 8 cores = 64MB.
+  // But on devices with memory >= 4GB, we increase buffer size to 256MB. This reduces the chance
+  // of cutting samples, which can cause broken callchains.
+  static constexpr size_t kLowMemoryRecordBufferSize = 64 * kMegabyte;
+  static constexpr size_t kHighMemoryRecordBufferSize = 256 * kMegabyte;
+  static constexpr size_t kSystemWideRecordBufferSize = 256 * kMegabyte;
+  // Ideally we can use >= 4GB here. But the memory size shown in /proc/meminfo is like to be 3.x GB
+  // on a device with 4GB memory. So we have to use <= 3GB.
+  static constexpr uint64_t kLowMemoryLimit = 3 * kGigabyte;
+
+  if (system_wide_recording) {
+    return kSystemWideRecordBufferSize;
+  }
+  auto device_memory = GetMemorySize();
+  if (!device_memory.has_value()) {
+    return std::nullopt;
+  }
+  return device_memory.value() <= kLowMemoryLimit ? kLowMemoryRecordBufferSize
+                                                  : kHighMemoryRecordBufferSize;
+}
+
 class RecordCommand : public Command {
  public:
   RecordCommand()
@@ -146,8 +165,9 @@
 "                      On non-rooted devices, the app must be debuggable,\n"
 "                      because we use run-as to switch to the app's context.\n"
 #endif
-"-p pid1,pid2,...       Record events on existing processes. Mutually exclusive\n"
-"                       with -a.\n"
+"-p pid_or_process_name_regex1,pid_or_process_name_regex2,...\n"
+"                      Record events on existing processes. Processes are searched either by pid\n"
+"                      or process name regex. Mutually exclusive with -a.\n"
 "-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
 "\n"
 "Select monitored event types:\n"
@@ -215,32 +235,14 @@
 "             This option requires at least one branch type among any, any_call,\n"
 "             any_ret, ind_call.\n"
 "-b           Enable taken branch stack sampling. Same as '-j any'.\n"
-"-m mmap_pages   Set the size of the buffer used to receiving sample data from\n"
-"                the kernel. It should be a power of 2. If not set, the max\n"
-"                possible value <= 1024 will be used.\n"
-"--aux-buffer-size <buffer_size>  Set aux buffer size, only used in cs-etm event type.\n"
-"                                 Need to be power of 2 and page size aligned.\n"
-"                                 Used memory size is (buffer_size * (cpu_count + 1).\n"
-"                                 Default is 4M.\n"
+"-m mmap_pages   Set pages used in the kernel to cache sample data for each cpu.\n"
+"                It should be a power of 2. If not set, the max possible value <= 1024\n"
+"                will be used.\n"
+"--user-buffer-size <buffer_size> Set buffer size in userspace to cache sample data.\n"
+"                                 By default, it is %s.\n"
 "--no-inherit  Don't record created child threads/processes.\n"
 "--cpu-percent <percent>  Set the max percent of cpu time used for recording.\n"
 "                         percent is in range [1-100], default is 25.\n"
-"--addr-filter filter_str1,filter_str2,...\n"
-"                Provide address filters for cs-etm instruction tracing.\n"
-"                filter_str accepts below formats:\n"
-"                  'filter  <addr-range>'  -- trace instructions in a range\n"
-"                  'start <addr>'          -- start tracing when ip is <addr>\n"
-"                  'stop <addr>'           -- stop tracing when ip is <addr>\n"
-"                <addr-range> accepts below formats:\n"
-"                  <file_path>                            -- code sections in a binary file\n"
-"                  <vaddr_start>-<vaddr_end>@<file_path>  -- part of a binary file\n"
-"                  <kernel_addr_start>-<kernel_addr_end>  -- part of kernel space\n"
-"                <addr> accepts below formats:\n"
-"                  <vaddr>@<file_path>      -- virtual addr in a binary file\n"
-"                  <kernel_addr>            -- a kernel address\n"
-"                Examples:\n"
-"                  'filter 0x456-0x480@/system/lib/libc.so'\n"
-"                  'start 0x456@/system/lib/libc.so,stop 0x480@/system/lib/libc.so'\n"
 "\n"
 "--tp-filter filter_string    Set filter_string for the previous tracepoint event.\n"
 "                             Format is in Documentation/trace/events.rst in the kernel.\n"
@@ -287,6 +289,31 @@
 "                 debug information, which are used for unwinding and dumping symbols.\n"
 "--add-meta-info key=value     Add extra meta info, which will be stored in the recording file.\n"
 "\n"
+"ETM recording options:\n"
+"--addr-filter filter_str1,filter_str2,...\n"
+"                Provide address filters for cs-etm instruction tracing.\n"
+"                filter_str accepts below formats:\n"
+"                  'filter  <addr-range>'  -- trace instructions in a range\n"
+"                  'start <addr>'          -- start tracing when ip is <addr>\n"
+"                  'stop <addr>'           -- stop tracing when ip is <addr>\n"
+"                <addr-range> accepts below formats:\n"
+"                  <file_path>                            -- code sections in a binary file\n"
+"                  <vaddr_start>-<vaddr_end>@<file_path>  -- part of a binary file\n"
+"                  <kernel_addr_start>-<kernel_addr_end>  -- part of kernel space\n"
+"                <addr> accepts below formats:\n"
+"                  <vaddr>@<file_path>      -- virtual addr in a binary file\n"
+"                  <kernel_addr>            -- a kernel address\n"
+"                Examples:\n"
+"                  'filter 0x456-0x480@/system/lib/libc.so'\n"
+"                  'start 0x456@/system/lib/libc.so,stop 0x480@/system/lib/libc.so'\n"
+"--aux-buffer-size <buffer_size>  Set aux buffer size, only used in cs-etm event type.\n"
+"                                 Need to be power of 2 and page size aligned.\n"
+"                                 Used memory size is (buffer_size * (cpu_count + 1).\n"
+"                                 Default is 4M.\n"
+"--decode-etm                     Convert ETM data into branch lists while recording.\n"
+"--binary binary_name             Used with --decode-etm to only generate data for binaries\n"
+"                                 matching binary_name regex.\n"
+"\n"
 "Other options:\n"
 "--exit-with-parent            Stop recording when the thread starting simpleperf dies.\n"
 "--use-cmd-exit-code           Exit with the same exit code as the monitored cmdline.\n"
@@ -319,7 +346,6 @@
         mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)),
         record_filename_("perf.data"),
         sample_record_count_(0),
-        lost_record_count_(0),
         in_app_context_(false),
         trace_offcpu_(false),
         exclude_kernel_callchain_(false),
@@ -334,6 +360,7 @@
     signal(SIGPIPE, SIG_IGN);
   }
 
+  std::string LongHelpString() const override;
   void Run(const std::vector<std::string>& args, int* exit_code) override;
   bool Run(const std::vector<std::string>& args) override {
     int exit_code;
@@ -352,8 +379,8 @@
   bool TraceOffCpu();
   bool SetEventSelectionFlags();
   bool CreateAndInitRecordFile();
-  std::unique_ptr<RecordFileWriter> CreateRecordFile(
-      const std::string& filename, const std::vector<EventAttrWithId>& override_attrs);
+  std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename,
+                                                     const EventAttrIds& attrs);
   bool DumpKernelSymbol();
   bool DumpTracingData();
   bool DumpMaps();
@@ -384,6 +411,7 @@
   bool DumpMetaInfoFeature(bool kernel_symbols_available);
   bool DumpDebugUnwindFeature(const std::unordered_set<Dso*>& dso_set);
   void CollectHitFileInfo(const SampleRecord& r, std::unordered_set<Dso*>* dso_set);
+  bool DumpETMBranchListFeature();
 
   std::unique_ptr<SampleSpeed> sample_speed_;
   bool system_wide_collection_;
@@ -405,6 +433,7 @@
   EventSelectionSet event_selection_set_;
 
   std::pair<size_t, size_t> mmap_page_range_;
+  std::optional<size_t> user_buffer_size_;
   size_t aux_buffer_size_ = kDefaultAuxBufferSize;
 
   ThreadTree thread_tree_;
@@ -414,7 +443,6 @@
   android::base::unique_fd stop_signal_fd_;
 
   uint64_t sample_record_count_;
-  uint64_t lost_record_count_;
   android::base::unique_fd start_profiling_fd_;
   bool stdio_controls_profiling_ = false;
 
@@ -447,8 +475,32 @@
   std::unordered_map<std::string, std::string> extra_meta_info_;
   bool use_cmd_exit_code_ = false;
   std::vector<std::string> add_counters_;
+
+  std::unique_ptr<ETMBranchListGenerator> etm_branch_list_generator_;
+  std::unique_ptr<RegEx> binary_name_regex_;
 };
 
+std::string RecordCommand::LongHelpString() const {
+  uint64_t process_buffer_size = 0;
+  uint64_t system_wide_buffer_size = 0;
+  if (auto size = GetDefaultRecordBufferSize(false); size) {
+    process_buffer_size = size.value() / kMegabyte;
+  }
+  if (auto size = GetDefaultRecordBufferSize(true); size) {
+    system_wide_buffer_size = size.value() / kMegabyte;
+  }
+  std::string buffer_size_str;
+  if (process_buffer_size == system_wide_buffer_size) {
+    buffer_size_str = android::base::StringPrintf("%" PRIu64 "M", process_buffer_size);
+  } else {
+    buffer_size_str =
+        android::base::StringPrintf("%" PRIu64 "M for process recording and %" PRIu64
+                                    "M\n                                 for system wide recording",
+                                    process_buffer_size, system_wide_buffer_size);
+  }
+  return android::base::StringPrintf(long_help_string_.c_str(), buffer_size_str.c_str());
+}
+
 void RecordCommand::Run(const std::vector<std::string>& args, int* exit_code) {
   *exit_code = 1;
   time_stat_.prepare_recording_time = GetSystemClock();
@@ -607,8 +659,16 @@
   if (!event_selection_set_.OpenEventFiles(cpus_)) {
     return false;
   }
-  size_t record_buffer_size =
-      system_wide_collection_ ? kSystemWideRecordBufferSize : kRecordBufferSize;
+  size_t record_buffer_size = 0;
+  if (user_buffer_size_.has_value()) {
+    record_buffer_size = user_buffer_size_.value();
+  } else {
+    auto default_size = GetDefaultRecordBufferSize(system_wide_collection_);
+    if (!default_size.has_value()) {
+      return false;
+    }
+    record_buffer_size = default_size.value();
+  }
   if (!event_selection_set_.MmapEventFiles(mmap_page_range_.first, mmap_page_range_.second,
                                            aux_buffer_size_, record_buffer_size,
                                            allow_cutting_samples_, exclude_perf_)) {
@@ -697,6 +757,15 @@
     if (!loop->AddPeriodicEvent(SecondToTimeval(kDefaultEtmDataFlushPeriodInSec), etm_flush)) {
       return false;
     }
+
+    if (etm_branch_list_generator_) {
+      if (exclude_perf_) {
+        etm_branch_list_generator_->SetExcludePid(getpid());
+      }
+      if (binary_name_regex_) {
+        etm_branch_list_generator_->SetBinaryFilter(binary_name_regex_.get());
+      }
+    }
   }
   return true;
 }
@@ -798,26 +867,59 @@
   if (event_selection_set_.HasAuxTrace()) {
     LOG(INFO) << "Aux data traced: " << record_stat.aux_data_size;
     if (record_stat.lost_aux_data_size != 0) {
-      LOG(INFO) << "Aux data lost in user space: " << record_stat.lost_aux_data_size;
+      LOG(INFO) << "Aux data lost in user space: " << record_stat.lost_aux_data_size
+                << ", consider increasing userspace buffer size(--user-buffer-size).";
     }
   } else {
-    std::string cut_samples;
-    if (record_stat.cut_stack_samples > 0) {
-      cut_samples = android::base::StringPrintf(" (cut %zu)", record_stat.cut_stack_samples);
+    // Here we report all lost records as samples. This isn't accurate. Because records like
+    // MmapRecords are not samples. But It's easier for users to understand.
+    size_t userspace_lost_samples =
+        record_stat.userspace_lost_samples + record_stat.userspace_lost_non_samples;
+    size_t lost_samples = record_stat.kernelspace_lost_records + userspace_lost_samples;
+
+    std::stringstream os;
+    os << "Samples recorded: " << sample_record_count_;
+    if (record_stat.userspace_cut_stack_samples > 0) {
+      os << " (cut " << record_stat.userspace_cut_stack_samples << ")";
     }
-    lost_record_count_ += record_stat.lost_samples + record_stat.lost_non_samples;
-    LOG(INFO) << "Samples recorded: " << sample_record_count_ << cut_samples
-              << ". Samples lost: " << lost_record_count_ << ".";
-    LOG(DEBUG) << "In user space, dropped " << record_stat.lost_samples << " samples, "
-               << record_stat.lost_non_samples << " non samples, cut stack of "
-               << record_stat.cut_stack_samples << " samples.";
-    if (sample_record_count_ + lost_record_count_ != 0) {
-      double lost_percent =
-          static_cast<double>(lost_record_count_) / (lost_record_count_ + sample_record_count_);
-      constexpr double LOST_PERCENT_WARNING_BAR = 0.1;
-      if (lost_percent >= LOST_PERCENT_WARNING_BAR) {
-        LOG(WARNING) << "Lost " << (lost_percent * 100) << "% of samples, "
-                     << "consider increasing mmap_pages(-m), "
+    os << ". Samples lost: " << lost_samples;
+    if (lost_samples != 0) {
+      os << " (kernelspace: " << record_stat.kernelspace_lost_records
+         << ", userspace: " << userspace_lost_samples << ")";
+    }
+    os << ".";
+    LOG(INFO) << os.str();
+
+    LOG(DEBUG) << "Record stat: kernelspace_lost_records=" << record_stat.kernelspace_lost_records
+               << ", userspace_lost_samples=" << record_stat.userspace_lost_samples
+               << ", userspace_lost_non_samples=" << record_stat.userspace_lost_non_samples
+               << ", userspace_cut_stack_samples=" << record_stat.userspace_cut_stack_samples;
+
+    if (sample_record_count_ + record_stat.kernelspace_lost_records != 0) {
+      double kernelspace_lost_percent =
+          static_cast<double>(record_stat.kernelspace_lost_records) /
+          (record_stat.kernelspace_lost_records + sample_record_count_);
+      constexpr double KERNELSPACE_LOST_PERCENT_WARNING_BAR = 0.1;
+      if (kernelspace_lost_percent >= KERNELSPACE_LOST_PERCENT_WARNING_BAR) {
+        LOG(WARNING) << "Lost " << (kernelspace_lost_percent * 100)
+                     << "% of samples in kernel space, "
+                     << "consider increasing kernel buffer size(-m), "
+                     << "or decreasing sample frequency(-f), "
+                     << "or increasing sample period(-c).";
+      }
+    }
+    size_t userspace_lost_cut_samples =
+        userspace_lost_samples + record_stat.userspace_cut_stack_samples;
+    size_t userspace_complete_samples =
+        sample_record_count_ - record_stat.userspace_cut_stack_samples;
+    if (userspace_complete_samples + userspace_lost_cut_samples != 0) {
+      double userspace_lost_percent = static_cast<double>(userspace_lost_cut_samples) /
+                                      (userspace_complete_samples + userspace_lost_cut_samples);
+      constexpr double USERSPACE_LOST_PERCENT_WARNING_BAR = 0.1;
+      if (userspace_lost_percent >= USERSPACE_LOST_PERCENT_WARNING_BAR) {
+        LOG(WARNING) << "Lost/Cut " << (userspace_lost_percent * 100)
+                     << "% of samples in user space, "
+                     << "consider increasing userspace buffer size(--user-buffer-size), "
                      << "or decreasing sample frequency(-f), "
                      << "or increasing sample period(-c).";
       }
@@ -890,6 +992,13 @@
     branch_sampling_ = branch_sampling_type_map["any"];
   }
 
+  if (auto value = options.PullValue("--binary"); value) {
+    binary_name_regex_ = RegEx::Create(*value->str_value);
+    if (binary_name_regex_ == nullptr) {
+      return false;
+    }
+  }
+
   if (!options.PullUintValue("--callchain-joiner-min-matching-nodes",
                              &callchain_joiner_min_matching_nodes_, 1)) {
     return false;
@@ -921,6 +1030,10 @@
     return false;
   }
 
+  if (options.PullBoolValue("--decode-etm")) {
+    etm_branch_list_generator_ = ETMBranchListGenerator::Create(system_wide_collection_);
+  }
+
   if (!options.PullDoubleValue("--duration", &duration_in_sec_, 1e-9)) {
     return false;
   }
@@ -992,8 +1105,8 @@
     out_fd_.reset(static_cast<int>(value->uint_value));
   }
 
-  for (const OptionValue& value : options.PullValues("-p")) {
-    if (auto pids = GetTidsFromString(*value.str_value, true); pids) {
+  if (auto strs = options.PullStringValues("-p"); !strs.empty()) {
+    if (auto pids = GetPidsFromStrings(strs, true, true); pids) {
       event_selection_set_.AddMonitoredProcesses(pids.value());
     } else {
       return false;
@@ -1011,6 +1124,15 @@
     post_unwind_ = false;
   }
 
+  if (auto value = options.PullValue("--user-buffer-size"); value) {
+    uint64_t v = value->uint_value;
+    if (v > std::numeric_limits<size_t>::max() || v == 0) {
+      LOG(ERROR) << "invalid user buffer size: " << v;
+      return false;
+    }
+    user_buffer_size_ = static_cast<size_t>(v);
+  }
+
   if (!options.PullUintValue("--size-limit", &size_limit_in_bytes_, 1)) {
     return false;
   }
@@ -1281,32 +1403,35 @@
 }
 
 bool RecordCommand::CreateAndInitRecordFile() {
-  record_file_writer_ =
-      CreateRecordFile(record_filename_, event_selection_set_.GetEventAttrWithId());
+  EventAttrIds attrs = event_selection_set_.GetEventAttrWithId();
+  bool remove_regs_and_stacks = unwind_dwarf_callchain_ && !post_unwind_;
+  if (remove_regs_and_stacks) {
+    for (auto& attr : attrs) {
+      ReplaceRegAndStackWithCallChain(attr.attr);
+    }
+  }
+  record_file_writer_ = CreateRecordFile(record_filename_, attrs);
   if (record_file_writer_ == nullptr) {
     return false;
   }
   // Use first perf_event_attr and first event id to dump mmap and comm records.
-  dumping_attr_id_ = event_selection_set_.GetEventAttrWithId()[0];
+  CHECK(!attrs.empty());
+  dumping_attr_id_ = attrs[0];
   CHECK(!dumping_attr_id_.ids.empty());
-  map_record_reader_.emplace(*dumping_attr_id_.attr, dumping_attr_id_.ids[0],
+  map_record_reader_.emplace(dumping_attr_id_.attr, dumping_attr_id_.ids[0],
                              event_selection_set_.RecordNotExecutableMaps());
   map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); });
 
   return DumpKernelSymbol() && DumpTracingData() && DumpMaps() && DumpAuxTraceInfo();
 }
 
-std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(
-    const std::string& filename, const std::vector<EventAttrWithId>& attrs) {
+std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename,
+                                                                  const EventAttrIds& attrs) {
   std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
-  if (writer == nullptr) {
-    return nullptr;
+  if (writer != nullptr && writer->WriteAttrSection(attrs)) {
+    return writer;
   }
-
-  if (!writer->WriteAttrSection(attrs)) {
-    return nullptr;
-  }
-  return writer;
+  return nullptr;
 }
 
 bool RecordCommand::DumpKernelSymbol() {
@@ -1348,9 +1473,11 @@
     // For system wide recording:
     //   If not aux tracing, only dump kernel maps. Maps of a process is dumped when needed (the
     //   first time a sample hits that process).
-    //   If aux tracing, we don't know which maps will be needed, so dump all process maps. To
-    //   reduce pre recording time, we dump process maps in map record thread while recording.
-    if (event_selection_set_.HasAuxTrace()) {
+    //   If aux tracing with decoding etm data, the maps are dumped by etm_branch_list_generator.
+    //   If aux tracing without decoding etm data, we don't know which maps will be needed, so dump
+    //   all process maps. To reduce pre recording time, we dump process maps in map record thread
+    //   while recording.
+    if (event_selection_set_.HasAuxTrace() && !etm_branch_list_generator_) {
       map_record_thread_.emplace(*map_record_reader_);
       return true;
     }
@@ -1409,6 +1536,15 @@
       return true;
     }
   }
+  if (etm_branch_list_generator_) {
+    bool consumed = false;
+    if (!etm_branch_list_generator_->ProcessRecord(*record, consumed)) {
+      return false;
+    }
+    if (consumed) {
+      return true;
+    }
+  }
   if (unwind_dwarf_callchain_) {
     if (post_unwind_) {
       return SaveRecordForPostUnwinding(record);
@@ -1487,8 +1623,6 @@
       return true;
     }
     sample_record_count_++;
-  } else if (record->type() == PERF_RECORD_LOST) {
-    lost_record_count_ += static_cast<LostRecord*>(record)->lost;
   } else {
     thread_tree_.Update(*record);
   }
@@ -1506,8 +1640,6 @@
       return true;
     }
     sample_record_count_++;
-  } else if (record->type() == PERF_RECORD_LOST) {
-    lost_record_count_ += static_cast<LostRecord*>(record)->lost;
   }
   return record_file_writer_->WriteRecord(*record);
 }
@@ -1518,7 +1650,7 @@
     if (info.type == JITDebugInfo::JIT_DEBUG_JIT_CODE) {
       uint64_t timestamp =
           jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
-      Mmap2Record record(*dumping_attr_id_.attr, false, info.pid, info.pid, info.jit_code_addr,
+      Mmap2Record record(dumping_attr_id_.attr, false, info.pid, info.pid, info.jit_code_addr,
                          info.jit_code_len, info.file_offset, map_flags::PROT_JIT_SYMFILE_MAP,
                          info.file_path, dumping_attr_id_.ids[0], timestamp);
       if (!ProcessRecord(&record)) {
@@ -1529,7 +1661,7 @@
         ThreadMmap& map = *info.extracted_dex_file_map;
         uint64_t timestamp =
             jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
-        Mmap2Record record(*dumping_attr_id_.attr, false, info.pid, info.pid, map.start_addr,
+        Mmap2Record record(dumping_attr_id_.attr, false, info.pid, info.pid, map.start_addr,
                            map.len, map.pgoff, map.prot, map.name, dumping_attr_id_.ids[0],
                            timestamp);
         if (!ProcessRecord(&record)) {
@@ -1637,9 +1769,11 @@
 }
 
 bool RecordCommand::UnwindRecord(SampleRecord& r) {
-  if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
-      (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) &&
-      (r.GetValidStackSize() > 0)) {
+  if (!(r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
+      (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER)) {
+    return true;
+  }
+  if (r.GetValidStackSize() > 0) {
     ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
     RegSet regs(r.regs_user_data.abi, r.regs_user_data.reg_mask, r.regs_user_data.regs);
     std::vector<uint64_t> ips;
@@ -1668,6 +1802,9 @@
                                          CallChainJoiner::ORIGINAL_OFFLINE, ips, sps)) {
       return false;
     }
+  } else {
+    // For kernel samples, we still need to remove user stack and register fields.
+    r.ReplaceRegAndStackWithCallChain({});
   }
   return true;
 }
@@ -1692,11 +1829,15 @@
     return nullptr;
   }
   record_file_writer_.reset();
-  {
-    std::error_code ec;
-    std::filesystem::rename(record_filename_, old_filename, ec);
-    if (ec) {
-      LOG(ERROR) << "Failed to rename: " << ec.message();
+  std::error_code ec;
+  std::filesystem::rename(record_filename_, old_filename, ec);
+  if (ec) {
+    LOG(DEBUG) << "Failed to rename: " << ec.message();
+    // rename() fails on Android N x86 emulator, which uses kernel 3.10. Because rename() in bionic
+    // uses renameat2 syscall, which isn't support on kernel < 3.15. So add a fallback to mv
+    // command. The mv command can also work with other situations when rename() doesn't work.
+    // So we'd like to keep it as a fallback to rename().
+    if (!Workload::RunCmd({"mv", record_filename_, old_filename})) {
       return nullptr;
     }
   }
@@ -1755,8 +1896,16 @@
   if (!reader) {
     return false;
   }
+  // Write new event attrs without regs and stacks fields.
+  EventAttrIds attrs = reader->AttrSection();
+  for (auto& attr : attrs) {
+    ReplaceRegAndStackWithCallChain(attr.attr);
+  }
+  if (!record_file_writer_->WriteAttrSection(attrs)) {
+    return false;
+  }
+
   sample_record_count_ = 0;
-  lost_record_count_ = 0;
   auto callback = [this](std::unique_ptr<Record> record) {
     return SaveRecordAfterUnwinding(record.get());
   };
@@ -1867,6 +2016,9 @@
   if (keep_failed_unwinding_debug_info_) {
     feature_count += 2;
   }
+  if (etm_branch_list_generator_) {
+    feature_count++;
+  }
   if (!record_file_writer_->BeginWriteFeatures(feature_count)) {
     return false;
   }
@@ -1909,6 +2061,9 @@
   if (keep_failed_unwinding_debug_info_ && !DumpDebugUnwindFeature(debug_unwinding_files)) {
     return false;
   }
+  if (etm_branch_list_generator_ && !DumpETMBranchListFeature()) {
+    return false;
+  }
 
   if (!record_file_writer_->EndWriteFeatures()) {
     return false;
@@ -1926,34 +2081,9 @@
     if (!dso->HasDumpId() && !event_selection_set_.HasAuxTrace()) {
       continue;
     }
-    if (dso->type() == DSO_KERNEL) {
-      if (!GetKernelBuildId(&build_id)) {
-        continue;
-      }
-      build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
-    } else if (dso->type() == DSO_KERNEL_MODULE) {
-      bool has_build_id = false;
-      if (android::base::EndsWith(dso->Path(), ".ko")) {
-        has_build_id = GetBuildIdFromDsoPath(dso->Path(), &build_id);
-      } else if (const std::string& path = dso->Path();
-                 path.size() > 2 && path[0] == '[' && path.back() == ']') {
-        // For kernel modules that we can't find the corresponding file, read build id from /sysfs.
-        has_build_id = GetModuleBuildId(path.substr(1, path.size() - 2), &build_id);
-      }
-      if (has_build_id) {
-        build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
-      } else {
-        LOG(DEBUG) << "Can't read build_id for module " << dso->Path();
-      }
-    } else if (dso->type() == DSO_ELF_FILE) {
-      if (dso->Path() == DEFAULT_EXECNAME_FOR_THREAD_MMAP || dso->IsForJavaMethod()) {
-        continue;
-      }
-      if (!GetBuildIdFromDsoPath(dso->Path(), &build_id)) {
-        LOG(DEBUG) << "Can't read build_id from file " << dso->Path();
-        continue;
-      }
-      build_id_records.push_back(BuildIdRecord(false, UINT_MAX, build_id, dso->Path()));
+    if (GetBuildId(*dso, build_id)) {
+      bool in_kernel = dso->type() == DSO_KERNEL || dso->type() == DSO_KERNEL_MODULE;
+      build_id_records.emplace_back(in_kernel, UINT_MAX, build_id, dso->Path());
     }
   }
   if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) {
@@ -2013,6 +2143,15 @@
   if (dwarf_callchain_sampling_ && !unwind_dwarf_callchain_) {
     OfflineUnwinder::CollectMetaInfo(&info_map);
   }
+  auto record_stat = event_selection_set_.GetRecordStat();
+  info_map["record_stat"] = android::base::StringPrintf(
+      "sample_record_count=%" PRIu64
+      ",kernelspace_lost_records=%zu,userspace_lost_samples=%zu,"
+      "userspace_lost_non_samples=%zu,userspace_cut_stack_samples=%zu",
+      sample_record_count_, record_stat.kernelspace_lost_records,
+      record_stat.userspace_lost_samples, record_stat.userspace_lost_non_samples,
+      record_stat.userspace_cut_stack_samples);
+
   return record_file_writer_->WriteMetaInfoFeature(info_map);
 }
 
@@ -2064,6 +2203,16 @@
   }
 }
 
+bool RecordCommand::DumpETMBranchListFeature() {
+  BranchListBinaryMap binary_map = etm_branch_list_generator_->GetBranchListBinaryMap();
+  std::string s;
+  if (!BranchListBinaryMapToString(binary_map, s)) {
+    return false;
+  }
+  return record_file_writer_->WriteFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST, s.data(),
+                                           s.size());
+}
+
 }  // namespace
 
 static bool ConsumeStr(const char*& p, const char* s) {
diff --git a/simpleperf/cmd_record_impl.h b/simpleperf/cmd_record_impl.h
index 2ef407b..3f27791 100644
--- a/simpleperf/cmd_record_impl.h
+++ b/simpleperf/cmd_record_impl.h
@@ -40,6 +40,7 @@
         {"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
         {"--aux-buffer-size", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"-b", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+        {"--binary", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"-c", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}},
         {"--call-graph", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
         {"--callchain-joiner-min-matching-nodes",
@@ -47,6 +48,7 @@
         {"--clockid", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"--cpu", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"--cpu-percent", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+        {"--decode-etm", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"--duration", {OptionValueType::DOUBLE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"-e", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
         {"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
@@ -76,6 +78,7 @@
         {"--post-unwind", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"--post-unwind=no", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"--post-unwind=yes", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+        {"--user-buffer-size", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"--size-limit", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
         {"--start_profiling_fd",
          {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 01861c6..b8300d7 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -98,15 +98,15 @@
   ASSERT_TRUE(type != nullptr);
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_file);
   ASSERT_TRUE(reader);
-  std::vector<EventAttrWithId> attrs = reader->AttrSection();
-  for (auto& attr : attrs) {
-    if (attr.attr->type == type->type && attr.attr->config == type->config) {
-      if (attr.attr->freq == 0) {
-        ASSERT_EQ(sample_period, attr.attr->sample_period);
+  for (const auto& attr_with_id : reader->AttrSection()) {
+    const perf_event_attr& attr = attr_with_id.attr;
+    if (attr.type == type->type && attr.config == type->config) {
+      if (attr.freq == 0) {
+        ASSERT_EQ(sample_period, attr.sample_period);
         ASSERT_EQ(sample_freq, 0u);
       } else {
         ASSERT_EQ(sample_period, 0u);
-        ASSERT_EQ(sample_freq, attr.attr->sample_freq);
+        ASSERT_EQ(sample_freq, attr.sample_freq);
       }
       return;
     }
@@ -192,6 +192,9 @@
   } else if (GetTargetArch() == ARCH_X86_32 || GetTargetArch() == ARCH_X86_64) {
     // As in volume 3 chapter 19 of the Intel manual, 0x00c0 is the event number for instruction.
     event_number = 0x00c0;
+  } else if (GetTargetArch() == ARCH_RISCV64) {
+    // RISCV_PMU_INSTRET = 1
+    event_number = 0x1;
   } else {
     GTEST_LOG_(INFO) << "Omit arch " << GetTargetArch();
     return;
@@ -201,10 +204,10 @@
   ASSERT_TRUE(RunRecordCmd({"-e", event_name}, tmpfile.path));
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
   ASSERT_TRUE(reader);
-  std::vector<EventAttrWithId> attrs = reader->AttrSection();
+  const EventAttrIds& attrs = reader->AttrSection();
   ASSERT_EQ(1u, attrs.size());
-  ASSERT_EQ(PERF_TYPE_RAW, attrs[0].attr->type);
-  ASSERT_EQ(event_number, attrs[0].attr->config);
+  ASSERT_EQ(PERF_TYPE_RAW, attrs[0].attr.type);
+  ASSERT_EQ(event_number, attrs[0].attr.config);
 }
 
 TEST(record_cmd, branch_sampling) {
@@ -254,7 +257,16 @@
   ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf"}));
   ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,16384"}));
   ASSERT_FALSE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,65536"}));
-  ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}));
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}, tmpfile.path));
+  auto reader = RecordFileReader::CreateInstance(tmpfile.path);
+  ASSERT_TRUE(reader);
+  const EventAttrIds& attrs = reader->AttrSection();
+  ASSERT_GT(attrs.size(), 0);
+  // Check that reg and stack fields are removed after unwinding.
+  for (const auto& attr : attrs) {
+    ASSERT_EQ(attr.attr.sample_type & (PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER), 0);
+  }
 }
 
 TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
@@ -349,14 +361,16 @@
   auto reader = RecordFileReader::CreateInstance(perf_data_file);
   ASSERT_TRUE(reader);
   FileFeature file;
-  size_t read_pos = 0;
-  while (reader->ReadFileFeature(read_pos, &file)) {
+  uint64_t read_pos = 0;
+  bool error = false;
+  while (reader->ReadFileFeature(read_pos, file, error)) {
     for (const auto& symbol : file.symbols) {
       if (callback(symbol, file.type)) {
         return;
       }
     }
   }
+  ASSERT_FALSE(error);
 }
 
 // Check if dumped symbols in perf.data matches our expectation.
@@ -507,6 +521,7 @@
   auto& info_map = reader->GetMetaInfoFeature();
   ASSERT_NE(info_map.find("simpleperf_version"), info_map.end());
   ASSERT_NE(info_map.find("timestamp"), info_map.end());
+  ASSERT_NE(info_map.find("record_stat"), info_map.end());
 #if defined(__ANDROID__)
   ASSERT_NE(info_map.find("product_props"), info_map.end());
   ASSERT_NE(info_map.find("android_version"), info_map.end());
@@ -546,7 +561,7 @@
   auto info_map = reader->GetMetaInfoFeature();
   ASSERT_EQ(info_map["trace_offcpu"], "true");
   if (IsSwitchRecordSupported()) {
-    ASSERT_EQ(reader->AttrSection()[0].attr->context_switch, 1);
+    ASSERT_EQ(reader->AttrSection()[0].attr.context_switch, 1);
   }
   // Release recording environment in perf.data, to avoid affecting tests below.
   reader.reset();
@@ -709,8 +724,9 @@
 
   bool RecordData(const std::string& record_cmd) {
     std::vector<std::string> args = android::base::Split(record_cmd, " ");
-    args.emplace_back("-o");
-    args.emplace_back(perf_data_file_.path);
+    // record_cmd may end with child command. We should put output options before it.
+    args.emplace(args.begin(), "-o");
+    args.emplace(args.begin() + 1, GetDataPath());
     return RecordCmd()->Run(args);
   }
 
@@ -722,10 +738,15 @@
       }
       return success;
     };
-    ProcessSymbolsInPerfDataFile(perf_data_file_.path, callback);
+    ProcessSymbolsInPerfDataFile(GetDataPath(), callback);
+    if (!success) {
+      DumpData();
+    }
     return success;
   }
 
+  void DumpData() { CreateCommandInstance("report")->Run({"-i", GetDataPath()}); }
+
   std::string GetDataPath() const { return perf_data_file_.path; }
 
  private:
@@ -763,7 +784,9 @@
   reader.reset(nullptr);
 
   // Check that simpleperf can't execute child command in app uid.
-  ASSERT_FALSE(helper.RecordData("--app " + app_name + " -e " + GetDefaultEvent() + " sleep 1"));
+  if (!IsRoot()) {
+    ASSERT_FALSE(helper.RecordData("--app " + app_name + " -e " + GetDefaultEvent() + " sleep 1"));
+  }
 }
 
 TEST(record_cmd, app_option_for_debuggable_app) {
@@ -812,15 +835,18 @@
   }
 
   // Check perf.data by looking for java symbols.
+  const char* java_symbols[] = {
+      "androidx.test.runner",
+      "androidx.test.espresso",
+      "android.app.ActivityThread.main",
+  };
   auto process_symbol = [&](const char* name) {
-#if !defined(IN_CTS_TEST)
-    const char* expected_name_with_keyguard = "androidx.test.runner";  // when screen is locked
-    if (strstr(name, expected_name_with_keyguard) != nullptr) {
-      return true;
+    for (const char* java_symbol : java_symbols) {
+      if (strstr(name, java_symbol) != nullptr) {
+        return true;
+      }
     }
-#endif
-    const char* expected_name = "androidx.test.espresso";  // when screen stays awake
-    return strstr(name, expected_name) != nullptr;
+    return false;
   };
   ASSERT_TRUE(helper.CheckData(process_symbol));
 #else
@@ -930,9 +956,9 @@
 
   // cs-etm uses sample period instead of sample freq.
   ASSERT_EQ(reader->AttrSection().size(), 1u);
-  const perf_event_attr* attr = reader->AttrSection()[0].attr;
-  ASSERT_EQ(attr->freq, 0);
-  ASSERT_EQ(attr->sample_period, 1);
+  const perf_event_attr& attr = reader->AttrSection()[0].attr;
+  ASSERT_EQ(attr.freq, 0);
+  ASSERT_EQ(attr.sample_period, 1);
 
   bool has_auxtrace_info = false;
   bool has_auxtrace = false;
@@ -1033,6 +1059,23 @@
   ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
 }
 
+TEST(record_cmd, decode_etm_option) {
+  if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+    GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+    return;
+  }
+  ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm"}));
+  ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm", "--exclude-perf"}));
+}
+
+TEST(record_cmd, binary_option) {
+  if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+    GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+    return;
+  }
+  ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm", "--binary", ".*"}));
+}
+
 TEST(record_cmd, pmu_event_option) {
   TEST_REQUIRE_PMU_COUNTER();
   TEST_REQUIRE_HW_COUNTER();
@@ -1197,6 +1240,7 @@
 }
 
 TEST(record_cmd, device_meta_info) {
+#if defined(__ANDROID__)
   TemporaryFile tmpfile;
   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
   auto reader = RecordFileReader::CreateInstance(tmpfile.path);
@@ -1209,6 +1253,9 @@
   it = meta_info.find("android_build_type");
   ASSERT_NE(it, meta_info.end());
   ASSERT_FALSE(it->second.empty());
+#else
+  GTEST_LOG_(INFO) << "This test tests a function only available on Android.";
+#endif
 }
 
 TEST(record_cmd, add_counter_option) {
@@ -1231,3 +1278,25 @@
   }));
   ASSERT_TRUE(has_sample);
 }
+
+TEST(record_cmd, user_buffer_size_option) {
+  ASSERT_TRUE(RunRecordCmd({"--user-buffer-size", "256M"}));
+}
+
+TEST(record_cmd, record_process_name) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RecordCmd()->Run({"-e", GetDefaultEvent(), "-o", tmpfile.path, "sleep", SLEEP_SEC}));
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+  ASSERT_TRUE(reader);
+  bool has_comm = false;
+  ASSERT_TRUE(reader->ReadDataSection([&](std::unique_ptr<Record> r) {
+    if (r->type() == PERF_RECORD_COMM) {
+      CommRecord* cr = static_cast<CommRecord*>(r.get());
+      if (strcmp(cr->comm, "sleep") == 0) {
+        has_comm = true;
+      }
+    }
+    return true;
+  }));
+  ASSERT_TRUE(has_comm);
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 8457df2..2ab9854 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -662,8 +662,8 @@
     return false;
   }
 
-  for (const OptionValue& value : options.PullValues("--pids")) {
-    if (auto pids = GetTidsFromString(*value.str_value, false); pids) {
+  if (auto strs = options.PullStringValues("--pids"); !strs.empty()) {
+    if (auto pids = GetPidsFromStrings(strs, false, false); pids) {
       record_filter_.AddPids(pids.value(), false);
     } else {
       return false;
@@ -856,9 +856,8 @@
 }
 
 bool ReportCommand::ReadEventAttrFromRecordFile() {
-  std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
-  for (const auto& attr_with_id : attrs) {
-    const perf_event_attr& attr = *attr_with_id.attr;
+  for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
+    const perf_event_attr& attr = attr_with_id.attr;
     attr_names_.emplace_back(GetEventNameByAttr(attr));
 
     // There are no samples for events added by --add-counter. So skip them.
@@ -895,7 +894,9 @@
 }
 
 bool ReportCommand::ReadFeaturesFromRecordFile() {
-  record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+  if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+    return false;
+  }
 
   std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
   if (!arch.empty()) {
@@ -991,11 +992,14 @@
 }
 
 bool ReportCommand::ProcessTracingData(const std::vector<char>& data) {
-  Tracing tracing(data);
+  auto tracing = Tracing::Create(data);
+  if (!tracing) {
+    return false;
+  }
   for (size_t i = 0; i < event_attrs_.size(); i++) {
     if (event_attrs_[i].type == PERF_TYPE_TRACEPOINT) {
       uint64_t trace_event_id = event_attrs_[i].config;
-      attr_names_[i] = tracing.GetTracingEventNameHavingId(trace_event_id);
+      attr_names_[i] = tracing->GetTracingEventNameHavingId(trace_event_id);
     }
   }
   return true;
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp
index dc2a3d4..12e3ed6 100644
--- a/simpleperf/cmd_report_sample.cpp
+++ b/simpleperf/cmd_report_sample.cpp
@@ -372,8 +372,7 @@
   // files[file_id] is the number of symbols in the file.
   std::vector<uint32_t> files;
   uint32_t max_message_size = 64 * (1 << 20);
-  uint32_t warning_message_size = 512 * (1 << 20);
-  coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
+  coded_is.SetTotalBytesLimit(max_message_size);
   while (true) {
     uint32_t size;
     if (!coded_is.ReadLittleEndian32(&size)) {
@@ -386,7 +385,7 @@
     // Handle files having large symbol table.
     if (size > max_message_size) {
       max_message_size = size;
-      coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
+      coded_is.SetTotalBytesLimit(max_message_size);
     }
     auto limit = coded_is.PushLimit(size);
     proto::Record proto_record;
@@ -516,12 +515,14 @@
   if (record_file_reader_ == nullptr) {
     return false;
   }
-  record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+  if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+    return false;
+  }
   auto& meta_info = record_file_reader_->GetMetaInfoFeature();
   if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
     trace_offcpu_ = it->second == "true";
     if (trace_offcpu_) {
-      std::string event_name = GetEventNameByAttr(*record_file_reader_->AttrSection()[0].attr);
+      std::string event_name = GetEventNameByAttr(record_file_reader_->AttrSection()[0].attr);
       if (!android::base::StartsWith(event_name, "cpu-clock") &&
           !android::base::StartsWith(event_name, "task-clock")) {
         LOG(ERROR) << "Recording file " << record_filename_ << " is no longer supported. "
@@ -536,8 +537,8 @@
   if (!record_filter_.CheckClock(record_file_reader_->GetClockId())) {
     return false;
   }
-  for (EventAttrWithId& attr : record_file_reader_->AttrSection()) {
-    event_types_.push_back(GetEventNameByAttr(*attr.attr));
+  for (const EventAttrWithId& attr : record_file_reader_->AttrSection()) {
+    event_types_.push_back(GetEventNameByAttr(attr.attr));
   }
   return true;
 }
@@ -782,7 +783,7 @@
 }
 
 bool ReportSampleCommand::WriteRecordInProtobuf(proto::Record& proto_record) {
-  coded_os_->WriteLittleEndian32(proto_record.ByteSize());
+  coded_os_->WriteLittleEndian32(static_cast<uint32_t>(proto_record.ByteSizeLong()));
   if (!proto_record.SerializeToCodedStream(coded_os_)) {
     LOG(ERROR) << "failed to write record to protobuf";
     return false;
diff --git a/simpleperf/cmd_report_sample.proto b/simpleperf/cmd_report_sample.proto
index d5a6b86..8944fbe 100644
--- a/simpleperf/cmd_report_sample.proto
+++ b/simpleperf/cmd_report_sample.proto
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
 // The file format generated by cmd_report_sample.proto is as below:
 // char magic[10] = "SIMPLEPERF";
 // LittleEndian16(version) = 1;
diff --git a/simpleperf/cmd_report_sample_test.cpp b/simpleperf/cmd_report_sample_test.cpp
index ca6db2f..fc2f9f9 100644
--- a/simpleperf/cmd_report_sample_test.cpp
+++ b/simpleperf/cmd_report_sample_test.cpp
@@ -18,8 +18,8 @@
 
 #include <android-base/file.h>
 #include <android-base/strings.h>
-#include <regex>
 
+#include "RegEx.h"
 #include "command.h"
 #include "get_test_data.h"
 
@@ -96,8 +96,8 @@
       {"context_switch:", "switch_on: false", "time: 676374953363850", "thread_id: 6525"},
   };
   for (auto& test_case : cases) {
-    auto pattern = std::regex(android::base::Join(test_case, R"((\s|\n|\r)+)"));
-    ASSERT_TRUE(std::regex_search(data, pattern));
+    auto regex = RegEx::Create(android::base::Join(test_case, R"((\s|\n|\r)+)"));
+    ASSERT_TRUE(regex->Search(data));
   }
 }
 
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index e126b52..6dfbae5 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -16,7 +16,6 @@
 
 #include <gtest/gtest.h>
 
-#include <regex>
 #include <set>
 #include <unordered_map>
 
@@ -25,6 +24,7 @@
 #include <android-base/strings.h>
 #include <android-base/test_utils.h>
 
+#include "RegEx.h"
 #include "command.h"
 #include "get_test_data.h"
 #include "perf_regs.h"
@@ -67,10 +67,11 @@
   }
 
   size_t GetSampleCount() {
-    std::smatch m;
-    if (std::regex_search(content, m, std::regex(R"(Samples: (\d+))"))) {
+    auto regex = RegEx::Create(R"(Samples: (\d+))");
+    auto match = regex->SearchAll(content);
+    if (match->IsValid()) {
       size_t count;
-      if (android::base::ParseUint(m[1], &count)) {
+      if (android::base::ParseUint(match->GetField(1), &count)) {
         return count;
       }
     }
@@ -226,7 +227,7 @@
         Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--pids", "2,bogus"});
         exit(success ? 0 : 1);
       },
-      testing::ExitedWithCode(1), "Invalid tid 'bogus'");
+      testing::ExitedWithCode(1), "invalid pid: bogus");
 }
 
 TEST_F(ReportCommandTest, tid_filter_option) {
@@ -403,21 +404,6 @@
   ASSERT_NE(content.find("unknown"), std::string::npos);
 }
 
-TEST_F(ReportCommandTest, no_symbol_table_warning) {
-  ASSERT_EXIT(
-      {
-        Report(PERF_DATA, {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
-        if (!success) {
-          exit(1);
-        }
-        if (content.find("GlobalFunc") != std::string::npos) {
-          exit(2);
-        }
-        exit(0);
-      },
-      testing::ExitedWithCode(0), "elf doesn't contain symbol table");
-}
-
 TEST_F(ReportCommandTest, read_elf_file_warning) {
   ASSERT_EXIT(
       {
@@ -570,32 +556,31 @@
   Report("perf.data", {"--print-event-count"});
   ASSERT_TRUE(success);
   ASSERT_NE(content.find("EventCount"), std::string::npos);
-  ASSERT_TRUE(std::regex_search(
-      content, std::regex(R"(325005586\s+elf\s+26083\s+26083\s+/elf\s+GlobalFunc)")));
+  ASSERT_TRUE(
+      RegEx::Create(R"(325005586\s+elf\s+26083\s+26083\s+/elf\s+GlobalFunc)")->Search(content));
 
   // Report record file recorded with --add-counter.
   const std::string record_file = "perf_with_add_counter.data";
   Report(record_file, {"--print-event-count"});
   ASSERT_TRUE(success);
+  ASSERT_TRUE(RegEx::Create(R"(EventCount_cpu-cycles\s+EventCount_instructions)")->Search(content));
   ASSERT_TRUE(
-      std::regex_search(content, std::regex(R"(EventCount_cpu-cycles\s+EventCount_instructions)")));
-  ASSERT_TRUE(std::regex_search(
-      content, std::regex(R"(175099\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")));
+      RegEx::Create(R"(175099\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")->Search(content));
 
   // Report accumulated event counts.
   Report(record_file, {"--print-event-count", "--children"});
   ASSERT_TRUE(success);
-  ASSERT_TRUE(std::regex_search(
-      content,
-      std::regex(
+  ASSERT_TRUE(
+      RegEx::Create(
           R"(AccEventCount_cpu-cycles\s+SelfEventCount_cpu-cycles\s+AccEventCount_instructions\s+)"
-          R"(SelfEventCount_instructions)")));
-  ASSERT_TRUE(std::regex_search(
-      content,
-      std::regex(R"(175099\s+175099\s+140443\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")));
-  ASSERT_TRUE(std::regex_search(
-      content,
-      std::regex(R"(366116\s+0\s+297474\s+0\s+sleep\s+689664\s+689664.+__libc_start_main)")));
+          R"(SelfEventCount_instructions)")
+          ->Search(content));
+  ASSERT_TRUE(
+      RegEx::Create(R"(175099\s+175099\s+140443\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")
+          ->Search(content));
+  ASSERT_TRUE(
+      RegEx::Create(R"(366116\s+0\s+297474\s+0\s+sleep\s+689664\s+689664.+__libc_start_main)")
+          ->Search(content));
 }
 
 TEST_F(ReportCommandTest, exclude_include_pid_options) {
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index bd1bffb..009afc3 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -380,8 +380,10 @@
 "-o output_filename  Write report to output_filename instead of standard output.\n"
 "--per-core       Print counters for each cpu core.\n"
 "--per-thread     Print counters for each thread.\n"
-"-p pid1,pid2,... Stat events on existing processes. Mutually exclusive with -a.\n"
-"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\n"
+"-p pid_or_process_name_regex1,pid_or_process_name_regex2,...\n"
+"                      Stat events on existing processes. Processes are searched either by pid\n"
+"                      or process name regex. Mutually exclusive with -a.\n"
+"-t tid1,tid2,...      Stat events on existing threads. Mutually exclusive with -a.\n"
 "--print-hw-counter    Test and print CPU PMU hardware counters available on the device.\n"
 "--sort key1,key2,...  Select keys used to sort the report, used when --per-thread\n"
 "                      or --per-core appears. The appearance order of keys decides\n"
@@ -440,6 +442,7 @@
   void AdjustToIntervalOnlyValues(std::vector<CountersInfo>& counters);
   bool ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec, FILE* fp);
   void CheckHardwareCounterMultiplexing();
+  void PrintWarningForInaccurateEvents();
 
   bool verbose_mode_;
   bool system_wide_collection_;
@@ -620,9 +623,10 @@
     }
   }
 
-  // 7. Print hardware counter multiplexing warning when needed.
+  // 7. Print warnings when needed.
   event_selection_set_.CloseEventFiles();
   CheckHardwareCounterMultiplexing();
+  PrintWarningForInaccurateEvents();
 
   return true;
 }
@@ -688,8 +692,8 @@
   report_per_core_ = options.PullBoolValue("--per-core");
   report_per_thread_ = options.PullBoolValue("--per-thread");
 
-  for (const OptionValue& value : options.PullValues("-p")) {
-    if (auto pids = GetTidsFromString(*value.str_value, true); pids) {
+  if (auto strs = options.PullStringValues("-p"); !strs.empty()) {
+    if (auto pids = GetPidsFromStrings(strs, true, true); pids) {
       event_selection_set_.AddMonitoredProcesses(pids.value());
     } else {
       return false;
@@ -955,6 +959,16 @@
   }
 }
 
+void StatCommand::PrintWarningForInaccurateEvents() {
+  for (const EventType* event : event_selection_set_.GetEvents()) {
+    if (event->name == "raw-l3d-cache-lmiss-rd") {
+      LOG(WARNING) << "PMU event L3D_CACHE_LMISS_RD might undercount on A510. Please use "
+                      "L3D_CACHE_REFILL_RD instead.";
+      break;
+    }
+  }
+}
+
 }  // namespace
 
 void RegisterStatCommand() {
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 78eac57..53ff0d4 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -67,6 +67,9 @@
   } else if (GetTargetArch() == ARCH_X86_32 || GetTargetArch() == ARCH_X86_64) {
     // As in volume 3 chapter 19 of the Intel manual, 0x00c0 is the event number for instruction.
     event_number = 0x00c0;
+  } else if (GetTargetArch() == ARCH_RISCV64) {
+    //  RISCV_PMU_INSTRET = 1
+    event_number = 0x1;
   } else {
     GTEST_LOG_(INFO) << "Omit arch " << GetTargetArch();
     return;
@@ -83,6 +86,8 @@
     event_string = "cpu/instructions/";
   } else if (GetTargetArch() == ARCH_ARM64) {
     event_string = "armv8_pmuv3/inst_retired/";
+  } else if (GetTargetArch() == ARCH_RISCV64) {
+    event_string = "cpu/instructions/";
   } else {
     GTEST_LOG_(INFO) << "Omit arch " << GetTargetArch();
     return;
@@ -228,12 +233,12 @@
   ASSERT_TRUE(set.AddEventType("cpu-cycles"));
   set.AddMonitoredProcesses({getpid()});
   ASSERT_TRUE(set.OpenEventFiles({-1}));
-  std::vector<EventAttrWithId> attrs = set.GetEventAttrWithId();
+  const EventAttrIds& 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);
+    ASSERT_EQ(attr.attr.sample_period, 0u);
+    ASSERT_EQ(attr.attr.sample_freq, 0u);
+    ASSERT_EQ(attr.attr.freq, 0u);
   }
 }
 
@@ -611,4 +616,4 @@
   ASSERT_EQ(*GetComment(1), "1.000 M/sec");
   ASSERT_EQ(*GetComment(2), "1.000 K/sec");
   ASSERT_EQ(*GetComment(3), "1.000 /sec");
-}
\ No newline at end of file
+}
diff --git a/simpleperf/cmd_trace_sched.cpp b/simpleperf/cmd_trace_sched.cpp
index 0fcabd2..d02495a 100644
--- a/simpleperf/cmd_trace_sched.cpp
+++ b/simpleperf/cmd_trace_sched.cpp
@@ -100,7 +100,7 @@
   bool ParseOptions(const std::vector<std::string>& args);
   bool RecordSchedEvents(const std::string& record_file_path);
   bool ParseSchedEvents(const std::string& record_file_path);
-  void ProcessRecord(Record& record);
+  bool ProcessRecord(Record& record);
   void ProcessSampleRecord(const SampleRecord& record);
   std::vector<ProcessInfo> BuildProcessInfo();
   void ReportProcessInfo(const std::vector<ProcessInfo>& processes);
@@ -192,21 +192,18 @@
     return false;
   }
   const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
-  std::vector<EventAttrWithId> attrs = reader->AttrSection();
-  if (attrs.size() != 1u || attrs[0].attr->type != event->type ||
-      attrs[0].attr->config != event->config) {
+  const EventAttrIds& attrs = reader->AttrSection();
+  if (attrs.size() != 1u || attrs[0].attr.type != event->type ||
+      attrs[0].attr.config != event->config) {
     LOG(ERROR) << "sched:sched_stat_runtime isn't recorded in " << record_file_path;
     return false;
   }
 
-  auto callback = [this](std::unique_ptr<Record> record) {
-    ProcessRecord(*record);
-    return true;
-  };
+  auto callback = [this](std::unique_ptr<Record> record) { return ProcessRecord(*record); };
   return reader->ReadDataSection(callback);
 }
 
-void TraceSchedCommand::ProcessRecord(Record& record) {
+bool TraceSchedCommand::ProcessRecord(Record& record) {
   switch (record.type()) {
     case PERF_RECORD_SAMPLE: {
       ProcessSampleRecord(*static_cast<SampleRecord*>(&record));
@@ -234,15 +231,19 @@
     case PERF_RECORD_TRACING_DATA:
     case SIMPLE_PERF_RECORD_TRACING_DATA: {
       const TracingDataRecord& r = *static_cast<const TracingDataRecord*>(&record);
-      Tracing tracing(std::vector<char>(r.data, r.data + r.data_size));
+      auto tracing = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
+      if (!tracing) {
+        return false;
+      }
       const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
       CHECK(event != nullptr);
-      TracingFormat format = tracing.GetTracingFormatHavingId(event->config);
+      TracingFormat format = tracing->GetTracingFormatHavingId(event->config);
       format.GetField("comm", tracing_field_comm_);
       format.GetField("runtime", tracing_field_runtime_);
       break;
     }
   }
+  return true;
 }
 
 void TraceSchedCommand::ProcessSampleRecord(const SampleRecord& record) {
diff --git a/simpleperf/command.h b/simpleperf/command.h
index a46da6f..35db846 100644
--- a/simpleperf/command.h
+++ b/simpleperf/command.h
@@ -117,6 +117,14 @@
     return res;
   }
 
+  std::vector<std::string> PullStringValues(const OptionName& name) {
+    std::vector<std::string> res;
+    for (const auto& value : PullValues(name)) {
+      res.emplace_back(*value.str_value);
+    }
+    return res;
+  }
+
   std::vector<OptionValue> PullValues(const OptionName& name) {
     auto pair = values.equal_range(name);
     if (pair.first != pair.second) {
@@ -161,7 +169,7 @@
 
   const std::string& ShortHelpString() const { return short_help_string_; }
 
-  const std::string LongHelpString() const { return long_help_string_; }
+  virtual std::string LongHelpString() const { return long_help_string_; }
 
   virtual bool Run(const std::vector<std::string>&) { return false; }
   virtual void Run(const std::vector<std::string>& args, int* exit_code) {
@@ -195,7 +203,6 @@
   bool NextArgumentOrError(const std::vector<std::string>& args, size_t* pi);
   void ReportUnknownOption(const std::vector<std::string>& args, size_t i);
 
- private:
   const std::string name_;
   const std::string short_help_string_;
   const std::string long_help_string_;
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index 9b78c64..526ab87 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -497,7 +497,7 @@
       verbose_mode = true;
     }
   }
-  android::base::InitLogging(argv, android::base::StderrLogger);
   testing::InitGoogleTest(&argc, argv);
+  android::base::InitLogging(argv, android::base::StderrLogger);
   return RUN_ALL_TESTS();
 }
diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md
index c1ef30a..8e554a8 100644
--- a/simpleperf/demo/README.md
+++ b/simpleperf/demo/README.md
@@ -19,9 +19,9 @@
 meaning of each directory is as below:
 
     ../scripts/                  -- contain simpleperf binaries and scripts.
-    SimpleperfExamplePureJava/   -- contains an Android Studio project using only Java code.
+    SimpleperfExampleJava/       -- contains an Android Studio project using only Java code.
     SimpleperfExampleCpp/        -- contains an Android Studio project using both Java and C++ code.
-    SimpleperfExampleOfKotlin/   -- contains an Android Studio project using Kotlin code.
+    SimpleperfExampleKotlin/     -- contains an Android Studio project using Kotlin code.
     CppApi/                      -- contains an Android Studio project using c++ app_api to record.
     JavaApi/                     -- contains an Android Studio project using Java app_api to record.
 
@@ -43,19 +43,20 @@
 
 ## Profile a Java application
 
-Android Studio project: SimpleExamplePureJava
+Android Studio project: SimpleExampleJava
 
 steps:
 1. Build and install the application:
 
 ```sh
-# Open SimpleperfExamplePureJava project with Android Studio,
+# Open SimpleperfExampleJava project with Android Studio,
 # and build this project successfully, otherwise the `./gradlew` command below will fail.
-$ cd SimpleperfExamplePureJava
+$ cd SimpleperfExampleJava
 
+# Build and install a debuggable app. We can also build and install a released app on Android >= Q.
 # On windows, use "gradlew" instead.
 $ ./gradlew clean assemble
-$ adb install -r app/build/outputs/apk/app-profiling.apk
+$ adb install -r app/build/outputs/apk/debug/app-debug.apk
 ```
 
 2. Record profiling data:
@@ -63,7 +64,7 @@
 ```sh
 $ cd ../../scripts/
 # app_profiler.py collects profiling data in perf.data, and binaries on device in binary_cache/.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava
+$ python app_profiler.py -p simpleperf.example.java
 ```
 
 3. Show profiling data:
@@ -107,19 +108,20 @@
 
 ## Profile a Kotlin application
 
-Android Studio project: SimpleExampleOfKotlin
+Android Studio project: SimpleExampleKotlin
 
 steps:
 1. Build and install the application:
 
 ```sh
-# Open SimpleperfExampleOfKotlin project with Android Studio,
+# Open SimpleperfExampleKotlin project with Android Studio,
 # and build this project sucessfully, otherwise the `./gradlew` command below will fail.
-$ cd SimpleperfExampleOfKotlin
+$ cd SimpleperfExampleKotlin
 
+# Build and install a debuggable app. We can also build and install a released app on Android >= Q.
 # On windows, use "gradlew" instead.
 $ ./gradlew clean assemble
-$ adb install -r app/build/outputs/apk/profiling/app-profiling.apk
+$ adb install -r app/build/outputs/apk/debug/app-debug.apk
 ```
 
 2. Record profiling data:
@@ -127,7 +129,7 @@
 ```sh
 $ cd ../../scripts/
 # app_profiler.py collects profiling data in perf.data, and binaries on device in binary_cache/.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexampleofkotlin
+$ python app_profiler.py -p simpleperf.example.kotlin
 ```
 
 3. Show profiling data:
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle b/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle
index f327dbf..8cbdf27 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle
@@ -3,12 +3,12 @@
 }
 
 android {
-    compileSdkVersion 30
+    compileSdkVersion 32
 
     defaultConfig {
         applicationId "simpleperf.example.cpp"
         minSdkVersion 24
-        targetSdkVersion 30
+        targetSdkVersion 32
         versionCode 1
         versionName "1.0"
 
@@ -35,7 +35,7 @@
     externalNativeBuild {
         cmake {
             path file('src/main/cpp/CMakeLists.txt')
-            version '3.10.2'
+            version '3.18.1'
         }
     }
     buildFeatures {
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
index ad974c8..6c366c0 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so
index afacd9f..d8ec66d 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so
index 883b643..a1505db 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so
index 103ed97..93a68ca 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk b/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk
index ba6ad0c..b1aa5c4 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/release/app-release.apk b/simpleperf/demo/SimpleperfExampleCpp/app/release/app-release.apk
new file mode 100644
index 0000000..01e0e5a
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/release/app-release.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml
index 18ffc0c..7bb592d 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml
@@ -9,7 +9,9 @@
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/Theme.SimpleperfExampleCpp">
-        <activity android:name=".MainActivity">
+        <profileable android:shell="true" />
+        <activity android:name=".MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt
index c5b83ac..9c11f07 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt
@@ -3,7 +3,7 @@
 
 # Sets the minimum version of CMake required to build the native library.
 
-cmake_minimum_required(VERSION 3.10.2)
+cmake_minimum_required(VERSION 3.18.1)
 
 # Declares and names the project.
 
diff --git a/simpleperf/demo/SimpleperfExampleCpp/build.gradle b/simpleperf/demo/SimpleperfExampleCpp/build.gradle
index 5a7c7fc..71b99f4 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/build.gradle
+++ b/simpleperf/demo/SimpleperfExampleCpp/build.gradle
@@ -1,25 +1,5 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
-buildscript {
-    repositories {
-        google()
-        mavenCentral()
-    }
-    dependencies {
-        classpath "com.android.tools.build:gradle:4.2.2"
-
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
-    }
-}
-
-allprojects {
-    repositories {
-        google()
-        mavenCentral()
-        jcenter() // Warning: this repository is going to shut down soon
-    }
-}
-
-task clean(type: Delete) {
-    delete rootProject.buildDir
+plugins {
+    id 'com.android.application' version '7.3.1' apply false
+    id 'com.android.library' version '7.3.1' apply false
 }
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties b/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties
index f793a34..6903aad 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties
+++ b/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 #Fri Oct 15 16:48:24 PDT 2021
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/simpleperf/demo/SimpleperfExampleCpp/settings.gradle b/simpleperf/demo/SimpleperfExampleCpp/settings.gradle
index a389315..15bae72 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/settings.gradle
+++ b/simpleperf/demo/SimpleperfExampleCpp/settings.gradle
@@ -1,2 +1,16 @@
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
 rootProject.name = "SimpleperfExampleCpp"
 include ':app'
diff --git a/simpleperf/demo/SimpleperfExampleJava/.idea/compiler.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/compiler.xml
new file mode 100644
index 0000000..fb7f4a8
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/gradle.xml
similarity index 89%
rename from simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml
rename to simpleperf/demo/SimpleperfExampleJava/.idea/gradle.xml
index 7ac24c7..a9f4e52 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/gradle.xml
@@ -3,6 +3,7 @@
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
         <option name="distributionType" value="DEFAULT_WRAPPED" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="modules">
@@ -11,7 +12,6 @@
             <option value="$PROJECT_DIR$/app" />
           </set>
         </option>
-        <option name="resolveModulePerSourceSet" value="false" />
       </GradleProjectSettings>
     </option>
   </component>
diff --git a/simpleperf/demo/SimpleperfExampleJava/.idea/misc.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/misc.xml
new file mode 100644
index 0000000..bdd9278
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/misc.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/.idea/vcs.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/vcs.xml
new file mode 100644
index 0000000..c2365ab
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/build.gradle b/simpleperf/demo/SimpleperfExampleJava/app/build.gradle
new file mode 100644
index 0000000..c2c1fe0
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/build.gradle
@@ -0,0 +1,39 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    namespace 'simpleperf.example.java'
+    compileSdk 32
+
+    defaultConfig {
+        applicationId "simpleperf.example.java"
+        minSdk 24
+        targetSdk 32
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.appcompat:appcompat:1.4.1'
+    implementation 'com.google.android.material:material:1.5.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/build/outputs/apk/debug/app-debug.apk b/simpleperf/demo/SimpleperfExampleJava/app/build/outputs/apk/debug/app-debug.apk
new file mode 100644
index 0000000..dcb1a6d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/build/outputs/apk/debug/app-debug.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro b/simpleperf/demo/SimpleperfExampleJava/app/proguard-rules.pro
similarity index 69%
rename from simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro
rename to simpleperf/demo/SimpleperfExampleJava/app/proguard-rules.pro
index b9d149a..481bb43 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro
+++ b/simpleperf/demo/SimpleperfExampleJava/app/proguard-rules.pro
@@ -1,14 +1,10 @@
 # Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /home/yabinc/Android/Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
 #
 # For more details, see
 #   http://developer.android.com/guide/developing/tools/proguard.html
 
-# Add any project specific keep options here:
-
 # If your project uses WebView with JS, uncomment the following
 # and specify the fully qualified class name to the JavaScript interface
 # class:
@@ -22,4 +18,4 @@
 
 # If you keep the line number information, uncomment this to
 # hide the original source file name.
-#-renamesourcefileattribute SourceFile
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/release/app-release.apk b/simpleperf/demo/SimpleperfExampleJava/app/release/app-release.apk
new file mode 100644
index 0000000..87b46c2
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/release/app-release.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/release/output-metadata.json b/simpleperf/demo/SimpleperfExampleJava/app/release/output-metadata.json
new file mode 100644
index 0000000..d95c5c6
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+  "version": 3,
+  "artifactType": {
+    "type": "APK",
+    "kind": "Directory"
+  },
+  "applicationId": "simpleperf.example.java",
+  "variantName": "release",
+  "elements": [
+    {
+      "type": "SINGLE",
+      "filters": [],
+      "attributes": [],
+      "versionCode": 1,
+      "versionName": "1.0",
+      "outputFile": "app-release.apk"
+    }
+  ],
+  "elementType": "File"
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/androidTest/java/simpleperf/example/java/ExampleInstrumentedTest.java b/simpleperf/demo/SimpleperfExampleJava/app/src/androidTest/java/simpleperf/example/java/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..46af487
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/androidTest/java/simpleperf/example/java/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package simpleperf.example.java;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("simpleperf.example.java", appContext.getPackageName());
+    }
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5baf725
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.SimpleperfExampleJava"
+        tools:targetApi="31">
+        <profileable android:shell="true" />
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".SleepActivity"
+            android:exported="true"/>
+        <activity
+            android:name=".MultiProcessActivity"
+            android:exported="true"/>
+        <service
+            android:name=".MultiProcessService"
+            android:process=":multiprocess_service"/>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MainActivity.java
similarity index 85%
rename from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java
rename to simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MainActivity.java
index 2d6196b..7296dba 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MainActivity.java
@@ -1,6 +1,7 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
 
-import android.support.v7.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatActivity;
+
 import android.os.Bundle;
 
 public class MainActivity extends AppCompatActivity {
@@ -9,7 +10,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
-
         createBusyThread();
     }
 
@@ -29,4 +29,4 @@
             }
         }, "BusyThread").start();
     }
-}
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessActivity.java
similarity index 94%
rename from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
rename to simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessActivity.java
index de698ec..25dc796 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessActivity.java
@@ -1,20 +1,20 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
+
+import androidx.appcompat.app.AppCompatActivity;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Bundle;
 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;
 
@@ -22,7 +22,6 @@
     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();
@@ -75,4 +74,4 @@
             }
         }, "BusyThread").start();
     }
-}
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessService.java
similarity index 94%
rename from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
rename to simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessService.java
index 2fd4d57..3e85c24 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessService.java
@@ -1,9 +1,10 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
 
 import android.app.Service;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 
@@ -47,4 +48,4 @@
             }
         }, "BusyService").start();
     }
-}
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/SleepActivity.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/SleepActivity.java
similarity index 93%
rename from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/SleepActivity.java
rename to simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/SleepActivity.java
index 25dc7e3..4c310df 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/SleepActivity.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/SleepActivity.java
@@ -1,6 +1,7 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
 
-import android.support.v7.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatActivity;
+
 import android.os.Bundle;
 
 public class SleepActivity extends AppCompatActivity {
@@ -51,5 +52,4 @@
             }
         }, "RunSleepThread").start();
     }
-
-}
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable/ic_launcher_background.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..c937b69
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".MainActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Running a busy thread!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_multi_process.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_multi_process.xml
new file mode 100644
index 0000000..8b23ff4
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_multi_process.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".MultiProcessActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Running a busy thread and a busy service thread!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_sleep.xml
new file mode 100644
index 0000000..fd8ce42
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_sleep.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".SleepActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Running a half busy half sleep thread!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values-night/themes.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..6327fb7
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.SimpleperfExampleJava" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..069da5c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">SimpleperfExampleJava</string>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/themes.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..5d8a48d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.SimpleperfExampleJava" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/backup_rules.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/data_extraction_rules.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/test/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleUnitTest.java b/simpleperf/demo/SimpleperfExampleJava/app/src/test/java/simpleperf/example/java/ExampleUnitTest.java
similarity index 73%
rename from simpleperf/demo/SimpleperfExamplePureJava/app/src/test/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleUnitTest.java
rename to simpleperf/demo/SimpleperfExampleJava/app/src/test/java/simpleperf/example/java/ExampleUnitTest.java
index 0f3813d..8bcec03 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/test/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleUnitTest.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/test/java/simpleperf/example/java/ExampleUnitTest.java
@@ -1,4 +1,4 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
 
 import org.junit.Test;
 
@@ -11,7 +11,7 @@
  */
 public class ExampleUnitTest {
     @Test
-    public void addition_isCorrect() throws Exception {
+    public void addition_isCorrect() {
         assertEquals(4, 2 + 2);
     }
 }
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/build.gradle b/simpleperf/demo/SimpleperfExampleJava/build.gradle
new file mode 100644
index 0000000..71b99f4
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/build.gradle
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id 'com.android.application' version '7.3.1' apply false
+    id 'com.android.library' version '7.3.1' apply false
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/gradle.properties b/simpleperf/demo/SimpleperfExampleJava/gradle.properties
new file mode 100644
index 0000000..3e927b1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.properties
similarity index 80%
rename from simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties
rename to simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.properties
index be54b8d..51d529e 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties
+++ b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Apr 10 15:23:21 PDT 2018
+#Wed Nov 09 13:48:30 PST 2022
 distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
 distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradlew b/simpleperf/demo/SimpleperfExampleJava/gradlew
similarity index 65%
rename from simpleperf/demo/SimpleperfExamplePureJava/gradlew
rename to simpleperf/demo/SimpleperfExampleJava/gradlew
index 9d82f78..4f906e0 100755
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradlew
+++ b/simpleperf/demo/SimpleperfExampleJava/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+#      https://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.
+#
 
 ##############################################################################
 ##
@@ -6,42 +22,6 @@
 ##
 ##############################################################################
 
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
 # Attempt to set APP_HOME
 # Resolve links: $0 may be a link
 PRG="$0"
@@ -60,8 +40,49 @@
 APP_HOME="`pwd -P`"
 cd "$SAVED" >/dev/null
 
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
+
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -85,7 +106,7 @@
 fi
 
 # Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
     MAX_FD_LIMIT=`ulimit -H -n`
     if [ $? -eq 0 ] ; then
         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -105,10 +126,11 @@
     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
 fi
 
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
     JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
@@ -134,27 +156,30 @@
         else
             eval `echo args$i`="\"$arg\""
         fi
-        i=$((i+1))
+        i=`expr $i + 1`
     done
     case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
     esac
 fi
 
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
 }
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=`save "$@"`
 
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat b/simpleperf/demo/SimpleperfExampleJava/gradlew.bat
similarity index 66%
rename from simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat
rename to simpleperf/demo/SimpleperfExampleJava/gradlew.bat
index aec9973..ac1b06f 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat
+++ b/simpleperf/demo/SimpleperfExampleJava/gradlew.bat
@@ -1,3 +1,19 @@
+@rem

+@rem Copyright 2015 the original author or authors.

+@rem

+@rem Licensed under the Apache License, Version 2.0 (the "License");

+@rem you may not use this file except in compliance with the License.

+@rem You may obtain a copy of the License at

+@rem

+@rem      https://www.apache.org/licenses/LICENSE-2.0

+@rem

+@rem Unless required by applicable law or agreed to in writing, software

+@rem distributed under the License is distributed on an "AS IS" BASIS,

+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+@rem See the License for the specific language governing permissions and

+@rem limitations under the License.

+@rem

+

 @if "%DEBUG%" == "" @echo off

 @rem ##########################################################################

 @rem

@@ -8,20 +24,23 @@
 @rem Set local scope for the variables with windows NT shell

 if "%OS%"=="Windows_NT" setlocal

 

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS=

-

 set DIRNAME=%~dp0

 if "%DIRNAME%" == "" set DIRNAME=.

 set APP_BASE_NAME=%~n0

 set APP_HOME=%DIRNAME%

 

+@rem Resolve any "." and ".." in APP_HOME to make it shorter.

+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

+

 @rem Find java.exe

 if defined JAVA_HOME goto findJavaFromJavaHome

 

 set JAVA_EXE=java.exe

 %JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

+if "%ERRORLEVEL%" == "0" goto execute

 

 echo.

 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

@@ -35,7 +54,7 @@
 set JAVA_HOME=%JAVA_HOME:"=%

 set JAVA_EXE=%JAVA_HOME%/bin/java.exe

 

-if exist "%JAVA_EXE%" goto init

+if exist "%JAVA_EXE%" goto execute

 

 echo.

 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

@@ -45,34 +64,14 @@
 

 goto fail

 

-:init

-@rem Get command-line arguments, handling Windowz variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-if "%@eval[2+2]" == "4" goto 4NT_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-goto execute

-

-:4NT_args

-@rem Get arguments from the 4NT Shell from JP Software

-set CMD_LINE_ARGS=%$

-

 :execute

 @rem Setup the command line

 

 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

 

+

 @rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

 

 :end

 @rem End local scope for the variables with windows NT shell

diff --git a/simpleperf/demo/SimpleperfExampleJava/settings.gradle b/simpleperf/demo/SimpleperfExampleJava/settings.gradle
new file mode 100644
index 0000000..75cefd9
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+rootProject.name = "SimpleperfExampleJava"
+include ':app'
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/.idea/compiler.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/compiler.xml
new file mode 100644
index 0000000..fb7f4a8
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/gradle.xml
similarity index 89%
copy from simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml
copy to simpleperf/demo/SimpleperfExampleKotlin/.idea/gradle.xml
index 7ac24c7..a9f4e52 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/gradle.xml
@@ -3,6 +3,7 @@
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
         <option name="distributionType" value="DEFAULT_WRAPPED" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="modules">
@@ -11,7 +12,6 @@
             <option value="$PROJECT_DIR$/app" />
           </set>
         </option>
-        <option name="resolveModulePerSourceSet" value="false" />
       </GradleProjectSettings>
     </option>
   </component>
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/.idea/misc.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/misc.xml
new file mode 100644
index 0000000..bdd9278
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/misc.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/.idea/vcs.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/vcs.xml
new file mode 100644
index 0000000..c2365ab
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/build.gradle b/simpleperf/demo/SimpleperfExampleKotlin/app/build.gradle
new file mode 100644
index 0000000..0b9f2d5
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+    id 'com.android.application'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    namespace 'simpleperf.example.kotlin'
+    compileSdk 32
+
+    defaultConfig {
+        applicationId "simpleperf.example.kotlin"
+        minSdk 24
+        targetSdk 32
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.core:core-ktx:1.7.0'
+    implementation 'androidx.appcompat:appcompat:1.4.1'
+    implementation 'com.google.android.material:material:1.5.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/build/outputs/apk/debug/app-debug.apk b/simpleperf/demo/SimpleperfExampleKotlin/app/build/outputs/apk/debug/app-debug.apk
new file mode 100644
index 0000000..ffb872e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/build/outputs/apk/debug/app-debug.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro b/simpleperf/demo/SimpleperfExampleKotlin/app/proguard-rules.pro
similarity index 69%
copy from simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro
copy to simpleperf/demo/SimpleperfExampleKotlin/app/proguard-rules.pro
index b9d149a..481bb43 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/proguard-rules.pro
@@ -1,14 +1,10 @@
 # Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /home/yabinc/Android/Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
 #
 # For more details, see
 #   http://developer.android.com/guide/developing/tools/proguard.html
 
-# Add any project specific keep options here:
-
 # If your project uses WebView with JS, uncomment the following
 # and specify the fully qualified class name to the JavaScript interface
 # class:
@@ -22,4 +18,4 @@
 
 # If you keep the line number information, uncomment this to
 # hide the original source file name.
-#-renamesourcefileattribute SourceFile
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/release/app-release.apk b/simpleperf/demo/SimpleperfExampleKotlin/app/release/app-release.apk
new file mode 100644
index 0000000..517a442
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/release/app-release.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/release/output-metadata.json b/simpleperf/demo/SimpleperfExampleKotlin/app/release/output-metadata.json
new file mode 100644
index 0000000..16a114c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+  "version": 3,
+  "artifactType": {
+    "type": "APK",
+    "kind": "Directory"
+  },
+  "applicationId": "simpleperf.example.kotlin",
+  "variantName": "release",
+  "elements": [
+    {
+      "type": "SINGLE",
+      "filters": [],
+      "attributes": [],
+      "versionCode": 1,
+      "versionName": "1.0",
+      "outputFile": "app-release.apk"
+    }
+  ],
+  "elementType": "File"
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/androidTest/java/simpleperf/example/kotlin/ExampleInstrumentedTest.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/androidTest/java/simpleperf/example/kotlin/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..01e4c34
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/androidTest/java/simpleperf/example/kotlin/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package simpleperf.example.kotlin
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("simpleperf.example.kotlin", appContext.packageName)
+    }
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f6de7a
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.SimpleperfExampleKotlin"
+        tools:targetApi="31">
+        <profileable android:shell="true" />
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".SleepActivity"
+            android:exported="true">
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/MainActivity.kt
similarity index 84%
rename from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt
rename to simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/MainActivity.kt
index 55d47aa..785ba3c 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/MainActivity.kt
@@ -1,10 +1,9 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
+package simpleperf.example.kotlin
 
-import android.support.v7.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatActivity
 import android.os.Bundle
 
 class MainActivity : AppCompatActivity() {
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
@@ -26,4 +25,4 @@
             }
         }.start()
     }
-}
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/SleepActivity.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/SleepActivity.kt
similarity index 90%
rename from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/SleepActivity.kt
rename to simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/SleepActivity.kt
index 2ed5afe..cc744e6 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/SleepActivity.kt
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/SleepActivity.kt
@@ -1,14 +1,13 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
+package simpleperf.example.kotlin
 
-import android.support.v7.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatActivity
 import android.os.Bundle
 
 class SleepActivity : AppCompatActivity() {
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_sleep)
-        createRunSleepThread();
+        createRunSleepThread()
     }
 
     fun createRunSleepThread() {
@@ -47,4 +46,4 @@
             }
         }.start()
     }
-}
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable/ic_launcher_background.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..c937b69
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".MainActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Running a busy thread!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_sleep.xml
new file mode 100644
index 0000000..fd8ce42
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_sleep.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".SleepActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Running a half busy half sleep thread!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values-night/themes.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..812d083
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.SimpleperfExampleKotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..af60980
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">SimpleperfExampleKotlin</string>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/themes.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..7568d49
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.SimpleperfExampleKotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/backup_rules.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/data_extraction_rules.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/test/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleUnitTest.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/test/java/simpleperf/example/kotlin/ExampleUnitTest.kt
similarity index 84%
rename from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/test/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleUnitTest.kt
rename to simpleperf/demo/SimpleperfExampleKotlin/app/src/test/java/simpleperf/example/kotlin/ExampleUnitTest.kt
index 39fbbf8..09e76f4 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/test/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleUnitTest.kt
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/test/java/simpleperf/example/kotlin/ExampleUnitTest.kt
@@ -1,4 +1,4 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
+package simpleperf.example.kotlin
 
 import org.junit.Test
 
@@ -14,4 +14,4 @@
     fun addition_isCorrect() {
         assertEquals(4, 2 + 2)
     }
-}
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/build.gradle b/simpleperf/demo/SimpleperfExampleKotlin/build.gradle
new file mode 100644
index 0000000..2536974
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/build.gradle
@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id 'com.android.application' version '7.3.1' apply false
+    id 'com.android.library' version '7.3.1' apply false
+    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
+}
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/gradle.properties b/simpleperf/demo/SimpleperfExampleKotlin/gradle.properties
new file mode 100644
index 0000000..3c5031e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.properties
similarity index 80%
copy from simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties
copy to simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.properties
index be54b8d..f656d2f 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Apr 10 15:23:21 PDT 2018
+#Wed Nov 09 11:27:42 PST 2022
 distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
 distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew b/simpleperf/demo/SimpleperfExampleKotlin/gradlew
similarity index 65%
rename from simpleperf/demo/SimpleperfExampleOfKotlin/gradlew
rename to simpleperf/demo/SimpleperfExampleKotlin/gradlew
index 9d82f78..4f906e0 100755
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+#      https://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.
+#
 
 ##############################################################################
 ##
@@ -6,42 +22,6 @@
 ##
 ##############################################################################
 
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
 # Attempt to set APP_HOME
 # Resolve links: $0 may be a link
 PRG="$0"
@@ -60,8 +40,49 @@
 APP_HOME="`pwd -P`"
 cd "$SAVED" >/dev/null
 
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
+
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -85,7 +106,7 @@
 fi
 
 # Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
     MAX_FD_LIMIT=`ulimit -H -n`
     if [ $? -eq 0 ] ; then
         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -105,10 +126,11 @@
     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
 fi
 
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
     JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
@@ -134,27 +156,30 @@
         else
             eval `echo args$i`="\"$arg\""
         fi
-        i=$((i+1))
+        i=`expr $i + 1`
     done
     case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
     esac
 fi
 
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
 }
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=`save "$@"`
 
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat b/simpleperf/demo/SimpleperfExampleKotlin/gradlew.bat
similarity index 66%
copy from simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat
copy to simpleperf/demo/SimpleperfExampleKotlin/gradlew.bat
index aec9973..ac1b06f 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradlew.bat
@@ -1,3 +1,19 @@
+@rem

+@rem Copyright 2015 the original author or authors.

+@rem

+@rem Licensed under the Apache License, Version 2.0 (the "License");

+@rem you may not use this file except in compliance with the License.

+@rem You may obtain a copy of the License at

+@rem

+@rem      https://www.apache.org/licenses/LICENSE-2.0

+@rem

+@rem Unless required by applicable law or agreed to in writing, software

+@rem distributed under the License is distributed on an "AS IS" BASIS,

+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+@rem See the License for the specific language governing permissions and

+@rem limitations under the License.

+@rem

+

 @if "%DEBUG%" == "" @echo off

 @rem ##########################################################################

 @rem

@@ -8,20 +24,23 @@
 @rem Set local scope for the variables with windows NT shell

 if "%OS%"=="Windows_NT" setlocal

 

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS=

-

 set DIRNAME=%~dp0

 if "%DIRNAME%" == "" set DIRNAME=.

 set APP_BASE_NAME=%~n0

 set APP_HOME=%DIRNAME%

 

+@rem Resolve any "." and ".." in APP_HOME to make it shorter.

+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

+

 @rem Find java.exe

 if defined JAVA_HOME goto findJavaFromJavaHome

 

 set JAVA_EXE=java.exe

 %JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

+if "%ERRORLEVEL%" == "0" goto execute

 

 echo.

 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

@@ -35,7 +54,7 @@
 set JAVA_HOME=%JAVA_HOME:"=%

 set JAVA_EXE=%JAVA_HOME%/bin/java.exe

 

-if exist "%JAVA_EXE%" goto init

+if exist "%JAVA_EXE%" goto execute

 

 echo.

 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

@@ -45,34 +64,14 @@
 

 goto fail

 

-:init

-@rem Get command-line arguments, handling Windowz variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-if "%@eval[2+2]" == "4" goto 4NT_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-goto execute

-

-:4NT_args

-@rem Get arguments from the 4NT Shell from JP Software

-set CMD_LINE_ARGS=%$

-

 :execute

 @rem Setup the command line

 

 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

 

+

 @rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

 

 :end

 @rem End local scope for the variables with windows NT shell

diff --git a/simpleperf/demo/SimpleperfExampleKotlin/settings.gradle b/simpleperf/demo/SimpleperfExampleKotlin/settings.gradle
new file mode 100644
index 0000000..82a9613
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+rootProject.name = "SimpleperfExampleKotlin"
+include ':app'
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.gitignore b/simpleperf/demo/SimpleperfExampleOfKotlin/.gitignore
deleted file mode 100644
index 39fb081..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
-/build
-/captures
-.externalNativeBuild
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/caches/build_file_checksums.ser b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/caches/build_file_checksums.ser
deleted file mode 100644
index 6468d99..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/caches/build_file_checksums.ser
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/codeStyles/Project.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/codeStyles/Project.xml
deleted file mode 100644
index 30aa626..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<component name="ProjectCodeStyleConfiguration">
-  <code_scheme name="Project" version="173">
-    <Objective-C-extensions>
-      <file>
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
-      </file>
-      <class>
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
-      </class>
-      <extensions>
-        <pair source="cpp" header="h" fileNamingConvention="NONE" />
-        <pair source="c" header="h" fileNamingConvention="NONE" />
-      </extensions>
-    </Objective-C-extensions>
-  </code_scheme>
-</component>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/kotlinc.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/kotlinc.xml
deleted file mode 100644
index 1d2cf4e..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/kotlinc.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="KotlinCommonCompilerArguments">
-    <option name="errors">
-      <ArgumentParseErrors />
-    </option>
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/misc.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/misc.xml
deleted file mode 100644
index 99202cc..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/misc.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="NullableNotNullManager">
-    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
-    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
-    <option name="myNullables">
-      <value>
-        <list size="5">
-          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
-          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
-          <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
-          <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
-          <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
-        </list>
-      </value>
-    </option>
-    <option name="myNotNulls">
-      <value>
-        <list size="4">
-          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
-          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
-          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
-          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
-        </list>
-      </value>
-    </option>
-  </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
-    <output url="file://$PROJECT_DIR$/build/classes" />
-  </component>
-  <component name="ProjectType">
-    <option name="id" value="Android" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/modules.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/modules.xml
deleted file mode 100644
index 2bb601f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/modules.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ProjectModuleManager">
-    <modules>
-      <module fileurl="file://$PROJECT_DIR$/SimpleperfExampleOfKotlin.iml" filepath="$PROJECT_DIR$/SimpleperfExampleOfKotlin.iml" />
-      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
-    </modules>
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/runConfigurations.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="RunConfigurationProducerService">
-    <option name="ignoredProducers">
-      <set>
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
-      </set>
-    </option>
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/.gitignore b/simpleperf/demo/SimpleperfExampleOfKotlin/app/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/app/build.gradle
deleted file mode 100644
index 132e64b..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build.gradle
+++ /dev/null
@@ -1,37 +0,0 @@
-apply plugin: 'com.android.application'
-
-apply plugin: 'kotlin-android'
-
-apply plugin: 'kotlin-android-extensions'
-
-apply from: 'profiling.gradle'
-
-android {
-    compileSdkVersion 25
-    buildToolsVersion '27.0.3'
-    defaultConfig {
-        applicationId "com.example.simpleperf.simpleperfexampleofkotlin"
-        minSdkVersion 15
-        targetSdkVersion 26
-        versionCode 1
-        versionName "1.0"
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-    }
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-        }
-    }
-}
-
-dependencies {
-    implementation fileTree(dir: 'libs', include: ['*.jar'])
-    androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', {
-        exclude group: 'com.android.support', module: 'support-annotations'
-    })
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
-    implementation 'com.android.support:appcompat-v7:25.4.0'
-    testImplementation 'junit:junit:4.12'
-    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
-}
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build/outputs/apk/profiling/app-profiling.apk b/simpleperf/demo/SimpleperfExampleOfKotlin/app/build/outputs/apk/profiling/app-profiling.apk
deleted file mode 100644
index 8b69bd4..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build/outputs/apk/profiling/app-profiling.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling.gradle
deleted file mode 100644
index aa23c8d..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-
-// Set when building only part of the abis in the apk.
-def abiFiltersForWrapScript = []
-
-android {
-    buildTypes {
-        profiling {
-            initWith debug
-            externalNativeBuild {
-                cmake {
-                    // cmake Debug build type uses -O0, which makes the code slow.
-                    arguments "-DCMAKE_BUILD_TYPE=Release"
-                }
-            }
-            packagingOptions {
-
-                // Exclude wrap.sh for architectures not built.
-                if (abiFiltersForWrapScript) {
-                    def exclude_abis = ["armeabi", "armeabi-v7a", "arm64-v8a",
-                                        "x86", "x86_64", "mips", "mips64"]
-                            .findAll{ !(it in abiFiltersForWrapScript) }
-                            .collect{ "**/" + it + "/wrap.sh" }
-                    excludes += exclude_abis
-                }
-            }
-
-            // Add lib/xxx/wrap.sh in the apk. This is to enable java profiling on Android O
-            // devices.
-            sourceSets {
-                profiling {
-                    resources {
-                        srcDir {
-                            "profiling_apk_add_dir"
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-def writeWrapScriptToFullyCompileJavaApp(wrapFile) {
-    wrapFile.withWriter { writer ->
-        writer.write('#!/system/bin/sh\n')
-        writer.write('\$@\n')
-    }
-}
-
-task createProfilingApkAddDir {
-    for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"]) {
-        def dir = new File("app/profiling_apk_add_dir/lib/" + abi)
-        dir.mkdirs()
-        def wrapFile = new File(dir, "wrap.sh")
-        writeWrapScriptToFullyCompileJavaApp(wrapFile)
-        println "write file " + wrapFile.path
-    }
-}
-
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips64/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips64/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86_64/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/proguard-rules.pro b/simpleperf/demo/SimpleperfExampleOfKotlin/app/proguard-rules.pro
deleted file mode 100644
index bd13885..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/proguard-rules.pro
+++ /dev/null
@@ -1,25 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /usr/local/google/home/yabinc/Android/Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/androidTest/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleInstrumentedTest.kt b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/androidTest/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleInstrumentedTest.kt
deleted file mode 100644
index 8946158..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/androidTest/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
-
-import android.support.test.InstrumentationRegistry
-import android.support.test.runner.AndroidJUnit4
-
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import org.junit.Assert.*
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
-    @Test
-    fun useAppContext() {
-        // Context of the app under test.
-        val appContext = InstrumentationRegistry.getTargetContext()
-        assertEquals("com.example.simpleperf.simpleperfexampleofkotlin", appContext.packageName)
-    }
-}
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 8cb4074..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.example.simpleperf.simpleperfexampleofkotlin">
-
-    <application android:allowBackup="true"
-         android:icon="@mipmap/ic_launcher"
-         android:label="@string/app_name"
-         android:roundIcon="@mipmap/ic_launcher_round"
-         android:supportsRtl="true"
-         android:theme="@style/AppTheme">
-        <activity android:name=".MainActivity"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity android:name=".SleepActivity"
-             android:exported="true">
-
-        </activity>
-    </application>
-
-</manifest>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 4c7edcf..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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.simpleperfexampleofkotlin.MainActivity">
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Hello World!"
-        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/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_sleep.xml
deleted file mode 100644
index 7043eb1..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_sleep.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?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.simpleperfexampleofkotlin.SleepActivity">
-
-</android.support.constraint.ConstraintLayout>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index 5507303..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 8fab6a3..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 6bc7fcd..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index 1eecc0e..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index ec87dce..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 05ca079..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 6f67f21..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 8bac0f2..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 0327e13..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index bacd3e7..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/colors.xml
deleted file mode 100644
index 3ab3e9c..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <color name="colorPrimary">#3F51B5</color>
-    <color name="colorPrimaryDark">#303F9F</color>
-    <color name="colorAccent">#FF4081</color>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/strings.xml
deleted file mode 100644
index 7972edf..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
-    <string name="app_name">SimpleperfExampleOfKotlin</string>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/styles.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/styles.xml
deleted file mode 100644
index 5885930..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
-
-    <!-- Base application theme. -->
-    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
-        <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
-    </style>
-
-</resources>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/build.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/build.gradle
deleted file mode 100644
index ba06bb9..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
-    ext.kotlin_version = '1.1.2-4'
-    repositories {
-        google()
-        jcenter()
-    }
-    dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.0-alpha09'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
-    }
-}
-
-allprojects {
-    repositories {
-        google()
-        jcenter()
-        mavenCentral()
-    }
-}
-
-task clean(type: Delete) {
-    delete rootProject.buildDir
-}
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle.properties b/simpleperf/demo/SimpleperfExampleOfKotlin/gradle.properties
deleted file mode 100644
index aac7c9b..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372ae..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/settings.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/settings.gradle
deleted file mode 100644
index e7b4def..0000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.gitignore b/simpleperf/demo/SimpleperfExamplePureJava/.gitignore
deleted file mode 100644
index 39fb081..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
-/build
-/captures
-.externalNativeBuild
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/caches/build_file_checksums.ser b/simpleperf/demo/SimpleperfExamplePureJava/.idea/caches/build_file_checksums.ser
deleted file mode 100644
index 5ad3daf..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/caches/build_file_checksums.ser
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/codeStyles/Project.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/codeStyles/Project.xml
deleted file mode 100644
index 30aa626..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<component name="ProjectCodeStyleConfiguration">
-  <code_scheme name="Project" version="173">
-    <Objective-C-extensions>
-      <file>
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
-      </file>
-      <class>
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
-        <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
-      </class>
-      <extensions>
-        <pair source="cpp" header="h" fileNamingConvention="NONE" />
-        <pair source="c" header="h" fileNamingConvention="NONE" />
-      </extensions>
-    </Objective-C-extensions>
-  </code_scheme>
-</component>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/compiler.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/compiler.xml
deleted file mode 100644
index 96cc43e..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/compiler.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="CompilerConfiguration">
-    <resourceExtensions />
-    <wildcardResourcePatterns>
-      <entry name="!?*.java" />
-      <entry name="!?*.form" />
-      <entry name="!?*.class" />
-      <entry name="!?*.groovy" />
-      <entry name="!?*.scala" />
-      <entry name="!?*.flex" />
-      <entry name="!?*.kt" />
-      <entry name="!?*.clj" />
-      <entry name="!?*.aj" />
-    </wildcardResourcePatterns>
-    <annotationProcessing>
-      <profile default="true" name="Default" enabled="false">
-        <processorPath useClasspath="true" />
-      </profile>
-    </annotationProcessing>
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/copyright/profiles_settings.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/copyright/profiles_settings.xml
deleted file mode 100644
index e7bedf3..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/copyright/profiles_settings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<component name="CopyrightManager">
-  <settings default="" />
-</component>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/gradle.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/gradle.xml
deleted file mode 100644
index 7ac24c7..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/gradle.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="GradleSettings">
-    <option name="linkedExternalProjectsSettings">
-      <GradleProjectSettings>
-        <option name="distributionType" value="DEFAULT_WRAPPED" />
-        <option name="externalProjectPath" value="$PROJECT_DIR$" />
-        <option name="modules">
-          <set>
-            <option value="$PROJECT_DIR$" />
-            <option value="$PROJECT_DIR$/app" />
-          </set>
-        </option>
-        <option name="resolveModulePerSourceSet" value="false" />
-      </GradleProjectSettings>
-    </option>
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/misc.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/misc.xml
deleted file mode 100644
index ba7052b..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/misc.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="NullableNotNullManager">
-    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
-    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
-    <option name="myNullables">
-      <value>
-        <list size="4">
-          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
-          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
-          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
-          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
-        </list>
-      </value>
-    </option>
-    <option name="myNotNulls">
-      <value>
-        <list size="4">
-          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
-          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
-          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
-          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
-        </list>
-      </value>
-    </option>
-  </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
-    <output url="file://$PROJECT_DIR$/build/classes" />
-  </component>
-  <component name="ProjectType">
-    <option name="id" value="Android" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/modules.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/modules.xml
deleted file mode 100644
index 5b6be61..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/modules.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ProjectModuleManager">
-    <modules>
-      <module fileurl="file://$PROJECT_DIR$/SimpleperfExamplePureJava.iml" filepath="$PROJECT_DIR$/SimpleperfExamplePureJava.iml" />
-      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
-    </modules>
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/runConfigurations.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="RunConfigurationProducerService">
-    <option name="ignoredProducers">
-      <set>
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
-      </set>
-    </option>
-  </component>
-</project>
\ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/.gitignore b/simpleperf/demo/SimpleperfExamplePureJava/app/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/build.gradle b/simpleperf/demo/SimpleperfExamplePureJava/app/build.gradle
deleted file mode 100644
index 0dcc199..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-apply plugin: 'com.android.application'
-
-apply from: 'profiling.gradle'
-
-android {
-    compileSdkVersion 25
-    buildToolsVersion '27.0.3'
-    defaultConfig {
-        applicationId "com.example.simpleperf.simpleperfexamplepurejava"
-        minSdkVersion 15
-        targetSdkVersion 25
-        versionCode 1
-        versionName "1.0"
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-    }
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-        }
-    }
-}
-
-dependencies {
-    implementation fileTree(dir: 'libs', include: ['*.jar'])
-    androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', {
-        exclude group: 'com.android.support', module: 'support-annotations'
-    })
-    implementation 'com.android.support:appcompat-v7:25.3.1'
-    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
-    testImplementation 'junit:junit:4.12'
-}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/profiling/app-profiling.apk b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/profiling/app-profiling.apk
deleted file mode 100644
index f7ead36..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/profiling/app-profiling.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle
deleted file mode 100644
index aa23c8d..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-
-// Set when building only part of the abis in the apk.
-def abiFiltersForWrapScript = []
-
-android {
-    buildTypes {
-        profiling {
-            initWith debug
-            externalNativeBuild {
-                cmake {
-                    // cmake Debug build type uses -O0, which makes the code slow.
-                    arguments "-DCMAKE_BUILD_TYPE=Release"
-                }
-            }
-            packagingOptions {
-
-                // Exclude wrap.sh for architectures not built.
-                if (abiFiltersForWrapScript) {
-                    def exclude_abis = ["armeabi", "armeabi-v7a", "arm64-v8a",
-                                        "x86", "x86_64", "mips", "mips64"]
-                            .findAll{ !(it in abiFiltersForWrapScript) }
-                            .collect{ "**/" + it + "/wrap.sh" }
-                    excludes += exclude_abis
-                }
-            }
-
-            // Add lib/xxx/wrap.sh in the apk. This is to enable java profiling on Android O
-            // devices.
-            sourceSets {
-                profiling {
-                    resources {
-                        srcDir {
-                            "profiling_apk_add_dir"
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-def writeWrapScriptToFullyCompileJavaApp(wrapFile) {
-    wrapFile.withWriter { writer ->
-        writer.write('#!/system/bin/sh\n')
-        writer.write('\$@\n')
-    }
-}
-
-task createProfilingApkAddDir {
-    for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"]) {
-        def dir = new File("app/profiling_apk_add_dir/lib/" + abi)
-        dir.mkdirs()
-        def wrapFile = new File(dir, "wrap.sh")
-        writeWrapScriptToFullyCompileJavaApp(wrapFile)
-        println "write file " + wrapFile.path
-    }
-}
-
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips64/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips64/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86_64/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
deleted file mode 100644
index 047ea6f..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/androidTest/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleInstrumentedTest.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/androidTest/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleInstrumentedTest.java
deleted file mode 100644
index cb91806..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/androidTest/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
-
-import static org.junit.Assert.*;
-
-import android.content.Context;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Instrumentation test, which will execute on an Android device.
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
-    @Test
-    public void useAppContext() throws Exception {
-        // Context of the app under test.
-        Context appContext = InstrumentationRegistry.getTargetContext();
-
-        assertEquals("com.example.simpleperf.simpleperfexamplepurejava", appContext.getPackageName());
-    }
-}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
deleted file mode 100644
index ee42fea..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.example.simpleperf.simpleperfexamplepurejava">
-
-    <application android:allowBackup="true"
-         android:icon="@mipmap/ic_launcher"
-         android:label="@string/app_name"
-         android:roundIcon="@mipmap/ic_launcher_round"
-         android:supportsRtl="true"
-         android:theme="@style/AppTheme">
-        <activity android:name=".MainActivity"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity android:name=".SleepActivity"
-             android:exported="true"/>
-        <activity android:name=".MultiProcessActivity"
-             android:exported="true"/>
-
-        <service android:name=".MultiProcessService"
-             android:process=":multiprocess_service"/>
-    </application>
-
-</manifest>
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
deleted file mode 100644
index 4a09b1a..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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.MainActivity">
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="MainActivity"
-        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_multi_process.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
deleted file mode 100644
index f97b72e..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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
deleted file mode 100644
index f732f77..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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.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/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index cde69bc..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 9a078e3..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c133a0c..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index efc028a..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index bfa42f0..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 3af2608..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72c..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 9bec2e6..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index aee44e1..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 34947cd..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/colors.xml
deleted file mode 100644
index 3ab3e9c..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <color name="colorPrimary">#3F51B5</color>
-    <color name="colorPrimaryDark">#303F9F</color>
-    <color name="colorAccent">#FF4081</color>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/strings.xml
deleted file mode 100644
index 9cb14df..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
-    <string name="app_name">SimpleperfExamplePureJava</string>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/styles.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/styles.xml
deleted file mode 100644
index 5885930..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
-
-    <!-- Base application theme. -->
-    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
-        <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
-    </style>
-
-</resources>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/build.gradle b/simpleperf/demo/SimpleperfExamplePureJava/build.gradle
deleted file mode 100644
index 5af83b4..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
-    repositories {
-        jcenter()
-        google()
-    }
-    dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.0-alpha09'
-
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
-    }
-}
-
-allprojects {
-    repositories {
-        google()
-        jcenter()
-    }
-}
-
-task clean(type: Delete) {
-    delete rootProject.buildDir
-}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradle.properties b/simpleperf/demo/SimpleperfExamplePureJava/gradle.properties
deleted file mode 100644
index aac7c9b..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradle.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372ae..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.properties b/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 275de5d..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Apr 10 14:48:08 PDT 2018
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradlew.bat b/simpleperf/demo/SimpleperfExamplePureJava/gradlew.bat
deleted file mode 100644
index aec9973..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@if "%DEBUG%" == "" @echo off

-@rem ##########################################################################

-@rem

-@rem  Gradle startup script for Windows

-@rem

-@rem ##########################################################################

-

-@rem Set local scope for the variables with windows NT shell

-if "%OS%"=="Windows_NT" setlocal

-

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS=

-

-set DIRNAME=%~dp0

-if "%DIRNAME%" == "" set DIRNAME=.

-set APP_BASE_NAME=%~n0

-set APP_HOME=%DIRNAME%

-

-@rem Find java.exe

-if defined JAVA_HOME goto findJavaFromJavaHome

-

-set JAVA_EXE=java.exe

-%JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

-

-echo.

-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:findJavaFromJavaHome

-set JAVA_HOME=%JAVA_HOME:"=%

-set JAVA_EXE=%JAVA_HOME%/bin/java.exe

-

-if exist "%JAVA_EXE%" goto init

-

-echo.

-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:init

-@rem Get command-line arguments, handling Windowz variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-if "%@eval[2+2]" == "4" goto 4NT_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-goto execute

-

-:4NT_args

-@rem Get arguments from the 4NT Shell from JP Software

-set CMD_LINE_ARGS=%$

-

-:execute

-@rem Setup the command line

-

-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

-

-@rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

-

-:end

-@rem End local scope for the variables with windows NT shell

-if "%ERRORLEVEL%"=="0" goto mainEnd

-

-:fail

-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

-rem the _cmd.exe /c_ return code!

-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

-exit /b 1

-

-:mainEnd

-if "%OS%"=="Windows_NT" endlocal

-

-:omega

diff --git a/simpleperf/demo/SimpleperfExamplePureJava/settings.gradle b/simpleperf/demo/SimpleperfExamplePureJava/settings.gradle
deleted file mode 100644
index e7b4def..0000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index 51ffc83..d1ecbcc 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -1,5 +1,9 @@
 # Simpleperf
 
+Android Studio includes a graphical front end to Simpleperf, documented in
+[Inspect CPU activity with CPU Profiler](https://developer.android.com/studio/profile/cpu-profiler).
+Most users will prefer to use that instead of using Simpleperf directly.
+
 Simpleperf is a native CPU profiling tool for Android. It can be used to profile
 both Android applications and native processes running on Android. It can
 profile both Java and C++ code on Android. The simpleperf executable can run on Android >=L,
@@ -57,6 +61,7 @@
 
 3. Scripts used for parsing profiling data, like simpleperf_report_lib.py.
 
+The python scripts are tested on Python >= 3.9. Older versions may not be supported.
 Detailed documentation for the Python scripts is [here](#scripts-reference).
 
 
@@ -101,60 +106,124 @@
 
 ## Answers to common issues
 
-### Why we suggest profiling on Android >= N devices?
+### Support on different Android versions
 
-1. Running on a device reflects a real running situation, so we suggest
-   profiling on real devices instead of emulators.
-2. To profile Java code, we need ART running in oat mode, which is only
-   available >= L for rooted devices, and >= N for non-rooted devices.
-3. Old Android versions are likely to be shipped with old kernels (< 3.18),
-   which may not support profiling features like recording dwarf based call graphs.
-4. Old Android versions are likely to be shipped with Arm32 chips. In Arm32
-   mode, recording stack frame based call graphs doesn't work well.
+On Android < N, the kernel may be too old (< 3.18) to support features like recording DWARF
+based call graphs.
+On Android M - O, we can only profile C++ code and fully compiled Java code.
+On Android >= P, the ART interpreter supports DWARF based unwinding. So we can profile Java code.
+On Android >= Q, we can used simpleperf shipped on device to profile released Android apps, with
+  `<profileable android:shell="true" />`.
 
-### Suggestions about recording call graphs
 
-Below is our experiences of dwarf based call graphs and stack frame based call graphs.
+### Comparing DWARF based and stack frame based call graphs
 
-dwarf based call graphs:
-1. Need support of debug information in binaries.
-2. Behave normally well on both ARM and ARM64, for both fully compiled Java code and C++ code.
-3. Can only unwind 64K stack for each sample. So usually can't show complete flamegraph. But
-   probably is enough for users to identify hot places.
-4. Take more CPU time than stack frame based call graphs. So the sample frequency is suggested
-   to be 1000 Hz. Thus at most 1000 samples per second.
+Simpleperf supports two ways recording call stacks with samples. One is DWARF based call graph,
+the other is stack frame based call graph. Below is their comparison:
 
-stack frame based call graphs:
-1. Need support of stack frame registers.
-2. Don't work well on ARM. Because ARM is short of registers, and ARM and THUMB code have different
-   stack frame registers. So the kernel can't unwind user stack containing both ARM/THUMB code.
-3. Also don't work well on fully compiled Java code on ARM64. Because the ART compiler doesn't
-   reserve stack frame registers.
-4. Work well when profiling native programs on ARM64. One example is profiling surfacelinger. And
+Recording DWARF based call graph:
+1. Needs support of debug information in binaries.
+2. Behaves normally well on both ARM and ARM64, for both Java code and C++ code.
+3. Can only unwind 64K stack for each sample. So it isn't always possible to unwind to the bottom.
+   However, this is alleviated in simpleperf, as explained in the next section.
+4. Takes more CPU time than stack frame based call graphs. So it has higher overhead, and can't
+   sample at very high frequency (usually <= 4000 Hz).
+
+Recording stack frame based call graph:
+1. Needs support of stack frame registers.
+2. Doesn't work well on ARM. Because ARM is short of registers, and ARM and THUMB code have
+   different stack frame registers. So the kernel can't unwind user stack containing both ARM and
+   THUMB code.
+3. Also doesn't work well on Java code. Because the ART compiler doesn't reserve stack frame
+   registers. And it can't get frames for interpreted Java code.
+4. Works well when profiling native programs on ARM64. One example is profiling surfacelinger. And
    usually shows complete flamegraph when it works well.
-5. Take less CPU time than dwarf based call graphs. So the sample frequency can be 4000 Hz or
+5. Takes much less CPU time than DWARF based call graphs. So the sample frequency can be 10000 Hz or
    higher.
 
-So if you need to profile code on ARM or profile fully compiled Java code, dwarf based call graphs
-may be better. If you need to profile C++ code on ARM64, stack frame based call graphs may be
-better. After all, you can always try dwarf based call graph first, because it always produces
-reasonable results when given unstripped binaries properly. If it doesn't work well enough, then
-try stack frame based call graphs instead.
+So if you need to profile code on ARM or profile Java code, DWARF based call graph is better. If you
+need to profile C++ code on ARM64, stack frame based call graphs may be better. After all, you can
+fisrt try DWARF based call graph, which is also the default option when `-g` is used. Because it
+always produces reasonable results. If it doesn't work well enough, then try stack frame based call
+graph instead.
 
-Simpleperf may need unstripped native binaries on the device to generate good dwarf based call
-graphs. It can be supported by downloading unstripped native libraries on device, as [here](#fix-broken-callchain-stopped-at-c-functions).
 
-### Why we can't always get complete DWARF-based call graphs?
+### Fix broken DWARF based call graph
 
-DWARF-based call graphs are generated by unwinding thread stacks. When a sample is generated, up to
-64KB stack data is dumped by the kernel. By unwinding the stack based on dwarf information, we get
-a callchain. But the thread stack can be much longer than 64KB. In that case, we can't unwind to
-the thread start point.
+A DWARF-based call graph is generated by unwinding thread stacks. When a sample is recorded, a
+kernel dumps up to 64 kilobytes of stack data. By unwinding the stack based on DWARF information,
+we can get a call stack.
 
-To alleviate the problem, simpleperf joins callchains after recording them. If two callchains of
-a thread have an entry containing the same ip and sp address, then simpleperf tries to join them to
-make the callchains longer. In that case, the longer we run, the more samples we get. This makes it
-more likely to get complete callchains, but it's still not guaranteed to get complete call graphs.
+Two reasons may cause a broken call stack:
+1. The kernel can only dump up to 64 kilobytes of stack data for each sample, but a thread can have
+   much larger stack. In this case, we can't unwind to the thread start point.
+
+2. We need binaries containing DWARF call frame information to unwind stack frames. The binary
+   should have one of the following sections: .eh_frame, .debug_frame, .ARM.exidx or .gnu_debugdata.
+
+To mitigate these problems,
+
+
+For the missing stack data problem:
+1. To alleviate it, simpleperf joins callchains (call stacks) after recording. If two callchains of
+   a thread have an entry containing the same ip and sp address, then simpleperf tries to join them
+   to make the callchains longer. So we can get more complete callchains by recording longer and
+   joining more samples. This doesn't guarantee to get complete call graphs. But it usually works
+   well.
+
+2. Simpleperf stores samples in a buffer before unwinding them. If the bufer is low in free space,
+   simpleperf may decide to cut stack data for a sample to 1K. Hopefully, this can be recovered by
+   callchain joiner. But when a high percentage of samples are cut, many callchains can be broken.
+   We can tell if many samples are cut in the record command output, like:
+
+```sh
+$ simpleperf record ...
+simpleperf I cmd_record.cpp:809] Samples recorded: 105584 (cut 86291). Samples lost: 6501.
+```
+
+   There are two ways to avoid cutting samples. One is increasing the buffer size, like
+   `--user-buffer-size 1G`. But `--user-buffer-size` is only available on latest simpleperf. If that
+   option isn't available, we can use `--no-cut-samples` to disable cutting samples.
+
+For the missing DWARF call frame info problem:
+1. Most C++ code generates binaries containing call frame info, in .eh_frame or .ARM.exidx sections.
+   These sections are not stripped, and are usually enough for stack unwinding.
+
+2. For C code and a small percentage of C++ code that the compiler is sure will not generate
+   exceptions, the call frame info is generated in .debug_frame section. .debug_frame section is
+   usually stripped with other debug sections. One way to fix it, is to download unstripped binaries
+   on device, as [here](#fix-broken-callchain-stopped-at-c-functions).
+
+3. The compiler doesn't generate unwind instructions for function prologue and epilogue. Because
+   they operates stack frames and will not generate exceptions. But profiling may hit these
+   instructions, and fails to unwind them. This usually doesn't matter in a frame graph. But in a
+   time based Stack Chart (like in Android Studio and Firefox profiler), this causes stack gaps once
+   in a while. We can remove stack gaps via `--remove-gaps`, which is already enabled by default.
+
+
+### Fix broken callchain stopped at C functions
+
+When using dwarf based call graphs, simpleperf generates callchains during recording to save space.
+The debug information needed to unwind C functions is in .debug_frame section, which is usually
+stripped in native libraries in apks. To fix this, we can download unstripped version of native
+libraries on device, and ask simpleperf to use them when recording.
+
+To use simpleperf directly:
+
+```sh
+# create native_libs dir on device, and push unstripped libs in it (nested dirs are not supported).
+$ adb shell mkdir /data/local/tmp/native_libs
+$ adb push <unstripped_dir>/*.so /data/local/tmp/native_libs
+# run simpleperf record with --symfs option.
+$ adb shell simpleperf record xxx --symfs /data/local/tmp/native_libs
+```
+
+To use app_profiler.py:
+
+```sh
+$ ./app_profiler.py -lib <unstripped_dir>
+```
+
 
 ### How to solve missing symbols in report?
 
@@ -179,28 +248,6 @@
 $ ./report_html.py
 ```
 
-### Fix broken callchain stopped at C functions
-
-When using dwarf based call graphs, simpleperf generates callchains during recording to save space.
-The debug information needed to unwind C functions is in .debug_frame section, which is usually
-stripped in native libraries in apks. To fix this, we can download unstripped version of native
-libraries on device, and ask simpleperf to use them when recording.
-
-To use simpleperf directly:
-
-```sh
-# create native_libs dir on device, and push unstripped libs in it (nested dirs are not supported).
-$ adb shell mkdir /data/local/tmp/native_libs
-$ adb push <unstripped_dir>/*.so /data/local/tmp/native_libs
-# run simpleperf record with --symfs option.
-$ adb shell simpleperf record xxx --symfs /data/local/tmp/native_libs
-```
-
-To use app_profiler.py:
-
-```sh
-$ ./app_profiler.py -lib <unstripped_dir>
-```
 
 ### Show annotated source code and disassembly
 
@@ -238,3 +285,13 @@
 
 If built successfully, out/target/product/generic_arm64/system/bin/simpleperf is for ARM64, and
 out/target/product/generic_arm64/system/bin/simpleperf32 is for ARM.
+
+The source code of simpleperf python scripts is in [system/extras/simpleperf/scripts](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/scripts/).
+Most scripts rely on simpleperf binaries to work. To update binaries for scripts (using linux
+x86_64 host and android arm64 target as an example):
+```sh
+$ cp out/host/linux-x86/lib64/libsimpleperf_report.so system/extras/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
+$ cp out/target/product/generic_arm64/system/bin/simpleperf_ndk64 system/extras/simpleperf/scripts/bin/android/arm64/simpleperf
+```
+
+Then you can try the latest simpleperf scripts and binaries in system/extras/simpleperf/scripts.
diff --git a/simpleperf/doc/android_application_profiling.md b/simpleperf/doc/android_application_profiling.md
index 65fae75..5ee46a5 100644
--- a/simpleperf/doc/android_application_profiling.md
+++ b/simpleperf/doc/android_application_profiling.md
@@ -70,7 +70,7 @@
 }
 
 task createWrapShLibDir
-    for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) {
+    for (String abi : ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) {
         def dir = new File("app/wrap_sh_lib_dir/lib/" + abi)
         dir.mkdirs()
         def wrapFile = new File(dir, "wrap.sh")
diff --git a/simpleperf/doc/collect_etm_data_for_autofdo.md b/simpleperf/doc/collect_etm_data_for_autofdo.md
index 145c0ad..2c00101 100644
--- a/simpleperf/doc/collect_etm_data_for_autofdo.md
+++ b/simpleperf/doc/collect_etm_data_for_autofdo.md
@@ -81,9 +81,18 @@
 
 We need to split perf_inject.data, and make sure one file only contains info for one binary.
 
-Then we can use [AutoFDO](https://github.com/google/autofdo) to create profile like below:
+Then we can use [AutoFDO](https://github.com/google/autofdo) to create profile. AutoFDO only works
+for binaries having an executable segment as its first loadable segment. But binaries built in
+Android may not follow this rule. Simpleperf inject command knows how to work around this problem.
+But there is a check in AutoFDO forcing binaries to start with an executable segment. We need to
+disable the check in AutoFDO, by commenting out L127-L136 in
+https://github.com/google/autofdo/commit/188db2834ce74762ed17108ca344916994640708#diff-2d132ecbb5e4f13e0da65419f6d1759dd27d6b696786dd7096c0c34d499b1710R127-R136.
+Then we can use `create_llvm_prof` in AutoFDO to create profiles used by clang.
 
 ```sh
+# perf_inject_binary1.data is split from perf_inject.data, and only contains branch info for binary1.
+host $ autofdo/create_llvm_prof -profile perf_inject_binary1.data -profiler text -binary path_of_binary1 -out a.prof -format binary
+
 # perf_inject_kernel.data is split from perf_inject.data, and only contains branch info for [kernel.kallsyms].
 host $ autofdo/create_llvm_prof -profile perf_inject_kernel.data -profiler text -binary vmlinux -out a.prof -format binary
 ```
@@ -91,6 +100,96 @@
 Then we can use a.prof for PGO during compilation, via `-fprofile-sample-use=a.prof`.
 [Here](https://clang.llvm.org/docs/UsersManual.html#using-sampling-profilers) are more details.
 
+### A complete example: etm_test_loop.cpp
+
+`etm_test_loop.cpp` is an example to show the complete process.
+The source code is in [etm_test_loop.cpp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/runtest/etm_test_loop.cpp).
+The build script is in [Android.bp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/runtest/Android.bp).
+It builds an executable called `etm_test_loop`, which runs on device.
+
+Step 1: Build `etm_test_loop` binary.
+
+```sh
+(host) <AOSP>$ . build/envsetup.sh
+(host) <AOSP>$ lunch aosp_arm64-userdebug
+(host) <AOSP>$ make etm_test_loop
+```
+
+Step 2: Run `etm_test_loop` on device, and collect ETM data for its running.
+
+```sh
+(host) <AOSP>$ adb push out/target/product/generic_arm64/system/bin/etm_test_loop /data/local/tmp
+(host) <AOSP>$ adb root
+(host) <AOSP>$ adb shell
+(device) / # cd /data/local/tmp
+(device) /data/local/tmp # chmod a+x etm_test_loop
+(device) /data/local/tmp # simpleperf record -e cs-etm:u ./etm_test_loop
+simpleperf I cmd_record.cpp:729] Recorded for 0.0370068 seconds. Start post processing.
+simpleperf I cmd_record.cpp:799] Aux data traced: 1689136
+(device) /data/local/tmp # simpleperf inject -i perf.data --output branch-list -o branch_list.data
+simpleperf W dso.cpp:557] failed to read min virtual address of [vdso]: File not found
+(device) /data/local/tmp # exit
+(host) <AOSP>$ adb pull /data/local/tmp/branch_list.data
+```
+
+Step 3: Convert ETM data to AutoFDO data.
+
+```sh
+# Build simpleperf tool on host.
+(host) <AOSP>$ make simpleperf_ndk
+(host) <AOSP>$ simpleperf_ndk64 inject -i branch_list.data -o perf_inject_etm_test_loop.data --symdir out/target/product/generic_arm64/symbols/system/bin
+simpleperf W cmd_inject.cpp:505] failed to build instr ranges for binary [vdso]: File not found
+(host) <AOSP>$ cat perf_inject_etm_test_loop.data
+13
+1000-1010:1
+1014-1050:1
+...
+112c->0:1
+// /data/local/tmp/etm_test_loop
+
+(host) <AOSP>$ create_llvm_prof -profile perf_inject_etm_test_loop.data -profiler text -binary out/target/product/generic_arm64/symbols/system/bin/etm_test_loop -out etm_test_loop.afdo -format binary
+(host) <AOSP>$ ls -lh etm_test_loop.afdo
+rw-r--r-- 1 user group 241 Aug 29 16:04 etm_test_loop.afdo
+```
+
+Step 4: Use AutoFDO data to build optimized binary.
+
+```sh
+(host) <AOSP>$ mkdir toolchain/pgo-profiles/sampling/
+(host) <AOSP>$ cp etm_test_loop.afdo toolchain/pgo-profiles/sampling/
+(host) <AOSP>$ vi toolchain/pgo-profiles/sampling/Android.bp
+# edit Android.bp to add a fdo_profile module
+# soong_namespace {}
+#
+# fdo_profile {
+#    name: "etm_test_loop_afdo",
+#    profile: ["etm_test_loop.afdo"],
+# }
+```
+
+`soong_namespace` is added to support fdo_profile modules with the same name
+
+In a product config mk file, update `PRODUCT_AFDO_PROFILES` with
+
+```make
+PRODUCT_AFDO_PROFILES += etm_test_loop://toolchain/pgo-profiles/sampling:etm_test_loop_afdo
+```
+
+```sh
+(host) <AOSP>$ vi system/extras/simpleperf/runtest/Android.bp
+# edit Android.bp to enable afdo for etm_test_loop.
+# cc_binary {
+#    name: "etm_test_loop",
+#    srcs: ["etm_test_loop.cpp"],
+#    afdo: true,
+# }
+(host) <AOSP>$ make etm_test_loop
+```
+
+If comparing the disassembly of `out/target/product/generic_arm64/symbols/system/bin/etm_test_loop`
+before and after optimizing with AutoFDO data, we can see different preferences when branching.
+
+
 ## Collect ETM data with a daemon
 
 Android also has a daemon collecting ETM data periodically. It only runs on userdebug and eng
@@ -138,6 +237,15 @@
 fulfill the request, simpleperf will report out of memory error. Fortunately, we can use
 "arm,scatter-gather" flag to let ETR run in scatter gather mode, which uses non-contiguous memory.
 
+
+### A possible problem: trace_id mismatch
+
+Each CPU has an ETM device, which has a unique trace_id assigned from the kernel.
+The formula is: `trace_id = 0x10 + cpu * 2`, as in https://github.com/torvalds/linux/blob/master/include/linux/coresight-pmu.h#L37.
+If the formula is modified by local patches, then simpleperf inject command can't parse ETM data
+properly and is likely to give empty output.
+
+
 ## Enable ETM in the bootloader
 
 Unless ARMv8.4 Self-hosted Trace extension is implemented, ETM is considered as an external debug
diff --git a/simpleperf/doc/executable_commands_reference.md b/simpleperf/doc/executable_commands_reference.md
index ccff5ce..0e29fbf 100644
--- a/simpleperf/doc/executable_commands_reference.md
+++ b/simpleperf/doc/executable_commands_reference.md
@@ -98,14 +98,17 @@
 $ simpleperf stat -p 7394 --duration 10
 Performance counter statistics:
 
- 1,320,496,145  cpu-cycles         # 0.131736 GHz                     (100%)
-   510,426,028  instructions       # 2.587047 cycles per instruction  (100%)
-     4,692,338  branch-misses      # 468.118 K/sec                    (100%)
-886.008130(ms)  task-clock         # 0.088390 cpus used               (100%)
-           753  context-switches   # 75.121 /sec                      (100%)
-           870  page-faults        # 86.793 /sec                      (100%)
+#         count  event_name                # count / runtime
+     16,513,564  cpu-cycles                # 1.612904 GHz
+      4,564,133  stalled-cycles-frontend   # 341.490 M/sec
+      6,520,383  stalled-cycles-backend    # 591.666 M/sec
+      4,900,403  instructions              # 612.859 M/sec
+         47,821  branch-misses             # 6.085 M/sec
+  25.274251(ms)  task-clock                # 0.002520 cpus used
+              4  context-switches          # 158.264 /sec
+            466  page-faults               # 18.438 K/sec
 
-Total test time: 10.023829 seconds.
+Total test time: 10.027923 seconds.
 ```
 
 ### Select events to stat
@@ -122,9 +125,8 @@
 
 When running the stat command, if the number of hardware events is larger than the number of
 hardware counters available in the PMU, the kernel shares hardware counters between events, so each
-event is only monitored for part of the total time. In the example below, there is a percentage at
-the end of each row, showing the percentage of the total time that each event was actually
-monitored.
+event is only monitored for part of the total time. As a result, the number of events shown is
+smaller than the number of events that actually happened. The following is an example.
 
 ```sh
 # Stat using event cache-references, cache-references:u,....
@@ -132,37 +134,66 @@
       -e cache-misses,cache-misses:u,cache-misses:k,instructions --duration 1
 Performance counter statistics:
 
-4,331,018  cache-references     # 4.861 M/sec    (87%)
-3,064,089  cache-references:u   # 3.439 M/sec    (87%)
-1,364,959  cache-references:k   # 1.532 M/sec    (87%)
-   91,721  cache-misses         # 102.918 K/sec  (87%)
-   45,735  cache-misses:u       # 51.327 K/sec   (87%)
-   38,447  cache-misses:k       # 43.131 K/sec   (87%)
-9,688,515  instructions         # 10.561 M/sec   (89%)
+#   count  event_name           # count / runtime
+  490,713  cache-references     # 151.682 M/sec
+  899,652  cache-references:u   # 130.152 M/sec
+  855,218  cache-references:k   # 111.356 M/sec
+   61,602  cache-misses         # 7.710 M/sec
+   33,282  cache-misses:u       # 5.050 M/sec
+   11,662  cache-misses:k       # 4.478 M/sec
+        0  instructions         #
 
-Total test time: 1.026802 seconds.
+Total test time: 1.000867 seconds.
+simpleperf W cmd_stat.cpp:946] It seems the number of hardware events are more than the number of
+available CPU PMU hardware counters. That will trigger hardware counter
+multiplexing. As a result, events are not counted all the time processes
+running, and event counts are smaller than what really happens.
+Use --print-hw-counter to show available hardware counters.
 ```
 
-In the example above, each event is monitored about 87% of the total time. But there is no
-guarantee that any pair of events are always monitored at the same time. If we want to have some
-events monitored at the same time, we can use --group.
+In the example above, we monitor 7 events. Each event is only monitored part of the total time.
+Because the number of cache-references is smaller than the number of cache-references:u
+(cache-references only in userspace) and cache-references:k (cache-references only in kernel).
+The number of instructions is zero. After printing the result, simpleperf checks if CPUs have
+enough hardware counters to count hardware events at the same time. If not, it prints a warning.
+
+To avoid hardware counter multiplexing, we can use `simpleperf stat --print-hw-counter` to show
+available counters on each CPU. Then don't monitor more hardware events than counters available.
+
+```sh
+$ simpleperf stat --print-hw-counter
+There are 2 CPU PMU hardware counters available on cpu 0.
+There are 2 CPU PMU hardware counters available on cpu 1.
+There are 2 CPU PMU hardware counters available on cpu 2.
+There are 2 CPU PMU hardware counters available on cpu 3.
+There are 2 CPU PMU hardware counters available on cpu 4.
+There are 2 CPU PMU hardware counters available on cpu 5.
+There are 2 CPU PMU hardware counters available on cpu 6.
+There are 2 CPU PMU hardware counters available on cpu 7.
+```
+
+When counter multiplexing happens, there is no guarantee of which events will be monitored at
+which time. If we want to ensure some events are always monitored at the same time, we can use
+`--group`.
 
 ```sh
 # Stat using event cache-references, cache-references:u,....
 $ simpleperf stat -p 7964 --group cache-references,cache-misses \
       --group cache-references:u,cache-misses:u --group cache-references:k,cache-misses:k \
-      -e instructions --duration 1
+      --duration 1
 Performance counter statistics:
 
-3,638,900  cache-references     # 4.786 M/sec          (74%)
-   65,171  cache-misses         # 1.790953% miss rate  (74%)
-2,390,433  cache-references:u   # 3.153 M/sec          (74%)
-   32,280  cache-misses:u       # 1.350383% miss rate  (74%)
-  879,035  cache-references:k   # 1.251 M/sec          (68%)
-   30,303  cache-misses:k       # 3.447303% miss rate  (68%)
-8,921,161  instructions         # 10.070 M/sec         (86%)
+#     count  event_name           # count / runtime
+  2,088,463  cache-references     # 181.360 M/sec
+     47,871  cache-misses         # 2.292164% miss rate
+  1,277,600  cache-references:u   # 136.419 M/sec
+     25,977  cache-misses:u       # 2.033265% miss rate
+    326,305  cache-references:k   # 74.724 M/sec
+     13,596  cache-misses:k       # 4.166654% miss rate
 
-Total test time: 1.029843 seconds.
+Total test time: 1.029729 seconds.
+simpleperf W cmd_stat.cpp:946] It seems the number of hardware events are more than the number of
+...
 ```
 
 ### Select target to stat
@@ -175,15 +206,23 @@
 # Stat process 11904 and 11905.
 $ simpleperf stat -p 11904,11905 --duration 10
 
+# Stat processes with name containing "chrome".
+$ simpleperf stat -p chrome --duration 10
+# Stat processes with name containing part matching regex "chrome:(privileged|sandboxed)".
+$ simpleperf stat -p "chrome:(privileged|sandboxed)" --duration 10
+
 # Stat thread 11904 and 11905.
 $ simpleperf stat -t 11904,11905 --duration 10
 
 # Start a child process running `ls`, and stat it.
 $ simpleperf stat ls
 
-# Stat the process of an Android application. This only works for debuggable apps on non-rooted
-# devices.
-$ simpleperf stat --app simpleperf.example.cpp
+# Stat the process of an Android application. On non-root devices, this only works for debuggable
+# or profileable from shell apps.
+$ simpleperf stat --app simpleperf.example.cpp --duration 10
+
+# Stat only selected thread 11904 in an app.
+$ simpleperf stat --app simpleperf.example.cpp -t 11904 --duration 10
 
 # Stat system wide using -a.
 $ simpleperf stat -a --duration 10
@@ -274,17 +313,18 @@
 ```sh
 # Print event counts for each cpu running threads in process 11904.
 # A percentage shows runtime_on_a_cpu / runtime_on_all_cpus.
-$ simpleperf stat --per-core -p 11904 --duration 1
+$ simpleperf stat -e cpu-cycles --per-core -p 1057 --duration 3
 Performance counter statistics:
 
-# cpu       count  event_name   # percentage = event_run_time / enabled_time
-  7    56,552,838  cpu-cycles   #   (60%)
-  3    25,958,605  cpu-cycles   #   (20%)
-  0    22,822,698  cpu-cycles   #   (15%)
-  1     6,661,495  cpu-cycles   #   (5%)
-  4     1,519,093  cpu-cycles   #   (0%)
+# cpu        count  event_name   # count / runtime
+  0      1,667,660  cpu-cycles   # 1.571565 GHz
+  1      3,850,440  cpu-cycles   # 1.736958 GHz
+  2      2,463,792  cpu-cycles   # 1.701367 GHz
+  3      2,350,528  cpu-cycles   # 1.700841 GHz
+  5      7,919,520  cpu-cycles   # 2.377081 GHz
+  6    105,622,673  cpu-cycles   # 2.381331 GHz
 
-Total test time: 1.001082 seconds.
+Total test time: 3.002703 seconds.
 
 # Print event counts for each cpu system wide.
 $ su 0 simpleperf stat --per-core -a --duration 1
@@ -330,15 +370,23 @@
 # Record process 11904 and 11905.
 $ simpleperf record -p 11904,11905 --duration 10
 
+# Record processes with name containing "chrome".
+$ simpleperf record -p chrome --duration 10
+# Record processes with name containing part matching regex "chrome:(privileged|sandboxed)".
+$ simpleperf record -p "chrome:(privileged|sandboxed)" --duration 10
+
 # Record thread 11904 and 11905.
 $ simpleperf record -t 11904,11905 --duration 10
 
 # Record a child process running `ls`.
 $ simpleperf record ls
 
-# Record the process of an Android application. This only works for debuggable apps on non-rooted
-# devices.
-$ simpleperf record --app simpleperf.example.cpp
+# Record the process of an Android application. On non-root devices, this only works for debuggable
+# or profileable from shell apps.
+$ simpleperf record --app simpleperf.example.cpp --duration 10
+
+# Record only selected thread 11904 in an app.
+$ simpleperf record --app simpleperf.example.cpp -t 11904 --duration 10
 
 # Record system wide.
 $ simpleperf record -a --duration 10
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 6d55977..288f75e 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -267,6 +267,9 @@
 }
 
 extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status);
+#if defined(__linux__) || defined(__darwin__)
+extern "C" char* rustc_demangle(const char* mangled, char* out, size_t* len, int* status);
+#endif
 
 std::string Dso::Demangle(const std::string& name) {
   if (!demangle_) {
@@ -278,19 +281,35 @@
   if (is_linker_symbol) {
     mangled_str += linker_prefix.size();
   }
-  std::string result = name;
-  char* demangled_name = __cxa_demangle(mangled_str, nullptr, nullptr, &status);
-  if (status == 0) {
-    if (is_linker_symbol) {
-      result = std::string("[linker]") + demangled_name;
-    } else {
-      result = demangled_name;
+
+  if (mangled_str[0] == '_') {
+    char* demangled_name = nullptr;
+    int status = -2;  // -2 means name didn't demangle.
+    if (mangled_str[1] == 'Z') {
+      demangled_name = __cxa_demangle(mangled_str, nullptr, nullptr, &status);
+#if defined(__linux__) || defined(__darwin__)
+    } else if (mangled_str[1] == 'R') {
+      demangled_name = rustc_demangle(mangled_str, nullptr, nullptr, &status);
+#endif
     }
-    free(demangled_name);
-  } else if (is_linker_symbol) {
-    result = std::string("[linker]") + mangled_str;
+    if (status == 0) {
+      // demangled successfully
+      std::string result;
+      if (is_linker_symbol) {
+        result = std::string("[linker]") + demangled_name;
+      } else {
+        result = demangled_name;
+      }
+      free(demangled_name);
+      return result;
+    }
   }
-  return result;
+
+  // failed to demangle
+  if (is_linker_symbol) {
+    return std::string("[linker]") + mangled_str;
+  }
+  return name;
 }
 
 bool Dso::SetSymFsDir(const std::string& symfs_dir) {
@@ -554,8 +573,10 @@
       if (elf) {
         min_vaddr_ = elf->ReadMinExecutableVaddr(&file_offset_of_min_vaddr_);
       } else {
-        LOG(WARNING) << "failed to read min virtual address of " << GetDebugFilePath() << ": "
-                     << status;
+        // This is likely to be a file wrongly thought of as an ELF file, due to stack unwinding.
+        // No need to report it by default.
+        LOG(DEBUG) << "failed to read min virtual address of " << GetDebugFilePath() << ": "
+                   << status;
       }
     }
     *min_vaddr = min_vaddr_;
@@ -617,8 +638,17 @@
     if (elf) {
       status = elf->ParseSymbols(symbol_callback);
     }
-    ReportReadElfSymbolResult(status, path_, GetDebugFilePath(),
-                              symbols_.empty() ? android::base::WARNING : android::base::DEBUG);
+    android::base::LogSeverity log_level = android::base::WARNING;
+    if (!symbols_.empty() || !symbols.empty()) {
+      // We already have some symbols when recording.
+      log_level = android::base::DEBUG;
+    }
+    if ((status == ElfStatus::FILE_NOT_FOUND || status == ElfStatus::FILE_MALFORMED) &&
+        build_id.IsEmpty()) {
+      // This is likely to be a file wrongly thought of as an ELF file, due to stack unwinding.
+      log_level = android::base::DEBUG;
+    }
+    ReportReadElfSymbolResult(status, path_, GetDebugFilePath(), log_level);
     SortAndFixSymbols(symbols);
     return symbols;
   }
@@ -642,10 +672,7 @@
       ElfStatus status;
       if (ElfFile::Open(vmlinux_, &build_id, &status)) {
         debug_file_path_ = vmlinux_;
-        has_debug_file_ = true;
       }
-    } else if (IsRegularFile(GetDebugFilePath())) {
-      has_debug_file_ = true;
     }
   }
 
@@ -675,9 +702,7 @@
 
   std::vector<Symbol> LoadSymbolsImpl() override {
     std::vector<Symbol> symbols;
-    if (has_debug_file_) {
-      ReadSymbolsFromDebugFile(&symbols);
-    }
+    ReadSymbolsFromDebugFile(&symbols);
 
     if (symbols.empty() && !kallsyms_.empty()) {
       ReadSymbolsFromKallsyms(kallsyms_, &symbols);
@@ -696,6 +721,12 @@
 
  private:
   void ReadSymbolsFromDebugFile(std::vector<Symbol>* symbols) {
+    ElfStatus status;
+    auto elf = ElfFile::Open(GetDebugFilePath(), &status);
+    if (!elf) {
+      return;
+    }
+
     if (!fix_kernel_address_randomization_) {
       LOG(WARNING) << "Don't know how to fix addresses changed by kernel address randomization. So "
                       "symbols in "
@@ -712,10 +743,7 @@
         symbols->emplace_back(symbol.name, symbol.vaddr, symbol.len);
       }
     };
-    ElfStatus status;
-    if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
-      status = elf->ParseSymbols(symbol_callback);
-    }
+    status = elf->ParseSymbols(symbol_callback);
     ReportReadElfSymbolResult(status, path_, GetDebugFilePath());
   }
 
@@ -779,21 +807,18 @@
   void ParseKernelStartAddr() {
     kernel_start_addr_ = 0;
     kernel_start_file_offset_ = 0;
-    if (has_debug_file_) {
-      ElfStatus status;
-      if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
-        for (const auto& section : elf->GetSectionHeader()) {
-          if (section.name == ".text") {
-            kernel_start_addr_ = section.vaddr;
-            kernel_start_file_offset_ = section.file_offset;
-            break;
-          }
+    ElfStatus status;
+    if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
+      for (const auto& section : elf->GetSectionHeader()) {
+        if (section.name == ".text") {
+          kernel_start_addr_ = section.vaddr;
+          kernel_start_file_offset_ = section.file_offset;
+          break;
         }
       }
     }
   }
 
-  bool has_debug_file_ = false;
   bool fix_kernel_address_randomization_ = false;
   std::optional<uint64_t> kernel_start_addr_;
   std::optional<uint64_t> kernel_start_file_offset_;
@@ -866,10 +891,9 @@
     // need to know its relative position in the module memory. There are two ways:
     // 1. Read the kernel module file to calculate the relative position of .text section. It
     // is relatively complex and depends on both PLT entries and the kernel version.
-    // 2. Find a module symbol in .text section, get its address in memory from /proc/kallsyms, and
-    // its vaddr_in_file from the kernel module file. Then other symbols in .text section can be
-    // mapped in the same way.
-    // Below we use the second method.
+    // 2. Find a module symbol in .text section, get its address in memory from /proc/kallsyms,
+    // and its vaddr_in_file from the kernel module file. Then other symbols in .text section can
+    // be mapped in the same way. Below we use the second method.
 
     // 1. Select a module symbol in /proc/kallsyms.
     kernel_dso_->LoadSymbols();
@@ -944,9 +968,9 @@
     case DSO_UNKNOWN_FILE:
       return std::unique_ptr<Dso>(new UnknownDso(dso_path));
     default:
-      LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type);
+      LOG(ERROR) << "Unexpected dso_type " << static_cast<int>(dso_type);
+      return nullptr;
   }
-  return nullptr;
 }
 
 std::unique_ptr<Dso> Dso::CreateDsoWithBuildId(DsoType dso_type, const std::string& dso_path,
@@ -963,7 +987,7 @@
       dso.reset(new KernelModuleDso(dso_path, 0, 0, nullptr));
       break;
     default:
-      LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type);
+      LOG(ERROR) << "Unexpected dso_type " << static_cast<int>(dso_type);
       return nullptr;
   }
   dso->debug_file_path_ = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
@@ -1001,4 +1025,30 @@
   return false;
 }
 
+bool GetBuildId(const Dso& dso, BuildId& build_id) {
+  if (dso.type() == DSO_KERNEL) {
+    if (GetKernelBuildId(&build_id)) {
+      return true;
+    }
+  } else if (dso.type() == DSO_KERNEL_MODULE) {
+    bool has_build_id = false;
+    if (android::base::EndsWith(dso.Path(), ".ko")) {
+      return GetBuildIdFromDsoPath(dso.Path(), &build_id);
+    }
+    if (const std::string& path = dso.Path();
+        path.size() > 2 && path[0] == '[' && path.back() == ']') {
+      // For kernel modules that we can't find the corresponding file, read build id from /sysfs.
+      return GetModuleBuildId(path.substr(1, path.size() - 2), &build_id);
+    }
+  } else if (dso.type() == DSO_ELF_FILE) {
+    if (dso.Path() == DEFAULT_EXECNAME_FOR_THREAD_MMAP || dso.IsForJavaMethod()) {
+      return false;
+    }
+    if (GetBuildIdFromDsoPath(dso.Path(), &build_id)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace simpleperf
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index 3042776..41cab75 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -228,6 +228,7 @@
 
 const char* DsoTypeToString(DsoType dso_type);
 bool GetBuildIdFromDsoPath(const std::string& dso_path, BuildId* build_id);
+bool GetBuildId(const Dso& dso, BuildId& build_id);
 
 }  // namespace simpleperf
 
diff --git a/simpleperf/dso_test.cpp b/simpleperf/dso_test.cpp
index 53f62a5..11bcde1 100644
--- a/simpleperf/dso_test.cpp
+++ b/simpleperf/dso_test.cpp
@@ -140,7 +140,6 @@
   DebugElfFileFinder finder;
   finder.SetSymFsDir(GetTestDataDir());
   CapturedStderr capture;
-  capture.Start();
   BuildId mismatch_build_id("0c12a384a9f4a3f3659b7171ca615dbec3a81f71");
   std::string debug_file = finder.FindDebugFile(ELF_FILE, false, mismatch_build_id);
   capture.Stop();
@@ -250,6 +249,15 @@
   std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
   ASSERT_TRUE(dso);
   ASSERT_EQ(dso->GetDebugFilePath(), vmlinux_path);
+  ASSERT_EQ(0x400927, dso->IpToVaddrInFile(0x800527, 0x800000, 0));
+
+  // Find vmlinux by CreateDsoWithBuildId.
+  Dso::SetBuildIds({});
+  BuildId build_id(ELF_FILE_BUILD_ID);
+  dso = Dso::CreateDsoWithBuildId(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME, build_id);
+  ASSERT_TRUE(dso);
+  ASSERT_EQ(dso->GetDebugFilePath(), vmlinux_path);
+  ASSERT_EQ(0x400927, dso->IpToVaddrInFile(0x800527, 0x800000, 0));
 }
 
 TEST(dso, kernel_module) {
@@ -315,10 +323,58 @@
   Dso::SetBuildIds({std::make_pair("/elf", BuildId("1b12a384a9f4a3f3659b7171ca615dbec3a81f71"))});
   Dso::SetSymFsDir(GetTestDataDir());
   CapturedStderr capture;
-  capture.Start();
   auto dso = Dso::CreateDso(DSO_ELF_FILE, "/elf");
   ASSERT_EQ(capture.str().find("build id mismatch"), std::string::npos);
   ASSERT_EQ(dso->GetDebugFilePath(), "/elf");
   ASSERT_NE(capture.str().find("build id mismatch"), std::string::npos);
   capture.Stop();
 }
+
+TEST(dso, read_symbol_warning) {
+  {
+    // Don't warn when the file may not be an ELF file.
+    auto dso = Dso::CreateDso(DSO_ELF_FILE, GetTestData("not_exist_file"));
+    CapturedStderr capture;
+    dso->LoadSymbols();
+    ASSERT_EQ(capture.str().find("failed to read symbols"), std::string::npos);
+  }
+  {
+    // Don't warn when the file may not be an ELF file.
+    auto dso = Dso::CreateDso(DSO_ELF_FILE, GetTestData("base.vdex"));
+    CapturedStderr capture;
+    dso->LoadSymbols();
+    ASSERT_EQ(capture.str().find("failed to read symbols"), std::string::npos);
+  }
+  {
+    // Warn when the file is an ELF file (having a build id).
+    std::string file_path = GetTestData("not_exist_file");
+    Dso::SetBuildIds(
+        {std::make_pair(file_path, BuildId("1b12a384a9f4a3f3659b7171ca615dbec3a81f71"))});
+    auto dso = Dso::CreateDso(DSO_ELF_FILE, file_path);
+    CapturedStderr capture;
+    dso->LoadSymbols();
+    ASSERT_NE(capture.str().find("failed to read symbols"), std::string::npos);
+  }
+  {
+    // Don't warn when we already have symbols.
+    std::string file_path = GetTestData("not_exist_file");
+    Dso::SetBuildIds(
+        {std::make_pair(file_path, BuildId("1b12a384a9f4a3f3659b7171ca615dbec3a81f71"))});
+    auto dso = Dso::CreateDso(DSO_ELF_FILE, file_path);
+    std::vector<Symbol> symbols;
+    symbols.emplace_back("fake_symbol", 0x1234, 0x60);
+    dso->SetSymbols(&symbols);
+    CapturedStderr capture;
+    dso->LoadSymbols();
+    ASSERT_EQ(capture.str().find("failed to read symbols"), std::string::npos);
+  }
+}
+
+TEST(dso, demangle) {
+  ASSERT_EQ(Dso::Demangle("main"), "main");
+  ASSERT_EQ(Dso::Demangle("_ZN4main4main17h2a68d4d833d7495aE"), "main::main::h2a68d4d833d7495a");
+#if defined(__linux__) || defined(__darwin__)
+  // Demangling rust symbol is only supported on linux and darwin.
+  ASSERT_EQ(Dso::Demangle("_RNvC6_123foo3bar"), "123foo::bar");
+#endif
+}
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 8e95d44..5ff0916 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -241,6 +241,16 @@
 #endif
 }
 
+std::optional<uint64_t> GetMemorySize() {
+  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/meminfo", "r"), fclose);
+  uint64_t size;
+  if (fp && fscanf(fp.get(), "MemTotal:%" PRIu64 " k", &size) == 1) {
+    return size * kKilobyte;
+  }
+  PLOG(ERROR) << "failed to get memory size";
+  return std::nullopt;
+}
+
 static const char* GetLimitLevelDescription(int limit_level) {
   switch (limit_level) {
     case -1:
@@ -299,15 +309,16 @@
     }
   }
   if (can_read_allow_file) {
-    LOG(WARNING) << perf_event_allow_path << " is " << limit_level << ", "
-                 << GetLimitLevelDescription(limit_level) << ".";
+    LOG(ERROR) << perf_event_allow_path << " is " << limit_level << ", "
+               << GetLimitLevelDescription(limit_level) << ".";
   }
-  LOG(WARNING) << "Try using `adb shell setprop security.perf_harden 0` to allow profiling.";
+  LOG(ERROR) << "Try using `adb shell setprop security.perf_harden 0` to allow profiling.";
   return false;
 #else
   if (can_read_allow_file) {
-    LOG(WARNING) << perf_event_allow_path << " is " << limit_level << ", "
-                 << GetLimitLevelDescription(limit_level) << ".";
+    LOG(ERROR) << perf_event_allow_path << " is " << limit_level << ", "
+               << GetLimitLevelDescription(limit_level) << ". Try using `echo -1 >"
+               << perf_event_allow_path << "` to enable profiling.";
     return false;
   }
 #endif
@@ -332,9 +343,10 @@
   }
   // Wait for init process to change perf event limits based on properties.
   const size_t max_wait_us = 3 * 1000000;
+  const size_t interval_us = 10000;
   int finish_mask = 0;
-  for (size_t i = 0; i < max_wait_us && finish_mask != 7; ++i) {
-    usleep(1);  // Wait 1us to avoid busy loop.
+  for (size_t i = 0; i < max_wait_us && finish_mask != 7; i += interval_us) {
+    usleep(interval_us);  // Wait 10ms to avoid busy loop.
     if ((finish_mask & 1) == 0) {
       uint64_t freq;
       if (!GetMaxSampleFrequency(&freq) || freq == sample_freq) {
@@ -486,12 +498,10 @@
   while (true) {
     std::vector<pid_t> pids = GetAllProcesses();
     for (pid_t pid : pids) {
-      std::string cmdline;
-      if (!android::base::ReadFileToString("/proc/" + std::to_string(pid) + "/cmdline", &cmdline)) {
-        // Maybe we don't have permission to read it.
+      std::string process_name = GetCompleteProcessName(pid);
+      if (process_name.empty()) {
         continue;
       }
-      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(':');
@@ -824,9 +834,22 @@
   // On Android <= O, the hard limit is 4096, and the soft limit is 1024.
   // On Android >= P, both the hard and soft limit are 32768.
   rlimit limit;
-  if (getrlimit(RLIMIT_NOFILE, &limit) == 0) {
-    limit.rlim_cur = limit.rlim_max;
-    setrlimit(RLIMIT_NOFILE, &limit);
+  if (getrlimit(RLIMIT_NOFILE, &limit) != 0) {
+    return;
+  }
+  rlim_t new_limit = limit.rlim_max;
+  if (IsRoot()) {
+    rlim_t sysctl_nr_open = 0;
+    if (ReadUintFromProcFile("/proc/sys/fs/nr_open", &sysctl_nr_open) &&
+        sysctl_nr_open > new_limit) {
+      new_limit = sysctl_nr_open;
+    }
+  }
+  if (limit.rlim_cur < new_limit) {
+    limit.rlim_cur = limit.rlim_max = new_limit;
+    if (setrlimit(RLIMIT_NOFILE, &limit) == 0) {
+      LOG(DEBUG) << "increased open file limit to " << new_limit;
+    }
   }
 }
 
@@ -934,18 +957,18 @@
 }
 
 std::string GetCompleteProcessName(pid_t pid) {
-  std::string s;
-  if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &s)) {
-    s.clear();
+  std::string argv0;
+  if (!android::base::ReadFileToString("/proc/" + std::to_string(pid) + "/cmdline", &argv0)) {
+    // Maybe we don't have permission to read it.
+    return std::string();
   }
-  for (size_t i = 0; i < s.size(); ++i) {
-    // /proc/pid/cmdline uses 0 to separate arguments.
-    if (isspace(s[i]) || s[i] == 0) {
-      s.resize(i);
-      break;
-    }
+  size_t pos = argv0.find('\0');
+  if (pos != std::string::npos) {
+    argv0.resize(pos);
   }
-  return s;
+  // argv0 can be empty if the process is in zombie state. In that case, we don't want to pass argv0
+  // to Basename(), which returns ".".
+  return argv0.empty() ? std::string() : android::base::Basename(argv0);
 }
 
 const char* GetTraceFsDir() {
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 34ce7fa..a69abaa 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -85,6 +85,7 @@
 bool GetPerfEventMlockKb(uint64_t* mlock_kb);
 bool SetPerfEventMlockKb(uint64_t mlock_kb);
 bool CanRecordRawData();
+std::optional<uint64_t> GetMemorySize();
 
 ArchType GetMachineArch();
 void PrepareVdsoFile();
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
index a95caca..511f184 100644
--- a/simpleperf/environment_test.cpp
+++ b/simpleperf/environment_test.cpp
@@ -136,3 +136,9 @@
   ASSERT_EQ(GetAppType("com.android.simpleperf.profileable"), "profileable");
   ASSERT_EQ(GetAppType("com.android.simpleperf.app_not_exist"), "not_exist");
 }
+
+TEST(environment, GetMemorySize) {
+  auto value = GetMemorySize();
+  ASSERT_TRUE(value);
+  ASSERT_GT(value.value(), 0);
+}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index dea77a9..b8acc34 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -145,7 +145,7 @@
   PrintIndented(indent + 1, "sample_stack_user 0x%" PRIx64 "\n", attr.sample_stack_user);
 }
 
-bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+bool GetCommonEventIdPositionsForAttrs(const EventAttrIds& attrs,
                                        size_t* event_id_pos_in_sample_records,
                                        size_t* event_id_reverse_pos_in_non_sample_records) {
   // When there are more than one perf_event_attrs, we need to read event id
@@ -153,7 +153,7 @@
   // we need to determine the event id position in a record here.
   std::vector<uint64_t> sample_types;
   for (const auto& attr : attrs) {
-    sample_types.push_back(attr.sample_type);
+    sample_types.push_back(attr.attr.sample_type);
   }
   // First determine event_id_pos_in_sample_records.
   // If PERF_SAMPLE_IDENTIFIER is enabled, it is just after perf_event_header.
@@ -192,7 +192,7 @@
   // also be the same.
   bool sample_id_all_enabled = true;
   for (const auto& attr : attrs) {
-    if (attr.sample_id_all == 0) {
+    if (attr.attr.sample_id_all == 0) {
       sample_id_all_enabled = false;
     }
   }
@@ -254,4 +254,11 @@
   return name;
 }
 
+void ReplaceRegAndStackWithCallChain(perf_event_attr& attr) {
+  attr.sample_type &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
+  attr.exclude_callchain_user = 0;
+  attr.sample_regs_user = 0;
+  attr.sample_stack_user = 0;
+}
+
 }  // namespace simpleperf
diff --git a/simpleperf/event_attr.h b/simpleperf/event_attr.h
index a9d8bba..5521f32 100644
--- a/simpleperf/event_attr.h
+++ b/simpleperf/event_attr.h
@@ -18,6 +18,7 @@
 #define SIMPLE_PERF_EVENT_ATTR_H_
 
 #include <stddef.h>
+#include <string.h>
 
 #include <string>
 #include <vector>
@@ -29,15 +30,17 @@
 struct EventType;
 
 struct EventAttrWithId {
-  const perf_event_attr* attr;
+  perf_event_attr attr;
   std::vector<uint64_t> ids;
 };
 
+using EventAttrIds = std::vector<EventAttrWithId>;
+
 inline constexpr uint64_t INFINITE_SAMPLE_PERIOD = 1ULL << 62;
 
 perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type);
 void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent = 0);
-bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+bool GetCommonEventIdPositionsForAttrs(const EventAttrIds& attrs,
                                        size_t* event_id_pos_in_sample_records,
                                        size_t* event_id_reverse_pos_in_non_sample_records);
 bool IsTimestampSupported(const perf_event_attr& attr);
@@ -45,6 +48,15 @@
 // Return event name with modifier if the event is found, otherwise return "unknown".
 // This function is slow for using linear search, so only used when reporting.
 std::string GetEventNameByAttr(const perf_event_attr& attr);
+void ReplaceRegAndStackWithCallChain(perf_event_attr& attr);
+
+inline bool operator==(const perf_event_attr& attr1, const perf_event_attr& attr2) {
+  return memcmp(&attr1, &attr2, sizeof(perf_event_attr)) == 0;
+}
+
+inline bool operator!=(const perf_event_attr& attr1, const perf_event_attr& attr2) {
+  return !(attr1 == attr2);
+}
 
 }  // namespace simpleperf
 
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index 58b4b95..2fdb88f 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -367,16 +367,17 @@
   return true;
 }
 
-std::vector<EventAttrWithId> EventSelectionSet::GetEventAttrWithId() const {
-  std::vector<EventAttrWithId> result;
+EventAttrIds EventSelectionSet::GetEventAttrWithId() const {
+  EventAttrIds result;
   for (const auto& group : groups_) {
     for (const auto& selection : group) {
-      EventAttrWithId attr_id;
-      attr_id.attr = &selection.event_attr;
+      std::vector<uint64_t> ids;
       for (const auto& fd : selection.event_fds) {
-        attr_id.ids.push_back(fd->Id());
+        ids.push_back(fd->Id());
       }
-      result.push_back(attr_id);
+      result.resize(result.size() + 1);
+      result.back().attr = selection.event_attr;
+      result.back().ids = std::move(ids);
     }
   }
   return result;
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index 07754fd..757034d 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -115,7 +115,7 @@
   std::vector<const EventType*> GetTracepointEvents() const;
   bool ExcludeKernel() const;
   bool HasAuxTrace() const { return has_aux_trace_; }
-  std::vector<EventAttrWithId> GetEventAttrWithId() const;
+  EventAttrIds GetEventAttrWithId() const;
   std::unordered_map<uint64_t, std::string> GetEventNamesById() const;
 
   void SetEnableOnExec(bool enable);
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
index 2711d5e..e246366 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -97,8 +97,6 @@
 static const std::string CORRECT_SYMFS_FOR_BUILD_ID_CHECK = "data/correct_symfs_for_build_id_check";
 static const std::string WRONG_SYMFS_FOR_BUILD_ID_CHECK = "data/wrong_symfs_for_build_id_check";
 
-static const std::string SYMFS_FOR_NO_SYMBOL_TABLE_WARNING =
-    "data/symfs_for_no_symbol_table_warning";
 static const std::string SYMFS_FOR_READ_ELF_FILE_WARNING = "data/symfs_for_read_elf_file_warning";
 
 static BuildId CHECK_ELF_FILE_BUILD_ID("91b1c10fdd9fe2221dfec525497637f2229bfdbb");
@@ -144,6 +142,6 @@
     "perf_with_ip_zero_in_callchain.data";
 
 // generated by `simpleperf record -e cs-etm:u ./etm_test_loop`
-static const std::string PERF_DATA_ETM_TEST_LOOP = "etm/perf.data";
+static const std::string PERF_DATA_ETM_TEST_LOOP = "etm/perf_etm.data";
 
 #endif  // SIMPLE_PERF_GET_TEST_DATA_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 3fce985..c8942b6 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -76,6 +76,7 @@
     return RunSimpleperfCmd(argc, argv) ? 0 : 1;
   }
 
+  testing::InitGoogleTest(&argc, argv);
   android::base::InitLogging(argv, android::base::StderrLogger);
   android::base::LogSeverity log_severity = android::base::WARNING;
   testdata_dir = std::string(dirname(argv[0])) + "/testdata";
@@ -104,7 +105,6 @@
   ScopedEnablingPerf scoped_enabling_perf;
 #endif
 
-  testing::InitGoogleTest(&argc, argv);
   if (!::testing::GTEST_FLAG(list_tests)) {
     if (!IsDir(testdata_dir)) {
       LOG(ERROR) << "testdata wasn't found. Use \"" << argv[0] << " -t <testdata_dir>\"";
diff --git a/simpleperf/include/simpleperf_profcollect.hpp b/simpleperf/include/simpleperf_profcollect.hpp
index 67123e5..d1e6661 100644
--- a/simpleperf/include/simpleperf_profcollect.hpp
+++ b/simpleperf/include/simpleperf_profcollect.hpp
@@ -18,6 +18,8 @@
 
 bool HasDriverSupport();
 bool HasDeviceSupport();
-bool Record(const char* event_name, const char* output, float duration);
+bool Record(const char* event_name, const char* output, float duration, const char* binary_filter);
 bool Inject(const char* traceInput, const char* profileOutput, const char* binary_filter);
+void SetLogFile(const char* filename);
+void ResetLogFile();
 }
diff --git a/simpleperf/kallsyms.cpp b/simpleperf/kallsyms.cpp
index f9a9358..9cec784 100644
--- a/simpleperf/kallsyms.cpp
+++ b/simpleperf/kallsyms.cpp
@@ -24,6 +24,7 @@
 #include <android-base/properties.h>
 
 #include "environment.h"
+#include "read_elf.h"
 #include "utils.h"
 
 namespace simpleperf {
@@ -263,6 +264,10 @@
       p = data_end;
     }
     if (ret >= 3) {
+      if (IsArmMappingSymbol(name)) {
+        continue;
+      }
+
       symbol.name = name;
       size_t module_len = strlen(module);
       if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
diff --git a/simpleperf/kallsyms_test.cpp b/simpleperf/kallsyms_test.cpp
index d45aeaa..cacd163 100644
--- a/simpleperf/kallsyms_test.cpp
+++ b/simpleperf/kallsyms_test.cpp
@@ -64,6 +64,25 @@
       data, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
 }
 
+TEST(kallsyms, ProcessKernelSymbols_ignore_arm_mapping_symbols) {
+  std::string data =
+      "aaaaaaaaaaaaaaaa t $x.9 [coresight_etm4x]\n"
+      "bbbbbbbbbbbbbbbb t etm4_pm_clear [coresight_etm4x]\n";
+  bool has_normal_symbol = false;
+  bool has_arm_mapping_symbol = false;
+  auto callback = [&](const KernelSymbol& sym) {
+    if (strcmp(sym.name, "etm4_pm_clear") == 0) {
+      has_normal_symbol = true;
+    } else {
+      has_arm_mapping_symbol = true;
+    }
+    return false;
+  };
+  ProcessKernelSymbols(data, callback);
+  ASSERT_TRUE(has_normal_symbol);
+  ASSERT_FALSE(has_arm_mapping_symbol);
+}
+
 #if defined(__ANDROID__)
 TEST(kallsyms, GetKernelStartAddress) {
   TEST_REQUIRE_ROOT();
diff --git a/simpleperf/libsimpleperf_report_fuzzer.cpp b/simpleperf/libsimpleperf_report_fuzzer.cpp
index 4c68610..8c6d878 100644
--- a/simpleperf/libsimpleperf_report_fuzzer.cpp
+++ b/simpleperf/libsimpleperf_report_fuzzer.cpp
@@ -1,14 +1,17 @@
 
 #include <android-base/file.h>
 
+#include "command.h"
 #include "report_lib_interface.cpp"
+#include "test_util.h"
 
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  TemporaryFile tmpfile;
-  android::base::WriteStringToFd(std::string(reinterpret_cast<const char*>(data), size),
-                                 tmpfile.fd);
+using namespace simpleperf;
+
+namespace {
+
+void TestReportLib(const char* record_file) {
   ReportLib* report_lib = CreateReportLib();
-  SetRecordFile(report_lib, tmpfile.path);
+  SetRecordFile(report_lib, record_file);
   while (true) {
     Sample* sample = GetNextSample(report_lib);
     if (sample == nullptr) {
@@ -16,5 +19,21 @@
     }
   }
   DestroyReportLib(report_lib);
+}
+
+void TestDumpCmd(const char* record_file) {
+  std::unique_ptr<Command> dump_cmd = CreateCommandInstance("dump");
+  CaptureStdout capture;
+  capture.Start();
+  dump_cmd->Run({"-i", record_file, "--dump-etm", "raw,packet,element"});
+}
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  TemporaryFile tmpfile;
+  android::base::WriteFully(tmpfile.fd, data, size);
+  TestReportLib(tmpfile.path);
+  TestDumpCmd(tmpfile.path);
   return 0;
 }
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
index 23471f6..e7084fe 100644
--- a/simpleperf/nonlinux_support/nonlinux_support.cpp
+++ b/simpleperf/nonlinux_support/nonlinux_support.cpp
@@ -23,10 +23,22 @@
 
 namespace simpleperf {
 
+bool GetThreadMmapsInProcess(pid_t, std::vector<ThreadMmap>*) {
+  return false;
+}
+
 bool GetKernelBuildId(BuildId*) {
   return false;
 }
 
+bool GetModuleBuildId(const std::string&, BuildId*, const std::string&) {
+  return false;
+}
+
+bool ReadThreadNameAndPid(pid_t, std::string*, pid_t*) {
+  return false;
+}
+
 bool CanRecordRawData() {
   return false;
 }
diff --git a/simpleperf/perf_regs.cpp b/simpleperf/perf_regs.cpp
index 59db45f..55bd213 100644
--- a/simpleperf/perf_regs.cpp
+++ b/simpleperf/perf_regs.cpp
@@ -34,6 +34,8 @@
     return ARCH_X86_32;
   } else if (arch == "x86_64") {
     return ARCH_X86_64;
+  } else if (arch == "riscv64") {
+    return ARCH_RISCV64;
   } else if (arch == "aarch64") {
     return ARCH_ARM64;
   } else if (android::base::StartsWith(arch, "arm")) {
@@ -81,6 +83,8 @@
       return "arm64";
     case ARCH_ARM:
       return "arm";
+    case ARCH_RISCV64:
+      return "riscv64";
     default:
       break;
   }
@@ -99,6 +103,8 @@
       return ((1ULL << PERF_REG_ARM_MAX) - 1);
     case ARCH_ARM64:
       return ((1ULL << PERF_REG_ARM64_MAX) - 1);
+    case ARCH_RISCV64:
+      return ((1ULL << PERF_REG_RISCV_MAX) - 1);
     default:
       return 0;
   }
@@ -125,6 +131,41 @@
     {PERF_REG_ARM64_PC, "pc"},
 };
 
+static std::unordered_map<size_t, std::string> riscv64_reg_map = {
+    {PERF_REG_RISCV_PC, "pc"},
+    {PERF_REG_RISCV_RA, "ra"},
+    {PERF_REG_RISCV_SP, "sp"},
+    {PERF_REG_RISCV_GP, "gp"},
+    {PERF_REG_RISCV_TP, "tp"},
+    {PERF_REG_RISCV_T0, "t0"},
+    {PERF_REG_RISCV_T1, "t1"},
+    {PERF_REG_RISCV_T2, "t2"},
+    {PERF_REG_RISCV_S0, "s0"},
+    {PERF_REG_RISCV_S1, "s1"},
+    {PERF_REG_RISCV_A0, "a0"},
+    {PERF_REG_RISCV_A1, "a1"},
+    {PERF_REG_RISCV_A2, "a2"},
+    {PERF_REG_RISCV_A3, "a3"},
+    {PERF_REG_RISCV_A4, "a4"},
+    {PERF_REG_RISCV_A5, "a5"},
+    {PERF_REG_RISCV_A6, "a6"},
+    {PERF_REG_RISCV_A7, "a7"},
+    {PERF_REG_RISCV_S2, "s2"},
+    {PERF_REG_RISCV_S3, "s3"},
+    {PERF_REG_RISCV_S4, "s4"},
+    {PERF_REG_RISCV_S5, "s5"},
+    {PERF_REG_RISCV_S6, "s6"},
+    {PERF_REG_RISCV_S7, "s7"},
+    {PERF_REG_RISCV_S8, "s8"},
+    {PERF_REG_RISCV_S9, "s9"},
+    {PERF_REG_RISCV_S10, "s10"},
+    {PERF_REG_RISCV_S11, "s11"},
+    {PERF_REG_RISCV_T3, "t3"},
+    {PERF_REG_RISCV_T4, "t4"},
+    {PERF_REG_RISCV_T5, "t5"},
+    {PERF_REG_RISCV_T6, "t6"},
+};
+
 std::string GetRegName(size_t regno, ArchType arch) {
   // Cast regno to int type to avoid -Werror=type-limits.
   int reg = static_cast<int>(regno);
@@ -157,6 +198,11 @@
       CHECK(it != arm64_reg_map.end()) << "unknown reg " << reg;
       return it->second;
     }
+    case ARCH_RISCV64: {
+      auto it = riscv64_reg_map.find(reg);
+      CHECK(it != riscv64_reg_map.end()) << "unknown reg " << reg;
+      return it->second;
+    }
     default:
       return "unknown";
   }
@@ -200,6 +246,9 @@
     case ARCH_ARM64:
       regno = PERF_REG_ARM64_SP;
       break;
+    case ARCH_RISCV64:
+      regno = PERF_REG_RISCV_SP;
+      break;
     default:
       return false;
   }
@@ -219,6 +268,9 @@
     case ARCH_ARM64:
       regno = PERF_REG_ARM64_PC;
       break;
+    case ARCH_RISCV64:
+      regno = PERF_REG_RISCV_PC;
+      break;
     default:
       return false;
   }
diff --git a/simpleperf/perf_regs.h b/simpleperf/perf_regs.h
index 569a469..6e083e0 100644
--- a/simpleperf/perf_regs.h
+++ b/simpleperf/perf_regs.h
@@ -19,14 +19,24 @@
 
 #if defined(USE_BIONIC_UAPI_HEADERS)
 #include <uapi/asm-arm/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
 #include <uapi/asm-x86/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
+#include <uapi/asm-riscv/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
 #define perf_event_arm_regs perf_event_arm64_regs
 #include <uapi/asm-arm64/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
 #else
 #include <asm-arm/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
 #include <asm-x86/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
+#include <asm-riscv/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
 #define perf_event_arm_regs perf_event_arm64_regs
 #include <asm-arm64/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
 #endif
 
 #include <stdint.h>
@@ -42,6 +52,7 @@
   ARCH_X86_64,
   ARCH_ARM,
   ARCH_ARM64,
+  ARCH_RISCV64,
   ARCH_UNSUPPORTED,
 };
 
@@ -54,6 +65,8 @@
   return ARCH_ARM64;
 #elif defined(__arm__)
   return ARCH_ARM;
+#elif defined(__riscv)
+  return ARCH_RISCV64;
 #else
   return ARCH_UNSUPPORTED;
 #endif
diff --git a/simpleperf/perf_regs_test.cpp b/simpleperf/perf_regs_test.cpp
index fc75cca..0af9747 100644
--- a/simpleperf/perf_regs_test.cpp
+++ b/simpleperf/perf_regs_test.cpp
@@ -21,9 +21,10 @@
 using namespace simpleperf;
 
 TEST(RegSet, arch) {
-  ArchType arch_pairs[2][2] = {
+  ArchType arch_pairs[3][2] = {
       {ARCH_X86_32, ARCH_X86_64},
       {ARCH_ARM, ARCH_ARM64},
+      {ARCH_RISCV64, ARCH_RISCV64},
   };
   for (ArchType* arch_pair : arch_pairs) {
     for (size_t i = 0; i < 2; i++) {
diff --git a/simpleperf/profcollect.cpp b/simpleperf/profcollect.cpp
index 724111e..9cffbdd 100644
--- a/simpleperf/profcollect.cpp
+++ b/simpleperf/profcollect.cpp
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
+#include <time.h>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+
 #include <wakelock/wakelock.h>
 #include <include/simpleperf_profcollect.hpp>
 
@@ -26,48 +31,96 @@
 using namespace simpleperf;
 
 bool HasDriverSupport() {
-  return ETMRecorder::GetInstance().IsETMDriverAvailable();
+  bool result = ETMRecorder::GetInstance().IsETMDriverAvailable();
+  LOG(INFO) << "HasDriverSupport result " << result;
+  return result;
 }
 
 bool HasDeviceSupport() {
   auto result = ETMRecorder::GetInstance().CheckEtmSupport();
   if (!result.ok()) {
-    LOG(DEBUG) << result.error();
+    LOG(INFO) << "HasDeviceSupport check failed: " << result.error();
     return false;
   }
   const EventType* type = FindEventTypeByName("cs-etm", false);
   if (type == nullptr) {
+    LOG(INFO) << "HasDeviceSupport check failed: no etm event";
     return false;
   }
-  return IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), type->name);
+  bool ret = IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), type->name);
+  LOG(INFO) << "HasDeviceSupport result " << ret;
+  return ret;
 }
 
-bool Record(const char* event_name, const char* output, float duration) {
+bool Record(const char* event_name, const char* output, float duration, const char* binary_filter) {
+  LOG(INFO) << "Record " << event_name << ", duration " << duration << ", output " << output
+            << ", binary_filter " << binary_filter;
   // The kernel may panic when trying to hibernate or hotplug CPUs while collecting
   // ETM data. So get wakelock to keep the CPUs on.
   auto wakelock = android::wakelock::WakeLock::tryGet("profcollectd");
   if (!wakelock) {
-    LOG(ERROR) << "Failed to request wakelock.";
+    LOG(ERROR) << "Record failed: Failed to request wakelock.";
     return false;
   }
   auto recordCmd = CreateCommandInstance("record");
-  std::vector<std::string> args;
-  args.push_back("-a");
-  args.insert(args.end(), {"-e", event_name});
-  args.insert(args.end(), {"--duration", std::to_string(duration)});
-  args.insert(args.end(), {"-o", output});
-  return recordCmd->Run(args);
+  std::vector<std::string> args = {"-a",
+                                   "-e",
+                                   event_name,
+                                   "--duration",
+                                   std::to_string(duration),
+                                   "--decode-etm",
+                                   "--exclude-perf",
+                                   "--binary",
+                                   binary_filter,
+                                   "-o",
+                                   output};
+  bool result = recordCmd->Run(args);
+  LOG(INFO) << "Record result " << result;
+  return result;
 }
 
 bool Inject(const char* traceInput, const char* profileOutput, const char* binary_filter) {
+  LOG(INFO) << "Inject traceInput " << traceInput << ", profileOutput " << profileOutput
+            << ", binary_filter " << binary_filter;
   auto injectCmd = CreateCommandInstance("inject");
-  std::vector<std::string> args;
-  args.insert(args.end(), {"-i", traceInput});
-  args.insert(args.end(), {"-o", profileOutput});
-  if (binary_filter) {
-    args.insert(args.end(), {"--binary", binary_filter});
+  std::vector<std::string> args = {"-i",       traceInput,    "-o",       profileOutput,
+                                   "--output", "branch-list", "--binary", binary_filter};
+  bool result = injectCmd->Run(args);
+  LOG(INFO) << "Inject result " << result;
+  return result;
+}
+
+static android::base::unique_fd log_fd;
+static android::base::LogFunction saved_log_func;
+
+static void FileLogger(android::base::LogId id, android::base::LogSeverity severity,
+                       const char* tag, const char* file, unsigned int line, const char* message) {
+  if (log_fd.ok()) {
+    static const char log_characters[] = "VDIWEFF";
+    char severity_char = log_characters[severity];
+    struct tm now;
+    time_t t = time(nullptr);
+    localtime_r(&t, &now);
+    char timestamp[32];
+    strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now);
+    std::string s = android::base::StringPrintf("%s %c %s %s:%u] %s\n", tag, severity_char,
+                                                timestamp, file, line, message);
+    WriteStringToFd(s, log_fd);
   }
-  args.insert(args.end(), {"--output", "branch-list"});
-  args.emplace_back("--exclude-perf");
-  return injectCmd->Run(args);
+  saved_log_func(id, severity, tag, file, line, message);
+}
+
+void SetLogFile(const char* filename) {
+  int fd = TEMP_FAILURE_RETRY(open(filename, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, 0600));
+  if (fd == -1) {
+    PLOG(ERROR) << "failed to open " << filename;
+    return;
+  }
+  log_fd.reset(fd);
+  saved_log_func = SetLogger(FileLogger);
+}
+
+void ResetLogFile() {
+  log_fd.reset();
+  SetLogger(std::move(saved_log_func));
 }
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index 4590b9e..29e5d11 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -39,7 +39,12 @@
     }                                     \
   } while (0)
 
-#define CHECK_SIZE_U64(p, end, u64_count) CHECK_SIZE(p, end, (u64_count) * sizeof(uint64_t))
+#define CHECK_SIZE_U64(p, end, u64_count)                           \
+  do {                                                              \
+    if (UNLIKELY(((end) - (p)) / sizeof(uint64_t) < (u64_count))) { \
+      return false;                                                 \
+    }                                                               \
+  } while (0)
 
 static std::string RecordTypeToString(int record_type) {
   static std::unordered_map<int, std::string> record_type_names = {
@@ -205,7 +210,9 @@
   binary_ = p;
   CHECK(end != nullptr);
   CHECK_SIZE(p, end, sizeof(perf_event_header));
-  header = RecordHeader(p);
+  if (!header.Parse(p)) {
+    return false;
+  }
   CHECK_SIZE(p, end, header.size);
   end = p + header.size;
   p += sizeof(perf_event_header);
@@ -496,9 +503,16 @@
       CHECK_SIZE_U64(p, end, 1);
       MoveFromBinaryFormat(nr, p);
     }
-    size_t u64_count = (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) ? 1 : 0;
+    uint64_t u64_count = (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) ? 1 : 0;
     u64_count += (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) ? 1 : 0;
-    u64_count += ((read_format & PERF_FORMAT_ID) ? 2 : 1) * nr;
+    if (__builtin_add_overflow(u64_count, nr, &u64_count)) {
+      return false;
+    }
+    if (read_format & PERF_FORMAT_ID) {
+      if (__builtin_add_overflow(u64_count, nr, &u64_count)) {
+        return false;
+      }
+    }
     CHECK_SIZE_U64(p, end, u64_count);
     if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) {
       MoveFromBinaryFormat(read_data.time_enabled, p);
@@ -543,6 +557,8 @@
     MoveFromBinaryFormat(regs_user_data.abi, p);
     if (regs_user_data.abi == 0) {
       regs_user_data.reg_mask = 0;
+      regs_user_data.reg_nr = 0;
+      regs_user_data.regs = nullptr;
     } else {
       regs_user_data.reg_mask = attr.sample_regs_user;
       size_t bit_nr = __builtin_popcountll(regs_user_data.reg_mask);
@@ -566,7 +582,7 @@
   }
   // TODO: Add parsing of other PERF_SAMPLE_*.
   if (UNLIKELY(p < end)) {
-    LOG(DEBUG) << "Record has " << end - p << " bytes left\n";
+    LOG(DEBUG) << "Sample (" << time_data.time << ") has " << end - p << " bytes left";
   }
   return true;
 }
@@ -697,10 +713,16 @@
 }
 
 void SampleRecord::ReplaceRegAndStackWithCallChain(const std::vector<uint64_t>& ips) {
-  uint32_t size_added_in_callchain = sizeof(uint64_t) * (ips.size() + 1);
-  uint32_t size_reduced_in_reg_stack =
-      regs_user_data.reg_nr * sizeof(uint64_t) + stack_user_data.size + sizeof(uint64_t);
-  uint32_t new_size = size() + size_added_in_callchain - size_reduced_in_reg_stack;
+  uint32_t add_size_in_callchain = ips.empty() ? 0 : sizeof(uint64_t) * (ips.size() + 1);
+  uint32_t reduce_size_in_reg = (regs_user_data.reg_nr + 1) * sizeof(uint64_t);
+  uint32_t reduce_size_in_stack =
+      stack_user_data.size == 0 ? sizeof(uint64_t) : (stack_user_data.size + 2 * sizeof(uint64_t));
+  uint32_t reduce_size = reduce_size_in_reg + reduce_size_in_stack;
+
+  uint32_t new_size = size() + add_size_in_callchain;
+  CHECK_GT(new_size, reduce_size);
+  new_size -= reduce_size;
+  sample_type &= ~(PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER);
   BuildBinaryWithNewCallChain(new_size, ips);
 }
 
@@ -802,16 +824,21 @@
     memcpy(p, &raw_data.size, sizeof(uint32_t));
   }
   uint64_t* p64 = reinterpret_cast<uint64_t*>(p);
-  p64 -= ips.size();
-  memcpy(p64, ips.data(), ips.size() * sizeof(uint64_t));
-  *--p64 = PERF_CONTEXT_USER;
-  if (callchain_data.ip_nr > 0) {
-    p64 -= callchain_data.ip_nr;
-    memcpy(p64, callchain_data.ips, callchain_data.ip_nr * sizeof(uint64_t));
+  if (!ips.empty()) {
+    p64 -= ips.size();
+    memcpy(p64, ips.data(), ips.size() * sizeof(uint64_t));
+    *--p64 = PERF_CONTEXT_USER;
   }
-  callchain_data.ips = p64;
-  callchain_data.ip_nr += 1 + ips.size();
-  *--p64 = callchain_data.ip_nr;
+  p64 -= callchain_data.ip_nr;
+  if (p64 != callchain_data.ips) {
+    memcpy(p64, callchain_data.ips, callchain_data.ip_nr * sizeof(uint64_t));
+    callchain_data.ips = p64;
+  }
+  p64--;
+  if (!ips.empty()) {
+    callchain_data.ip_nr += 1 + ips.size();
+    *p64 = callchain_data.ip_nr;
+  }
   CHECK_EQ(callchain_pos, static_cast<size_t>(reinterpret_cast<char*>(p64) - new_binary))
       << "record time " << time_data.time;
   if (new_binary != binary_) {
@@ -1084,6 +1111,7 @@
     return false;
   }
   for (uint32_t i = 0; i < data->nr_cpu; ++i) {
+    CHECK_SIZE(p, end, sizeof(uint64_t));
     uint64_t magic = *reinterpret_cast<uint64_t*>(p);
     if (magic == MAGIC_ETM4) {
       CHECK_SIZE(p, end, sizeof(ETM4Info));
@@ -1338,8 +1366,10 @@
 }
 
 void TracingDataRecord::DumpData(size_t indent) const {
-  Tracing tracing(std::vector<char>(data, data + data_size));
-  tracing.Dump(indent);
+  auto tracing = Tracing::Create(std::vector<char>(data, data + data_size));
+  if (tracing) {
+    tracing->Dump(indent);
+  }
 }
 
 bool EventIdRecord::Parse(const perf_event_attr&, char* p, char* end) {
diff --git a/simpleperf/record.h b/simpleperf/record.h
index 325f601..925eaaa 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -152,7 +152,7 @@
 
   RecordHeader() : type(0), misc(0), size(0) {}
 
-  explicit RecordHeader(const char* p) {
+  bool Parse(const char* p) {
     auto pheader = reinterpret_cast<const perf_event_header*>(p);
     if (pheader->type < SIMPLE_PERF_RECORD_TYPE_START) {
       type = pheader->type;
@@ -164,6 +164,11 @@
       misc = 0;
       size = (sheader->size1 << 16) | sheader->size0;
     }
+    if (size < sizeof(perf_event_header)) {
+      LOG(ERROR) << "invalid record";
+      return false;
+    }
+    return true;
   }
 
   void MoveToBinaryFormat(char*& p) const {
@@ -433,7 +438,7 @@
     uint64_t aux_offset;
     uint64_t aux_size;
     uint64_t flags;
-  } * data;
+  }* data;
 
   bool Parse(const perf_event_attr& attr, char* p, char* end) override;
   bool Unformatted() const { return data->flags & PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW; }
@@ -514,7 +519,7 @@
     uint32_t pmu_type;
     uint64_t snapshot;
     uint64_t info[0];
-  } * data;
+  }* data;
 
   AuxTraceInfoRecord() {}
   AuxTraceInfoRecord(const DataType& data, const std::vector<ETEInfo>& ete_info);
@@ -533,7 +538,7 @@
     uint32_t tid;
     uint32_t cpu;
     uint32_t reserved1;
-  } * data;
+  }* data;
   // AuxTraceRecord is followed by aux tracing data with size data->aux_size.
   // The location of aux tracing data in memory or file is kept in location.
   struct AuxDataLocation {
@@ -545,6 +550,7 @@
   AuxTraceRecord(uint64_t aux_size, uint64_t offset, uint32_t idx, uint32_t tid, uint32_t cpu);
 
   bool Parse(const perf_event_attr& attr, char* p, char* end) override;
+  uint32_t Cpu() const override { return data->cpu; }
   static size_t Size() { return sizeof(perf_event_header) + sizeof(DataType); }
 
  protected:
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index 1ada080..fa69626 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -81,7 +81,7 @@
   RecordFileWriter(const std::string& filename, FILE* fp, bool own_fp);
   ~RecordFileWriter();
 
-  bool WriteAttrSection(const std::vector<EventAttrWithId>& attr_ids);
+  bool WriteAttrSection(const EventAttrIds& attr_ids);
   bool WriteRecord(const Record& record);
   bool WriteData(const void* buf, size_t len);
 
@@ -141,14 +141,7 @@
 
   const PerfFileFormat::FileHeader& FileHeader() const { return header_; }
 
-  std::vector<EventAttrWithId> AttrSection() const {
-    std::vector<EventAttrWithId> result(file_attrs_.size());
-    for (size_t i = 0; i < file_attrs_.size(); ++i) {
-      result[i].attr = &file_attrs_[i].attr;
-      result[i].ids = event_ids_for_file_attrs_[i];
-    }
-    return result;
-  }
+  const EventAttrIds& AttrSection() const { return event_attrs_; }
 
   const std::unordered_map<uint64_t, size_t>& EventIdMap() const { return event_id_to_attr_map_; }
 
@@ -183,18 +176,24 @@
 
   // File feature section contains many file information. This function reads
   // one file information located at [read_pos]. [read_pos] is 0 at the first
-  // call, and is updated to point to the next file information. Return true
-  // if read successfully, and return false if there is no more file
-  // information.
-  bool ReadFileFeature(size_t& read_pos, FileFeature* file);
+  // call, and is updated to point to the next file information.
+  // When read successfully, return true and set error to false.
+  // When no more data to read, return false and set error to false.
+  // When having error, return false and set error to true.
+  bool ReadFileFeature(uint64_t& read_pos, FileFeature& file, bool& error);
 
   const std::unordered_map<std::string, std::string>& GetMetaInfoFeature() { return meta_info_; }
   std::string GetClockId();
   std::optional<DebugUnwindFeature> ReadDebugUnwindFeature();
 
-  void LoadBuildIdAndFileFeatures(ThreadTree& thread_tree);
+  bool LoadBuildIdAndFileFeatures(ThreadTree& thread_tree);
 
-  bool ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf, size_t size);
+  // Read aux data into buf.
+  // When read successfully, return true and set error to false.
+  // When the data isn't available, return false and set error to false.
+  // When having error, return false and set error to true.
+  bool ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t size, std::vector<uint8_t>& buf,
+                   bool& error);
 
   bool Close();
 
@@ -204,12 +203,13 @@
  private:
   RecordFileReader(const std::string& filename, FILE* fp);
   bool ReadHeader();
-  bool CheckSectionDesc(const PerfFileFormat::SectionDesc& desc, uint64_t min_offset);
+  bool CheckSectionDesc(const PerfFileFormat::SectionDesc& desc, uint64_t min_offset,
+                        uint64_t alignment = 1);
   bool ReadAttrSection();
-  bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
+  bool ReadIdSection(const PerfFileFormat::SectionDesc& section, std::vector<uint64_t>* ids);
   bool ReadFeatureSectionDescriptors();
-  bool ReadFileV1Feature(size_t& read_pos, FileFeature* file);
-  bool ReadFileV2Feature(size_t& read_pos, FileFeature* file);
+  bool ReadFileV1Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file);
+  bool ReadFileV2Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file);
   bool ReadMetaInfoFeature();
   void UseRecordingEnvironment();
   std::unique_ptr<Record> ReadRecord();
@@ -222,8 +222,7 @@
   uint64_t file_size_;
 
   PerfFileFormat::FileHeader header_;
-  std::vector<PerfFileFormat::FileAttr> file_attrs_;
-  std::vector<std::vector<uint64_t>> event_ids_for_file_attrs_;
+  EventAttrIds event_attrs_;
   std::unordered_map<uint64_t, size_t> event_id_to_attr_map_;
   std::map<int, PerfFileFormat::SectionDesc> feature_section_descriptors_;
 
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
index 53c6719..0b36f35 100644
--- a/simpleperf/record_file_format.h
+++ b/simpleperf/record_file_format.h
@@ -82,6 +82,9 @@
   uint32_t file_msg2_size;
   FileFeature file_msg2;
   ...
+
+etm_branch_list feature section:
+  ETMBranchList etm_branch_list;  // from etm_branch_list.proto
 */
 
 namespace simpleperf {
@@ -116,6 +119,7 @@
   FEAT_DEBUG_UNWIND,
   FEAT_DEBUG_UNWIND_FILE,
   FEAT_FILE2,
+  FEAT_ETM_BRANCH_LIST,
   FEAT_MAX_NUM = 256,
 };
 
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index 5bfc5ac..1b1bab7 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -18,7 +18,9 @@
 
 #include <fcntl.h>
 #include <string.h>
+
 #include <set>
+#include <string_view>
 #include <vector>
 
 #include <android-base/logging.h>
@@ -58,6 +60,7 @@
     {FEAT_DEBUG_UNWIND, "debug_unwind"},
     {FEAT_DEBUG_UNWIND_FILE, "debug_unwind_file"},
     {FEAT_FILE2, "file2"},
+    {FEAT_ETM_BRANCH_LIST, "etm_branch_list"},
 };
 
 std::string GetFeatureName(int feature_id) {
@@ -133,18 +136,26 @@
   return true;
 }
 
-bool RecordFileReader::CheckSectionDesc(const SectionDesc& desc, uint64_t min_offset) {
+bool RecordFileReader::CheckSectionDesc(const SectionDesc& desc, uint64_t min_offset,
+                                        uint64_t alignment) {
   uint64_t desc_end;
   if (desc.offset < min_offset || __builtin_add_overflow(desc.offset, desc.size, &desc_end) ||
       desc_end > file_size_) {
     return false;
   }
+  if (desc.size % alignment != 0) {
+    return false;
+  }
   return true;
 }
 
 bool RecordFileReader::ReadAttrSection() {
   size_t attr_count = header_.attrs.size / header_.attr_size;
   if (header_.attr_size != sizeof(FileAttr)) {
+    if (header_.attr_size <= sizeof(SectionDesc)) {
+      LOG(ERROR) << "invalid attr section in " << filename_;
+      return false;
+    }
     LOG(DEBUG) << "attr size (" << header_.attr_size << ") in " << filename_
                << " doesn't match expected size (" << sizeof(FileAttr) << ")";
   }
@@ -156,42 +167,42 @@
     PLOG(ERROR) << "fseek() failed";
     return false;
   }
+  event_attrs_.resize(attr_count);
+  std::vector<SectionDesc> id_sections(attr_count);
+  size_t attr_size_in_file = header_.attr_size - sizeof(SectionDesc);
   for (size_t i = 0; i < attr_count; ++i) {
     std::vector<char> buf(header_.attr_size);
     if (!Read(buf.data(), buf.size())) {
       return false;
     }
-    // The size of perf_event_attr is changing between different linux kernel versions.
-    // Make sure we copy correct data to memory.
-    FileAttr attr;
-    memset(&attr, 0, sizeof(attr));
-    size_t section_desc_size = sizeof(attr.ids);
-    size_t perf_event_attr_size = header_.attr_size - section_desc_size;
-    memcpy(&attr.attr, &buf[0], std::min(sizeof(attr.attr), perf_event_attr_size));
-    memcpy(&attr.ids, &buf[perf_event_attr_size], section_desc_size);
-    if (!CheckSectionDesc(attr.ids, 0)) {
+    // The struct perf_event_attr is defined in a Linux header file. It can be extended in newer
+    // kernel versions with more fields and a bigger size. To disable these extensions, set their
+    // values to zero. So to copy perf_event_attr from file to memory safely, ensure the copy
+    // doesn't overflow the file or memory, and set the values of any extra fields in memory to
+    // zero.
+    if (attr_size_in_file >= sizeof(perf_event_attr)) {
+      memcpy(&event_attrs_[i].attr, &buf[0], sizeof(perf_event_attr));
+    } else {
+      memset(&event_attrs_[i].attr, 0, sizeof(perf_event_attr));
+      memcpy(&event_attrs_[i].attr, &buf[0], attr_size_in_file);
+    }
+    memcpy(&id_sections[i], &buf[attr_size_in_file], sizeof(SectionDesc));
+    if (!CheckSectionDesc(id_sections[i], 0, sizeof(uint64_t))) {
       LOG(ERROR) << "invalid attr section in " << filename_;
       return false;
     }
-    file_attrs_.push_back(attr);
   }
-  if (file_attrs_.size() > 1) {
-    std::vector<perf_event_attr> attrs;
-    for (const auto& file_attr : file_attrs_) {
-      attrs.push_back(file_attr.attr);
-    }
-    if (!GetCommonEventIdPositionsForAttrs(attrs, &event_id_pos_in_sample_records_,
+  if (event_attrs_.size() > 1) {
+    if (!GetCommonEventIdPositionsForAttrs(event_attrs_, &event_id_pos_in_sample_records_,
                                            &event_id_reverse_pos_in_non_sample_records_)) {
       return false;
     }
   }
-  for (size_t i = 0; i < file_attrs_.size(); ++i) {
-    std::vector<uint64_t> ids;
-    if (!ReadIdsForAttr(file_attrs_[i], &ids)) {
+  for (size_t i = 0; i < attr_count; ++i) {
+    if (!ReadIdSection(id_sections[i], &event_attrs_[i].ids)) {
       return false;
     }
-    event_ids_for_file_attrs_.push_back(ids);
-    for (auto id : ids) {
+    for (auto id : event_attrs_[i].ids) {
       event_id_to_attr_map_[id] = i;
     }
   }
@@ -227,14 +238,14 @@
   return true;
 }
 
-bool RecordFileReader::ReadIdsForAttr(const FileAttr& attr, std::vector<uint64_t>* ids) {
-  size_t id_count = attr.ids.size / sizeof(uint64_t);
-  if (fseek(record_fp_, attr.ids.offset, SEEK_SET) != 0) {
+bool RecordFileReader::ReadIdSection(const SectionDesc& section, std::vector<uint64_t>* ids) {
+  size_t id_count = section.size / sizeof(uint64_t);
+  if (fseek(record_fp_, section.offset, SEEK_SET) != 0) {
     PLOG(ERROR) << "fseek() failed";
     return false;
   }
   ids->resize(id_count);
-  if (!Read(ids->data(), attr.ids.size)) {
+  if (!Read(ids->data(), section.size)) {
     return false;
   }
   return true;
@@ -289,36 +300,37 @@
 
 std::unique_ptr<Record> RecordFileReader::ReadRecord() {
   char header_buf[Record::header_size()];
-  if (!Read(header_buf, Record::header_size())) {
+  RecordHeader header;
+  if (!Read(header_buf, Record::header_size()) || !header.Parse(header_buf)) {
     return nullptr;
   }
-  RecordHeader header(header_buf);
   std::unique_ptr<char[]> p;
   if (header.type == SIMPLE_PERF_RECORD_SPLIT) {
     // Read until meeting a RECORD_SPLIT_END record.
     std::vector<char> buf;
-    size_t cur_size = 0;
-    char header_buf[Record::header_size()];
     while (header.type == SIMPLE_PERF_RECORD_SPLIT) {
-      size_t bytes_to_read = header.size - Record::header_size();
-      buf.resize(cur_size + bytes_to_read);
-      if (!Read(&buf[cur_size], bytes_to_read)) {
+      size_t add_size = header.size - Record::header_size();
+      size_t old_size = buf.size();
+      buf.resize(old_size + add_size);
+      if (!Read(&buf[old_size], add_size)) {
         return nullptr;
       }
-      cur_size += bytes_to_read;
       read_record_size_ += header.size;
-      if (!Read(header_buf, Record::header_size())) {
+      if (!Read(header_buf, Record::header_size()) || !header.Parse(header_buf)) {
         return nullptr;
       }
-      header = RecordHeader(header_buf);
     }
     if (header.type != SIMPLE_PERF_RECORD_SPLIT_END) {
       LOG(ERROR) << "SPLIT records are not followed by a SPLIT_END record.";
       return nullptr;
     }
     read_record_size_ += header.size;
-    header = RecordHeader(buf.data());
-    p.reset(new char[header.size]);
+    if (buf.size() < Record::header_size() || !header.Parse(buf.data()) ||
+        header.size != buf.size()) {
+      LOG(ERROR) << "invalid record merged from SPLIT records";
+      return nullptr;
+    }
+    p.reset(new char[buf.size()]);
     memcpy(p.get(), buf.data(), buf.size());
   } else {
     p.reset(new char[header.size]);
@@ -331,8 +343,8 @@
     read_record_size_ += header.size;
   }
 
-  const perf_event_attr* attr = &file_attrs_[0].attr;
-  if (file_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
+  const perf_event_attr* attr = &event_attrs_[0].attr;
+  if (event_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
     bool has_event_id = false;
     uint64_t event_id;
     if (header.type == PERF_RECORD_SAMPLE) {
@@ -350,7 +362,7 @@
     if (has_event_id) {
       auto it = event_id_to_attr_map_.find(event_id);
       if (it != event_id_to_attr_map_.end()) {
-        attr = &file_attrs_[it->second].attr;
+        attr = &event_attrs_[it->second].attr;
       }
     }
   }
@@ -390,8 +402,9 @@
 
 void RecordFileReader::ProcessEventIdRecord(const EventIdRecord& r) {
   for (size_t i = 0; i < r.count; ++i) {
-    event_ids_for_file_attrs_[r.data[i].attr_id].push_back(r.data[i].event_id);
-    event_id_to_attr_map_[r.data[i].event_id] = r.data[i].attr_id;
+    const auto& data = r.data[i];
+    event_attrs_[data.attr_id].ids.push_back(data.event_id);
+    event_id_to_attr_map_[data.event_id] = data.attr_id;
   }
 }
 
@@ -440,22 +453,25 @@
 std::vector<std::string> RecordFileReader::ReadCmdlineFeature() {
   std::vector<char> buf;
   if (!ReadFeatureSection(FEAT_CMDLINE, &buf)) {
-    return std::vector<std::string>();
+    return {};
   }
-  const char* p = buf.data();
-  const char* end = buf.data() + buf.size();
+  BinaryReader reader(buf.data(), buf.size());
   std::vector<std::string> cmdline;
-  uint32_t arg_count;
-  MoveFromBinaryFormat(arg_count, p);
-  CHECK_LE(p, end);
-  for (size_t i = 0; i < arg_count; ++i) {
-    uint32_t len;
-    MoveFromBinaryFormat(len, p);
-    CHECK_LE(p + len, end);
-    cmdline.push_back(p);
-    p += len;
+
+  uint32_t arg_count = 0;
+  reader.Read(arg_count);
+  for (size_t i = 0; i < arg_count && !reader.error; ++i) {
+    uint32_t aligned_len;
+    reader.Read(aligned_len);
+    cmdline.emplace_back(reader.ReadString());
+    uint32_t len = cmdline.back().size() + 1;
+    if (aligned_len != Align(len, 64)) {
+      reader.error = true;
+      break;
+    }
+    reader.Move(aligned_len - len);
   }
-  return cmdline;
+  return reader.error ? std::vector<std::string>() : cmdline;
 }
 
 std::vector<BuildIdRecord> RecordFileReader::ReadBuildIdFeature() {
@@ -466,16 +482,16 @@
   const char* p = buf.data();
   const char* end = buf.data() + buf.size();
   std::vector<BuildIdRecord> result;
-  while (p < end) {
+  while (p + sizeof(perf_event_header) < end) {
     auto header = reinterpret_cast<const perf_event_header*>(p);
-    if (p + header->size > end) {
+    if ((header->size <= sizeof(perf_event_header)) || (header->size > end - p)) {
       return {};
     }
     std::unique_ptr<char[]> binary(new char[header->size]);
     memcpy(binary.get(), p, header->size);
     p += header->size;
     BuildIdRecord record;
-    if (!record.Parse(file_attrs_[0].attr, binary.get(), binary.get() + header->size)) {
+    if (!record.Parse(event_attrs_[0].attr, binary.get(), binary.get() + header->size)) {
       return {};
     }
     binary.release();
@@ -492,12 +508,11 @@
   if (!ReadFeatureSection(feature, &buf)) {
     return std::string();
   }
-  const char* p = buf.data();
-  const char* end = buf.data() + buf.size();
-  uint32_t len;
-  MoveFromBinaryFormat(len, p);
-  CHECK_LE(p + len, end);
-  return p;
+  BinaryReader reader(buf.data(), buf.size());
+  uint32_t len = 0;
+  reader.Read(len);
+  std::string s = reader.ReadString();
+  return reader.error ? "" : s;
 }
 
 std::vector<uint64_t> RecordFileReader::ReadAuxTraceFeature() {
@@ -505,144 +520,163 @@
   if (!ReadFeatureSection(FEAT_AUXTRACE, &buf)) {
     return {};
   }
-  std::vector<uint64_t> auxtrace_offset;
-  const char* p = buf.data();
-  const char* end = buf.data() + buf.size();
-  if (buf.size() / sizeof(uint64_t) % 2 == 1) {
-    // Recording files generated by linux perf contain an extra uint64 field. Skip it here.
-    p += sizeof(uint64_t);
+  BinaryReader reader(buf.data(), buf.size());
+  if (reader.LeftSize() % sizeof(uint64_t) != 0) {
+    return {};
   }
-  while (p < end) {
+  if (reader.LeftSize() / sizeof(uint64_t) % 2 == 1) {
+    // Recording files generated by linux perf contain an extra uint64 field. Skip it here.
+    reader.Move(sizeof(uint64_t));
+  }
+
+  std::vector<uint64_t> auxtrace_offset;
+  while (!reader.error && reader.LeftSize() > 0u) {
     uint64_t offset;
     uint64_t size;
-    MoveFromBinaryFormat(offset, p);
+    reader.Read(offset);
+    reader.Read(size);
     auxtrace_offset.push_back(offset);
-    MoveFromBinaryFormat(size, p);
-    CHECK_EQ(size, AuxTraceRecord::Size());
+    if (size != AuxTraceRecord::Size()) {
+      reader.error = true;
+    }
   }
-  return auxtrace_offset;
+  return reader.error ? std::vector<uint64_t>() : auxtrace_offset;
 }
 
-bool RecordFileReader::ReadFileFeature(size_t& read_pos, FileFeature* file) {
-  file->Clear();
-  if (HasFeature(FEAT_FILE)) {
-    return ReadFileV1Feature(read_pos, file);
-  }
-  if (HasFeature(FEAT_FILE2)) {
-    return ReadFileV2Feature(read_pos, file);
-  }
-  return false;
-}
+bool RecordFileReader::ReadFileFeature(uint64_t& read_pos, FileFeature& file, bool& error) {
+  file.Clear();
+  error = false;
 
-bool RecordFileReader::ReadFileV1Feature(size_t& read_pos, FileFeature* file) {
-  auto it = feature_section_descriptors_.find(FEAT_FILE);
-  if (it == feature_section_descriptors_.end()) {
+  bool use_v1 = false;
+  PerfFileFormat::SectionDesc desc;
+  if (auto it = feature_section_descriptors_.find(FEAT_FILE);
+      it != feature_section_descriptors_.end()) {
+    use_v1 = true;
+    desc = it->second;
+  } else if (auto it = feature_section_descriptors_.find(FEAT_FILE2);
+             it != feature_section_descriptors_.end()) {
+    desc = it->second;
+  } else {
     return false;
   }
-  if (read_pos >= it->second.size) {
+
+  if (read_pos >= desc.size) {
     return false;
   }
   if (read_pos == 0) {
-    if (fseek(record_fp_, it->second.offset, SEEK_SET) != 0) {
+    if (fseek(record_fp_, desc.offset, SEEK_SET) != 0) {
       PLOG(ERROR) << "fseek() failed";
+      error = true;
       return false;
     }
   }
-  uint32_t size;
-  if (!Read(&size, 4)) {
+
+  bool result = false;
+  if (use_v1) {
+    result = ReadFileV1Feature(read_pos, desc.size - read_pos, file);
+  } else {
+    result = ReadFileV2Feature(read_pos, desc.size - read_pos, file);
+  }
+  if (!result) {
+    LOG(ERROR) << "failed to read file feature section";
+    error = true;
+  }
+  return result;
+}
+
+bool RecordFileReader::ReadFileV1Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file) {
+  uint32_t size = 0;
+  if (max_size < 4 || !Read(&size, 4) || max_size - 4 < size) {
     return false;
   }
+  read_pos += 4;
   std::vector<char> buf(size);
   if (!Read(buf.data(), size)) {
     return false;
   }
-  read_pos += 4 + size;
-  const char* p = buf.data();
-  file->path = p;
-  p += file->path.size() + 1;
-  uint32_t file_type;
-  MoveFromBinaryFormat(file_type, p);
+  read_pos += size;
+  BinaryReader reader(buf.data(), buf.size());
+  file.path = reader.ReadString();
+  uint32_t file_type = 0;
+  reader.Read(file_type);
   if (file_type > DSO_UNKNOWN_FILE) {
-    LOG(ERROR) << "unknown file type for " << file->path
+    LOG(ERROR) << "unknown file type for " << file.path
                << " in file feature section: " << file_type;
     return false;
   }
-  file->type = static_cast<DsoType>(file_type);
-  MoveFromBinaryFormat(file->min_vaddr, p);
-  uint32_t symbol_count;
-  MoveFromBinaryFormat(symbol_count, p);
-  file->symbols.reserve(symbol_count);
-  for (uint32_t i = 0; i < symbol_count; ++i) {
-    uint64_t start_vaddr;
-    uint32_t len;
-    MoveFromBinaryFormat(start_vaddr, p);
-    MoveFromBinaryFormat(len, p);
-    std::string name = p;
-    p += name.size() + 1;
-    file->symbols.emplace_back(name, start_vaddr, len);
-  }
-  if (file->type == DSO_DEX_FILE) {
-    uint32_t offset_count;
-    MoveFromBinaryFormat(offset_count, p);
-    file->dex_file_offsets.resize(offset_count);
-    MoveFromBinaryFormat(file->dex_file_offsets.data(), offset_count, p);
-  }
-  file->file_offset_of_min_vaddr = std::numeric_limits<uint64_t>::max();
-  if ((file->type == DSO_ELF_FILE || file->type == DSO_KERNEL_MODULE) &&
-      static_cast<size_t>(p - buf.data()) < size) {
-    MoveFromBinaryFormat(file->file_offset_of_min_vaddr, p);
-  }
-  CHECK_EQ(size, static_cast<size_t>(p - buf.data()))
-      << "file " << file->path << ", type " << file->type;
-  return true;
-}
-
-bool RecordFileReader::ReadFileV2Feature(size_t& read_pos, FileFeature* file) {
-  auto it = feature_section_descriptors_.find(FEAT_FILE2);
-  if (it == feature_section_descriptors_.end()) {
+  file.type = static_cast<DsoType>(file_type);
+  reader.Read(file.min_vaddr);
+  uint32_t symbol_count = 0;
+  reader.Read(symbol_count);
+  if (symbol_count > size) {
     return false;
   }
-  if (read_pos >= it->second.size) {
-    return false;
+  file.symbols.reserve(symbol_count);
+  while (symbol_count-- > 0) {
+    uint64_t start_vaddr = 0;
+    uint32_t len = 0;
+    reader.Read(start_vaddr);
+    reader.Read(len);
+    std::string name = reader.ReadString();
+    file.symbols.emplace_back(name, start_vaddr, len);
   }
-  if (read_pos == 0) {
-    if (fseek(record_fp_, it->second.offset, SEEK_SET) != 0) {
-      PLOG(ERROR) << "fseek() failed";
+  if (file.type == DSO_DEX_FILE) {
+    uint32_t offset_count = 0;
+    reader.Read(offset_count);
+    if (offset_count > size) {
       return false;
     }
+    file.dex_file_offsets.resize(offset_count);
+    reader.Read(file.dex_file_offsets.data(), offset_count);
   }
+  file.file_offset_of_min_vaddr = std::numeric_limits<uint64_t>::max();
+  if ((file.type == DSO_ELF_FILE || file.type == DSO_KERNEL_MODULE) && !reader.error &&
+      reader.LeftSize() > 0) {
+    reader.Read(file.file_offset_of_min_vaddr);
+  }
+  return !reader.error && reader.LeftSize() == 0;
+}
+
+bool RecordFileReader::ReadFileV2Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file) {
   uint32_t size;
-  if (!Read(&size, 4)) {
+  if (max_size < 4 || !Read(&size, 4) || max_size - 4 < size) {
     return false;
   }
-  read_pos += 4 + size;
+  read_pos += 4;
   std::string s(size, '\0');
   if (!Read(s.data(), size)) {
     return false;
   }
+  read_pos += size;
   proto::FileFeature proto_file;
   if (!proto_file.ParseFromString(s)) {
     return false;
   }
-  file->path = proto_file.path();
-  file->type = static_cast<DsoType>(proto_file.type());
-  file->min_vaddr = proto_file.min_vaddr();
-  file->symbols.reserve(proto_file.symbol_size());
+  file.path = proto_file.path();
+  file.type = static_cast<DsoType>(proto_file.type());
+  file.min_vaddr = proto_file.min_vaddr();
+  file.symbols.reserve(proto_file.symbol_size());
   for (size_t i = 0; i < proto_file.symbol_size(); i++) {
     const auto& proto_symbol = proto_file.symbol(i);
-    file->symbols.emplace_back(proto_symbol.name(), proto_symbol.vaddr(), proto_symbol.len());
+    file.symbols.emplace_back(proto_symbol.name(), proto_symbol.vaddr(), proto_symbol.len());
   }
-  if (file->type == DSO_DEX_FILE) {
-    CHECK(proto_file.has_dex_file());
+  if (file.type == DSO_DEX_FILE) {
+    if (!proto_file.has_dex_file()) {
+      return false;
+    }
     const auto& dex_file_offsets = proto_file.dex_file().dex_file_offset();
-    file->dex_file_offsets.insert(file->dex_file_offsets.end(), dex_file_offsets.begin(),
-                                  dex_file_offsets.end());
-  } else if (file->type == DSO_ELF_FILE) {
-    CHECK(proto_file.has_elf_file());
-    file->file_offset_of_min_vaddr = proto_file.elf_file().file_offset_of_min_vaddr();
-  } else if (file->type == DSO_KERNEL_MODULE) {
-    CHECK(proto_file.has_kernel_module());
-    file->file_offset_of_min_vaddr = proto_file.kernel_module().memory_offset_of_min_vaddr();
+    file.dex_file_offsets.insert(file.dex_file_offsets.end(), dex_file_offsets.begin(),
+                                 dex_file_offsets.end());
+  } else if (file.type == DSO_ELF_FILE) {
+    if (!proto_file.has_elf_file()) {
+      return false;
+    }
+    file.file_offset_of_min_vaddr = proto_file.elf_file().file_offset_of_min_vaddr();
+  } else if (file.type == DSO_KERNEL_MODULE) {
+    if (!proto_file.has_kernel_module()) {
+      return false;
+    }
+    file.file_offset_of_min_vaddr = proto_file.kernel_module().memory_offset_of_min_vaddr();
   }
   return true;
 }
@@ -653,14 +687,24 @@
     if (!ReadFeatureSection(FEAT_META_INFO, &buf)) {
       return false;
     }
-    const char* p = buf.data();
-    const char* end = buf.data() + buf.size();
-    while (p < end) {
-      const char* key = p;
-      const char* value = key + strlen(key) + 1;
-      CHECK(value < end);
-      meta_info_[p] = value;
-      p = value + strlen(value) + 1;
+    std::string_view s(buf.data(), buf.size());
+    size_t key_start = 0;
+    while (key_start < s.size()) {
+      // Parse a C-string for key.
+      size_t key_end = s.find('\0', key_start);
+      if (key_end == key_start || key_end == s.npos) {
+        LOG(ERROR) << "invalid meta info in " << filename_;
+        return false;
+      }
+      // Parse a C-string for value.
+      size_t value_start = key_end + 1;
+      size_t value_end = s.find('\0', value_start);
+      if (value_end == value_start || value_end == s.npos) {
+        LOG(ERROR) << "invalid meta info in " << filename_;
+        return false;
+      }
+      meta_info_[&s[key_start]] = &s[value_start];
+      key_start = value_end + 1;
     }
   }
   return true;
@@ -691,7 +735,7 @@
   return std::nullopt;
 }
 
-void RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
+bool RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
   std::vector<BuildIdRecord> records = ReadBuildIdFeature();
   std::vector<std::pair<std::string, BuildId>> build_ids;
   for (auto& r : records) {
@@ -699,22 +743,34 @@
   }
   Dso::SetBuildIds(build_ids);
 
-  if (HasFeature(PerfFileFormat::FEAT_FILE) || HasFeature(PerfFileFormat::FEAT_FILE2)) {
-    FileFeature file_feature;
-    size_t read_pos = 0;
-    while (ReadFileFeature(read_pos, &file_feature)) {
-      thread_tree.AddDsoInfo(file_feature);
+  FileFeature file_feature;
+  uint64_t read_pos = 0;
+  bool error = false;
+  while (ReadFileFeature(read_pos, file_feature, error)) {
+    if (!thread_tree.AddDsoInfo(file_feature)) {
+      return false;
     }
   }
+  return !error;
 }
 
-bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf, size_t size) {
+bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t size,
+                                   std::vector<uint8_t>& buf, bool& error) {
+  error = false;
   long saved_pos = ftell(record_fp_);
   if (saved_pos == -1) {
     PLOG(ERROR) << "ftell() failed";
+    error = true;
+    return false;
+  }
+  OverflowResult aux_end = SafeAdd(aux_offset, size);
+  if (aux_end.overflow) {
+    LOG(ERROR) << "aux_end overflow";
+    error = true;
     return false;
   }
   if (aux_data_location_.empty() && !BuildAuxDataLocation()) {
+    error = true;
     return false;
   }
   AuxDataLocation* location = nullptr;
@@ -726,21 +782,27 @@
     auto location_it = std::upper_bound(it->second.begin(), it->second.end(), aux_offset, comp);
     if (location_it != it->second.begin()) {
       --location_it;
-      if (location_it->aux_offset + location_it->aux_size >= aux_offset + size) {
+      if (location_it->aux_offset + location_it->aux_size >= aux_end.value) {
         location = &*location_it;
       }
     }
   }
   if (location == nullptr) {
-    LOG(ERROR) << "failed to find file offset of aux data: cpu " << cpu << ", aux_offset "
-               << aux_offset << ", size " << size;
+    // ETM data can be dropped when recording if the userspace buffer is full. This isn't an error.
+    LOG(INFO) << "aux data is missing: cpu " << cpu << ", aux_offset " << aux_offset << ", size "
+              << size << ". Probably the data is lost when recording.";
     return false;
   }
-  if (!ReadAtOffset(aux_offset - location->aux_offset + location->file_offset, buf, size)) {
+  if (buf.size() < size) {
+    buf.resize(size);
+  }
+  if (!ReadAtOffset(aux_offset - location->aux_offset + location->file_offset, buf.data(), size)) {
+    error = true;
     return false;
   }
   if (fseek(record_fp_, saved_pos, SEEK_SET) != 0) {
     PLOG(ERROR) << "fseek() failed";
+    error = true;
     return false;
   }
   return true;
@@ -748,21 +810,36 @@
 
 bool RecordFileReader::BuildAuxDataLocation() {
   std::vector<uint64_t> auxtrace_offset = ReadAuxTraceFeature();
-  if (auxtrace_offset.empty()) {
-    LOG(ERROR) << "failed to read auxtrace feature section";
-    return false;
-  }
   std::unique_ptr<char[]> buf(new char[AuxTraceRecord::Size()]);
   for (auto offset : auxtrace_offset) {
     if (!ReadAtOffset(offset, buf.get(), AuxTraceRecord::Size())) {
       return false;
     }
     AuxTraceRecord auxtrace;
-    if (!auxtrace.Parse(file_attrs_[0].attr, buf.get(), buf.get() + AuxTraceRecord::Size())) {
+    if (!auxtrace.Parse(event_attrs_[0].attr, buf.get(), buf.get() + AuxTraceRecord::Size())) {
       return false;
     }
-    aux_data_location_[auxtrace.data->cpu].emplace_back(
-        auxtrace.data->offset, auxtrace.data->aux_size, offset + auxtrace.size());
+    AuxDataLocation location(auxtrace.data->offset, auxtrace.data->aux_size,
+                             offset + auxtrace.size());
+    OverflowResult aux_end = SafeAdd(location.aux_offset, location.aux_size);
+    OverflowResult file_end = SafeAdd(location.file_offset, location.aux_size);
+    if (aux_end.overflow || file_end.overflow || file_end.value > file_size_) {
+      LOG(ERROR) << "invalid auxtrace feature section";
+      return false;
+    }
+    auto location_it = aux_data_location_.find(auxtrace.data->cpu);
+    if (location_it != aux_data_location_.end()) {
+      const AuxDataLocation& prev_location = location_it->second.back();
+      uint64_t prev_aux_end = prev_location.aux_offset + prev_location.aux_size;
+      // The AuxTraceRecords should be sorted by aux_offset for each cpu.
+      if (prev_aux_end > location.aux_offset) {
+        LOG(ERROR) << "invalid auxtrace feature section";
+        return false;
+      }
+      location_it->second.emplace_back(location);
+    } else {
+      aux_data_location_[auxtrace.data->cpu].emplace_back(location);
+    }
   }
   return true;
 }
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index bf0436a..e43e4bd 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -40,20 +40,18 @@
   void SetUp() override { close(tmpfile_.release()); }
 
   void AddEventType(const std::string& event_type_str) {
+    uint64_t fake_id = attr_ids_.size();
+    attr_ids_.resize(attr_ids_.size() + 1);
+    EventAttrWithId& attr_id = attr_ids_.back();
     std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
     ASSERT_TRUE(event_type_modifier != nullptr);
-    perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
-    attr.sample_id_all = 1;
-    attrs_.push_back(std::unique_ptr<perf_event_attr>(new perf_event_attr(attr)));
-    EventAttrWithId attr_id;
-    attr_id.attr = attrs_.back().get();
-    attr_id.ids.push_back(attrs_.size());  // Fake id.
-    attr_ids_.push_back(attr_id);
+    attr_id.attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+    attr_id.attr.sample_id_all = 1;
+    attr_id.ids.push_back(fake_id);
   }
 
   TemporaryFile tmpfile_;
-  std::vector<std::unique_ptr<perf_event_attr>> attrs_;
-  std::vector<EventAttrWithId> attr_ids_;
+  EventAttrIds attr_ids_;
 };
 
 TEST_F(RecordFileTest, smoke) {
@@ -66,7 +64,7 @@
   ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
 
   // Write data section.
-  MmapRecord mmap_record(*(attr_ids_[0].attr), true, 1, 1, 0x1000, 0x2000, 0x3000,
+  MmapRecord mmap_record(attr_ids_[0].attr, true, 1, 1, 0x1000, 0x2000, 0x3000,
                          "mmap_record_example", attr_ids_[0].ids[0]);
   ASSERT_TRUE(writer->WriteRecord(mmap_record));
 
@@ -86,9 +84,9 @@
   // Read from a record file.
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(reader != nullptr);
-  std::vector<EventAttrWithId> attrs = reader->AttrSection();
+  const EventAttrIds& attrs = reader->AttrSection();
   ASSERT_EQ(1u, attrs.size());
-  ASSERT_EQ(0, memcmp(attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+  ASSERT_EQ(0, memcmp(&attrs[0].attr, &attr_ids_[0].attr, sizeof(perf_event_attr)));
   ASSERT_EQ(attrs[0].ids, attr_ids_[0].ids);
 
   // Read and check data section.
@@ -120,10 +118,10 @@
   // Read from a record file.
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(reader != nullptr);
-  std::vector<EventAttrWithId> attrs = reader->AttrSection();
+  const EventAttrIds& attrs = reader->AttrSection();
   ASSERT_EQ(3u, attrs.size());
   for (size_t i = 0; i < attrs.size(); ++i) {
-    ASSERT_EQ(0, memcmp(attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+    ASSERT_EQ(0, memcmp(&attrs[i].attr, &attr_ids_[i].attr, sizeof(perf_event_attr)));
     ASSERT_EQ(attrs[i].ids, attr_ids_[i].ids);
   }
 }
@@ -218,15 +216,16 @@
   // Read from a record file.
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(reader != nullptr);
-  size_t read_pos = 0;
+  uint64_t read_pos = 0;
   FileFeature file;
+  bool error = false;
 
   auto check_symbol = [](const Symbol& sym1, const Symbol& sym2) {
     return sym1.addr == sym2.addr && sym1.len == sym2.len && strcmp(sym1.Name(), sym2.Name()) == 0;
   };
 
   size_t file_id = 0;
-  while (reader->ReadFileFeature(read_pos, &file)) {
+  while (reader->ReadFileFeature(read_pos, file, error)) {
     ASSERT_LT(file_id, files.size());
     const FileFeature& expected_file = files[file_id++];
     ASSERT_EQ(file.path, expected_file.path);
@@ -253,5 +252,6 @@
       ASSERT_EQ(file.file_offset_of_min_vaddr, expected_file.file_offset_of_min_vaddr);
     }
   }
+  ASSERT_FALSE(error);
   ASSERT_EQ(file_id, files.size());
 }
\ No newline at end of file
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
index 0773777..ed94e39 100644
--- a/simpleperf/record_file_writer.cpp
+++ b/simpleperf/record_file_writer.cpp
@@ -74,7 +74,7 @@
   }
 }
 
-bool RecordFileWriter::WriteAttrSection(const std::vector<EventAttrWithId>& attr_ids) {
+bool RecordFileWriter::WriteAttrSection(const EventAttrIds& attr_ids) {
   if (attr_ids.empty()) {
     return false;
   }
@@ -102,7 +102,7 @@
   }
   for (auto& attr_id : attr_ids) {
     FileAttr file_attr;
-    file_attr.attr = *attr_id.attr;
+    file_attr.attr = attr_id.attr;
     file_attr.ids.offset = id_section_offset;
     file_attr.ids.size = attr_id.ids.size() * sizeof(uint64_t);
     id_section_offset += file_attr.ids.size;
@@ -121,7 +121,7 @@
   data_section_offset_ = data_section_offset;
 
   // Save event_attr for use when reading records.
-  event_attr_ = *attr_ids[0].attr;
+  event_attr_ = attr_ids[0].attr;
   return true;
 }
 
@@ -202,7 +202,10 @@
     if (!Read(record_buf.data(), Record::header_size())) {
       return false;
     }
-    RecordHeader header(record_buf.data());
+    RecordHeader header;
+    if (!header.Parse(record_buf.data())) {
+      return false;
+    }
     if (record_buf.size() < header.size) {
       record_buf.resize(header.size);
     }
@@ -365,7 +368,13 @@
     proto::FileFeature::Symbol* proto_symbol = proto_file.add_symbol();
     proto_symbol->set_vaddr(symbol.addr);
     proto_symbol->set_len(symbol.len);
-    proto_symbol->set_name(symbol.Name());
+    // Store demangled names for rust symbols. Because simpleperf on windows host doesn't know
+    // how to demangle them.
+    if (strncmp(symbol.Name(), "_R", 2) == 0) {
+      proto_symbol->set_name(symbol.DemangledName());
+    } else {
+      proto_symbol->set_name(symbol.Name());
+    }
   };
   for (const Symbol& symbol : file.symbols) {
     write_symbol(symbol);
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
index ddecc9b..20c9c18 100644
--- a/simpleperf/record_test.cpp
+++ b/simpleperf/record_test.cpp
@@ -104,14 +104,29 @@
 }
 
 TEST_F(RecordTest, SampleRecord_ReplaceRegAndStackWithCallChain) {
-  event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
-  SampleRecord expected(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1, PERF_CONTEXT_USER, 2, 3, 4, 5}, {},
-                        0);
-  for (size_t stack_size : {8, 1024}) {
-    SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1}, std::vector<char>(stack_size), 10);
-    r.ReplaceRegAndStackWithCallChain({2, 3, 4, 5});
-    CheckRecordMatchBinary(r);
-    CheckRecordEqual(r, expected);
+  event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+  std::vector<std::vector<uint64_t>> user_ip_tests = {
+      {},                     // no userspace ips, just remove stack and reg fields
+      {2},                    // add one userspace ip, no need to allocate new binary
+      {2, 3, 4, 5, 6, 7, 8},  // add more userspace ips, may need to allocate new binary
+  };
+  std::vector<uint64_t> stack_size_tests = {0, 8, 1024};
+
+  for (const auto& user_ips : user_ip_tests) {
+    std::vector<uint64_t> ips = {1};
+    if (!user_ips.empty()) {
+      ips.push_back(PERF_CONTEXT_USER);
+      ips.insert(ips.end(), user_ips.begin(), user_ips.end());
+    }
+    SampleRecord expected(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, ips, {}, 0);
+    for (size_t stack_size : stack_size_tests) {
+      event_attr.sample_type |= PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+      SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1}, std::vector<char>(stack_size), 10);
+      event_attr.sample_type &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
+      r.ReplaceRegAndStackWithCallChain(user_ips);
+      CheckRecordMatchBinary(r);
+      CheckRecordEqual(r, expected);
+    }
   }
 }
 
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index 1025c32..3654c57 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -200,6 +200,7 @@
   const char* GetSupportedTraceOffCpuModes();
   bool SetTraceOffCpuMode(const char* mode);
   bool SetSampleFilter(const char** filters, int filters_len);
+  bool AggregateThreads(const char** thread_name_regex, int thread_name_regex_len);
 
   Sample* GetNextSample();
   Event* GetEventOfCurrentSample() { return &current_event_; }
@@ -240,6 +241,7 @@
   FeatureSection feature_section_;
   std::vector<char> feature_section_data_;
   CallChainReportBuilder callchain_report_builder_;
+  ThreadReportBuilder thread_report_builder_;
   std::unique_ptr<Tracing> tracing_;
   RecordFilter record_filter_;
 };
@@ -310,17 +312,27 @@
   return record_filter_.ParseOptions(options);
 }
 
+bool ReportLib::AggregateThreads(const char** thread_name_regex, int thread_name_regex_len) {
+  std::vector<std::string> regs(thread_name_regex_len);
+  for (int i = 0; i < thread_name_regex_len; ++i) {
+    regs[i] = thread_name_regex[i];
+  }
+  return thread_report_builder_.AggregateThreads(regs);
+}
+
 bool ReportLib::OpenRecordFileIfNecessary() {
   if (record_file_reader_ == nullptr) {
     record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
     if (record_file_reader_ == nullptr) {
       return false;
     }
-    record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+    if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+      return false;
+    }
     auto& meta_info = record_file_reader_->GetMetaInfoFeature();
     if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end() && it->second == "true") {
       // If recorded with --trace-offcpu, default is to report on-off-cpu samples.
-      std::string event_name = GetEventNameByAttr(*record_file_reader_->AttrSection()[0].attr);
+      std::string event_name = GetEventNameByAttr(record_file_reader_->AttrSection()[0].attr);
       if (!android::base::StartsWith(event_name, "cpu-clock") &&
           !android::base::StartsWith(event_name, "task-clock")) {
         LOG(ERROR) << "Recording file " << record_filename_ << " is no longer supported. "
@@ -362,7 +374,10 @@
     } else if (record->type() == PERF_RECORD_TRACING_DATA ||
                record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) {
       const auto& r = *static_cast<TracingDataRecord*>(record.get());
-      tracing_.reset(new Tracing(std::vector<char>(r.data, r.data + r.data_size)));
+      tracing_ = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
+      if (!tracing_) {
+        return nullptr;
+      }
     }
   }
   SetCurrentSample(*sample_record_queue_.front());
@@ -442,10 +457,11 @@
   current_mappings_.clear();
   callchain_entries_.clear();
   current_sample_.ip = r.ip_data.ip;
-  current_sample_.pid = r.tid_data.pid;
-  current_sample_.tid = r.tid_data.tid;
   current_thread_ = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
-  current_sample_.thread_comm = current_thread_->comm;
+  ThreadReport thread_report = thread_report_builder_.Build(*current_thread_);
+  current_sample_.pid = thread_report.pid;
+  current_sample_.tid = thread_report.tid;
+  current_sample_.thread_comm = thread_report.thread_name;
   current_sample_.time = r.time_data.time;
   current_sample_.in_kernel = r.InKernel();
   current_sample_.cpu = r.cpu_data.cpu;
@@ -501,10 +517,10 @@
 }
 
 void ReportLib::CreateEvents() {
-  std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+  const EventAttrIds& attrs = record_file_reader_->AttrSection();
   events_.resize(attrs.size());
   for (size_t i = 0; i < attrs.size(); ++i) {
-    events_[i].attr = *attrs[i].attr;
+    events_[i].attr = attrs[i].attr;
     events_[i].name = GetEventNameByAttr(events_[i].attr);
     EventInfo::TracingInfo& tracing_info = events_[i].tracing_info;
     if (events_[i].attr.type == PERF_TYPE_TRACEPOINT && tracing_) {
@@ -599,6 +615,8 @@
 const char* GetSupportedTraceOffCpuModes(ReportLib* report_lib) EXPORT;
 bool SetTraceOffCpuMode(ReportLib* report_lib, const char* mode) EXPORT;
 bool SetSampleFilter(ReportLib* report_lib, const char** filters, int filters_len) EXPORT;
+bool AggregateThreads(ReportLib* report_lib, const char** thread_name_regex,
+                      int thread_name_regex_len) EXPORT;
 
 Sample* GetNextSample(ReportLib* report_lib) EXPORT;
 Event* GetEventOfCurrentSample(ReportLib* report_lib) EXPORT;
@@ -663,6 +681,11 @@
   return report_lib->SetSampleFilter(filters, filters_len);
 }
 
+bool AggregateThreads(ReportLib* report_lib, const char** thread_name_regex,
+                      int thread_name_regex_len) {
+  return report_lib->AggregateThreads(thread_name_regex, thread_name_regex_len);
+}
+
 Sample* GetNextSample(ReportLib* report_lib) {
   return report_lib->GetNextSample();
 }
diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp
index a0ff784..ecbc6aa 100644
--- a/simpleperf/report_utils.cpp
+++ b/simpleperf/report_utils.cpp
@@ -16,6 +16,10 @@
 
 #include "report_utils.h"
 
+#include <stdlib.h>
+
+#include <android-base/parsebool.h>
+#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 
 #include "JITDebugReader.h"
@@ -23,6 +27,157 @@
 
 namespace simpleperf {
 
+bool ProguardMappingRetrace::AddProguardMappingFile(std::string_view mapping_file) {
+  // The mapping file format is described in
+  // https://www.guardsquare.com/en/products/proguard/manual/retrace.
+  // Additional info provided by R8 is described in
+  // https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md.
+  line_reader_.reset(new LineReader(mapping_file));
+  android::base::ScopeGuard g([&]() { line_reader_ = nullptr; });
+
+  if (!line_reader_->Ok()) {
+    PLOG(ERROR) << "failed to read " << mapping_file;
+    return false;
+  }
+
+  MoveToNextLine();
+  while (cur_line_.type != LineType::LINE_EOF) {
+    if (cur_line_.type == LineType::CLASS_LINE) {
+      // Match line "original_classname -> obfuscated_classname:".
+      std::string_view s = cur_line_.data;
+      auto arrow_pos = s.find(" -> ");
+      auto arrow_end_pos = arrow_pos + strlen(" -> ");
+      if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) {
+        std::string_view original_classname = s.substr(0, arrow_pos);
+        std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos));
+        MappingClass& cur_class = class_map_[obfuscated_classname];
+        cur_class.original_classname = original_classname;
+        MoveToNextLine();
+        if (cur_line_.type == LineType::SYNTHESIZED_COMMENT) {
+          cur_class.synthesized = true;
+          MoveToNextLine();
+        }
+
+        while (cur_line_.type == LineType::METHOD_LINE) {
+          ParseMethod(cur_class);
+        }
+        continue;
+      }
+    }
+
+    // Skip unparsed line.
+    MoveToNextLine();
+  }
+  return true;
+}
+
+void ProguardMappingRetrace::ParseMethod(MappingClass& mapping_class) {
+  // Match line "... [original_classname.]original_methodname(...)... -> obfuscated_methodname".
+  std::string_view s = cur_line_.data;
+  auto arrow_pos = s.find(" -> ");
+  auto arrow_end_pos = arrow_pos + strlen(" -> ");
+  if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) {
+    if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) {
+      std::string_view name = s.substr(space_pos + 1, left_brace_pos - space_pos - 1);
+      bool contains_classname = name.find('.') != name.npos;
+      if (contains_classname && android::base::StartsWith(name, mapping_class.original_classname)) {
+        name.remove_prefix(mapping_class.original_classname.size() + 1);
+        contains_classname = false;
+      }
+      std::string original_methodname(name);
+      std::string obfuscated_methodname(s.substr(arrow_end_pos));
+      bool synthesized = false;
+
+      MoveToNextLine();
+      if (cur_line_.type == LineType::SYNTHESIZED_COMMENT) {
+        synthesized = true;
+        MoveToNextLine();
+      }
+
+      auto& method_map = mapping_class.method_map;
+      if (auto it = method_map.find(obfuscated_methodname); it != method_map.end()) {
+        // The obfuscated method name already exists. We don't know which one to choose.
+        // So just prefer the latter one unless it's synthesized.
+        if (!synthesized) {
+          it->second.original_name = original_methodname;
+          it->second.contains_classname = contains_classname;
+          it->second.synthesized = synthesized;
+        }
+      } else {
+        auto& method = method_map[obfuscated_methodname];
+        method.original_name = original_methodname;
+        method.contains_classname = contains_classname;
+        method.synthesized = synthesized;
+      }
+      return;
+    }
+  }
+
+  // Skip unparsed line.
+  MoveToNextLine();
+}
+
+void ProguardMappingRetrace::MoveToNextLine() {
+  std::string* line;
+  while ((line = line_reader_->ReadLine()) != nullptr) {
+    std::string_view s = *line;
+    if (s.empty()) {
+      continue;
+    }
+    size_t non_space_pos = s.find_first_not_of(' ');
+    if (non_space_pos != s.npos && s[non_space_pos] == '#') {
+      // Skip all comments unless it's synthesized comment.
+      if (s.find("com.android.tools.r8.synthesized") != s.npos) {
+        cur_line_.type = SYNTHESIZED_COMMENT;
+        cur_line_.data = s;
+        return;
+      }
+      continue;
+    }
+    if (s.find(" -> ") == s.npos) {
+      // Skip unknown lines.
+      continue;
+    }
+    cur_line_.data = s;
+    if (s[0] == ' ') {
+      cur_line_.type = METHOD_LINE;
+    } else {
+      cur_line_.type = CLASS_LINE;
+    }
+    return;
+  }
+  cur_line_.type = LINE_EOF;
+}
+
+bool ProguardMappingRetrace::DeObfuscateJavaMethods(std::string_view obfuscated_name,
+                                                    std::string* original_name, bool* synthesized) {
+  if (auto split_pos = obfuscated_name.rfind('.'); split_pos != obfuscated_name.npos) {
+    std::string obfuscated_classname(obfuscated_name.substr(0, split_pos));
+
+    if (auto it = class_map_.find(obfuscated_classname); it != class_map_.end()) {
+      const MappingClass& mapping_class = it->second;
+      const auto& method_map = mapping_class.method_map;
+      std::string obfuscated_methodname(obfuscated_name.substr(split_pos + 1));
+
+      if (auto method_it = method_map.find(obfuscated_methodname); method_it != method_map.end()) {
+        const auto& method = method_it->second;
+        if (method.contains_classname) {
+          *original_name = method.original_name;
+        } else {
+          *original_name = mapping_class.original_classname + "." + method.original_name;
+        }
+        *synthesized = method.synthesized;
+      } else {
+        // Only the classname is obfuscated.
+        *original_name = mapping_class.original_classname + "." + obfuscated_methodname;
+        *synthesized = mapping_class.synthesized;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
 static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) {
   if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
     // art_jni_trampoline/art_quick_generic_jni_trampoline are trampolines used to call jni
@@ -34,51 +189,26 @@
   return false;
 };
 
-bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
-  // The mapping file format is described in
-  // https://www.guardsquare.com/en/products/proguard/manual/retrace.
-  LineReader reader(mapping_file);
-  if (!reader.Ok()) {
-    PLOG(ERROR) << "failed to read " << mapping_file;
-    return false;
+CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree)
+    : thread_tree_(thread_tree) {
+  const char* env_name = "REMOVE_R8_SYNTHESIZED_FRAME";
+  const char* s = getenv(env_name);
+  if (s != nullptr) {
+    auto result = android::base::ParseBool(s);
+    if (result == android::base::ParseBoolResult::kError) {
+      LOG(WARNING) << "invalid value in env variable " << env_name;
+    } else if (result == android::base::ParseBoolResult::kTrue) {
+      LOG(INFO) << "R8 synthesized frames will be removed.";
+      remove_r8_synthesized_frame_ = true;
+    }
   }
-  ProguardMappingClass* cur_class = nullptr;
-  std::string* line;
-  while ((line = reader.ReadLine()) != nullptr) {
-    std::string_view s = *line;
-    if (s.empty() || s[0] == '#') {
-      continue;
-    }
-    auto arrow_pos = s.find(" -> ");
-    if (arrow_pos == s.npos) {
-      continue;
-    }
-    auto arrow_end_pos = arrow_pos + strlen(" -> ");
+}
 
-    if (s[0] != ' ') {
-      // Match line "original_classname -> obfuscated_classname:".
-      if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) {
-        std::string_view original_classname = s.substr(0, arrow_pos);
-        std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos));
-        cur_class = &proguard_class_map_[obfuscated_classname];
-        cur_class->original_classname = original_classname;
-      }
-    } else if (cur_class != nullptr) {
-      // Match line "... [original_classname.]original_methodname(...)... ->
-      // obfuscated_methodname".
-      if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) {
-        if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) {
-          auto original_methodname = s.substr(space_pos + 1, left_brace_pos - space_pos - 1);
-          if (android::base::StartsWith(original_methodname, cur_class->original_classname)) {
-            original_methodname.remove_prefix(cur_class->original_classname.size() + 1);
-          }
-          std::string obfuscated_methodname(s.substr(arrow_end_pos));
-          cur_class->method_map[obfuscated_methodname] = original_methodname;
-        }
-      }
-    }
+bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
+  if (!retrace_) {
+    retrace_.reset(new ProguardMappingRetrace);
   }
-  return true;
+  return retrace_->AddProguardMappingFile(mapping_file);
 }
 
 std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread,
@@ -118,7 +248,7 @@
   if (convert_jit_frame_) {
     ConvertJITFrame(result);
   }
-  if (!proguard_class_map_.empty()) {
+  if (retrace_) {
     DeObfuscateJavaMethods(result);
   }
   return result;
@@ -227,24 +357,78 @@
 }
 
 void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) {
-  for (auto& entry : callchain) {
+  for (size_t i = 0; i < callchain.size();) {
+    auto& entry = callchain[i];
     if (!IsJavaEntry(entry)) {
+      i++;
       continue;
     }
     std::string_view name = entry.symbol->FunctionName();
-    if (auto split_pos = name.rfind('.'); split_pos != name.npos) {
-      std::string obfuscated_classname(name.substr(0, split_pos));
-      if (auto it = proguard_class_map_.find(obfuscated_classname);
-          it != proguard_class_map_.end()) {
-        const ProguardMappingClass& proguard_class = it->second;
-        std::string obfuscated_methodname(name.substr(split_pos + 1));
-        if (auto method_it = proguard_class.method_map.find(obfuscated_methodname);
-            method_it != proguard_class.method_map.end()) {
-          std::string new_symbol_name = proguard_class.original_classname + "." + method_it->second;
-          entry.symbol->SetDemangledName(new_symbol_name);
-        }
+    std::string original_name;
+    bool synthesized;
+    if (retrace_->DeObfuscateJavaMethods(name, &original_name, &synthesized)) {
+      if (synthesized && remove_r8_synthesized_frame_) {
+        callchain.erase(callchain.begin() + i);
+        continue;
       }
+      entry.symbol->SetDemangledName(original_name);
     }
+    i++;
+  }
+}
+
+bool ThreadReportBuilder::AggregateThreads(const std::vector<std::string>& thread_name_regex) {
+  size_t i = thread_regs_.size();
+  thread_regs_.resize(i + thread_name_regex.size());
+  for (const auto& reg_str : thread_name_regex) {
+    std::unique_ptr<RegEx> re = RegEx::Create(reg_str);
+    if (!re) {
+      return false;
+    }
+    thread_regs_[i++].re = std::move(re);
+  }
+  return true;
+}
+
+ThreadReport ThreadReportBuilder::Build(const ThreadEntry& thread) {
+  ThreadReport report(thread.pid, thread.tid, thread.comm);
+  ModifyReportToAggregateThreads(report);
+  return report;
+}
+
+void ThreadReportBuilder::ModifyReportToAggregateThreads(ThreadReport& report) {
+  if (thread_regs_.empty()) {
+    // No modification when there are no regular expressions.
+    return;
+  }
+  const std::string thread_name = report.thread_name;
+  if (auto it = thread_map_.find(thread_name); it != thread_map_.end()) {
+    // Found cached result in thread_map_.
+    if (it->second != -1) {
+      report = thread_regs_[it->second].report;
+    }
+    return;
+  }
+  // Run the slow path to walk through every regular expression.
+  size_t index;
+  for (index = 0; index < thread_regs_.size(); ++index) {
+    if (thread_regs_[index].re->Match(thread_name)) {
+      break;
+    }
+  }
+  if (index == thread_regs_.size()) {
+    thread_map_[thread_name] = -1;
+  } else {
+    thread_map_[thread_name] = static_cast<int>(index);
+    // Modify thread report.
+    auto& aggregated_report = thread_regs_[index].report;
+    if (aggregated_report.thread_name == nullptr) {
+      // Use regular expression as the name of the aggregated thread. So users know it's an
+      // aggregated thread.
+      aggregated_report =
+          ThreadReport(report.pid, report.tid, thread_regs_[index].re->GetPattern().c_str());
+    }
+    report = aggregated_report;
   }
 }
 
diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h
index e4cc23d..fa42bba 100644
--- a/simpleperf/report_utils.h
+++ b/simpleperf/report_utils.h
@@ -23,11 +23,56 @@
 #include <unordered_map>
 #include <vector>
 
+#include "RegEx.h"
 #include "dso.h"
 #include "thread_tree.h"
+#include "utils.h"
 
 namespace simpleperf {
 
+class ProguardMappingRetrace {
+ public:
+  // Add proguard mapping.txt to de-obfuscate minified symbols.
+  bool AddProguardMappingFile(std::string_view mapping_file);
+
+  bool DeObfuscateJavaMethods(std::string_view obfuscated_name, std::string* original_name,
+                              bool* synthesized);
+
+ private:
+  struct MappingMethod {
+    std::string original_name;
+    bool contains_classname;
+    bool synthesized;
+  };
+
+  struct MappingClass {
+    std::string original_classname;
+    bool synthesized = false;
+    // Map from obfuscated method names to MappingMethod.
+    std::unordered_map<std::string, MappingMethod> method_map;
+  };
+
+  enum LineType {
+    SYNTHESIZED_COMMENT,
+    CLASS_LINE,
+    METHOD_LINE,
+    LINE_EOF,
+  };
+
+  struct LineInfo {
+    LineType type;
+    std::string_view data;
+  };
+
+  void ParseMethod(MappingClass& mapping_class);
+  void MoveToNextLine();
+
+  // Map from obfuscated class names to ProguardMappingClass.
+  std::unordered_map<std::string, MappingClass> class_map_;
+  std::unique_ptr<LineReader> line_reader_;
+  LineInfo cur_line_;
+};
+
 enum class CallChainExecutionType {
   NATIVE_METHOD,
   INTERPRETED_JVM_METHOD,
@@ -48,7 +93,7 @@
 
 class CallChainReportBuilder {
  public:
-  CallChainReportBuilder(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+  CallChainReportBuilder(ThreadTree& thread_tree);
   // If true, remove interpreter frames both before and after a Java frame.
   // Default is true.
   void SetRemoveArtFrame(bool enable) { remove_art_frame_ = enable; }
@@ -67,12 +112,6 @@
     JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {}
   };
 
-  struct ProguardMappingClass {
-    std::string original_classname;
-    // Map from minified method names to original method names.
-    std::unordered_map<std::string, std::string> method_map;
-  };
-
   void MarkArtFrame(std::vector<CallChainReportEntry>& callchain);
   void ConvertJITFrame(std::vector<CallChainReportEntry>& callchain);
   void CollectJavaMethods();
@@ -80,11 +119,41 @@
 
   ThreadTree& thread_tree_;
   bool remove_art_frame_ = true;
+  bool remove_r8_synthesized_frame_ = false;
   bool convert_jit_frame_ = true;
   bool java_method_initialized_ = false;
   std::unordered_map<std::string, JavaMethod> java_method_map_;
-  // Map from minified class names to ProguardMappingClass.
-  std::unordered_map<std::string, ProguardMappingClass> proguard_class_map_;
+  std::unique_ptr<ProguardMappingRetrace> retrace_;
+};
+
+struct ThreadReport {
+  int pid;
+  int tid;
+  const char* thread_name;
+
+  ThreadReport(int pid = 0, int tid = 0, const char* thread_name = nullptr)
+      : pid(pid), tid(tid), thread_name(thread_name) {}
+};
+
+// Report thread info of a sample.
+class ThreadReportBuilder {
+ public:
+  // Aggregate threads with names matching the same regex.
+  bool AggregateThreads(const std::vector<std::string>& thread_name_regex);
+  ThreadReport Build(const ThreadEntry& thread);
+
+ private:
+  void ModifyReportToAggregateThreads(ThreadReport& report);
+
+  struct ThreadNameRegInfo {
+    std::unique_ptr<RegEx> re;
+    ThreadReport report;
+  };
+
+  std::vector<ThreadNameRegInfo> thread_regs_;
+  // Map from thread name to the corresponding index in thread_regs_.
+  // Return -1 if the thread name doesn't match any regular expression.
+  std::unordered_map<std::string, int> thread_map_;
 };
 
 }  // namespace simpleperf
diff --git a/simpleperf/report_utils_test.cpp b/simpleperf/report_utils_test.cpp
index 401bbd7..5577121 100644
--- a/simpleperf/report_utils_test.cpp
+++ b/simpleperf/report_utils_test.cpp
@@ -16,6 +16,8 @@
 
 #include <gtest/gtest.h>
 
+#include <stdlib.h>
+
 #include <string>
 #include <vector>
 
@@ -25,6 +27,47 @@
 
 using namespace simpleperf;
 
+TEST(ProguardMappingRetrace, smoke) {
+  TemporaryFile tmpfile;
+  close(tmpfile.release());
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("original.class.A -> A:\n"
+                                       "\n"
+                                       "    void method_a() -> a\n"
+                                       "    void method_b() -> b\n"
+                                       "      # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
+                                       "      # some other comments\n"
+                                       "    void original.class.M.method_c() -> c\n"
+                                       "    void original.class.A.method_d() -> d\n"
+                                       "original.class.B -> B:\n"
+                                       "# some other comments\n"
+                                       "original.class.C -> C:\n"
+                                       "# {\'id\':\'com.android.tools.r8.synthesized\'}\n",
+                                       tmpfile.path));
+  ProguardMappingRetrace retrace;
+  ASSERT_TRUE(retrace.AddProguardMappingFile(tmpfile.path));
+  std::string original_name;
+  bool synthesized;
+  ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.a", &original_name, &synthesized));
+  ASSERT_EQ(original_name, "original.class.A.method_a");
+  ASSERT_FALSE(synthesized);
+  ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.b", &original_name, &synthesized));
+  ASSERT_EQ(original_name, "original.class.A.method_b");
+  ASSERT_TRUE(synthesized);
+  ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.c", &original_name, &synthesized));
+  ASSERT_EQ(original_name, "original.class.M.method_c");
+  ASSERT_FALSE(synthesized);
+  ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.d", &original_name, &synthesized));
+  ASSERT_EQ(original_name, "original.class.A.method_d");
+  ASSERT_FALSE(synthesized);
+  ASSERT_TRUE(retrace.DeObfuscateJavaMethods("B.b", &original_name, &synthesized));
+  ASSERT_EQ(original_name, "original.class.B.b");
+  ASSERT_FALSE(synthesized);
+  ASSERT_TRUE(retrace.DeObfuscateJavaMethods("C.c", &original_name, &synthesized));
+  ASSERT_EQ(original_name, "original.class.C.c");
+  ASSERT_TRUE(synthesized);
+}
+
 class CallChainReportBuilderTest : public testing::Test {
  protected:
   virtual void SetUp() {
@@ -65,6 +108,7 @@
                    Symbol("java_method2", 0x3000, 0x100),
                    Symbol("java_method3", 0x3100, 0x100),
                    Symbol("obfuscated_class.obfuscated_java_method2", 0x3200, 0x100),
+                   Symbol("obfuscated_class.java_method4", 0x3300, 0x100),
                });
 
     // Add map layout for libraries used in the thread:
@@ -265,11 +309,12 @@
   std::vector<uint64_t> fake_ips = {
       0x2200,  // 2200,  // obfuscated_class.obfuscated_java_method
       0x3200,  // 3200,  // obfuscated_class.obfuscated_java_method2
+      0x3300,  // 3300,  // obfuscated_class.java_method4
   };
   CallChainReportBuilder builder(thread_tree);
   // Symbol names aren't changed when not given proguard mapping files.
   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
-  ASSERT_EQ(entries.size(), 2);
+  ASSERT_EQ(entries.size(), 3);
   ASSERT_EQ(entries[0].ip, 0x2200);
   ASSERT_STREQ(entries[0].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method");
   ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
@@ -280,6 +325,11 @@
   ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
   ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+  ASSERT_EQ(entries[2].ip, 0x3300);
+  ASSERT_STREQ(entries[2].symbol->DemangledName(), "obfuscated_class.java_method4");
+  ASSERT_EQ(entries[2].dso->Path(), fake_jit_cache_path);
+  ASSERT_EQ(entries[2].vaddr_in_file, 0x3300);
+  ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
 
   // Symbol names are changed when given a proguard mapping file.
   TemporaryFile tmpfile;
@@ -294,6 +344,47 @@
       tmpfile.path));
   builder.AddProguardMappingFile(tmpfile.path);
   entries = builder.Build(thread, fake_ips, 0);
+  ASSERT_EQ(entries.size(), 3);
+  ASSERT_EQ(entries[0].ip, 0x2200);
+  ASSERT_STREQ(entries[0].symbol->DemangledName(),
+               "android.support.v4.app.RemoteActionCompatParcelizer.read");
+  ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
+  ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
+  ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+  ASSERT_EQ(entries[1].ip, 0x3200);
+  ASSERT_STREQ(entries[1].symbol->DemangledName(),
+               "android.support.v4.app.RemoteActionCompatParcelizer.read2");
+  ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
+  ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
+  ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+  ASSERT_STREQ(entries[2].symbol->DemangledName(),
+               "android.support.v4.app.RemoteActionCompatParcelizer.java_method4");
+  ASSERT_EQ(entries[2].dso->Path(), fake_jit_cache_path);
+  ASSERT_EQ(entries[2].vaddr_in_file, 0x3300);
+  ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+}
+
+TEST_F(CallChainReportBuilderTest, not_remove_synthesized_frame_by_default) {
+  std::vector<uint64_t> fake_ips = {
+      0x2200,  // 2200,  // obfuscated_class.obfuscated_java_method
+      0x3200,  // 3200,  // obfuscated_class.obfuscated_java_method2
+  };
+
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(android::base::WriteStringToFile(
+      "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
+      "    13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
+      "Parcel) -> obfuscated_java_method\n"
+      "      # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
+      "    13:13:androidx.core.app.RemoteActionCompat "
+      "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
+      "VersionedParcel) -> obfuscated_java_method2",
+      tmpfile.path));
+
+  // By default, synthesized frames are kept.
+  CallChainReportBuilder builder(thread_tree);
+  builder.AddProguardMappingFile(tmpfile.path);
+  std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
   ASSERT_EQ(entries.size(), 2);
   ASSERT_EQ(entries[0].ip, 0x2200);
   ASSERT_STREQ(entries[0].symbol->DemangledName(),
@@ -309,6 +400,41 @@
   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
 }
 
+TEST_F(CallChainReportBuilderTest, remove_synthesized_frame_with_env_variable) {
+  // Windows doesn't support setenv and unsetenv. So don't test on it.
+#if !defined(__WIN32)
+  std::vector<uint64_t> fake_ips = {
+      0x2200,  // 2200,  // obfuscated_class.obfuscated_java_method
+      0x3200,  // 3200,  // obfuscated_class.obfuscated_java_method2
+  };
+
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(android::base::WriteStringToFile(
+      "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
+      "    13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
+      "Parcel) -> obfuscated_java_method\n"
+      "      # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
+      "    13:13:androidx.core.app.RemoteActionCompat "
+      "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
+      "VersionedParcel) -> obfuscated_java_method2",
+      tmpfile.path));
+
+  // With environment variable set, synthesized frames are removed.
+  ASSERT_EQ(setenv("REMOVE_R8_SYNTHESIZED_FRAME", "1", 1), 0);
+  CallChainReportBuilder builder(thread_tree);
+  ASSERT_EQ(unsetenv("REMOVE_R8_SYNTHESIZED_FRAME"), 0);
+  builder.AddProguardMappingFile(tmpfile.path);
+  std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+  ASSERT_EQ(entries.size(), 1);
+  ASSERT_EQ(entries[0].ip, 0x3200);
+  ASSERT_STREQ(entries[0].symbol->DemangledName(),
+               "android.support.v4.app.RemoteActionCompatParcelizer.read2");
+  ASSERT_EQ(entries[0].dso->Path(), fake_jit_cache_path);
+  ASSERT_EQ(entries[0].vaddr_in_file, 0x3200);
+  ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+#endif  // !defined(__WIN32)
+}
+
 TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file_for_jit_method_with_signature) {
   std::vector<uint64_t> fake_ips = {
       0x3200,  // 3200,  // void ctep.v(cteo, ctgc, ctbn)
@@ -413,3 +539,45 @@
   ASSERT_EQ(entries[1].vaddr_in_file, 0x200);
   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
 }
+
+class ThreadReportBuilderTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    thread_tree.SetThreadName(1, 1, "thread1");
+    thread_tree.SetThreadName(1, 2, "thread-pool1");
+    thread_tree.SetThreadName(1, 3, "thread-pool2");
+  }
+
+  bool IsReportEqual(const ThreadReport& report1, const ThreadReport& report2) {
+    return report1.pid == report2.pid && report1.tid == report2.tid &&
+           strcmp(report1.thread_name, report2.thread_name) == 0;
+  }
+
+  ThreadTree thread_tree;
+};
+
+TEST_F(ThreadReportBuilderTest, no_setting) {
+  ThreadReportBuilder builder;
+  ThreadEntry* thread = thread_tree.FindThread(1);
+  ThreadReport report = builder.Build(*thread);
+  ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 1, "thread1")));
+}
+
+TEST_F(ThreadReportBuilderTest, aggregate_threads) {
+  ThreadReportBuilder builder;
+  ASSERT_TRUE(builder.AggregateThreads({"thread-pool.*"}));
+  ThreadEntry* thread = thread_tree.FindThread(1);
+  ThreadReport report = builder.Build(*thread);
+  ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 1, "thread1")));
+  thread = thread_tree.FindThread(2);
+  report = builder.Build(*thread);
+  ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 2, "thread-pool.*")));
+  thread = thread_tree.FindThread(3);
+  report = builder.Build(*thread);
+  ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 2, "thread-pool.*")));
+}
+
+TEST_F(ThreadReportBuilderTest, aggregate_threads_bad_regex) {
+  ThreadReportBuilder builder;
+  ASSERT_FALSE(builder.AggregateThreads({"?thread-pool*"}));
+}
diff --git a/simpleperf/runtest/Android.bp b/simpleperf/runtest/Android.bp
index dee4ffd..53fdfef 100644
--- a/simpleperf/runtest/Android.bp
+++ b/simpleperf/runtest/Android.bp
@@ -96,3 +96,10 @@
     defaults: ["simpleperf_runtest_defaults"],
     srcs: ["run_and_sleep.cpp"],
 }
+
+// Used as an example in collect_etm_data_for_autofdo.md.
+cc_binary {
+    name: "etm_test_loop",
+    srcs: ["etm_test_loop.cpp"],
+    // afdo: true,
+}
diff --git a/simpleperf/runtest/etm_test_loop.cpp b/simpleperf/runtest/etm_test_loop.cpp
new file mode 100644
index 0000000..a5f4f97
--- /dev/null
+++ b/simpleperf/runtest/etm_test_loop.cpp
@@ -0,0 +1,20 @@
+
+void f1() {
+  for (volatile int i = 0; i < 100; i++) {
+  }
+}
+
+void f2() {
+  for (volatile int i = 0; i < 1000; i++) {
+  }
+}
+
+int main() {
+  for (volatile int i = 0; i < 10; i++) {
+    if (i * 3 < 6) {
+      f1();
+    } else {
+      f2();
+    }
+  }
+}
diff --git a/simpleperf/rust/lib.rs b/simpleperf/rust/lib.rs
index 7d39273..94ad879 100644
--- a/simpleperf/rust/lib.rs
+++ b/simpleperf/rust/lib.rs
@@ -48,7 +48,7 @@
 }
 
 /// Trigger an ETM trace event.
-pub fn record(trace_file: &Path, duration: &Duration, scope: RecordScope) {
+pub fn record(trace_file: &Path, duration: &Duration, binary_filter: &str, scope: RecordScope) {
     let event_name: CString = match scope {
         RecordScope::USERSPACE => CString::new("cs-etm:u").unwrap(),
         RecordScope::KERNEL => CString::new("cs-etm:k").unwrap(),
@@ -56,9 +56,15 @@
     };
     let trace_file = path_to_cstr(trace_file);
     let duration = duration.as_secs_f32();
+    let binary_filter = CString::new(binary_filter).unwrap();
 
     unsafe {
-        simpleperf_profcollect_bindgen::Record(event_name.as_ptr(), trace_file.as_ptr(), duration);
+        simpleperf_profcollect_bindgen::Record(
+            event_name.as_ptr(),
+            trace_file.as_ptr(),
+            duration,
+            binary_filter.as_ptr(),
+        );
     }
 }
 
@@ -76,3 +82,18 @@
         );
     }
 }
+
+/// Save logs in file.
+pub fn set_log_file(filename: &Path) {
+    let log_file = path_to_cstr(filename);
+    unsafe {
+        simpleperf_profcollect_bindgen::SetLogFile(log_file.as_ptr());
+    }
+}
+
+/// Stop using log file.
+pub fn reset_log_file() {
+    unsafe {
+        simpleperf_profcollect_bindgen::ResetLogFile();
+    }
+}
diff --git a/simpleperf/scripts/Android.bp b/simpleperf/scripts/Android.bp
new file mode 100644
index 0000000..abbbe24
--- /dev/null
+++ b/simpleperf/scripts/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2022 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.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_extras_simpleperf_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_extras_simpleperf_license"],
+}
+
+python_library_host {
+    name: "simpleperf_report_lib",
+    srcs: [
+        "simpleperf_report_lib.py",
+        "simpleperf_utils.py",
+    ],
+    data: [
+        "bin/darwin/x86_64/libsimpleperf_report.dylib",
+        "bin/linux/x86_64/libsimpleperf_report.so",
+        "bin/windows/**/*.dll",
+    ],
+}
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index 808e3e9..a6399ef 100755
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -233,7 +233,7 @@
         raise NotImplementedError
 
     def start_profiling(self, target_args):
-        """Start simpleperf reocrd process on device."""
+        """Start simpleperf record process on device."""
         args = ['/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data',
                 self.args.record_options]
         if self.adb.run(['shell', 'ls', NATIVE_LIBS_DIR_ON_DEVICE]):
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index db2fde7..bf6b056 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 785b6e3..a81a14b 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 97a168d..32fc919 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 c250c45..cb7f7bf 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_64/libsimpleperf_report.dylib b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
index 191e51f..cd829eb 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 1a6a887..4a4396d 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_64/libsimpleperf_report.so b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
index ca444ec..1f9f13f 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 dca7b30..d8dd840 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_64/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
index b49e8f8..4282b4c 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 9838e77..47fcd20 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/binary_cache_builder.py b/simpleperf/scripts/binary_cache_builder.py
index 4322e2c..46c8532 100755
--- a/simpleperf/scripts/binary_cache_builder.py
+++ b/simpleperf/scripts/binary_cache_builder.py
@@ -19,13 +19,14 @@
     it, and put them in binary_cache.
 """
 
-from dataclasses import dataclass
+from collections import defaultdict
 import logging
 import os
 import os.path
 from pathlib import Path
 import shutil
-from typing import List, Optional, Union
+import sys
+from typing import Dict, List, Optional, Tuple, Union
 
 from simpleperf_report_lib import ReportLib
 from simpleperf_utils import (
@@ -37,27 +38,233 @@
     return dso_name.split('/')[-1].startswith('TemporaryFile')
 
 
-class BinaryCacheBuilder(object):
+class BinaryCache:
+    def __init__(self, binary_dir: Path):
+        self.binary_dir = binary_dir
+
+    def get_path_in_cache(self, device_path: str, build_id: str) -> Path:
+        """ Given a binary path in perf.data, return its corresponding path in the cache.
+        """
+        if build_id:
+            filename = device_path.split('/')[-1]
+            # Add build id to make the filename unique.
+            unique_filename = build_id[2:] + '-' + filename
+            return self.binary_dir / unique_filename
+
+        # For elf file without build id, we can only follow its path on device. Otherwise,
+        # simpleperf can't find it. However, we don't prefer this way. Because:
+        # 1) It doesn't work for native libs loaded directly from apk
+        #    (android:extractNativeLibs=”false”).
+        # 2) It may exceed path limit on windows.
+        if device_path.startswith('/'):
+            device_path = device_path[1:]
+        device_path = device_path.replace('/', os.sep)
+        return Path(os.path.join(self.binary_dir, device_path))
+
+
+class BinarySource:
+    """ Source to find debug binaries. """
+
+    def __init__(self, readelf: ReadElf):
+        self.readelf = readelf
+
+    def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
+        """ pull binaries needed in perf.data to binary_cache.
+            binaries: maps from binary path to its build_id in perf.data.
+        """
+        raise Exception('not implemented')
+
+    def read_build_id(self, path: Path):
+        return self.readelf.get_build_id(path)
+
+
+class BinarySourceFromDevice(BinarySource):
+    """ Pull binaries from device. """
+
+    def __init__(self, readelf: ReadElf, disable_adb_root: bool):
+        super().__init__(readelf)
+        self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root)
+
+    def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
+        if not self.adb.is_device_available():
+            return
+        for path, build_id in binaries.items():
+            self.collect_binary(path, build_id, binary_cache)
+        self.pull_kernel_symbols(binary_cache.binary_dir / 'kallsyms')
+
+    def collect_binary(self, path: str, build_id: str, binary_cache: BinaryCache):
+        if not path.startswith('/') or path == "//anon" or path.startswith("/dev/"):
+            # [kernel.kallsyms] or unknown, or something we can't find binary.
+            return
+        binary_cache_file = binary_cache.get_path_in_cache(path, build_id)
+        self.check_and_pull_binary(path, build_id, binary_cache_file)
+
+    def check_and_pull_binary(self, path: str, expected_build_id: str, binary_cache_file: Path):
+        """If the binary_cache_file exists and has the expected_build_id, there
+           is no need to pull the binary from device. Otherwise, pull it.
+        """
+        if binary_cache_file.is_file() and (
+                not expected_build_id or expected_build_id == self.read_build_id(binary_cache_file)
+        ):
+            logging.info('use current file in binary_cache: %s', binary_cache_file)
+        else:
+            logging.info('pull file to binary_cache: %s to %s', path, binary_cache_file)
+            target_dir = binary_cache_file.parent
+            if not target_dir.is_dir():
+                os.makedirs(target_dir)
+            if binary_cache_file.is_file():
+                binary_cache_file.unlink()
+            self.pull_file_from_device(path, binary_cache_file)
+
+    def pull_file_from_device(self, device_path: str, host_path: Path):
+        if self.adb.run(['pull', device_path, str(host_path)]):
+            return True
+        # On non-root devices, we can't pull /data/app/XXX/base.odex directly.
+        # Instead, we can first copy the file to /data/local/tmp, then pull it.
+        filename = device_path[device_path.rfind('/')+1:]
+        if (self.adb.run(['shell', 'cp', device_path, '/data/local/tmp']) and
+                self.adb.run(['pull', '/data/local/tmp/' + filename, host_path])):
+            self.adb.run(['shell', 'rm', '/data/local/tmp/' + filename])
+            return True
+        logging.warning('failed to pull %s from device', device_path)
+        return False
+
+    def pull_kernel_symbols(self, file_path: Path):
+        if file_path.is_file():
+            file_path.unlink()
+        if self.adb.switch_to_root():
+            self.adb.run(['shell', 'echo', '0', '>/proc/sys/kernel/kptr_restrict'])
+            self.adb.run(['pull', '/proc/kallsyms', file_path])
+
+
+class BinarySourceFromLibDirs(BinarySource):
+    """ Collect binaries from lib dirs. """
+
+    def __init__(self, readelf: ReadElf, lib_dirs: List[Path]):
+        super().__init__(readelf)
+        self.lib_dirs = lib_dirs
+        self.filename_map = None
+        self.build_id_map = None
+        self.binary_cache = None
+
+    def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
+        self.create_filename_map(binaries)
+        self.create_build_id_map(binaries)
+        self.binary_cache = binary_cache
+
+        # Search all files in lib_dirs, and copy matching files to build_cache.
+        for lib_dir in self.lib_dirs:
+            if self.is_platform_symbols_dir(lib_dir):
+                self.search_platform_symbols_dir(lib_dir)
+            else:
+                self.search_dir(lib_dir)
+
+    def create_filename_map(self, binaries: Dict[str, str]):
+        """ Create a map mapping from filename to binaries having the name. """
+        self.filename_map = defaultdict(list)
+        for path, build_id in binaries.items():
+            index = path.rfind('/')
+            filename = path[index + 1:]
+            self.filename_map[filename].append((path, build_id))
+
+    def create_build_id_map(self, binaries: Dict[str, str]):
+        """ Create a map mapping from build id to binary path. """
+        self.build_id_map = {}
+        for path, build_id in binaries.items():
+            if build_id:
+                self.build_id_map[build_id] = path
+
+    def is_platform_symbols_dir(self, lib_dir: Path):
+        """ Check if lib_dir points to $ANDROID_PRODUCT_OUT/symbols. """
+        subdir_names = [p.name for p in lib_dir.iterdir()]
+        return lib_dir.name == 'symbols' and 'system' in subdir_names
+
+    def search_platform_symbols_dir(self, lib_dir: Path):
+        """ Platform symbols dir contains too many binaries. Reading build ids for
+            all of them takes a long time. So we only read build ids for binaries
+            having names exist in filename_map.
+        """
+        for root, _, files in os.walk(lib_dir):
+            for filename in files:
+                binaries = self.filename_map.get(filename)
+                if not binaries:
+                    continue
+                file_path = Path(os.path.join(root, filename))
+                build_id = self.read_build_id(file_path)
+                for path, expected_build_id in binaries:
+                    if expected_build_id == build_id:
+                        self.copy_to_binary_cache(file_path, build_id, path)
+
+    def search_dir(self, lib_dir: Path):
+        """ For a normal lib dir, it's unlikely to contain many binaries. So we can read
+            build ids for all binaries in it. But users may give debug binaries with a name
+            different from the one recorded in perf.data. So we should only rely on build id
+            if it is available.
+        """
+        for root, _, files in os.walk(lib_dir):
+            for filename in files:
+                file_path = Path(os.path.join(root, filename))
+                build_id = self.read_build_id(file_path)
+                if build_id:
+                    # For elf file with build id, use build id to match.
+                    device_path = self.build_id_map.get(build_id)
+                    if device_path:
+                        self.copy_to_binary_cache(file_path, build_id, device_path)
+                elif self.readelf.is_elf_file(file_path):
+                    # For elf file without build id, use filename to match.
+                    for path, expected_build_id in self.filename_map.get(filename, []):
+                        if not expected_build_id:
+                            self.copy_to_binary_cache(file_path, '', path)
+                            break
+
+    def copy_to_binary_cache(
+            self, from_path: Path, expected_build_id: str, device_path: str):
+        to_path = self.binary_cache.get_path_in_cache(device_path, expected_build_id)
+        if not self.need_to_copy(from_path, to_path, expected_build_id):
+            # The existing file in binary_cache can provide more information, so no need to copy.
+            return
+        to_dir = to_path.parent
+        if not to_dir.is_dir():
+            os.makedirs(to_dir)
+        logging.info('copy to binary_cache: %s to %s', from_path, to_path)
+        shutil.copy(from_path, to_path)
+
+    def need_to_copy(self, from_path: Path, to_path: Path, expected_build_id: str):
+        if not to_path.is_file() or self.read_build_id(to_path) != expected_build_id:
+            return True
+        return self.get_file_stripped_level(from_path) < self.get_file_stripped_level(to_path)
+
+    def get_file_stripped_level(self, path: Path) -> int:
+        """Return stripped level of an ELF file. Larger value means more stripped."""
+        sections = self.readelf.get_sections(path)
+        if '.debug_line' in sections:
+            return 0
+        if '.symtab' in sections:
+            return 1
+        return 2
+
+
+class BinaryCacheBuilder:
     """Collect all binaries needed by perf.data in binary_cache."""
 
     def __init__(self, ndk_path: Optional[str], disable_adb_root: bool):
-        self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root)
         self.readelf = ReadElf(ndk_path)
-        self.binary_cache_dir = 'binary_cache'
-        if not os.path.isdir(self.binary_cache_dir):
-            os.makedirs(self.binary_cache_dir)
+        self.device_source = BinarySourceFromDevice(self.readelf, disable_adb_root)
+        self.binary_cache_dir = Path('binary_cache')
+        self.binary_cache = BinaryCache(self.binary_cache_dir)
         self.binaries = {}
 
-    def build_binary_cache(self, perf_data_path: str, symfs_dirs: List[Union[Path, str]]):
+    def build_binary_cache(self, perf_data_path: str, symfs_dirs: List[Union[Path, str]]) -> bool:
+        self.binary_cache_dir.mkdir(exist_ok=True)
         self.collect_used_binaries(perf_data_path)
-        self.copy_binaries_from_symfs_dirs(symfs_dirs)
-        if self.adb.is_device_available():
-            self.pull_binaries_from_device()
-            self._pull_kernel_symbols()
+        if not self.copy_binaries_from_symfs_dirs(symfs_dirs):
+            return False
+        self.pull_binaries_from_device()
         self.create_build_id_list()
+        return True
 
     def collect_used_binaries(self, perf_data_path):
-        """read perf.data, collect all used binaries and their build id (if available)."""
+        """read perf.data, collect all used binaries and their build id(if available)."""
         # A dict mapping from binary name to build_id
         binaries = {}
         lib = ReportLib()
@@ -82,149 +289,45 @@
                     binaries[name] = lib.GetBuildIdForPath(dso_name)
         self.binaries = binaries
 
-    def copy_binaries_from_symfs_dirs(self, symfs_dirs: List[Union[Path, str]]):
-        """collect all files in symfs_dirs."""
-        if not symfs_dirs:
-            return
-
-        # It is possible that the path of the binary in symfs_dirs doesn't match
-        # the one recorded in perf.data. For example, a file in symfs_dirs might
-        # be "debug/arm/obj/armeabi-v7a/libsudo-game-jni.so", but the path in
-        # perf.data is "/data/app/xxxx/lib/arm/libsudo-game-jni.so". So we match
-        # binaries if they have the same filename (like libsudo-game-jni.so)
-        # and same build_id.
-
-        # Map from filename to binary paths.
-        filename_dict = {}
-        for binary in self.binaries:
-            index = binary.rfind('/')
-            filename = binary[index+1:]
-            paths = filename_dict.get(filename)
-            if paths is None:
-                filename_dict[filename] = paths = []
-            paths.append(binary)
-
-        # Walk through all files in symfs_dirs, and copy matching files to build_cache.
-        for symfs_dir in symfs_dirs:
-            for root, _, files in os.walk(symfs_dir):
-                for filename in files:
-                    paths = filename_dict.get(filename)
-                    if not paths:
-                        continue
-                    build_id = self._read_build_id(os.path.join(root, filename))
-                    for binary in paths:
-                        expected_build_id = self.binaries.get(binary)
-                        if expected_build_id == build_id:
-                            self._copy_to_binary_cache(os.path.join(root, filename),
-                                                       expected_build_id, binary)
-                            break
-
-    def _copy_to_binary_cache(self, from_path, expected_build_id, target_file):
-        if target_file[0] == '/':
-            target_file = target_file[1:]
-        target_file = target_file.replace('/', os.sep)
-        target_file = os.path.join(self.binary_cache_dir, target_file)
-        if not self._need_to_copy(from_path, target_file, expected_build_id):
-            # The existing file in binary_cache can provide more information, so no need to copy.
-            return
-        target_dir = os.path.dirname(target_file)
-        if not os.path.isdir(target_dir):
-            os.makedirs(target_dir)
-        logging.info('copy to binary_cache: %s to %s' % (from_path, target_file))
-        shutil.copy(from_path, target_file)
-
-    def _need_to_copy(self, source_file, target_file, expected_build_id):
-        if not os.path.isfile(target_file):
-            return True
-        if self._read_build_id(target_file) != expected_build_id:
-            return True
-        return self._get_file_stripped_level(source_file) < self._get_file_stripped_level(
-            target_file)
-
-    def _get_file_stripped_level(self, file_path):
-        """Return stripped level of an ELF file. Larger value means more stripped."""
-        sections = self.readelf.get_sections(file_path)
-        if '.debug_line' in sections:
-            return 0
-        if '.symtab' in sections:
-            return 1
-        return 2
+    def copy_binaries_from_symfs_dirs(self, symfs_dirs: List[Union[str, Path]]) -> bool:
+        if symfs_dirs:
+            lib_dirs: List[Path] = []
+            for symfs_dir in symfs_dirs:
+                if isinstance(symfs_dir, str):
+                    symfs_dir = Path(symfs_dir)
+                if not symfs_dir.is_dir():
+                    logging.error("can't find dir %s", symfs_dir)
+                    return False
+                lib_dirs.append(symfs_dir)
+            lib_dir_source = BinarySourceFromLibDirs(self.readelf, lib_dirs)
+            lib_dir_source.collect_binaries(self.binaries, self.binary_cache)
+        return True
 
     def pull_binaries_from_device(self):
-        """pull binaries needed in perf.data to binary_cache."""
-        for binary in self.binaries:
-            build_id = self.binaries[binary]
-            if not binary.startswith('/') or binary == "//anon" or binary.startswith("/dev/"):
-                # [kernel.kallsyms] or unknown, or something we can't find binary.
-                continue
-            binary_cache_file = binary[1:].replace('/', os.sep)
-            binary_cache_file = os.path.join(self.binary_cache_dir, binary_cache_file)
-            self._check_and_pull_binary(binary, build_id, binary_cache_file)
-
-    def _check_and_pull_binary(self, binary, expected_build_id, binary_cache_file):
-        """If the binary_cache_file exists and has the expected_build_id, there
-           is no need to pull the binary from device. Otherwise, pull it.
-        """
-        need_pull = True
-        if os.path.isfile(binary_cache_file):
-            need_pull = False
-            if expected_build_id:
-                build_id = self._read_build_id(binary_cache_file)
-                if expected_build_id != build_id:
-                    need_pull = True
-        if need_pull:
-            target_dir = os.path.dirname(binary_cache_file)
-            if not os.path.isdir(target_dir):
-                os.makedirs(target_dir)
-            if os.path.isfile(binary_cache_file):
-                os.remove(binary_cache_file)
-            logging.info('pull file to binary_cache: %s to %s' % (binary, binary_cache_file))
-            self._pull_file_from_device(binary, binary_cache_file)
-        else:
-            logging.info('use current file in binary_cache: %s' % binary_cache_file)
-
-    def _read_build_id(self, file_path):
-        """read build id of a binary on host."""
-        return self.readelf.get_build_id(file_path)
-
-    def _pull_file_from_device(self, device_path, host_path):
-        if self.adb.run(['pull', device_path, host_path]):
-            return True
-        # In non-root device, we can't pull /data/app/XXX/base.odex directly.
-        # Instead, we can first copy the file to /data/local/tmp, then pull it.
-        filename = device_path[device_path.rfind('/')+1:]
-        if (self.adb.run(['shell', 'cp', device_path, '/data/local/tmp']) and
-                self.adb.run(['pull', '/data/local/tmp/' + filename, host_path])):
-            self.adb.run(['shell', 'rm', '/data/local/tmp/' + filename])
-            return True
-        logging.warning('failed to pull %s from device' % device_path)
-        return False
-
-    def _pull_kernel_symbols(self):
-        file_path = os.path.join(self.binary_cache_dir, 'kallsyms')
-        if os.path.isfile(file_path):
-            os.remove(file_path)
-        if self.adb.switch_to_root():
-            self.adb.run(['shell', 'echo', '0', '>/proc/sys/kernel/kptr_restrict'])
-            self.adb.run(['pull', '/proc/kallsyms', file_path])
+        self.device_source.collect_binaries(self.binaries, self.binary_cache)
 
     def create_build_id_list(self):
         """ Create build_id_list. So report scripts can find a binary by its build_id instead of
             path.
         """
-        build_id_list_path = os.path.join(self.binary_cache_dir, 'build_id_list')
+        build_id_list_path = self.binary_cache_dir / 'build_id_list'
+        # Write in binary mode to avoid "\r\n" problem on windows, which can confuse simpleperf.
         with open(build_id_list_path, 'wb') as fh:
             for root, _, files in os.walk(self.binary_cache_dir):
                 for filename in files:
-                    path = os.path.join(root, filename)
-                    relative_path = path[len(self.binary_cache_dir) + 1:]
-                    build_id = self._read_build_id(path)
+                    path = Path(os.path.join(root, filename))
+                    build_id = self.readelf.get_build_id(path)
                     if build_id:
+                        relative_path = path.relative_to(self.binary_cache_dir)
                         line = f'{build_id}={relative_path}\n'
                         fh.write(str_to_bytes(line))
 
+    def find_path_in_cache(self, device_path: str) -> Optional[Path]:
+        build_id = self.binaries.get(device_path)
+        return self.binary_cache.get_path_in_cache(device_path, build_id)
 
-def main():
+
+def main() -> bool:
     parser = BaseArgumentParser(description="""
         Pull binaries needed by perf.data from device to binary_cache directory.""")
     parser.add_argument('-i', '--perf_data_path', default='perf.data', type=extant_file, help="""
@@ -238,8 +341,8 @@
     ndk_path = None if not args.ndk_path else args.ndk_path[0]
     builder = BinaryCacheBuilder(ndk_path, args.disable_adb_root)
     symfs_dirs = flatten_arg_list(args.native_lib_dir)
-    builder.build_binary_cache(args.perf_data_path, symfs_dirs)
+    return builder.build_binary_cache(args.perf_data_path, symfs_dirs)
 
 
 if __name__ == '__main__':
-    main()
+    sys.exit(0 if main() else 1)
diff --git a/simpleperf/scripts/gecko_profile_generator.py b/simpleperf/scripts/gecko_profile_generator.py
index 396c9a5..741841c 100755
--- a/simpleperf/scripts/gecko_profile_generator.py
+++ b/simpleperf/scripts/gecko_profile_generator.py
@@ -25,13 +25,15 @@
   Then open gecko-profile.json.gz in https://profiler.firefox.com/
 """
 
-import json
-import sys
-
+from collections import Counter
 from dataclasses import dataclass, field
+import json
+import logging
+import sys
+from typing import List, Dict, Optional, NamedTuple, Tuple
+
 from simpleperf_report_lib import ReportLib
-from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
-from typing import List, Dict, Optional, NamedTuple, Set, Tuple
+from simpleperf_utils import BaseArgumentParser, ReportLibOptions
 
 
 StringID = int
@@ -67,6 +69,10 @@
     stack_id: Optional[StackID]
     time_ms: Milliseconds
     responsiveness: int
+    complete_stack: bool
+
+    def to_json(self):
+        return [self.stack_id, self.time_ms, self.responsiveness]
 
 
 # Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/profile.js#L425
@@ -108,6 +114,13 @@
         "color": 'green',
         "subcategories": ['Other']
     },
+    {
+        "name": 'Off-CPU',
+        # Follow Brendan Gregg's Flamegraph convention: blue for off-CPU
+        # https://github.com/brendangregg/FlameGraph/blob/810687f180f3c4929b5d965f54817a5218c9d89b/flamegraph.pl#L470
+        "color": 'blue',
+        "subcategories": ['Other']
+    },
     # Not used by this exporter yet, but some Firefox Profiler code assumes
     # there is an 'Other' category by searching for a category with
     # color=grey, so include this.
@@ -119,6 +132,14 @@
 ]
 
 
+def is_complete_stack(stack: List[str]) -> bool:
+    """ Check if the callstack is complete. The stack starts from root. """
+    for entry in stack:
+        if ('__libc_init' in entry) or ('__start_thread' in entry):
+            return True
+    return False
+
+
 @dataclass
 class Thread:
     """A builder for a profile of a single thread.
@@ -183,12 +204,19 @@
         # Heuristic: kernel code contains "kallsyms" as the library name.
         if "kallsyms" in frame_str or ".ko" in frame_str:
             category = 1
+            if frame_str.startswith("__schedule "):
+                category = 5
         elif ".so" in frame_str:
             category = 2
         elif ".vdex" in frame_str:
             category = 3
         elif ".oat" in frame_str:
             category = 4
+        # Heuristic: empirically, off-CPU profiles mostly measure off-CPU time
+        # accounted to the linux kernel __schedule function, which handles
+        # blocking. This only works if we have kernel symbol (kallsyms)
+        # access though.
+        # https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/kernel/sched/core.c;l=6593;drc=0c99414a07ddaa18d8eb4be90b551d2687cbde2f
 
         self.frameTable.append(Frame(
             string_id=string_id,
@@ -203,7 +231,7 @@
         ))
         return frame_id
 
-    def _add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None:
+    def add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None:
         """Add a timestamped stack trace sample to the thread builder.
 
         Args:
@@ -223,13 +251,43 @@
 
         self.samples.append(Sample(stack_id=prefix_stack_id,
                                    time_ms=time_ms,
-                                   responsiveness=0))
+                                   responsiveness=0,
+                                   complete_stack=is_complete_stack(stack)))
 
-    def _to_json_dict(self) -> Dict:
-        """Converts this Thread to GeckoThread JSON format."""
-        # The samples aren't guaranteed to be in order. Sort them by time.
+    def sort_samples(self) -> None:
+        """ The samples aren't guaranteed to be in order. Sort them by time. """
         self.samples.sort(key=lambda s: s.time_ms)
 
+    def remove_stack_gaps(self, max_remove_gap_length: int, gap_distr: Dict[int, int]) -> None:
+        """ Ideally all callstacks are complete. But some may be broken for different reasons.
+            To create a smooth view in "Stack Chart", remove small gaps of broken callstacks.
+
+        Args:
+            max_remove_gap_length: the max length of continuous broken-stack samples to remove
+        """
+        if max_remove_gap_length == 0:
+            return
+        i = 0
+        remove_flags = [False] * len(self.samples)
+        while i < len(self.samples):
+            if self.samples[i].complete_stack:
+                i += 1
+                continue
+            n = 1
+            while (i + n < len(self.samples)) and (not self.samples[i + n].complete_stack):
+                n += 1
+            gap_distr[n] += 1
+            if n <= max_remove_gap_length:
+                for j in range(i, i + n):
+                    remove_flags[j] = True
+            i += n
+        if True in remove_flags:
+            old_samples = self.samples
+            self.samples = [s for s, remove in zip(old_samples, remove_flags) if not remove]
+
+    def to_json_dict(self) -> Dict:
+        """Converts this Thread to GeckoThread JSON format."""
+
         # Gecko profile format is row-oriented data as List[List],
         # And a schema for interpreting each index.
         # Schema:
@@ -258,7 +316,7 @@
                     "time": 1,
                     "responsiveness": 2,
                 },
-                "data": self.samples
+                "data": [s.to_json() for s in self.samples],
             },
             # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
             "frameTable": {
@@ -291,11 +349,37 @@
         }
 
 
+def remove_stack_gaps(max_remove_gap_length: int, thread_map: Dict[int, Thread]) -> None:
+    """ Remove stack gaps for each thread, and print status. """
+    if max_remove_gap_length == 0:
+        return
+    total_sample_count = 0
+    remove_sample_count = 0
+    gap_distr = Counter()
+    for tid in list(thread_map.keys()):
+        thread = thread_map[tid]
+        old_n = len(thread.samples)
+        thread.remove_stack_gaps(max_remove_gap_length, gap_distr)
+        new_n = len(thread.samples)
+        total_sample_count += old_n
+        remove_sample_count += old_n - new_n
+        if new_n == 0:
+            del thread_map[tid]
+    if total_sample_count != 0:
+        logging.info('Remove stack gaps with length <= %d. %d (%.2f%%) samples are removed.',
+                     max_remove_gap_length, remove_sample_count,
+                     remove_sample_count / total_sample_count * 100
+                     )
+        logging.debug('Stack gap length distribution among samples (gap_length: count): %s',
+                      gap_distr)
+
+
 def _gecko_profile(
         record_file: str,
         symfs_dir: Optional[str],
         kallsyms_file: Optional[str],
-        report_lib_options: ReportLibOptions) -> GeckoProfile:
+        report_lib_options: ReportLibOptions,
+        max_remove_gap_length: int) -> GeckoProfile:
     """convert a simpleperf profile to gecko format"""
     lib = ReportLib()
 
@@ -319,7 +403,6 @@
         if sample is None:
             lib.Close()
             break
-        event = lib.GetEventOfCurrentSample()
         symbol = lib.GetSymbolOfCurrentSample()
         callchain = lib.GetCallChainOfCurrentSample()
         sample_time_ms = sample.time / 1000000
@@ -336,7 +419,7 @@
         if thread is None:
             thread = Thread(comm=sample.thread_comm, pid=sample.pid, tid=sample.tid)
             threadMap[sample.tid] = thread
-        thread._add_sample(
+        thread.add_sample(
             comm=sample.thread_comm,
             stack=stack,
             # We are being a bit fast and loose here with time here.  simpleperf
@@ -347,7 +430,12 @@
             # setting `simpleperf record --clockid realtime`.
             time_ms=sample_time_ms)
 
-    threads = [thread._to_json_dict() for thread in threadMap.values()]
+    for thread in threadMap.values():
+        thread.sort_samples()
+
+    remove_stack_gaps(max_remove_gap_length, threadMap)
+
+    threads = [thread.to_json_dict() for thread in threadMap.values()]
 
     profile_timestamp = meta_info.get('timestamp')
     end_time_ms = (int(profile_timestamp) * 1000) if profile_timestamp else 0
@@ -397,13 +485,22 @@
     parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.')
     parser.add_argument('-i', '--record_file', nargs='?', default='perf.data',
                         help='Default is perf.data.')
+    parser.add_argument('--remove-gaps', metavar='MAX_GAP_LENGTH', dest='max_remove_gap_length',
+                        type=int, default=3, help="""
+                        Ideally all callstacks are complete. But some may be broken for different
+                        reasons. To create a smooth view in "Stack Chart", remove small gaps of
+                        broken callstacks. MAX_GAP_LENGTH is the max length of continuous
+                        broken-stack samples we want to remove.
+                        """
+                        )
     parser.add_report_lib_options()
     args = parser.parse_args()
     profile = _gecko_profile(
         record_file=args.record_file,
         symfs_dir=args.symfs,
         kallsyms_file=args.kallsyms,
-        report_lib_options=args.report_lib_options)
+        report_lib_options=args.report_lib_options,
+        max_remove_gap_length=args.max_remove_gap_length)
 
     json.dump(profile, sys.stdout, sort_keys=True)
 
diff --git a/simpleperf/scripts/inferno/Android.bp b/simpleperf/scripts/inferno/Android.bp
index 14d1877..e60a699 100644
--- a/simpleperf/scripts/inferno/Android.bp
+++ b/simpleperf/scripts/inferno/Android.bp
@@ -32,9 +32,4 @@
         "inferno.b64",
         "script.js",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-    },
 }
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index 57c988b..a22e424 100755
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -35,8 +35,9 @@
                               flatten_arg_list, log_exit, ReadElf, ToolFinder)
 try:
     import profile_pb2
-except ImportError:
-    log_exit('google.protobuf module is missing. Please install it first.')
+except ImportError as e:
+    log_exit(f'{e}\nprotobuf package is missing or too old. Please install it like ' +
+             '`pip install protobuf==4.21`.')
 
 
 # Some units of common event names
diff --git a/simpleperf/scripts/profile_pb2.py b/simpleperf/scripts/profile_pb2.py
index 707b23a..79db092 100644
--- a/simpleperf/scripts/profile_pb2.py
+++ b/simpleperf/scripts/profile_pb2.py
@@ -1,13 +1,11 @@
+# -*- coding: utf-8 -*-
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: profile.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
 from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
+from google.protobuf import descriptor_pool as _descriptor_pool
 from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
 # @@protoc_insertion_point(imports)
 
 _sym_db = _symbol_database.Default()
@@ -15,583 +13,28 @@
 
 
 
-DESCRIPTOR = _descriptor.FileDescriptor(
-  name='profile.proto',
-  package='perftools.profiles',
-  syntax='proto3',
-  serialized_pb=_b('\n\rprofile.proto\x12\x12perftools.profiles\"\xd5\x03\n\x07Profile\x12\x32\n\x0bsample_type\x18\x01 \x03(\x0b\x32\x1d.perftools.profiles.ValueType\x12*\n\x06sample\x18\x02 \x03(\x0b\x32\x1a.perftools.profiles.Sample\x12,\n\x07mapping\x18\x03 \x03(\x0b\x32\x1b.perftools.profiles.Mapping\x12.\n\x08location\x18\x04 \x03(\x0b\x32\x1c.perftools.profiles.Location\x12.\n\x08\x66unction\x18\x05 \x03(\x0b\x32\x1c.perftools.profiles.Function\x12\x14\n\x0cstring_table\x18\x06 \x03(\t\x12\x13\n\x0b\x64rop_frames\x18\x07 \x01(\x03\x12\x13\n\x0bkeep_frames\x18\x08 \x01(\x03\x12\x12\n\ntime_nanos\x18\t \x01(\x03\x12\x16\n\x0e\x64uration_nanos\x18\n \x01(\x03\x12\x32\n\x0bperiod_type\x18\x0b \x01(\x0b\x32\x1d.perftools.profiles.ValueType\x12\x0e\n\x06period\x18\x0c \x01(\x03\x12\x0f\n\x07\x63omment\x18\r \x03(\x03\x12\x1b\n\x13\x64\x65\x66\x61ult_sample_type\x18\x0e \x01(\x03\"\'\n\tValueType\x12\x0c\n\x04type\x18\x01 \x01(\x03\x12\x0c\n\x04unit\x18\x02 \x01(\x03\"V\n\x06Sample\x12\x13\n\x0blocation_id\x18\x01 \x03(\x04\x12\r\n\x05value\x18\x02 \x03(\x03\x12(\n\x05label\x18\x03 \x03(\x0b\x32\x19.perftools.profiles.Label\".\n\x05Label\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\x0b\n\x03str\x18\x02 \x01(\x03\x12\x0b\n\x03num\x18\x03 \x01(\x03\"\xdd\x01\n\x07Mapping\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x0cmemory_start\x18\x02 \x01(\x04\x12\x14\n\x0cmemory_limit\x18\x03 \x01(\x04\x12\x13\n\x0b\x66ile_offset\x18\x04 \x01(\x04\x12\x10\n\x08\x66ilename\x18\x05 \x01(\x03\x12\x10\n\x08\x62uild_id\x18\x06 \x01(\x03\x12\x15\n\rhas_functions\x18\x07 \x01(\x08\x12\x15\n\rhas_filenames\x18\x08 \x01(\x08\x12\x18\n\x10has_line_numbers\x18\t \x01(\x08\x12\x19\n\x11has_inline_frames\x18\n \x01(\x08\"c\n\x08Location\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x12\n\nmapping_id\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\x04\x12&\n\x04line\x18\x04 \x03(\x0b\x32\x18.perftools.profiles.Line\")\n\x04Line\x12\x13\n\x0b\x66unction_id\x18\x01 \x01(\x04\x12\x0c\n\x04line\x18\x02 \x01(\x03\"_\n\x08\x46unction\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\x03\x12\x13\n\x0bsystem_name\x18\x03 \x01(\x03\x12\x10\n\x08\x66ilename\x18\x04 \x01(\x03\x12\x12\n\nstart_line\x18\x05 \x01(\x03\x42-\n\x1d\x63om.google.perftools.profilesB\x0cProfileProtob\x06proto3')
-)
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rprofile.proto\x12\x12perftools.profiles\"\xd5\x03\n\x07Profile\x12\x32\n\x0bsample_type\x18\x01 \x03(\x0b\x32\x1d.perftools.profiles.ValueType\x12*\n\x06sample\x18\x02 \x03(\x0b\x32\x1a.perftools.profiles.Sample\x12,\n\x07mapping\x18\x03 \x03(\x0b\x32\x1b.perftools.profiles.Mapping\x12.\n\x08location\x18\x04 \x03(\x0b\x32\x1c.perftools.profiles.Location\x12.\n\x08\x66unction\x18\x05 \x03(\x0b\x32\x1c.perftools.profiles.Function\x12\x14\n\x0cstring_table\x18\x06 \x03(\t\x12\x13\n\x0b\x64rop_frames\x18\x07 \x01(\x03\x12\x13\n\x0bkeep_frames\x18\x08 \x01(\x03\x12\x12\n\ntime_nanos\x18\t \x01(\x03\x12\x16\n\x0e\x64uration_nanos\x18\n \x01(\x03\x12\x32\n\x0bperiod_type\x18\x0b \x01(\x0b\x32\x1d.perftools.profiles.ValueType\x12\x0e\n\x06period\x18\x0c \x01(\x03\x12\x0f\n\x07\x63omment\x18\r \x03(\x03\x12\x1b\n\x13\x64\x65\x66\x61ult_sample_type\x18\x0e \x01(\x03\"\'\n\tValueType\x12\x0c\n\x04type\x18\x01 \x01(\x03\x12\x0c\n\x04unit\x18\x02 \x01(\x03\"V\n\x06Sample\x12\x13\n\x0blocation_id\x18\x01 \x03(\x04\x12\r\n\x05value\x18\x02 \x03(\x03\x12(\n\x05label\x18\x03 \x03(\x0b\x32\x19.perftools.profiles.Label\"@\n\x05Label\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\x0b\n\x03str\x18\x02 \x01(\x03\x12\x0b\n\x03num\x18\x03 \x01(\x03\x12\x10\n\x08num_unit\x18\x04 \x01(\x03\"\xdd\x01\n\x07Mapping\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x0cmemory_start\x18\x02 \x01(\x04\x12\x14\n\x0cmemory_limit\x18\x03 \x01(\x04\x12\x13\n\x0b\x66ile_offset\x18\x04 \x01(\x04\x12\x10\n\x08\x66ilename\x18\x05 \x01(\x03\x12\x10\n\x08\x62uild_id\x18\x06 \x01(\x03\x12\x15\n\rhas_functions\x18\x07 \x01(\x08\x12\x15\n\rhas_filenames\x18\x08 \x01(\x08\x12\x18\n\x10has_line_numbers\x18\t \x01(\x08\x12\x19\n\x11has_inline_frames\x18\n \x01(\x08\"v\n\x08Location\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x12\n\nmapping_id\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\x04\x12&\n\x04line\x18\x04 \x03(\x0b\x32\x18.perftools.profiles.Line\x12\x11\n\tis_folded\x18\x05 \x01(\x08\")\n\x04Line\x12\x13\n\x0b\x66unction_id\x18\x01 \x01(\x04\x12\x0c\n\x04line\x18\x02 \x01(\x03\"_\n\x08\x46unction\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\x03\x12\x13\n\x0bsystem_name\x18\x03 \x01(\x03\x12\x10\n\x08\x66ilename\x18\x04 \x01(\x03\x12\x12\n\nstart_line\x18\x05 \x01(\x03\x42-\n\x1d\x63om.google.perftools.profilesB\x0cProfileProtob\x06proto3')
 
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'profile_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
 
-
-
-_PROFILE = _descriptor.Descriptor(
-  name='Profile',
-  full_name='perftools.profiles.Profile',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='sample_type', full_name='perftools.profiles.Profile.sample_type', index=0,
-      number=1, type=11, cpp_type=10, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='sample', full_name='perftools.profiles.Profile.sample', index=1,
-      number=2, type=11, cpp_type=10, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='mapping', full_name='perftools.profiles.Profile.mapping', index=2,
-      number=3, type=11, cpp_type=10, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='location', full_name='perftools.profiles.Profile.location', index=3,
-      number=4, type=11, cpp_type=10, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='function', full_name='perftools.profiles.Profile.function', index=4,
-      number=5, type=11, cpp_type=10, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='string_table', full_name='perftools.profiles.Profile.string_table', index=5,
-      number=6, type=9, cpp_type=9, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='drop_frames', full_name='perftools.profiles.Profile.drop_frames', index=6,
-      number=7, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='keep_frames', full_name='perftools.profiles.Profile.keep_frames', index=7,
-      number=8, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='time_nanos', full_name='perftools.profiles.Profile.time_nanos', index=8,
-      number=9, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='duration_nanos', full_name='perftools.profiles.Profile.duration_nanos', index=9,
-      number=10, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='period_type', full_name='perftools.profiles.Profile.period_type', index=10,
-      number=11, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='period', full_name='perftools.profiles.Profile.period', index=11,
-      number=12, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='comment', full_name='perftools.profiles.Profile.comment', index=12,
-      number=13, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='default_sample_type', full_name='perftools.profiles.Profile.default_sample_type', index=13,
-      number=14, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=38,
-  serialized_end=507,
-)
-
-
-_VALUETYPE = _descriptor.Descriptor(
-  name='ValueType',
-  full_name='perftools.profiles.ValueType',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='type', full_name='perftools.profiles.ValueType.type', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='unit', full_name='perftools.profiles.ValueType.unit', index=1,
-      number=2, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=509,
-  serialized_end=548,
-)
-
-
-_SAMPLE = _descriptor.Descriptor(
-  name='Sample',
-  full_name='perftools.profiles.Sample',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='location_id', full_name='perftools.profiles.Sample.location_id', index=0,
-      number=1, type=4, cpp_type=4, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='value', full_name='perftools.profiles.Sample.value', index=1,
-      number=2, type=3, cpp_type=2, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='label', full_name='perftools.profiles.Sample.label', index=2,
-      number=3, type=11, cpp_type=10, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=550,
-  serialized_end=636,
-)
-
-
-_LABEL = _descriptor.Descriptor(
-  name='Label',
-  full_name='perftools.profiles.Label',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='key', full_name='perftools.profiles.Label.key', index=0,
-      number=1, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='str', full_name='perftools.profiles.Label.str', index=1,
-      number=2, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='num', full_name='perftools.profiles.Label.num', index=2,
-      number=3, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=638,
-  serialized_end=684,
-)
-
-
-_MAPPING = _descriptor.Descriptor(
-  name='Mapping',
-  full_name='perftools.profiles.Mapping',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='id', full_name='perftools.profiles.Mapping.id', index=0,
-      number=1, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='memory_start', full_name='perftools.profiles.Mapping.memory_start', index=1,
-      number=2, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='memory_limit', full_name='perftools.profiles.Mapping.memory_limit', index=2,
-      number=3, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='file_offset', full_name='perftools.profiles.Mapping.file_offset', index=3,
-      number=4, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='filename', full_name='perftools.profiles.Mapping.filename', index=4,
-      number=5, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='build_id', full_name='perftools.profiles.Mapping.build_id', index=5,
-      number=6, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='has_functions', full_name='perftools.profiles.Mapping.has_functions', index=6,
-      number=7, type=8, cpp_type=7, label=1,
-      has_default_value=False, default_value=False,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='has_filenames', full_name='perftools.profiles.Mapping.has_filenames', index=7,
-      number=8, type=8, cpp_type=7, label=1,
-      has_default_value=False, default_value=False,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='has_line_numbers', full_name='perftools.profiles.Mapping.has_line_numbers', index=8,
-      number=9, type=8, cpp_type=7, label=1,
-      has_default_value=False, default_value=False,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='has_inline_frames', full_name='perftools.profiles.Mapping.has_inline_frames', index=9,
-      number=10, type=8, cpp_type=7, label=1,
-      has_default_value=False, default_value=False,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=687,
-  serialized_end=908,
-)
-
-
-_LOCATION = _descriptor.Descriptor(
-  name='Location',
-  full_name='perftools.profiles.Location',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='id', full_name='perftools.profiles.Location.id', index=0,
-      number=1, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='mapping_id', full_name='perftools.profiles.Location.mapping_id', index=1,
-      number=2, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='address', full_name='perftools.profiles.Location.address', index=2,
-      number=3, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='line', full_name='perftools.profiles.Location.line', index=3,
-      number=4, type=11, cpp_type=10, label=3,
-      has_default_value=False, default_value=[],
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=910,
-  serialized_end=1009,
-)
-
-
-_LINE = _descriptor.Descriptor(
-  name='Line',
-  full_name='perftools.profiles.Line',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='function_id', full_name='perftools.profiles.Line.function_id', index=0,
-      number=1, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='line', full_name='perftools.profiles.Line.line', index=1,
-      number=2, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1011,
-  serialized_end=1052,
-)
-
-
-_FUNCTION = _descriptor.Descriptor(
-  name='Function',
-  full_name='perftools.profiles.Function',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='id', full_name='perftools.profiles.Function.id', index=0,
-      number=1, type=4, cpp_type=4, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='name', full_name='perftools.profiles.Function.name', index=1,
-      number=2, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='system_name', full_name='perftools.profiles.Function.system_name', index=2,
-      number=3, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='filename', full_name='perftools.profiles.Function.filename', index=3,
-      number=4, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-    _descriptor.FieldDescriptor(
-      name='start_line', full_name='perftools.profiles.Function.start_line', index=4,
-      number=5, type=3, cpp_type=2, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      options=None),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1054,
-  serialized_end=1149,
-)
-
-_PROFILE.fields_by_name['sample_type'].message_type = _VALUETYPE
-_PROFILE.fields_by_name['sample'].message_type = _SAMPLE
-_PROFILE.fields_by_name['mapping'].message_type = _MAPPING
-_PROFILE.fields_by_name['location'].message_type = _LOCATION
-_PROFILE.fields_by_name['function'].message_type = _FUNCTION
-_PROFILE.fields_by_name['period_type'].message_type = _VALUETYPE
-_SAMPLE.fields_by_name['label'].message_type = _LABEL
-_LOCATION.fields_by_name['line'].message_type = _LINE
-DESCRIPTOR.message_types_by_name['Profile'] = _PROFILE
-DESCRIPTOR.message_types_by_name['ValueType'] = _VALUETYPE
-DESCRIPTOR.message_types_by_name['Sample'] = _SAMPLE
-DESCRIPTOR.message_types_by_name['Label'] = _LABEL
-DESCRIPTOR.message_types_by_name['Mapping'] = _MAPPING
-DESCRIPTOR.message_types_by_name['Location'] = _LOCATION
-DESCRIPTOR.message_types_by_name['Line'] = _LINE
-DESCRIPTOR.message_types_by_name['Function'] = _FUNCTION
-
-Profile = _reflection.GeneratedProtocolMessageType('Profile', (_message.Message,), dict(
-  DESCRIPTOR = _PROFILE,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.Profile)
-  ))
-_sym_db.RegisterMessage(Profile)
-
-ValueType = _reflection.GeneratedProtocolMessageType('ValueType', (_message.Message,), dict(
-  DESCRIPTOR = _VALUETYPE,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.ValueType)
-  ))
-_sym_db.RegisterMessage(ValueType)
-
-Sample = _reflection.GeneratedProtocolMessageType('Sample', (_message.Message,), dict(
-  DESCRIPTOR = _SAMPLE,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.Sample)
-  ))
-_sym_db.RegisterMessage(Sample)
-
-Label = _reflection.GeneratedProtocolMessageType('Label', (_message.Message,), dict(
-  DESCRIPTOR = _LABEL,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.Label)
-  ))
-_sym_db.RegisterMessage(Label)
-
-Mapping = _reflection.GeneratedProtocolMessageType('Mapping', (_message.Message,), dict(
-  DESCRIPTOR = _MAPPING,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.Mapping)
-  ))
-_sym_db.RegisterMessage(Mapping)
-
-Location = _reflection.GeneratedProtocolMessageType('Location', (_message.Message,), dict(
-  DESCRIPTOR = _LOCATION,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.Location)
-  ))
-_sym_db.RegisterMessage(Location)
-
-Line = _reflection.GeneratedProtocolMessageType('Line', (_message.Message,), dict(
-  DESCRIPTOR = _LINE,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.Line)
-  ))
-_sym_db.RegisterMessage(Line)
-
-Function = _reflection.GeneratedProtocolMessageType('Function', (_message.Message,), dict(
-  DESCRIPTOR = _FUNCTION,
-  __module__ = 'profile_pb2'
-  # @@protoc_insertion_point(class_scope:perftools.profiles.Function)
-  ))
-_sym_db.RegisterMessage(Function)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\035com.google.perftools.profilesB\014ProfileProto'))
+  DESCRIPTOR._options = None
+  DESCRIPTOR._serialized_options = b'\n\035com.google.perftools.profilesB\014ProfileProto'
+  _PROFILE._serialized_start=38
+  _PROFILE._serialized_end=507
+  _VALUETYPE._serialized_start=509
+  _VALUETYPE._serialized_end=548
+  _SAMPLE._serialized_start=550
+  _SAMPLE._serialized_end=636
+  _LABEL._serialized_start=638
+  _LABEL._serialized_end=702
+  _MAPPING._serialized_start=705
+  _MAPPING._serialized_end=926
+  _LOCATION._serialized_start=928
+  _LOCATION._serialized_end=1046
+  _LINE._serialized_start=1048
+  _LINE._serialized_end=1089
+  _FUNCTION._serialized_start=1091
+  _FUNCTION._serialized_end=1186
 # @@protoc_insertion_point(module_scope)
diff --git a/simpleperf/scripts/report_sample.py b/simpleperf/scripts/report_sample.py
index dc5c4e2..9b6947f 100755
--- a/simpleperf/scripts/report_sample.py
+++ b/simpleperf/scripts/report_sample.py
@@ -18,6 +18,7 @@
 """report_sample.py: report samples in the same format as `perf script`.
 """
 
+import sys
 from simpleperf_report_lib import ReportLib
 from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
 from typing import List, Set, Optional
@@ -88,8 +89,13 @@
     parser.add_argument('--show_tracing_data', action='store_true', help='print tracing data.')
     parser.add_argument('--header', action='store_true',
                         help='Show metadata header, like perf script --header')
+    parser.add_argument('-o', '--output_file', default='', help="""
+        The path of the generated report.  Default is stdout.""")
     parser.add_report_lib_options()
     args = parser.parse_args()
+    # If the output file has been set, redirect stdout.
+    if args.output_file != '' and args.output_file != '-':
+        sys.stdout = open(file=args.output_file, mode='w')
     report_sample(
         record_file=args.record_file,
         symfs_dir=args.symfs,
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 48b3119..5f990ac 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -264,6 +264,8 @@
         self._SetTraceOffCpuModeFunc.restype = ct.c_bool
         self._SetSampleFilterFunc = self._lib.SetSampleFilter
         self._SetSampleFilterFunc.restype = ct.c_bool
+        self._AggregateThreadsFunc = self._lib.AggregateThreads
+        self._AggregateThreadsFunc.restype = ct.c_bool
         self._GetNextSampleFunc = self._lib.GetNextSample
         self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
         self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
@@ -309,6 +311,8 @@
             self.SetTraceOffCpuMode(options.trace_offcpu)
         if options.sample_filters:
             self.SetSampleFilter(options.sample_filters)
+        if options.aggregate_threads:
+            self.AggregateThreads(options.aggregate_threads)
 
     def SetLogSeverity(self, log_level: str = 'info'):
         """ Set log severity of native lib, can be verbose,debug,info,error,fatal."""
@@ -399,6 +403,18 @@
         res: bool = self._SetSampleFilterFunc(self.getInstance(), filter_array, len(filters))
         _check(res, f'Failed to call SetSampleFilter({filters})')
 
+    def AggregateThreads(self, thread_name_regex_list: List[str]):
+        """ Given a list of thread name regex, threads with names matching the same regex are merged
+            into one thread. As a result, samples from different threads (like a thread pool) can be
+            shown in one flamegraph.
+        """
+        regex_array = (ct.c_char_p * len(thread_name_regex_list))()
+        regex_array[:] = [_char_pt(f) for f in thread_name_regex_list]
+        res: bool = self._AggregateThreadsFunc(
+            self.getInstance(),
+            regex_array, len(thread_name_regex_list))
+        _check(res, f'Failed to call AggregateThreads({thread_name_regex_list})')
+
     def GetNextSample(self) -> Optional[SampleStruct]:
         """ Return the next sample. If no more samples, return None. """
         psample = self._GetNextSampleFunc(self.getInstance())
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py
index 2a7dfd3..f8d50dc 100644
--- a/simpleperf/scripts/simpleperf_utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -447,7 +447,7 @@
                 return path
         # Find binary by path in binary cache.
         if self.binary_cache_dir:
-            path = self.binary_cache_dir / dso_path_in_record_file[1:]
+            path = self.binary_cache_dir / dso_path_in_record_file[1:].replace('/', os.sep)
             if self._check_path(path, expected_build_id):
                 return path
         # Find binary by its absolute path.
@@ -1006,6 +1006,7 @@
     trace_offcpu: str
     proguard_mapping_files: List[str]
     sample_filters: List[str]
+    aggregate_threads: List[str]
 
 
 class BaseArgumentParser(argparse.ArgumentParser):
@@ -1036,6 +1037,11 @@
                     If not set, mixed-on-off-cpu mode is used.
                 """)
         self._add_sample_filter_options(sample_filter_group, sample_filter_with_pid_shortcut)
+        parser.add_argument(
+            '--aggregate-threads', nargs='+', metavar='thread_name_regex',
+            help="""Aggregate threads with names matching the same regex. As a result, samples from
+                    different threads (like a thread pool) can be shown in one flamegraph.
+                """)
 
     def _add_sample_filter_options(
             self, group: Optional[Any] = None, with_pid_shortcut: bool = True):
@@ -1118,7 +1124,7 @@
             sample_filters = self._build_sample_filter(namespace)
             report_lib_options = ReportLibOptions(
                 namespace.show_art_frames, namespace.trace_offcpu, namespace.proguard_mapping_file,
-                sample_filters)
+                sample_filters, namespace.aggregate_threads)
             setattr(namespace, 'report_lib_options', report_lib_options)
 
         if not Log.initialized:
diff --git a/simpleperf/scripts/test/app_test.py b/simpleperf/scripts/test/app_test.py
index c5db86b..a13200a 100644
--- a/simpleperf/scripts/test/app_test.py
+++ b/simpleperf/scripts/test/app_test.py
@@ -29,16 +29,15 @@
 
 class TestExampleBase(TestBase):
     @classmethod
-    def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
+    def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False,
+                apk_name: str = 'app-debug.apk'):
         cls.adb = AdbHelper(enable_switch_to_root=adb_root)
         cls.example_path = TestHelper.testdata_path(example_name)
         if not os.path.isdir(cls.example_path):
             log_fatal("can't find " + cls.example_path)
-        apk_files = list(Path(cls.example_path).glob('**/app-profiling.apk'))
+        apk_files = list(Path(cls.example_path).glob(f'**/{apk_name}'))
         if not apk_files:
-            apk_files = list(Path(cls.example_path).glob('**/app-debug.apk'))
-        if not apk_files:
-            log_fatal("can't find apk under " + cls.example_path)
+            log_fatal(f"can't find {apk_name} under " + cls.example_path)
         cls.apk_path = apk_files[0]
         cls.package_name = package_name
         cls.activity_name = activity_name
@@ -56,12 +55,16 @@
 
     @classmethod
     def tearDownClass(cls):
-        remove(cls.testcase_dir)
+        if hasattr(cls, 'testcase_dir'):
+            remove(cls.testcase_dir)
         if hasattr(cls, 'package_name'):
             cls.adb.check_run(["uninstall", cls.package_name])
 
     def setUp(self):
         super(TestExampleBase, self).setUp()
+        if TestHelper.android_version == 8 and (
+                'ExampleJava' in self.id() or 'ExampleKotlin' in self.id()):
+            self.skipTest('Profiling java code needs wrap.sh on Android O (8.x)')
         if 'TraceOffCpu' in self.id() and not TestHelper.is_trace_offcpu_supported():
             self.skipTest('trace-offcpu is not supported on device')
         # Use testcase_dir to share a common perf.data for reporting. So we don't need to
diff --git a/simpleperf/scripts/test/binary_cache_builder_test.py b/simpleperf/scripts/test/binary_cache_builder_test.py
index 21b2133..ece7d6d 100644
--- a/simpleperf/scripts/test/binary_cache_builder_test.py
+++ b/simpleperf/scripts/test/binary_cache_builder_test.py
@@ -19,6 +19,7 @@
 from pathlib import Path
 import shutil
 import tempfile
+import zipfile
 
 from binary_cache_builder import BinaryCacheBuilder
 from simpleperf_utils import ReadElf, remove, ToolFinder
@@ -28,7 +29,7 @@
 class TestBinaryCacheBuilder(TestBase):
     def test_copy_binaries_from_symfs_dirs(self):
         readelf = ReadElf(TestHelper.ndk_path)
-        strip = ToolFinder.find_tool_path('llvm-strip', arch='arm')
+        strip = ToolFinder.find_tool_path('llvm-strip', ndk_path=TestHelper.ndk_path, arch='arm')
         self.assertIsNotNone(strip)
         symfs_dir = os.path.join(self.test_dir, 'symfs_dir')
         remove(symfs_dir)
@@ -36,12 +37,12 @@
         filename = 'simpleperf_runtest_two_functions_arm'
         origin_file = TestHelper.testdata_path(filename)
         source_file = os.path.join(symfs_dir, filename)
-        target_file = os.path.join('binary_cache', filename)
-        expected_build_id = readelf.get_build_id(origin_file)
+        build_id = readelf.get_build_id(origin_file)
         binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
-        binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id
+        binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = build_id
 
         # Copy binary if target file doesn't exist.
+        target_file = binary_cache_builder.find_path_in_cache(filename)
         remove(target_file)
         self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file])
         binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
@@ -62,34 +63,69 @@
         binary_cache_builder.binaries['elf'] = ''
         symfs_dir = TestHelper.testdata_path('data/symfs_without_build_id')
         source_file = os.path.join(symfs_dir, 'elf')
-        target_file = os.path.join('binary_cache', 'elf')
+        target_file = binary_cache_builder.find_path_in_cache('elf')
         binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
         self.assertTrue(filecmp.cmp(target_file, source_file))
         binary_cache_builder.pull_binaries_from_device()
         self.assertTrue(filecmp.cmp(target_file, source_file))
 
+    def test_copy_binary_with_different_name(self):
+        # Build symfs_dir.
+        symfs_dir = self.test_dir / 'symfs_dir'
+        remove(symfs_dir)
+        symfs_dir.mkdir()
+        filename = 'simpleperf_runtest_two_functions_arm'
+        origin_file = TestHelper.testdata_path(filename)
+        modified_name = 'two_functions_arm'
+        source_file = os.path.join(symfs_dir, modified_name)
+        shutil.copy(origin_file, source_file)
+
+        # Copy binary with the same build id but a different name.
+        builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+        builder.binaries[filename] = builder.readelf.get_build_id(origin_file)
+        builder.copy_binaries_from_symfs_dirs([symfs_dir])
+
+        target_file = builder.find_path_in_cache(filename)
+        self.assertTrue(filecmp.cmp(target_file, source_file))
+
+    def test_copy_binary_for_native_lib_embedded_in_apk(self):
+        apk_path = TestHelper.testdata_path('data/app/com.example.hellojni-1/base.apk')
+        symfs_dir = self.test_dir / 'symfs_dir'
+        with zipfile.ZipFile(apk_path, 'r') as zip_ref:
+            zip_ref.extractall(symfs_dir)
+        builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+        builder.collect_used_binaries(
+            TestHelper.testdata_path('has_embedded_native_libs_apk_perf.data'))
+        builder.copy_binaries_from_symfs_dirs([symfs_dir])
+
+        device_path = [p for p in builder.binaries if 'libhello-jni.so' in p][0]
+        target_file = builder.find_path_in_cache(device_path)
+        self.assertTrue(target_file.is_file())
+        # Check that we are not using path format of embedded lib in apk. Because
+        # simpleperf can't use it from binary_cache.
+        self.assertNotIn('!/', str(target_file))
+
     def test_prefer_binary_with_debug_info(self):
         binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
         binary_cache_builder.collect_used_binaries(
             TestHelper.testdata_path('runtest_two_functions_arm64_perf.data'))
+        filename = 'simpleperf_runtest_two_functions_arm64'
 
         # Create a symfs_dir, which contains elf file with and without debug info.
         with tempfile.TemporaryDirectory() as tmp_dir:
             shutil.copy(
                 TestHelper.testdata_path(
                     'simpleperf_runtest_two_functions_arm64_without_debug_info'),
-                Path(tmp_dir) / 'simpleperf_runtest_two_functions_arm64')
+                Path(tmp_dir) / filename)
 
             debug_dir = Path(tmp_dir) / 'debug'
             debug_dir.mkdir()
-            shutil.copy(TestHelper.testdata_path(
-                'simpleperf_runtest_two_functions_arm64'), debug_dir)
+            debug_file = TestHelper.testdata_path(filename)
+            shutil.copy(debug_file, debug_dir)
             # Check if the elf file with debug info is chosen.
             binary_cache_builder.copy_binaries_from_symfs_dirs([tmp_dir])
-            elf_path = (Path(binary_cache_builder.binary_cache_dir) / 'data' /
-                        'local' / 'tmp' / 'simpleperf_runtest_two_functions_arm64')
-            self.assertTrue(elf_path.is_file())
-            self.assertIn('.debug_info', binary_cache_builder.readelf.get_sections(elf_path))
+            target_file = binary_cache_builder.find_path_in_cache('/data/local/tmp/' + filename)
+            self.assertTrue(filecmp.cmp(target_file, debug_file))
 
     def test_create_build_id_list(self):
         symfs_dir = TestHelper.testdata_dir
@@ -97,9 +133,10 @@
         binary_cache_builder.collect_used_binaries(
             TestHelper.testdata_path('runtest_two_functions_arm64_perf.data'))
         binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
-        elf_path = (Path(binary_cache_builder.binary_cache_dir) / 'data' /
-                    'local' / 'tmp' / 'simpleperf_runtest_two_functions_arm64')
-        self.assertTrue(elf_path.is_file())
+
+        target_file = binary_cache_builder.find_path_in_cache(
+            '/data/local/tmp/simpleperf_runtest_two_functions_arm64')
+        self.assertTrue(target_file.is_file())
 
         binary_cache_builder.create_build_id_list()
         build_id_list_path = Path(binary_cache_builder.binary_cache_dir) / 'build_id_list'
diff --git a/simpleperf/scripts/test/cpp_app_test.py b/simpleperf/scripts/test/cpp_app_test.py
index 86667d3..0939cce 100644
--- a/simpleperf/scripts/test/cpp_app_test.py
+++ b/simpleperf/scripts/test/cpp_app_test.py
@@ -15,6 +15,7 @@
 # limitations under the License.
 
 import os
+import unittest
 
 from simpleperf_utils import remove
 from . app_test import TestExampleBase
@@ -75,6 +76,21 @@
                       '--add_disassembly', '--binary_filter', "libnative-lib.so"])
 
 
+class TestExampleCppProfileableApk(TestExampleCpp):
+    """ Test profiling a profileable released apk."""
+    @classmethod
+    def setUpClass(cls):
+        if TestHelper.android_version >= 10:
+            cls.prepare("SimpleperfExampleCpp",
+                        "simpleperf.example.cpp",
+                        ".MainActivity", apk_name='app-release.apk')
+
+    def setUp(self):
+        if TestHelper().android_version < 10:
+            raise unittest.SkipTest("Profileable apk isn't supported on Android < Q.")
+        super().setUp()
+
+
 class TestExampleCppRoot(TestExampleBase):
     @classmethod
     def setUpClass(cls):
diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py
index b95a1fe..012dc62 100755
--- a/simpleperf/scripts/test/do_test.py
+++ b/simpleperf/scripts/test/do_test.py
@@ -205,14 +205,13 @@
 @dataclass
 class TestResult:
     try_time: int
-    ok: bool
+    status: str
     duration: str
 
     def __str__(self) -> str:
-        if self.ok:
-            s = 'OK'
-        else:
-            s = f'FAILED (at try_time {self.try_time})'
+        s = self.status
+        if s == 'FAILED':
+            s += f' (at try_time {self.try_time})'
         s += f' {self.duration}'
         return s
 
@@ -289,7 +288,6 @@
 
     def _process_msg(self, msg: str):
         test_name, test_success, test_duration = msg.split()
-        test_success = test_success == 'OK'
         self.test_results[test_name] = TestResult(self.try_time, test_success, test_duration)
 
     def join(self):
@@ -304,7 +302,7 @@
             for test in self.tests:
                 if test not in self.test_results:
                     test_duration = '%.3fs' % (time.time() - self.last_update_time)
-                    self.test_results[test] = TestResult(self.try_time, False, test_duration)
+                    self.test_results[test] = TestResult(self.try_time, 'FAILED', test_duration)
             return False
 
         self.try_time += 1
@@ -366,7 +364,7 @@
     def failed_test_count(self) -> int:
         count = 0
         for result in self.results.values():
-            if result is None or not result.ok:
+            if result is None or result.status == 'FAILED':
                 count += 1
         return count
 
@@ -392,7 +390,7 @@
                 result = self.results[key]
                 message = f'{test_name}    {test_env}    {result}'
                 print(message, file=fh)
-                if not result or not result.ok:
+                if not result or result.status == 'FAILED':
                     print(message, file=failed_fh)
 
 
@@ -518,15 +516,6 @@
     return False
 
 
-def sign_executables_on_darwin():
-    """ Sign executables on M1 Mac, otherwise they can't run. """
-    if not is_darwin():
-        return
-    bin_dir = Path(__file__).resolve().parents[1] / 'bin' / 'darwin' / 'x86_64'
-    for path in bin_dir.iterdir():
-        subprocess.run(f'codesign --force -s - {path}', shell=True, check=True)
-
-
 def main() -> bool:
     args = get_args()
     tests = get_host_tests() if args.only_host_test else get_all_tests()
@@ -542,5 +531,4 @@
     # Switch to the test dir.
     os.chdir(test_dir)
     build_testdata(Path('testdata'))
-    sign_executables_on_darwin()
     return run_tests_in_child_process(tests, args)
diff --git a/simpleperf/scripts/test/gecko_profile_generator_test.py b/simpleperf/scripts/test/gecko_profile_generator_test.py
index 5a0d45e..b96dfc8 100644
--- a/simpleperf/scripts/test/gecko_profile_generator_test.py
+++ b/simpleperf/scripts/test/gecko_profile_generator_test.py
@@ -18,7 +18,7 @@
 import os
 import re
 import tempfile
-from typing import List, Optional, Set
+from typing import Dict, List, Optional, Set
 
 from . test_utils import TestBase, TestHelper
 
@@ -31,19 +31,42 @@
             args.extend(options)
         return self.run_cmd(args, return_output=True)
 
+    def generate_profile(self, testdata_file: str, options: Optional[List[str]] = None) -> Dict:
+        output = self.run_generator(testdata_file, options)
+        return json.loads(output)
+
     def test_golden(self):
-        output = self.run_generator('perf_with_interpreter_frames.data')
+        output = self.run_generator('perf_with_interpreter_frames.data', ['--remove-gaps', '0'])
         got = json.loads(output)
         golden_path = TestHelper.testdata_path('perf_with_interpreter_frames.gecko.json')
         with open(golden_path) as f:
             want = json.load(f)
+        # Golden data is formatted with `jq` tool (https://stedolan.github.io/jq/).
+        # Regenerate golden data by running:
+        # $ apt install jq
+        # $ ./gecko_profile_generator.py --remove-gaps 0 -i ../testdata/perf_with_interpreter_frames.data | jq > test/script_testdata/perf_with_interpreter_frames.gecko.json
+        self.assertEqual(
+            json.dumps(got, sort_keys=True, indent=2),
+            json.dumps(want, sort_keys=True, indent=2))
+
+    def test_golden_offcpu(self):
+        output = self.run_generator('perf_with_tracepoint_event.data', ['--remove-gaps', '0'])
+        got = json.loads(output)
+        golden_path = TestHelper.testdata_path('perf_with_tracepoint_event.gecko.json')
+        with open(golden_path) as f:
+            want = json.load(f)
+        # Golden data is formatted with `jq` tool (https://stedolan.github.io/jq/).
+        # Regenerate golden data by running:
+        # $ apt install jq
+        # $ ./gecko_profile_generator.py --remove-gaps 0 -i ../testdata/perf_with_tracepoint_event.data | jq > test/script_testdata/perf_with_tracepoint_event.gecko.json
         self.assertEqual(
             json.dumps(got, sort_keys=True, indent=2),
             json.dumps(want, sort_keys=True, indent=2))
 
     def test_sample_filters(self):
         def get_threads_for_filter(filter: str) -> Set[int]:
-            report = self.run_generator('perf_display_bitmaps.data', filter.split())
+            report = self.run_generator('perf_display_bitmaps.data',
+                                        filter.split() + ['--remove-gaps', '0'])
             pattern = re.compile(r'"tid":\s+(\d+),')
             threads = set()
             for m in re.finditer(pattern, report):
@@ -79,3 +102,17 @@
         self.assertNotIn(art_frame_str, report)
         report = self.run_generator('perf_with_interpreter_frames.data', ['--show-art-frames'])
         self.assertIn(art_frame_str, report)
+
+    def test_remove_gaps(self):
+        testdata = 'perf_with_interpreter_frames.data'
+
+        def get_sample_count(options: Optional[List[str]] = None) -> int:
+            data = self.generate_profile(testdata, options)
+            sample_count = 0
+            for thread in data['threads']:
+                sample_count += len(thread['samples']['data'])
+            return sample_count
+        # By default, the gap sample is removed.
+        self.assertEqual(4031, get_sample_count())
+        # Use `--remove-gaps 0` to disable removing gaps.
+        self.assertEqual(4032, get_sample_count(['--remove-gaps', '0']))
diff --git a/simpleperf/scripts/test/java_app_test.py b/simpleperf/scripts/test/java_app_test.py
index 03e7baf..5f74c64 100644
--- a/simpleperf/scripts/test/java_app_test.py
+++ b/simpleperf/scripts/test/java_app_test.py
@@ -19,17 +19,18 @@
 import subprocess
 import sys
 import time
+import unittest
 
 from simpleperf_utils import is_windows, remove
 from . app_test import TestExampleBase
 from . test_utils import TestHelper, INFERNO_SCRIPT
 
 
-class TestExamplePureJava(TestExampleBase):
+class TestExampleJava(TestExampleBase):
     @classmethod
     def setUpClass(cls):
-        cls.prepare("SimpleperfExamplePureJava",
-                    "com.example.simpleperf.simpleperfexamplepurejava",
+        cls.prepare("SimpleperfExampleJava",
+                    "simpleperf.example.java",
                     ".MainActivity")
 
     def test_app_profiler(self):
@@ -39,7 +40,7 @@
         self.run_app_profiler(start_activity=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",
+            "simpleperf.example.java.MainActivity$1.run",
             "__start_thread"])
 
     def test_app_profiler_multiprocesses(self):
@@ -59,6 +60,8 @@
         time.sleep(1)
         args = [sys.executable, TestHelper.script_path("app_profiler.py"),
                 "--app", self.package_name, "-r", "--duration 10000", "--disable_adb_root"]
+        if TestHelper.ndk_path:
+            args += ['--ndk_path', TestHelper.ndk_path]
         subproc = subprocess.Popen(args)
         time.sleep(3)
 
@@ -70,9 +73,11 @@
     def test_app_profiler_stop_after_app_exit(self):
         self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
         time.sleep(1)
-        subproc = subprocess.Popen(
-            [sys.executable, TestHelper.script_path('app_profiler.py'),
-             '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root'])
+        args = [sys.executable, TestHelper.script_path('app_profiler.py'),
+                '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root']
+        if TestHelper.ndk_path:
+            args += ['--ndk_path', TestHelper.ndk_path]
+        subproc = subprocess.Popen(args)
         time.sleep(3)
         self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
         subproc.wait()
@@ -88,18 +93,18 @@
         self.common_test_report()
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
         self.check_strings_in_file("report.txt", [
-            "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+            "simpleperf.example.java.MainActivity$1.run",
             "__start_thread"])
 
     def test_profile_with_process_id(self):
         self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
         time.sleep(1)
-        pid = self.adb.check_run_and_return_output([
-            'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip()
+        pid = self.adb.check_run_and_return_output(
+            ['shell', 'pidof', 'simpleperf.example.java']).strip()
         self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid)
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
         self.check_strings_in_file("report.txt", [
-            "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+            "simpleperf.example.java.MainActivity$1.run",
             "__start_thread"])
 
     def test_annotate(self):
@@ -117,29 +122,28 @@
 
     def test_report_sample(self):
         self.common_test_report_sample(
-            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+            ["simpleperf.example.java.MainActivity$1.run",
              "__start_thread"])
 
     def test_pprof_proto_generator(self):
         check_strings_with_lines = []
         if self.use_compiled_java_code:
             check_strings_with_lines = [
-                "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java",
+                "simpleperf/example/java/MainActivity.java",
                 "run"]
         self.common_test_pprof_proto_generator(
             check_strings_with_lines=check_strings_with_lines,
-            check_strings_without_lines=[
-                "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"])
+            check_strings_without_lines=["simpleperf.example.java.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)])
+            [('simpleperf.example.java.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)],
+            [('simpleperf.example.java.MainActivity$1.run', 80)],
             "report2.html")
 
     def test_inferno_in_another_dir(self):
@@ -169,11 +173,26 @@
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
 
 
-class TestExamplePureJavaRoot(TestExampleBase):
+class TestExampleJavaProfileableApk(TestExampleJava):
+    """ Test profiling a profileable released apk."""
     @classmethod
     def setUpClass(cls):
-        cls.prepare("SimpleperfExamplePureJava",
-                    "com.example.simpleperf.simpleperfexamplepurejava",
+        if TestHelper.android_version >= 10:
+            cls.prepare("SimpleperfExampleJava",
+                        "simpleperf.example.java",
+                        ".MainActivity", apk_name='app-release.apk')
+
+    def setUp(self):
+        if TestHelper().android_version < 10:
+            raise unittest.SkipTest("Profileable apk isn't supported on Android < Q.")
+        super().setUp()
+
+
+class TestExampleJavaRoot(TestExampleBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleJava",
+                    "simpleperf.example.java",
                     ".MainActivity",
                     adb_root=True)
 
@@ -181,20 +200,20 @@
         self.common_test_app_profiler()
 
 
-class TestExamplePureJavaTraceOffCpu(TestExampleBase):
+class TestExampleJavaTraceOffCpu(TestExampleBase):
     @classmethod
     def setUpClass(cls):
-        cls.prepare("SimpleperfExamplePureJava",
-                    "com.example.simpleperf.simpleperfexamplepurejava",
+        cls.prepare("SimpleperfExampleJava",
+                    "simpleperf.example.java",
                     ".SleepActivity")
 
     def test_smoke(self):
         self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-clock:u --trace-offcpu")
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
         self.check_strings_in_file("report.txt", [
-            "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run",
-            "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction",
-            "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction"
+            "simpleperf.example.java.SleepActivity$1.run",
+            "simpleperf.example.java.SleepActivity$1.RunFunction",
+            "simpleperf.example.java.SleepActivity$1.SleepFunction"
         ])
         remove("annotated_files")
         self.run_cmd(["annotate.py", "-s", self.example_path, '--summary-width', '1000'])
@@ -208,11 +227,9 @@
                 ("RunFunction", 20, 20),
                 ("SleepFunction", 20, 0),
                 ("line 24", 1, 0),
-                ("line 32", 20, 0)])
+                ("line 31", 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',
-              20)])
+            [('simpleperf.example.java.SleepActivity$1.run', 80),
+             ('simpleperf.example.java.SleepActivity$1.RunFunction', 20),
+             ('simpleperf.example.java.SleepActivity$1.SleepFunction', 20)])
diff --git a/simpleperf/scripts/test/kotlin_app_test.py b/simpleperf/scripts/test/kotlin_app_test.py
index 7b18e05..74f5022 100644
--- a/simpleperf/scripts/test/kotlin_app_test.py
+++ b/simpleperf/scripts/test/kotlin_app_test.py
@@ -15,17 +15,18 @@
 # limitations under the License.
 
 import os
+import unittest
 
 from simpleperf_utils import remove
 from . app_test import TestExampleBase
-from . test_utils import INFERNO_SCRIPT
+from . test_utils import INFERNO_SCRIPT, TestHelper
 
 
-class TestExampleOfKotlin(TestExampleBase):
+class TestExampleKotlin(TestExampleBase):
     @classmethod
     def setUpClass(cls):
-        cls.prepare("SimpleperfExampleOfKotlin",
-                    "com.example.simpleperf.simpleperfexampleofkotlin",
+        cls.prepare("SimpleperfExampleKotlin",
+                    "simpleperf.example.kotlin",
                     ".MainActivity")
 
     def test_app_profiler(self):
@@ -35,14 +36,14 @@
         self.run_app_profiler(start_activity=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." +
+            "simpleperf.example.kotlin.MainActivity$createBusyThread$1." +
             "run", "__start_thread"])
 
     def test_report(self):
         self.common_test_report()
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
         self.check_strings_in_file("report.txt", [
-            "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+            "simpleperf.example.kotlin.MainActivity$createBusyThread$1." +
             "run", "__start_thread"])
 
     def test_annotate(self):
@@ -60,36 +61,51 @@
 
     def test_report_sample(self):
         self.common_test_report_sample([
-            "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+            "simpleperf.example.kotlin.MainActivity$createBusyThread$1." +
             "run", "__start_thread"])
 
     def test_pprof_proto_generator(self):
         check_strings_with_lines = []
         if self.use_compiled_java_code:
             check_strings_with_lines = [
-                "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt",
+                "simpleperf/example/kotlin/MainActivity.kt",
                 "run"]
         self.common_test_pprof_proto_generator(
             check_strings_with_lines=check_strings_with_lines,
-            check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." +
-                                         "MainActivity$createBusyThread$1.run"])
+            check_strings_without_lines=[
+                'simpleperf.example.kotlin.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)])
+        self.check_inferno_report_html([
+            ('simpleperf.example.kotlin.MainActivity$createBusyThread$1.run', 80)])
 
     def test_report_html(self):
         self.common_test_report_html()
 
 
-class TestExampleOfKotlinRoot(TestExampleBase):
+class TestExampleKotlinProfileableApk(TestExampleKotlin):
+    """ Test profiling a profileable released apk."""
     @classmethod
     def setUpClass(cls):
-        cls.prepare("SimpleperfExampleOfKotlin",
-                    "com.example.simpleperf.simpleperfexampleofkotlin",
+        if TestHelper.android_version >= 10:
+            cls.prepare("SimpleperfExampleKotlin",
+                        "simpleperf.example.kotlin",
+                        ".MainActivity", apk_name='app-release.apk')
+
+    def setUp(self):
+        if TestHelper().android_version < 10:
+            raise unittest.SkipTest("Profileable apk isn't supported on Android < Q.")
+        super().setUp()
+
+
+class TestExampleKotlinRoot(TestExampleBase):
+    @classmethod
+    def setUpClass(cls):
+        cls.prepare("SimpleperfExampleKotlin",
+                    "simpleperf.example.kotlin",
                     ".MainActivity",
                     adb_root=True)
 
@@ -97,18 +113,17 @@
         self.common_test_app_profiler()
 
 
-class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
+class TestExampleKotlinTraceOffCpu(TestExampleBase):
     @classmethod
     def setUpClass(cls):
-        cls.prepare("SimpleperfExampleOfKotlin",
-                    "com.example.simpleperf.simpleperfexampleofkotlin",
+        cls.prepare("SimpleperfExampleKotlin",
+                    "simpleperf.example.kotlin",
                     ".SleepActivity")
 
     def test_smoke(self):
         self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-clock:u --trace-offcpu")
         self.run_cmd(["report.py", "-g", "-o", "report.txt"])
-        function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \
-                          "SleepActivity$createRunSleepThread$1."
+        function_prefix = 'simpleperf.example.kotlin.SleepActivity$createRunSleepThread$1.'
         self.check_strings_in_file("report.txt", [
             function_prefix + "run",
             function_prefix + "RunFunction",
@@ -125,8 +140,8 @@
                 ("run", 80, 0),
                 ("RunFunction", 20, 20),
                 ("SleepFunction", 20, 0),
-                ("line 24", 20, 0),
-                ("line 32", 20, 0)])
+                ("line 23", 20, 0),
+                ("line 31", 20, 0)])
 
         self.run_cmd([INFERNO_SCRIPT, "-sc"])
         self.check_inferno_report_html([
diff --git a/simpleperf/scripts/test/pprof_proto_generator_test.py b/simpleperf/scripts/test/pprof_proto_generator_test.py
index cbeb8d6..297cf14 100644
--- a/simpleperf/scripts/test/pprof_proto_generator_test.py
+++ b/simpleperf/scripts/test/pprof_proto_generator_test.py
@@ -217,8 +217,8 @@
         binary_cache_builder.build_binary_cache(testdata_file, [TestHelper.testdata_dir])
 
         # Read recording file.
-        config = {'ndk_path': None, 'max_chain_length': 1000000,
-                  'report_lib_options': ReportLibOptions(False, '', None, None)}
+        config = {'ndk_path': TestHelper.ndk_path, 'max_chain_length': 1000000,
+                  'report_lib_options': ReportLibOptions(False, '', None, None, None)}
         generator = PprofProfileGenerator(config)
         generator.load_record_file(testdata_file)
 
diff --git a/simpleperf/scripts/test/report_html_test.py b/simpleperf/scripts/test/report_html_test.py
index 250ad03..7241a5e 100644
--- a/simpleperf/scripts/test/report_html_test.py
+++ b/simpleperf/scripts/test/report_html_test.py
@@ -18,7 +18,7 @@
 import json
 import os
 import tempfile
-from typing import Any, Dict, List, Set
+from typing import Any, Dict, List, Optional, Set
 
 from binary_cache_builder import BinaryCacheBuilder
 from . test_utils import TestBase, TestHelper
@@ -254,3 +254,25 @@
         self.assertNotIn(art_frame_str, report)
         report = self.get_record_data_string(options + ['--show-art-frames'])
         self.assertIn(art_frame_str, report)
+
+    def test_aggregate_threads(self):
+        def get_thread_names(aggregate_threads_option: Optional[List[str]]) -> Dict[str, int]:
+            options = ['-i', TestHelper.testdata_path('perf_display_bitmaps.data')]
+            if aggregate_threads_option:
+                options += ['--aggregate-threads'] + aggregate_threads_option
+            record_data = self.get_record_data(options)
+            thread_names = {}
+            try:
+                for thread in record_data['sampleInfo'][0]['processes'][0]['threads']:
+                    tid = str(thread['tid'])
+                    thread_names[record_data['threadNames'][tid]] = thread['sampleCount']
+            except IndexError:
+                pass
+            return thread_names
+        thread_names = get_thread_names(None)
+        self.assertEqual(thread_names['AsyncTask #3'], 6)
+        self.assertEqual(thread_names['AsyncTask #4'], 13)
+        thread_names = get_thread_names(['AsyncTask.*'])
+        self.assertEqual(thread_names['AsyncTask.*'], 19)
+        self.assertNotIn('AsyncTask #3', thread_names)
+        self.assertNotIn('AsyncTask #4', thread_names)
diff --git a/simpleperf/scripts/test/report_lib_test.py b/simpleperf/scripts/test/report_lib_test.py
index 1447c5a..8e212a2 100644
--- a/simpleperf/scripts/test/report_lib_test.py
+++ b/simpleperf/scripts/test/report_lib_test.py
@@ -16,7 +16,7 @@
 
 import os
 import tempfile
-from typing import List, Set
+from typing import Dict, List, Optional, Set
 
 from simpleperf_report_lib import ReportLib
 from . test_utils import TestBase, TestHelper
@@ -311,3 +311,24 @@
             self.assertIn(31881, threads)
             self.assertNotIn(31850, threads)
         os.unlink(filter_file.name)
+
+    def test_aggregate_threads(self):
+        """ Test using ReportLib.AggregateThreads(). """
+        def get_thread_names(aggregate_regex_list: Optional[List[str]]) -> Dict[str, int]:
+            self.report_lib.Close()
+            self.report_lib = ReportLib()
+            self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data'))
+            if aggregate_regex_list:
+                self.report_lib.AggregateThreads(aggregate_regex_list)
+            thread_names = {}
+            while self.report_lib.GetNextSample():
+                sample = self.report_lib.GetCurrentSample()
+                thread_names[sample.thread_comm] = thread_names.get(sample.thread_comm, 0) + 1
+            return thread_names
+        thread_names = get_thread_names(None)
+        self.assertEqual(thread_names['AsyncTask #3'], 6)
+        self.assertEqual(thread_names['AsyncTask #4'], 13)
+        thread_names = get_thread_names(['AsyncTask.*'])
+        self.assertEqual(thread_names['AsyncTask.*'], 19)
+        self.assertNotIn('AsyncTask #3', thread_names)
+        self.assertNotIn('AsyncTask #4', thread_names)
diff --git a/simpleperf/scripts/test/report_sample_test.py b/simpleperf/scripts/test/report_sample_test.py
index 5b6681c..83f1d36 100644
--- a/simpleperf/scripts/test/report_sample_test.py
+++ b/simpleperf/scripts/test/report_sample_test.py
@@ -20,6 +20,7 @@
 from typing import List, Optional, Set
 
 from . test_utils import TestBase, TestHelper
+from simpleperf_utils import remove
 
 
 class TestReportSample(TestBase):
@@ -36,6 +37,33 @@
             want = f.read()
         self.assertEqual(got, want)
 
+    def test_output_flag(self):
+        # Test short form flag.
+        remove('some.output')
+        got = self.get_record_data_string('perf_display_bitmaps.data',
+                                          ['-o', 'some.output'])
+        self.assertEqual(got, '')
+        self.check_exist(filename='some.output')
+
+        # Test long form flag
+        remove("some.output")
+        got = self.get_record_data_string('perf_display_bitmaps.data',
+                                          ['--output_file', 'some.output'])
+        self.assertEqual(got, '')
+        self.check_exist(filename="some.output")
+
+        # Verify that the output file contains expected data
+        with open(TestHelper.testdata_path('perf_display_bitmaps.perf-script')) as f:
+            want = f.read()
+        with open('some.output') as f:
+            got = f.read()
+        self.assertEqual(got, want)
+
+        # Verify that an output spec of '-' is stdout
+        remove("some.output")
+        got = self.get_record_data_string('perf_display_bitmaps.data', ['-o', '-'])
+        self.assertEqual(got, want)
+
     def test_comm_filter_to_renderthread(self):
         got = self.get_record_data_string('perf_display_bitmaps.data', ['--comm', 'RenderThread'])
         self.assertIn('RenderThread', got)
diff --git a/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json b/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
index 1903451..e423687 100644
--- a/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
+++ b/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
@@ -40,6 +40,13 @@
         ]
       },
       {
+        "color": "blue",
+        "name": "Off-CPU",
+        "subcategories": [
+          "Other"
+        ]
+      },
+      {
         "color": "grey",
         "name": "Other",
         "subcategories": [
diff --git a/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json b/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json
new file mode 100644
index 0000000..7544232
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json
@@ -0,0 +1,542 @@
+{
+  "libs": [],
+  "meta": {
+    "abi": "aarch64",
+    "asyncstack": 1,
+    "categories": [
+      {
+        "color": "yellow",
+        "name": "User",
+        "subcategories": [
+          "Other"
+        ]
+      },
+      {
+        "color": "orange",
+        "name": "Kernel",
+        "subcategories": [
+          "Other"
+        ]
+      },
+      {
+        "color": "yellow",
+        "name": "Native",
+        "subcategories": [
+          "Other"
+        ]
+      },
+      {
+        "color": "green",
+        "name": "DEX",
+        "subcategories": [
+          "Other"
+        ]
+      },
+      {
+        "color": "green",
+        "name": "OAT",
+        "subcategories": [
+          "Other"
+        ]
+      },
+      {
+        "color": "blue",
+        "name": "Off-CPU",
+        "subcategories": [
+          "Other"
+        ]
+      },
+      {
+        "color": "grey",
+        "name": "Other",
+        "subcategories": [
+          "Other"
+        ]
+      }
+    ],
+    "debug": 0,
+    "device": "Google:Pixel 2:walleye",
+    "gcpoison": 0,
+    "interval": 1,
+    "markerSchema": [],
+    "oscpu": null,
+    "platform": null,
+    "presymbolicated": true,
+    "processType": 0,
+    "product": "/data/local/tmp/simpleperf record -e sched:sched_switch -e cpu-cycles sleep 1",
+    "shutdownTime": null,
+    "stackwalk": 1,
+    "startTime": 1512941771000,
+    "version": 24
+  },
+  "pausedRanges": [],
+  "processes": [],
+  "threads": [
+    {
+      "frameTable": {
+        "data": [
+          [
+            0,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            1,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            2,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            5,
+            0
+          ],
+          [
+            3,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            4,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            5,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            6,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            7,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            8,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            9,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ],
+          [
+            10,
+            false,
+            0,
+            null,
+            null,
+            null,
+            null,
+            1,
+            0
+          ]
+        ],
+        "schema": {
+          "category": 7,
+          "column": 6,
+          "implementation": 3,
+          "innerWindowID": 2,
+          "line": 5,
+          "location": 0,
+          "optimizations": 4,
+          "relevantForJS": 1,
+          "subcategory": 8
+        }
+      },
+      "markers": {
+        "data": [],
+        "schema": {
+          "category": 4,
+          "data": 5,
+          "endTime": 2,
+          "name": 0,
+          "phase": 3,
+          "startTime": 1
+        }
+      },
+      "name": "sleep",
+      "pid": 9896,
+      "processType": "default",
+      "registerTime": 0,
+      "samples": {
+        "data": [
+          [
+            0,
+            536732415.65594,
+            0
+          ],
+          [
+            1,
+            536732415.663024,
+            0
+          ],
+          [
+            2,
+            536732415.855628,
+            0
+          ],
+          [
+            3,
+            536732416.562399,
+            0
+          ],
+          [
+            2,
+            536732416.922451,
+            0
+          ],
+          [
+            4,
+            536732418.120784,
+            0
+          ],
+          [
+            2,
+            536732418.371201,
+            0
+          ],
+          [
+            5,
+            536732419.142503,
+            0
+          ],
+          [
+            5,
+            536732419.14969,
+            0
+          ],
+          [
+            2,
+            536732419.966826,
+            0
+          ],
+          [
+            6,
+            536732420.235419,
+            0
+          ],
+          [
+            2,
+            536732420.924794,
+            0
+          ],
+          [
+            7,
+            536732421.171461,
+            0
+          ],
+          [
+            2,
+            536732421.250211,
+            0
+          ],
+          [
+            4,
+            536732421.445836,
+            0
+          ],
+          [
+            2,
+            536732421.544899,
+            0
+          ],
+          [
+            7,
+            536732421.800888,
+            0
+          ],
+          [
+            8,
+            536732421.836878,
+            0
+          ],
+          [
+            9,
+            536732421.841878,
+            0
+          ],
+          [
+            2,
+            536732421.87318,
+            0
+          ],
+          [
+            2,
+            536732421.900315,
+            0
+          ],
+          [
+            5,
+            536732422.094638,
+            0
+          ],
+          [
+            2,
+            536732422.188128,
+            0
+          ],
+          [
+            2,
+            536732422.43094,
+            0
+          ],
+          [
+            2,
+            536732422.702972,
+            0
+          ],
+          [
+            2,
+            536732423.006357,
+            0
+          ],
+          [
+            2,
+            536732423.138076,
+            0
+          ],
+          [
+            2,
+            536732423.500628,
+            0
+          ],
+          [
+            2,
+            536732423.76943,
+            0
+          ],
+          [
+            2,
+            536732425.676201,
+            0
+          ],
+          [
+            2,
+            536732426.086722,
+            0
+          ],
+          [
+            2,
+            536732426.35693,
+            0
+          ],
+          [
+            2,
+            536732426.801513,
+            0
+          ],
+          [
+            2,
+            536732426.916774,
+            0
+          ],
+          [
+            2,
+            536732427.020263,
+            0
+          ],
+          [
+            2,
+            536732427.455524,
+            0
+          ],
+          [
+            2,
+            536732428.163961,
+            0
+          ],
+          [
+            2,
+            536732428.383753,
+            0
+          ],
+          [
+            2,
+            536732428.868284,
+            0
+          ],
+          [
+            2,
+            536732429.336982,
+            0
+          ],
+          [
+            2,
+            536732429.894222,
+            0
+          ],
+          [
+            2,
+            536732430.261357,
+            0
+          ],
+          [
+            10,
+            536732430.486097,
+            0
+          ],
+          [
+            2,
+            536732433.741565,
+            0
+          ],
+          [
+            2,
+            536732434.293857,
+            0
+          ]
+        ],
+        "schema": {
+          "responsiveness": 2,
+          "stack": 0,
+          "time": 1
+        }
+      },
+      "stackTable": {
+        "data": [
+          [
+            null,
+            0,
+            0
+          ],
+          [
+            null,
+            1,
+            0
+          ],
+          [
+            null,
+            2,
+            0
+          ],
+          [
+            null,
+            3,
+            0
+          ],
+          [
+            null,
+            4,
+            0
+          ],
+          [
+            null,
+            5,
+            0
+          ],
+          [
+            null,
+            6,
+            0
+          ],
+          [
+            null,
+            7,
+            0
+          ],
+          [
+            null,
+            8,
+            0
+          ],
+          [
+            null,
+            9,
+            0
+          ],
+          [
+            null,
+            10,
+            0
+          ]
+        ],
+        "schema": {
+          "category": 2,
+          "frame": 1,
+          "prefix": 0
+        }
+      },
+      "stringTable": [
+        "perf_event_exec (in [kernel.kallsyms])",
+        "memcpy (in [kernel.kallsyms])",
+        "__schedule (in [kernel.kallsyms])",
+        "schedule_timeout (in [kernel.kallsyms])",
+        "filemap_fault (in [kernel.kallsyms])",
+        "_raw_spin_unlock_irq (in [kernel.kallsyms])",
+        "__wait_on_bit_lock (in [kernel.kallsyms])",
+        "generic_file_read_iter (in [kernel.kallsyms])",
+        "__do_softirq (in [kernel.kallsyms])",
+        "_raw_spin_unlock_irqrestore (in [kernel.kallsyms])",
+        "__clean_dcache_area_pou (in [kernel.kallsyms])"
+      ],
+      "tid": 9896,
+      "unregisterTime": null
+    }
+  ]
+}
diff --git a/simpleperf/scripts/test/test_utils.py b/simpleperf/scripts/test/test_utils.py
index 422f04d..f10ea9b 100644
--- a/simpleperf/scripts/test/test_utils.py
+++ b/simpleperf/scripts/test/test_utils.py
@@ -139,6 +139,8 @@
         elif result.failures and result.failures[-1][0] == self:
             status = 'FAILED'
             err_info = result.failures[-1][1]
+        elif result.skipped and result.skipped[-1][0] == self:
+            status = 'SKIPPED'
         else:
             status = 'OK'
 
@@ -146,7 +148,7 @@
         TestHelper.log(
             'end test %s.%s %s (%.3fs)' %
             (self.__class__.__name__, self._testMethodName, status, time_taken))
-        if status != 'OK':
+        if status == 'FAILED':
             TestHelper.log(err_info)
 
         # Remove test data for passed tests to save space.
@@ -162,7 +164,7 @@
             args += TestHelper.browser_option
         if TestHelper.ndk_path:
             if args[0] in ['app_profiler.py', 'binary_cache_builder.py', 'pprof_proto_generator.py',
-                           'report_html.py']:
+                           'report_html.py', 'annotate.py']:
                 args += ['--ndk_path', TestHelper.ndk_path]
         if args[0].endswith('.py'):
             args = [sys.executable, TestHelper.script_path(args[0])] + args[1:]
diff --git a/simpleperf/scripts/test/tools_test.py b/simpleperf/scripts/test/tools_test.py
index 83ccf48..9c9fbd7 100644
--- a/simpleperf/scripts/test/tools_test.py
+++ b/simpleperf/scripts/test/tools_test.py
@@ -298,7 +298,7 @@
     def test_source_file_searcher(self):
         searcher = SourceFileSearcher(
             [TestHelper.testdata_path('SimpleperfExampleCpp'),
-             TestHelper.testdata_path('SimpleperfExampleOfKotlin')])
+             TestHelper.testdata_path('SimpleperfExampleKotlin')])
 
         def format_path(path):
             return os.path.join(TestHelper.testdata_dir, path.replace('/', os.sep))
@@ -317,8 +317,9 @@
             searcher.get_real_path('cpp/MainActivity.java'))
         # Find a Kotlin file.
         self.assertEqual(
-            format_path('SimpleperfExampleOfKotlin/app/src/main/java/com/example/' +
-                        'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'),
+            format_path(
+                'SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/' +
+                'MainActivity.kt'),
             searcher.get_real_path('MainActivity.kt'))
 
     def test_is_elf_file(self):
@@ -340,18 +341,22 @@
         build_id = readelf.get_build_id(elf_path)
         self.assertGreater(len(build_id), 0)
         binary_cache_builder.binaries[elf_name] = build_id
+
+        filename_without_build_id = '/data/symfs_without_build_id/elf'
+        binary_cache_builder.binaries[filename_without_build_id] = ''
+
         binary_cache_builder.copy_binaries_from_symfs_dirs([TestHelper.testdata_dir])
         binary_cache_builder.create_build_id_list()
 
         # Test BinaryFinder.
-        path_in_binary_cache = Path(binary_cache_builder.binary_cache_dir, elf_name)
+        path_in_binary_cache = binary_cache_builder.find_path_in_cache(elf_name)
         binary_finder = BinaryFinder(binary_cache_builder.binary_cache_dir, readelf)
         # Find binary using build id.
         path = binary_finder.find_binary('[not_exist_file]', build_id)
         self.assertEqual(path, path_in_binary_cache)
         # Find binary using path.
-        path = binary_finder.find_binary('/' + elf_name, None)
-        self.assertEqual(path, path_in_binary_cache)
+        path = binary_finder.find_binary(filename_without_build_id, None)
+        self.assertIsNotNone(path)
         # Find binary using absolute path.
         path = binary_finder.find_binary(str(path_in_binary_cache), None)
         self.assertEqual(path, path_in_binary_cache)
diff --git a/simpleperf/test_util.cpp b/simpleperf/test_util.cpp
index 43e2183..60d8ea3 100644
--- a/simpleperf/test_util.cpp
+++ b/simpleperf/test_util.cpp
@@ -43,6 +43,10 @@
       if (s.find("arm") == std::string::npos && s.find("aarch64") == std::string::npos) {
         in_native_abi = 0;
       }
+    } else if (GetTargetArch() == ARCH_RISCV64) {
+      if (s.find("riscv") == std::string::npos) {
+        in_native_abi = 0;
+      }
     }
   }
   return in_native_abi == 1;
@@ -81,9 +85,10 @@
                        android::base::StartsWith(fingerprint, "google/sdk_gpc") ||
                        android::base::StartsWith(fingerprint, "generic/cf");
 
-    if (arch == ARCH_X86_64 || arch == ARCH_X86_32 || is_emulator) {
-      // On x86 and x86_64, it's likely to run on an emulator or vm without hardware perf
-      // counters. It's hard to enumerate them all. So check the support at runtime.
+    if (arch == ARCH_X86_64 || arch == ARCH_X86_32 || !IsInNativeAbi() || is_emulator) {
+      // On x86 and x86_64, or when we are not in native abi, it's likely to run on an emulator or
+      // vm without hardware perf counters. It's hard to enumerate them all. So check the support
+      // at runtime.
       const simpleperf::EventType* type = simpleperf::FindEventTypeByName("cpu-cycles", false);
       CHECK(type != nullptr);
       perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index 662c3b2..21b89a5 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -188,6 +188,8 @@
     return "arm64-v8a";
 #elif defined(__arm__)
     return "armeabi-v7a";
+#elif defined(__riscv)
+    return "riscv64";
 #else
 #error "unrecognized ABI"
 #endif
@@ -195,4 +197,4 @@
 
   std::vector<std::string> installed_packages_;
   std::unique_ptr<Workload> app_start_proc_;
-};
\ No newline at end of file
+};
diff --git a/simpleperf/testdata/DisplayBitmaps.apk b/simpleperf/testdata/DisplayBitmaps.apk
index 63b4074..2911edd 100644
--- a/simpleperf/testdata/DisplayBitmaps.apk
+++ b/simpleperf/testdata/DisplayBitmaps.apk
Binary files differ
diff --git a/simpleperf/testdata/DisplayBitmapsTest.apk b/simpleperf/testdata/DisplayBitmapsTest.apk
index 1580719..db5575e 100644
--- a/simpleperf/testdata/DisplayBitmapsTest.apk
+++ b/simpleperf/testdata/DisplayBitmapsTest.apk
Binary files differ
diff --git a/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf b/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
deleted file mode 100644
index a92e41f..0000000
--- a/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/etm/perf.data b/simpleperf/testdata/etm/perf_etm.data
similarity index 100%
rename from simpleperf/testdata/etm/perf.data
rename to simpleperf/testdata/etm/perf_etm.data
Binary files differ
diff --git a/simpleperf/testdata/etm/perf_inject.data b/simpleperf/testdata/etm/perf_inject.data
index 4c121a3..9f83430 100644
--- a/simpleperf/testdata/etm/perf_inject.data
+++ b/simpleperf/testdata/etm/perf_inject.data
@@ -20,5 +20,6 @@
 10a0->1054:1
 10b0->0:1
 10ec->0:1
+// build_id: 0x0c9a20bf9c009d0e4e8bbf9fad0300ae00000000
 // /data/local/tmp/etm_test_loop
 
diff --git a/simpleperf/testdata/etm/perf_with_missing_aux_data.data b/simpleperf/testdata/etm/perf_with_missing_aux_data.data
new file mode 100644
index 0000000..781a0a3
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_with_missing_aux_data.data
Binary files differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index 2898722..a6d2e81 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -49,7 +49,15 @@
   }
 }
 
-void ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
+bool ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
+  // Check thread ID.
+  if (tid == ptid) {
+    return false;
+  }
+  // Check thread group ID (pid here) as in https://linux.die.net/man/2/clone2.
+  if (pid != tid && pid != ppid) {
+    return false;
+  }
   ThreadEntry* parent = FindThreadOrNew(ppid, ptid);
   ThreadEntry* child = FindThreadOrNew(pid, tid);
   child->comm = parent->comm;
@@ -64,6 +72,7 @@
       }
     }
   }
+  return true;
 }
 
 ThreadEntry* ThreadTree::FindThread(int tid) const {
@@ -159,6 +168,7 @@
                               const std::string& filename, uint32_t flags) {
   ThreadEntry* thread = FindThreadOrNew(pid, tid);
   Dso* dso = FindUserDsoOrNew(filename, start_addr);
+  CHECK(dso != nullptr);
   InsertMap(*thread->maps, MapEntry(start_addr, len, pgoff, dso, false, flags));
 }
 
@@ -197,6 +207,9 @@
   if (it == user_dso_tree_.end()) {
     bool force_64bit = start_addr > UINT_MAX;
     std::unique_ptr<Dso> dso = Dso::CreateDso(dso_type, filename, force_64bit);
+    if (!dso) {
+      return nullptr;
+    }
     auto pair = user_dso_tree_.insert(std::make_pair(filename, std::move(dso)));
     CHECK(pair.second);
     it = pair.first;
@@ -347,7 +360,7 @@
   map_storage_.clear();
 }
 
-void ThreadTree::AddDsoInfo(FileFeature& file) {
+bool ThreadTree::AddDsoInfo(FileFeature& file) {
   DsoType dso_type = file.type;
   Dso* dso = nullptr;
   if (dso_type == DSO_KERNEL) {
@@ -357,11 +370,15 @@
   } else {
     dso = FindUserDsoOrNew(file.path, 0, dso_type);
   }
+  if (!dso) {
+    return false;
+  }
   dso->SetMinExecutableVaddr(file.min_vaddr, file.file_offset_of_min_vaddr);
   dso->SetSymbols(&file.symbols);
   for (uint64_t offset : file.dex_file_offsets) {
     dso->AddDexFileOffset(offset);
   }
+  return true;
 }
 
 void ThreadTree::AddDexFileOffset(const std::string& file_path, uint64_t dex_file_offset) {
@@ -394,11 +411,13 @@
     const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
     ForkThread(r.data->pid, r.data->tid, r.data->ppid, r.data->ptid);
   } else if (record.type() == PERF_RECORD_EXIT) {
-    const ExitRecord& r = *static_cast<const ExitRecord*>(&record);
-    ExitThread(r.data->pid, r.data->tid);
+    if (!disable_thread_exit_records_) {
+      const ExitRecord& r = *static_cast<const ExitRecord*>(&record);
+      ExitThread(r.data->pid, r.data->tid);
+    }
   } else if (record.type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
     const auto& r = *static_cast<const KernelSymbolRecord*>(&record);
-    Dso::SetKallsyms(std::move(r.kallsyms));
+    Dso::SetKallsyms(std::string(r.kallsyms, r.kallsyms_size));
   }
 }
 
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index 33e8545..ea72376 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -100,8 +100,9 @@
   }
   virtual ~ThreadTree() {}
 
+  void DisableThreadExitRecords() { disable_thread_exit_records_ = true; }
   void SetThreadName(int pid, int tid, const std::string& comm);
-  void ForkThread(int pid, int tid, int ppid, int ptid);
+  bool ForkThread(int pid, int tid, int ppid, int ptid);
   virtual ThreadEntry* FindThread(int tid) const;
   ThreadEntry* FindThreadOrNew(int pid, int tid);
   void ExitThread(int pid, int tid);
@@ -131,7 +132,7 @@
   // Clear thread and map information, but keep loaded dso information. It saves
   // the time to reload dso information.
   void ClearThreadAndMap();
-  void AddDsoInfo(FileFeature& file);
+  bool AddDsoInfo(FileFeature& file);
   void AddDexFileOffset(const std::string& file_path, uint64_t dex_file_offset);
 
   // Update thread tree with information provided by record.
@@ -167,6 +168,7 @@
   bool show_ip_for_unknown_symbol_;
   bool show_mark_for_unknown_symbol_;
   Symbol unknown_symbol_;
+  bool disable_thread_exit_records_ = false;
 };
 
 }  // namespace simpleperf
diff --git a/simpleperf/thread_tree_test.cpp b/simpleperf/thread_tree_test.cpp
index cefa3f7..f5b71ec 100644
--- a/simpleperf/thread_tree_test.cpp
+++ b/simpleperf/thread_tree_test.cpp
@@ -143,3 +143,10 @@
   ASSERT_STREQ("two", FindSymbol(1, 1, 0x2010)->Name());
   ASSERT_STREQ("three", FindSymbol(1, 1, 0x302f)->Name());
 }
+
+TEST_F(ThreadTreeTest, invalid_fork) {
+  // tid == ptid
+  ASSERT_FALSE(thread_tree_.ForkThread(1, 2, 1, 2));
+  // pid != tid && pid != ppid
+  ASSERT_FALSE(thread_tree_.ForkThread(1, 2, 3, 1));
+}
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
index cb6a862..ab9c3fa 100644
--- a/simpleperf/tracing.cpp
+++ b/simpleperf/tracing.cpp
@@ -21,7 +21,6 @@
 
 #include <map>
 #include <optional>
-#include <regex>
 #include <string>
 #include <vector>
 
@@ -31,6 +30,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#include "RegEx.h"
 #include "environment.h"
 #include "perf_event.h"
 #include "utils.h"
@@ -40,15 +40,6 @@
 
 namespace simpleperf {
 
-template <>
-void MoveFromBinaryFormat(std::string& data, const char*& p) {
-  data.clear();
-  while (*p != '\0') {
-    data.push_back(*p++);
-  }
-  p++;
-}
-
 const char TRACING_INFO_MAGIC[10] = {23, 8, 68, 't', 'r', 'a', 'c', 'i', 'n', 'g'};
 
 template <class T>
@@ -78,12 +69,18 @@
   data.insert(data.end(), file.begin(), file.end());
 }
 
-static void DetachFile(const char*& p, std::string& file, uint32_t file_size_bytes = 8) {
-  uint64_t file_size = ConvertBytesToValue(p, file_size_bytes);
-  p += file_size_bytes;
-  file.clear();
-  file.insert(file.end(), p, p + file_size);
-  p += file_size;
+static std::string DetachFile(BinaryReader& reader, uint32_t file_size_bytes = 8) {
+  if (!reader.CheckLeftSize(file_size_bytes)) {
+    return "";
+  }
+  uint64_t file_size = ConvertBytesToValue(reader.head, file_size_bytes);
+  reader.head += file_size_bytes;
+  if (!reader.CheckLeftSize(file_size)) {
+    return "";
+  }
+  std::string result(reader.head, file_size);
+  reader.head += file_size;
+  return result;
 }
 
 static bool ReadTraceFsFile(const std::string& path, std::string* content,
@@ -119,7 +116,7 @@
   bool RecordKallsymsFile();
   bool RecordPrintkFormatsFile();
   std::vector<char> BinaryFormat() const;
-  void LoadFromBinary(const std::vector<char>& data);
+  bool LoadFromBinary(const std::vector<char>& data);
   void Dump(size_t indent) const;
   std::vector<TracingFormat> LoadTracingFormatsFromEventFiles() const;
   const std::string& GetKallsymsFile() const { return kallsyms_file; }
@@ -207,44 +204,44 @@
   return ret;
 }
 
-void TracingFile::LoadFromBinary(const std::vector<char>& data) {
-  const char* p = data.data();
-  const char* end = data.data() + data.size();
-  CHECK(memcmp(p, magic, sizeof(magic)) == 0);
-  p += sizeof(magic);
-  MoveFromBinaryFormat(version, p);
-  MoveFromBinaryFormat(endian, p);
-  MoveFromBinaryFormat(size_of_long, p);
-  MoveFromBinaryFormat(page_size, p);
-  std::string filename;
-  MoveFromBinaryFormat(filename, p);
-  CHECK_EQ(filename, "header_page");
-  DetachFile(p, header_page_file);
-  MoveFromBinaryFormat(filename, p);
-  CHECK_EQ(filename, "header_event");
-  DetachFile(p, header_event_file);
-  uint32_t count;
-  MoveFromBinaryFormat(count, p);
-  ftrace_format_files.resize(count);
-  for (uint32_t i = 0; i < count; ++i) {
-    DetachFile(p, ftrace_format_files[i]);
+bool TracingFile::LoadFromBinary(const std::vector<char>& data) {
+  BinaryReader reader(data.data(), data.size());
+  if (!reader.CheckLeftSize(sizeof(magic)) || memcmp(reader.head, magic, sizeof(magic)) != 0) {
+    return false;
   }
-  MoveFromBinaryFormat(count, p);
+  reader.head += sizeof(magic);
+  version = reader.ReadString();
+  reader.Read(endian);
+  reader.Read(size_of_long);
+  reader.Read(page_size);
+  if (reader.ReadString() != "header_page") {
+    return false;
+  }
+  header_page_file = DetachFile(reader);
+  if (reader.ReadString() != "header_event") {
+    return false;
+  }
+  header_event_file = DetachFile(reader);
+  uint32_t count = 0;
+  reader.Read(count);
+  ftrace_format_files.clear();
+  while (count-- > 0 && !reader.error) {
+    ftrace_format_files.emplace_back(DetachFile(reader));
+  }
+  reader.Read(count);
   event_format_files.clear();
-  for (uint32_t i = 0; i < count; ++i) {
-    std::string system;
-    MoveFromBinaryFormat(system, p);
-    uint32_t count_in_system;
-    MoveFromBinaryFormat(count_in_system, p);
-    for (uint32_t i = 0; i < count_in_system; ++i) {
-      std::string format;
-      DetachFile(p, format);
+  while (count-- > 0 && !reader.error) {
+    std::string system = reader.ReadString();
+    uint32_t count_in_system = 0;
+    reader.Read(count_in_system);
+    while (count_in_system-- > 0 && !reader.error) {
+      std::string format = DetachFile(reader);
       event_format_files.push_back(std::make_pair(system, std::move(format)));
     }
   }
-  DetachFile(p, kallsyms_file, 4);
-  DetachFile(p, printk_formats_file, 4);
-  CHECK_EQ(p, end);
+  kallsyms_file = DetachFile(reader, 4);
+  printk_formats_file = DetachFile(reader, 4);
+  return !reader.error && reader.head == reader.end;
 }
 
 void TracingFile::Dump(size_t indent) const {
@@ -286,14 +283,13 @@
   TracingField field;
   std::string name;
   std::string value;
-  std::regex re(R"((\w+):(.+?);)");
+  auto re = RegEx::Create(R"((\w+):(.+?);)");
 
-  std::sregex_iterator match_it(s.begin(), s.end(), re);
-  std::sregex_iterator match_end;
-  while (match_it != match_end) {
-    std::smatch match = *match_it++;
-    std::string name = match.str(1);
-    std::string value = match.str(2);
+  std::unique_ptr<RegExMatch> match = re->SearchAll(s);
+  while (match->IsValid()) {
+    std::string name = match->GetField(1);
+    std::string value = match->GetField(2);
+    match->MoveToNextMatch();
 
     if (name == "field") {
       std::string last_value_part = Split(value, " \t").back();
@@ -372,14 +368,18 @@
   return formats;
 }
 
-Tracing::Tracing(const std::vector<char>& data) {
-  tracing_file_ = new TracingFile;
-  tracing_file_->LoadFromBinary(data);
+std::unique_ptr<Tracing> Tracing::Create(const std::vector<char>& data) {
+  std::unique_ptr<Tracing> tracing(new Tracing);
+  if (!tracing->tracing_file_->LoadFromBinary(data)) {
+    LOG(ERROR) << "Failed to load tracing data";
+    return nullptr;
+  }
+  return tracing;
 }
 
-Tracing::~Tracing() {
-  delete tracing_file_;
-}
+Tracing::Tracing() : tracing_file_(new TracingFile) {}
+
+Tracing::~Tracing() {}
 
 void Tracing::Dump(size_t indent) {
   tracing_file_->Dump(indent);
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
index 101ac55..9194d02 100644
--- a/simpleperf/tracing.h
+++ b/simpleperf/tracing.h
@@ -98,7 +98,7 @@
 
 class Tracing {
  public:
-  explicit Tracing(const std::vector<char>& data);
+  static std::unique_ptr<Tracing> Create(const std::vector<char>& data);
   ~Tracing();
   void Dump(size_t indent);
   TracingFormat GetTracingFormatHavingId(uint64_t trace_event_id);
@@ -107,7 +107,9 @@
   uint32_t GetPageSize() const;
 
  private:
-  TracingFile* tracing_file_;
+  Tracing();
+
+  std::unique_ptr<TracingFile> tracing_file_;
   std::vector<TracingFormat> tracing_formats_;
 };
 
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index a041816..8600467 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -40,6 +40,9 @@
 #include <Xz.h>
 #include <XzCrc64.h>
 
+#include "RegEx.h"
+#include "environment.h"
+
 namespace simpleperf {
 
 using android::base::ParseInt;
@@ -424,6 +427,54 @@
   return tids;
 }
 
+std::optional<std::set<pid_t>> GetPidsFromStrings(const std::vector<std::string>& strs,
+                                                  bool check_if_exists,
+                                                  bool support_progress_name_regex) {
+  std::set<pid_t> pids;
+  std::vector<std::unique_ptr<RegEx>> regs;
+  for (const auto& s : strs) {
+    for (const auto& p : Split(s, ",")) {
+      int pid;
+      if (ParseInt(p.c_str(), &pid, 0)) {
+        if (check_if_exists && !IsDir(StringPrintf("/proc/%d", pid))) {
+          LOG(ERROR) << "no process with pid " << pid;
+          return std::nullopt;
+        }
+        pids.insert(pid);
+      } else if (support_progress_name_regex) {
+        auto reg = RegEx::Create(p);
+        if (!reg) {
+          return std::nullopt;
+        }
+        regs.emplace_back(std::move(reg));
+      } else {
+        LOG(ERROR) << "invalid pid: " << p;
+        return std::nullopt;
+      }
+    }
+  }
+  if (!regs.empty()) {
+#if defined(__linux__)
+    for (pid_t pid : GetAllProcesses()) {
+      std::string process_name = GetCompleteProcessName(pid);
+      if (process_name.empty()) {
+        continue;
+      }
+      for (const auto& reg : regs) {
+        if (reg->Search(process_name)) {
+          pids.insert(pid);
+          break;
+        }
+      }
+    }
+#else   // defined(__linux__)
+    LOG(ERROR) << "progress name regex isn't supported";
+    return std::nullopt;
+#endif  // defined(__linux__)
+  }
+  return pids;
+}
+
 size_t SafeStrlen(const char* s, const char* end) {
   const char* p = s;
   while (p < end && *p != '\0') {
@@ -432,4 +483,19 @@
   return p - s;
 }
 
+OverflowResult SafeAdd(uint64_t a, uint64_t b) {
+  OverflowResult result;
+  if (__builtin_add_overflow(a, b, &result.value)) {
+    result.overflow = true;
+  }
+  return result;
+}
+
+void OverflowSafeAdd(uint64_t& dest, uint64_t add) {
+  if (__builtin_add_overflow(dest, add, &dest)) {
+    LOG(WARNING) << "Branch count overflow happened.";
+    dest = UINT64_MAX;
+  }
+}
+
 }  // namespace simpleperf
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index 10de932..cda9bba 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -37,6 +37,10 @@
 
 namespace simpleperf {
 
+static constexpr size_t kKilobyte = 1024;
+static constexpr size_t kMegabyte = 1024 * kKilobyte;
+static constexpr uint64_t kGigabyte = 1024 * kMegabyte;
+
 static inline uint64_t AlignDown(uint64_t value, uint64_t alignment) {
   return value & ~(alignment - 1);
 }
@@ -148,6 +152,81 @@
   p += size;
 }
 
+// Read info from binary data.
+struct BinaryReader {
+ public:
+  BinaryReader(const char* head, size_t size) : head(head), end(head + size), error(false) {}
+
+  size_t LeftSize() const { return end - head; }
+
+  bool CheckLeftSize(size_t size) {
+    if (UNLIKELY(error)) {
+      return false;
+    }
+    if (UNLIKELY(LeftSize() < size)) {
+      error = true;
+      return false;
+    }
+    return true;
+  }
+
+  void Move(size_t size) {
+    if (CheckLeftSize(size)) {
+      head += size;
+    }
+  }
+
+  template <class T>
+  void Read(T& data) {
+    static_assert(std::is_standard_layout<T>::value, "not standard layout");
+    if (UNLIKELY(error)) {
+      return;
+    }
+    if (UNLIKELY(LeftSize() < sizeof(T))) {
+      error = true;
+    } else {
+      memcpy(&data, head, sizeof(T));
+      head += sizeof(T);
+    }
+  }
+
+  template <class T>
+  void Read(T* data_p, size_t n) {
+    static_assert(std::is_standard_layout<T>::value, "not standard layout");
+    if (UNLIKELY(error)) {
+      return;
+    }
+    size_t size;
+    if (UNLIKELY(__builtin_mul_overflow(n, sizeof(T), &size) || LeftSize() < size)) {
+      error = true;
+    } else {
+      memcpy(data_p, head, size);
+      head += size;
+    }
+  }
+
+  // Read a string ending with '\0'.
+  std::string ReadString() {
+    if (UNLIKELY(error)) {
+      return "";
+    }
+    std::string result;
+    while (head < end && *head != '\0') {
+      result.push_back(*head++);
+    }
+    if (LIKELY(head < end && *head == '\0')) {
+      head++;
+      return result;
+    }
+    error = true;
+    return "";
+  }
+
+  const char* head;
+  const char* end;
+  bool error;
+};
+
 void PrintIndented(size_t indent, const char* fmt, ...);
 void FprintIndented(FILE* fp, size_t indent, const char* fmt, ...);
 
@@ -177,6 +256,9 @@
 
 std::optional<std::set<int>> GetCpusFromString(const std::string& s);
 std::optional<std::set<pid_t>> GetTidsFromString(const std::string& s, bool check_if_exists);
+std::optional<std::set<pid_t>> GetPidsFromStrings(const std::vector<std::string>& strs,
+                                                  bool check_if_exists,
+                                                  bool support_progress_name_regex);
 
 template <typename T>
 std::optional<std::set<T>> ParseUintVector(const std::string& s) {
@@ -200,6 +282,14 @@
 
 size_t SafeStrlen(const char* s, const char* end);
 
+struct OverflowResult {
+  bool overflow = false;
+  uint64_t value = 0;
+};
+
+OverflowResult SafeAdd(uint64_t a, uint64_t b);
+void OverflowSafeAdd(uint64_t& dest, uint64_t add);
+
 }  // namespace simpleperf
 
 #endif  // SIMPLE_PERF_UTILS_H_
diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp
index 725ae4d..2dfbd90 100644
--- a/simpleperf/utils_test.cpp
+++ b/simpleperf/utils_test.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+#include "utils.h"
+
 #include <gtest/gtest.h>
 
 #include <android-base/file.h>
 
+#include "environment.h"
 #include "get_test_data.h"
-#include "utils.h"
 
 using namespace simpleperf;
 
@@ -75,6 +77,20 @@
   ASSERT_EQ(GetTidsFromString("-2", false), std::nullopt);
 }
 
+TEST(utils, GetPidsFromStrings) {
+  ASSERT_EQ(GetPidsFromStrings({"0,12", "9"}, false, false),
+            std::make_optional(std::set<pid_t>({0, 9, 12})));
+  ASSERT_EQ(GetPidsFromStrings({"-2"}, false, false), std::nullopt);
+#if defined(__linux__)
+  pid_t pid = getpid();
+  ASSERT_EQ(GetPidsFromStrings({std::to_string(pid)}, true, false),
+            std::make_optional(std::set<pid_t>({pid})));
+  std::string process_name = GetCompleteProcessName(pid);
+  ASSERT_EQ(GetPidsFromStrings({process_name}, true, true),
+            std::make_optional(std::set<pid_t>({pid})));
+#endif  // defined(__linux__)
+}
+
 TEST(utils, LineReader) {
   TemporaryFile tmpfile;
   close(tmpfile.release());
diff --git a/squashfs_utils/Android.bp b/squashfs_utils/Android.bp
index d777a49..421c0d8 100644
--- a/squashfs_utils/Android.bp
+++ b/squashfs_utils/Android.bp
@@ -47,7 +47,7 @@
 }
 
 sh_binary_host {
-    name: "mksquashfsimage.sh",
+    name: "mksquashfsimage",
     src: "mksquashfsimage.sh",
     required: [
         "img2simg",
diff --git a/tests/kernel.config/OWNERS b/tests/kernel.config/OWNERS
new file mode 100644
index 0000000..65d27f4
--- /dev/null
+++ b/tests/kernel.config/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 119452
+
+# TODO: we likely want to delete or factor out these tests
+# b/261015480#comment6
+
+smoreland@google.com
diff --git a/toolchain-extras/Android.bp b/toolchain-extras/Android.bp
index d48f081..f15defd 100644
--- a/toolchain-extras/Android.bp
+++ b/toolchain-extras/Android.bp
@@ -82,6 +82,10 @@
 cc_library_static {
     name: "libprofile-clang-extras",
     defaults: ["libprofile-clang-platform-defaults"],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 }
 
 cc_library_static {