Merge "mksquashfsimage.sh: Add forward slash for SRC_DIR"
diff --git a/ext4_utils/Android.mk b/ext4_utils/Android.mk
index 9f56cca..8953b45 100644
--- a/ext4_utils/Android.mk
+++ b/ext4_utils/Android.mk
@@ -10,7 +10,6 @@
     contents.c \
     extent.c \
     indirect.c \
-    uuid.c \
     sha1.c \
     wipe.c \
     crc16.c \
@@ -23,6 +22,8 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(libext4_utils_src_files)
 LOCAL_MODULE := libext4_utils_host
+# Various instances of dereferencing a type-punned pointer in extent.c
+LOCAL_CFLAGS += -fno-strict-aliasing
 LOCAL_STATIC_LIBRARIES := \
     libsparse_host \
     libz
@@ -35,15 +36,15 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := make_ext4fs_main.c canned_fs_config.c
 LOCAL_MODULE := make_ext4fs
+LOCAL_SHARED_LIBRARIES += libcutils
 LOCAL_STATIC_LIBRARIES += \
-    libcutils \
     libext4_utils_host \
     libsparse_host \
     libz
 ifeq ($(HOST_OS),windows)
   LOCAL_LDLIBS += -lws2_32
 else
-  LOCAL_STATIC_LIBRARIES += libselinux
+  LOCAL_SHARED_LIBRARIES += libselinux
   LOCAL_CFLAGS := -DHOST
 endif
 include $(BUILD_HOST_EXECUTABLE)
@@ -54,8 +55,8 @@
 #
 
 libext4_utils_src_files += \
+    key_control.cpp \
     ext4_crypt.cpp \
-    e4crypt_static.c \
     unencrypted_properties.cpp
 
 ifneq ($(HOST_OS),windows)
@@ -64,11 +65,15 @@
 LOCAL_SRC_FILES := $(libext4_utils_src_files)
 LOCAL_MODULE := libext4_utils
 LOCAL_C_INCLUDES += system/core/logwrapper/include
+# Various instances of dereferencing a type-punned pointer in extent.c
+LOCAL_CFLAGS += -fno-strict-aliasing
 LOCAL_SHARED_LIBRARIES := \
     libcutils \
+    libext2_uuid \
     libselinux \
     libsparse \
     libz
+LOCAL_CFLAGS := -DREAL_UUID
 include $(BUILD_SHARED_LIBRARY)
 
 
@@ -76,6 +81,8 @@
 LOCAL_SRC_FILES := $(libext4_utils_src_files) \
     ext4_crypt_init_extensions.cpp
 LOCAL_MODULE := libext4_utils_static
+# Various instances of dereferencing a type-punned pointer in extent.c
+LOCAL_CFLAGS += -fno-strict-aliasing
 LOCAL_STATIC_LIBRARIES := \
     libsparse_static
 include $(BUILD_STATIC_LIBRARY)
@@ -86,9 +93,11 @@
 LOCAL_MODULE := make_ext4fs
 LOCAL_SHARED_LIBRARIES := \
     libcutils \
+    libext2_uuid \
     libext4_utils \
     libselinux \
     libz
+LOCAL_CFLAGS := -DREAL_UUID
 include $(BUILD_EXECUTABLE)
 
 
@@ -106,9 +115,10 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := ext2simg.c
 LOCAL_MODULE := ext2simg
+LOCAL_SHARED_LIBRARIES += \
+    libselinux
 LOCAL_STATIC_LIBRARIES += \
     libext4_utils_host \
-    libselinux \
     libsparse_host \
     libz
 include $(BUILD_HOST_EXECUTABLE)
diff --git a/ext4_utils/e4crypt_static.c b/ext4_utils/e4crypt_static.c
deleted file mode 100644
index 1a62ce4..0000000
--- a/ext4_utils/e4crypt_static.c
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (c) 2015 Google, Inc.
- */
-
-#define TAG "ext4_utils"
-
-#include <dirent.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <sys/xattr.h>
-#include <sys/syscall.h>
-#include <sys/stat.h>
-
-#include <cutils/klog.h>
-
-#include "ext4_crypt.h"
-
-/* keyring keyctl commands */
-#define KEYCTL_SETPERM        5 /* set permissions for a key in a keyring */
-#define KEYCTL_UNLINK         9 /* unlink a key from a keyring */
-#define KEYCTL_SEARCH        10 /* search for a key in a keyring */
-
-#define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy"
-#define EXT4_KEYREF_DELIMITER ((char)'.')
-
-/* Validate that all path items are available and accessible. */
-static int is_path_valid(const char *path)
-{
-    if (access(path, W_OK)) {
-        KLOG_ERROR(TAG, "Can't access %s: %s\n",strerror(errno), path);
-        return 0;
-    }
-
-    return 1;
-}
-
-/* Checks whether the policy provided is valid */
-static int is_keyref_valid(const char *keyref)
-{
-    char *period = 0;
-    size_t key_location_len = 0;
-
-    /* Key ref must have a key and location delimiter character. */
-    period = strchr(keyref, EXT4_KEYREF_DELIMITER);
-    if (!period) {
-        return 0;
-    }
-
-    /* period must be >= keyref. */
-    key_location_len = period - keyref;
-
-    if (strncmp(keyref, "@t", key_location_len) == 0 ||
-        strncmp(keyref, "@p", key_location_len) == 0 ||
-        strncmp(keyref, "@s", key_location_len) == 0 ||
-        strncmp(keyref, "@u", key_location_len) == 0 ||
-        strncmp(keyref, "@g", key_location_len) == 0 ||
-        strncmp(keyref, "@us", key_location_len) == 0)
-        return 1;
-
-    return 0;
-}
-
-static int is_dir_empty(const char *dirname)
-{
-    int n = 0;
-    struct dirent *d;
-    DIR *dir;
-
-    dir = opendir(dirname);
-    while ((d = readdir(dir)) != NULL) {
-        if (strcmp(d->d_name, "lost+found") == 0) {
-            // Skip lost+found directory
-        } else if (++n > 2) {
-            break;
-        }
-    }
-    closedir(dir);
-    return n <= 2;
-}
-
-int do_policy_set(const char *directory, const char *policy)
-{
-    struct stat st;
-    ssize_t ret;
-
-    if (!is_keyref_valid(policy)) {
-        KLOG_ERROR(TAG, "Policy has invalid format.\n");
-        return -EINVAL;
-    }
-
-    if (!is_path_valid(directory)) {
-        return -EINVAL;
-    }
-
-    stat(directory, &st);
-    if (!S_ISDIR(st.st_mode)) {
-        KLOG_ERROR(TAG, "Can only set policy on a directory (%s)\n", directory);
-        return -EINVAL;
-    }
-
-    if (!is_dir_empty(directory)) {
-        KLOG_ERROR(TAG, "Can only set policy on an empty directory (%s)\n", directory);
-        return -EINVAL;
-    }
-
-    ret = lsetxattr(directory, XATTR_NAME_ENCRYPTION_POLICY, policy,
-                    strlen(policy), 0);
-
-    if (ret) {
-        KLOG_ERROR(TAG, "Failed to set encryption policy for %s: %s\n",
-                   directory, strerror(errno));
-        return -EINVAL;
-    }
-
-    KLOG_INFO(TAG, "Encryption policy for %s is set to %s\n", directory, policy);
-    return 0;
-}
-
-static long keyctl(int cmd, ...)
-{
-    va_list va;
-    unsigned long arg2, arg3, arg4, arg5;
-
-    va_start(va, cmd);
-    arg2 = va_arg(va, unsigned long);
-    arg3 = va_arg(va, unsigned long);
-    arg4 = va_arg(va, unsigned long);
-    arg5 = va_arg(va, unsigned long);
-    va_end(va);
-    return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
-}
-
-key_serial_t add_key(const char *type,
-                     const char *description,
-                     const void *payload,
-                     size_t plen,
-                     key_serial_t ringid)
-{
-    return syscall(__NR_add_key, type, description, payload, plen, ringid);
-}
-
-long keyctl_setperm(key_serial_t id, int permissions)
-{
-    return keyctl(KEYCTL_SETPERM, id, permissions);
-}
diff --git a/ext4_utils/ext4_crypt.cpp b/ext4_utils/ext4_crypt.cpp
index bb57332..886d17a 100644
--- a/ext4_utils/ext4_crypt.cpp
+++ b/ext4_utils/ext4_crypt.cpp
@@ -1,120 +1,131 @@
+/*
+ * Copyright (c) 2015 Google, Inc.
+ */
+
 #define TAG "ext4_utils"
 
-#include "ext4_crypt.h"
+#include "ext4_crypt_init_extensions.h"
 
-#include <string>
-#include <fstream>
-#include <map>
-
+#include <dirent.h>
 #include <errno.h>
-#include <sys/mount.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <asm/ioctl.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
 #include <cutils/klog.h>
-#include <cutils/properties.h>
 
 #include "unencrypted_properties.h"
 
-namespace {
-    std::map<std::string, std::string> s_password_store;
+#define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy"
+#define EXT4_KEYREF_DELIMITER ((char)'.')
+
+// ext4enc:TODO Include structure from somewhere sensible
+// MUST be in sync with ext4_crypto.c in kernel
+#define EXT4_KEY_DESCRIPTOR_SIZE 8
+struct ext4_encryption_policy {
+    char version;
+    char contents_encryption_mode;
+    char filenames_encryption_mode;
+    char flags;
+    char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];
+} __attribute__((__packed__));
+
+#define EXT4_ENCRYPTION_MODE_AES_256_XTS    1
+#define EXT4_ENCRYPTION_MODE_AES_256_CTS    4
+
+// ext4enc:TODO Get value from somewhere sensible
+#define EXT4_IOC_SET_ENCRYPTION_POLICY \
+    _IOR('f', 19, struct ext4_encryption_policy)
+
+/* Validate that all path items are available and accessible. */
+static int is_path_valid(const char *path)
+{
+    if (access(path, W_OK)) {
+        KLOG_ERROR(TAG, "Can't access %s: %s\n",strerror(errno), path);
+        return 0;
+    }
+
+    return 1;
+}
+
+static int is_dir_empty(const char *dirname)
+{
+    int n = 0;
+    struct dirent *d;
+    DIR *dir;
+
+    dir = opendir(dirname);
+    while ((d = readdir(dir)) != NULL) {
+        if (strcmp(d->d_name, "lost+found") == 0) {
+            // Skip lost+found directory
+        } else if (++n > 2) {
+            break;
+        }
+    }
+    closedir(dir);
+    return n <= 2;
+}
+
+int do_policy_set(const char *directory, const char *policy, int policy_length)
+{
+    struct stat st;
+    ssize_t ret;
+
+    if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
+        KLOG_ERROR("Policy wrong length\n");
+        return -EINVAL;
+    }
+
+    if (!is_path_valid(directory)) {
+        return -EINVAL;
+    }
+
+    stat(directory, &st);
+    if (!S_ISDIR(st.st_mode)) {
+        KLOG_ERROR(TAG, "Can only set policy on a directory (%s)\n", directory);
+        return -EINVAL;
+    }
+
+    if (!is_dir_empty(directory)) {
+        KLOG_ERROR(TAG, "Can only set policy on an empty directory (%s)\n",
+                   directory);
+        return -EINVAL;
+    }
+
+    int fd = open(directory, O_DIRECTORY);
+    if (fd == -1) {
+        KLOG_ERROR(TAG, "Failed to open directory (%s)\n", directory);
+        return -EINVAL;
+    }
+
+    ext4_encryption_policy eep;
+    eep.version = 0;
+    eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
+    eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
+    eep.flags = 0;
+    memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
+    ret = ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep);
+    auto preserve_errno = errno;
+    close(fd);
+
+    if (ret) {
+        KLOG_ERROR(TAG, "Failed to set encryption policy for %s: %s\n",
+                   directory, strerror(preserve_errno));
+        return -EINVAL;
+    }
+
+    KLOG_INFO(TAG, "Encryption policy for %s is set to %02x%02x%02x%02x\n",
+              directory, policy[0], policy[1], policy[2], policy[3]);
+    return 0;
 }
 
 bool e4crypt_non_default_key(const char* dir)
 {
-    int type = e4crypt_get_password_type(dir);
-
-    // ext4enc:TODO Use consts, not 1 here
-    return type != -1 && type != 1;
-}
-
-int e4crypt_get_password_type(const char* path)
-{
-    UnencryptedProperties props(path);
-    if (props.Get<std::string>(properties::key).empty()) {
-        KLOG_INFO(TAG, "No master key, so not ext4enc\n");
-        return -1;
-    }
-
-    return props.Get<int>(properties::type, 1);
-}
-
-int e4crypt_change_password(const char* path, int crypt_type,
-                            const char* password)
-{
-    // ext4enc:TODO Encrypt master key with password securely. Store hash of
-    // master key for validation
-    UnencryptedProperties props(path);
-    if (   props.Set(properties::password, password)
-        && props.Set(properties::type, crypt_type))
-        return 0;
-    return -1;
-}
-
-int e4crypt_crypto_complete(const char* path)
-{
-    KLOG_INFO(TAG, "ext4 crypto complete called on %s\n", path);
-    if (UnencryptedProperties(path).Get<std::string>(properties::key).empty()) {
-        KLOG_INFO(TAG, "No master key, so not ext4enc\n");
-        return -1;
-    }
-
-    return 0;
-}
-
-int e4crypt_check_passwd(const char* path, const char* password)
-{
-    UnencryptedProperties props(path);
-    if (props.Get<std::string>(properties::key).empty()) {
-        KLOG_INFO(TAG, "No master key, so not ext4enc\n");
-        return -1;
-    }
-
-    auto actual_password = props.Get<std::string>(properties::password);
-
-    if (actual_password == password) {
-        s_password_store[path] = password;
-        return 0;
-    } else {
-        return -1;
-    }
-}
-
-int e4crypt_restart(const char* path)
-{
-    int rc = 0;
-
-    KLOG_INFO(TAG, "ext4 restart called on %s\n", path);
-    property_set("vold.decrypt", "trigger_reset_main");
-    KLOG_INFO(TAG, "Just asked init to shut down class main\n");
-    sleep(2);
-
-    std::string tmp_path = std::string() + path + "/tmp_mnt";
-
-    // ext4enc:TODO add retry logic
-    rc = umount(tmp_path.c_str());
-    if (rc) {
-        KLOG_ERROR(TAG, "umount %s failed with rc %d, msg %s\n",
-                   tmp_path.c_str(), rc, strerror(errno));
-        return rc;
-    }
-
-    // ext4enc:TODO add retry logic
-    rc = umount(path);
-    if (rc) {
-        KLOG_ERROR(TAG, "umount %s failed with rc %d, msg %s\n",
-                   path, rc, strerror(errno));
-        return rc;
-    }
-
-    return 0;
-}
-
-const char* e4crypt_get_password(const char* path)
-{
-    // ext4enc:TODO scrub password after timeout
-    auto i = s_password_store.find(path);
-    if (i == s_password_store.end()) {
-        return 0;
-    } else {
-        return i->second.c_str();
-    }
+    UnencryptedProperties props(dir);
+    return props.Get<int>(properties::is_default, 1) != 1;
 }
diff --git a/ext4_utils/ext4_crypt.h b/ext4_utils/ext4_crypt.h
deleted file mode 100644
index cc69273..0000000
--- a/ext4_utils/ext4_crypt.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#include <stdbool.h>
-#include <sys/cdefs.h>
-#include <sys/types.h>
-
-__BEGIN_DECLS
-// These functions assume they are being called from init
-// They will not operate properly outside of init
-int e4crypt_install_keyring();
-int e4crypt_install_key(const char* dir);
-int e4crypt_create_device_key(const char* dir,
-                              int ensure_dir_exists(const char* dir));
-
-// General functions
-bool e4crypt_non_default_key(const char* dir);
-int e4crypt_set_directory_policy(const char* dir);
-int e4crypt_main(int argc, char* argv[]);
-int e4crypt_change_password(const char* path, int crypt_type,
-                            const char* password);
-int e4crypt_get_password_type(const char* path);
-int e4crypt_crypto_complete(const char* dir);
-int e4crypt_check_passwd(const char* dir, const char* password);
-const char* e4crypt_get_password(const char* dir);
-int e4crypt_restart(const char* dir);
-
-// Key functions. ext4enc:TODO Move to own file
-
-// ext4enc:TODO - get these keyring standard definitions from proper system file
-// keyring serial number type
-typedef int32_t key_serial_t;
-
-// special process keyring shortcut IDs
-#define KEY_SPEC_THREAD_KEYRING       -1 // key ID for thread-specific keyring
-#define KEY_SPEC_PROCESS_KEYRING      -2 // key ID for process-specific keyring
-#define KEY_SPEC_SESSION_KEYRING      -3 // key ID for session-specific keyring
-#define KEY_SPEC_USER_KEYRING         -4 // key ID for UID-specific keyring
-#define KEY_SPEC_USER_SESSION_KEYRING -5 // key ID for UID-session keyring
-#define KEY_SPEC_GROUP_KEYRING        -6 // key ID for GID-specific keyring
-
-key_serial_t add_key(const char *type,
-                     const char *description,
-                     const void *payload,
-                     size_t plen,
-                     key_serial_t ringid);
-
-long keyctl_setperm(key_serial_t id, int permissions);
-
-// Set policy on directory
-int do_policy_set(const char *directory, const char *policy);
-
-__END_DECLS
diff --git a/ext4_utils/ext4_crypt_init_extensions.cpp b/ext4_utils/ext4_crypt_init_extensions.cpp
index 1585911..7ca471a 100644
--- a/ext4_utils/ext4_crypt_init_extensions.cpp
+++ b/ext4_utils/ext4_crypt_init_extensions.cpp
@@ -1,12 +1,10 @@
 #define TAG "ext4_utils"
 
-#include "ext4_crypt.h"
+#include "ext4_crypt_init_extensions.h"
 
 #include <string>
-#include <fstream>
-#include <iomanip>
-#include <sstream>
 
+#include <dirent.h>
 #include <errno.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
@@ -14,29 +12,28 @@
 #include <cutils/klog.h>
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
+#include <poll.h>
 
+#include "key_control.h"
 #include "unencrypted_properties.h"
 
-// ext4enc:TODO Include structure from somewhere sensible
-// MUST be in sync with ext4_crypto.c in kernel
-#define EXT4_MAX_KEY_SIZE 76
-struct ext4_encryption_key {
-        uint32_t mode;
-        char raw[EXT4_MAX_KEY_SIZE];
-        uint32_t size;
-};
-
-static const std::string keyring = "@s";
 static const std::string arbitrary_sequence_number = "42";
-
-static key_serial_t device_keyring = -1;
+static const int vold_command_timeout_ms = 60 * 1000;
 
 static std::string vold_command(std::string const& command)
 {
     KLOG_INFO(TAG, "Running command %s\n", command.c_str());
-    int sock = socket_local_client("vold",
+    int sock = -1;
+
+    while (true) {
+        sock = socket_local_client("vold",
                                    ANDROID_SOCKET_NAMESPACE_RESERVED,
                                    SOCK_STREAM);
+        if (sock >= 0) {
+            break;
+        }
+        usleep(10000);
+    }
 
     if (sock < 0) {
         KLOG_INFO(TAG, "Cannot open vold, failing command\n");
@@ -61,94 +58,68 @@
         return "";
     }
 
-    while (1) {
-        struct timeval to;
-        to.tv_sec = 10;
-        to.tv_usec = 0;
+    struct pollfd poll_sock = {sock, POLLIN, 0};
 
-        fd_set read_fds;
-        FD_ZERO(&read_fds);
-        FD_SET(sock, &read_fds);
-
-        int rc = select(sock + 1, &read_fds, NULL, NULL, &to);
-        if (rc < 0) {
-            KLOG_ERROR(TAG, "Error in select %s\n", strerror(errno));
-            return "";
-        } else if (!rc) {
-            KLOG_ERROR(TAG, "Timeout\n");
-            return "";
-        } else if (FD_ISSET(sock, &read_fds)) {
-            char buffer[4096];
-            memset(buffer, 0, sizeof(buffer));
-            rc = read(sock, buffer, sizeof(buffer));
-            if (rc <= 0) {
-                if (rc == 0) {
-                    KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
-                } else {
-                    KLOG_ERROR(TAG, "Error reading data (%s)\n", strerror(errno));
-                }
-                return "";
-            }
-
-            // We don't truly know that this is the correct result. However,
-            // since this will only be used when the framework is down,
-            // it should be OK unless someone is running vdc at the same time.
-            // Worst case we force a reboot in the very rare synchronization
-            // error
-            return std::string(buffer, rc);
-        }
+    int rc = poll(&poll_sock, 1, vold_command_timeout_ms);
+    if (rc < 0) {
+        KLOG_ERROR(TAG, "Error in poll %s\n", strerror(errno));
+        return "";
     }
+    if (!(poll_sock.revents & POLLIN)) {
+        KLOG_ERROR(TAG, "Timeout\n");
+        return "";
+    }
+    char buffer[4096];
+    memset(buffer, 0, sizeof(buffer));
+    rc = read(sock, buffer, sizeof(buffer));
+    if (rc <= 0) {
+        if (rc == 0) {
+            KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
+        } else {
+            KLOG_ERROR(TAG, "Error reading data (%s)\n", strerror(errno));
+        }
+        return "";
+    }
+
+    // We don't truly know that this is the correct result. However,
+    // since this will only be used when the framework is down,
+    // it should be OK unless someone is running vdc at the same time.
+    // Worst case we force a reboot in the very rare synchronization
+    // error
+    return std::string(buffer, rc);
 }
 
 int e4crypt_create_device_key(const char* dir,
                               int ensure_dir_exists(const char*))
 {
+    // Already encrypted with password? If so bail
+    std::string temp_folder = std::string() + dir + "/tmp_mnt";
+    DIR* temp_dir = opendir(temp_folder.c_str());
+    if (temp_dir) {
+        closedir(temp_dir);
+        return 0;
+    }
+
     // Make sure folder exists. Use make_dir to set selinux permissions.
-    KLOG_INFO(TAG, "Creating test device key\n");
-    UnencryptedProperties props(dir);
-    if (ensure_dir_exists(props.GetPath().c_str())) {
+    if (ensure_dir_exists(UnencryptedProperties::GetPath(dir).c_str())) {
         KLOG_ERROR(TAG, "Failed to create %s with error %s\n",
-                   props.GetPath().c_str(), strerror(errno));
+                   UnencryptedProperties::GetPath(dir).c_str(),
+                   strerror(errno));
         return -1;
     }
 
-    if (props.Get<std::string>(properties::key).empty()) {
-        // Create new key since it doesn't already exist
-        std::ifstream urandom("/dev/urandom", std::ifstream::binary);
-        if (!urandom) {
-            KLOG_ERROR(TAG, "Failed to open /dev/urandom\n");
-            return -1;
-        }
-
-        // ext4enc:TODO Don't hardcode 32
-        std::string key_material(32, '\0');
-        urandom.read(&key_material[0], key_material.length());
-        if (!urandom) {
-            KLOG_ERROR(TAG, "Failed to read random bytes\n");
-            return -1;
-        }
-
-        if (!props.Set(properties::key, key_material)) {
-            KLOG_ERROR(TAG, "Failed to write key material\n");
-            return -1;
-        }
-    }
-
-    if (!props.Remove(properties::ref)) {
-        KLOG_ERROR(TAG, "Failed to remove key ref\n");
-        return -1;
-    }
+    auto result = vold_command("cryptfs enablefilecrypto");
+    // ext4enc:TODO proper error handling
+    KLOG_INFO(TAG, "enablefilecrypto returned with result %s\n",
+              result.c_str());
 
     return 0;
 }
 
 int e4crypt_install_keyring()
 {
-    device_keyring = add_key("keyring",
-                             "e4crypt",
-                             0,
-                             0,
-                             KEY_SPEC_SESSION_KEYRING);
+    key_serial_t device_keyring = add_key("keyring", "e4crypt", 0, 0,
+                                          KEY_SPEC_SESSION_KEYRING);
 
     if (device_keyring == -1) {
         KLOG_ERROR(TAG, "Failed to create keyring\n");
@@ -158,90 +129,6 @@
     KLOG_INFO(TAG, "Keyring created wth id %d in process %d\n",
               device_keyring, getpid());
 
-    // ext4enc:TODO set correct permissions
-    long result = keyctl_setperm(device_keyring, 0x3f3f3f3f);
-    if (result) {
-        KLOG_ERROR(TAG, "KEYCTL_SETPERM failed with error %ld\n", result);
-        return -1;
-    }
-
-    return 0;
-}
-
-int e4crypt_install_key(const char* dir)
-{
-    UnencryptedProperties props(dir);
-    auto key = props.Get<std::string>(properties::key);
-
-    // Get password to decrypt as needed
-    if (e4crypt_non_default_key(dir)) {
-        std::string result = vold_command("cryptfs getpw");
-        // result is either
-        // 200 0 -1
-        // or
-        // 200 0 {{sensitive}} 0001020304
-        // where 0001020304 is hex encoding of password
-        std::istringstream i(result);
-        std::string bit;
-        i >> bit;
-        if (bit != "200") {
-            KLOG_ERROR(TAG, "Expecting 200\n");
-            return -1;
-        }
-
-        i >> bit;
-        if (bit != arbitrary_sequence_number) {
-            KLOG_ERROR(TAG, "Expecting %s\n", arbitrary_sequence_number.c_str());
-            return -1;
-        }
-
-        i >> bit;
-        if (bit != "{{sensitive}}") {
-            KLOG_INFO(TAG, "Not encrypted\n");
-            return -1;
-        }
-
-        i >> bit;
-    }
-
-    // Add key to keyring
-    ext4_encryption_key ext4_key = {0, {0}, 0};
-    if (key.length() > sizeof(ext4_key.raw)) {
-        KLOG_ERROR(TAG, "Key too long\n");
-        return -1;
-    }
-
-    ext4_key.mode = 0;
-    memcpy(ext4_key.raw, &key[0], key.length());
-    ext4_key.size = key.length();
-
-    // ext4enc:TODO Use better reference not 1234567890
-    key_serial_t key_id = add_key("logon", "ext4-key:1234567890",
-                                  (void*)&ext4_key, sizeof(ext4_key),
-                                  device_keyring);
-
-    if (key_id == -1) {
-        KLOG_ERROR(TAG, "Failed to insert key into keyring with error %s\n",
-                   strerror(errno));
-        return -1;
-    }
-
-    KLOG_INFO(TAG, "Added key %d to keyring %d in process %d\n",
-              key_id, device_keyring, getpid());
-
-    // ext4enc:TODO set correct permissions
-    long result = keyctl_setperm(key_id, 0x3f3f3f3f);
-    if (result) {
-        KLOG_ERROR(TAG, "KEYCTL_SETPERM failed with error %ld\n", result);
-        return -1;
-    }
-
-    // Save reference to key so we can set policy later
-    if (!props.Set(properties::ref, "ext4-key:1234567890")) {
-        KLOG_ERROR(TAG, "Cannot save key reference\n");
-        return -1;
-    }
-
     return 0;
 }
 
@@ -254,14 +141,23 @@
     if (!dir || strncmp(dir, "/data/", 6) || strchr(dir + 6, '/')) {
         return 0;
     }
+    // ext4enc:TODO exclude /data/user with a horrible special case.
+    if (!strcmp(dir, "/data/user")) {
+        return 0;
+    }
 
     UnencryptedProperties props("/data");
-    std::string ref = props.Get<std::string>(properties::ref);
-    std::string policy = keyring + "." + ref;
-    KLOG_INFO(TAG, "Setting policy %s\n", policy.c_str());
-    int result = do_policy_set(dir, policy.c_str());
+    std::string policy = props.Get<std::string>(properties::ref);
+    if (policy.empty()) {
+        // ext4enc:TODO why is this OK?
+        return 0;
+    }
+
+    KLOG_INFO(TAG, "Setting policy on %s\n", dir);
+    int result = do_policy_set(dir, policy.c_str(), policy.size());
     if (result) {
-        KLOG_ERROR(TAG, "Setting policy on %s failed!\n", dir);
+        KLOG_ERROR(TAG, "Setting %s policy on %s failed!\n",
+                   policy.c_str(), dir);
         return -1;
     }
 
diff --git a/ext4_utils/ext4_crypt_init_extensions.h b/ext4_utils/ext4_crypt_init_extensions.h
new file mode 100644
index 0000000..7931124
--- /dev/null
+++ b/ext4_utils/ext4_crypt_init_extensions.h
@@ -0,0 +1,15 @@
+#include <sys/cdefs.h>
+#include <stdbool.h>
+
+__BEGIN_DECLS
+
+// These functions assume they are being called from init
+// They will not operate properly outside of init
+int e4crypt_install_keyring();
+int e4crypt_create_device_key(const char* path,
+                              int ensure_dir_exists(const char* dir));
+int e4crypt_set_directory_policy(const char* path);
+bool e4crypt_non_default_key(const char* path);
+int do_policy_set(const char *directory, const char *policy, int policy_length);
+
+__END_DECLS
diff --git a/ext4_utils/ext4_sb.h b/ext4_utils/ext4_sb.h
index 832fa33..159580d 100644
--- a/ext4_utils/ext4_sb.h
+++ b/ext4_utils/ext4_sb.h
@@ -25,6 +25,8 @@
 extern "C" {
 #endif
 
+#include <stdbool.h>
+
 struct fs_info {
 	int64_t len;	/* If set to 0, ask the block device for the size,
 			 * if less than 0, reserve that much space at the
@@ -41,6 +43,7 @@
 	uint32_t bg_desc_reserve_blocks;
 	const char *label;
 	uint8_t no_journal;
+	bool block_device;	/* target fd is a block device? */
 };
 
 int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info);
diff --git a/ext4_utils/ext4_utils.c b/ext4_utils/ext4_utils.c
index ad0491f..29cbc72 100644
--- a/ext4_utils/ext4_utils.c
+++ b/ext4_utils/ext4_utils.c
@@ -15,12 +15,15 @@
  */
 
 #include "ext4_utils.h"
-#include "uuid.h"
 #include "allocate.h"
 #include "indirect.h"
 #include "extent.h"
+#include "sha1.h"
 
 #include <sparse/sparse.h>
+#ifdef REAL_UUID
+#include <uuid.h>
+#endif
 
 #include <fcntl.h>
 #include <inttypes.h>
@@ -49,6 +52,44 @@
 
 jmp_buf setjmp_env;
 
+/* Definition from RFC-4122 */
+struct uuid {
+    u32 time_low;
+    u16 time_mid;
+    u16 time_hi_and_version;
+    u8 clk_seq_hi_res;
+    u8 clk_seq_low;
+    u16 node0_1;
+    u32 node2_5;
+};
+
+static void sha1_hash(const char *namespace, const char *name,
+    unsigned char sha1[SHA1_DIGEST_LENGTH])
+{
+    SHA1_CTX ctx;
+    SHA1Init(&ctx);
+    SHA1Update(&ctx, (const u8*)namespace, strlen(namespace));
+    SHA1Update(&ctx, (const u8*)name, strlen(name));
+    SHA1Final(sha1, &ctx);
+}
+
+static void generate_sha1_uuid(const char *namespace, const char *name, u8 result[16])
+{
+    unsigned char sha1[SHA1_DIGEST_LENGTH];
+    struct uuid *uuid = (struct uuid *)result;
+
+    sha1_hash(namespace, name, (unsigned char*)sha1);
+    memcpy(uuid, sha1, sizeof(struct uuid));
+
+    uuid->time_low = ntohl(uuid->time_low);
+    uuid->time_mid = ntohs(uuid->time_mid);
+    uuid->time_hi_and_version = ntohs(uuid->time_hi_and_version);
+    uuid->time_hi_and_version &= 0x0FFF;
+    uuid->time_hi_and_version |= (5 << 12);
+    uuid->clk_seq_hi_res &= ~(1 << 6);
+    uuid->clk_seq_hi_res |= 1 << 7;
+}
+
 /* returns 1 if a is a power of b */
 static int is_power_of(int a, int b)
 {
@@ -127,10 +168,31 @@
 		critical_error("failed to write all of superblock");
 }
 
+static void block_device_write_sb(int fd)
+{
+	unsigned long long offset;
+	u32 i;
+
+	/* write out the backup superblocks */
+	for (i = 1; i < aux_info.groups; i++) {
+		if (ext4_bg_has_super_block(i)) {
+			offset = info.block_size * (aux_info.first_data_block
+				+ i * info.blocks_per_group);
+			write_sb(fd, offset, aux_info.backup_sb[i]);
+		}
+	}
+
+	/* write out the primary superblock */
+	write_sb(fd, 1024, aux_info.sb);
+}
+
 /* Write the filesystem image to a file */
 void write_ext4_image(int fd, int gz, int sparse, int crc)
 {
 	sparse_file_write(ext4_sparse_file, fd, gz, sparse, crc);
+
+	if (info.block_device)
+		block_device_write_sb(fd);
 }
 
 /* Compute the rest of the parameters of the filesystem from the basic info */
@@ -162,7 +224,27 @@
 		aux_info.len_blocks -= last_group_size;
 	}
 
-	aux_info.sb = calloc(info.block_size, 1);
+	/* A zero-filled superblock to be written firstly to the block
+	 * device to mark the file-system as invalid
+	 */
+	aux_info.sb_zero = calloc(1, info.block_size);
+	if (!aux_info.sb_zero)
+		critical_error_errno("calloc");
+
+	/* The write_data* functions expect only block aligned calls.
+	 * This is not an issue, except when we write out the super
+	 * block on a system with a block size > 1K.  So, we need to
+	 * deal with that here.
+	 */
+	aux_info.sb_block = calloc(1, info.block_size);
+	if (!aux_info.sb_block)
+		critical_error_errno("calloc");
+
+	if (info.block_size > 1024)
+		aux_info.sb = (struct ext4_super_block *)((char *)aux_info.sb_block + 1024);
+	else
+		aux_info.sb = aux_info.sb_block;
+
 	/* Alloc an array to hold the pointers to the backup superblocks */
 	aux_info.backup_sb = calloc(aux_info.groups, sizeof(char *));
 
@@ -183,12 +265,13 @@
 		if (aux_info.backup_sb[i])
 			free(aux_info.backup_sb[i]);
 	}
-	free(aux_info.sb);
+	free(aux_info.sb_block);
+	free(aux_info.sb_zero);
 	free(aux_info.bg_desc);
 }
 
 /* Fill in the superblock memory buffer based on the filesystem parameters */
-void ext4_fill_in_sb()
+void ext4_fill_in_sb(int real_uuid)
 {
 	unsigned int i;
 	struct ext4_super_block *sb = aux_info.sb;
@@ -225,7 +308,16 @@
 	sb->s_feature_compat = info.feat_compat;
 	sb->s_feature_incompat = info.feat_incompat;
 	sb->s_feature_ro_compat = info.feat_ro_compat;
-	generate_uuid("extandroid/make_ext4fs", info.label, sb->s_uuid);
+	if (real_uuid == 1) {
+#ifdef REAL_UUID
+	    uuid_generate(sb->s_uuid);
+#else
+	    fprintf(stderr, "Not compiled with real UUID support\n");
+	    abort();
+#endif
+	} else {
+	    generate_sha1_uuid("extandroid/make_ext4fs", info.label, sb->s_uuid);
+	}
 	memset(sb->s_volume_name, 0, sizeof(sb->s_volume_name));
 	strncpy(sb->s_volume_name, info.label, sizeof(sb->s_volume_name));
 	memset(sb->s_last_mounted, 0, sizeof(sb->s_last_mounted));
@@ -271,11 +363,11 @@
 		if (ext4_bg_has_super_block(i)) {
 			if (i != 0) {
 				aux_info.backup_sb[i] = calloc(info.block_size, 1);
-				memcpy(aux_info.backup_sb[i], sb, info.block_size);
+				memcpy(aux_info.backup_sb[i], sb, sizeof(struct ext4_super_block));
 				/* Update the block group nr of this backup superblock */
 				aux_info.backup_sb[i]->s_block_group_nr = i;
-				sparse_file_add_data(ext4_sparse_file, aux_info.backup_sb[i],
-						info.block_size, group_start_block);
+				ext4_queue_sb(group_start_block, info.block_device ?
+						aux_info.sb_zero : aux_info.backup_sb[i]);
 			}
 			sparse_file_add_data(ext4_sparse_file, aux_info.bg_desc,
 				aux_info.bg_desc_blocks * info.block_size,
@@ -291,22 +383,23 @@
 		aux_info.bg_desc[i].bg_free_inodes_count = sb->s_inodes_per_group;
 		aux_info.bg_desc[i].bg_used_dirs_count = 0;
 	}
+
+	/* Queue the primary superblock to be written out - if it's a block device,
+	 * queue a zero-filled block first, the correct version of superblock will
+	 * be written to the block device after all other blocks are written.
+	 *
+	 * The file-system on the block device will not be valid until the correct
+	 * version of superblocks are written, this is to avoid the likelihood of a
+	 * partially created file-system.
+	 */
+	ext4_queue_sb(aux_info.first_data_block, info.block_device ?
+				aux_info.sb_zero : aux_info.sb_block);
 }
 
-void ext4_queue_sb(void)
+
+void ext4_queue_sb(u64 start_block, struct ext4_super_block *sb)
 {
-	/* The write_data* functions expect only block aligned calls.
-	 * This is not an issue, except when we write out the super
-	 * block on a system with a block size > 1K.  So, we need to
-	 * deal with that here.
-	 */
-	if (info.block_size > 1024) {
-		u8 *buf = calloc(info.block_size, 1);
-		memcpy(buf + 1024, (u8*)aux_info.sb, 1024);
-		sparse_file_add_data(ext4_sparse_file, buf, info.block_size, 0);
-	} else {
-		sparse_file_add_data(ext4_sparse_file, aux_info.sb, 1024, 1);
-	}
+	sparse_file_add_data(ext4_sparse_file, sb, info.block_size, start_block);
 }
 
 void ext4_parse_sb_info(struct ext4_super_block *sb)
diff --git a/ext4_utils/ext4_utils.h b/ext4_utils/ext4_utils.h
index 499753f..8cc8c2c 100644
--- a/ext4_utils/ext4_utils.h
+++ b/ext4_utils/ext4_utils.h
@@ -99,6 +99,8 @@
 
 struct fs_aux_info {
 	struct ext4_super_block *sb;
+	struct ext4_super_block *sb_block;
+	struct ext4_super_block *sb_zero;
 	struct ext4_super_block **backup_sb;
 	struct ext2_group_desc *bg_desc;
 	struct block_group_info *bgs;
@@ -138,11 +140,11 @@
 void write_ext4_image(int fd, int gz, int sparse, int crc);
 void ext4_create_fs_aux_info(void);
 void ext4_free_fs_aux_info(void);
-void ext4_fill_in_sb(void);
+void ext4_fill_in_sb(int real_uuid);
 void ext4_create_resize_inode(void);
 void ext4_create_journal_inode(void);
 void ext4_update_free(void);
-void ext4_queue_sb(void);
+void ext4_queue_sb(u64 start_block, struct ext4_super_block *sb);
 u64 get_block_device_size(int fd);
 int is_block_device_fd(int fd);
 u64 get_file_size(int fd);
@@ -157,7 +159,7 @@
 
 int make_ext4fs_internal(int fd, const char *directory,
 						 const char *mountpoint, fs_config_func_t fs_config_func, int gzip,
-						 int sparse, int crc, int wipe,
+						 int sparse, int crc, int wipe, int real_uuid,
 						 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
 						 FILE* block_list_file);
 
diff --git a/ext4_utils/key_control.cpp b/ext4_utils/key_control.cpp
new file mode 100644
index 0000000..3d775b7
--- /dev/null
+++ b/ext4_utils/key_control.cpp
@@ -0,0 +1,44 @@
+#include "key_control.h"
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+/* keyring keyctl commands */
+#define KEYCTL_SETPERM        5 /* set permissions for a key in a keyring */
+#define KEYCTL_UNLINK         9 /* unlink a key from a keyring */
+#define KEYCTL_SEARCH        10 /* search for a key in a keyring */
+
+static long keyctl(int cmd, ...)
+{
+    va_list va;
+    unsigned long arg2, arg3, arg4, arg5;
+
+    va_start(va, cmd);
+    arg2 = va_arg(va, unsigned long);
+    arg3 = va_arg(va, unsigned long);
+    arg4 = va_arg(va, unsigned long);
+    arg5 = va_arg(va, unsigned long);
+    va_end(va);
+    return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
+}
+
+key_serial_t add_key(const char *type,
+                     const char *description,
+                     const void *payload,
+                     size_t plen,
+                     key_serial_t ringid)
+{
+    return syscall(__NR_add_key, type, description, payload, plen, ringid);
+}
+
+long keyctl_setperm(key_serial_t id, int permissions)
+{
+    return keyctl(KEYCTL_SETPERM, id, permissions);
+}
+
+long keyctl_search(key_serial_t ringid, const char *type,
+                   const char *description, key_serial_t destringid)
+{
+    return keyctl(KEYCTL_SEARCH, ringid, type, description, destringid);
+}
diff --git a/ext4_utils/key_control.h b/ext4_utils/key_control.h
new file mode 100644
index 0000000..8e6e32b
--- /dev/null
+++ b/ext4_utils/key_control.h
@@ -0,0 +1,29 @@
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+// ext4enc:TODO - get these keyring standard definitions from proper system file
+// keyring serial number type
+typedef int32_t key_serial_t;
+
+// special process keyring shortcut IDs
+#define KEY_SPEC_THREAD_KEYRING       -1 // key ID for thread-specific keyring
+#define KEY_SPEC_PROCESS_KEYRING      -2 // key ID for process-specific keyring
+#define KEY_SPEC_SESSION_KEYRING      -3 // key ID for session-specific keyring
+#define KEY_SPEC_USER_KEYRING         -4 // key ID for UID-specific keyring
+#define KEY_SPEC_USER_SESSION_KEYRING -5 // key ID for UID-session keyring
+#define KEY_SPEC_GROUP_KEYRING        -6 // key ID for GID-specific keyring
+
+key_serial_t add_key(const char *type,
+                     const char *description,
+                     const void *payload,
+                     size_t plen,
+                     key_serial_t ringid);
+
+long keyctl_setperm(key_serial_t id, int permissions);
+
+long keyctl_search(key_serial_t ringid, const char *type,
+                   const char *description, key_serial_t destringid);
+
+__END_DECLS
diff --git a/ext4_utils/make_ext4fs.c b/ext4_utils/make_ext4fs.c
index 62a3f1a..52c3208 100644
--- a/ext4_utils/make_ext4fs.c
+++ b/ext4_utils/make_ext4fs.c
@@ -18,7 +18,6 @@
 #include "ext4_utils.h"
 #include "allocate.h"
 #include "contents.h"
-#include "uuid.h"
 #include "wipe.h"
 
 #include <sparse/sparse.h>
@@ -62,7 +61,6 @@
 
 #include <selinux/selinux.h>
 #include <selinux/label.h>
-#include <selinux/android.h>
 
 #define O_BINARY 0
 
@@ -406,7 +404,7 @@
 	reset_ext4fs_info();
 	info.len = len;
 
-	return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, sehnd, 0, -1, NULL);
+	return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, 0, sehnd, 0, -1, NULL);
 }
 
 int make_ext4fs(const char *filename, long long len,
@@ -424,7 +422,7 @@
 		return EXIT_FAILURE;
 	}
 
-	status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, sehnd, 0, -1, NULL);
+	status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, 0, sehnd, 0, -1, NULL);
 	close(fd);
 
 	return status;
@@ -492,7 +490,7 @@
 
 int make_ext4fs_internal(int fd, const char *_directory,
 						 const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
-						 int sparse, int crc, int wipe,
+						 int sparse, int crc, int wipe, int real_uuid,
 						 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
 						 FILE* block_list_file)
 {
@@ -504,6 +502,13 @@
 	if (setjmp(setjmp_env))
 		return EXIT_FAILURE; /* Handle a call to longjmp() */
 
+	info.block_device = is_block_device_fd(fd);
+
+	if (info.block_device && (sparse || gzip || crc)) {
+		fprintf(stderr, "No sparse/gzip/crc allowed for block device\n");
+		return EXIT_FAILURE;
+	}
+
 	if (_mountpoint == NULL) {
 		mountpoint = strdup("");
 	} else {
@@ -585,7 +590,7 @@
 
 	block_allocator_init();
 
-	ext4_fill_in_sb();
+	ext4_fill_in_sb(real_uuid);
 
 	if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED)
 		error("failed to reserve first 10 inodes");
@@ -630,8 +635,6 @@
 
 	ext4_update_free();
 
-	ext4_queue_sb();
-
 	if (block_list_file) {
 		size_t dirlen = directory ? strlen(directory) : 0;
 		struct block_allocation* p = get_saved_allocation_chain();
diff --git a/ext4_utils/make_ext4fs_main.c b/ext4_utils/make_ext4fs_main.c
index a6c5f61..f28e1b2 100644
--- a/ext4_utils/make_ext4fs_main.c
+++ b/ext4_utils/make_ext4fs_main.c
@@ -32,7 +32,9 @@
 #ifndef USE_MINGW
 #include <selinux/selinux.h>
 #include <selinux/label.h>
+#if !defined(HOST)
 #include <selinux/android.h>
+#endif
 #else
 struct selabel_handle;
 #endif
@@ -52,7 +54,7 @@
 {
 	fprintf(stderr, "%s [ -l <len> ] [ -j <journal size> ] [ -b <block_size> ]\n", basename(path));
 	fprintf(stderr, "    [ -g <blocks per group> ] [ -i <inodes> ] [ -I <inode size> ]\n");
-	fprintf(stderr, "    [ -L <label> ] [ -f ] [ -a <android mountpoint> ]\n");
+	fprintf(stderr, "    [ -L <label> ] [ -f ] [ -a <android mountpoint> ] [ -u ]\n");
 	fprintf(stderr, "    [ -S file_contexts ] [ -C fs_config ] [ -T timestamp ]\n");
 	fprintf(stderr, "    [ -z | -s ] [ -w ] [ -c ] [ -J ] [ -v ] [ -B <block_list_file> ]\n");
 	fprintf(stderr, "    <filename> [<directory>]\n");
@@ -70,6 +72,7 @@
 	int sparse = 0;
 	int crc = 0;
 	int wipe = 0;
+	int real_uuid = 0;
 	int fd;
 	int exitcode;
 	int verbose = 0;
@@ -80,7 +83,7 @@
 	struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "" } };
 #endif
 
-	while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:fwzJsctv")) != -1) {
+	while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:fwzJsctvu")) != -1) {
 		switch (opt) {
 		case 'l':
 			info.len = parse_num(optarg);
@@ -118,6 +121,9 @@
 		case 'w':
 			wipe = 1;
 			break;
+		case 'u':
+			real_uuid = 1;
+			break;
 		case 'z':
 			gzip = 1;
 			break;
@@ -227,7 +233,7 @@
 	}
 
 	exitcode = make_ext4fs_internal(fd, directory, mountpoint, fs_config_func, gzip,
-		sparse, crc, wipe, sehnd, verbose, fixed_time, block_list_file);
+		sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time, block_list_file);
 	close(fd);
 	if (block_list_file)
 		fclose(block_list_file);
diff --git a/ext4_utils/unencrypted_properties.cpp b/ext4_utils/unencrypted_properties.cpp
index bef7c57..ed36e20 100644
--- a/ext4_utils/unencrypted_properties.cpp
+++ b/ext4_utils/unencrypted_properties.cpp
@@ -1,12 +1,13 @@
 #include "unencrypted_properties.h"
 
 #include <sys/stat.h>
+#include <dirent.h>
 
 namespace properties {
     const char* key = "key";
     const char* ref = "ref";
-    const char* type = "type";
-    const char* password = "password";
+    const char* props = "props";
+    const char* is_default = "is_default";
 }
 
 namespace
@@ -14,9 +15,20 @@
     const char* unencrypted_folder = "unencrypted";
 }
 
-UnencryptedProperties::UnencryptedProperties(const char* device)
-  : folder_(std::string() + device + "/" + unencrypted_folder)
+std::string UnencryptedProperties::GetPath(const char* device)
 {
+    return std::string() + device + "/" + unencrypted_folder;
+}
+
+UnencryptedProperties::UnencryptedProperties(const char* device)
+  : folder_(GetPath(device))
+{
+    DIR* dir = opendir(folder_.c_str());
+    if (dir) {
+        closedir(dir);
+    } else {
+        folder_.clear();
+    }
 }
 
 UnencryptedProperties::UnencryptedProperties()
@@ -24,7 +36,7 @@
 }
 
 template<> std::string UnencryptedProperties::Get(const char* name,
-                                      std::string default_value)
+                                      std::string default_value) const
 {
     if (!OK()) return default_value;
     std::ifstream i(folder_ + "/" + name, std::ios::binary);
@@ -56,22 +68,23 @@
     return !o.fail();
 }
 
-UnencryptedProperties UnencryptedProperties::GetChild(const char* name)
+UnencryptedProperties UnencryptedProperties::GetChild(const char* name) const
 {
-    UnencryptedProperties e4p;
-    if (!OK()) return e4p;
+    UnencryptedProperties up;
+    if (!OK()) return up;
 
     std::string directory(folder_ + "/" + name);
     if (mkdir(directory.c_str(), 700) == -1 && errno != EEXIST) {
-        return e4p;
+        return up;
     }
 
-    e4p.folder_ = directory;
-    return e4p;
+    up.folder_ = directory;
+    return up;
 }
 
 bool UnencryptedProperties::Remove(const char* name)
 {
+    if (!OK()) return false;
     if (remove((folder_ + "/" + name).c_str())
         && errno != ENOENT) {
         return false;
diff --git a/ext4_utils/unencrypted_properties.h b/ext4_utils/unencrypted_properties.h
index 80f41df..b2d1295 100644
--- a/ext4_utils/unencrypted_properties.h
+++ b/ext4_utils/unencrypted_properties.h
@@ -5,8 +5,8 @@
 namespace properties {
     extern const char* key;
     extern const char* ref;
-    extern const char* type;
-    extern const char* password;
+    extern const char* props;
+    extern const char* is_default;
 }
 
 /**
@@ -18,34 +18,38 @@
 class UnencryptedProperties
 {
 public:
+    // Get path of folder. Must create before using any properties
+    // This is to allow proper setting of SELinux policy
+    static std::string GetPath(const char* device);
+
     // Opens properties folder on named device.
-    // If folder does not exist, construction will succeed, but all
+    // If folder does not exist, OK will return false, all
     // getters will return default properties and setters will fail.
     UnencryptedProperties(const char* device);
 
     // Get named object. Return default if object does not exist or error.
-    template<typename t> t Get(const char* name, t default_value = t());
+    template<typename t> t Get(const char* name, t default_value = t()) const;
 
     // Set named object. Return true if success, false otherwise
     template<typename t> bool Set(const char* name, t const& value);
 
     // Get child properties
-    UnencryptedProperties GetChild(const char* name);
+    UnencryptedProperties GetChild(const char* name) const;
 
     // Remove named object
     bool Remove(const char* name);
 
-    // Get path of folder
-    std::string const& GetPath() const {return folder_;}
+    // Does folder exist?
+    bool OK() const;
+
 private:
     UnencryptedProperties();
-    bool OK() const;
     std::string folder_;
 };
 
 
 template<typename t> t UnencryptedProperties::Get(const char* name,
-                                                  t default_value)
+                                                  t default_value) const
 {
     if (!OK()) return default_value;
     t value = default_value;
@@ -64,7 +68,7 @@
 
 // Specialized getters/setters for strings
 template<> std::string UnencryptedProperties::Get(const char* name,
-                                      std::string default_value);
+                                      std::string default_value) const;
 
 template<> bool UnencryptedProperties::Set(const char* name,
                                            std::string const& value);
diff --git a/ext4_utils/uuid.c b/ext4_utils/uuid.c
deleted file mode 100644
index 33d2494..0000000
--- a/ext4_utils/uuid.c
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 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 <string.h>
-
-#ifdef USE_MINGW
-#include <winsock2.h>
-#else
-#include <arpa/inet.h>
-#endif
-
-#include "ext4_utils.h"
-#include "sha1.h"
-#include "uuid.h"
-
-/* Definition from RFC-4122 */
-struct uuid {
-	u32 time_low;
-	u16 time_mid;
-	u16 time_hi_and_version;
-	u8 clk_seq_hi_res;
-	u8 clk_seq_low;
-	u16 node0_1;
-	u32 node2_5;
-};
-
-static void sha1_hash(const char *namespace, const char *name,
-	unsigned char sha1[SHA1_DIGEST_LENGTH])
-{
-	SHA1_CTX ctx;
-	SHA1Init(&ctx);
-	SHA1Update(&ctx, (const u8*)namespace, strlen(namespace));
-	SHA1Update(&ctx, (const u8*)name, strlen(name));
-	SHA1Final(sha1, &ctx);
-}
-
-void generate_uuid(const char *namespace, const char *name, u8 result[16])
-{
-	unsigned char sha1[SHA1_DIGEST_LENGTH];
-	struct uuid *uuid = (struct uuid *)result;
-
-	sha1_hash(namespace, name, (unsigned char*)sha1);
-	memcpy(uuid, sha1, sizeof(struct uuid));
-
-	uuid->time_low = ntohl(uuid->time_low);
-	uuid->time_mid = ntohs(uuid->time_mid);
-	uuid->time_hi_and_version = ntohs(uuid->time_hi_and_version);
-	uuid->time_hi_and_version &= 0x0FFF;
-	uuid->time_hi_and_version |= (5 << 12);
-	uuid->clk_seq_hi_res &= ~(1 << 6);
-	uuid->clk_seq_hi_res |= 1 << 7;
-}
diff --git a/f2fs_utils/f2fs_ioutils.c b/f2fs_utils/f2fs_ioutils.c
index 53b7b4b..a050cf8 100644
--- a/f2fs_utils/f2fs_ioutils.c
+++ b/f2fs_utils/f2fs_ioutils.c
@@ -78,7 +78,6 @@
 
 #include <selinux/selinux.h>
 #include <selinux/label.h>
-#include <selinux/android.h>
 
 #define O_BINARY 0
 
@@ -148,6 +147,11 @@
 /*
  * IO interfaces
  */
+int dev_read_version(void *buf, __u64 offset, size_t len)
+{
+        return 0;
+}
+
 int dev_read(void  *buf, __u64 offset, size_t len)
 {
 	return 0;
diff --git a/libpagemap/pm_map.c b/libpagemap/pm_map.c
index a65d315..c6a1798 100644
--- a/libpagemap/pm_map.c
+++ b/libpagemap/pm_map.c
@@ -46,7 +46,8 @@
     for (i = 0; i < len; i++) {
         usage.vss += map->proc->ker->pagesize;
 
-        if (!PM_PAGEMAP_PRESENT(pagemap[i]))
+        if (!PM_PAGEMAP_PRESENT(pagemap[i]) &&
+	    !PM_PAGEMAP_SWAPPED(pagemap[i]))
             continue;
 
         if (!PM_PAGEMAP_SWAPPED(pagemap[i])) {
diff --git a/libpagemap/pm_process.c b/libpagemap/pm_process.c
index 50791ef..4d56428 100644
--- a/libpagemap/pm_process.c
+++ b/libpagemap/pm_process.c
@@ -258,12 +258,16 @@
     maps_count = 0; maps_size = INITIAL_MAPS;
 
     error = snprintf(filename, MAX_FILENAME, "/proc/%d/maps", proc->pid);
-    if (error < 0 || error >= MAX_FILENAME)
+    if (error < 0 || error >= MAX_FILENAME) {
+        free(maps);
         return (error < 0) ? (errno) : (-1);
+    }
 
     maps_f = fopen(filename, "r");
-    if (!maps_f)
+    if (!maps_f) {
+        free(maps);
         return errno;
+    }
 
     while (fgets(line, MAX_LINE, maps_f)) {
         if (maps_count >= maps_size) {
@@ -292,6 +296,7 @@
             for (; maps_count > 0; maps_count--)
                 pm_map_destroy(maps[maps_count]);
             free(maps);
+            fclose(maps_f);
             return error;
         }
         strcpy(map->name, name);
diff --git a/librank/librank.c b/librank/librank.c
index 2e3c3fc..28322b9 100644
--- a/librank/librank.c
+++ b/librank/librank.c
@@ -443,13 +443,20 @@
 }
 
 static int getprocname(pid_t pid, char *buf, size_t len) {
-    char filename[20];
+    char filename[32];
     FILE *f;
 
-    sprintf(filename, "/proc/%d/cmdline", pid);
+    snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid);
     f = fopen(filename, "r");
-    if (!f) { *buf = '\0'; return 1; }
-    if (!fgets(buf, len, f)) { *buf = '\0'; return 2; }
+    if (!f) {
+        *buf = '\0';
+        return 1;
+    }
+    if (!fgets(buf, len, f)) {
+        *buf = '\0';
+        fclose(f);
+        return 2;
+    }
     fclose(f);
     return 0;
 }
diff --git a/micro_bench/micro_bench.cpp b/micro_bench/micro_bench.cpp
index fecd707..b758779 100644
--- a/micro_bench/micro_bench.cpp
+++ b/micro_bench/micro_bench.cpp
@@ -432,6 +432,22 @@
     return 0;
 }
 
+int benchmarkMemmoveBackwards(const char *name, const command_data_t &cmd_data, void_func_t func) {
+    memcpy_func_t memmove_func = reinterpret_cast<memcpy_func_t>(func);
+
+    size_t size = cmd_data.args[0];
+    size_t alloc_size = size * 2 + 3 * cmd_data.dst_align;
+    uint8_t* src = allocateAlignedMemory(size, cmd_data.src_align, cmd_data.src_or_mask);
+    if (!src)
+        return -1;
+    // Force memmove to do a backwards copy by getting a pointer into the source buffer.
+    uint8_t* dst = getAlignedMemory(src+1, cmd_data.dst_align, cmd_data.dst_or_mask);
+    if (!dst)
+        return -1;
+    MAINLOOP_DATA(name, cmd_data, size, memmove_func(dst, src, size));
+    return 0;
+}
+
 int benchmarkMemread(const char *name, const command_data_t &cmd_data, void_func_t /*func*/) {
     int size = cmd_data.args[0];
 
@@ -578,6 +594,8 @@
     { "cpu", benchmarkCpu, NULL },
     { "memcpy", benchmarkMemcpy, reinterpret_cast<void_func_t>(memcpy) },
     { "memcpy_cold", benchmarkMemcpyCold, reinterpret_cast<void_func_t>(memcpy) },
+    { "memmove_forward", benchmarkMemcpy, reinterpret_cast<void_func_t>(memmove) },
+    { "memmove_backward", benchmarkMemmoveBackwards, reinterpret_cast<void_func_t>(memmove) },
     { "memread", benchmarkMemread, NULL },
     { "memset", benchmarkMemset, reinterpret_cast<void_func_t>(memset) },
     { "memset_cold", benchmarkMemsetCold, reinterpret_cast<void_func_t>(memset) },
diff --git a/perfprofd/Android.mk b/perfprofd/Android.mk
index 4ae7031..fd1d517 100644
--- a/perfprofd/Android.mk
+++ b/perfprofd/Android.mk
@@ -15,8 +15,11 @@
 LOCAL_CPP_EXTENSION := cc
 LOCAL_MODULE := libperfprofdcore
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
 proto_header_dir := $(call local-generated-sources-dir)/proto/$(LOCAL_PATH)
 LOCAL_C_INCLUDES += $(proto_header_dir) $(LOCAL_PATH)/quipper/kernel-headers
+LOCAL_STATIC_LIBRARIES := libbase
 LOCAL_EXPORT_C_INCLUDE_DIRS += $(proto_header_dir)
 LOCAL_SRC_FILES :=  \
 	perf_profile.proto \
@@ -26,6 +29,7 @@
 	quipper/perf_reader.cc \
 	quipper/perf_parser.cc \
 	perf_data_converter.cc \
+	cpuconfig.cc \
 	perfprofdcore.cc \
 
 LOCAL_CPPFLAGS += $(perfprofd_cppflags)
@@ -39,6 +43,8 @@
 LOCAL_CPP_EXTENSION := cc
 LOCAL_CXX_STL := libc++
 LOCAL_MODULE := libperfprofdutils
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
 LOCAL_CPPFLAGS += $(perfprofd_cppflags)
 LOCAL_SRC_FILES := perfprofdutils.cc
 include $(BUILD_STATIC_LIBRARY)
@@ -52,24 +58,16 @@
 LOCAL_CXX_STL := libc++
 LOCAL_SRC_FILES := perfprofdmain.cc
 LOCAL_STATIC_LIBRARIES := libperfprofdcore libperfprofdutils
-LOCAL_SHARED_LIBRARIES := liblog libprotobuf-cpp-full
+LOCAL_SHARED_LIBRARIES := liblog libprotobuf-cpp-lite libbase
 LOCAL_SYSTEM_SHARED_LIBRARIES := libc libstdc++
 LOCAL_CPPFLAGS += $(perfprofd_cppflags)
 LOCAL_CFLAGS := -Wall -Werror -std=gnu++11
 LOCAL_MODULE := perfprofd
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
 LOCAL_SHARED_LIBRARIES += libcutils
 include $(BUILD_EXECUTABLE)
 
-#
-# Config file (perfprofd.conf)
-#
-include $(CLEAR_VARS)
-LOCAL_MODULE := perfprofd.conf
-LOCAL_SRC_FILES := perfprofd.conf
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)/system/etc
-include $(BUILD_PREBUILT)
-
 # Clean temp vars
 perfprofd_cppflags :=
 proto_header_dir :=
diff --git a/perfprofd/cpuconfig.cc b/perfprofd/cpuconfig.cc
new file mode 100644
index 0000000..4b3cc36
--- /dev/null
+++ b/perfprofd/cpuconfig.cc
@@ -0,0 +1,105 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string>
+#include <sstream>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <cutils/properties.h>
+
+#include "cpuconfig.h"
+#include "perfprofdutils.h"
+
+#define SYSFSCPU "/sys/devices/system/cpu"
+
+HardwireCpuHelper::HardwireCpuHelper(bool perform)
+    : mpdecision_stopped_(false)
+{
+  if (perform && GetMpdecisionRunning()) {
+    mpdecision_stopped_ = true;
+    StopMpdecision();
+    int ncores = GetNumCores();
+    for (int i = 0; i < ncores; ++i) {
+      OnlineCore(i, 1);
+    }
+  }
+}
+
+HardwireCpuHelper::~HardwireCpuHelper()
+{
+  if (mpdecision_stopped_) {
+    RestartMpdecision();
+  }
+}
+
+bool HardwireCpuHelper::GetMpdecisionRunning()
+{
+  char propBuf[PROPERTY_VALUE_MAX];
+  property_get("init.svc.mpdecision", propBuf, "");
+  return strcmp(propBuf, "running") == 0;
+}
+
+
+int HardwireCpuHelper::GetNumCores()
+{
+  int ncores = -1;
+  std::string possible(SYSFSCPU "/possible");
+  FILE *fp = fopen(possible.c_str(), "re");
+  if (fp) {
+    unsigned lo = 0, hi = 0;
+    if (fscanf(fp, "%u-%u", &lo, &hi) == 2) {
+      ncores = hi - lo + 1;
+    }
+    fclose(fp);
+  }
+  return ncores;
+}
+
+void HardwireCpuHelper::OnlineCore(int i, int onoff)
+{
+  std::stringstream ss;
+  ss << SYSFSCPU "/cpu" << i << "/online";
+  FILE *fp = fopen(ss.str().c_str(), "we");
+  if (fp) {
+    fprintf(fp, onoff ? "1\n" : "0\n");
+    fclose(fp);
+  } else {
+    W_ALOGW("open failed for %s", ss.str().c_str());
+  }
+}
+
+void HardwireCpuHelper::StopMpdecision()
+{
+  if (property_set("ctl.stop", "mpdecision")) {
+    W_ALOGE("setprop ctl.stop mpdecision failed");
+  }
+}
+
+void HardwireCpuHelper::RestartMpdecision()
+{
+  // Don't try to offline the cores we previously onlined -- let
+  // mpdecision figure out what to do
+
+  if (property_set("ctl.start", "mpdecision")) {
+    W_ALOGE("setprop ctl.start mpdecision failed");
+  }
+}
diff --git a/perfprofd/cpuconfig.h b/perfprofd/cpuconfig.h
new file mode 100644
index 0000000..11a52f0
--- /dev/null
+++ b/perfprofd/cpuconfig.h
@@ -0,0 +1,50 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+//
+// Helper class to perform cpu setup (if needed) prior to a profile collection.
+//
+class HardwireCpuHelper {
+ public:
+
+  // The constructor for this class checks to see if the 'mpdecision'
+  // service is running; if so (and if 'perform' is TRUE), then it
+  // disables the service and on-lines all of the available cores/cpus
+  // (anything listed in /sys/devices/system/cpu/possible). The
+  // destructor will re-enable the mpdecision service if it was
+  // previously disabled.
+  HardwireCpuHelper(bool perform);
+  virtual ~HardwireCpuHelper();
+
+ private:
+  bool mpdecision_stopped_;
+
+  // Collect the number of available cpus/cores from /sys/devices/system/cpu/possible
+  int GetNumCores();
+
+  // Returns TRUE if the system service 'mpdecision' is running
+  bool GetMpdecisionRunning();
+
+  // Online/offline the specified cpu
+  void OnlineCore(int whichCore, int onoff);
+
+  // Enable/disable the mpdecision service via the equivalent of
+  //   setprop ctl.start mpdecision
+  //   setprop ctl.stop mpdecision
+  void StopMpdecision();
+  void RestartMpdecision();
+};
diff --git a/perfprofd/perf_profile.proto b/perfprofd/perf_profile.proto
index 0c3da01..3932a16 100644
--- a/perfprofd/perf_profile.proto
+++ b/perfprofd/perf_profile.proto
@@ -3,6 +3,8 @@
 
 option java_package = "com.google.common.logging";
 
+option optimize_for = LITE_RUNTIME;
+
 package wireless_android_play_playlog;
 
 // An entry of the map from a stack of addresses to count.
@@ -89,4 +91,12 @@
 
   // List of all load modules.
   repeated LoadModule load_modules = 4;
-}
\ No newline at end of file
+
+  // is device screen on at point when profile is collected?
+  optional bool display_on = 5;
+
+  // system load at point when profile is collected; corresponds
+  // to first value from /proc/loadavg multiplied by 100 then
+  // converted to int32
+  optional int32 sys_load_average = 6;
+}
diff --git a/perfprofd/perfprofd.conf b/perfprofd/perfprofd.conf
index 482beea..696c3de 100644
--- a/perfprofd/perfprofd.conf
+++ b/perfprofd/perfprofd.conf
@@ -5,7 +5,11 @@
 #
 # Destination directory for profiles
 #
-destination_directory=/data/data/com.google.android.gms/files
+destination_directory=/data/misc/perfprofd
+#
+# Config directory for perfprofd
+#
+config_directory=/data/data/com.google.android.gms/files
 #
 # Sampling period (for perf -c option)
 #
diff --git a/perfprofd/perfprofdcore.cc b/perfprofd/perfprofdcore.cc
index 62cff18..8f5b013 100644
--- a/perfprofd/perfprofdcore.cc
+++ b/perfprofd/perfprofdcore.cc
@@ -25,22 +25,28 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <time.h>
 #include <unistd.h>
 #include <string>
+#include <sstream>
 #include <map>
+#include <set>
 #include <cctype>
 
+#include <base/file.h>
+#include <base/stringprintf.h>
 #include <cutils/properties.h>
 
 #include "perfprofdcore.h"
 #include "perfprofdutils.h"
 #include "perf_data_converter.h"
+#include "cpuconfig.h"
 
 //
 // Perf profiling daemon -- collects system-wide profiles using
 //
-//       /system/bin/perf record -a
+//       simpleperf record -a
 //
 // and encodes them so that they can be uploaded by a separate service.
 //
@@ -48,8 +54,7 @@
 //......................................................................
 
 //
-// Output file from 'perf record'. The linux 'perf' tool by default
-// creates a file with this name.
+// Output file from 'perf record'.
 //
 #define PERF_OUTPUT "perf.data"
 
@@ -61,10 +66,8 @@
   // All systems go for profile collection.
   DO_COLLECT_PROFILE,
 
-  // The destination directory selected in the conf file doesn't exist. Most
-  // likely this is due to a missing or out-of-date version of the uploading
-  // service in GMS core.
-  DONT_PROFILE_MISSING_DESTINATION_DIR,
+  // The selected configuration directory doesn't exist.
+  DONT_PROFILE_MISSING_CONFIG_DIR,
 
   // Destination directory does not contain the semaphore file that
   // the perf profile uploading service creates when it determines
@@ -100,12 +103,8 @@
 //
 // Config file path. May be overridden with -c command line option
 //
-static const char *config_file_path = "/system/etc/perfprofd.conf";
-
-//
-// Set by SIGHUP signal handler
-//
-volatile unsigned please_reread_config_file = 0;
+static const char *config_file_path =
+    "/data/data/com.google.android.gms/files/perfprofd.conf";
 
 //
 // This table describes the config file syntax in terms of key/value pairs.
@@ -119,11 +118,11 @@
   ~ConfigReader();
 
   // Ask for the current setting of a config item
-  unsigned getUnsignedValue(const char *key);
-  std::string getStringValue(const char *key);
+  unsigned getUnsignedValue(const char *key) const;
+  std::string getStringValue(const char *key) const;
 
   // read the specified config file, applying any settings it contains
-  void readFile(const char *configFilePath);
+  void readFile(bool initial);
 
  private:
   void addUnsignedEntry(const char *key,
@@ -160,7 +159,7 @@
   // set to 100, then over time we want to see a perf profile
   // collected every 100 seconds). The actual time within the interval
   // for the collection is chosen randomly.
-  addUnsignedEntry("collection_interval", 901, 100, UINT32_MAX);
+  addUnsignedEntry("collection_interval", 14400, 100, UINT32_MAX);
 
   // Use the specified fixed seed for random number generation (unit
   // testing)
@@ -172,11 +171,13 @@
 
   // Destination directory (where to write profiles). This location
   // chosen since it is accessible to the uploader service.
-  addStringEntry("destination_directory",
-                 "/data/data/com.google.android.gms/files");
+  addStringEntry("destination_directory", "/data/misc/perfprofd");
 
-  // Path to 'perf' executable.
-  addStringEntry("perf_path", "/system/bin/perf");
+  // Config directory (where to read configs).
+  addStringEntry("config_directory", "/data/data/com.google.android.gms/files");
+
+  // Full path to 'perf' executable.
+  addStringEntry("perf_path", "/system/xbin/simpleperf");
 
   // Desired sampling period (passed to perf -c option). Small
   // sampling periods can perturb the collected profiles, so enforce
@@ -192,7 +193,20 @@
   // Currently defaults to 1 (true).
   addUnsignedEntry("only_debug_build", 1, 0, 1);
 
-  // If set to 1, pass the -g option when invoking perf (requests
+  // If the "mpdecision" service is running at the point we are ready
+  // to kick off a profiling run, then temporarily disable the service
+  // and hard-wire all cores on prior to the collection run, provided
+  // that the duration of the recording is less than or equal to the value of
+  // 'hardwire_cpus_max_duration'.
+  addUnsignedEntry("hardwire_cpus", 1, 0, 1);
+  addUnsignedEntry("hardwire_cpus_max_duration", 5, 1, UINT32_MAX);
+
+  // Maximum number of unprocessed profiles we can accumulate in the
+  // destination directory. Once we reach this limit, we continue
+  // to collect, but we just overwrite the most recent profile.
+  addUnsignedEntry("max_unprocessed_profiles", 10, 1, UINT32_MAX);
+
+  // If set to 1, pass the -g option when invoking 'perf' (requests
   // stack traces as opposed to flat profile).
   addUnsignedEntry("stack_profile", 0, 0, 1);
 
@@ -227,14 +241,14 @@
     W_ALOGE("internal error -- duplicate entry for key %s", key);
     exit(9);
   }
-  if (! default_value) {
+  if (default_value == nullptr) {
     W_ALOGE("internal error -- bad default value for key %s", key);
     exit(9);
   }
   s_entries[ks] = std::string(default_value);
 }
 
-unsigned ConfigReader::getUnsignedValue(const char *key)
+unsigned ConfigReader::getUnsignedValue(const char *key) const
 {
   std::string ks(key);
   auto it = u_entries.find(ks);
@@ -242,7 +256,7 @@
   return it->second;
 }
 
-std::string ConfigReader::getStringValue(const char *key)
+std::string ConfigReader::getStringValue(const char *key) const
 {
   std::string ks(key);
   auto it = s_entries.find(ks);
@@ -265,7 +279,7 @@
   auto uit = u_entries.find(key);
   if (uit != u_entries.end()) {
     unsigned uvalue = 0;
-    if (! isdigit(value[0]) || sscanf(value, "%u", &uvalue) != 1) {
+    if (isdigit(value[0]) == 0 || sscanf(value, "%u", &uvalue) != 1) {
       W_ALOGW("line %d: malformed unsigned value (ignored)", linecount);
     } else {
       values vals;
@@ -303,18 +317,20 @@
 {
   for (std::string::const_iterator it = line.begin(); it != line.end(); ++it)
   {
-    if (! isspace(*it)) {
+    if (isspace(*it) == 0) {
       return false;
     }
   }
   return true;
 }
 
-void ConfigReader::readFile(const char *configFilePath)
+void ConfigReader::readFile(bool initial)
 {
-  FILE *fp = fopen(configFilePath, "r");
+  FILE *fp = fopen(config_file_path, "r");
   if (!fp) {
-    W_ALOGE("unable to open configuration file %s", config_file_path);
+    if (initial) {
+      W_ALOGE("unable to open configuration file %s", config_file_path);
+    }
     return;
   }
 
@@ -384,11 +400,11 @@
 //
 const char *ckprofile_result_to_string(CKPROFILE_RESULT result)
 {
-  switch(result) {
+  switch (result) {
     case DO_COLLECT_PROFILE:
       return "DO_COLLECT_PROFILE";
-    case DONT_PROFILE_MISSING_DESTINATION_DIR:
-      return "missing destination directory";
+    case DONT_PROFILE_MISSING_CONFIG_DIR:
+      return "missing config directory";
     case DONT_PROFILE_MISSING_SEMAPHORE:
       return "missing semaphore file";
     case DONT_PROFILE_MISSING_PERF_EXECUTABLE:
@@ -424,30 +440,6 @@
 }
 
 //
-// The daemon does a read of the main config file on startup, however
-// if the destination directory also contains a configf file, then we
-// read parameters from that as well. This provides a mechanism for
-// changing/controlling the behavior of the daemon via the settings
-// established in the uploader service (which may be easier to update
-// than the daemon).
-//
-static void read_aux_config(ConfigReader &config)
-{
-  std::string destConfig(config.getStringValue("destination_directory"));
-  destConfig += "/perfprofd.conf";
-  FILE *fp = fopen(destConfig.c_str(), "r");
-  if (fp) {
-    fclose(fp);
-    bool trace_config_read =
-        (config.getUnsignedValue("trace_config_read") != 0);
-    if (trace_config_read) {
-      W_ALOGI("reading auxiliary config file %s", destConfig.c_str());
-    }
-    config.readFile(destConfig.c_str());
-  }
-}
-
-//
 // Check to see whether we should perform a profile collection
 //
 static CKPROFILE_RESULT check_profiling_enabled(ConfigReader &config)
@@ -461,53 +453,70 @@
   }
 
   //
-  // Check for the existence of the destination directory
+  // Check for existence of semaphore file in config directory
   //
-  std::string destdir = config.getStringValue("destination_directory");
-  DIR* dir = opendir(destdir.c_str());
-  if (!dir) {
-    W_ALOGW("unable to open destination directory %s: (%s)",
-            destdir.c_str(), strerror(errno));
-    return DONT_PROFILE_MISSING_DESTINATION_DIR;
+  if (access(config.getStringValue("config_directory").c_str(), F_OK) == -1) {
+    W_ALOGW("unable to open config directory %s: (%s)",
+            config.getStringValue("config_directory").c_str(), strerror(errno));
+    return DONT_PROFILE_MISSING_CONFIG_DIR;
   }
 
-  // Reread aux config file -- it may have changed
-  read_aux_config(config);
 
-  // Check for existence of perf executable
+  // Check for existence of semaphore file
+  std::string semaphore_filepath = config.getStringValue("config_directory")
+                                   + "/" + SEMAPHORE_FILENAME;
+  if (access(semaphore_filepath.c_str(), F_OK) == -1) {
+    return DONT_PROFILE_MISSING_SEMAPHORE;
+  }
+
+  // Check for existence of simpleperf/perf executable
   std::string pp = config.getStringValue("perf_path");
   if (access(pp.c_str(), R_OK|X_OK) == -1) {
     W_ALOGW("unable to access/execute %s", pp.c_str());
-    closedir(dir);
     return DONT_PROFILE_MISSING_PERF_EXECUTABLE;
   }
 
-  // Check for existence of semaphore file
-  unsigned found = 0;
-  struct dirent* e;
-  while ((e = readdir(dir)) != 0) {
-    if (!strcmp(e->d_name, SEMAPHORE_FILENAME)) {
-      found = 1;
-      break;
-    }
-  }
-  closedir(dir);
-  if (!found) {
-    return DONT_PROFILE_MISSING_SEMAPHORE;
-  }
-
   //
   // We are good to go
   //
   return DO_COLLECT_PROFILE;
 }
 
+static void annotate_encoded_perf_profile(wireless_android_play_playlog::AndroidPerfProfile *profile)
+{
+  //
+  // Load average as reported by the kernel
+  //
+  std::string load;
+  double fload = 0.0;
+  if (android::base::ReadFileToString("/proc/loadavg", &load) &&
+      sscanf(load.c_str(), "%lf", &fload) == 1) {
+    int iload = static_cast<int>(fload * 100.0);
+    profile->set_sys_load_average(iload);
+  } else {
+    W_ALOGE("Failed to read or scan /proc/loadavg (%s)", strerror(errno));
+  }
+
+  //
+  // Examine the contents of wake_unlock to determine whether the
+  // device display is on or off. NB: is this really the only way to
+  // determine this info?
+  //
+  std::string disp;
+  if (android::base::ReadFileToString("/sys/power/wake_unlock", &disp)) {
+    bool ison = (strstr(disp.c_str(), "PowerManagerService.Display") == 0);
+    profile->set_display_on(ison);
+  } else {
+    W_ALOGE("Failed to read /sys/power/wake_unlock (%s)", strerror(errno));
+  }
+}
+
 inline char* string_as_array(std::string* str) {
   return str->empty() ? NULL : &*str->begin();
 }
 
 PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
-                               const std::string &encoded_file_path)
+                               const char *encoded_file_path)
 {
   //
   // Open and read perf.data file
@@ -515,9 +524,6 @@
   const wireless_android_play_playlog::AndroidPerfProfile &encodedProfile =
       wireless_android_logging_awp::RawPerfDataToAndroidPerfProfile(data_file_path);
 
-  fprintf(stderr, "data file path is %s\n", data_file_path.c_str());
-  fprintf(stderr, "encoded file path is %s\n", encoded_file_path.c_str());
-
   //
   // Issue error if no samples
   //
@@ -525,6 +531,12 @@
     return ERR_PERF_ENCODE_FAILED;
   }
 
+  // All of the info in 'encodedProfile' is derived from the perf.data file;
+  // here we tack display status and system load.
+  wireless_android_play_playlog::AndroidPerfProfile &prof =
+      const_cast<wireless_android_play_playlog::AndroidPerfProfile&>
+      (encodedProfile);
+  annotate_encoded_perf_profile(&prof);
 
   //
   // Serialize protobuf to array
@@ -539,7 +551,7 @@
   //
   // Open file and write encoded data to it
   //
-  FILE *fp = fopen(encoded_file_path.c_str(), "w");
+  FILE *fp = fopen(encoded_file_path, "w");
   if (!fp) {
     return ERR_OPEN_ENCODED_FILE_FAILED;
   }
@@ -549,16 +561,194 @@
     return ERR_WRITE_ENCODED_FILE_FAILED;
   }
   fclose(fp);
+  chmod(encoded_file_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
 
   return OK_PROFILE_COLLECTION;
 }
 
 //
+// Invoke "perf record". Return value is OK_PROFILE_COLLECTION for
+// success, or some other error code if something went wrong.
+//
+static PROFILE_RESULT invoke_perf(const std::string &perf_path,
+                                  unsigned sampling_period,
+                                  const char *stack_profile_opt,
+                                  unsigned duration,
+                                  const std::string &data_file_path,
+                                  const std::string &perf_stderr_path)
+{
+  pid_t pid = fork();
+
+  if (pid == -1) {
+    return ERR_FORK_FAILED;
+  }
+
+  if (pid == 0) {
+    // child
+
+    // Open file to receive stderr/stdout from perf
+    FILE *efp = fopen(perf_stderr_path.c_str(), "w");
+    if (efp) {
+      dup2(fileno(efp), STDERR_FILENO);
+      dup2(fileno(efp), STDOUT_FILENO);
+    } else {
+      W_ALOGW("unable to open %s for writing", perf_stderr_path.c_str());
+    }
+
+    // marshall arguments
+    constexpr unsigned max_args = 12;
+    const char *argv[max_args];
+    unsigned slot = 0;
+    argv[slot++] = perf_path.c_str();
+    argv[slot++] = "record";
+
+    // -o perf.data
+    argv[slot++] = "-o";
+    argv[slot++] = data_file_path.c_str();
+
+    // -c N
+    argv[slot++] = "-c";
+    std::string p_str = android::base::StringPrintf("%u", sampling_period);
+    argv[slot++] = p_str.c_str();
+
+    // -g if desired
+    if (stack_profile_opt)
+      argv[slot++] = stack_profile_opt;
+
+    // system wide profiling
+    argv[slot++] = "-a";
+
+    // sleep <duration>
+    argv[slot++] = "/system/bin/sleep";
+    std::string d_str = android::base::StringPrintf("%u", duration);
+    argv[slot++] = d_str.c_str();
+
+    // terminator
+    argv[slot++] = nullptr;
+    assert(slot < max_args);
+
+    // record the final command line in the error output file for
+    // posterity/debugging purposes
+    fprintf(stderr, "perf invocation (pid=%d):\n", getpid());
+    for (unsigned i = 0; argv[i] != nullptr; ++i) {
+      fprintf(stderr, "%s%s", i ? " " : "", argv[i]);
+    }
+    fprintf(stderr, "\n");
+
+    // exec
+    execvp(argv[0], (char * const *)argv);
+    fprintf(stderr, "exec failed: %s\n", strerror(errno));
+    exit(1);
+
+  } else {
+    // parent
+    int st = 0;
+    pid_t reaped = TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
+
+    if (reaped == -1) {
+      W_ALOGW("waitpid failed: %s", strerror(errno));
+    } else if (WIFSIGNALED(st)) {
+      W_ALOGW("perf killed by signal %d", WTERMSIG(st));
+    } else if (WEXITSTATUS(st) != 0) {
+      W_ALOGW("perf bad exit status %d", WEXITSTATUS(st));
+    } else {
+      return OK_PROFILE_COLLECTION;
+    }
+  }
+
+  return ERR_PERF_RECORD_FAILED;
+}
+
+//
+// Remove all files in the destination directory during initialization
+//
+static void cleanup_destination_dir(const ConfigReader &config)
+{
+  std::string dest_dir = config.getStringValue("destination_directory");
+  DIR* dir = opendir(dest_dir.c_str());
+  if (dir != NULL) {
+    struct dirent* e;
+    while ((e = readdir(dir)) != 0) {
+      if (e->d_name[0] != '.') {
+        std::string file_path = dest_dir + "/" + e->d_name;
+        remove(file_path.c_str());
+      }
+    }
+    closedir(dir);
+  } else {
+    W_ALOGW("unable to open destination dir %s for cleanup",
+            dest_dir.c_str());
+  }
+}
+
+//
+// Post-processes after profile is collected and converted to protobuf.
+// * GMS core stores processed file sequence numbers in
+//   /data/data/com.google.android.gms/files/perfprofd_processed.txt
+// * Update /data/misc/perfprofd/perfprofd_produced.txt to remove the sequence
+//   numbers that have been processed and append the current seq number
+// Returns true if the current_seq should increment.
+//
+static bool post_process(const ConfigReader &config, int current_seq)
+{
+  std::string dest_dir = config.getStringValue("destination_directory");
+  std::string processed_file_path =
+      config.getStringValue("config_directory") + "/" + PROCESSED_FILENAME;
+  std::string produced_file_path = dest_dir + "/" + PRODUCED_FILENAME;
+
+
+  std::set<int> processed;
+  FILE *fp = fopen(processed_file_path.c_str(), "r");
+  if (fp != NULL) {
+    int seq;
+    while(fscanf(fp, "%d\n", &seq) > 0) {
+      if (remove(android::base::StringPrintf(
+          "%s/perf.data.encoded.%d", dest_dir.c_str(),seq).c_str()) == 0) {
+        processed.insert(seq);
+      }
+    }
+    fclose(fp);
+  }
+
+  std::set<int> produced;
+  fp = fopen(produced_file_path.c_str(), "r");
+  if (fp != NULL) {
+    int seq;
+    while(fscanf(fp, "%d\n", &seq) > 0) {
+      if (processed.find(seq) == processed.end()) {
+        produced.insert(seq);
+      }
+    }
+    fclose(fp);
+  }
+
+  unsigned maxLive = config.getUnsignedValue("max_unprocessed_profiles");
+  if (produced.size() >= maxLive) {
+    return false;
+  }
+
+  produced.insert(current_seq);
+  fp = fopen(produced_file_path.c_str(), "w");
+  if (fp == NULL) {
+    W_ALOGW("Cannot write %s", produced_file_path.c_str());
+    return false;
+  }
+  for (std::set<int>::const_iterator iter = produced.begin();
+       iter != produced.end(); ++iter) {
+    fprintf(fp, "%d\n", *iter);
+  }
+  fclose(fp);
+  chmod(produced_file_path.c_str(),
+        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
+  return true;
+}
+
+//
 // Collect a perf profile. Steps for this operation are:
 // - kick off 'perf record'
 // - read perf.data, convert to protocol buf
 //
-static PROFILE_RESULT collect_profile(ConfigReader &config)
+static PROFILE_RESULT collect_profile(const ConfigReader &config, int seq)
 {
   //
   // Form perf.data file name, perf error output file name
@@ -582,41 +772,56 @@
   }
 
   //
-  // NB: it would probably be better to use explicit fork/exec with an
-  // alarm timeout in case of funny business. For now, call system().
+  // The "mpdecision" daemon can cause problems for profile
+  // collection: if it decides to online a CPU partway through the
+  // 'perf record' run, the activity on that CPU will be invisible to
+  // perf, and if it offlines a CPU during the recording this can
+  // sometimes leave the PMU in an unusable state (dmesg errors of the
+  // form "perfevents: unable to request IRQXXX for ...").  To avoid
+  // these issues, if "mpdecision" is running the helper below will
+  // stop the service and then online all available CPUs. The object
+  // destructor (invoked when this routine terminates) will then
+  // restart the service again when needed.
   //
-  std::string perf_path = config.getStringValue("perf_path");
   unsigned duration = config.getUnsignedValue("sample_duration");
+  unsigned hardwire = config.getUnsignedValue("hardwire_cpus");
+  unsigned max_duration = config.getUnsignedValue("hardwire_cpus_max_duration");
+  bool take_action = (hardwire && duration <= max_duration);
+  HardwireCpuHelper helper(take_action);
+
+  //
+  // Invoke perf
+  //
+  const char *stack_profile_opt =
+      (config.getUnsignedValue("stack_profile") != 0 ? "-g" : nullptr);
+  std::string perf_path = config.getStringValue("perf_path");
   unsigned period = config.getUnsignedValue("sampling_period");
-  const char *gopt = (config.getUnsignedValue("stack_profile") != 0 ? "-g" : "");
-  char cmd[8192];
-  snprintf(cmd, 8192, "%s record %s -c %u -o %s -a -- sleep %d 1> %s 2>&1 ",
-           perf_path.c_str(), gopt, period, data_file_path.c_str(), duration,
-           perf_stderr_path.c_str());
-  int rc = system(cmd);
-  if (rc == -1) {
-    return ERR_FORK_FAILED;
-  }
-  if (rc != 0) {
-    return ERR_PERF_RECORD_FAILED;
+
+  PROFILE_RESULT ret = invoke_perf(perf_path.c_str(),
+                                  period,
+                                  stack_profile_opt,
+                                  duration,
+                                  data_file_path,
+                                  perf_stderr_path);
+  if (ret != OK_PROFILE_COLLECTION) {
+    return ret;
   }
 
   //
   // Read the resulting perf.data file, encode into protocol buffer, then write
-  // the result to a *.pdb file
+  // the result to the file perf.data.encoded
   //
-  std::string encoded_file_path(data_file_path);
-  encoded_file_path += ".encoded";
-  return encode_to_proto(data_file_path, encoded_file_path);
+  std::string path = android::base::StringPrintf(
+      "%s.encoded.%d", data_file_path.c_str(), seq);
+  return encode_to_proto(data_file_path, path.c_str());
 }
 
 //
-// SIGHUP handler. Sets a flag to indicate that we should reread the
-// config file
+// SIGHUP handler. Sending SIGHUP to the daemon can be used to break it
+// out of a sleep() call so as to trigger a new collection (debugging)
 //
 static void sig_hup(int /* signum */)
 {
-  please_reread_config_file = 1;
 }
 
 //
@@ -665,8 +870,9 @@
 //
 static void init(ConfigReader &config)
 {
-  config.readFile(config_file_path);
+  config.readFile(true);
   set_seed(config);
+  cleanup_destination_dir(config);
 
   char propBuf[PROPERTY_VALUE_MAX];
   propBuf[0] = '\0';
@@ -695,7 +901,6 @@
 
   parse_args(argc, argv);
   init(config);
-  read_aux_config(config);
 
   // Early exit if we're not supposed to run on this build flavor
   if (is_debug_build != 1 &&
@@ -705,7 +910,8 @@
   }
 
   unsigned iterations = 0;
-  while(!config.getUnsignedValue("main_loop_iterations") ||
+  int seq = 0;
+  while(config.getUnsignedValue("main_loop_iterations") == 0 ||
         iterations < config.getUnsignedValue("main_loop_iterations")) {
 
     // Figure out where in the collection interval we're going to actually
@@ -716,11 +922,9 @@
                            config.getUnsignedValue("collection_interval"));
     perfprofd_sleep(sleep_before_collect);
 
-    // Reread config file if someone sent a SIGHUP
-    if (please_reread_config_file) {
-      config.readFile(config_file_path);
-      please_reread_config_file = 0;
-    }
+    // Reread config file -- the uploader may have rewritten it as a result
+    // of a gservices change
+    config.readFile(false);
 
     // Check for profiling enabled...
     CKPROFILE_RESULT ckresult = check_profiling_enabled(config);
@@ -730,11 +934,14 @@
     } else {
       // Kick off the profiling run...
       W_ALOGI("initiating profile collection");
-      PROFILE_RESULT result = collect_profile(config);
+      PROFILE_RESULT result = collect_profile(config, seq);
       if (result != OK_PROFILE_COLLECTION) {
         W_ALOGI("profile collection failed (%s)",
                 profile_result_to_string(result));
       } else {
+        if (post_process(config, seq)) {
+          seq++;
+        }
         W_ALOGI("profile collection complete");
       }
     }
diff --git a/perfprofd/perfprofdcore.h b/perfprofd/perfprofdcore.h
index 1bff9ba..53695e2 100644
--- a/perfprofd/perfprofdcore.h
+++ b/perfprofd/perfprofdcore.h
@@ -18,6 +18,16 @@
 // Semaphore file that indicates that the user is opting in
 #define SEMAPHORE_FILENAME "perf_profile_collection_enabled.txt"
 
+// File containing a list of sequence numbers corresponding to profiles
+// that have been processed/uploaded. Written by the GmsCore uploader,
+// within the GmsCore files directory.
+#define PROCESSED_FILENAME "perfprofd_processed.txt"
+
+// File containing a list of sequence numbers corresponding to profiles
+// that have been created by the perfprofd but not yet uploaded. Written
+// by perfprofd within the destination directory; consumed by GmsCore.
+#define PRODUCED_FILENAME "perfprofd_produced.txt"
+
 // Main routine for perfprofd daemon
 extern int perfprofd_main(int argc, char **argv);
 
@@ -53,4 +63,4 @@
 // was successful (either OK_PROFILE_COLLECTION or an error of some sort).
 //
 PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
-                               const std::string &encoded_file_path);
+                               const char *encoded_file_path);
diff --git a/perfprofd/tests/Android.mk b/perfprofd/tests/Android.mk
index c8347a1..d8ea10a 100644
--- a/perfprofd/tests/Android.mk
+++ b/perfprofd/tests/Android.mk
@@ -34,10 +34,8 @@
 LOCAL_CLANG := true
 LOCAL_CPP_EXTENSION := cc
 LOCAL_CXX_STL := libc++
-LOCAL_STATIC_LIBRARIES := \
-    libperfprofdcore \
-    libperfprofdmockutils
-LOCAL_SHARED_LIBRARIES := libprotobuf-cpp-full
+LOCAL_STATIC_LIBRARIES := libperfprofdcore libperfprofdmockutils libbase
+LOCAL_SHARED_LIBRARIES := libprotobuf-cpp-lite
 LOCAL_C_INCLUDES += system/extras/perfprofd external/protobuf/src
 LOCAL_SRC_FILES := perfprofd_test.cc
 LOCAL_CPPFLAGS += $(perfprofd_test_cppflags)
diff --git a/perfprofd/tests/perfprofd_test.cc b/perfprofd/tests/perfprofd_test.cc
index 94c2814..d13e21e 100644
--- a/perfprofd/tests/perfprofd_test.cc
+++ b/perfprofd/tests/perfprofd_test.cc
@@ -24,6 +24,8 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 
+#include <base/stringprintf.h>
+
 #include "perfprofdcore.h"
 #include "perfprofdutils.h"
 #include "perfprofdmockutils.h"
@@ -53,11 +55,10 @@
 // Temporary config file that we will emit for the daemon to read
 #define CONFIGFILE "perfprofd.conf"
 
-static std::string encoded_file_path()
+static std::string encoded_file_path(int seq)
 {
-  std::string path(dest_dir);
-  path += "/perf.data.encoded";
-  return path;
+  return android::base::StringPrintf("%s/perf.data.encoded.%d",
+                                     dest_dir.c_str(), seq);
 }
 
 class PerfProfdTest : public testing::Test {
@@ -65,31 +66,36 @@
   virtual void SetUp() {
     mock_perfprofdutils_init();
     create_dest_dir();
+    yesclean();
   }
 
   virtual void TearDown() {
     mock_perfprofdutils_finish();
-    remove_dest_dir();
+  }
+
+  void noclean() {
+    clean_ = false;
+  }
+  void yesclean() {
+    clean_ = true;
   }
 
  private:
+  bool clean_;
 
   void create_dest_dir() {
     setup_dirs();
     ASSERT_FALSE(dest_dir == "");
+    if (clean_) {
+      std::string cmd("rm -rf ");
+      cmd += dest_dir;
+      system(cmd.c_str());
+    }
     std::string cmd("mkdir -p ");
     cmd += dest_dir;
     system(cmd.c_str());
   }
 
-  void remove_dest_dir() {
-    setup_dirs();
-    ASSERT_FALSE(dest_dir == "");
-    std::string cmd("rm -rf ");
-    cmd += dest_dir;
-    system(cmd.c_str());
-  }
-
   void setup_dirs()
   {
     if (test_dir == "") {
@@ -136,14 +142,13 @@
  public:
   PerfProfdRunner()
       : config_path_(test_dir)
-      , aux_config_path_(dest_dir)
   {
     config_path_ += "/" CONFIGFILE;
-    aux_config_path_ += "/" CONFIGFILE;
   }
 
   ~PerfProfdRunner()
   {
+    remove_processed_file();
   }
 
   void addToConfig(const std::string &line)
@@ -152,35 +157,42 @@
     config_text_ += "\n";
   }
 
-  void addToAuxConfig(const std::string &line)
-  {
-    aux_config_text_ += line;
-    aux_config_text_ += "\n";
-  }
-
   void remove_semaphore_file()
   {
-    std::string semaphore(dest_dir);
+    std::string semaphore(test_dir);
     semaphore += "/" SEMAPHORE_FILENAME;
     unlink(semaphore.c_str());
   }
 
   void create_semaphore_file()
   {
-    std::string semaphore(dest_dir);
+    std::string semaphore(test_dir);
     semaphore += "/" SEMAPHORE_FILENAME;
     close(open(semaphore.c_str(), O_WRONLY|O_CREAT));
   }
 
+  void write_processed_file(int start_seq, int end_seq)
+  {
+    std::string processed = test_dir + "/" PROCESSED_FILENAME;
+    FILE *fp = fopen(processed.c_str(), "w");
+    for (int i = start_seq; i < end_seq; i++) {
+      fprintf(fp, "%d\n", i);
+    }
+    fclose(fp);
+  }
+
+  void remove_processed_file()
+  {
+    std::string processed = test_dir + "/" PROCESSED_FILENAME;
+    unlink(processed.c_str());
+  }
+
   int invoke()
   {
     static const char *argv[3] = { "perfprofd", "-c", "" };
     argv[2] = config_path_.c_str();
 
     writeConfigFile(config_path_, config_text_);
-    if (aux_config_text_.length()) {
-      writeConfigFile(aux_config_path_, aux_config_text_);
-    }
 
     // execute daemon main
     return perfprofd_main(3, (char **) argv);
@@ -189,8 +201,6 @@
  private:
   std::string config_path_;
   std::string config_text_;
-  std::string aux_config_path_;
-  std::string aux_config_text_;
 
   void writeConfigFile(const std::string &config_path,
                        const std::string &config_text)
@@ -205,17 +215,16 @@
 //......................................................................
 
 static void readEncodedProfile(const char *testpoint,
-                               wireless_android_play_playlog::AndroidPerfProfile &encodedProfile,
-                               bool debugDump=false)
+                               wireless_android_play_playlog::AndroidPerfProfile &encodedProfile)
 {
   struct stat statb;
-  int perf_data_stat_result = stat(encoded_file_path().c_str(), &statb);
+  int perf_data_stat_result = stat(encoded_file_path(0).c_str(), &statb);
   ASSERT_NE(-1, perf_data_stat_result);
 
   // read
   std::string encoded;
   encoded.resize(statb.st_size);
-  FILE *ifp = fopen(encoded_file_path().c_str(), "r");
+  FILE *ifp = fopen(encoded_file_path(0).c_str(), "r");
   ASSERT_NE(nullptr, ifp);
   size_t items_read = fread((void*) encoded.data(), statb.st_size, 1, ifp);
   ASSERT_EQ(1, items_read);
@@ -223,17 +232,35 @@
 
   // decode
   encodedProfile.ParseFromString(encoded);
+}
 
-  if (debugDump) {
-    std::string textdump;
-    ::google::protobuf::TextFormat::PrintToString(encodedProfile, &textdump);
-    std::string dfp(dest_dir); dfp += "/"; dfp += testpoint; dfp += ".dump_encoded.txt";
-    FILE *ofp = fopen(dfp.c_str(), "w");
-    if (ofp) {
-      fwrite(textdump.c_str(), textdump.size(), 1, ofp);
-      fclose(ofp);
-    }
+static std::string encodedLoadModuleToString(const wireless_android_play_playlog::LoadModule &lm)
+{
+  std::stringstream ss;
+  ss << "name: \"" << lm.name() << "\"\n";
+  if (lm.build_id() != "") {
+    ss << "build_id: \"" << lm.build_id() << "\"\n";
   }
+  return ss.str();
+}
+
+static std::string encodedModuleSamplesToString(const wireless_android_play_playlog::LoadModuleSamples &mod)
+{
+  std::stringstream ss;
+
+  ss << "load_module_id: " << mod.load_module_id() << "\n";
+  for (size_t k = 0; k < mod.address_samples_size(); k++) {
+    const auto &sample = mod.address_samples(k);
+    ss << "  address_samples {\n";
+    for (size_t l = 0; l < mod.address_samples(k).address_size();
+         l++) {
+      auto address = mod.address_samples(k).address(l);
+      ss << "    address: " << address << "\n";
+    }
+    ss << "    count: " << sample.count() << "\n";
+    ss << "  }\n";
+  }
+  return ss.str();
 }
 
 #define RAW_RESULT(x) #x
@@ -271,14 +298,14 @@
   //
   // AWP requires cooperation between the daemon and the GMS core
   // piece. If we're running on a device that has an old or damaged
-  // version of GMS core, then the directory we're interested in may
-  // not be there. This test insures that the daemon does the right
-  // thing in this case.
+  // version of GMS core, then the config directory we're interested in
+  // may not be there. This test insures that the daemon does the
+  // right thing in this case.
   //
   PerfProfdRunner runner;
   runner.addToConfig("only_debug_build=0");
-  runner.addToConfig("trace_config_read=1");
-  runner.addToConfig("destination_directory=/does/not/exist");
+  runner.addToConfig("trace_config_read=0");
+  runner.addToConfig("config_directory=/does/not/exist");
   runner.addToConfig("main_loop_iterations=1");
   runner.addToConfig("use_fixed_seed=1");
   runner.addToConfig("collection_interval=100");
@@ -291,26 +318,17 @@
 
   // Verify log contents
   const std::string expected = RAW_RESULT(
-      I: starting Android Wide Profiling daemon
-      I: config file path set to /data/nativetest/perfprofd_test/perfprofd.conf
-      I: option destination_directory set to /does/not/exist
-      I: option main_loop_iterations set to 1
-      I: option use_fixed_seed set to 1
-      I: option collection_interval set to 100
-      I: random seed set to 1
       I: sleep 90 seconds
-      W: unable to open destination directory /does/not/exist: (No such file or directory)
-      I: profile collection skipped (missing destination directory)
-      I: sleep 10 seconds
-      I: finishing Android Wide Profiling daemon
-                                          );\
+      W: unable to open config directory /does/not/exist: (No such file or directory)
+      I: profile collection skipped (missing config directory)
+                                          );
 
   // check to make sure entire log matches
-  bool compareEntireLog = true;
   compareLogMessages(mock_perfprofdutils_getlogged(),
-                     expected, "MissingGMS", compareEntireLog);
+                     expected, "MissingGMS");
 }
 
+
 TEST_F(PerfProfdTest, MissingOptInSemaphoreFile)
 {
   //
@@ -322,6 +340,8 @@
   //
   PerfProfdRunner runner;
   runner.addToConfig("only_debug_build=0");
+  std::string cfparam("config_directory="); cfparam += test_dir;
+  runner.addToConfig(cfparam);
   std::string ddparam("destination_directory="); ddparam += dest_dir;
   runner.addToConfig(ddparam);
   runner.addToConfig("main_loop_iterations=1");
@@ -348,13 +368,16 @@
 TEST_F(PerfProfdTest, MissingPerfExecutable)
 {
   //
-  // AWP currently relies on the 'perf' tool to collect profiles (although
-  // this may conceivably change in the future). This test checks to make
-  // sure that if 'perf' is not present we bail out from collecting profiles.
+  // Perfprofd uses the 'simpleperf' tool to collect profiles
+  // (although this may conceivably change in the future). This test
+  // checks to make sure that if 'simpleperf' is not present we bail out
+  // from collecting profiles.
   //
   PerfProfdRunner runner;
   runner.addToConfig("only_debug_build=0");
   runner.addToConfig("trace_config_read=1");
+  std::string cfparam("config_directory="); cfparam += test_dir;
+  runner.addToConfig(cfparam);
   std::string ddparam("destination_directory="); ddparam += dest_dir;
   runner.addToConfig(ddparam);
   runner.addToConfig("main_loop_iterations=1");
@@ -383,13 +406,15 @@
 TEST_F(PerfProfdTest, BadPerfRun)
 {
   //
-  // The linux 'perf' tool tends to be tightly coupled with a
-  // specific kernel version -- if things are out of sync perf could
-  // easily fail or crash. This test makes sure that we detect such a
-  // case and log the error.
+  // Perf tools tend to be tightly coupled with a specific kernel
+  // version -- if things are out of sync perf could fail or
+  // crash. This test makes sure that we detect such a case and log
+  // the error.
   //
   PerfProfdRunner runner;
   runner.addToConfig("only_debug_build=0");
+  std::string cfparam("config_directory="); cfparam += test_dir;
+  runner.addToConfig(cfparam);
   std::string ddparam("destination_directory="); ddparam += dest_dir;
   runner.addToConfig(ddparam);
   runner.addToConfig("main_loop_iterations=1");
@@ -455,58 +480,6 @@
                      expected, "ConfigFileParsing");
 }
 
-TEST_F(PerfProfdTest, AuxiliaryConfigFile)
-{
-  //
-  // We want to be able to tweak profile collection parameters (sample
-  // duration, etc) using changes to gservices. To carry this out, the
-  // GMS core upload service writes out an perfprofd.conf config file when
-  // it starts up. This test verifies that we can read this file.
-  //
-
-  // Minimal settings in main config file
-  PerfProfdRunner runner;
-  runner.addToConfig("only_debug_build=0");
-  runner.addToConfig("trace_config_read=1");
-  runner.addToConfig("use_fixed_seed=1");
-  std::string ddparam("destination_directory="); ddparam += dest_dir;
-  runner.addToConfig(ddparam);
-
-  // Remaining settings in aux config file
-  runner.addToAuxConfig("main_loop_iterations=1");
-  runner.addToAuxConfig("collection_interval=100");
-  runner.addToAuxConfig("perf_path=/system/bin/true");
-  runner.addToAuxConfig("stack_profile=1");
-  runner.addToAuxConfig("sampling_period=9999");
-  runner.addToAuxConfig("sample_duration=333");
-
-  runner.remove_semaphore_file();
-
-  // Kick off daemon
-  int daemon_main_return_code = runner.invoke();
-
-  // Check return code from daemon
-  EXPECT_EQ(0, daemon_main_return_code);
-
-  // Verify log contents
-  const std::string expected = RAW_RESULT(
-      I: reading auxiliary config file /data/nativetest/perfprofd_test/tmp/perfprofd.conf
-      I: option main_loop_iterations set to 1
-      I: option collection_interval set to 100
-      I: option perf_path set to /system/bin/true
-      I: option stack_profile set to 1
-      I: option sampling_period set to 9999
-      I: option sample_duration set to 333
-      I: sleep 90 seconds
-      I: reading auxiliary config file /data/nativetest/perfprofd_test/tmp/perfprofd.conf
-      I: option main_loop_iterations set to 1
-                                          );
-
-  // check to make sure log excerpt matches
-  compareLogMessages(mock_perfprofdutils_getlogged(),
-                     expected, "AuxiliaryConfigFile");
-}
-
 TEST_F(PerfProfdTest, BasicRunWithCannedPerf)
 {
   //
@@ -520,7 +493,7 @@
 
   // Kick off encoder and check return code
   PROFILE_RESULT result =
-      encode_to_proto(input_perf_data, encoded_file_path());
+      encode_to_proto(input_perf_data, encoded_file_path(0).c_str());
   EXPECT_EQ(OK_PROFILE_COLLECTION, result);
 
   // Read and decode the resulting perf.data.encoded file
@@ -533,8 +506,7 @@
 
   // Check a couple of load modules
   { const auto &lm0 = encodedProfile.load_modules(0);
-    std::string act_lm0;
-    ::google::protobuf::TextFormat::PrintToString(lm0, &act_lm0);
+    std::string act_lm0 = encodedLoadModuleToString(lm0);
     std::string sqact0 = squeezeWhite(act_lm0, "actual for lm 0");
     const std::string expected_lm0 = RAW_RESULT(
         name: "/data/app/com.google.android.apps.plus-1/lib/arm/libcronet.so"
@@ -543,8 +515,7 @@
     EXPECT_STREQ(sqexp0.c_str(), sqact0.c_str());
   }
   { const auto &lm9 = encodedProfile.load_modules(9);
-    std::string act_lm9;
-    ::google::protobuf::TextFormat::PrintToString(lm9, &act_lm9);
+    std::string act_lm9 = encodedLoadModuleToString(lm9);
     std::string sqact9 = squeezeWhite(act_lm9, "actual for lm 9");
     const std::string expected_lm9 = RAW_RESULT(
         name: "/system/lib/libandroid_runtime.so" build_id: "8164ed7b3a8b8f5a220d027788922510"
@@ -556,8 +527,7 @@
   // Examine some of the samples now
   { const auto &p1 = encodedProfile.programs(0);
     const auto &lm1 = p1.modules(0);
-    std::string act_lm1;
-    ::google::protobuf::TextFormat::PrintToString(lm1, &act_lm1);
+    std::string act_lm1 = encodedModuleSamplesToString(lm1);
     std::string sqact1 = squeezeWhite(act_lm1, "actual for lm1");
     const std::string expected_lm1 = RAW_RESULT(
         load_module_id: 9 address_samples { address: 296100 count: 1 }
@@ -567,8 +537,7 @@
   }
   { const auto &p1 = encodedProfile.programs(2);
     const auto &lm2 = p1.modules(0);
-    std::string act_lm2;
-    ::google::protobuf::TextFormat::PrintToString(lm2, &act_lm2);
+    std::string act_lm2 = encodedModuleSamplesToString(lm2);
     std::string sqact2 = squeezeWhite(act_lm2, "actual for lm2");
     const std::string expected_lm2 = RAW_RESULT(
         load_module_id: 2
@@ -590,11 +559,13 @@
   runner.addToConfig("only_debug_build=0");
   std::string ddparam("destination_directory="); ddparam += dest_dir;
   runner.addToConfig(ddparam);
+  std::string cfparam("config_directory="); cfparam += test_dir;
+  runner.addToConfig(cfparam);
   runner.addToConfig("main_loop_iterations=1");
   runner.addToConfig("use_fixed_seed=12345678");
+  runner.addToConfig("max_unprocessed_profiles=100");
   runner.addToConfig("collection_interval=9999");
-  runner.addToConfig("stack_profile=1");
-  runner.addToConfig("sample_duration=5");
+  runner.addToConfig("sample_duration=2");
 
   // Create semaphore file
   runner.create_semaphore_file();
@@ -629,6 +600,70 @@
                      expected, "BasicRunWithLivePerf", true);
 }
 
+TEST_F(PerfProfdTest, MultipleRunWithLivePerf)
+{
+  //
+  // Basic test to exercise the main loop of the daemon. It includes
+  // a live 'perf' run
+  //
+  PerfProfdRunner runner;
+  runner.addToConfig("only_debug_build=0");
+  std::string ddparam("destination_directory="); ddparam += dest_dir;
+  runner.addToConfig(ddparam);
+  std::string cfparam("config_directory="); cfparam += test_dir;
+  runner.addToConfig(cfparam);
+  runner.addToConfig("main_loop_iterations=3");
+  runner.addToConfig("use_fixed_seed=12345678");
+  runner.addToConfig("collection_interval=9999");
+  runner.addToConfig("sample_duration=2");
+  runner.write_processed_file(1, 2);
+
+  // Create semaphore file
+  runner.create_semaphore_file();
+
+  // Kick off daemon
+  int daemon_main_return_code = runner.invoke();
+
+  // Check return code from daemon
+  EXPECT_EQ(0, daemon_main_return_code);
+
+  // Read and decode the resulting perf.data.encoded file
+  wireless_android_play_playlog::AndroidPerfProfile encodedProfile;
+  readEncodedProfile("BasicRunWithLivePerf", encodedProfile);
+
+  // Examine what we get back. Since it's a live profile, we can't
+  // really do much in terms of verifying the contents.
+  EXPECT_LT(0, encodedProfile.programs_size());
+
+  // Examine that encoded.1 file is removed while encoded.{0|2} exists.
+  EXPECT_EQ(0, access(encoded_file_path(0).c_str(), F_OK));
+  EXPECT_NE(0, access(encoded_file_path(1).c_str(), F_OK));
+  EXPECT_EQ(0, access(encoded_file_path(2).c_str(), F_OK));
+
+  // Verify log contents
+  const std::string expected = RAW_RESULT(
+      I: starting Android Wide Profiling daemon
+      I: config file path set to /data/nativetest/perfprofd_test/perfprofd.conf
+      I: random seed set to 12345678
+      I: sleep 674 seconds
+      I: initiating profile collection
+      I: profile collection complete
+      I: sleep 9325 seconds
+      I: sleep 4974 seconds
+      I: initiating profile collection
+      I: profile collection complete
+      I: sleep 5025 seconds
+      I: sleep 501 seconds
+      I: initiating profile collection
+      I: profile collection complete
+      I: sleep 9498 seconds
+      I: finishing Android Wide Profiling daemon
+                                          );
+  // check to make sure log excerpt matches
+  compareLogMessages(mock_perfprofdutils_getlogged(),
+                     expected, "BasicRunWithLivePerf", true);
+}
+
 int main(int argc, char **argv) {
   executable_path = argv[0];
   // switch to / before starting testing (perfprofd
diff --git a/perfprofd/tests/perfprofdmockutils.cc b/perfprofd/tests/perfprofdmockutils.cc
index cc4ea94..5af58c4 100644
--- a/perfprofd/tests/perfprofdmockutils.cc
+++ b/perfprofd/tests/perfprofdmockutils.cc
@@ -54,6 +54,7 @@
   for (const std::string &s : (*mock_log)) {
     result += s;
   }
+  mock_log->clear();
   return result;
 }
 
diff --git a/procrank/procrank.c b/procrank/procrank.c
index de26cd1..1728467 100644
--- a/procrank/procrank.c
+++ b/procrank/procrank.c
@@ -324,7 +324,7 @@
     }
 
     if (has_swap) {
-        printf("%6" PRIu64 "K  ", total_swap);
+        printf("%6" PRIu64 "K  ", total_swap / 1024);
     }
 
     printf("TOTAL\n");
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
new file mode 100644
index 0000000..4642e2a
--- /dev/null
+++ b/simpleperf/Android.mk
@@ -0,0 +1,142 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+simpleperf_common_cppflags := -std=c++11 -Wall -Wextra -Werror -Wunused
+simpleperf_use_bionic_perf_event_h_flag := -DUSE_BIONIC_PERF_EVENT_H -I bionic
+simpleperf_host_common_cppflags := $(simpleperf_common_cppflags) $(simpleperf_use_bionic_perf_event_h_flag)
+
+simpleperf_common_shared_libraries := \
+  libbase \
+  libLLVM \
+
+LLVM_ROOT_PATH := external/llvm
+
+libsimpleperf_src_files := \
+  cmd_dumprecord.cpp \
+  cmd_help.cpp \
+  cmd_list.cpp \
+  cmd_record.cpp \
+  cmd_report.cpp \
+  cmd_stat.cpp \
+  command.cpp \
+  dso.cpp \
+  environment.cpp \
+  event_attr.cpp \
+  event_fd.cpp \
+  event_selection_set.cpp \
+  event_type.cpp \
+  read_elf.cpp \
+  record.cpp \
+  record_file.cpp \
+  sample_tree.cpp \
+  utils.cpp \
+  workload.cpp \
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := $(libsimpleperf_src_files)
+LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries)
+LOCAL_MODULE := libsimpleperf
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(LLVM_ROOT_PATH)/llvm.mk
+include $(LLVM_DEVICE_BUILD_MK)
+include $(BUILD_STATIC_LIBRARY)
+
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_host_common_cppflags)
+LOCAL_SRC_FILES := $(libsimpleperf_src_files)
+LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries)
+LOCAL_LDLIBS := -lrt
+LOCAL_MODULE := libsimpleperf
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(LLVM_ROOT_PATH)/llvm.mk
+include $(LLVM_HOST_BUILD_MK)
+include $(BUILD_HOST_STATIC_LIBRARY)
+endif
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := main.cpp
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
+LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries)
+LOCAL_MODULE := simpleperf
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_EXECUTABLE)
+
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_host_common_cppflags)
+LOCAL_SRC_FILES := main.cpp
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
+LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries)
+LOCAL_LDLIBS := -lrt
+LOCAL_MODULE := simpleperf
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_EXECUTABLE)
+endif
+
+simpleperf_unit_test_src_files := \
+  cmd_dumprecord_test.cpp \
+  cmd_list_test.cpp \
+  cmd_record_test.cpp \
+  cmd_report_test.cpp \
+  cmd_stat_test.cpp \
+  command_test.cpp \
+  cpu_offline_test.cpp \
+  environment_test.cpp \
+  gtest_main.cpp \
+  read_elf_test.cpp \
+  record_file_test.cpp \
+  record_test.cpp \
+  sample_tree_test.cpp \
+  workload_test.cpp \
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := $(simpleperf_unit_test_src_files)
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
+LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries)
+LOCAL_MODULE := simpleperf_unit_test
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_NATIVE_TEST)
+
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_host_common_cppflags)
+LOCAL_SRC_FILES := $(simpleperf_unit_test_src_files)
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
+LOCAL_SHARED_LIBRARIES := $(simpleperf_common_shared_libraries)
+LOCAL_MODULE := simpleperf_unit_test
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_NATIVE_TEST)
+endif
diff --git a/ext4_utils/uuid.h b/simpleperf/build_id.h
similarity index 67%
copy from ext4_utils/uuid.h
copy to simpleperf/build_id.h
index ff1b438..5a4b12c 100644
--- a/ext4_utils/uuid.h
+++ b/simpleperf/build_id.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef _UUID_H_
-#define _UUID_H_
+#ifndef SIMPLE_PERF_BUILD_ID_H_
+#define SIMPLE_PERF_BUILD_ID_H_
 
-#include "ext4_utils.h"
+#include <array>
 
-void generate_uuid(const char *namespace, const char *name, u8 result[16]);
+static constexpr int BUILD_ID_SIZE = 20;
 
-#endif
+typedef std::array<unsigned char, BUILD_ID_SIZE> BuildId;
+
+#endif  // SIMPLE_PERF_BUILD_ID_H_
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
new file mode 100644
index 0000000..e5e239b
--- /dev/null
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#include "command.h"
+#include "event_attr.h"
+#include "record.h"
+#include "record_file.h"
+
+using namespace PerfFileFormat;
+
+class DumpRecordCommand : public Command {
+ public:
+  DumpRecordCommand()
+      : Command("dump", "dump perf record file",
+                "Usage: simpleperf dumprecord [options] [perf_record_file]\n"
+                "    Dump different parts of a perf record file. Default file is perf.data.\n"),
+        record_filename_("perf.data") {
+  }
+
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args);
+  void DumpFileHeader();
+  void DumpAttrSection();
+  void DumpDataSection();
+  void DumpFeatureSection();
+
+  std::string record_filename_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+
+  std::vector<int> features_;
+};
+
+bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
+  if (!ParseOptions(args)) {
+    return false;
+  }
+  record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+  if (record_file_reader_ == nullptr) {
+    return false;
+  }
+  DumpFileHeader();
+  DumpAttrSection();
+  DumpDataSection();
+  DumpFeatureSection();
+
+  return true;
+}
+
+bool DumpRecordCommand::ParseOptions(const std::vector<std::string>& args) {
+  if (args.size() == 1) {
+    record_filename_ = args[0];
+  } else if (args.size() > 1) {
+    ReportUnknownOption(args, 1);
+    return false;
+  }
+  return true;
+}
+
+static const std::string GetFeatureName(int feature);
+
+void DumpRecordCommand::DumpFileHeader() {
+  const FileHeader* header = record_file_reader_->FileHeader();
+  printf("magic: ");
+  for (size_t i = 0; i < 8; ++i) {
+    printf("%c", header->magic[i]);
+  }
+  printf("\n");
+  printf("header_size: %" PRId64 "\n", header->header_size);
+  if (header->header_size != sizeof(*header)) {
+    PLOG(WARNING) << "record file header size doesn't match expected header size "
+                  << sizeof(*header);
+  }
+  printf("attr_size: %" PRId64 "\n", header->attr_size);
+  if (header->attr_size != sizeof(FileAttr)) {
+    PLOG(WARNING) << "record file attr size doesn't match expected attr size " << sizeof(FileAttr);
+  }
+  printf("attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n", header->attrs.offset,
+         header->attrs.size);
+  printf("data[file section]: offset %" PRId64 ", size %" PRId64 "\n", header->data.offset,
+         header->data.size);
+  printf("event_types[file section]: offset %" PRId64 ", size %" PRId64 "\n",
+         header->event_types.offset, header->event_types.size);
+
+  features_.clear();
+  for (size_t i = 0; i < FEAT_MAX_NUM; ++i) {
+    size_t j = i / 8;
+    size_t k = i % 8;
+    if ((header->features[j] & (1 << k)) != 0) {
+      features_.push_back(i);
+    }
+  }
+  for (auto& feature : features_) {
+    printf("feature: %s\n", GetFeatureName(feature).c_str());
+  }
+}
+
+static const std::string GetFeatureName(int feature) {
+  static std::map<int, std::string> feature_name_map = {
+      {FEAT_TRACING_DATA, "tracing_data"},
+      {FEAT_BUILD_ID, "build_id"},
+      {FEAT_HOSTNAME, "hostname"},
+      {FEAT_OSRELEASE, "osrelease"},
+      {FEAT_VERSION, "version"},
+      {FEAT_ARCH, "arch"},
+      {FEAT_NRCPUS, "nrcpus"},
+      {FEAT_CPUDESC, "cpudesc"},
+      {FEAT_CPUID, "cpuid"},
+      {FEAT_TOTAL_MEM, "total_mem"},
+      {FEAT_CMDLINE, "cmdline"},
+      {FEAT_EVENT_DESC, "event_desc"},
+      {FEAT_CPU_TOPOLOGY, "cpu_topology"},
+      {FEAT_NUMA_TOPOLOGY, "numa_topology"},
+      {FEAT_BRANCH_STACK, "branch_stack"},
+      {FEAT_PMU_MAPPINGS, "pmu_mappings"},
+      {FEAT_GROUP_DESC, "group_desc"},
+  };
+  auto it = feature_name_map.find(feature);
+  if (it != feature_name_map.end()) {
+    return it->second;
+  }
+  return android::base::StringPrintf("unknown_feature(%d)", feature);
+}
+
+void DumpRecordCommand::DumpAttrSection() {
+  std::vector<const FileAttr*> attrs = record_file_reader_->AttrSection();
+  for (size_t i = 0; i < attrs.size(); ++i) {
+    auto& attr = attrs[i];
+    printf("file_attr %zu:\n", i + 1);
+    DumpPerfEventAttr(attr->attr, 1);
+    printf("  ids[file_section]: offset %" PRId64 ", size %" PRId64 "\n", attr->ids.offset,
+           attr->ids.size);
+    std::vector<uint64_t> ids = record_file_reader_->IdsForAttr(attr);
+    if (ids.size() > 0) {
+      printf("  ids:");
+      for (auto& id : ids) {
+        printf(" %" PRId64, id);
+      }
+      printf("\n");
+    }
+  }
+}
+
+void DumpRecordCommand::DumpDataSection() {
+  std::vector<std::unique_ptr<const Record>> records = record_file_reader_->DataSection();
+  for (auto& record : records) {
+    record->Dump();
+  }
+}
+
+void DumpRecordCommand::DumpFeatureSection() {
+  std::vector<SectionDesc> sections = record_file_reader_->FeatureSectionDescriptors();
+  CHECK_EQ(sections.size(), features_.size());
+  for (size_t i = 0; i < features_.size(); ++i) {
+    int feature = features_[i];
+    SectionDesc& section = sections[i];
+    printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n",
+           GetFeatureName(feature).c_str(), section.offset, section.size);
+    if (feature == FEAT_BUILD_ID) {
+      const char* p = record_file_reader_->DataAtOffset(section.offset);
+      const char* end = p + section.size;
+      while (p < end) {
+        const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p);
+        CHECK_LE(p + header->size, end);
+        BuildIdRecord record(header);
+        record.header.type = PERF_RECORD_BUILD_ID;  // Set type explicitly as perf doesn't set it.
+        record.Dump(1);
+        p += header->size;
+      }
+    }
+  }
+}
+
+__attribute__((constructor)) static void RegisterDumpRecordCommand() {
+  RegisterCommand("dump", [] { return std::unique_ptr<Command>(new DumpRecordCommand); });
+}
diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp
new file mode 100644
index 0000000..f23ae16
--- /dev/null
+++ b/simpleperf/cmd_dumprecord_test.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "command.h"
+
+class DumpRecordCommandTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    record_cmd = CreateCommandInstance("record");
+    ASSERT_TRUE(record_cmd != nullptr);
+    dumprecord_cmd = CreateCommandInstance("dump");
+    ASSERT_TRUE(dumprecord_cmd != nullptr);
+  }
+
+  std::unique_ptr<Command> record_cmd;
+  std::unique_ptr<Command> dumprecord_cmd;
+};
+
+TEST_F(DumpRecordCommandTest, no_options) {
+  ASSERT_TRUE(record_cmd->Run({"-a", "sleep", "1"}));
+  ASSERT_TRUE(dumprecord_cmd->Run({}));
+}
+
+TEST_F(DumpRecordCommandTest, record_file_option) {
+  ASSERT_TRUE(record_cmd->Run({"-a", "-o", "perf2.data", "sleep", "1"}));
+  ASSERT_TRUE(dumprecord_cmd->Run({"perf2.data"}));
+}
diff --git a/simpleperf/cmd_help.cpp b/simpleperf/cmd_help.cpp
new file mode 100644
index 0000000..75df732
--- /dev/null
+++ b/simpleperf/cmd_help.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "command.h"
+
+class HelpCommand : public Command {
+ public:
+  HelpCommand()
+      : Command("help", "print help information for simpleperf",
+                "Usage: simpleperf help [subcommand]\n"
+                "    Without subcommand, print short help string for every subcommand.\n"
+                "    With subcommand, print long help string for the subcommand.\n\n") {
+  }
+
+  bool Run(const std::vector<std::string>& args) override;
+
+ private:
+  void PrintShortHelp();
+  void PrintLongHelpForOneCommand(const Command& cmd);
+};
+
+bool HelpCommand::Run(const std::vector<std::string>& args) {
+  if (args.empty()) {
+    PrintShortHelp();
+  } else {
+    std::unique_ptr<Command> cmd = CreateCommandInstance(args[0]);
+    if (cmd == nullptr) {
+      LOG(ERROR) << "malformed command line: can't find help string for unknown command " << args[0];
+      LOG(ERROR) << "try using \"--help\"";
+      return false;
+    } else {
+      PrintLongHelpForOneCommand(*cmd);
+    }
+  }
+  return true;
+}
+
+void HelpCommand::PrintShortHelp() {
+  printf("Usage: simpleperf [--help] subcommand [args_for_subcommand]\n\n");
+  for (auto& cmd_name : GetAllCommandNames()) {
+    std::unique_ptr<Command> cmd = CreateCommandInstance(cmd_name);
+    printf("%-20s%s\n", cmd_name.c_str(), cmd->ShortHelpString().c_str());
+  }
+}
+
+void HelpCommand::PrintLongHelpForOneCommand(const Command& command) {
+  printf("%s\n", command.LongHelpString().c_str());
+}
+
+__attribute__((constructor)) static void RegisterHelpCommand() {
+  RegisterCommand("help", [] { return std::unique_ptr<Command>(new HelpCommand); });
+}
diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp
new file mode 100644
index 0000000..221f3fb
--- /dev/null
+++ b/simpleperf/cmd_list.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "command.h"
+#include "event_type.h"
+#include "perf_event.h"
+
+static void PrintEventTypesOfType(uint32_t type, const std::string& type_name,
+                                  const std::vector<EventType>& event_types) {
+  printf("List of %s:\n", type_name.c_str());
+  for (auto& event_type : event_types) {
+    if (event_type.type == type && event_type.IsSupportedByKernel()) {
+      printf("  %s\n", event_type.name.c_str());
+    }
+  }
+  printf("\n");
+}
+
+class ListCommand : public Command {
+ public:
+  ListCommand()
+      : Command("list", "list available event types",
+                "Usage: simpleperf list [hw|sw|cache|tracepoint]\n"
+                "    List all available perf events on this machine.\n") {
+  }
+
+  bool Run(const std::vector<std::string>& args) override;
+};
+
+bool ListCommand::Run(const std::vector<std::string>& args) {
+  static std::map<std::string, std::pair<int, std::string>> type_map = {
+      {"hw", {PERF_TYPE_HARDWARE, "hardware events"}},
+      {"sw", {PERF_TYPE_SOFTWARE, "software events"}},
+      {"cache", {PERF_TYPE_HW_CACHE, "hw-cache events"}},
+      {"tracepoint", {PERF_TYPE_TRACEPOINT, "tracepoint events"}},
+  };
+
+  std::vector<std::string> names;
+  if (args.empty()) {
+    for (auto& item : type_map) {
+      names.push_back(item.first);
+    }
+  } else {
+    for (auto& arg : args) {
+      if (type_map.find(arg) != type_map.end()) {
+        names.push_back(arg);
+      } else {
+        LOG(ERROR) << "unknown event type category: " << arg << ", try using \"help list\"";
+        return false;
+      }
+    }
+  }
+
+  auto& event_types = GetAllEventTypes();
+
+  for (auto& name : names) {
+    auto it = type_map.find(name);
+    PrintEventTypesOfType(it->second.first, it->second.second, event_types);
+  }
+  return true;
+}
+
+__attribute__((constructor)) static void RegisterListCommand() {
+  RegisterCommand("list", [] { return std::unique_ptr<Command>(new ListCommand); });
+}
diff --git a/simpleperf/cmd_list_test.cpp b/simpleperf/cmd_list_test.cpp
new file mode 100644
index 0000000..2bc6421
--- /dev/null
+++ b/simpleperf/cmd_list_test.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "command.h"
+
+class ListCommandTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    list_cmd = CreateCommandInstance("list");
+    ASSERT_TRUE(list_cmd != nullptr);
+  }
+
+  std::unique_ptr<Command> list_cmd;
+};
+
+TEST_F(ListCommandTest, no_options) {
+  ASSERT_TRUE(list_cmd->Run({}));
+}
+
+TEST_F(ListCommandTest, one_option) {
+  ASSERT_TRUE(list_cmd->Run({"sw"}));
+}
+
+TEST_F(ListCommandTest, multiple_options) {
+  ASSERT_TRUE(list_cmd->Run({"hw", "tracepoint"}));
+}
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
new file mode 100644
index 0000000..1b692c9
--- /dev/null
+++ b/simpleperf/cmd_record.cpp
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <libgen.h>
+#include <poll.h>
+#include <signal.h>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings.h>
+
+#include "command.h"
+#include "environment.h"
+#include "event_selection_set.h"
+#include "event_type.h"
+#include "read_elf.h"
+#include "record.h"
+#include "record_file.h"
+#include "utils.h"
+#include "workload.h"
+
+static std::string default_measured_event_type = "cpu-cycles";
+
+static std::unordered_map<std::string, uint64_t> branch_sampling_type_map = {
+    {"u", PERF_SAMPLE_BRANCH_USER},
+    {"k", PERF_SAMPLE_BRANCH_KERNEL},
+    {"any", PERF_SAMPLE_BRANCH_ANY},
+    {"any_call", PERF_SAMPLE_BRANCH_ANY_CALL},
+    {"any_ret", PERF_SAMPLE_BRANCH_ANY_RETURN},
+    {"ind_call", PERF_SAMPLE_BRANCH_IND_CALL},
+};
+
+static volatile bool signaled;
+static void signal_handler(int) {
+  signaled = true;
+}
+
+class RecordCommand : public Command {
+ public:
+  RecordCommand()
+      : Command(
+            "record", "record sampling info in perf.data",
+            "Usage: simpleperf record [options] [command [command-args]]\n"
+            "    Gather sampling information when running [command].\n"
+            "    -a           System-wide collection.\n"
+            "    -b           Enable take branch stack sampling. Same as '-j any'\n"
+            "    -c count     Set event sample period.\n"
+            "    -e event[:modifier]\n"
+            "                 Select the event to sample. Use `simpleperf list` to find\n"
+            "                 all possible event names. Modifiers can be added to define\n"
+            "                 how the event should be monitored. Possible modifiers are:\n"
+            "                   u - monitor user space events only\n"
+            "                   k - monitor kernel space events only\n"
+            "    -f freq      Set event sample frequency.\n"
+            "    -F freq      Same as '-f freq'.\n"
+            "    -g           Enables call-graph recording.\n"
+            "    -j branch_filter1,branch_filter2,...\n"
+            "                 Enable taken branch stack sampling. Each sample\n"
+            "                 captures a series of consecutive taken branches.\n"
+            "                 The following filters are defined:\n"
+            "                   any: any type of branch\n"
+            "                   any_call: any function call or system call\n"
+            "                   any_ret: any function return or system call return\n"
+            "                   ind_call: any indirect branch\n"
+            "                   u: only when the branch target is at the user level\n"
+            "                   k: only when the branch target is in the kernel\n"
+            "                 This option requires at least one branch type among any,\n"
+            "                 any_call, any_ret, ind_call.\n"
+            "    --no-inherit\n"
+            "                 Don't record created child threads/processes.\n"
+            "    -o record_file_name    Set record file name, default is perf.data.\n"
+            "    -p pid1,pid2,...\n"
+            "                 Record events on existing processes. Mutually exclusive with -a.\n"
+            "    -t tid1,tid2,...\n"
+            "                 Record events on existing threads. Mutually exclusive with -a.\n"),
+        use_sample_freq_(true),
+        sample_freq_(4000),
+        system_wide_collection_(false),
+        branch_sampling_(0),
+        callchain_sampling_(false),
+        child_inherit_(true),
+        perf_mmap_pages_(256),
+        record_filename_("perf.data") {
+    signaled = false;
+    signal_handler_register_.reset(
+        new SignalHandlerRegister({SIGCHLD, SIGINT, SIGTERM}, signal_handler));
+  }
+
+  bool Run(const std::vector<std::string>& args);
+
+  static bool ReadMmapDataCallback(const char* data, size_t size);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
+  bool SetMeasuredEventType(const std::string& event_type_name);
+  bool SetEventSelection();
+  bool WriteData(const char* data, size_t size);
+  bool DumpKernelAndModuleMmaps();
+  bool DumpThreadCommAndMmaps();
+  bool DumpAdditionalFeatures();
+  bool DumpBuildIdFeature();
+
+  bool use_sample_freq_;    // Use sample_freq_ when true, otherwise using sample_period_.
+  uint64_t sample_freq_;    // Sample 'sample_freq_' times per second.
+  uint64_t sample_period_;  // Sample once when 'sample_period_' events occur.
+
+  bool system_wide_collection_;
+  uint64_t branch_sampling_;
+  bool callchain_sampling_;
+  bool child_inherit_;
+  std::vector<pid_t> monitored_threads_;
+  std::unique_ptr<EventTypeAndModifier> measured_event_type_modifier_;
+  EventSelectionSet event_selection_set_;
+
+  // mmap pages used by each perf event file, should be power of 2.
+  const size_t perf_mmap_pages_;
+
+  std::string record_filename_;
+  std::unique_ptr<RecordFileWriter> record_file_writer_;
+
+  std::unique_ptr<SignalHandlerRegister> signal_handler_register_;
+};
+
+bool RecordCommand::Run(const std::vector<std::string>& args) {
+  // 1. Parse options, and use default measured event type if not given.
+  std::vector<std::string> workload_args;
+  if (!ParseOptions(args, &workload_args)) {
+    return false;
+  }
+  if (measured_event_type_modifier_ == nullptr) {
+    if (!SetMeasuredEventType(default_measured_event_type)) {
+      return false;
+    }
+  }
+  if (!SetEventSelection()) {
+    return false;
+  }
+
+  // 2. Create workload.
+  std::unique_ptr<Workload> workload;
+  if (!workload_args.empty()) {
+    workload = Workload::CreateWorkload(workload_args);
+    if (workload == nullptr) {
+      return false;
+    }
+  }
+  if (!system_wide_collection_ && monitored_threads_.empty()) {
+    if (workload != nullptr) {
+      monitored_threads_.push_back(workload->GetPid());
+      event_selection_set_.SetEnableOnExec(true);
+    } else {
+      LOG(ERROR) << "No threads to monitor. Try `simpleperf help record` for help\n";
+      return false;
+    }
+  }
+
+  // 3. Open perf_event_files, create memory mapped buffers for perf_event_files, add prepare poll
+  //    for perf_event_files.
+  if (system_wide_collection_) {
+    if (!event_selection_set_.OpenEventFilesForAllCpus()) {
+      return false;
+    }
+  } else {
+    if (!event_selection_set_.OpenEventFilesForThreadsOnAllCpus(monitored_threads_)) {
+      return false;
+    }
+  }
+  if (!event_selection_set_.MmapEventFiles(perf_mmap_pages_)) {
+    return false;
+  }
+  std::vector<pollfd> pollfds;
+  event_selection_set_.PreparePollForEventFiles(&pollfds);
+
+  // 4. Open record file writer, and dump kernel/modules/threads mmap information.
+  record_file_writer_ = RecordFileWriter::CreateInstance(
+      record_filename_,
+      event_selection_set_.FindEventAttrByType(measured_event_type_modifier_->event_type),
+      event_selection_set_.FindEventFdsByType(measured_event_type_modifier_->event_type));
+  if (record_file_writer_ == nullptr) {
+    return false;
+  }
+  if (!DumpKernelAndModuleMmaps()) {
+    return false;
+  }
+  if (system_wide_collection_ && !DumpThreadCommAndMmaps()) {
+    return false;
+  }
+
+  // 5. Write records in mmap buffers of perf_event_files to output file while workload is running.
+  if (!event_selection_set_.GetEnableOnExec()) {
+    if (!event_selection_set_.EnableEvents()) {
+      return false;
+    }
+  }
+  if (workload != nullptr && !workload->Start()) {
+    return false;
+  }
+  auto callback =
+      std::bind(&RecordCommand::WriteData, this, std::placeholders::_1, std::placeholders::_2);
+  while (true) {
+    if (!event_selection_set_.ReadMmapEventData(callback)) {
+      return false;
+    }
+    if (signaled) {
+      break;
+    }
+    poll(&pollfds[0], pollfds.size(), -1);
+  }
+
+  // 6. Dump additional features, and close record file.
+  if (!DumpAdditionalFeatures()) {
+    return false;
+  }
+  if (!record_file_writer_->Close()) {
+    return false;
+  }
+  return true;
+}
+
+bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
+                                 std::vector<std::string>* non_option_args) {
+  std::set<pid_t> tid_set;
+  size_t i;
+  for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
+    if (args[i] == "-a") {
+      system_wide_collection_ = true;
+    } else if (args[i] == "-b") {
+      branch_sampling_ = branch_sampling_type_map["any"];
+    } else if (args[i] == "-c") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      char* endptr;
+      sample_period_ = strtoull(args[i].c_str(), &endptr, 0);
+      if (*endptr != '\0' || sample_period_ == 0) {
+        LOG(ERROR) << "Invalid sample period: '" << args[i] << "'";
+        return false;
+      }
+      use_sample_freq_ = false;
+    } else if (args[i] == "-e") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!SetMeasuredEventType(args[i])) {
+        return false;
+      }
+    } else if (args[i] == "-f" || args[i] == "-F") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      char* endptr;
+      sample_freq_ = strtoull(args[i].c_str(), &endptr, 0);
+      if (*endptr != '\0' || sample_freq_ == 0) {
+        LOG(ERROR) << "Invalid sample frequency: '" << args[i] << "'";
+        return false;
+      }
+      use_sample_freq_ = true;
+    } else if (args[i] == "-g") {
+      callchain_sampling_ = true;
+    } else if (args[i] == "-j") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> branch_sampling_types = android::base::Split(args[i], ",");
+      for (auto& type : branch_sampling_types) {
+        auto it = branch_sampling_type_map.find(type);
+        if (it == branch_sampling_type_map.end()) {
+          LOG(ERROR) << "unrecognized branch sampling filter: " << type;
+          return false;
+        }
+        branch_sampling_ |= it->second;
+      }
+    } else if (args[i] == "--no-inherit") {
+      child_inherit_ = false;
+    } else if (args[i] == "-o") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      record_filename_ = args[i];
+    } else if (args[i] == "-p") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!GetValidThreadsFromProcessString(args[i], &tid_set)) {
+        return false;
+      }
+    } else if (args[i] == "-t") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!GetValidThreadsFromThreadString(args[i], &tid_set)) {
+        return false;
+      }
+    } else {
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+
+  monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end());
+  if (system_wide_collection_ && !monitored_threads_.empty()) {
+    LOG(ERROR)
+        << "Record system wide and existing processes/threads can't be used at the same time.";
+    return false;
+  }
+
+  if (non_option_args != nullptr) {
+    non_option_args->clear();
+    for (; i < args.size(); ++i) {
+      non_option_args->push_back(args[i]);
+    }
+  }
+  return true;
+}
+
+bool RecordCommand::SetMeasuredEventType(const std::string& event_type_name) {
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_name);
+  if (event_type_modifier == nullptr) {
+    return false;
+  }
+  measured_event_type_modifier_ = std::move(event_type_modifier);
+  return true;
+}
+
+bool RecordCommand::SetEventSelection() {
+  event_selection_set_.AddEventType(*measured_event_type_modifier_);
+  if (use_sample_freq_) {
+    event_selection_set_.SetSampleFreq(sample_freq_);
+  } else {
+    event_selection_set_.SetSamplePeriod(sample_period_);
+  }
+  event_selection_set_.SampleIdAll();
+  if (!event_selection_set_.SetBranchSampling(branch_sampling_)) {
+    return false;
+  }
+  if (callchain_sampling_) {
+    event_selection_set_.EnableCallChainSampling();
+  }
+  event_selection_set_.SetInherit(child_inherit_);
+  return true;
+}
+
+bool RecordCommand::WriteData(const char* data, size_t size) {
+  return record_file_writer_->WriteData(data, size);
+}
+
+bool RecordCommand::DumpKernelAndModuleMmaps() {
+  KernelMmap kernel_mmap;
+  std::vector<ModuleMmap> module_mmaps;
+  if (!GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps)) {
+    return false;
+  }
+  const perf_event_attr& attr =
+      event_selection_set_.FindEventAttrByType(measured_event_type_modifier_->event_type);
+  MmapRecord mmap_record = CreateMmapRecord(attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
+                                            kernel_mmap.len, kernel_mmap.pgoff, kernel_mmap.name);
+  if (!record_file_writer_->WriteData(mmap_record.BinaryFormat())) {
+    return false;
+  }
+  for (auto& module_mmap : module_mmaps) {
+    std::string filename = module_mmap.filepath;
+    if (filename.empty()) {
+      filename = "[" + module_mmap.name + "]";
+    }
+    MmapRecord mmap_record = CreateMmapRecord(attr, true, UINT_MAX, 0, module_mmap.start_addr,
+                                              module_mmap.len, 0, filename);
+    if (!record_file_writer_->WriteData(mmap_record.BinaryFormat())) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RecordCommand::DumpThreadCommAndMmaps() {
+  std::vector<ThreadComm> thread_comms;
+  if (!GetThreadComms(&thread_comms)) {
+    return false;
+  }
+  const perf_event_attr& attr =
+      event_selection_set_.FindEventAttrByType(measured_event_type_modifier_->event_type);
+
+  // Dump processes.
+  for (auto& thread : thread_comms) {
+    if (thread.pid != thread.tid) {
+      continue;
+    }
+    CommRecord record = CreateCommRecord(attr, thread.pid, thread.tid, thread.comm);
+    if (!record_file_writer_->WriteData(record.BinaryFormat())) {
+      return false;
+    }
+    std::vector<ThreadMmap> thread_mmaps;
+    if (!GetThreadMmapsInProcess(thread.pid, &thread_mmaps)) {
+      // The thread may exit before we get its info.
+      continue;
+    }
+    for (auto& thread_mmap : thread_mmaps) {
+      if (thread_mmap.executable == 0) {
+        continue;  // No need to dump non-executable mmap info.
+      }
+      MmapRecord record =
+          CreateMmapRecord(attr, false, thread.pid, thread.tid, thread_mmap.start_addr,
+                           thread_mmap.len, thread_mmap.pgoff, thread_mmap.name);
+      if (!record_file_writer_->WriteData(record.BinaryFormat())) {
+        return false;
+      }
+    }
+  }
+
+  // Dump threads.
+  for (auto& thread : thread_comms) {
+    if (thread.pid == thread.tid) {
+      continue;
+    }
+    ForkRecord fork_record = CreateForkRecord(attr, thread.pid, thread.tid, thread.pid, thread.pid);
+    if (!record_file_writer_->WriteData(fork_record.BinaryFormat())) {
+      return false;
+    }
+    CommRecord comm_record = CreateCommRecord(attr, thread.pid, thread.tid, thread.comm);
+    if (!record_file_writer_->WriteData(comm_record.BinaryFormat())) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RecordCommand::DumpAdditionalFeatures() {
+  size_t feature_count = (branch_sampling_ != 0 ? 2 : 1);
+  if (!record_file_writer_->WriteFeatureHeader(feature_count)) {
+    return false;
+  }
+  if (!DumpBuildIdFeature()) {
+    return false;
+  }
+  if (branch_sampling_ != 0 && !record_file_writer_->WriteBranchStackFeature()) {
+    return false;
+  }
+  return true;
+}
+
+bool RecordCommand::DumpBuildIdFeature() {
+  std::vector<std::string> hit_kernel_modules;
+  std::vector<std::string> hit_user_files;
+  if (!record_file_writer_->GetHitModules(&hit_kernel_modules, &hit_user_files)) {
+    return false;
+  }
+  std::vector<BuildIdRecord> build_id_records;
+  BuildId build_id;
+  // Add build_ids for kernel/modules.
+  for (auto& filename : hit_kernel_modules) {
+    if (filename == DEFAULT_KERNEL_MMAP_NAME) {
+      if (!GetKernelBuildId(&build_id)) {
+        LOG(DEBUG) << "can't read build_id for kernel";
+        continue;
+      }
+      build_id_records.push_back(
+          CreateBuildIdRecord(true, UINT_MAX, build_id, DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID));
+    } else {
+      std::string module_name = basename(&filename[0]);
+      if (android::base::EndsWith(module_name, ".ko")) {
+        module_name = module_name.substr(0, module_name.size() - 3);
+      }
+      if (!GetModuleBuildId(module_name, &build_id)) {
+        LOG(DEBUG) << "can't read build_id for module " << module_name;
+        continue;
+      }
+      build_id_records.push_back(CreateBuildIdRecord(true, UINT_MAX, build_id, filename));
+    }
+  }
+  // Add build_ids for user elf files.
+  for (auto& filename : hit_user_files) {
+    if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) {
+      continue;
+    }
+    if (!GetBuildIdFromElfFile(filename, &build_id)) {
+      LOG(DEBUG) << "can't read build_id from file " << filename;
+      continue;
+    }
+    build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, filename));
+  }
+  if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) {
+    return false;
+  }
+  return true;
+}
+
+__attribute__((constructor)) static void RegisterRecordCommand() {
+  RegisterCommand("record", [] { return std::unique_ptr<Command>(new RecordCommand()); });
+}
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
new file mode 100644
index 0000000..a4e2be6
--- /dev/null
+++ b/simpleperf/cmd_record_test.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <base/stringprintf.h>
+
+#include "command.h"
+#include "environment.h"
+#include "record.h"
+#include "record_file.h"
+#include "test_util.h"
+
+using namespace PerfFileFormat;
+
+static std::unique_ptr<Command> RecordCmd() {
+  return CreateCommandInstance("record");
+}
+
+TEST(record_cmd, no_options) {
+  ASSERT_TRUE(RecordCmd()->Run({"sleep", "1"}));
+}
+
+TEST(record_cmd, system_wide_option) {
+  ASSERT_TRUE(RecordCmd()->Run({"-a", "sleep", "1"}));
+}
+
+TEST(record_cmd, sample_period_option) {
+  ASSERT_TRUE(RecordCmd()->Run({"-c", "100000", "sleep", "1"}));
+}
+
+TEST(record_cmd, event_option) {
+  ASSERT_TRUE(RecordCmd()->Run({"-e", "cpu-clock", "sleep", "1"}));
+}
+
+TEST(record_cmd, freq_option) {
+  ASSERT_TRUE(RecordCmd()->Run({"-f", "99", "sleep", "1"}));
+  ASSERT_TRUE(RecordCmd()->Run({"-F", "99", "sleep", "1"}));
+}
+
+TEST(record_cmd, output_file_option) {
+  ASSERT_TRUE(RecordCmd()->Run({"-o", "perf2.data", "sleep", "1"}));
+}
+
+TEST(record_cmd, dump_kernel_mmap) {
+  ASSERT_TRUE(RecordCmd()->Run({"sleep", "1"}));
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance("perf.data");
+  ASSERT_TRUE(reader != nullptr);
+  std::vector<std::unique_ptr<const Record>> records = reader->DataSection();
+  ASSERT_GT(records.size(), 0U);
+  bool have_kernel_mmap = false;
+  for (auto& record : records) {
+    if (record->header.type == PERF_RECORD_MMAP) {
+      const MmapRecord* mmap_record = static_cast<const MmapRecord*>(record.get());
+      if (mmap_record->filename == DEFAULT_KERNEL_MMAP_NAME) {
+        have_kernel_mmap = true;
+        break;
+      }
+    }
+  }
+  ASSERT_TRUE(have_kernel_mmap);
+}
+
+TEST(record_cmd, dump_build_id_feature) {
+  ASSERT_TRUE(RecordCmd()->Run({"sleep", "1"}));
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance("perf.data");
+  ASSERT_TRUE(reader != nullptr);
+  const FileHeader* file_header = reader->FileHeader();
+  ASSERT_TRUE(file_header != nullptr);
+  ASSERT_TRUE(file_header->features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8)));
+  ASSERT_GT(reader->FeatureSectionDescriptors().size(), 0u);
+}
+
+TEST(record_cmd, tracepoint_event) {
+  ASSERT_TRUE(RecordCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"}));
+}
+
+extern bool IsBranchSamplingSupported();
+
+TEST(record_cmd, branch_sampling) {
+  if (IsBranchSamplingSupported()) {
+    ASSERT_TRUE(RecordCmd()->Run({"-a", "-b", "sleep", "1"}));
+    ASSERT_TRUE(RecordCmd()->Run({"-j", "any,any_call,any_ret,ind_call", "sleep", "1"}));
+    ASSERT_TRUE(RecordCmd()->Run({"-j", "any,k", "sleep", "1"}));
+    ASSERT_TRUE(RecordCmd()->Run({"-j", "any,u", "sleep", "1"}));
+    ASSERT_FALSE(RecordCmd()->Run({"-j", "u", "sleep", "1"}));
+  } else {
+    GTEST_LOG_(INFO)
+        << "This test does nothing as branch stack sampling is not supported on this device.";
+  }
+}
+
+TEST(record_cmd, event_modifier) {
+  ASSERT_TRUE(RecordCmd()->Run({"-e", "cpu-cycles:u", "sleep", "1"}));
+}
+
+TEST(record_cmd, callchain_sampling) {
+  ASSERT_TRUE(RecordCmd()->Run({"-g", "sleep", "1"}));
+}
+
+TEST(record_cmd, existing_processes) {
+  std::vector<std::unique_ptr<Workload>> workloads;
+  CreateProcesses(2, &workloads);
+  std::string pid_list =
+      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+  ASSERT_TRUE(RecordCmd()->Run({"-p", pid_list}));
+}
+
+TEST(record_cmd, existing_threads) {
+  std::vector<std::unique_ptr<Workload>> workloads;
+  CreateProcesses(2, &workloads);
+  // Process id can also be used as thread id in linux.
+  std::string tid_list =
+      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+  ASSERT_TRUE(RecordCmd()->Run({"-t", tid_list}));
+}
+
+TEST(record_cmd, no_monitored_threads) {
+  ASSERT_FALSE(RecordCmd()->Run({""}));
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
new file mode 100644
index 0000000..ffba9ea
--- /dev/null
+++ b/simpleperf/cmd_report.cpp
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <functional>
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+#include <base/strings.h>
+
+#include "command.h"
+#include "environment.h"
+#include "event_attr.h"
+#include "event_type.h"
+#include "record.h"
+#include "record_file.h"
+#include "sample_tree.h"
+
+typedef int (*compare_sample_entry_t)(const SampleEntry& sample1, const SampleEntry& sample2);
+typedef std::string (*print_sample_entry_header_t)();
+typedef std::string (*print_sample_entry_t)(const SampleEntry& sample);
+
+struct ReportItem {
+  size_t width;
+  compare_sample_entry_t compare_function;
+  print_sample_entry_header_t print_header_function;
+  print_sample_entry_t print_function;
+};
+
+static int ComparePid(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return sample1.thread->pid - sample2.thread->pid;
+}
+
+static std::string PrintHeaderPid() {
+  return "Pid";
+}
+
+static std::string PrintPid(const SampleEntry& sample) {
+  return android::base::StringPrintf("%d", sample.thread->pid);
+}
+
+static ReportItem report_pid = {
+    .compare_function = ComparePid,
+    .print_header_function = PrintHeaderPid,
+    .print_function = PrintPid,
+};
+
+static int CompareTid(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return sample1.thread->tid - sample2.thread->tid;
+}
+
+static std::string PrintHeaderTid() {
+  return "Tid";
+}
+
+static std::string PrintTid(const SampleEntry& sample) {
+  return android::base::StringPrintf("%d", sample.thread->tid);
+}
+
+static ReportItem report_tid = {
+    .compare_function = CompareTid,
+    .print_header_function = PrintHeaderTid,
+    .print_function = PrintTid,
+};
+
+static int CompareComm(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return strcmp(sample1.thread_comm, sample2.thread_comm);
+}
+
+static std::string PrintHeaderComm() {
+  return "Command";
+}
+
+static std::string PrintComm(const SampleEntry& sample) {
+  return sample.thread_comm;
+}
+
+static ReportItem report_comm = {
+    .compare_function = CompareComm,
+    .print_header_function = PrintHeaderComm,
+    .print_function = PrintComm,
+};
+
+static int CompareDso(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return strcmp(sample1.map->dso->path.c_str(), sample2.map->dso->path.c_str());
+}
+
+static std::string PrintHeaderDso() {
+  return "Shared Object";
+}
+
+static std::string PrintDso(const SampleEntry& sample) {
+  std::string filename = sample.map->dso->path;
+  if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) {
+    filename = "[unknown]";
+  }
+  return filename;
+}
+
+static ReportItem report_dso = {
+    .compare_function = CompareDso,
+    .print_header_function = PrintHeaderDso,
+    .print_function = PrintDso,
+};
+
+static int CompareSymbol(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return strcmp(sample1.symbol->name.c_str(), sample2.symbol->name.c_str());
+}
+
+static std::string PrintHeaderSymbol() {
+  return "Symbol";
+}
+
+static std::string PrintSymbol(const SampleEntry& sample) {
+  return sample.symbol->name;
+}
+
+static ReportItem report_symbol = {
+    .compare_function = CompareSymbol,
+    .print_header_function = PrintHeaderSymbol,
+    .print_function = PrintSymbol,
+};
+
+static std::string PrintHeaderSampleCount() {
+  return "Sample";
+}
+
+static std::string PrintSampleCount(const SampleEntry& sample) {
+  return android::base::StringPrintf("%" PRId64, sample.sample_count);
+}
+
+static ReportItem report_sample_count = {
+    .compare_function = nullptr,
+    .print_header_function = PrintHeaderSampleCount,
+    .print_function = PrintSampleCount,
+};
+
+static std::unordered_map<std::string, ReportItem*> report_item_map = {
+    {"comm", &report_comm}, {"pid", &report_pid},       {"tid", &report_tid},
+    {"dso", &report_dso},   {"symbol", &report_symbol},
+};
+
+class ReportCommand : public Command {
+ public:
+  ReportCommand()
+      : Command("report", "report sampling information in perf.data",
+                "Usage: simpleperf report [options]\n"
+                "    -i <file>     Specify path of record file, default is perf.data.\n"
+                "    -n            Print the sample count for each item.\n"
+                "    --no-demangle        Don't demangle symbol names.\n"
+                "    --sort key1,key2,... Select the keys to sort and print the report.\n"
+                "                         Possible keys include pid, tid, comm, dso, symbol.\n"
+                "                         Default keys are \"comm,pid,tid,dso,symbol\"\n"
+                "    --symfs <dir>  Look for files with symbols relative to this directory.\n"),
+        record_filename_("perf.data") {
+  }
+
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args);
+  bool ReadEventAttrFromRecordFile();
+  void ReadSampleTreeFromRecordFile();
+  int CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2);
+  void PrintReport();
+  void PrintReportContext();
+  void CollectReportWidth();
+  void CollectReportEntryWidth(const SampleEntry& sample);
+  void PrintReportHeader();
+  void PrintReportEntry(const SampleEntry& sample);
+
+  std::string record_filename_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+  perf_event_attr event_attr_;
+  std::vector<ReportItem*> report_items_;
+  std::unique_ptr<SampleTree> sample_tree_;
+};
+
+bool ReportCommand::Run(const std::vector<std::string>& args) {
+  // 1. Parse options.
+  if (!ParseOptions(args)) {
+    return false;
+  }
+
+  // 2. Read record file and build SampleTree.
+  record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+  if (record_file_reader_ == nullptr) {
+    return false;
+  }
+  if (!ReadEventAttrFromRecordFile()) {
+    return false;
+  }
+  ReadSampleTreeFromRecordFile();
+
+  // 3. Read symbol table from elf files.
+
+  // 4. Show collected information.
+  PrintReport();
+
+  return true;
+}
+
+bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
+  bool print_sample_count = false;
+  for (size_t i = 0; i < args.size(); ++i) {
+    if (args[i] == "-i") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      record_filename_ = args[i];
+
+    } else if (args[i] == "-n") {
+      print_sample_count = true;
+
+    } else if (args[i] == "--no-demangle") {
+      DsoFactory::SetDemangle(false);
+
+    } else if (args[i] == "--sort") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> sort_keys = android::base::Split(args[i], ",");
+      for (auto& key : sort_keys) {
+        auto it = report_item_map.find(key);
+        if (it != report_item_map.end()) {
+          report_items_.push_back(it->second);
+        } else {
+          LOG(ERROR) << "Unknown sort key: " << key;
+          return false;
+        }
+      }
+    } else if (args[i] == "--symfs") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!DsoFactory::SetSymFsDir(args[i])) {
+        return false;
+      }
+    } else {
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+
+  if (report_items_.empty()) {
+    report_items_.push_back(report_item_map["comm"]);
+    report_items_.push_back(report_item_map["pid"]);
+    report_items_.push_back(report_item_map["tid"]);
+    report_items_.push_back(report_item_map["dso"]);
+    report_items_.push_back(report_item_map["symbol"]);
+  }
+  if (print_sample_count) {
+    report_items_.insert(report_items_.begin(), &report_sample_count);
+  }
+  return true;
+}
+
+bool ReportCommand::ReadEventAttrFromRecordFile() {
+  std::vector<const PerfFileFormat::FileAttr*> attrs = record_file_reader_->AttrSection();
+  if (attrs.size() != 1) {
+    LOG(ERROR) << "record file contains " << attrs.size() << " attrs";
+    return false;
+  }
+  event_attr_ = attrs[0]->attr;
+  return true;
+}
+
+void ReportCommand::ReadSampleTreeFromRecordFile() {
+  compare_sample_func_t compare_sample_callback = std::bind(
+      &ReportCommand::CompareSampleEntry, this, std::placeholders::_1, std::placeholders::_2);
+  sample_tree_ = std::unique_ptr<SampleTree>(new SampleTree(compare_sample_callback));
+  sample_tree_->AddThread(0, 0, "swapper");
+
+  std::vector<std::unique_ptr<const Record>> records = record_file_reader_->DataSection();
+  for (auto& record : records) {
+    if (record->header.type == PERF_RECORD_MMAP) {
+      const MmapRecord& r = *static_cast<const MmapRecord*>(record.get());
+      if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) {
+        sample_tree_->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff,
+                                   r.sample_id.time_data.time, r.filename);
+      } else {
+        sample_tree_->AddThreadMap(r.data.pid, r.data.tid, r.data.addr, r.data.len, r.data.pgoff,
+                                   r.sample_id.time_data.time, r.filename);
+      }
+    } else if (record->header.type == PERF_RECORD_MMAP2) {
+      const Mmap2Record& r = *static_cast<const Mmap2Record*>(record.get());
+      if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) {
+        sample_tree_->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff,
+                                   r.sample_id.time_data.time, r.filename);
+      } else {
+        sample_tree_->AddThreadMap(r.data.pid, r.data.tid, r.data.addr, r.data.len, r.data.pgoff,
+                                   r.sample_id.time_data.time, r.filename);
+      }
+    } else if (record->header.type == PERF_RECORD_SAMPLE) {
+      const SampleRecord& r = *static_cast<const SampleRecord*>(record.get());
+      bool in_kernel = (r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL;
+      sample_tree_->AddSample(r.tid_data.pid, r.tid_data.tid, r.ip_data.ip, r.time_data.time,
+                              r.period_data.period, in_kernel);
+    } else if (record->header.type == PERF_RECORD_COMM) {
+      const CommRecord& r = *static_cast<const CommRecord*>(record.get());
+      sample_tree_->AddThread(r.data.pid, r.data.tid, r.comm);
+    } else if (record->header.type == PERF_RECORD_FORK) {
+      const ForkRecord& r = *static_cast<const ForkRecord*>(record.get());
+      sample_tree_->ForkThread(r.data.pid, r.data.tid, r.data.ppid, r.data.ptid);
+    }
+  }
+}
+
+int ReportCommand::CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2) {
+  for (auto& item : report_items_) {
+    if (item->compare_function != nullptr) {
+      int result = item->compare_function(sample1, sample2);
+      if (result != 0) {
+        return result;
+      }
+    }
+  }
+  return 0;
+}
+
+void ReportCommand::PrintReport() {
+  PrintReportContext();
+  CollectReportWidth();
+  PrintReportHeader();
+  sample_tree_->VisitAllSamples(
+      std::bind(&ReportCommand::PrintReportEntry, this, std::placeholders::_1));
+  fflush(stdout);
+}
+
+void ReportCommand::PrintReportContext() {
+  const EventType* event_type = FindEventTypeByConfig(event_attr_.type, event_attr_.config);
+  std::string event_type_name;
+  if (event_type != nullptr) {
+    event_type_name = event_type->name;
+  } else {
+    event_type_name =
+        android::base::StringPrintf("(type %u, config %llu)", event_attr_.type, event_attr_.config);
+  }
+  printf("Samples: %" PRIu64 " of event '%s'\n", sample_tree_->TotalSamples(),
+         event_type_name.c_str());
+  printf("Event count: %" PRIu64 "\n\n", sample_tree_->TotalPeriod());
+}
+
+void ReportCommand::CollectReportWidth() {
+  for (auto& item : report_items_) {
+    std::string s = item->print_header_function();
+    item->width = s.size();
+  }
+  sample_tree_->VisitAllSamples(
+      std::bind(&ReportCommand::CollectReportEntryWidth, this, std::placeholders::_1));
+}
+
+void ReportCommand::CollectReportEntryWidth(const SampleEntry& sample) {
+  for (auto& item : report_items_) {
+    std::string s = item->print_function(sample);
+    item->width = std::max(item->width, s.size());
+  }
+}
+
+void ReportCommand::PrintReportHeader() {
+  printf("%8s", "Overhead");
+  for (size_t i = 0; i < report_items_.size(); ++i) {
+    auto& item = report_items_[i];
+    printf("  ");
+    std::string s = item->print_header_function();
+    printf("%-*s", (i + 1 == report_items_.size()) ? 0 : static_cast<int>(item->width), s.c_str());
+  }
+  printf("\n");
+}
+
+void ReportCommand::PrintReportEntry(const SampleEntry& sample) {
+  double percentage = 0.0;
+  if (sample_tree_->TotalPeriod() != 0) {
+    percentage = 100.0 * sample.period / sample_tree_->TotalPeriod();
+  }
+  printf("%7.2lf%%", percentage);
+  for (size_t i = 0; i < report_items_.size(); ++i) {
+    auto& item = report_items_[i];
+    printf("  ");
+    std::string s = item->print_function(sample);
+    printf("%-*s", (i + 1 == report_items_.size()) ? 0 : static_cast<int>(item->width), s.c_str());
+  }
+  printf("\n");
+}
+
+__attribute__((constructor)) static void RegisterReportCommand() {
+  RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); });
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
new file mode 100644
index 0000000..8281a15
--- /dev/null
+++ b/simpleperf/cmd_report_test.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "command.h"
+
+static std::unique_ptr<Command> RecordCmd() {
+  return CreateCommandInstance("record");
+}
+
+static std::unique_ptr<Command> ReportCmd() {
+  return CreateCommandInstance("report");
+}
+
+class ReportCommandTest : public ::testing::Test {
+ protected:
+  static void SetUpTestCase() {
+    ASSERT_TRUE(RecordCmd()->Run({"-a", "sleep", "1"}));
+    ASSERT_TRUE(RecordCmd()->Run({"-a", "-o", "perf2.data", "sleep", "1"}));
+  }
+};
+
+TEST_F(ReportCommandTest, no_options) {
+  ASSERT_TRUE(ReportCmd()->Run({}));
+}
+
+TEST_F(ReportCommandTest, input_file_option) {
+  ASSERT_TRUE(ReportCmd()->Run({"-i", "perf2.data"}));
+}
+
+TEST_F(ReportCommandTest, sort_option_pid) {
+  ASSERT_TRUE(ReportCmd()->Run({"--sort", "pid"}));
+}
+
+TEST_F(ReportCommandTest, sort_option_all) {
+  ASSERT_TRUE(ReportCmd()->Run({"--sort", "comm,pid,dso,symbol"}));
+}
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
new file mode 100644
index 0000000..83a4a3f
--- /dev/null
+++ b/simpleperf/cmd_stat.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+#include <chrono>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings.h>
+
+#include "command.h"
+#include "environment.h"
+#include "event_selection_set.h"
+#include "event_type.h"
+#include "perf_event.h"
+#include "utils.h"
+#include "workload.h"
+
+static std::vector<std::string> default_measured_event_types{
+    "cpu-cycles",   "stalled-cycles-frontend", "stalled-cycles-backend",
+    "instructions", "branch-instructions",     "branch-misses",
+    "task-clock",   "context-switches",        "page-faults",
+};
+
+static volatile bool signaled;
+static void signal_handler(int) {
+  signaled = true;
+}
+
+class StatCommand : public Command {
+ public:
+  StatCommand()
+      : Command("stat", "gather performance counter information",
+                "Usage: simpleperf stat [options] [command [command-args]]\n"
+                "    Gather performance counter information of running [command].\n"
+                "    -a           Collect system-wide information.\n"
+                "    -e event1[:modifier1],event2[:modifier2],...\n"
+                "                 Select the event list to count. Use `simpleperf list` to find\n"
+                "                 all possible event names. Modifiers can be added to define\n"
+                "                 how the event should be monitored. Possible modifiers are:\n"
+                "                   u - monitor user space events only\n"
+                "                   k - monitor kernel space events only\n"
+                "    --no-inherit\n"
+                "                 Don't stat created child threads/processes.\n"
+                "    -p pid1,pid2,...\n"
+                "                 Stat events on existing processes. Mutually exclusive with -a.\n"
+                "    -t tid1,tid2,...\n"
+                "                 Stat events on existing threads. Mutually exclusive with -a.\n"
+                "    --verbose    Show result in verbose mode.\n"),
+        verbose_mode_(false),
+        system_wide_collection_(false),
+        child_inherit_(true) {
+    signaled = false;
+    signal_handler_register_.reset(
+        new SignalHandlerRegister({SIGCHLD, SIGINT, SIGTERM}, signal_handler));
+  }
+
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
+  bool AddMeasuredEventType(const std::string& event_type_name, bool report_unsupported_type = true);
+  bool AddDefaultMeasuredEventTypes();
+  void SetEventSelection();
+  bool ShowCounters(const std::map<const EventType*, std::vector<PerfCounter>>& counters_map,
+                    std::chrono::steady_clock::duration counting_duration);
+
+  bool verbose_mode_;
+  bool system_wide_collection_;
+  bool child_inherit_;
+  std::vector<pid_t> monitored_threads_;
+  std::vector<std::pair<std::string, EventTypeAndModifier>> measured_event_types_;
+  EventSelectionSet event_selection_set_;
+
+  std::unique_ptr<SignalHandlerRegister> signal_handler_register_;
+};
+
+bool StatCommand::Run(const std::vector<std::string>& args) {
+  // 1. Parse options, and use default measured event types if not given.
+  std::vector<std::string> workload_args;
+  if (!ParseOptions(args, &workload_args)) {
+    return false;
+  }
+  if (measured_event_types_.empty()) {
+    if (!AddDefaultMeasuredEventTypes()) {
+      return false;
+    }
+  }
+  SetEventSelection();
+
+  // 2. Create workload.
+  std::unique_ptr<Workload> workload;
+  if (!workload_args.empty()) {
+    workload = Workload::CreateWorkload(workload_args);
+    if (workload == nullptr) {
+      return false;
+    }
+  }
+  if (!system_wide_collection_ && monitored_threads_.empty()) {
+    if (workload != nullptr) {
+      monitored_threads_.push_back(workload->GetPid());
+      event_selection_set_.SetEnableOnExec(true);
+    } else {
+      LOG(ERROR) << "No threads to monitor. Try `simpleperf help stat` for help\n";
+      return false;
+    }
+  }
+
+  // 3. Open perf_event_files.
+  if (system_wide_collection_) {
+    if (!event_selection_set_.OpenEventFilesForAllCpus()) {
+      return false;
+    }
+  } else {
+    if (!event_selection_set_.OpenEventFilesForThreads(monitored_threads_)) {
+      return false;
+    }
+  }
+
+  // 4. Count events while workload running.
+  auto start_time = std::chrono::steady_clock::now();
+  if (!event_selection_set_.GetEnableOnExec()) {
+    if (!event_selection_set_.EnableEvents()) {
+      return false;
+    }
+  }
+  if (workload != nullptr && !workload->Start()) {
+    return false;
+  }
+  while (!signaled) {
+    sleep(1);
+  }
+  auto end_time = std::chrono::steady_clock::now();
+
+  // 5. Read and print counters.
+  std::map<const EventType*, std::vector<PerfCounter>> counters_map;
+  if (!event_selection_set_.ReadCounters(&counters_map)) {
+    return false;
+  }
+  if (!ShowCounters(counters_map, end_time - start_time)) {
+    return false;
+  }
+  return true;
+}
+
+bool StatCommand::ParseOptions(const std::vector<std::string>& args,
+                               std::vector<std::string>* non_option_args) {
+  std::set<pid_t> tid_set;
+  size_t i;
+  for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
+    if (args[i] == "-a") {
+      system_wide_collection_ = true;
+    } else if (args[i] == "-e") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> event_types = android::base::Split(args[i], ",");
+      for (auto& event_type : event_types) {
+        if (!AddMeasuredEventType(event_type)) {
+          return false;
+        }
+      }
+    } else if (args[i] == "--no-inherit") {
+      child_inherit_ = false;
+    } else if (args[i] == "-p") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!GetValidThreadsFromProcessString(args[i], &tid_set)) {
+        return false;
+      }
+    } else if (args[i] == "-t") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!GetValidThreadsFromThreadString(args[i], &tid_set)) {
+        return false;
+      }
+    } else if (args[i] == "--verbose") {
+      verbose_mode_ = true;
+    } else {
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+
+  monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end());
+  if (system_wide_collection_ && !monitored_threads_.empty()) {
+    LOG(ERROR) << "Stat system wide and existing processes/threads can't be used at the same time.";
+    return false;
+  }
+
+  if (non_option_args != nullptr) {
+    non_option_args->clear();
+    for (; i < args.size(); ++i) {
+      non_option_args->push_back(args[i]);
+    }
+  }
+  return true;
+}
+
+bool StatCommand::AddMeasuredEventType(const std::string& event_type_name,
+                                       bool report_unsupported_type) {
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier =
+      ParseEventType(event_type_name, report_unsupported_type);
+  if (event_type_modifier == nullptr) {
+    return false;
+  }
+  measured_event_types_.push_back(std::make_pair(event_type_name, *event_type_modifier));
+  return true;
+}
+
+bool StatCommand::AddDefaultMeasuredEventTypes() {
+  for (auto& name : default_measured_event_types) {
+    // It is not an error when some event types in the default list are not supported by the kernel.
+    AddMeasuredEventType(name, false);
+  }
+  if (measured_event_types_.empty()) {
+    LOG(ERROR) << "Failed to add any supported default measured types";
+    return false;
+  }
+  return true;
+}
+
+void StatCommand::SetEventSelection() {
+  for (auto& pair : measured_event_types_) {
+    event_selection_set_.AddEventType(pair.second);
+  }
+  event_selection_set_.SetInherit(child_inherit_);
+}
+
+bool StatCommand::ShowCounters(
+    const std::map<const EventType*, std::vector<PerfCounter>>& counters_map,
+    std::chrono::steady_clock::duration counting_duration) {
+  printf("Performance counter statistics:\n\n");
+
+  for (auto& pair : counters_map) {
+    auto& event_type = pair.first;
+    auto& counters = pair.second;
+    if (verbose_mode_) {
+      for (auto& counter : counters) {
+        printf("%s: value %'" PRId64 ", time_enabled %" PRId64 ", time_running %" PRId64
+               ", id %" PRId64 "\n",
+               event_selection_set_.FindEventFileNameById(counter.id).c_str(), counter.value,
+               counter.time_enabled, counter.time_running, counter.id);
+      }
+    }
+
+    PerfCounter sum_counter;
+    memset(&sum_counter, 0, sizeof(sum_counter));
+    for (auto& counter : counters) {
+      sum_counter.value += counter.value;
+      sum_counter.time_enabled += counter.time_enabled;
+      sum_counter.time_running += counter.time_running;
+    }
+    bool scaled = false;
+    int64_t scaled_count = sum_counter.value;
+    if (sum_counter.time_running < sum_counter.time_enabled) {
+      if (sum_counter.time_running == 0) {
+        scaled_count = 0;
+      } else {
+        scaled = true;
+        scaled_count = static_cast<int64_t>(static_cast<double>(sum_counter.value) *
+                                            sum_counter.time_enabled / sum_counter.time_running);
+      }
+    }
+    std::string event_type_name;
+    for (auto& pair : measured_event_types_) {
+      if (pair.second.event_type.name == event_type->name) {
+        event_type_name = pair.first;
+      }
+    }
+    printf("%'30" PRId64 "%s  %s\n", scaled_count, scaled ? "(scaled)" : "       ",
+           event_type_name.c_str());
+  }
+  printf("\nTotal test time: %lf seconds.\n",
+         std::chrono::duration_cast<std::chrono::duration<double>>(counting_duration).count());
+  return true;
+}
+
+__attribute__((constructor)) static void RegisterStatCommand() {
+  RegisterCommand("stat", [] { return std::unique_ptr<Command>(new StatCommand); });
+}
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
new file mode 100644
index 0000000..c6c4ef7
--- /dev/null
+++ b/simpleperf/cmd_stat_test.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <base/stringprintf.h>
+
+#include "command.h"
+#include "test_util.h"
+
+static std::unique_ptr<Command> StatCmd() {
+  return CreateCommandInstance("stat");
+}
+
+TEST(stat_cmd, no_options) {
+  ASSERT_TRUE(StatCmd()->Run({"sleep", "1"}));
+}
+
+TEST(stat_cmd, event_option) {
+  ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-clock,task-clock", "sleep", "1"}));
+}
+
+TEST(stat_cmd, system_wide_option) {
+  ASSERT_TRUE(StatCmd()->Run({"-a", "sleep", "1"}));
+}
+
+TEST(stat_cmd, verbose_option) {
+  ASSERT_TRUE(StatCmd()->Run({"--verbose", "sleep", "1"}));
+}
+
+TEST(stat_cmd, tracepoint_event) {
+  ASSERT_TRUE(StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"}));
+}
+
+TEST(stat_cmd, event_modifier) {
+  ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-cycles:u,sched:sched_switch:k", "sleep", "1"}));
+}
+
+TEST(stat_cmd, existing_processes) {
+  std::vector<std::unique_ptr<Workload>> workloads;
+  CreateProcesses(2, &workloads);
+  std::string pid_list =
+      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+  ASSERT_TRUE(StatCmd()->Run({"-p", pid_list}));
+}
+
+TEST(stat_cmd, existing_threads) {
+  std::vector<std::unique_ptr<Workload>> workloads;
+  CreateProcesses(2, &workloads);
+  // Process id can be used as thread id in linux.
+  std::string tid_list =
+      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+  ASSERT_TRUE(StatCmd()->Run({"-t", tid_list}));
+}
+
+TEST(stat_cmd, no_monitored_threads) {
+  ASSERT_FALSE(StatCmd()->Run({""}));
+}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
new file mode 100644
index 0000000..8889d7f
--- /dev/null
+++ b/simpleperf/command.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "command.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+
+bool Command::NextArgumentOrError(const std::vector<std::string>& args, size_t* pi) {
+  if (*pi + 1 == args.size()) {
+    LOG(ERROR) << "No argument following " << args[*pi] << " option. Try `simpleperf help " << name_
+               << "`";
+    return false;
+  }
+  ++*pi;
+  return true;
+}
+
+void Command::ReportUnknownOption(const std::vector<std::string>& args, size_t i) {
+  LOG(ERROR) << "Unknown option for " << name_ << " command: '" << args[i]
+             << "'. Try `simpleperf help " << name_ << "`";
+}
+
+typedef std::function<std::unique_ptr<Command>(void)> callback_t;
+
+static std::map<std::string, callback_t>& CommandMap() {
+  // commands is used in the constructor of Command. Defining it as a static
+  // variable in a function makes sure it is initialized before use.
+  static std::map<std::string, callback_t> command_map;
+  return command_map;
+}
+
+void RegisterCommand(const std::string& cmd_name,
+                     std::function<std::unique_ptr<Command>(void)> callback) {
+  CommandMap().insert(std::make_pair(cmd_name, callback));
+}
+
+void UnRegisterCommand(const std::string& cmd_name) {
+  CommandMap().erase(cmd_name);
+}
+
+std::unique_ptr<Command> CreateCommandInstance(const std::string& cmd_name) {
+  auto it = CommandMap().find(cmd_name);
+  return (it == CommandMap().end()) ? nullptr : (it->second)();
+}
+
+const std::vector<std::string> GetAllCommandNames() {
+  std::vector<std::string> names;
+  for (auto pair : CommandMap()) {
+    names.push_back(pair.first);
+  }
+  return names;
+}
diff --git a/simpleperf/command.h b/simpleperf/command.h
new file mode 100644
index 0000000..e2c8453
--- /dev/null
+++ b/simpleperf/command.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_COMMAND_H_
+#define SIMPLE_PERF_COMMAND_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+class Command {
+ public:
+  Command(const std::string& name, const std::string& short_help_string,
+          const std::string& long_help_string)
+      : name_(name), short_help_string_(short_help_string), long_help_string_(long_help_string) {
+  }
+
+  virtual ~Command() {
+  }
+
+  const std::string& Name() const {
+    return name_;
+  }
+
+  const std::string& ShortHelpString() const {
+    return short_help_string_;
+  }
+
+  const std::string LongHelpString() const {
+    return long_help_string_;
+  }
+
+  virtual bool Run(const std::vector<std::string>& args) = 0;
+
+ protected:
+  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_;
+
+  DISALLOW_COPY_AND_ASSIGN(Command);
+};
+
+void RegisterCommand(const std::string& cmd_name,
+                     std::function<std::unique_ptr<Command>(void)> callback);
+void UnRegisterCommand(const std::string& cmd_name);
+std::unique_ptr<Command> CreateCommandInstance(const std::string& cmd_name);
+const std::vector<std::string> GetAllCommandNames();
+
+#endif  // SIMPLE_PERF_COMMAND_H_
diff --git a/simpleperf/command_test.cpp b/simpleperf/command_test.cpp
new file mode 100644
index 0000000..18cb569
--- /dev/null
+++ b/simpleperf/command_test.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "command.h"
+
+class MockCommand : public Command {
+ public:
+  MockCommand() : Command("mock", "mock_short_help", "mock_long_help") {
+  }
+
+  bool Run(const std::vector<std::string>&) override {
+    return true;
+  }
+};
+
+TEST(command, CreateCommandInstance) {
+  ASSERT_TRUE(CreateCommandInstance("mock1") == nullptr);
+  RegisterCommand("mock1", [] { return std::unique_ptr<Command>(new MockCommand); });
+  ASSERT_TRUE(CreateCommandInstance("mock1") != nullptr);
+  UnRegisterCommand("mock1");
+  ASSERT_TRUE(CreateCommandInstance("mock1") == nullptr);
+}
+
+TEST(command, GetAllCommands) {
+  size_t command_count = GetAllCommandNames().size();
+  RegisterCommand("mock1", [] { return std::unique_ptr<Command>(new MockCommand); });
+  ASSERT_EQ(command_count + 1, GetAllCommandNames().size());
+  UnRegisterCommand("mock1");
+  ASSERT_EQ(command_count, GetAllCommandNames().size());
+}
diff --git a/simpleperf/cpu_offline_test.cpp b/simpleperf/cpu_offline_test.cpp
new file mode 100644
index 0000000..723518a
--- /dev/null
+++ b/simpleperf/cpu_offline_test.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <sys/stat.h>
+
+#include <base/file.h>
+
+#include "event_attr.h"
+#include "event_fd.h"
+#include "event_type.h"
+
+static std::unique_ptr<EventFd> OpenHardwareEventOnCpu0() {
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+  if (event_type_modifier == nullptr) {
+    return nullptr;
+  }
+  perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+  return EventFd::OpenEventFile(attr, getpid(), 0);
+}
+
+static const char* cpu1_online_path = "/sys/devices/system/cpu/cpu1/online";
+
+static bool HaveCpuOne() {
+  struct stat st;
+  return (stat(cpu1_online_path, &st) == 0 && S_ISREG(st.st_mode));
+}
+
+static void IsCpuOneOnline(bool* online, bool* has_error) {
+  std::string content;
+  *has_error = true;
+  ASSERT_TRUE(android::base::ReadFileToString(cpu1_online_path, &content));
+  ASSERT_GT(content.size(), 0U);
+  *has_error = false;
+  *online = (content[0] == '0') ? false : true;
+}
+
+static void SetCpuOneOnline(bool online, bool* has_error, bool* interrupted) {
+  *interrupted = false;
+  errno = 0;
+  int ret = android::base::WriteStringToFile(online ? "1" : "0", cpu1_online_path);
+  int saved_errno = errno;
+  bool new_state;
+  IsCpuOneOnline(&new_state, has_error);
+  if (*has_error) {
+    return;
+  }
+  if (new_state == online) {
+    return;
+  } else if (ret) {
+    *interrupted = true;
+  } else {
+    *has_error = true;
+    FAIL() << "Failed to SetCpuOneOnline, online = " << online
+           << ", error = " << strerror(saved_errno) << ", new_state = " << new_state;
+  }
+}
+
+// On some devices like flo, the kernel can't work correctly if a cpu
+// is offlined when perf is monitoring a hardware event.
+TEST(cpu_offline, smoke) {
+  if (!HaveCpuOne()) {
+    GTEST_LOG_(INFO) << "This test does nothing on uniprocessor devices.";
+    return;
+  }
+
+  bool has_error;
+  bool interrupted;
+  bool saved_online;
+  bool success = false;
+  IsCpuOneOnline(&saved_online, &has_error);
+  // A loop is used in case the test is interrupted by other processes controling cpu hotplug, like
+  // mpdecision.
+  for (size_t loop_count = 0; !has_error && loop_count < 50; ++loop_count) {
+    SetCpuOneOnline(true, &has_error, &interrupted);
+    if (has_error || interrupted) {
+      continue;
+    }
+
+    std::unique_ptr<EventFd> event_fd = OpenHardwareEventOnCpu0();
+    ASSERT_TRUE(event_fd != nullptr);
+
+    bool online;
+    IsCpuOneOnline(&online, &has_error);
+    if (has_error || !online) {
+      continue;
+    }
+    SetCpuOneOnline(false, &has_error, &interrupted);
+    if (has_error || interrupted) {
+      continue;
+    }
+
+    event_fd = nullptr;
+    event_fd = OpenHardwareEventOnCpu0();
+    ASSERT_TRUE(event_fd != nullptr);
+    success = true;
+    break;
+  }
+  SetCpuOneOnline(saved_online, &has_error, &interrupted);
+  ASSERT_TRUE(success);
+}
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
new file mode 100644
index 0000000..b5880ca
--- /dev/null
+++ b/simpleperf/dso.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dso.h"
+
+#include <stdlib.h>
+#include <base/logging.h>
+#include "environment.h"
+#include "read_elf.h"
+#include "utils.h"
+
+bool SymbolComparator::operator()(const std::unique_ptr<SymbolEntry>& symbol1,
+                                  const std::unique_ptr<SymbolEntry>& symbol2) {
+  return symbol1->addr < symbol2->addr;
+}
+
+const SymbolEntry* DsoEntry::FindSymbol(uint64_t offset_in_dso) {
+  std::unique_ptr<SymbolEntry> symbol(new SymbolEntry{
+      .name = "", .addr = offset_in_dso, .len = 0,
+  });
+
+  auto it = symbols.upper_bound(symbol);
+  if (it != symbols.begin()) {
+    --it;
+    if ((*it)->addr <= offset_in_dso && (*it)->addr + (*it)->len > offset_in_dso) {
+      return (*it).get();
+    }
+  }
+  return nullptr;
+}
+
+bool DsoFactory::demangle = true;
+
+void DsoFactory::SetDemangle(bool demangle) {
+  DsoFactory::demangle = demangle;
+}
+
+std::string DsoFactory::symfs_dir;
+
+bool DsoFactory::SetSymFsDir(const std::string& symfs_dir) {
+  std::string dirname = symfs_dir;
+  if (!dirname.empty() && dirname.back() != '/') {
+    dirname.push_back('/');
+  }
+  std::vector<std::string> files;
+  std::vector<std::string> subdirs;
+  GetEntriesInDir(symfs_dir, &files, &subdirs);
+  if (files.empty() && subdirs.empty()) {
+    LOG(ERROR) << "Invalid symfs_dir '" << symfs_dir << "'";
+    return false;
+  }
+  DsoFactory::symfs_dir = dirname;
+  return true;
+}
+
+static bool IsKernelFunctionSymbol(const KernelSymbol& symbol) {
+  return (symbol.type == 'T' || symbol.type == 't' || symbol.type == 'W' || symbol.type == 'w');
+}
+
+static bool KernelSymbolCallback(const KernelSymbol& kernel_symbol, DsoEntry* dso) {
+  if (IsKernelFunctionSymbol(kernel_symbol)) {
+    SymbolEntry* symbol = new SymbolEntry{
+        .name = kernel_symbol.name, .addr = kernel_symbol.addr, .len = 0,
+    };
+    dso->symbols.insert(std::unique_ptr<SymbolEntry>(symbol));
+  }
+  return false;
+}
+
+static void FixupSymbolLength(DsoEntry* dso) {
+  SymbolEntry* prev_symbol = nullptr;
+  for (auto& symbol : dso->symbols) {
+    if (prev_symbol != nullptr && prev_symbol->len == 0) {
+      prev_symbol->len = symbol->addr - prev_symbol->addr;
+    }
+    prev_symbol = symbol.get();
+  }
+  if (prev_symbol != nullptr && prev_symbol->len == 0) {
+    prev_symbol->len = ULLONG_MAX - prev_symbol->addr;
+  }
+}
+
+std::unique_ptr<DsoEntry> DsoFactory::LoadKernel() {
+  std::unique_ptr<DsoEntry> dso(new DsoEntry);
+  dso->path = "[kernel.kallsyms]";
+
+  ProcessKernelSymbols("/proc/kallsyms",
+                       std::bind(&KernelSymbolCallback, std::placeholders::_1, dso.get()));
+  FixupSymbolLength(dso.get());
+  return dso;
+}
+
+static void ParseSymbolCallback(const ElfFileSymbol& elf_symbol, DsoEntry* dso,
+                                bool (*filter)(const ElfFileSymbol&)) {
+  if (filter(elf_symbol)) {
+    SymbolEntry* symbol = new SymbolEntry{
+        .name = elf_symbol.name, .addr = elf_symbol.start_in_file, .len = elf_symbol.len,
+    };
+    dso->symbols.insert(std::unique_ptr<SymbolEntry>(symbol));
+  }
+}
+
+static bool SymbolFilterForKernelModule(const ElfFileSymbol& elf_symbol) {
+  // TODO: Parse symbol outside of .text section.
+  return (elf_symbol.is_func && elf_symbol.is_in_text_section);
+}
+
+std::unique_ptr<DsoEntry> DsoFactory::LoadKernelModule(const std::string& dso_path) {
+  std::unique_ptr<DsoEntry> dso(new DsoEntry);
+  dso->path = dso_path;
+  ParseSymbolsFromElfFile(symfs_dir + dso_path, std::bind(ParseSymbolCallback, std::placeholders::_1,
+                                                          dso.get(), SymbolFilterForKernelModule));
+  FixupSymbolLength(dso.get());
+  return dso;
+}
+
+static bool SymbolFilterForDso(const ElfFileSymbol& elf_symbol) {
+  return elf_symbol.is_func || (elf_symbol.is_label && elf_symbol.is_in_text_section);
+}
+
+extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status);
+
+static void DemangleInPlace(std::string* name) {
+  int status;
+  bool is_linker_symbol = (name->find(linker_prefix) == 0);
+  const char* mangled_str = name->c_str();
+  if (is_linker_symbol) {
+    mangled_str += linker_prefix.size();
+  }
+  char* demangled_name = __cxa_demangle(mangled_str, nullptr, nullptr, &status);
+  if (status == 0) {
+    if (is_linker_symbol) {
+      *name = std::string("[linker]") + demangled_name;
+    } else {
+      *name = demangled_name;
+    }
+    free(demangled_name);
+  } else if (is_linker_symbol) {
+    std::string temp = std::string("[linker]") + mangled_str;
+    *name = std::move(temp);
+  }
+}
+
+std::unique_ptr<DsoEntry> DsoFactory::LoadDso(const std::string& dso_path) {
+  std::unique_ptr<DsoEntry> dso(new DsoEntry);
+  dso->path = dso_path;
+  ParseSymbolsFromElfFile(symfs_dir + dso_path, std::bind(ParseSymbolCallback, std::placeholders::_1,
+                                                          dso.get(), SymbolFilterForDso));
+  if (demangle) {
+    for (auto& symbol : dso->symbols) {
+      DemangleInPlace(&symbol->name);
+    }
+  }
+  FixupSymbolLength(dso.get());
+  return dso;
+}
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
new file mode 100644
index 0000000..2d79c92
--- /dev/null
+++ b/simpleperf/dso.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_DSO_H_
+#define SIMPLE_PERF_DSO_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+struct SymbolEntry {
+  std::string name;
+  uint64_t addr;
+  uint64_t len;
+};
+
+struct SymbolComparator {
+  bool operator()(const std::unique_ptr<SymbolEntry>& symbol1,
+                  const std::unique_ptr<SymbolEntry>& symbol2);
+};
+
+struct DsoEntry {
+  std::string path;
+  std::set<std::unique_ptr<SymbolEntry>, SymbolComparator> symbols;
+
+  const SymbolEntry* FindSymbol(uint64_t offset_in_dso);
+};
+
+class DsoFactory {
+ public:
+  static void SetDemangle(bool demangle);
+  static bool SetSymFsDir(const std::string& symfs_dir);
+  static std::unique_ptr<DsoEntry> LoadKernel();
+  static std::unique_ptr<DsoEntry> LoadKernelModule(const std::string& dso_path);
+  static std::unique_ptr<DsoEntry> LoadDso(const std::string& dso_path);
+
+ private:
+  static bool demangle;
+  static std::string symfs_dir;
+};
+
+#endif  // SIMPLE_PERF_DSO_H_
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
new file mode 100644
index 0000000..f4e98e6
--- /dev/null
+++ b/simpleperf/environment.cpp
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "environment.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unordered_map>
+#include <vector>
+
+#include <base/file.h>
+#include <base/logging.h>
+#include <base/strings.h>
+#include <base/stringprintf.h>
+
+#include "read_elf.h"
+#include "utils.h"
+
+std::vector<int> GetOnlineCpus() {
+  std::vector<int> result;
+  FILE* fp = fopen("/sys/devices/system/cpu/online", "re");
+  if (fp == nullptr) {
+    PLOG(ERROR) << "can't open online cpu information";
+    return result;
+  }
+
+  LineReader reader(fp);
+  char* line;
+  if ((line = reader.ReadLine()) != nullptr) {
+    result = GetOnlineCpusFromString(line);
+  }
+  CHECK(!result.empty()) << "can't get online cpu information";
+  return result;
+}
+
+std::vector<int> GetOnlineCpusFromString(const std::string& s) {
+  std::vector<int> result;
+  bool have_dash = false;
+  const char* p = s.c_str();
+  char* endp;
+  long cpu;
+  // Parse line like: 0,1-3, 5, 7-8
+  while ((cpu = strtol(p, &endp, 10)) != 0 || endp != p) {
+    if (have_dash && result.size() > 0) {
+      for (int t = result.back() + 1; t < cpu; ++t) {
+        result.push_back(t);
+      }
+    }
+    have_dash = false;
+    result.push_back(cpu);
+    p = endp;
+    while (!isdigit(*p) && *p != '\0') {
+      if (*p == '-') {
+        have_dash = true;
+      }
+      ++p;
+    }
+  }
+  return result;
+}
+
+bool ProcessKernelSymbols(const std::string& symbol_file,
+                          std::function<bool(const KernelSymbol&)> callback) {
+  FILE* fp = fopen(symbol_file.c_str(), "re");
+  if (fp == nullptr) {
+    PLOG(ERROR) << "failed to open file " << symbol_file;
+    return false;
+  }
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    // Parse line like: ffffffffa005c4e4 d __warned.41698       [libsas]
+    char name[reader.MaxLineSize()];
+    char module[reader.MaxLineSize()];
+    strcpy(module, "");
+
+    KernelSymbol symbol;
+    if (sscanf(line, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module) < 3) {
+      continue;
+    }
+    symbol.name = name;
+    size_t module_len = strlen(module);
+    if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
+      module[module_len - 1] = '\0';
+      symbol.module = &module[1];
+    } else {
+      symbol.module = nullptr;
+    }
+
+    if (callback(symbol)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool FindStartOfKernelSymbolCallback(const KernelSymbol& symbol, uint64_t* start_addr) {
+  if (symbol.module == nullptr) {
+    *start_addr = symbol.addr;
+    return true;
+  }
+  return false;
+}
+
+static bool FindStartOfKernelSymbol(const std::string& symbol_file, uint64_t* start_addr) {
+  return ProcessKernelSymbols(
+      symbol_file, std::bind(&FindStartOfKernelSymbolCallback, std::placeholders::_1, start_addr));
+}
+
+static bool FindKernelFunctionSymbolCallback(const KernelSymbol& symbol, const std::string& name,
+                                             uint64_t* addr) {
+  if ((symbol.type == 'T' || symbol.type == 'W' || symbol.type == 'A') &&
+      symbol.module == nullptr && name == symbol.name) {
+    *addr = symbol.addr;
+    return true;
+  }
+  return false;
+}
+
+static bool FindKernelFunctionSymbol(const std::string& symbol_file, const std::string& name,
+                                     uint64_t* addr) {
+  return ProcessKernelSymbols(
+      symbol_file, std::bind(&FindKernelFunctionSymbolCallback, std::placeholders::_1, name, addr));
+}
+
+std::vector<ModuleMmap> GetLoadedModules() {
+  std::vector<ModuleMmap> result;
+  FILE* fp = fopen("/proc/modules", "re");
+  if (fp == nullptr) {
+    // There is no /proc/modules on Android devices, so we don't print error if failed to open it.
+    PLOG(DEBUG) << "failed to open file /proc/modules";
+    return result;
+  }
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    // Parse line like: nf_defrag_ipv6 34768 1 nf_conntrack_ipv6, Live 0xffffffffa0fe5000
+    char name[reader.MaxLineSize()];
+    uint64_t addr;
+    if (sscanf(line, "%s%*lu%*u%*s%*s 0x%" PRIx64, name, &addr) == 2) {
+      ModuleMmap map;
+      map.name = name;
+      map.start_addr = addr;
+      result.push_back(map);
+    }
+  }
+  return result;
+}
+
+static std::string GetLinuxVersion() {
+  std::string content;
+  if (android::base::ReadFileToString("/proc/version", &content)) {
+    char s[content.size() + 1];
+    if (sscanf(content.c_str(), "Linux version %s", s) == 1) {
+      return s;
+    }
+  }
+  PLOG(FATAL) << "can't read linux version";
+  return "";
+}
+
+static void GetAllModuleFiles(const std::string& path,
+                              std::unordered_map<std::string, std::string>* module_file_map) {
+  std::vector<std::string> files;
+  std::vector<std::string> subdirs;
+  GetEntriesInDir(path, &files, &subdirs);
+  for (auto& name : files) {
+    if (android::base::EndsWith(name, ".ko")) {
+      std::string module_name = name.substr(0, name.size() - 3);
+      std::replace(module_name.begin(), module_name.end(), '-', '_');
+      module_file_map->insert(std::make_pair(module_name, path + "/" + name));
+    }
+  }
+  for (auto& name : subdirs) {
+    GetAllModuleFiles(path + "/" + name, module_file_map);
+  }
+}
+
+static std::vector<ModuleMmap> GetModulesInUse() {
+  // TODO: There is no /proc/modules or /lib/modules on Android, find methods work on it.
+  std::vector<ModuleMmap> module_mmaps = GetLoadedModules();
+  std::string linux_version = GetLinuxVersion();
+  std::string module_dirpath = "/lib/modules/" + linux_version + "/kernel";
+  std::unordered_map<std::string, std::string> module_file_map;
+  GetAllModuleFiles(module_dirpath, &module_file_map);
+  for (auto& module : module_mmaps) {
+    auto it = module_file_map.find(module.name);
+    if (it != module_file_map.end()) {
+      module.filepath = it->second;
+    }
+  }
+  return module_mmaps;
+}
+
+bool GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<ModuleMmap>* module_mmaps) {
+  if (!FindStartOfKernelSymbol("/proc/kallsyms", &kernel_mmap->start_addr)) {
+    LOG(DEBUG) << "call FindStartOfKernelSymbol() failed";
+    return false;
+  }
+  if (!FindKernelFunctionSymbol("/proc/kallsyms", "_text", &kernel_mmap->pgoff)) {
+    LOG(DEBUG) << "call FindKernelFunctionSymbol() failed";
+    return false;
+  }
+  kernel_mmap->name = DEFAULT_KERNEL_MMAP_NAME;
+  *module_mmaps = GetModulesInUse();
+  if (module_mmaps->size() == 0) {
+    kernel_mmap->len = ULLONG_MAX - kernel_mmap->start_addr;
+  } else {
+    std::sort(
+        module_mmaps->begin(), module_mmaps->end(),
+        [](const ModuleMmap& m1, const ModuleMmap& m2) { return m1.start_addr < m2.start_addr; });
+    CHECK_LE(kernel_mmap->start_addr, (*module_mmaps)[0].start_addr);
+    // When not having enough privilege, all addresses are read as 0.
+    if (kernel_mmap->start_addr == (*module_mmaps)[0].start_addr) {
+      kernel_mmap->len = 0;
+    } else {
+      kernel_mmap->len = (*module_mmaps)[0].start_addr - kernel_mmap->start_addr - 1;
+    }
+    for (size_t i = 0; i + 1 < module_mmaps->size(); ++i) {
+      if ((*module_mmaps)[i].start_addr == (*module_mmaps)[i + 1].start_addr) {
+        (*module_mmaps)[i].len = 0;
+      } else {
+        (*module_mmaps)[i].len =
+            (*module_mmaps)[i + 1].start_addr - (*module_mmaps)[i].start_addr - 1;
+      }
+    }
+    module_mmaps->back().len = ULLONG_MAX - module_mmaps->back().start_addr;
+  }
+  return true;
+}
+
+static bool StringToPid(const std::string& s, pid_t* pid) {
+  char* endptr;
+  *pid = static_cast<pid_t>(strtol(s.c_str(), &endptr, 10));
+  return *endptr == '\0';
+}
+
+static bool ReadThreadNameAndTgid(const std::string& status_file, std::string* comm, pid_t* tgid) {
+  FILE* fp = fopen(status_file.c_str(), "re");
+  if (fp == nullptr) {
+    return false;
+  }
+  bool read_comm = false;
+  bool read_tgid = false;
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    char s[reader.MaxLineSize()];
+    if (sscanf(line, "Name:%s", s) == 1) {
+      *comm = s;
+      read_comm = true;
+    } else if (sscanf(line, "Tgid:%d", tgid) == 1) {
+      read_tgid = true;
+    }
+    if (read_comm && read_tgid) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static std::vector<pid_t> GetThreadsInProcess(pid_t pid) {
+  std::vector<pid_t> result;
+  std::string task_dirname = android::base::StringPrintf("/proc/%d/task", pid);
+  std::vector<std::string> subdirs;
+  GetEntriesInDir(task_dirname, nullptr, &subdirs);
+  for (auto& name : subdirs) {
+    pid_t tid;
+    if (!StringToPid(name, &tid)) {
+      continue;
+    }
+    result.push_back(tid);
+  }
+  return result;
+}
+
+static bool GetThreadComm(pid_t pid, std::vector<ThreadComm>* thread_comms) {
+  std::vector<pid_t> tids = GetThreadsInProcess(pid);
+  for (auto& tid : tids) {
+    std::string status_file = android::base::StringPrintf("/proc/%d/task/%d/status", pid, tid);
+    std::string comm;
+    pid_t tgid;
+    // It is possible that the process or thread exited before we can read its status.
+    if (!ReadThreadNameAndTgid(status_file, &comm, &tgid)) {
+      continue;
+    }
+    CHECK_EQ(pid, tgid);
+    ThreadComm thread;
+    thread.tid = tid;
+    thread.pid = pid;
+    thread.comm = comm;
+    thread_comms->push_back(thread);
+  }
+  return true;
+}
+
+bool GetThreadComms(std::vector<ThreadComm>* thread_comms) {
+  thread_comms->clear();
+  std::vector<std::string> subdirs;
+  GetEntriesInDir("/proc", nullptr, &subdirs);
+  for (auto& name : subdirs) {
+    pid_t pid;
+    if (!StringToPid(name, &pid)) {
+      continue;
+    }
+    if (!GetThreadComm(pid, thread_comms)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps) {
+  std::string map_file = android::base::StringPrintf("/proc/%d/maps", pid);
+  FILE* fp = fopen(map_file.c_str(), "re");
+  if (fp == nullptr) {
+    PLOG(DEBUG) << "can't open file " << map_file;
+    return false;
+  }
+  thread_mmaps->clear();
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    // Parse line like: 00400000-00409000 r-xp 00000000 fc:00 426998  /usr/lib/gvfs/gvfsd-http
+    uint64_t start_addr, end_addr, pgoff;
+    char type[reader.MaxLineSize()];
+    char execname[reader.MaxLineSize()];
+    strcpy(execname, "");
+    if (sscanf(line, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %*x:%*x %*u %s\n", &start_addr,
+               &end_addr, type, &pgoff, execname) < 4) {
+      continue;
+    }
+    if (strcmp(execname, "") == 0) {
+      strcpy(execname, DEFAULT_EXECNAME_FOR_THREAD_MMAP);
+    }
+    ThreadMmap thread;
+    thread.start_addr = start_addr;
+    thread.len = end_addr - start_addr;
+    thread.pgoff = pgoff;
+    thread.name = execname;
+    thread.executable = (type[2] == 'x');
+    thread_mmaps->push_back(thread);
+  }
+  return true;
+}
+
+bool GetKernelBuildId(BuildId* build_id) {
+  return GetBuildIdFromNoteFile("/sys/kernel/notes", build_id);
+}
+
+bool GetModuleBuildId(const std::string& module_name, BuildId* build_id) {
+  std::string notefile = "/sys/module/" + module_name + "/notes/.note.gnu.build-id";
+  return GetBuildIdFromNoteFile(notefile, build_id);
+}
+
+bool GetValidThreadsFromProcessString(const std::string& pid_str, std::set<pid_t>* tid_set) {
+  std::vector<std::string> strs = android::base::Split(pid_str, ",");
+  for (auto& s : strs) {
+    pid_t pid;
+    if (!StringToPid(s, &pid)) {
+      LOG(ERROR) << "Invalid pid '" << s << "'";
+      return false;
+    }
+    std::vector<pid_t> tids = GetThreadsInProcess(pid);
+    if (tids.empty()) {
+      LOG(ERROR) << "Non existing process '" << pid << "'";
+      return false;
+    }
+    tid_set->insert(tids.begin(), tids.end());
+  }
+  return true;
+}
+
+bool GetValidThreadsFromThreadString(const std::string& tid_str, std::set<pid_t>* tid_set) {
+  std::vector<std::string> strs = android::base::Split(tid_str, ",");
+  for (auto& s : strs) {
+    pid_t tid;
+    if (!StringToPid(s, &tid)) {
+      LOG(ERROR) << "Invalid tid '" << s << "'";
+      return false;
+    }
+    if (!IsDir(android::base::StringPrintf("/proc/%d", tid))) {
+      LOG(ERROR) << "Non existing thread '" << tid << "'";
+      return false;
+    }
+    tid_set->insert(tid);
+  }
+  return true;
+}
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
new file mode 100644
index 0000000..6d844ff
--- /dev/null
+++ b/simpleperf/environment.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_ENVIRONMENT_H_
+#define SIMPLE_PERF_ENVIRONMENT_H_
+
+#include <functional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "build_id.h"
+
+std::vector<int> GetOnlineCpus();
+
+static const char* DEFAULT_KERNEL_MMAP_NAME = "[kernel.kallsyms]_text";
+
+struct KernelMmap {
+  std::string name;
+  uint64_t start_addr;
+  uint64_t len;
+  uint64_t pgoff;
+};
+
+struct ModuleMmap {
+  std::string name;
+  uint64_t start_addr;
+  uint64_t len;
+  std::string filepath;
+};
+
+bool GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<ModuleMmap>* module_mmaps);
+
+struct ThreadComm {
+  pid_t pid, tid;
+  std::string comm;
+};
+
+bool GetThreadComms(std::vector<ThreadComm>* thread_comms);
+
+static const char* DEFAULT_EXECNAME_FOR_THREAD_MMAP = "//anon";
+
+struct ThreadMmap {
+  uint64_t start_addr;
+  uint64_t len;
+  uint64_t pgoff;
+  std::string name;
+  bool executable;
+};
+
+bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps);
+
+static const char* DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID = "[kernel.kallsyms]";
+
+bool GetKernelBuildId(BuildId* build_id);
+bool GetModuleBuildId(const std::string& module_name, BuildId* build_id);
+
+bool GetValidThreadsFromProcessString(const std::string& pid_str, std::set<pid_t>* tid_set);
+bool GetValidThreadsFromThreadString(const std::string& tid_str, std::set<pid_t>* tid_set);
+
+// Expose the following functions for unit tests.
+std::vector<int> GetOnlineCpusFromString(const std::string& s);
+
+struct KernelSymbol {
+  uint64_t addr;
+  char type;
+  const char* name;
+  const char* module;  // If nullptr, the symbol is not in a kernel module.
+};
+
+bool ProcessKernelSymbols(const std::string& symbol_file,
+                          std::function<bool(const KernelSymbol&)> callback);
+
+#endif  // SIMPLE_PERF_ENVIRONMENT_H_
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
new file mode 100644
index 0000000..3cf81fa
--- /dev/null
+++ b/simpleperf/environment_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <base/file.h>
+
+#include "environment.h"
+
+TEST(environment, GetOnlineCpusFromString) {
+  ASSERT_EQ(GetOnlineCpusFromString(""), std::vector<int>());
+  ASSERT_EQ(GetOnlineCpusFromString("0-2"), std::vector<int>({0, 1, 2}));
+  ASSERT_EQ(GetOnlineCpusFromString("0,2-3"), std::vector<int>({0, 2, 3}));
+}
+
+static bool FindKernelSymbol(const KernelSymbol& sym1, const KernelSymbol& sym2) {
+  return sym1.addr == sym2.addr && sym1.type == sym2.type && strcmp(sym1.name, sym2.name) == 0 &&
+         ((sym1.module == nullptr && sym2.module == nullptr) ||
+          (strcmp(sym1.module, sym2.module) == 0));
+}
+
+TEST(environment, ProcessKernelSymbols) {
+  std::string data =
+      "ffffffffa005c4e4 d __warned.41698   [libsas]\n"
+      "aaaaaaaaaaaaaaaa T _text\n"
+      "cccccccccccccccc c ccccc\n";
+  const char* tempfile = "tempfile_process_kernel_symbols";
+  ASSERT_TRUE(android::base::WriteStringToFile(data, tempfile));
+  KernelSymbol expected_symbol;
+  expected_symbol.addr = 0xffffffffa005c4e4ULL;
+  expected_symbol.type = 'd';
+  expected_symbol.name = "__warned.41698";
+  expected_symbol.module = "libsas";
+  ASSERT_TRUE(ProcessKernelSymbols(
+      tempfile, std::bind(&FindKernelSymbol, std::placeholders::_1, expected_symbol)));
+
+  expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL;
+  expected_symbol.type = 'T';
+  expected_symbol.name = "_text";
+  expected_symbol.module = nullptr;
+  ASSERT_TRUE(ProcessKernelSymbols(
+      tempfile, std::bind(&FindKernelSymbol, std::placeholders::_1, expected_symbol)));
+
+  expected_symbol.name = "non_existent_symbol";
+  ASSERT_FALSE(ProcessKernelSymbols(
+      tempfile, std::bind(&FindKernelSymbol, std::placeholders::_1, expected_symbol)));
+  ASSERT_EQ(0, unlink(tempfile));
+}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
new file mode 100644
index 0000000..c7ee182
--- /dev/null
+++ b/simpleperf/event_attr.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "event_attr.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string>
+#include <unordered_map>
+
+#include <base/logging.h>
+
+#include "event_type.h"
+#include "utils.h"
+
+static std::string BitsToString(const std::string& name, uint64_t bits,
+                                const std::vector<std::pair<int, std::string>>& bit_names) {
+  std::string result;
+  for (auto& p : bit_names) {
+    if (bits & p.first) {
+      bits &= ~p.first;
+      if (!result.empty()) {
+        result += ", ";
+      }
+      result += p.second;
+    }
+  }
+  if (bits != 0) {
+    LOG(DEBUG) << "unknown " << name << " bits: " << std::hex << bits;
+  }
+  return result;
+}
+
+static std::string SampleTypeToString(uint64_t sample_type) {
+  static std::vector<std::pair<int, std::string>> sample_type_names = {
+      {PERF_SAMPLE_ADDR, "addr"},
+      {PERF_SAMPLE_CALLCHAIN, "callchain"},
+      {PERF_SAMPLE_CPU, "cpu"},
+      {PERF_SAMPLE_ID, "id"},
+      {PERF_SAMPLE_IP, "ip"},
+      {PERF_SAMPLE_PERIOD, "period"},
+      {PERF_SAMPLE_RAW, "raw"},
+      {PERF_SAMPLE_READ, "read"},
+      {PERF_SAMPLE_STREAM_ID, "stream_id"},
+      {PERF_SAMPLE_TID, "tid"},
+      {PERF_SAMPLE_TIME, "time"},
+  };
+  return BitsToString("sample_type", sample_type, sample_type_names);
+}
+
+static std::string ReadFormatToString(uint64_t read_format) {
+  static std::vector<std::pair<int, std::string>> read_format_names = {
+      {PERF_FORMAT_TOTAL_TIME_ENABLED, "total_time_enabled"},
+      {PERF_FORMAT_TOTAL_TIME_RUNNING, "total_time_running"},
+      {PERF_FORMAT_ID, "id"},
+      {PERF_FORMAT_GROUP, "group"},
+  };
+  return BitsToString("read_format", read_format, read_format_names);
+}
+
+perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type) {
+  perf_event_attr attr;
+  memset(&attr, 0, sizeof(attr));
+  attr.size = sizeof(perf_event_attr);
+  attr.type = event_type.type;
+  attr.config = event_type.config;
+  attr.mmap = 1;
+  attr.comm = 1;
+  attr.disabled = 1;
+  // Changing read_format affects the layout of the data read from perf_event_file, namely
+  // PerfCounter in event_fd.h.
+  attr.read_format =
+      PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID;
+  attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_PERIOD;
+
+  if (attr.type == PERF_TYPE_TRACEPOINT) {
+    attr.sample_freq = 0;
+    attr.sample_period = 1;
+  }
+  return attr;
+}
+
+void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
+  std::string event_name = "unknown";
+  const EventType* event_type = FindEventTypeByConfig(attr.type, attr.config);
+  if (event_type != nullptr) {
+    event_name = event_type->name;
+  }
+
+  PrintIndented(indent, "event_attr: for event type %s\n", event_name.c_str());
+
+  PrintIndented(indent + 1, "type %u, size %u, config %llu\n", attr.type, attr.size, attr.config);
+
+  if (attr.freq != 0) {
+    PrintIndented(indent + 1, "sample_freq %llu\n", attr.sample_freq);
+  } else {
+    PrintIndented(indent + 1, "sample_period %llu\n", attr.sample_period);
+  }
+
+  PrintIndented(indent + 1, "sample_type (0x%llx) %s\n", attr.sample_type,
+                SampleTypeToString(attr.sample_type).c_str());
+
+  PrintIndented(indent + 1, "read_format (0x%llx) %s\n", attr.read_format,
+                ReadFormatToString(attr.read_format).c_str());
+
+  PrintIndented(indent + 1, "disabled %u, inherit %u, pinned %u, exclusive %u\n", attr.disabled,
+                attr.inherit, attr.pinned, attr.exclusive);
+
+  PrintIndented(indent + 1, "exclude_user %u, exclude_kernel %u, exclude_hv %u\n",
+                attr.exclude_user, attr.exclude_kernel, attr.exclude_hv);
+
+  PrintIndented(indent + 1, "exclude_idle %u, mmap %u, comm %u, freq %u\n", attr.exclude_idle,
+                attr.mmap, attr.comm, attr.freq);
+
+  PrintIndented(indent + 1, "inherit_stat %u, enable_on_exec %u, task %u\n", attr.inherit_stat,
+                attr.enable_on_exec, attr.task);
+
+  PrintIndented(indent + 1, "watermark %u, precise_ip %u, mmap_data %u\n", attr.watermark,
+                attr.precise_ip, attr.mmap_data);
+
+  PrintIndented(indent + 1, "sample_id_all %u, exclude_host %u, exclude_guest %u\n",
+                attr.sample_id_all, attr.exclude_host, attr.exclude_guest);
+}
diff --git a/ext4_utils/uuid.h b/simpleperf/event_attr.h
similarity index 60%
copy from ext4_utils/uuid.h
copy to simpleperf/event_attr.h
index ff1b438..79d3df4 100644
--- a/ext4_utils/uuid.h
+++ b/simpleperf/event_attr.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef _UUID_H_
-#define _UUID_H_
+#ifndef SIMPLE_PERF_EVENT_ATTR_H_
+#define SIMPLE_PERF_EVENT_ATTR_H_
 
-#include "ext4_utils.h"
+#include <stddef.h>
 
-void generate_uuid(const char *namespace, const char *name, u8 result[16]);
+#include "perf_event.h"
 
-#endif
+struct EventType;
+
+perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type);
+void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent = 0);
+
+#endif  // SIMPLE_PERF_EVENT_ATTR_H_
diff --git a/simpleperf/event_fd.cpp b/simpleperf/event_fd.cpp
new file mode 100644
index 0000000..547be29
--- /dev/null
+++ b/simpleperf/event_fd.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "event_fd.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <atomic>
+#include <memory>
+
+#include <base/file.h>
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#include "event_type.h"
+#include "perf_event.h"
+#include "utils.h"
+
+static int perf_event_open(perf_event_attr* attr, pid_t pid, int cpu, int group_fd,
+                           unsigned long flags) {
+  return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
+}
+
+std::unique_ptr<EventFd> EventFd::OpenEventFile(const perf_event_attr& attr, pid_t tid, int cpu,
+                                                bool report_error) {
+  perf_event_attr perf_attr = attr;
+  std::string event_name = "unknown event";
+  const EventType* event_type = FindEventTypeByConfig(perf_attr.type, perf_attr.config);
+  if (event_type != nullptr) {
+    event_name = event_type->name;
+  }
+  int perf_event_fd = perf_event_open(&perf_attr, tid, cpu, -1, 0);
+  if (perf_event_fd == -1) {
+    (report_error ? PLOG(ERROR) : PLOG(DEBUG)) << "open perf_event_file (event " << event_name
+                                               << ", tid " << tid << ", cpu " << cpu << ") failed";
+    return nullptr;
+  }
+  if (fcntl(perf_event_fd, F_SETFD, FD_CLOEXEC) == -1) {
+    (report_error ? PLOG(ERROR) : PLOG(DEBUG)) << "fcntl(FD_CLOEXEC) for perf_event_file (event "
+                                               << event_name << ", tid " << tid << ", cpu " << cpu
+                                               << ") failed";
+    return nullptr;
+  }
+  return std::unique_ptr<EventFd>(new EventFd(perf_event_fd, event_name, tid, cpu));
+}
+
+EventFd::~EventFd() {
+  if (mmap_addr_ != nullptr) {
+    munmap(mmap_addr_, mmap_len_);
+  }
+  close(perf_event_fd_);
+}
+
+std::string EventFd::Name() const {
+  return android::base::StringPrintf("perf_event_file(event %s, tid %d, cpu %d)",
+                                     event_name_.c_str(), tid_, cpu_);
+}
+
+uint64_t EventFd::Id() const {
+  if (id_ == 0) {
+    PerfCounter counter;
+    if (ReadCounter(&counter)) {
+      id_ = counter.id;
+    }
+  }
+  return id_;
+}
+
+bool EventFd::EnableEvent() {
+  int result = ioctl(perf_event_fd_, PERF_EVENT_IOC_ENABLE, 0);
+  if (result < 0) {
+    PLOG(ERROR) << "ioctl(enable) " << Name() << " failed";
+    return false;
+  }
+  return true;
+}
+
+bool EventFd::DisableEvent() {
+  int result = ioctl(perf_event_fd_, PERF_EVENT_IOC_DISABLE, 0);
+  if (result < 0) {
+    PLOG(ERROR) << "ioctl(disable) " << Name() << " failed";
+    return false;
+  }
+  return true;
+}
+
+bool EventFd::ReadCounter(PerfCounter* counter) const {
+  CHECK(counter != nullptr);
+  if (!android::base::ReadFully(perf_event_fd_, counter, sizeof(*counter))) {
+    PLOG(ERROR) << "ReadCounter from " << Name() << " failed";
+    return false;
+  }
+  return true;
+}
+
+bool EventFd::MmapContent(size_t mmap_pages) {
+  CHECK(IsPowerOfTwo(mmap_pages));
+  size_t page_size = sysconf(_SC_PAGE_SIZE);
+  size_t mmap_len = (mmap_pages + 1) * page_size;
+  void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, perf_event_fd_, 0);
+  if (mmap_addr == MAP_FAILED) {
+    PLOG(ERROR) << "mmap() failed for " << Name();
+    return false;
+  }
+  mmap_addr_ = mmap_addr;
+  mmap_len_ = mmap_len;
+  mmap_metadata_page_ = reinterpret_cast<perf_event_mmap_page*>(mmap_addr_);
+  mmap_data_buffer_ = reinterpret_cast<char*>(mmap_addr_) + page_size;
+  mmap_data_buffer_size_ = mmap_len_ - page_size;
+  return true;
+}
+
+size_t EventFd::GetAvailableMmapData(char** pdata) {
+  // The mmap_data_buffer is used as a ring buffer like below. The kernel continuously writes
+  // records to the buffer, and the user continuously read records out.
+  //         _________________________________________
+  // buffer | can write   |   can read   |  can write |
+  //                      ^              ^
+  //                    read_head       write_head
+  //
+  // So the user can read records in [read_head, write_head), and the kernel can write records
+  // in [write_head, read_head). The kernel is responsible for updating write_head, and the user
+  // is responsible for updating read_head.
+
+  uint64_t buf_mask = mmap_data_buffer_size_ - 1;
+  uint64_t write_head = mmap_metadata_page_->data_head & buf_mask;
+  uint64_t read_head = mmap_metadata_page_->data_tail & buf_mask;
+
+  if (read_head == write_head) {
+    // No available data.
+    return 0;
+  }
+
+  // Make sure we can see the data after the fence.
+  std::atomic_thread_fence(std::memory_order_acquire);
+
+  *pdata = mmap_data_buffer_ + read_head;
+  if (read_head < write_head) {
+    return write_head - read_head;
+  } else {
+    return mmap_data_buffer_size_ - read_head;
+  }
+}
+
+void EventFd::DiscardMmapData(size_t discard_size) {
+  mmap_metadata_page_->data_tail += discard_size;
+}
+
+void EventFd::PreparePollForMmapData(pollfd* poll_fd) {
+  memset(poll_fd, 0, sizeof(pollfd));
+  poll_fd->fd = perf_event_fd_;
+  poll_fd->events = POLLIN;
+}
diff --git a/simpleperf/event_fd.h b/simpleperf/event_fd.h
new file mode 100644
index 0000000..bcacc28
--- /dev/null
+++ b/simpleperf/event_fd.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_EVENT_FD_H_
+#define SIMPLE_PERF_EVENT_FD_H_
+
+#include <poll.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+
+#include "perf_event.h"
+
+struct PerfCounter {
+  uint64_t value;         // The value of the event specified by the perf_event_file.
+  uint64_t time_enabled;  // The enabled time.
+  uint64_t time_running;  // The running time.
+  uint64_t id;            // The id of the perf_event_file.
+};
+
+// EventFd represents an opened perf_event_file.
+class EventFd {
+ public:
+  static std::unique_ptr<EventFd> OpenEventFile(const perf_event_attr& attr, pid_t tid, int cpu,
+                                                bool report_error = true);
+
+  ~EventFd();
+
+  // Give information about this perf_event_file, like (event_name, tid, cpu).
+  std::string Name() const;
+
+  uint64_t Id() const;
+
+  // It tells the kernel to start counting and recording events specified by this file.
+  bool EnableEvent();
+
+  // It tells the kernel to stop counting and recording events specified by this file.
+  bool DisableEvent();
+
+  bool ReadCounter(PerfCounter* counter) const;
+
+  // Call mmap() for this perf_event_file, so we can read sampled records from mapped area.
+  // mmap_pages should be power of 2.
+  bool MmapContent(size_t mmap_pages);
+
+  // When the kernel writes new sampled records to the mapped area, we can get them by returning
+  // the start address and size of the data.
+  size_t GetAvailableMmapData(char** pdata);
+
+  // Discard how much data we have read, so the kernel can reuse this part of mapped area to store
+  // new data.
+  void DiscardMmapData(size_t discard_size);
+
+  // Prepare pollfd for poll() to wait on available mmap_data.
+  void PreparePollForMmapData(pollfd* poll_fd);
+
+ private:
+  EventFd(int perf_event_fd, const std::string& event_name, pid_t tid, int cpu)
+      : perf_event_fd_(perf_event_fd),
+        id_(0),
+        event_name_(event_name),
+        tid_(tid),
+        cpu_(cpu),
+        mmap_addr_(nullptr),
+        mmap_len_(0) {
+  }
+
+  int perf_event_fd_;
+  mutable uint64_t id_;
+  const std::string event_name_;
+  pid_t tid_;
+  int cpu_;
+
+  void* mmap_addr_;
+  size_t mmap_len_;
+  perf_event_mmap_page* mmap_metadata_page_;  // The first page of mmap_area.
+  char* mmap_data_buffer_;  // Starts from the second page of mmap_area, containing records written
+                            // by then kernel.
+  size_t mmap_data_buffer_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventFd);
+};
+
+#endif  // SIMPLE_PERF_EVENT_FD_H_
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
new file mode 100644
index 0000000..0f23c14
--- /dev/null
+++ b/simpleperf/event_selection_set.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "event_selection_set.h"
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#include "environment.h"
+#include "event_attr.h"
+#include "event_type.h"
+
+bool IsBranchSamplingSupported() {
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles", false);
+  if (event_type_modifier == nullptr) {
+    return false;
+  }
+  perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+  attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
+  attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY;
+  auto event_fd = EventFd::OpenEventFile(attr, getpid(), -1, false);
+  return event_fd != nullptr;
+}
+
+void EventSelectionSet::AddEventType(const EventTypeAndModifier& event_type_modifier) {
+  EventSelection selection;
+  selection.event_type = event_type_modifier.event_type;
+  selection.event_attr = CreateDefaultPerfEventAttr(event_type_modifier.event_type);
+  selection.event_attr.exclude_user = event_type_modifier.exclude_user;
+  selection.event_attr.exclude_kernel = event_type_modifier.exclude_kernel;
+  selection.event_attr.exclude_hv = event_type_modifier.exclude_hv;
+  selection.event_attr.exclude_host = event_type_modifier.exclude_host;
+  selection.event_attr.exclude_guest = event_type_modifier.exclude_guest;
+  selection.event_attr.precise_ip = event_type_modifier.precise_ip;
+  selections_.push_back(std::move(selection));
+}
+
+void EventSelectionSet::SetEnableOnExec(bool enable) {
+  for (auto& selection : selections_) {
+    selection.event_attr.enable_on_exec = (enable ? 1 : 0);
+  }
+}
+
+bool EventSelectionSet::GetEnableOnExec() {
+  for (auto& selection : selections_) {
+    if (selection.event_attr.enable_on_exec == 0) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void EventSelectionSet::SampleIdAll() {
+  for (auto& selection : selections_) {
+    selection.event_attr.sample_id_all = 1;
+  }
+}
+
+void EventSelectionSet::SetSampleFreq(uint64_t sample_freq) {
+  for (auto& selection : selections_) {
+    perf_event_attr& attr = selection.event_attr;
+    attr.freq = 1;
+    attr.sample_freq = sample_freq;
+  }
+}
+
+void EventSelectionSet::SetSamplePeriod(uint64_t sample_period) {
+  for (auto& selection : selections_) {
+    perf_event_attr& attr = selection.event_attr;
+    attr.freq = 0;
+    attr.sample_period = sample_period;
+  }
+}
+
+bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
+  if (branch_sample_type != 0 &&
+      (branch_sample_type & (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
+                             PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
+    LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex << branch_sample_type;
+    return false;
+  }
+  if (branch_sample_type != 0 && !IsBranchSamplingSupported()) {
+    LOG(ERROR) << "branch stack sampling is not supported on this device.";
+    return false;
+  }
+  for (auto& selection : selections_) {
+    perf_event_attr& attr = selection.event_attr;
+    if (branch_sample_type != 0) {
+      attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
+    } else {
+      attr.sample_type &= ~PERF_SAMPLE_BRANCH_STACK;
+    }
+    attr.branch_sample_type = branch_sample_type;
+  }
+  return true;
+}
+
+void EventSelectionSet::EnableCallChainSampling() {
+  for (auto& selection : selections_) {
+    selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+  }
+}
+
+void EventSelectionSet::SetInherit(bool enable) {
+  for (auto& selection : selections_) {
+    selection.event_attr.inherit = (enable ? 1 : 0);
+  }
+}
+
+bool EventSelectionSet::OpenEventFilesForAllCpus() {
+  return OpenEventFilesForThreadsOnAllCpus({-1});
+}
+
+bool EventSelectionSet::OpenEventFilesForThreads(const std::vector<pid_t>& threads) {
+  return OpenEventFiles(threads, {-1});
+}
+
+bool EventSelectionSet::OpenEventFilesForThreadsOnAllCpus(const std::vector<pid_t>& threads) {
+  std::vector<int> cpus = GetOnlineCpus();
+  if (cpus.empty()) {
+    return false;
+  }
+  return OpenEventFiles(threads, cpus);
+}
+
+bool EventSelectionSet::OpenEventFiles(const std::vector<pid_t>& threads,
+                                       const std::vector<int>& cpus) {
+  for (auto& selection : selections_) {
+    for (auto& tid : threads) {
+      size_t open_per_thread = 0;
+      for (auto& cpu : cpus) {
+        auto event_fd = EventFd::OpenEventFile(selection.event_attr, tid, cpu);
+        if (event_fd != nullptr) {
+          selection.event_fds.push_back(std::move(event_fd));
+          ++open_per_thread;
+        }
+      }
+      // As the online cpus can be enabled or disabled at runtime, we may not open event file for
+      // all cpus successfully. But we should open at least one cpu successfully.
+      if (open_per_thread == 0) {
+        PLOG(ERROR) << "failed to open perf event file for event_type " << selection.event_type.name
+                    << " for "
+                    << (tid == -1 ? "all threads" : android::base::StringPrintf(" thread %d", tid));
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool EventSelectionSet::EnableEvents() {
+  for (auto& selection : selections_) {
+    for (auto& event_fd : selection.event_fds) {
+      if (!event_fd->EnableEvent()) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool EventSelectionSet::ReadCounters(
+    std::map<const EventType*, std::vector<PerfCounter>>* counters_map) {
+  for (auto& selection : selections_) {
+    std::vector<PerfCounter> counters;
+    for (auto& event_fd : selection.event_fds) {
+      PerfCounter counter;
+      if (!event_fd->ReadCounter(&counter)) {
+        return false;
+      }
+      counters.push_back(counter);
+    }
+    counters_map->insert(std::make_pair(&selection.event_type, counters));
+  }
+  return true;
+}
+
+void EventSelectionSet::PreparePollForEventFiles(std::vector<pollfd>* pollfds) {
+  for (auto& selection : selections_) {
+    for (auto& event_fd : selection.event_fds) {
+      pollfd poll_fd;
+      event_fd->PreparePollForMmapData(&poll_fd);
+      pollfds->push_back(poll_fd);
+    }
+  }
+}
+
+bool EventSelectionSet::MmapEventFiles(size_t mmap_pages) {
+  for (auto& selection : selections_) {
+    for (auto& event_fd : selection.event_fds) {
+      if (!event_fd->MmapContent(mmap_pages)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+static bool ReadMmapEventDataForFd(std::unique_ptr<EventFd>& event_fd,
+                                   std::function<bool(const char*, size_t)> callback,
+                                   bool* have_data) {
+  *have_data = false;
+  while (true) {
+    char* data;
+    size_t size = event_fd->GetAvailableMmapData(&data);
+    if (size == 0) {
+      break;
+    }
+    if (!callback(data, size)) {
+      return false;
+    }
+    *have_data = true;
+    event_fd->DiscardMmapData(size);
+  }
+  return true;
+}
+
+bool EventSelectionSet::ReadMmapEventData(std::function<bool(const char*, size_t)> callback) {
+  for (auto& selection : selections_) {
+    for (auto& event_fd : selection.event_fds) {
+      while (true) {
+        bool have_data;
+        if (!ReadMmapEventDataForFd(event_fd, callback, &have_data)) {
+          return false;
+        }
+        if (!have_data) {
+          break;
+        }
+      }
+    }
+  }
+  return true;
+}
+
+std::string EventSelectionSet::FindEventFileNameById(uint64_t id) {
+  for (auto& selection : selections_) {
+    for (auto& event_fd : selection.event_fds) {
+      if (event_fd->Id() == id) {
+        return event_fd->Name();
+      }
+    }
+  }
+  return "";
+}
+
+EventSelectionSet::EventSelection* EventSelectionSet::FindSelectionByType(
+    const EventType& event_type) {
+  for (auto& selection : selections_) {
+    if (selection.event_type.name == event_type.name) {
+      return &selection;
+    }
+  }
+  return nullptr;
+}
+
+const perf_event_attr& EventSelectionSet::FindEventAttrByType(const EventType& event_type) {
+  return FindSelectionByType(event_type)->event_attr;
+}
+
+const std::vector<std::unique_ptr<EventFd>>& EventSelectionSet::FindEventFdsByType(
+    const EventType& event_type) {
+  return FindSelectionByType(event_type)->event_fds;
+}
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
new file mode 100644
index 0000000..adf3440
--- /dev/null
+++ b/simpleperf/event_selection_set.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_EVENT_SELECTION_SET_H_
+#define SIMPLE_PERF_EVENT_SELECTION_SET_H_
+
+#include <poll.h>
+#include <functional>
+#include <map>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "event_fd.h"
+#include "event_type.h"
+#include "perf_event.h"
+
+// EventSelectionSet helps to monitor events.
+// Firstly, the user creates an EventSelectionSet, and adds the specific event types to monitor.
+// Secondly, the user defines how to monitor the events (by setting enable_on_exec flag,
+// sample frequency, etc).
+// Then, the user can start monitoring by ordering the EventSelectionSet to open perf event files
+// and enable events (if enable_on_exec flag isn't used).
+// After that, the user can read counters or read mapped event records.
+// At last, the EventSelectionSet will clean up resources at destruction automatically.
+
+class EventSelectionSet {
+ public:
+  EventSelectionSet() {
+  }
+
+  bool Empty() const {
+    return selections_.empty();
+  }
+
+  void AddEventType(const EventTypeAndModifier& event_type_modifier);
+
+  void SetEnableOnExec(bool enable);
+  bool GetEnableOnExec();
+  void SampleIdAll();
+  void SetSampleFreq(uint64_t sample_freq);
+  void SetSamplePeriod(uint64_t sample_period);
+  bool SetBranchSampling(uint64_t branch_sample_type);
+  void EnableCallChainSampling();
+  void SetInherit(bool enable);
+
+  bool OpenEventFilesForAllCpus();
+  bool OpenEventFilesForThreads(const std::vector<pid_t>& threads);
+  bool OpenEventFilesForThreadsOnAllCpus(const std::vector<pid_t>& threads);
+  bool EnableEvents();
+  bool ReadCounters(std::map<const EventType*, std::vector<PerfCounter>>* counters_map);
+  void PreparePollForEventFiles(std::vector<pollfd>* pollfds);
+  bool MmapEventFiles(size_t mmap_pages);
+  bool ReadMmapEventData(std::function<bool(const char*, size_t)> callback);
+
+  std::string FindEventFileNameById(uint64_t id);
+  const perf_event_attr& FindEventAttrByType(const EventType& event_type);
+  const std::vector<std::unique_ptr<EventFd>>& FindEventFdsByType(const EventType& event_type);
+
+ private:
+  bool OpenEventFiles(const std::vector<pid_t>& threads, const std::vector<int>& cpus);
+
+  struct EventSelection {
+    EventType event_type;
+    perf_event_attr event_attr;
+    std::vector<std::unique_ptr<EventFd>> event_fds;
+  };
+  EventSelection* FindSelectionByType(const EventType& event_type);
+
+  std::vector<EventSelection> selections_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
+};
+
+#endif  // SIMPLE_PERF_EVENT_SELECTION_SET_H_
diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp
new file mode 100644
index 0000000..56a17b8
--- /dev/null
+++ b/simpleperf/event_type.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "event_type.h"
+
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/file.h>
+#include <base/logging.h>
+
+#include "event_attr.h"
+#include "event_fd.h"
+#include "utils.h"
+
+#define EVENT_TYPE_TABLE_ENTRY(name, type, config) \
+  { name, type, config }                           \
+  ,
+
+static const std::vector<EventType> static_event_type_array = {
+#include "event_type_table.h"
+};
+
+static bool IsEventTypeSupportedByKernel(const EventType& event_type) {
+  auto event_fd =
+      EventFd::OpenEventFile(CreateDefaultPerfEventAttr(event_type), getpid(), -1, false);
+  return event_fd != nullptr;
+}
+
+bool EventType::IsSupportedByKernel() const {
+  return IsEventTypeSupportedByKernel(*this);
+}
+
+static const std::vector<EventType> GetTracepointEventTypes() {
+  std::vector<EventType> result;
+  const std::string tracepoint_dirname = "/sys/kernel/debug/tracing/events";
+  std::vector<std::string> system_dirs;
+  GetEntriesInDir(tracepoint_dirname, nullptr, &system_dirs);
+  for (auto& system_name : system_dirs) {
+    std::string system_path = tracepoint_dirname + "/" + system_name;
+    std::vector<std::string> event_dirs;
+    GetEntriesInDir(system_path, nullptr, &event_dirs);
+    for (auto& event_name : event_dirs) {
+      std::string id_path = system_path + "/" + event_name + "/id";
+      std::string id_content;
+      if (!android::base::ReadFileToString(id_path, &id_content)) {
+        continue;
+      }
+      char* endptr;
+      uint64_t id = strtoull(id_content.c_str(), &endptr, 10);
+      if (endptr == id_content.c_str()) {
+        LOG(DEBUG) << "unexpected id '" << id_content << "' in " << id_path;
+        continue;
+      }
+      result.push_back(EventType(system_name + ":" + event_name, PERF_TYPE_TRACEPOINT, id));
+    }
+  }
+  std::sort(result.begin(), result.end(),
+            [](const EventType& type1, const EventType& type2) { return type1.name < type2.name; });
+  return result;
+}
+
+const std::vector<EventType>& GetAllEventTypes() {
+  static std::vector<EventType> event_type_array;
+  if (event_type_array.empty()) {
+    event_type_array.insert(event_type_array.end(), static_event_type_array.begin(),
+                            static_event_type_array.end());
+    const std::vector<EventType> tracepoint_array = GetTracepointEventTypes();
+    event_type_array.insert(event_type_array.end(), tracepoint_array.begin(),
+                            tracepoint_array.end());
+  }
+  return event_type_array;
+}
+
+const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config) {
+  for (auto& event_type : GetAllEventTypes()) {
+    if (event_type.type == type && event_type.config == config) {
+      return &event_type;
+    }
+  }
+  return nullptr;
+}
+
+static const EventType* FindEventTypeByName(const std::string& name, bool report_unsupported_type) {
+  const EventType* result = nullptr;
+  for (auto& event_type : GetAllEventTypes()) {
+    if (event_type.name == name) {
+      result = &event_type;
+      break;
+    }
+  }
+  if (result == nullptr) {
+    LOG(ERROR) << "Unknown event_type '" << name
+               << "', try `simpleperf list` to list all possible event type names";
+    return nullptr;
+  }
+  if (!result->IsSupportedByKernel()) {
+    (report_unsupported_type ? PLOG(ERROR) : PLOG(DEBUG)) << "Event type '" << result->name
+                                                          << "' is not supported by the kernel";
+    return nullptr;
+  }
+  return result;
+}
+
+std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_type_str,
+                                                     bool report_unsupported_type) {
+  static std::string modifier_characters = "ukhGHp";
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier(new EventTypeAndModifier);
+  std::string name = event_type_str;
+  std::string modifier;
+  size_t comm_pos = event_type_str.rfind(':');
+  if (comm_pos != std::string::npos) {
+    bool match_modifier = true;
+    for (size_t i = comm_pos + 1; i < event_type_str.size(); ++i) {
+      char c = event_type_str[i];
+      if (c != ' ' && modifier_characters.find(c) == std::string::npos) {
+        match_modifier = false;
+        break;
+      }
+    }
+    if (match_modifier) {
+      name = event_type_str.substr(0, comm_pos);
+      modifier = event_type_str.substr(comm_pos + 1);
+    }
+  }
+  const EventType* event_type = FindEventTypeByName(name, report_unsupported_type);
+  if (event_type == nullptr) {
+    // Try if the modifier belongs to the event type name, like some tracepoint events.
+    if (!modifier.empty()) {
+      name = event_type_str;
+      modifier.clear();
+      event_type = FindEventTypeByName(name, report_unsupported_type);
+    }
+    if (event_type == nullptr) {
+      return nullptr;
+    }
+  }
+  event_type_modifier->event_type = *event_type;
+  if (modifier.find_first_of("ukh") != std::string::npos) {
+    event_type_modifier->exclude_user = true;
+    event_type_modifier->exclude_kernel = true;
+    event_type_modifier->exclude_hv = true;
+  }
+  if (modifier.find_first_of("GH") != std::string::npos) {
+    event_type_modifier->exclude_guest = true;
+    event_type_modifier->exclude_host = true;
+  }
+
+  for (auto& c : modifier) {
+    switch (c) {
+      case 'u':
+        event_type_modifier->exclude_user = false;
+        break;
+      case 'k':
+        event_type_modifier->exclude_kernel = false;
+        break;
+      case 'h':
+        event_type_modifier->exclude_hv = false;
+        break;
+      case 'G':
+        event_type_modifier->exclude_guest = false;
+        break;
+      case 'H':
+        event_type_modifier->exclude_host = false;
+        break;
+      case 'p':
+        event_type_modifier->precise_ip++;
+        break;
+      case ' ':
+        break;
+      default:
+        LOG(ERROR) << "Unknown event type modifier '" << c << "'";
+    }
+  }
+  return event_type_modifier;
+}
diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h
new file mode 100644
index 0000000..9c365fa
--- /dev/null
+++ b/simpleperf/event_type.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_EVENT_H_
+#define SIMPLE_PERF_EVENT_H_
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+// EventType represents one type of event, like cpu_cycle_event, cache_misses_event.
+// The user knows one event type by its name, and the kernel knows one event type by its
+// (type, config) pair. EventType connects the two representations, and tells the user if
+// the event type is supported by the kernel.
+
+struct EventType {
+  EventType(const std::string& name, uint32_t type, uint64_t config)
+      : name(name), type(type), config(config) {
+  }
+
+  EventType() : type(0), config(0) {
+  }
+
+  bool IsSupportedByKernel() const;
+
+  std::string name;
+  uint32_t type;
+  uint64_t config;
+};
+
+const std::vector<EventType>& GetAllEventTypes();
+const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config);
+
+struct EventTypeAndModifier {
+  EventType event_type;
+  bool exclude_user;
+  bool exclude_kernel;
+  bool exclude_hv;
+  bool exclude_host;
+  bool exclude_guest;
+  int precise_ip : 2;
+
+  EventTypeAndModifier()
+      : exclude_user(false),
+        exclude_kernel(false),
+        exclude_hv(false),
+        exclude_host(false),
+        exclude_guest(false),
+        precise_ip(0) {
+  }
+};
+
+std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_type_str,
+                                                     bool report_unsupported_type = true);
+
+#endif  // SIMPLE_PERF_EVENT_H_
diff --git a/simpleperf/event_type_table.h b/simpleperf/event_type_table.h
new file mode 100644
index 0000000..a77be0a
--- /dev/null
+++ b/simpleperf/event_type_table.h
@@ -0,0 +1,65 @@
+// This file is auto-generated by generate-event_table.py.
+
+{"cpu-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES},
+{"instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS},
+{"cache-references", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES},
+{"cache-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES},
+{"branch-instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS},
+{"branch-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES},
+{"bus-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES},
+{"stalled-cycles-frontend", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND},
+{"stalled-cycles-backend", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND},
+
+{"cpu-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK},
+{"task-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK},
+{"page-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS},
+{"context-switches", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES},
+{"cpu-migrations", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS},
+{"minor-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN},
+{"major-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ},
+{"alignment-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS},
+{"emulation-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS},
+
+{"L1-dcache-loads", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"L1-dcache-load-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"L1-dcache-stores", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"L1-dcache-store-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"L1-dcache-prefetches", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"L1-dcache-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"L1-icache-loads", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"L1-icache-load-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"L1-icache-stores", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"L1-icache-store-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"L1-icache-prefetches", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"L1-icache-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"LLC-loads", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"LLC-load-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"LLC-stores", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"LLC-store-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"LLC-prefetches", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"LLC-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"dTLB-loads", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"dTLB-load-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"dTLB-stores", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"dTLB-store-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"dTLB-prefetches", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"dTLB-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"iTLB-loads", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"iTLB-load-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"iTLB-stores", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"iTLB-store-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"iTLB-prefetches", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"iTLB-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"branch-loads", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"branch-load-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"branch-stores", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"branch-store-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"branch-prefetches", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"branch-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"node-loads", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"node-load-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"node-stores", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"node-store-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+{"node-prefetches", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16))},
+{"node-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | (PERF_COUNT_HW_CACHE_RESULT_MISS << 16))},
+
diff --git a/simpleperf/generate_event_type_table.py b/simpleperf/generate_event_type_table.py
new file mode 100755
index 0000000..9fbd278
--- /dev/null
+++ b/simpleperf/generate_event_type_table.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+def gen_event_type_entry_str(event_type_name, event_type, event_config):
+    """
+    return string like:
+    {"cpu-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES},
+    """
+    return '{"%s", %s, %s},\n' % (event_type_name, event_type, event_config)
+
+
+def gen_hardware_events():
+    hardware_configs = ["cpu-cycles",
+                        "instructions",
+                        "cache-references",
+                        "cache-misses",
+                        "branch-instructions",
+                        "branch-misses",
+                        "bus-cycles",
+                        "stalled-cycles-frontend",
+                        "stalled-cycles-backend",
+                        ]
+    generated_str = ""
+    for config in hardware_configs:
+        event_type_name = config
+        event_config = "PERF_COUNT_HW_" + config.replace('-', '_').upper()
+
+        generated_str += gen_event_type_entry_str(
+            event_type_name, "PERF_TYPE_HARDWARE", event_config)
+
+    return generated_str
+
+
+def gen_software_events():
+    software_configs = ["cpu-clock",
+                        "task-clock",
+                        "page-faults",
+                        "context-switches",
+                        "cpu-migrations",
+                        ["minor-faults", "PERF_COUNT_SW_PAGE_FAULTS_MIN"],
+                        ["major-faults", "PERF_COUNT_SW_PAGE_FAULTS_MAJ"],
+                        "alignment-faults",
+                        "emulation-faults",
+                        ]
+    generated_str = ""
+    for config in software_configs:
+        if type(config) is list:
+            event_type_name = config[0]
+            event_config = config[1]
+        else:
+            event_type_name = config
+            event_config = "PERF_COUNT_SW_" + config.replace('-', '_').upper()
+
+        generated_str += gen_event_type_entry_str(
+            event_type_name, "PERF_TYPE_SOFTWARE", event_config)
+
+    return generated_str
+
+
+def gen_hw_cache_events():
+    hw_cache_types = [["L1-dcache", "PERF_COUNT_HW_CACHE_L1D"],
+                      ["L1-icache", "PERF_COUNT_HW_CACHE_L1I"],
+                      ["LLC", "PERF_COUNT_HW_CACHE_LL"],
+                      ["dTLB", "PERF_COUNT_HW_CACHE_DTLB"],
+                      ["iTLB", "PERF_COUNT_HW_CACHE_ITLB"],
+                      ["branch", "PERF_COUNT_HW_CACHE_BPU"],
+                      ["node", "PERF_COUNT_HW_CACHE_NODE"],
+                      ]
+    hw_cache_ops = [["loads", "load", "PERF_COUNT_HW_CACHE_OP_READ"],
+                    ["stores", "store", "PERF_COUNT_HW_CACHE_OP_WRITE"],
+                    ["prefetches", "prefetch",
+                     "PERF_COUNT_HW_CACHE_OP_PREFETCH"],
+                    ]
+    hw_cache_op_results = [["accesses", "PERF_COUNT_HW_CACHE_RESULT_ACCESS"],
+                           ["misses", "PERF_COUNT_HW_CACHE_RESULT_MISS"],
+                           ]
+    generated_str = ""
+    for (type_name, type_config) in hw_cache_types:
+        for (op_name_access, op_name_miss, op_config) in hw_cache_ops:
+            for (result_name, result_config) in hw_cache_op_results:
+                if result_name == "accesses":
+                    event_type_name = type_name + '-' + op_name_access
+                else:
+                    event_type_name = type_name + '-' + \
+                        op_name_miss + '-' + result_name
+                event_config = "((%s) | (%s << 8) | (%s << 16))" % (
+                    type_config, op_config, result_config)
+                generated_str += gen_event_type_entry_str(
+                    event_type_name, "PERF_TYPE_HW_CACHE", event_config)
+
+    return generated_str
+
+
+def gen_events():
+    generated_str = "// This file is auto-generated by generate-event_table.py.\n\n"
+    generated_str += gen_hardware_events() + '\n'
+    generated_str += gen_software_events() + '\n'
+    generated_str += gen_hw_cache_events() + '\n'
+    return generated_str
+
+generated_str = gen_events()
+fh = open('event_type_table.h', 'w')
+fh.write(generated_str)
+fh.close()
diff --git a/ext4_utils/uuid.h b/simpleperf/gtest_main.cpp
similarity index 68%
rename from ext4_utils/uuid.h
rename to simpleperf/gtest_main.cpp
index ff1b438..33ec32f 100644
--- a/ext4_utils/uuid.h
+++ b/simpleperf/gtest_main.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef _UUID_H_
-#define _UUID_H_
+#include <gtest/gtest.h>
 
-#include "ext4_utils.h"
+#include <base/logging.h>
 
-void generate_uuid(const char *namespace, const char *name, u8 result[16]);
-
-#endif
+int main(int argc, char** argv) {
+  InitLogging(argv, android::base::StderrLogger);
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp
new file mode 100644
index 0000000..93c52e5
--- /dev/null
+++ b/simpleperf/main.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "command.h"
+
+int main(int argc, char** argv) {
+  InitLogging(argv, android::base::StderrLogger);
+  std::vector<std::string> args;
+
+  if (argc == 1) {
+    args.push_back("help");
+  } else {
+    for (int i = 1; i < argc; ++i) {
+      if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
+        args.insert(args.begin(), "help");
+      } else {
+        args.push_back(argv[i]);
+      }
+    }
+  }
+
+  std::unique_ptr<Command> command = CreateCommandInstance(args[0]);
+  if (command == nullptr) {
+    LOG(ERROR) << "malformed command line: unknown command " << args[0];
+    return 1;
+  }
+  std::string command_name = args[0];
+  args.erase(args.begin());
+
+  LOG(DEBUG) << "command '" << command_name << "' starts running";
+  bool result = command->Run(args);
+  LOG(DEBUG) << "command '" << command_name << "' "
+             << (result ? "finished successfully" : "failed");
+  return result ? 0 : 1;
+}
diff --git a/ext4_utils/uuid.h b/simpleperf/perf_event.h
similarity index 66%
copy from ext4_utils/uuid.h
copy to simpleperf/perf_event.h
index ff1b438..1688dc9 100644
--- a/ext4_utils/uuid.h
+++ b/simpleperf/perf_event.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
-#ifndef _UUID_H_
-#define _UUID_H_
+#ifndef SIMPLE_PERF_PERF_EVENT_H_
+#define SIMPLE_PERF_PERF_EVENT_H_
 
-#include "ext4_utils.h"
+#if defined(USE_BIONIC_PERF_EVENT_H)
 
-void generate_uuid(const char *namespace, const char *name, u8 result[16]);
+#include <libc/kernel/uapi/linux/perf_event.h>
+
+#else
+
+#include <linux/perf_event.h>
 
 #endif
+
+#endif  // SIMPLE_PERF_PERF_EVENT_H_
diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp
new file mode 100644
index 0000000..4d41165
--- /dev/null
+++ b/simpleperf/read_elf.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "read_elf.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <algorithm>
+#include <base/file.h>
+#include <base/logging.h>
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+#include <llvm/ADT/StringRef.h>
+#include <llvm/Object/Binary.h>
+#include <llvm/Object/ELFObjectFile.h>
+#include <llvm/Object/ObjectFile.h>
+
+#pragma clang diagnostic pop
+
+#include "utils.h"
+
+#define ELF_NOTE_GNU "GNU"
+#define NT_GNU_BUILD_ID 3
+
+static bool GetBuildIdFromNoteSection(const char* section, size_t section_size, BuildId* build_id) {
+  const char* p = section;
+  const char* end = p + section_size;
+  while (p < end) {
+    CHECK_LE(p + 12, end);
+    size_t namesz = *reinterpret_cast<const uint32_t*>(p);
+    p += 4;
+    size_t descsz = *reinterpret_cast<const uint32_t*>(p);
+    p += 4;
+    uint32_t type = *reinterpret_cast<const uint32_t*>(p);
+    p += 4;
+    namesz = ALIGN(namesz, 4);
+    descsz = ALIGN(descsz, 4);
+    CHECK_LE(p + namesz + descsz, end);
+    if ((type == NT_GNU_BUILD_ID) && (strcmp(p, ELF_NOTE_GNU) == 0)) {
+      std::fill(build_id->begin(), build_id->end(), 0);
+      memcpy(build_id->data(), p + namesz, std::min(build_id->size(), descsz));
+      return true;
+    }
+    p += namesz + descsz;
+  }
+  return false;
+}
+
+bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id) {
+  std::string content;
+  if (!android::base::ReadFileToString(filename, &content)) {
+    LOG(DEBUG) << "can't read note file " << filename;
+    return false;
+  }
+  if (GetBuildIdFromNoteSection(content.c_str(), content.size(), build_id) == false) {
+    LOG(DEBUG) << "can't read build_id from note file " << filename;
+    return false;
+  }
+  return true;
+}
+
+template <class ELFT>
+bool GetBuildIdFromELFFile(const llvm::object::ELFFile<ELFT>* elf, BuildId* build_id) {
+  for (auto section_iterator = elf->begin_sections(); section_iterator != elf->end_sections();
+       ++section_iterator) {
+    if (section_iterator->sh_type == llvm::ELF::SHT_NOTE) {
+      auto contents = elf->getSectionContents(&*section_iterator);
+      if (contents.getError()) {
+        LOG(DEBUG) << "read note section error";
+        continue;
+      }
+      if (GetBuildIdFromNoteSection(reinterpret_cast<const char*>(contents->data()),
+                                    contents->size(), build_id)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id) {
+  auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename));
+  if (owning_binary.getError()) {
+    PLOG(DEBUG) << "can't open file " << filename;
+    return false;
+  }
+  bool result = false;
+  llvm::object::Binary* binary = owning_binary.get().getBinary();
+  if (auto obj = llvm::dyn_cast<llvm::object::ObjectFile>(binary)) {
+    if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(obj)) {
+      result = GetBuildIdFromELFFile(elf->getELFFile(), build_id);
+    } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) {
+      result = GetBuildIdFromELFFile(elf->getELFFile(), build_id);
+    } else {
+      PLOG(DEBUG) << "unknown elf format in file " << filename;
+    }
+  }
+  if (!result) {
+    PLOG(DEBUG) << "can't read build_id from file " << filename;
+  }
+  return result;
+}
+
+bool IsArmMappingSymbol(const char* name) {
+  // Mapping symbols in arm, which are described in "ELF for ARM Architecture" and
+  // "ELF for ARM 64-bit Architecture". The regular expression to match mapping symbol
+  // is ^\$(a|d|t|x)(\..*)?$
+  return name[0] == '$' && strchr("adtx", name[1]) != nullptr && (name[2] == '\0' || name[2] == '.');
+}
+
+template <class ELFT>
+bool ParseSymbolsFromELFFile(const llvm::object::ELFFile<ELFT>* elf,
+                             std::function<void(const ElfFileSymbol&)> callback) {
+  bool is_arm = (elf->getHeader()->e_machine == llvm::ELF::EM_ARM ||
+                 elf->getHeader()->e_machine == llvm::ELF::EM_AARCH64);
+  auto begin = elf->begin_symbols();
+  auto end = elf->end_symbols();
+  if (begin == end) {
+    begin = elf->begin_dynamic_symbols();
+    end = elf->end_dynamic_symbols();
+  }
+  for (; begin != end; ++begin) {
+    auto& elf_symbol = *begin;
+
+    ElfFileSymbol symbol;
+    memset(&symbol, '\0', sizeof(symbol));
+
+    auto shdr = elf->getSection(&elf_symbol);
+    if (shdr == nullptr) {
+      continue;
+    }
+    auto section_name = elf->getSectionName(shdr);
+    if (section_name.getError() || section_name.get().empty()) {
+      continue;
+    }
+    if (section_name.get() == ".text") {
+      symbol.is_in_text_section = true;
+    }
+
+    auto symbol_name = elf->getSymbolName(begin);
+    if (symbol_name.getError()) {
+      continue;
+    }
+    symbol.name = symbol_name.get();
+    if (symbol.name.empty()) {
+      continue;
+    }
+
+    symbol.start_in_file = elf_symbol.st_value - shdr->sh_addr + shdr->sh_offset;
+    if ((symbol.start_in_file & 1) != 0 && is_arm) {
+      // Arm sets bit 0 to mark it as thumb code, remove the flag.
+      symbol.start_in_file &= ~1;
+    }
+    symbol.len = elf_symbol.st_size;
+    int type = elf_symbol.getType();
+    if (type == llvm::ELF::STT_FUNC) {
+      symbol.is_func = true;
+    } else if (type == llvm::ELF::STT_NOTYPE) {
+      if (symbol.is_in_text_section) {
+        symbol.is_label = true;
+        if (is_arm) {
+          // Remove mapping symbols in arm.
+          const char* p = (symbol.name.compare(0, linker_prefix.size(), linker_prefix) == 0)
+                              ? symbol.name.c_str() + linker_prefix.size()
+                              : symbol.name.c_str();
+          if (IsArmMappingSymbol(p)) {
+            symbol.is_label = false;
+          }
+        }
+      }
+    }
+
+    callback(symbol);
+  }
+  return true;
+}
+
+bool ParseSymbolsFromElfFile(const std::string& filename,
+                             std::function<void(const ElfFileSymbol&)> callback) {
+  auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename));
+  if (owning_binary.getError()) {
+    PLOG(DEBUG) << "can't open file '" << filename << "'";
+    return false;
+  }
+  bool result = false;
+  llvm::object::Binary* binary = owning_binary.get().getBinary();
+  if (auto obj = llvm::dyn_cast<llvm::object::ObjectFile>(binary)) {
+    if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(obj)) {
+      result = ParseSymbolsFromELFFile(elf->getELFFile(), callback);
+    } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) {
+      result = ParseSymbolsFromELFFile(elf->getELFFile(), callback);
+    } else {
+      PLOG(DEBUG) << "unknown elf format in file" << filename;
+    }
+  }
+  if (!result) {
+    PLOG(DEBUG) << "can't parse symbols from file " << filename;
+  }
+  return result;
+}
diff --git a/simpleperf/read_elf.h b/simpleperf/read_elf.h
new file mode 100644
index 0000000..96eb2f3
--- /dev/null
+++ b/simpleperf/read_elf.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_READ_ELF_H_
+#define SIMPLE_PERF_READ_ELF_H_
+
+#include <functional>
+#include <string>
+#include "build_id.h"
+
+bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id);
+bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id);
+
+// The symbol prefix used to indicate that the symbol belongs to android linker.
+static const std::string linker_prefix = "__dl_";
+
+struct ElfFileSymbol {
+  uint64_t start_in_file;
+  uint64_t len;
+  bool is_func;
+  bool is_label;
+  bool is_in_text_section;
+  std::string name;
+};
+
+bool ParseSymbolsFromElfFile(const std::string& filename,
+                             std::function<void(const ElfFileSymbol&)> callback);
+
+// Expose the following functions for unit tests.
+bool IsArmMappingSymbol(const char* name);
+
+#endif  // SIMPLE_PERF_READ_ELF_H_
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
new file mode 100644
index 0000000..c0ff660
--- /dev/null
+++ b/simpleperf/read_elf_test.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "read_elf.h"
+
+#include <gtest/gtest.h>
+
+static void ParseSymbol(const ElfFileSymbol& symbol, bool* result) {
+  if (symbol.is_func) {
+    *result = true;
+  }
+}
+
+TEST(read_elf, parse_symbols_from_elf_file) {
+  char elf_file[PATH_MAX];
+  ssize_t elf_file_len = readlink("/proc/self/exe", elf_file, sizeof(elf_file));
+  ASSERT_GT(elf_file_len, 0L);
+  ASSERT_LT(static_cast<size_t>(elf_file_len), sizeof(elf_file));
+  elf_file[elf_file_len] = '\0';
+
+  bool result = false;
+  ASSERT_TRUE(
+      ParseSymbolsFromElfFile(elf_file, std::bind(ParseSymbol, std::placeholders::_1, &result)));
+  ASSERT_TRUE(result);
+}
+
+TEST(read_elf, arm_mapping_symbol) {
+  ASSERT_TRUE(IsArmMappingSymbol("$a"));
+  ASSERT_FALSE(IsArmMappingSymbol("$b"));
+  ASSERT_TRUE(IsArmMappingSymbol("$a.anything"));
+  ASSERT_FALSE(IsArmMappingSymbol("$a_no_dot"));
+}
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
new file mode 100644
index 0000000..f9d7e5b
--- /dev/null
+++ b/simpleperf/record.cpp
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "record.h"
+
+#include <inttypes.h>
+#include <unordered_map>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#include "environment.h"
+#include "utils.h"
+
+static std::string RecordTypeToString(int record_type) {
+  static std::unordered_map<int, std::string> record_type_names = {
+      {PERF_RECORD_MMAP, "mmap"},         {PERF_RECORD_LOST, "lost"},
+      {PERF_RECORD_COMM, "comm"},         {PERF_RECORD_EXIT, "exit"},
+      {PERF_RECORD_THROTTLE, "throttle"}, {PERF_RECORD_UNTHROTTLE, "unthrottle"},
+      {PERF_RECORD_FORK, "fork"},         {PERF_RECORD_READ, "read"},
+      {PERF_RECORD_SAMPLE, "sample"},     {PERF_RECORD_BUILD_ID, "build_id"},
+      {PERF_RECORD_MMAP2, "mmap2"},
+  };
+
+  auto it = record_type_names.find(record_type);
+  if (it != record_type_names.end()) {
+    return it->second;
+  }
+  return android::base::StringPrintf("unknown(%d)", record_type);
+}
+
+template <class T>
+void MoveFromBinaryFormat(T& data, const char*& p) {
+  data = *reinterpret_cast<const T*>(p);
+  p += sizeof(T);
+}
+
+template <class T>
+void MoveFromBinaryFormat(T* data_p, size_t n, const char*& p) {
+  size_t size = n * sizeof(T);
+  memcpy(data_p, p, size);
+  p += size;
+}
+
+template <class T>
+void MoveToBinaryFormat(const T& data, char*& p) {
+  *reinterpret_cast<T*>(p) = data;
+  p += sizeof(T);
+}
+
+SampleId::SampleId() {
+  memset(this, 0, sizeof(SampleId));
+}
+
+// Return sample_id size in binary format.
+size_t SampleId::CreateContent(const perf_event_attr& attr) {
+  sample_id_all = attr.sample_id_all;
+  sample_type = attr.sample_type;
+  // Other data are not necessary. TODO: Set missing SampleId data.
+  size_t size = 0;
+  if (sample_id_all) {
+    if (sample_type & PERF_SAMPLE_TID) {
+      size += sizeof(PerfSampleTidType);
+    }
+    if (sample_type & PERF_SAMPLE_TIME) {
+      size += sizeof(PerfSampleTimeType);
+    }
+    if (sample_type & PERF_SAMPLE_ID) {
+      size += sizeof(PerfSampleIdType);
+    }
+    if (sample_type & PERF_SAMPLE_STREAM_ID) {
+      size += sizeof(PerfSampleStreamIdType);
+    }
+    if (sample_type & PERF_SAMPLE_CPU) {
+      size += sizeof(PerfSampleCpuType);
+    }
+  }
+  return size;
+}
+
+void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end) {
+  sample_id_all = attr.sample_id_all;
+  sample_type = attr.sample_type;
+  if (sample_id_all) {
+    if (sample_type & PERF_SAMPLE_TID) {
+      MoveFromBinaryFormat(tid_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_TIME) {
+      MoveFromBinaryFormat(time_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_ID) {
+      MoveFromBinaryFormat(id_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_STREAM_ID) {
+      MoveFromBinaryFormat(stream_id_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_CPU) {
+      MoveFromBinaryFormat(cpu_data, p);
+    }
+    // TODO: Add parsing of PERF_SAMPLE_IDENTIFIER.
+  }
+  CHECK_LE(p, end);
+  if (p < end) {
+    LOG(DEBUG) << "Record SampleId part has " << end - p << " bytes left\n";
+  }
+}
+
+void SampleId::WriteToBinaryFormat(char*& p) const {
+  if (sample_id_all) {
+    if (sample_type & PERF_SAMPLE_TID) {
+      MoveToBinaryFormat(tid_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_TIME) {
+      MoveToBinaryFormat(time_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_ID) {
+      MoveToBinaryFormat(id_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_STREAM_ID) {
+      MoveToBinaryFormat(stream_id_data, p);
+    }
+    if (sample_type & PERF_SAMPLE_CPU) {
+      MoveToBinaryFormat(cpu_data, p);
+    }
+  }
+}
+
+void SampleId::Dump(size_t indent) const {
+  if (sample_id_all) {
+    if (sample_type & PERF_SAMPLE_TID) {
+      PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid, tid_data.tid);
+    }
+    if (sample_type & PERF_SAMPLE_TIME) {
+      PrintIndented(indent, "sample_id: time %" PRId64 "\n", time_data.time);
+    }
+    if (sample_type & PERF_SAMPLE_ID) {
+      PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", id_data.id);
+    }
+    if (sample_type & PERF_SAMPLE_STREAM_ID) {
+      PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", stream_id_data.stream_id);
+    }
+    if (sample_type & PERF_SAMPLE_CPU) {
+      PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu, cpu_data.res);
+    }
+  }
+}
+
+Record::Record() {
+  memset(&header, 0, sizeof(header));
+}
+
+Record::Record(const perf_event_header* pheader) {
+  header = *pheader;
+}
+
+void Record::Dump(size_t indent) const {
+  PrintIndented(indent, "record %s: type %u, misc %u, size %u\n",
+                RecordTypeToString(header.type).c_str(), header.type, header.misc, header.size);
+  DumpData(indent + 1);
+  sample_id.Dump(indent + 1);
+}
+
+MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader)
+    : Record(pheader) {
+  const char* p = reinterpret_cast<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+  MoveFromBinaryFormat(data, p);
+  filename = p;
+  p += ALIGN(filename.size() + 1, 8);
+  CHECK_LE(p, end);
+  sample_id.ReadFromBinaryFormat(attr, p, end);
+}
+
+void MmapRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "pid %u, tid %u, addr 0x" PRIx64 ", len 0x%" PRIx64 "\n", data.pid,
+                data.tid, data.addr, data.len);
+  PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data.pgoff, filename.c_str());
+}
+
+std::vector<char> MmapRecord::BinaryFormat() const {
+  std::vector<char> buf(header.size);
+  char* p = buf.data();
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(data, p);
+  strcpy(p, filename.c_str());
+  p += ALIGN(filename.size() + 1, 8);
+  sample_id.WriteToBinaryFormat(p);
+  return buf;
+}
+
+Mmap2Record::Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader)
+    : Record(pheader) {
+  const char* p = reinterpret_cast<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+  MoveFromBinaryFormat(data, p);
+  filename = p;
+  p += ALIGN(filename.size() + 1, 8);
+  CHECK_LE(p, end);
+  sample_id.ReadFromBinaryFormat(attr, p, end);
+}
+
+void Mmap2Record::DumpData(size_t indent) const {
+  PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid,
+                data.tid, data.addr, data.len);
+  PrintIndented(indent,
+                "pgoff 0x" PRIx64 ", maj %u, min %u, ino %" PRId64 ", ino_generation %" PRIu64 "\n",
+                data.pgoff, data.maj, data.min, data.ino, data.ino_generation);
+  PrintIndented(indent, "prot %u, flags %u, filenames %s\n", data.prot, data.flags,
+                filename.c_str());
+}
+
+CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* pheader)
+    : Record(pheader) {
+  const char* p = reinterpret_cast<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+  MoveFromBinaryFormat(data, p);
+  comm = p;
+  p += ALIGN(strlen(p) + 1, 8);
+  CHECK_LE(p, end);
+  sample_id.ReadFromBinaryFormat(attr, p, end);
+}
+
+void CommRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "pid %u, tid %u, comm %s\n", data.pid, data.tid, comm.c_str());
+}
+
+std::vector<char> CommRecord::BinaryFormat() const {
+  std::vector<char> buf(header.size);
+  char* p = buf.data();
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(data, p);
+  strcpy(p, comm.c_str());
+  p += ALIGN(comm.size() + 1, 8);
+  sample_id.WriteToBinaryFormat(p);
+  return buf;
+}
+
+ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader)
+    : Record(pheader) {
+  const char* p = reinterpret_cast<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+  MoveFromBinaryFormat(data, p);
+  CHECK_LE(p, end);
+  sample_id.ReadFromBinaryFormat(attr, p, end);
+}
+
+void ExitOrForkRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data.pid, data.ppid, data.tid,
+                data.ptid);
+}
+
+std::vector<char> ForkRecord::BinaryFormat() const {
+  std::vector<char> buf(header.size);
+  char* p = buf.data();
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(data, p);
+  sample_id.WriteToBinaryFormat(p);
+  return buf;
+}
+
+SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader)
+    : Record(pheader) {
+  const char* p = reinterpret_cast<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+  sample_type = attr.sample_type;
+
+  if (sample_type & PERF_SAMPLE_IP) {
+    MoveFromBinaryFormat(ip_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_TID) {
+    MoveFromBinaryFormat(tid_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_TIME) {
+    MoveFromBinaryFormat(time_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_ADDR) {
+    MoveFromBinaryFormat(addr_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_ID) {
+    MoveFromBinaryFormat(id_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_STREAM_ID) {
+    MoveFromBinaryFormat(stream_id_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_CPU) {
+    MoveFromBinaryFormat(cpu_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_PERIOD) {
+    MoveFromBinaryFormat(period_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+    uint64_t nr;
+    MoveFromBinaryFormat(nr, p);
+    callchain_data.ips.resize(nr);
+    MoveFromBinaryFormat(callchain_data.ips.data(), nr, p);
+  }
+  if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+    uint64_t nr;
+    MoveFromBinaryFormat(nr, p);
+    branch_stack_data.stack.resize(nr);
+    MoveFromBinaryFormat(branch_stack_data.stack.data(), nr, p);
+  }
+  // TODO: Add parsing of other PERF_SAMPLE_*.
+  CHECK_LE(p, end);
+  if (p < end) {
+    LOG(DEBUG) << "Record has " << end - p << " bytes left\n";
+  }
+}
+
+void SampleRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "sample_type: 0x%" PRIx64 "\n", sample_type);
+  if (sample_type & PERF_SAMPLE_IP) {
+    PrintIndented(indent, "ip %p\n", reinterpret_cast<void*>(ip_data.ip));
+  }
+  if (sample_type & PERF_SAMPLE_TID) {
+    PrintIndented(indent, "pid %u, tid %u\n", tid_data.pid, tid_data.tid);
+  }
+  if (sample_type & PERF_SAMPLE_TIME) {
+    PrintIndented(indent, "time %" PRId64 "\n", time_data.time);
+  }
+  if (sample_type & PERF_SAMPLE_ADDR) {
+    PrintIndented(indent, "addr %p\n", reinterpret_cast<void*>(addr_data.addr));
+  }
+  if (sample_type & PERF_SAMPLE_ID) {
+    PrintIndented(indent, "id %" PRId64 "\n", id_data.id);
+  }
+  if (sample_type & PERF_SAMPLE_STREAM_ID) {
+    PrintIndented(indent, "stream_id %" PRId64 "\n", stream_id_data.stream_id);
+  }
+  if (sample_type & PERF_SAMPLE_CPU) {
+    PrintIndented(indent, "cpu %u, res %u\n", cpu_data.cpu, cpu_data.res);
+  }
+  if (sample_type & PERF_SAMPLE_PERIOD) {
+    PrintIndented(indent, "period %" PRId64 "\n", period_data.period);
+  }
+  if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+    PrintIndented(indent, "callchain nr=%" PRIu64 "\n", callchain_data.ips.size());
+    for (auto& ip : callchain_data.ips) {
+      PrintIndented(indent + 1, "0x%" PRIx64 "\n", ip);
+    }
+  }
+  if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+    PrintIndented(indent, "branch_stack nr=%" PRIu64 "\n", branch_stack_data.stack.size());
+    for (auto& item : branch_stack_data.stack) {
+      PrintIndented(indent + 1, "from 0x%" PRIx64 ", to 0x%" PRIx64 ", flags 0x%" PRIx64 "\n",
+                    item.from, item.to, item.flags);
+    }
+  }
+}
+
+BuildIdRecord::BuildIdRecord(const perf_event_header* pheader) : Record(pheader) {
+  const char* p = reinterpret_cast<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+  MoveFromBinaryFormat(pid, p);
+  std::copy_n(p, build_id.size(), build_id.begin());
+  p += ALIGN(build_id.size(), 8);
+  filename = p;
+  p += ALIGN(filename.size() + 1, 64);
+  CHECK_EQ(p, end);
+}
+
+void BuildIdRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "pid %u\n", pid);
+  PrintIndented(indent, "build_id 0x");
+  for (auto& c : build_id) {
+    printf("%02x", c);
+  }
+  printf("\n");
+  PrintIndented(indent, "filename %s\n", filename.c_str());
+}
+
+std::vector<char> BuildIdRecord::BinaryFormat() const {
+  std::vector<char> buf(header.size);
+  char* p = buf.data();
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(pid, p);
+  memcpy(p, build_id.data(), build_id.size());
+  p += ALIGN(build_id.size(), 8);
+  strcpy(p, filename.c_str());
+  p += ALIGN(filename.size() + 1, 64);
+  return buf;
+}
+
+std::unique_ptr<const Record> ReadRecordFromBuffer(const perf_event_attr& attr,
+                                                   const perf_event_header* pheader) {
+  switch (pheader->type) {
+    case PERF_RECORD_MMAP:
+      return std::unique_ptr<const Record>(new MmapRecord(attr, pheader));
+    case PERF_RECORD_MMAP2:
+      return std::unique_ptr<const Record>(new Mmap2Record(attr, pheader));
+    case PERF_RECORD_COMM:
+      return std::unique_ptr<const Record>(new CommRecord(attr, pheader));
+    case PERF_RECORD_EXIT:
+      return std::unique_ptr<const Record>(new ExitRecord(attr, pheader));
+    case PERF_RECORD_FORK:
+      return std::unique_ptr<const Record>(new ForkRecord(attr, pheader));
+    case PERF_RECORD_SAMPLE:
+      return std::unique_ptr<const Record>(new SampleRecord(attr, pheader));
+    default:
+      return std::unique_ptr<const Record>(new Record(pheader));
+  }
+}
+
+MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+                            uint64_t addr, uint64_t len, uint64_t pgoff,
+                            const std::string& filename) {
+  MmapRecord record;
+  record.header.type = PERF_RECORD_MMAP;
+  record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+  record.data.pid = pid;
+  record.data.tid = tid;
+  record.data.addr = addr;
+  record.data.len = len;
+  record.data.pgoff = pgoff;
+  record.filename = filename;
+  size_t sample_id_size = record.sample_id.CreateContent(attr);
+  record.header.size = sizeof(record.header) + sizeof(record.data) +
+                       ALIGN(record.filename.size() + 1, 8) + sample_id_size;
+  return record;
+}
+
+CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+                            const std::string& comm) {
+  CommRecord record;
+  record.header.type = PERF_RECORD_COMM;
+  record.header.misc = 0;
+  record.data.pid = pid;
+  record.data.tid = tid;
+  record.comm = comm;
+  size_t sample_id_size = record.sample_id.CreateContent(attr);
+  record.header.size = sizeof(record.header) + sizeof(record.data) +
+                       ALIGN(record.comm.size() + 1, 8) + sample_id_size;
+  return record;
+}
+
+ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
+                            uint32_t ptid) {
+  ForkRecord record;
+  record.header.type = PERF_RECORD_FORK;
+  record.header.misc = 0;
+  record.data.pid = pid;
+  record.data.ppid = ppid;
+  record.data.tid = tid;
+  record.data.ptid = ptid;
+  record.data.time = 0;
+  size_t sample_id_size = record.sample_id.CreateContent(attr);
+  record.header.size = sizeof(record.header) + sizeof(record.data) + sample_id_size;
+  return record;
+}
+
+BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
+                                  const std::string& filename) {
+  BuildIdRecord record;
+  record.header.type = PERF_RECORD_BUILD_ID;
+  record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+  record.pid = pid;
+  record.build_id = build_id;
+  record.filename = filename;
+  record.header.size = sizeof(record.header) + sizeof(record.pid) +
+                       ALIGN(record.build_id.size(), 8) + ALIGN(filename.size() + 1, 64);
+  return record;
+}
diff --git a/simpleperf/record.h b/simpleperf/record.h
new file mode 100644
index 0000000..6f41234
--- /dev/null
+++ b/simpleperf/record.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_RECORD_H_
+#define SIMPLE_PERF_RECORD_H_
+
+#include <string>
+#include <vector>
+
+#include "build_id.h"
+#include "perf_event.h"
+
+struct KernelMmap;
+struct ModuleMmap;
+struct ThreadComm;
+struct ThreadMmap;
+
+enum user_record_type {
+  PERF_RECORD_ATTR = 64,
+  PERF_RECORD_EVENT_TYPE,
+  PERF_RECORD_TRACING_DATA,
+  PERF_RECORD_BUILD_ID,
+  PERF_RECORD_FINISHED_ROUND,
+};
+
+struct PerfSampleIpType {
+  uint64_t ip;
+};
+
+struct PerfSampleTidType {
+  uint32_t pid, tid;
+};
+
+struct PerfSampleTimeType {
+  uint64_t time;
+};
+
+struct PerfSampleAddrType {
+  uint64_t addr;
+};
+
+struct PerfSampleIdType {
+  uint64_t id;
+};
+
+struct PerfSampleStreamIdType {
+  uint64_t stream_id;
+};
+
+struct PerfSampleCpuType {
+  uint32_t cpu, res;
+};
+
+struct PerfSamplePeriodType {
+  uint64_t period;
+};
+
+struct PerfSampleCallChainType {
+  std::vector<uint64_t> ips;
+};
+
+struct PerfSampleBranchStackType {
+  struct BranchStackItemType {
+    uint64_t from;
+    uint64_t to;
+    uint64_t flags;
+  };
+  std::vector<BranchStackItemType> stack;
+};
+
+// SampleId is optional at the end of a record in binary format. Its content is determined by
+// sample_id_all and sample_type in perf_event_attr. To avoid the complexity of referring to
+// perf_event_attr each time, we copy sample_id_all and sample_type inside the SampleId structure.
+struct SampleId {
+  bool sample_id_all;
+  uint64_t sample_type;
+
+  PerfSampleTidType tid_data;             // Valid if sample_id_all && PERF_SAMPLE_TID.
+  PerfSampleTimeType time_data;           // Valid if sample_id_all && PERF_SAMPLE_TIME.
+  PerfSampleIdType id_data;               // Valid if sample_id_all && PERF_SAMPLE_ID.
+  PerfSampleStreamIdType stream_id_data;  // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID.
+  PerfSampleCpuType cpu_data;             // Valid if sample_id_all && PERF_SAMPLE_CPU.
+
+  SampleId();
+
+  // Create the content of sample_id. It depends on the attr we use.
+  size_t CreateContent(const perf_event_attr& attr);
+
+  // Parse sample_id from binary format in the buffer pointed by p.
+  void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end);
+
+  // Write the binary format of sample_id to the buffer pointed by p.
+  void WriteToBinaryFormat(char*& p) const;
+  void Dump(size_t indent) const;
+};
+
+// Usually one record contains the following three parts in order in binary format:
+//   perf_event_header (at the head of a record, containing type and size information)
+//   data depends on the record type
+//   sample_id (optional part at the end of a record)
+// We hold the common parts (perf_event_header and sample_id) in the base class Record, and
+// hold the type specific data part in classes derived from Record.
+struct Record {
+  perf_event_header header;
+  SampleId sample_id;
+
+  Record();
+  Record(const perf_event_header* pheader);
+
+  virtual ~Record() {
+  }
+
+  void Dump(size_t indent = 0) const;
+
+ protected:
+  virtual void DumpData(size_t) const {
+  }
+};
+
+struct MmapRecord : public Record {
+  struct MmapRecordDataType {
+    uint32_t pid, tid;
+    uint64_t addr;
+    uint64_t len;
+    uint64_t pgoff;
+  } data;
+  std::string filename;
+
+  MmapRecord() {  // For storage in std::vector.
+  }
+
+  MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+  std::vector<char> BinaryFormat() const;
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct Mmap2Record : public Record {
+  struct Mmap2RecordDataType {
+    uint32_t pid, tid;
+    uint64_t addr;
+    uint64_t len;
+    uint64_t pgoff;
+    uint32_t maj;
+    uint32_t min;
+    uint64_t ino;
+    uint64_t ino_generation;
+    uint32_t prot, flags;
+  } data;
+  std::string filename;
+
+  Mmap2Record() {
+  }
+
+  Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct CommRecord : public Record {
+  struct CommRecordDataType {
+    uint32_t pid, tid;
+  } data;
+  std::string comm;
+
+  CommRecord() {
+  }
+
+  CommRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+  std::vector<char> BinaryFormat() const;
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct ExitOrForkRecord : public Record {
+  struct ExitOrForkRecordDataType {
+    uint32_t pid, ppid;
+    uint32_t tid, ptid;
+    uint64_t time;
+  } data;
+
+  ExitOrForkRecord() {
+  }
+  ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct ExitRecord : public ExitOrForkRecord {
+  ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader)
+      : ExitOrForkRecord(attr, pheader) {
+  }
+};
+
+struct ForkRecord : public ExitOrForkRecord {
+  ForkRecord() {
+  }
+  ForkRecord(const perf_event_attr& attr, const perf_event_header* pheader)
+      : ExitOrForkRecord(attr, pheader) {
+  }
+  std::vector<char> BinaryFormat() const;
+};
+
+struct SampleRecord : public Record {
+  uint64_t sample_type;  // sample_type is a bit mask determining which fields below are valid.
+
+  PerfSampleIpType ip_data;               // Valid if PERF_SAMPLE_IP.
+  PerfSampleTidType tid_data;             // Valid if PERF_SAMPLE_TID.
+  PerfSampleTimeType time_data;           // Valid if PERF_SAMPLE_TIME.
+  PerfSampleAddrType addr_data;           // Valid if PERF_SAMPLE_ADDR.
+  PerfSampleIdType id_data;               // Valid if PERF_SAMPLE_ID.
+  PerfSampleStreamIdType stream_id_data;  // Valid if PERF_SAMPLE_STREAM_ID.
+  PerfSampleCpuType cpu_data;             // Valid if PERF_SAMPLE_CPU.
+  PerfSamplePeriodType period_data;       // Valid if PERF_SAMPLE_PERIOD.
+
+  PerfSampleCallChainType callchain_data;       // Valid if PERF_SAMPLE_CALLCHAIN.
+  PerfSampleBranchStackType branch_stack_data;  // Valid if PERF_SAMPLE_BRANCH_STACK.
+
+  SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+// BuildIdRecord is defined in user-space, stored in BuildId feature section in record file.
+struct BuildIdRecord : public Record {
+  uint32_t pid;
+  BuildId build_id;
+  std::string filename;
+
+  BuildIdRecord() {
+  }
+
+  BuildIdRecord(const perf_event_header* pheader);
+  std::vector<char> BinaryFormat() const;
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+std::unique_ptr<const Record> ReadRecordFromBuffer(const perf_event_attr& attr,
+                                                   const perf_event_header* pheader);
+MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+                            uint64_t addr, uint64_t len, uint64_t pgoff,
+                            const std::string& filename);
+CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+                            const std::string& comm);
+ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
+                            uint32_t ptid);
+BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
+                                  const std::string& filename);
+#endif  // SIMPLE_PERF_RECORD_H_
diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h
new file mode 100644
index 0000000..03768dc
--- /dev/null
+++ b/simpleperf/record_equal_test.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+static void CheckMmapRecordDataEqual(const MmapRecord& r1, const MmapRecord& r2) {
+  ASSERT_EQ(0, memcmp(&r1.data, &r2.data, sizeof(r1.data)));
+  ASSERT_EQ(r1.filename, r2.filename);
+}
+
+static void CheckCommRecordDataEqual(const CommRecord& r1, const CommRecord& r2) {
+  ASSERT_EQ(0, memcmp(&r1.data, &r2.data, sizeof(r1.data)));
+  ASSERT_EQ(r1.comm, r2.comm);
+}
+
+static void CheckBuildIdRecordDataEqual(const BuildIdRecord& r1, const BuildIdRecord& r2) {
+  ASSERT_EQ(r1.pid, r2.pid);
+  ASSERT_EQ(r1.build_id, r2.build_id);
+  ASSERT_EQ(r1.filename, r2.filename);
+}
+
+static void CheckRecordEqual(const Record& r1, const Record& r2) {
+  ASSERT_EQ(0, memcmp(&r1.header, &r2.header, sizeof(r1.header)));
+  ASSERT_EQ(0, memcmp(&r1.sample_id, &r2.sample_id, sizeof(r1.sample_id)));
+  if (r1.header.type == PERF_RECORD_MMAP) {
+    CheckMmapRecordDataEqual(static_cast<const MmapRecord&>(r1), static_cast<const MmapRecord&>(r2));
+  } else if (r1.header.type == PERF_RECORD_COMM) {
+    CheckCommRecordDataEqual(static_cast<const CommRecord&>(r1), static_cast<const CommRecord&>(r2));
+  } else if (r1.header.type == PERF_RECORD_BUILD_ID) {
+    CheckBuildIdRecordDataEqual(static_cast<const BuildIdRecord&>(r1),
+                                static_cast<const BuildIdRecord&>(r2));
+  }
+}
diff --git a/simpleperf/record_file.cpp b/simpleperf/record_file.cpp
new file mode 100644
index 0000000..2a7c6a1
--- /dev/null
+++ b/simpleperf/record_file.cpp
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "record_file.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <set>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "event_fd.h"
+#include "perf_event.h"
+#include "record.h"
+#include "utils.h"
+
+using namespace PerfFileFormat;
+
+std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(
+    const std::string& filename, const perf_event_attr& event_attr,
+    const std::vector<std::unique_ptr<EventFd>>& event_fds) {
+  // Remove old perf.data to avoid file ownership problems.
+  if (!RemovePossibleFile(filename)) {
+    return nullptr;
+  }
+  FILE* fp = fopen(filename.c_str(), "web+");
+  if (fp == nullptr) {
+    PLOG(ERROR) << "failed to open record file '" << filename << "'";
+    return nullptr;
+  }
+
+  auto writer = std::unique_ptr<RecordFileWriter>(new RecordFileWriter(filename, fp));
+  if (!writer->WriteAttrSection(event_attr, event_fds)) {
+    return nullptr;
+  }
+  return writer;
+}
+
+RecordFileWriter::RecordFileWriter(const std::string& filename, FILE* fp)
+    : filename_(filename),
+      record_fp_(fp),
+      attr_section_offset_(0),
+      attr_section_size_(0),
+      data_section_offset_(0),
+      data_section_size_(0),
+      feature_count_(0),
+      current_feature_index_(0) {
+}
+
+RecordFileWriter::~RecordFileWriter() {
+  if (record_fp_ != nullptr) {
+    Close();
+  }
+}
+
+bool RecordFileWriter::WriteAttrSection(const perf_event_attr& event_attr,
+                                        const std::vector<std::unique_ptr<EventFd>>& event_fds) {
+  // Skip file header part.
+  if (fseek(record_fp_, sizeof(FileHeader), SEEK_SET) == -1) {
+    return false;
+  }
+
+  // Write id section.
+  std::vector<uint64_t> ids;
+  for (auto& event_fd : event_fds) {
+    ids.push_back(event_fd->Id());
+  }
+  long id_section_offset = ftell(record_fp_);
+  if (id_section_offset == -1) {
+    return false;
+  }
+  if (!Write(ids.data(), ids.size() * sizeof(uint64_t))) {
+    return false;
+  }
+
+  // Write attr section.
+  FileAttr attr;
+  attr.attr = event_attr;
+  attr.ids.offset = id_section_offset;
+  attr.ids.size = ids.size() * sizeof(uint64_t);
+
+  long attr_section_offset = ftell(record_fp_);
+  if (attr_section_offset == -1) {
+    return false;
+  }
+  if (!Write(&attr, sizeof(attr))) {
+    return false;
+  }
+
+  long data_section_offset = ftell(record_fp_);
+  if (data_section_offset == -1) {
+    return false;
+  }
+
+  attr_section_offset_ = attr_section_offset;
+  attr_section_size_ = sizeof(attr);
+  data_section_offset_ = data_section_offset;
+
+  // Save event_attr for use when reading records.
+  event_attr_ = event_attr;
+  return true;
+}
+
+bool RecordFileWriter::WriteData(const void* buf, size_t len) {
+  if (!Write(buf, len)) {
+    return false;
+  }
+  data_section_size_ += len;
+  return true;
+}
+
+bool RecordFileWriter::Write(const void* buf, size_t len) {
+  if (fwrite(buf, len, 1, record_fp_) != 1) {
+    PLOG(ERROR) << "failed to write to record file '" << filename_ << "'";
+    return false;
+  }
+  return true;
+}
+
+void RecordFileWriter::GetHitModulesInBuffer(const char* p, const char* end,
+                                             std::vector<std::string>* hit_kernel_modules,
+                                             std::vector<std::string>* hit_user_files) {
+  std::vector<std::unique_ptr<const Record>> kernel_mmaps;
+  std::vector<std::unique_ptr<const Record>> user_mmaps;
+  std::set<std::string> hit_kernel_set;
+  std::set<std::string> hit_user_set;
+
+  while (p < end) {
+    auto header = reinterpret_cast<const perf_event_header*>(p);
+    CHECK_LE(p + header->size, end);
+    p += header->size;
+    std::unique_ptr<const Record> record = ReadRecordFromBuffer(event_attr_, header);
+    CHECK(record != nullptr);
+    if (record->header.type == PERF_RECORD_MMAP) {
+      if (record->header.misc & PERF_RECORD_MISC_KERNEL) {
+        kernel_mmaps.push_back(std::move(record));
+      } else {
+        user_mmaps.push_back(std::move(record));
+      }
+    } else if (record->header.type == PERF_RECORD_SAMPLE) {
+      auto& r = *static_cast<const SampleRecord*>(record.get());
+      if (!(r.sample_type & PERF_SAMPLE_IP) || !(r.sample_type & PERF_SAMPLE_TID)) {
+        continue;
+      }
+      uint32_t pid = r.tid_data.pid;
+      uint64_t ip = r.ip_data.ip;
+      if (r.header.misc & PERF_RECORD_MISC_KERNEL) {
+        // Loop from back to front, because new MmapRecords are inserted at the end of the mmaps,
+        // and we want to match the newest one.
+        for (auto it = kernel_mmaps.rbegin(); it != kernel_mmaps.rend(); ++it) {
+          auto& m_record = *reinterpret_cast<const MmapRecord*>(it->get());
+          if (ip >= m_record.data.addr && ip < m_record.data.addr + m_record.data.len) {
+            hit_kernel_set.insert(m_record.filename);
+            break;
+          }
+        }
+      } else {
+        for (auto it = user_mmaps.rbegin(); it != user_mmaps.rend(); ++it) {
+          auto& m_record = *reinterpret_cast<const MmapRecord*>(it->get());
+          if (pid == m_record.data.pid && ip >= m_record.data.addr &&
+              ip < m_record.data.addr + m_record.data.len) {
+            hit_user_set.insert(m_record.filename);
+            break;
+          }
+        }
+      }
+    }
+  }
+  hit_kernel_modules->clear();
+  hit_kernel_modules->insert(hit_kernel_modules->begin(), hit_kernel_set.begin(),
+                             hit_kernel_set.end());
+  hit_user_files->clear();
+  hit_user_files->insert(hit_user_files->begin(), hit_user_set.begin(), hit_user_set.end());
+}
+
+bool RecordFileWriter::GetHitModules(std::vector<std::string>* hit_kernel_modules,
+                                     std::vector<std::string>* hit_user_files) {
+  if (fflush(record_fp_) != 0) {
+    PLOG(ERROR) << "fflush() failed";
+    return false;
+  }
+  uint64_t file_size;
+  if (!SeekFileEnd(&file_size)) {
+    return false;
+  }
+  size_t mmap_len = static_cast<size_t>(file_size);
+  void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ, MAP_SHARED, fileno(record_fp_), 0);
+  if (mmap_addr == MAP_FAILED) {
+    PLOG(ERROR) << "mmap() failed";
+    return false;
+  }
+  const char* data_section_p = reinterpret_cast<const char*>(mmap_addr) + data_section_offset_;
+  const char* data_section_end = data_section_p + data_section_size_;
+  GetHitModulesInBuffer(data_section_p, data_section_end, hit_kernel_modules, hit_user_files);
+
+  if (munmap(mmap_addr, mmap_len) == -1) {
+    PLOG(ERROR) << "munmap() failed";
+    return false;
+  }
+  return true;
+}
+
+bool RecordFileWriter::SeekFileEnd(uint64_t* file_end) {
+  if (fseek(record_fp_, 0, SEEK_END) == -1) {
+    PLOG(ERROR) << "fseek() failed";
+    return false;
+  }
+  long offset = ftell(record_fp_);
+  if (offset == -1) {
+    PLOG(ERROR) << "ftell() failed";
+    return false;
+  }
+  *file_end = static_cast<uint64_t>(offset);
+  return true;
+}
+
+bool RecordFileWriter::WriteFeatureHeader(size_t feature_count) {
+  feature_count_ = feature_count;
+  current_feature_index_ = 0;
+  uint64_t feature_header_size = feature_count * sizeof(SectionDesc);
+
+  // Reserve enough space in the record file for the feature header.
+  std::vector<unsigned char> zero_data(feature_header_size);
+  if (fseek(record_fp_, data_section_offset_ + data_section_size_, SEEK_SET) == -1) {
+    PLOG(ERROR) << "fseek() failed";
+    return false;
+  }
+  return Write(zero_data.data(), zero_data.size());
+}
+
+bool RecordFileWriter::WriteBuildIdFeature(const std::vector<BuildIdRecord>& build_id_records) {
+  CHECK_LT(current_feature_index_, feature_count_);
+  // Always write features at the end of the file.
+  uint64_t start_offset;
+  if (!SeekFileEnd(&start_offset)) {
+    return false;
+  }
+  for (auto& record : build_id_records) {
+    std::vector<char> data = record.BinaryFormat();
+    if (!Write(data.data(), data.size())) {
+      return false;
+    }
+  }
+  uint64_t end_offset;
+  if (!SeekFileEnd(&end_offset)) {
+    return false;
+  }
+
+  if (!ModifyFeatureSectionDescriptor(current_feature_index_, start_offset,
+                                      end_offset - start_offset)) {
+    return false;
+  }
+  ++current_feature_index_;
+  features_.push_back(FEAT_BUILD_ID);
+  return true;
+}
+
+bool RecordFileWriter::WriteBranchStackFeature() {
+  CHECK_LT(current_feature_index_, feature_count_);
+  uint64_t start_offset;
+  if (!SeekFileEnd(&start_offset)) {
+    return false;
+  }
+  if (!ModifyFeatureSectionDescriptor(current_feature_index_, start_offset, 0)) {
+    return false;
+  }
+  ++current_feature_index_;
+  features_.push_back(FEAT_BRANCH_STACK);
+  return true;
+}
+
+bool RecordFileWriter::ModifyFeatureSectionDescriptor(size_t feature_index, uint64_t offset,
+                                                      uint64_t size) {
+  SectionDesc desc;
+  desc.offset = offset;
+  desc.size = size;
+  uint64_t feature_offset = data_section_offset_ + data_section_size_;
+  if (fseek(record_fp_, feature_offset + feature_index * sizeof(SectionDesc), SEEK_SET) == -1) {
+    PLOG(ERROR) << "fseek() failed";
+    return false;
+  }
+  if (fwrite(&desc, sizeof(SectionDesc), 1, record_fp_) != 1) {
+    PLOG(ERROR) << "fwrite() failed";
+    return false;
+  }
+  return true;
+}
+
+bool RecordFileWriter::WriteFileHeader() {
+  FileHeader header;
+  memset(&header, 0, sizeof(header));
+  memcpy(header.magic, PERF_MAGIC, sizeof(header.magic));
+  header.header_size = sizeof(header);
+  header.attr_size = sizeof(FileAttr);
+  header.attrs.offset = attr_section_offset_;
+  header.attrs.size = attr_section_size_;
+  header.data.offset = data_section_offset_;
+  header.data.size = data_section_size_;
+  for (auto& feature : features_) {
+    int i = feature / 8;
+    int j = feature % 8;
+    header.features[i] |= (1 << j);
+  }
+
+  if (fseek(record_fp_, 0, SEEK_SET) == -1) {
+    return false;
+  }
+  if (!Write(&header, sizeof(header))) {
+    return false;
+  }
+  return true;
+}
+
+bool RecordFileWriter::Close() {
+  CHECK(record_fp_ != nullptr);
+  bool result = true;
+
+  // Write file header. We gather enough information to write file header only after
+  // writing data section and feature section.
+  if (!WriteFileHeader()) {
+    result = false;
+  }
+
+  if (fclose(record_fp_) != 0) {
+    PLOG(ERROR) << "failed to close record file '" << filename_ << "'";
+    result = false;
+  }
+  record_fp_ = nullptr;
+  return result;
+}
+
+std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::string& filename) {
+  int fd = open(filename.c_str(), O_RDONLY | O_CLOEXEC);
+  if (fd == -1) {
+    PLOG(ERROR) << "failed to open record file '" << filename << "'";
+    return nullptr;
+  }
+  auto reader = std::unique_ptr<RecordFileReader>(new RecordFileReader(filename, fd));
+  if (!reader->MmapFile()) {
+    return nullptr;
+  }
+  return reader;
+}
+
+RecordFileReader::RecordFileReader(const std::string& filename, int fd)
+    : filename_(filename), record_fd_(fd), mmap_addr_(nullptr), mmap_len_(0) {
+}
+
+RecordFileReader::~RecordFileReader() {
+  if (record_fd_ != -1) {
+    Close();
+  }
+}
+
+bool RecordFileReader::Close() {
+  bool result = true;
+  if (munmap(const_cast<char*>(mmap_addr_), mmap_len_) == -1) {
+    PLOG(ERROR) << "failed to munmap() record file '" << filename_ << "'";
+    result = false;
+  }
+  if (close(record_fd_) == -1) {
+    PLOG(ERROR) << "failed to close record file '" << filename_ << "'";
+    result = false;
+  }
+  record_fd_ = -1;
+  return result;
+}
+
+bool RecordFileReader::MmapFile() {
+  off64_t file_size = lseek64(record_fd_, 0, SEEK_END);
+  if (file_size == -1) {
+    return false;
+  }
+  size_t mmap_len = file_size;
+  void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ, MAP_SHARED, record_fd_, 0);
+  if (mmap_addr == MAP_FAILED) {
+    PLOG(ERROR) << "failed to mmap() record file '" << filename_ << "'";
+    return false;
+  }
+
+  mmap_addr_ = reinterpret_cast<const char*>(mmap_addr);
+  mmap_len_ = mmap_len;
+  return true;
+}
+
+const FileHeader* RecordFileReader::FileHeader() {
+  return reinterpret_cast<const struct FileHeader*>(mmap_addr_);
+}
+
+std::vector<const FileAttr*> RecordFileReader::AttrSection() {
+  std::vector<const FileAttr*> result;
+  const struct FileHeader* header = FileHeader();
+  size_t attr_count = header->attrs.size / header->attr_size;
+  const FileAttr* attr = reinterpret_cast<const FileAttr*>(mmap_addr_ + header->attrs.offset);
+  for (size_t i = 0; i < attr_count; ++i) {
+    result.push_back(attr++);
+  }
+  return result;
+}
+
+std::vector<uint64_t> RecordFileReader::IdsForAttr(const FileAttr* attr) {
+  std::vector<uint64_t> result;
+  size_t id_count = attr->ids.size / sizeof(uint64_t);
+  const uint64_t* id = reinterpret_cast<const uint64_t*>(mmap_addr_ + attr->ids.offset);
+  for (size_t i = 0; i < id_count; ++i) {
+    result.push_back(*id++);
+  }
+  return result;
+}
+
+static bool IsRecordHappensBefore(const std::unique_ptr<const Record>& r1,
+                                  const std::unique_ptr<const Record>& r2) {
+  bool is_r1_sample = (r1->header.type == PERF_RECORD_SAMPLE);
+  bool is_r2_sample = (r2->header.type == PERF_RECORD_SAMPLE);
+  uint64_t time1 = (is_r1_sample ? static_cast<const SampleRecord*>(r1.get())->time_data.time
+                                 : r1->sample_id.time_data.time);
+  uint64_t time2 = (is_r2_sample ? static_cast<const SampleRecord*>(r2.get())->time_data.time
+                                 : r2->sample_id.time_data.time);
+  // The record with smaller time happens first.
+  if (time1 != time2) {
+    return time1 < time2;
+  }
+  // If happening at the same time, make non-sample records before sample records,
+  // because non-sample records may contain useful information to parse sample records.
+  if (is_r1_sample != is_r2_sample) {
+    return is_r1_sample ? false : true;
+  }
+  // Otherwise, don't care of the order.
+  return false;
+}
+
+std::vector<std::unique_ptr<const Record>> RecordFileReader::DataSection() {
+  std::vector<std::unique_ptr<const Record>> result;
+  const struct FileHeader* header = FileHeader();
+  auto file_attrs = AttrSection();
+  CHECK(file_attrs.size() > 0);
+  perf_event_attr attr = file_attrs[0]->attr;
+
+  const char* end = mmap_addr_ + header->data.offset + header->data.size;
+  const char* p = mmap_addr_ + header->data.offset;
+  while (p < end) {
+    const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p);
+    if (p + header->size <= end) {
+      result.push_back(std::move(ReadRecordFromBuffer(attr, header)));
+    }
+    p += header->size;
+  }
+  if ((attr.sample_type & PERF_SAMPLE_TIME) && attr.sample_id_all) {
+    std::sort(result.begin(), result.end(), IsRecordHappensBefore);
+  }
+  return result;
+}
+
+std::vector<SectionDesc> RecordFileReader::FeatureSectionDescriptors() {
+  std::vector<SectionDesc> result;
+  const struct FileHeader* header = FileHeader();
+  size_t feature_count = 0;
+  for (size_t i = 0; i < sizeof(header->features); ++i) {
+    for (size_t j = 0; j < 8; ++j) {
+      if (header->features[i] & (1 << j)) {
+        ++feature_count;
+      }
+    }
+  }
+  uint64_t feature_section_offset = header->data.offset + header->data.size;
+  const SectionDesc* p = reinterpret_cast<const SectionDesc*>(mmap_addr_ + feature_section_offset);
+  for (size_t i = 0; i < feature_count; ++i) {
+    result.push_back(*p++);
+  }
+  return result;
+}
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
new file mode 100644
index 0000000..e217b3c
--- /dev/null
+++ b/simpleperf/record_file.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_RECORD_FILE_H_
+#define SIMPLE_PERF_RECORD_FILE_H_
+
+#include <stdio.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "perf_event.h"
+#include "record.h"
+#include "record_file_format.h"
+
+class EventFd;
+
+// RecordFileWriter writes to a perf record file, like perf.data.
+class RecordFileWriter {
+ public:
+  static std::unique_ptr<RecordFileWriter> CreateInstance(
+      const std::string& filename, const perf_event_attr& event_attr,
+      const std::vector<std::unique_ptr<EventFd>>& event_fds);
+
+  ~RecordFileWriter();
+
+  bool WriteData(const void* buf, size_t len);
+
+  bool WriteData(const std::vector<char>& data) {
+    return WriteData(data.data(), data.size());
+  }
+
+  // Use MmapRecords and SampleRecords in record file to conclude which modules/files were executing
+  // at sample times.
+  bool GetHitModules(std::vector<std::string>* hit_kernel_modules,
+                     std::vector<std::string>* hit_user_files);
+
+  bool WriteFeatureHeader(size_t feature_count);
+  bool WriteBuildIdFeature(const std::vector<BuildIdRecord>& build_id_records);
+  bool WriteBranchStackFeature();
+
+  // Normally, Close() should be called after writing. But if something
+  // wrong happens and we need to finish in advance, the destructor
+  // will take care of calling Close().
+  bool Close();
+
+ private:
+  RecordFileWriter(const std::string& filename, FILE* fp);
+  bool WriteAttrSection(const perf_event_attr& event_attr,
+                        const std::vector<std::unique_ptr<EventFd>>& event_fds);
+  void GetHitModulesInBuffer(const char* p, const char* end,
+                             std::vector<std::string>* hit_kernel_modules,
+                             std::vector<std::string>* hit_user_files);
+  bool WriteFileHeader();
+  bool Write(const void* buf, size_t len);
+  bool SeekFileEnd(uint64_t* file_end);
+  bool ModifyFeatureSectionDescriptor(size_t feature_index, uint64_t offset, uint64_t size);
+
+  const std::string filename_;
+  FILE* record_fp_;
+
+  perf_event_attr event_attr_;
+  uint64_t attr_section_offset_;
+  uint64_t attr_section_size_;
+  uint64_t data_section_offset_;
+  uint64_t data_section_size_;
+
+  std::vector<int> features_;
+  int feature_count_;
+  int current_feature_index_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecordFileWriter);
+};
+
+// RecordFileReader read contents from a perf record file, like perf.data.
+class RecordFileReader {
+ public:
+  static std::unique_ptr<RecordFileReader> CreateInstance(const std::string& filename);
+
+  ~RecordFileReader();
+
+  const PerfFileFormat::FileHeader* FileHeader();
+  std::vector<const PerfFileFormat::FileAttr*> AttrSection();
+  std::vector<uint64_t> IdsForAttr(const PerfFileFormat::FileAttr* attr);
+  std::vector<std::unique_ptr<const Record>> DataSection();
+  std::vector<PerfFileFormat::SectionDesc> FeatureSectionDescriptors();
+  const char* DataAtOffset(uint64_t offset) {
+    return mmap_addr_ + offset;
+  }
+  bool Close();
+
+ private:
+  RecordFileReader(const std::string& filename, int fd);
+  bool MmapFile();
+
+  const std::string filename_;
+  int record_fd_;
+
+  const char* mmap_addr_;
+  size_t mmap_len_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecordFileReader);
+};
+
+#endif  // SIMPLE_PERF_RECORD_FILE_H_
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
new file mode 100644
index 0000000..9758f11
--- /dev/null
+++ b/simpleperf/record_file_format.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_RECORD_FILE_FORMAT_H_
+#define SIMPLE_PERF_RECORD_FILE_FORMAT_H_
+
+#include "perf_event.h"
+
+// The file structure of perf.data:
+//    file_header
+//    id_section
+//    attr section
+//    data section
+//    feature section
+//
+//  The feature section has the following structure:
+//    a section descriptor array, each element contains the section information of one add_feature.
+//    data section of feature 1
+//    data section of feature 2
+//    ....
+
+namespace PerfFileFormat {
+
+enum {
+  FEAT_RESERVED = 0,
+  FEAT_FIRST_FEATURE = 1,
+  FEAT_TRACING_DATA = 1,
+  FEAT_BUILD_ID,
+  FEAT_HOSTNAME,
+  FEAT_OSRELEASE,
+  FEAT_VERSION,
+  FEAT_ARCH,
+  FEAT_NRCPUS,
+  FEAT_CPUDESC,
+  FEAT_CPUID,
+  FEAT_TOTAL_MEM,
+  FEAT_CMDLINE,
+  FEAT_EVENT_DESC,
+  FEAT_CPU_TOPOLOGY,
+  FEAT_NUMA_TOPOLOGY,
+  FEAT_BRANCH_STACK,
+  FEAT_PMU_MAPPINGS,
+  FEAT_GROUP_DESC,
+  FEAT_LAST_FEATURE,
+  FEAT_MAX_NUM = 256,
+};
+
+struct SectionDesc {
+  uint64_t offset;
+  uint64_t size;
+};
+
+static const char* PERF_MAGIC = "PERFILE2";
+
+struct FileHeader {
+  char magic[8];
+  uint64_t header_size;
+  uint64_t attr_size;
+  SectionDesc attrs;
+  SectionDesc data;
+  SectionDesc event_types;
+  unsigned char features[FEAT_MAX_NUM / 8];
+};
+
+struct FileAttr {
+  perf_event_attr attr;
+  SectionDesc ids;
+};
+
+}  // namespace PerfFileFormat
+
+#endif  // SIMPLE_PERF_RECORD_FILE_FORMAT_H_
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
new file mode 100644
index 0000000..c4bb255
--- /dev/null
+++ b/simpleperf/record_file_test.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <string.h>
+#include "environment.h"
+#include "event_attr.h"
+#include "event_fd.h"
+#include "event_type.h"
+#include "record.h"
+#include "record_file.h"
+
+#include "record_equal_test.h"
+
+using namespace PerfFileFormat;
+
+class RecordFileTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    filename = "temporary.record_file";
+    std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+    ASSERT_TRUE(event_type_modifier != nullptr);
+    event_attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+    event_attr.sample_id_all = 1;
+    event_attr.sample_type |= PERF_SAMPLE_TIME;
+    std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(event_attr, getpid(), -1);
+    ASSERT_TRUE(event_fd != nullptr);
+    event_fds.push_back(std::move(event_fd));
+  }
+
+  std::string filename;
+  perf_event_attr event_attr;
+  std::vector<std::unique_ptr<EventFd>> event_fds;
+};
+
+TEST_F(RecordFileTest, smoke) {
+  // Write to a record file.
+  std::unique_ptr<RecordFileWriter> writer =
+      RecordFileWriter::CreateInstance(filename, event_attr, event_fds);
+  ASSERT_TRUE(writer != nullptr);
+
+  // Write data section.
+  MmapRecord mmap_record =
+      CreateMmapRecord(event_attr, true, 1, 1, 0x1000, 0x2000, 0x3000, "mmap_record_example");
+  ASSERT_TRUE(writer->WriteData(mmap_record.BinaryFormat()));
+
+  // Write feature section.
+  ASSERT_TRUE(writer->WriteFeatureHeader(1));
+  BuildId build_id;
+  for (size_t i = 0; i < build_id.size(); ++i) {
+    build_id[i] = i;
+  }
+  BuildIdRecord build_id_record = CreateBuildIdRecord(false, getpid(), build_id, "init");
+  ASSERT_TRUE(writer->WriteBuildIdFeature({build_id_record}));
+  ASSERT_TRUE(writer->Close());
+
+  // Read from a record file.
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(filename);
+  ASSERT_TRUE(reader != nullptr);
+  const FileHeader* file_header = reader->FileHeader();
+  ASSERT_TRUE(file_header != nullptr);
+  std::vector<const FileAttr*> attrs = reader->AttrSection();
+  ASSERT_EQ(1u, attrs.size());
+  ASSERT_EQ(0, memcmp(&attrs[0]->attr, &event_attr, sizeof(perf_event_attr)));
+  std::vector<uint64_t> ids = reader->IdsForAttr(attrs[0]);
+  ASSERT_EQ(1u, ids.size());
+
+  // Read and check data section.
+  std::vector<std::unique_ptr<const Record>> records = reader->DataSection();
+  ASSERT_EQ(1u, records.size());
+  CheckRecordEqual(mmap_record, *records[0]);
+
+  // Read and check feature section.
+  ASSERT_TRUE(file_header->features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8)));
+  std::vector<SectionDesc> sections = reader->FeatureSectionDescriptors();
+  ASSERT_EQ(1u, sections.size());
+  const perf_event_header* header =
+      reinterpret_cast<const perf_event_header*>(reader->DataAtOffset(sections[0].offset));
+  ASSERT_TRUE(header != nullptr);
+  ASSERT_EQ(sections[0].size, header->size);
+  CheckRecordEqual(build_id_record, BuildIdRecord(header));
+
+  ASSERT_TRUE(reader->Close());
+}
+
+TEST_F(RecordFileTest, records_sorted_by_time) {
+  // Write to a record file;
+  std::unique_ptr<RecordFileWriter> writer =
+      RecordFileWriter::CreateInstance(filename, event_attr, event_fds);
+  ASSERT_TRUE(writer != nullptr);
+
+  // Write data section.
+  MmapRecord r1 = CreateMmapRecord(event_attr, true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1");
+  MmapRecord r2 = r1;
+  MmapRecord r3 = r1;
+  r1.sample_id.time_data.time = 2;
+  r2.sample_id.time_data.time = 1;
+  r3.sample_id.time_data.time = 3;
+  ASSERT_TRUE(writer->WriteData(r1.BinaryFormat()));
+  ASSERT_TRUE(writer->WriteData(r2.BinaryFormat()));
+  ASSERT_TRUE(writer->WriteData(r3.BinaryFormat()));
+  ASSERT_TRUE(writer->Close());
+
+  // Read from a record file.
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(filename);
+  ASSERT_TRUE(reader != nullptr);
+  std::vector<std::unique_ptr<const Record>> records = reader->DataSection();
+  ASSERT_EQ(3u, records.size());
+  CheckRecordEqual(r2, *records[0]);
+  CheckRecordEqual(r1, *records[1]);
+  CheckRecordEqual(r3, *records[2]);
+
+  ASSERT_TRUE(reader->Close());
+}
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
new file mode 100644
index 0000000..a15972b
--- /dev/null
+++ b/simpleperf/record_test.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "event_attr.h"
+#include "event_type.h"
+#include "record.h"
+#include "record_equal_test.h"
+
+class RecordTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+    ASSERT_TRUE(event_type_modifier != nullptr);
+    event_attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+  }
+
+  template <class RecordType>
+  void CheckRecordMatchBinary(const RecordType& record);
+
+  perf_event_attr event_attr;
+};
+
+template <class RecordType>
+void RecordTest::CheckRecordMatchBinary(const RecordType& record) {
+  std::vector<char> binary = record.BinaryFormat();
+  std::unique_ptr<const Record> record_p =
+      ReadRecordFromBuffer(event_attr, reinterpret_cast<const perf_event_header*>(binary.data()));
+  ASSERT_TRUE(record_p != nullptr);
+  CheckRecordEqual(record, *record_p);
+}
+
+TEST_F(RecordTest, MmapRecordMatchBinary) {
+  MmapRecord record =
+      CreateMmapRecord(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000, "MmapRecord");
+  CheckRecordMatchBinary(record);
+}
+
+TEST_F(RecordTest, CommRecordMatchBinary) {
+  CommRecord record = CreateCommRecord(event_attr, 1, 2, "CommRecord");
+  CheckRecordMatchBinary(record);
+}
diff --git a/simpleperf/sample_tree.cpp b/simpleperf/sample_tree.cpp
new file mode 100644
index 0000000..e01f671
--- /dev/null
+++ b/simpleperf/sample_tree.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sample_tree.h"
+
+#include <base/logging.h>
+
+#include "environment.h"
+
+bool MapComparator::operator()(const MapEntry* map1, const MapEntry* map2) const {
+  if (map1->start_addr != map2->start_addr) {
+    return map1->start_addr < map2->start_addr;
+  }
+  if (map1->len != map2->len) {
+    return map1->len < map2->len;
+  }
+  if (map1->time != map2->time) {
+    return map1->time < map2->time;
+  }
+  return false;
+}
+
+void SampleTree::AddThread(int pid, int tid, const std::string& comm) {
+  auto it = thread_tree_.find(tid);
+  if (it == thread_tree_.end()) {
+    ThreadEntry* thread = new ThreadEntry{
+        .pid = pid, .tid = tid,
+    };
+    auto pair = thread_tree_.insert(std::make_pair(tid, std::unique_ptr<ThreadEntry>(thread)));
+    CHECK(pair.second);
+    it = pair.first;
+  }
+  thread_comm_storage_.push_back(std::unique_ptr<std::string>(new std::string(comm)));
+  it->second->comm = thread_comm_storage_.back()->c_str();
+}
+
+void SampleTree::ForkThread(int pid, int tid, int ppid, int ptid) {
+  ThreadEntry* parent = FindThreadOrNew(ppid, ptid);
+  ThreadEntry* child = FindThreadOrNew(pid, tid);
+  child->comm = parent->comm;
+  child->maps = parent->maps;
+}
+
+static void RemoveOverlappedMap(std::set<MapEntry*, MapComparator>* map_set, const MapEntry* map) {
+  for (auto it = map_set->begin(); it != map_set->end();) {
+    if ((*it)->start_addr >= map->start_addr + map->len) {
+      break;
+    }
+    if ((*it)->start_addr + (*it)->len <= map->start_addr) {
+      ++it;
+    } else {
+      it = map_set->erase(it);
+    }
+  }
+}
+
+void SampleTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
+                              const std::string& filename) {
+  // kernel map len can be 0 when record command is not run in supervisor mode.
+  if (len == 0) {
+    return;
+  }
+  DsoEntry* dso = FindKernelDsoOrNew(filename);
+  MapEntry* map = new MapEntry{
+      .start_addr = start_addr, .len = len, .pgoff = pgoff, .time = time, .dso = dso,
+  };
+  map_storage_.push_back(std::unique_ptr<MapEntry>(map));
+  RemoveOverlappedMap(&kernel_map_tree_, map);
+  auto pair = kernel_map_tree_.insert(map);
+  CHECK(pair.second);
+}
+
+DsoEntry* SampleTree::FindKernelDsoOrNew(const std::string& filename) {
+  if (filename == DEFAULT_KERNEL_MMAP_NAME) {
+    if (kernel_dso_ == nullptr) {
+      kernel_dso_ = DsoFactory::LoadKernel();
+    }
+    return kernel_dso_.get();
+  }
+  auto it = module_dso_tree_.find(filename);
+  if (it == module_dso_tree_.end()) {
+    module_dso_tree_[filename] = DsoFactory::LoadKernelModule(filename);
+    it = module_dso_tree_.find(filename);
+  }
+  return it->second.get();
+}
+
+void SampleTree::AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
+                              uint64_t time, const std::string& filename) {
+  ThreadEntry* thread = FindThreadOrNew(pid, tid);
+  DsoEntry* dso = FindUserDsoOrNew(filename);
+  MapEntry* map = new MapEntry{
+      .start_addr = start_addr, .len = len, .pgoff = pgoff, .time = time, .dso = dso,
+  };
+  map_storage_.push_back(std::unique_ptr<MapEntry>(map));
+  RemoveOverlappedMap(&thread->maps, map);
+  auto pair = thread->maps.insert(map);
+  CHECK(pair.second);
+}
+
+ThreadEntry* SampleTree::FindThreadOrNew(int pid, int tid) {
+  auto it = thread_tree_.find(tid);
+  if (it == thread_tree_.end()) {
+    AddThread(pid, tid, "unknown");
+    it = thread_tree_.find(tid);
+  } else {
+    CHECK_EQ(pid, it->second.get()->pid);
+  }
+  return it->second.get();
+}
+
+DsoEntry* SampleTree::FindUserDsoOrNew(const std::string& filename) {
+  auto it = user_dso_tree_.find(filename);
+  if (it == user_dso_tree_.end()) {
+    user_dso_tree_[filename] = DsoFactory::LoadDso(filename);
+    it = user_dso_tree_.find(filename);
+  }
+  return it->second.get();
+}
+
+static bool IsIpInMap(uint64_t ip, const MapEntry* map) {
+  return (map->start_addr <= ip && map->start_addr + map->len > ip);
+}
+
+const MapEntry* SampleTree::FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel) {
+  // Construct a map_entry which is strictly after the searched map_entry, based on MapComparator.
+  MapEntry find_map = {
+      .start_addr = ip, .len = static_cast<uint64_t>(-1), .time = static_cast<uint64_t>(-1),
+  };
+  if (!in_kernel) {
+    auto it = thread->maps.upper_bound(&find_map);
+    if (it != thread->maps.begin() && IsIpInMap(ip, *--it)) {
+      return *it;
+    }
+  } else {
+    auto it = kernel_map_tree_.upper_bound(&find_map);
+    if (it != kernel_map_tree_.begin() && IsIpInMap(ip, *--it)) {
+      return *it;
+    }
+  }
+  return &unknown_map_;
+}
+
+void SampleTree::AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
+                           bool in_kernel) {
+  ThreadEntry* thread = FindThreadOrNew(pid, tid);
+  const MapEntry* map = FindMap(thread, ip, in_kernel);
+  const SymbolEntry* symbol = FindSymbol(map, ip);
+
+  SampleEntry find_sample = {
+      .ip = ip,
+      .time = time,
+      .period = period,
+      .sample_count = 1,
+      .thread = thread,
+      .thread_comm = thread->comm,
+      .map = map,
+      .symbol = symbol,
+  };
+  auto it = sample_tree_.find(find_sample);
+  if (it == sample_tree_.end()) {
+    auto pair = sample_tree_.insert(find_sample);
+    CHECK(pair.second);
+  } else {
+    SampleEntry* sample_entry = const_cast<SampleEntry*>(&*it);
+    sample_entry->period += period;
+    sample_entry->sample_count++;
+  }
+  total_samples_++;
+  total_period_ += period;
+}
+
+const SymbolEntry* SampleTree::FindSymbol(const MapEntry* map, uint64_t ip) {
+  uint64_t offset_in_file;
+  if (map->dso == kernel_dso_.get()) {
+    offset_in_file = ip;
+  } else {
+    offset_in_file = ip - map->start_addr + map->pgoff;
+  }
+  const SymbolEntry* symbol = map->dso->FindSymbol(offset_in_file);
+  if (symbol == nullptr) {
+    symbol = &unknown_symbol_;
+  }
+  return symbol;
+}
+
+void SampleTree::VisitAllSamples(std::function<void(const SampleEntry&)> callback) {
+  if (sorted_sample_tree_.size() != sample_tree_.size()) {
+    sorted_sample_tree_.clear();
+    for (auto& sample : sample_tree_) {
+      sorted_sample_tree_.insert(sample);
+    }
+  }
+  for (auto& sample : sorted_sample_tree_) {
+    callback(sample);
+  }
+}
diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h
new file mode 100644
index 0000000..b6b133f
--- /dev/null
+++ b/simpleperf/sample_tree.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_SAMPLE_TREE_H_
+#define SIMPLE_PERF_SAMPLE_TREE_H_
+
+#include <limits.h>
+#include <functional>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "dso.h"
+
+struct MapEntry {
+  uint64_t start_addr;
+  uint64_t len;
+  uint64_t pgoff;
+  uint64_t time;  // Map creation time.
+  DsoEntry* dso;
+};
+
+struct MapComparator {
+  bool operator()(const MapEntry* map1, const MapEntry* map2) const;
+};
+
+struct ThreadEntry {
+  int pid;
+  int tid;
+  const char* comm;  // It always refers to the latest comm.
+  std::set<MapEntry*, MapComparator> maps;
+};
+
+struct SampleEntry {
+  uint64_t ip;
+  uint64_t time;
+  uint64_t period;
+  uint64_t sample_count;
+  const ThreadEntry* thread;
+  const char* thread_comm;  // It refers to the thread comm when the sample happens.
+  const MapEntry* map;
+  const SymbolEntry* symbol;
+};
+
+typedef std::function<int(const SampleEntry&, const SampleEntry&)> compare_sample_func_t;
+
+class SampleTree {
+ public:
+  SampleTree(compare_sample_func_t sample_compare_function)
+      : sample_comparator_(sample_compare_function),
+        sample_tree_(sample_comparator_),
+        sorted_sample_comparator_(sample_compare_function),
+        sorted_sample_tree_(sorted_sample_comparator_),
+        total_samples_(0),
+        total_period_(0) {
+    unknown_map_ = {
+        .start_addr = 0, .len = ULLONG_MAX, .pgoff = 0, .time = 0, .dso = &unknown_dso_,
+    };
+    unknown_dso_ = {
+        .path = "unknown",
+    };
+    unknown_symbol_ = {
+        .name = "unknown", .addr = 0, .len = ULLONG_MAX,
+    };
+  }
+
+  void AddThread(int pid, int tid, const std::string& comm);
+  void ForkThread(int pid, int tid, int ppid, int ptid);
+  void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
+                    const std::string& filename);
+  void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
+                    uint64_t time, const std::string& filename);
+  void AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period, bool in_kernel);
+  void VisitAllSamples(std::function<void(const SampleEntry&)> callback);
+
+  uint64_t TotalSamples() const {
+    return total_samples_;
+  }
+
+  uint64_t TotalPeriod() const {
+    return total_period_;
+  }
+
+ private:
+  ThreadEntry* FindThreadOrNew(int pid, int tid);
+  const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel);
+  DsoEntry* FindKernelDsoOrNew(const std::string& filename);
+  DsoEntry* FindUserDsoOrNew(const std::string& filename);
+  const SymbolEntry* FindSymbol(const MapEntry* map, uint64_t ip);
+
+  struct SampleComparator {
+    bool operator()(const SampleEntry& sample1, const SampleEntry& sample2) const {
+      return compare_function(sample1, sample2) < 0;
+    }
+    SampleComparator(compare_sample_func_t compare_function) : compare_function(compare_function) {
+    }
+
+    compare_sample_func_t compare_function;
+  };
+
+  struct SortedSampleComparator {
+    bool operator()(const SampleEntry& sample1, const SampleEntry& sample2) const {
+      if (sample1.period != sample2.period) {
+        return sample1.period > sample2.period;
+      }
+      return compare_function(sample1, sample2) < 0;
+    }
+    SortedSampleComparator(compare_sample_func_t compare_function)
+        : compare_function(compare_function) {
+    }
+
+    compare_sample_func_t compare_function;
+  };
+
+  std::unordered_map<int, std::unique_ptr<ThreadEntry>> thread_tree_;
+  std::vector<std::unique_ptr<std::string>> thread_comm_storage_;
+
+  std::set<MapEntry*, MapComparator> kernel_map_tree_;
+  std::vector<std::unique_ptr<MapEntry>> map_storage_;
+  MapEntry unknown_map_;
+
+  std::unique_ptr<DsoEntry> kernel_dso_;
+  std::unordered_map<std::string, std::unique_ptr<DsoEntry>> module_dso_tree_;
+  std::unordered_map<std::string, std::unique_ptr<DsoEntry>> user_dso_tree_;
+  DsoEntry unknown_dso_;
+  SymbolEntry unknown_symbol_;
+
+  SampleComparator sample_comparator_;
+  std::set<SampleEntry, SampleComparator> sample_tree_;
+  SortedSampleComparator sorted_sample_comparator_;
+  std::set<SampleEntry, SortedSampleComparator> sorted_sample_tree_;
+
+  uint64_t total_samples_;
+  uint64_t total_period_;
+};
+
+#endif  // SIMPLE_PERF_SAMPLE_TREE_H_
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
new file mode 100644
index 0000000..9b37f47
--- /dev/null
+++ b/simpleperf/sample_tree_test.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "sample_tree.h"
+
+struct ExpectedSampleInMap {
+  int pid;
+  int tid;
+  const char* comm;
+  std::string dso_name;
+  uint64_t map_start_addr;
+  size_t sample_count;
+};
+
+static void SampleMatchExpectation(const SampleEntry& sample, const ExpectedSampleInMap& expected,
+                                   bool* has_error) {
+  *has_error = true;
+  ASSERT_TRUE(sample.thread != nullptr);
+  ASSERT_EQ(expected.pid, sample.thread->pid);
+  ASSERT_EQ(expected.tid, sample.thread->tid);
+  ASSERT_STREQ(expected.comm, sample.thread_comm);
+  ASSERT_TRUE(sample.map != nullptr);
+  ASSERT_EQ(expected.dso_name, sample.map->dso->path);
+  ASSERT_EQ(expected.map_start_addr, sample.map->start_addr);
+  ASSERT_EQ(expected.sample_count, sample.sample_count);
+  *has_error = false;
+}
+
+static void CheckSampleCallback(const SampleEntry& sample,
+                                std::vector<ExpectedSampleInMap>& expected_samples, size_t* pos) {
+  ASSERT_LT(*pos, expected_samples.size());
+  bool has_error;
+  SampleMatchExpectation(sample, expected_samples[*pos], &has_error);
+  ASSERT_FALSE(has_error) << "Error matching sample at pos " << *pos;
+  ++*pos;
+}
+
+static int CompareSampleFunction(const SampleEntry& sample1, const SampleEntry& sample2) {
+  if (sample1.thread->pid != sample2.thread->pid) {
+    return sample1.thread->pid - sample2.thread->pid;
+  }
+  if (sample1.thread->tid != sample2.thread->tid) {
+    return sample1.thread->tid - sample2.thread->tid;
+  }
+  if (strcmp(sample1.thread_comm, sample2.thread_comm) != 0) {
+    return strcmp(sample1.thread_comm, sample2.thread_comm);
+  }
+  if (sample1.map->dso->path != sample2.map->dso->path) {
+    return sample1.map->dso->path > sample2.map->dso->path ? 1 : -1;
+  }
+  if (sample1.map->start_addr != sample2.map->start_addr) {
+    return sample1.map->start_addr - sample2.map->start_addr;
+  }
+  return 0;
+}
+
+void VisitSampleTree(SampleTree* sample_tree,
+                     const std::vector<ExpectedSampleInMap>& expected_samples) {
+  size_t pos = 0;
+  sample_tree->VisitAllSamples(
+      std::bind(&CheckSampleCallback, std::placeholders::_1, expected_samples, &pos));
+  ASSERT_EQ(expected_samples.size(), pos);
+}
+
+class SampleTreeTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    sample_tree = std::unique_ptr<SampleTree>(new SampleTree(CompareSampleFunction));
+    sample_tree->AddThread(1, 1, "p1t1");
+    sample_tree->AddThread(1, 11, "p1t11");
+    sample_tree->AddThread(2, 2, "p2t2");
+    sample_tree->AddThreadMap(1, 1, 1, 5, 0, 0, "process1_thread1");
+    sample_tree->AddThreadMap(1, 1, 6, 5, 0, 0, "process1_thread1_map2");
+    sample_tree->AddThreadMap(1, 11, 1, 10, 0, 0, "process1_thread11");
+    sample_tree->AddThreadMap(2, 2, 1, 20, 0, 0, "process2_thread2");
+    sample_tree->AddKernelMap(10, 20, 0, 0, "kernel");
+  }
+
+  void VisitSampleTree(const std::vector<ExpectedSampleInMap>& expected_samples) {
+    ::VisitSampleTree(sample_tree.get(), expected_samples);
+  }
+
+  std::unique_ptr<SampleTree> sample_tree;
+};
+
+TEST_F(SampleTreeTest, ip_in_map) {
+  sample_tree->AddSample(1, 1, 1, 0, 0, false);
+  sample_tree->AddSample(1, 1, 2, 0, 0, false);
+  sample_tree->AddSample(1, 1, 5, 0, 0, false);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "p1t1", "process1_thread1", 1, 3},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, different_pid) {
+  sample_tree->AddSample(1, 1, 1, 0, 0, false);
+  sample_tree->AddSample(2, 2, 1, 0, 0, false);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "p1t1", "process1_thread1", 1, 1}, {2, 2, "p2t2", "process2_thread2", 1, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, different_tid) {
+  sample_tree->AddSample(1, 1, 1, 0, 0, false);
+  sample_tree->AddSample(1, 11, 1, 0, 0, false);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 11, "p1t11", "process1_thread11", 1, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, different_comm) {
+  sample_tree->AddSample(1, 1, 1, 0, 0, false);
+  sample_tree->AddThread(1, 1, "p1t1_comm2");
+  sample_tree->AddSample(1, 1, 1, 0, 0, false);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 1, "p1t1_comm2", "process1_thread1", 1, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, different_map) {
+  sample_tree->AddSample(1, 1, 1, 0, 0, false);
+  sample_tree->AddSample(1, 1, 6, 0, 0, false);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 1, "p1t1", "process1_thread1_map2", 6, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, unmapped_sample) {
+  sample_tree->AddSample(1, 1, 0, 0, 0, false);
+  sample_tree->AddSample(1, 1, 31, 0, 0, false);
+  sample_tree->AddSample(1, 1, 70, 0, 0, false);
+  // Match the unknown map.
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "p1t1", "unknown", 0, 3},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, map_kernel) {
+  sample_tree->AddSample(1, 1, 10, 0, 0, true);
+  sample_tree->AddSample(1, 1, 10, 0, 0, false);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "p1t1", "kernel", 10, 1}, {1, 1, "p1t1", "process1_thread1_map2", 6, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST(sample_tree, overlapped_map) {
+  auto sample_tree = std::unique_ptr<SampleTree>(new SampleTree(CompareSampleFunction));
+  sample_tree->AddThread(1, 1, "thread1");
+  sample_tree->AddThreadMap(1, 1, 1, 10, 0, 0, "map1");  // Add map 1.
+  sample_tree->AddSample(1, 1, 5, 0, 0, false);          // Hit map 1.
+  sample_tree->AddThreadMap(1, 1, 5, 20, 0, 0, "map2");  // Add map 2.
+  sample_tree->AddSample(1, 1, 6, 0, 0, false);          // Hit map 2.
+  sample_tree->AddSample(1, 1, 4, 0, 0, false);          // Hit unknown map.
+  sample_tree->AddThreadMap(1, 1, 2, 7, 0, 0, "map3");   // Add map 3.
+  sample_tree->AddSample(1, 1, 7, 0, 0, false);          // Hit map 3.
+  sample_tree->AddSample(1, 1, 10, 0, 0, false);         // Hit unknown map.
+
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "thread1", "map1", 1, 1},
+      {1, 1, "thread1", "map2", 5, 1},
+      {1, 1, "thread1", "map3", 2, 1},
+      {1, 1, "thread1", "unknown", 0, 2},
+  };
+  VisitSampleTree(sample_tree.get(), expected_samples);
+}
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
new file mode 100644
index 0000000..34155a3
--- /dev/null
+++ b/simpleperf/test_util.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "workload.h"
+
+static void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads) {
+  workloads->clear();
+  for (size_t i = 0; i < count; ++i) {
+    auto workload = Workload::CreateWorkload({"sleep", "1"});
+    ASSERT_TRUE(workload != nullptr);
+    ASSERT_TRUE(workload->Start());
+    workloads->push_back(std::move(workload));
+  }
+}
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
new file mode 100644
index 0000000..b212263
--- /dev/null
+++ b/simpleperf/utils.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+void PrintIndented(size_t indent, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  printf("%*s", static_cast<int>(indent * 2), "");
+  vprintf(fmt, ap);
+  va_end(ap);
+}
+
+bool IsPowerOfTwo(uint64_t value) {
+  return (value != 0 && ((value & (value - 1)) == 0));
+}
+
+void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
+                     std::vector<std::string>* subdirs) {
+  if (files != nullptr) {
+    files->clear();
+  }
+  if (subdirs != nullptr) {
+    subdirs->clear();
+  }
+  DIR* dir = opendir(dirpath.c_str());
+  if (dir == nullptr) {
+    PLOG(DEBUG) << "can't open dir " << dirpath;
+    return;
+  }
+  dirent* entry;
+  while ((entry = readdir(dir)) != nullptr) {
+    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
+      continue;
+    }
+    if (entry->d_type == DT_DIR) {
+      if (subdirs != nullptr) {
+        subdirs->push_back(entry->d_name);
+      }
+    } else {
+      if (files != nullptr) {
+        files->push_back(entry->d_name);
+      }
+    }
+  }
+  closedir(dir);
+}
+
+bool IsDir(const std::string& dirpath) {
+  struct stat st;
+  if (stat(dirpath.c_str(), &st) == 0) {
+    if (S_ISDIR(st.st_mode)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool RemovePossibleFile(const std::string& filename) {
+  struct stat st;
+  if (stat(filename.c_str(), &st) == 0) {
+    if (!S_ISREG(st.st_mode)) {
+      LOG(ERROR) << filename << " is not a file.";
+      return false;
+    }
+    if (unlink(filename.c_str()) == -1) {
+      PLOG(ERROR) << "unlink(" << filename << ") failed";
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
new file mode 100644
index 0000000..b017197
--- /dev/null
+++ b/simpleperf/utils.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_UTILS_H_
+#define SIMPLE_PERF_UTILS_H_
+
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+#define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1))
+
+class LineReader {
+ public:
+  LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
+  }
+
+  ~LineReader() {
+    free(buf_);
+    fclose(fp_);
+  }
+
+  char* ReadLine() {
+    if (getline(&buf_, &bufsize_, fp_) != -1) {
+      return buf_;
+    }
+    return nullptr;
+  }
+
+  size_t MaxLineSize() {
+    return bufsize_;
+  }
+
+ private:
+  FILE* fp_;
+  char* buf_;
+  size_t bufsize_;
+};
+
+class SignalHandlerRegister {
+ public:
+  SignalHandlerRegister(const std::vector<int>& signums, void (*handler)(int)) {
+    for (auto& sig : signums) {
+      sighandler_t old_handler = signal(sig, handler);
+      saved_signal_handlers_.push_back(std::make_pair(sig, old_handler));
+    }
+  }
+
+  ~SignalHandlerRegister() {
+    for (auto& pair : saved_signal_handlers_) {
+      signal(pair.first, pair.second);
+    }
+  }
+
+ private:
+  std::vector<std::pair<int, sighandler_t>> saved_signal_handlers_;
+};
+
+void PrintIndented(size_t indent, const char* fmt, ...);
+
+bool IsPowerOfTwo(uint64_t value);
+
+void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
+                     std::vector<std::string>* subdirs);
+bool IsDir(const std::string& dirpath);
+bool RemovePossibleFile(const std::string& filename);
+
+#endif  // SIMPLE_PERF_UTILS_H_
diff --git a/simpleperf/workload.cpp b/simpleperf/workload.cpp
new file mode 100644
index 0000000..9138afa
--- /dev/null
+++ b/simpleperf/workload.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "workload.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+std::unique_ptr<Workload> Workload::CreateWorkload(const std::vector<std::string>& args) {
+  std::unique_ptr<Workload> workload(new Workload(args));
+  if (workload != nullptr && workload->CreateNewProcess()) {
+    return workload;
+  }
+  return nullptr;
+}
+
+Workload::~Workload() {
+  if (work_pid_ != -1 && work_state_ != NotYetCreateNewProcess) {
+    if (!Workload::WaitChildProcess(false)) {
+      kill(work_pid_, SIGKILL);
+      Workload::WaitChildProcess(true);
+    }
+  }
+  if (start_signal_fd_ != -1) {
+    close(start_signal_fd_);
+  }
+  if (exec_child_fd_ != -1) {
+    close(exec_child_fd_);
+  }
+}
+
+static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd, int exec_child_fd);
+
+bool Workload::CreateNewProcess() {
+  CHECK_EQ(work_state_, NotYetCreateNewProcess);
+
+  int start_signal_pipe[2];
+  if (pipe2(start_signal_pipe, O_CLOEXEC) != 0) {
+    PLOG(ERROR) << "pipe2() failed";
+    return false;
+  }
+
+  int exec_child_pipe[2];
+  if (pipe2(exec_child_pipe, O_CLOEXEC) != 0) {
+    PLOG(ERROR) << "pipe2() failed";
+    close(start_signal_pipe[0]);
+    close(start_signal_pipe[1]);
+    return false;
+  }
+
+  pid_t pid = fork();
+  if (pid == -1) {
+    PLOG(ERROR) << "fork() failed";
+    close(start_signal_pipe[0]);
+    close(start_signal_pipe[1]);
+    close(exec_child_pipe[0]);
+    close(exec_child_pipe[1]);
+    return false;
+  } else if (pid == 0) {
+    // In child process.
+    close(start_signal_pipe[1]);
+    close(exec_child_pipe[0]);
+    ChildProcessFn(args_, start_signal_pipe[0], exec_child_pipe[1]);
+  }
+  // In parent process.
+  close(start_signal_pipe[0]);
+  close(exec_child_pipe[1]);
+  start_signal_fd_ = start_signal_pipe[1];
+  exec_child_fd_ = exec_child_pipe[0];
+  work_pid_ = pid;
+  work_state_ = NotYetStartNewProcess;
+  return true;
+}
+
+static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd, int exec_child_fd) {
+  std::vector<char*> argv(args.size() + 1);
+  for (size_t i = 0; i < args.size(); ++i) {
+    argv[i] = &args[i][0];
+  }
+  argv[args.size()] = nullptr;
+
+  char start_signal = 0;
+  ssize_t nread = TEMP_FAILURE_RETRY(read(start_signal_fd, &start_signal, 1));
+  if (nread == 1 && start_signal == 1) {
+    close(start_signal_fd);
+    execvp(argv[0], argv.data());
+    // If execvp() succeed, we will not arrive here. But if it failed, we need to
+    // report the failure to the parent process by writing 1 to exec_child_fd.
+    int saved_errno = errno;
+    char exec_child_failed = 1;
+    TEMP_FAILURE_RETRY(write(exec_child_fd, &exec_child_failed, 1));
+    close(exec_child_fd);
+    errno = saved_errno;
+    PLOG(FATAL) << "execvp(" << argv[0] << ") failed";
+  } else {
+    PLOG(FATAL) << "child process failed to receive start_signal, nread = " << nread;
+  }
+}
+
+bool Workload::Start() {
+  CHECK_EQ(work_state_, NotYetStartNewProcess);
+  char start_signal = 1;
+  ssize_t nwrite = TEMP_FAILURE_RETRY(write(start_signal_fd_, &start_signal, 1));
+  if (nwrite != 1) {
+    PLOG(ERROR) << "write start signal failed";
+    return false;
+  }
+  char exec_child_failed;
+  ssize_t nread = TEMP_FAILURE_RETRY(read(exec_child_fd_, &exec_child_failed, 1));
+  if (nread != 0) {
+    ((nread == -1) ? PLOG(ERROR) : LOG(ERROR)) << "exec child failed, nread = " << nread;
+    return false;
+  }
+  work_state_ = Started;
+  return true;
+}
+
+bool Workload::WaitChildProcess(bool wait_forever) {
+  bool finished = false;
+  int status;
+  pid_t result = TEMP_FAILURE_RETRY(waitpid(work_pid_, &status, (wait_forever ? 0 : WNOHANG)));
+  if (result == work_pid_) {
+    finished = true;
+    if (WIFSIGNALED(status)) {
+      LOG(WARNING) << "child process was terminated by signal " << strsignal(WTERMSIG(status));
+    } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+      LOG(WARNING) << "child process exited with exit code " << WEXITSTATUS(status);
+    }
+  } else if (result == -1) {
+    PLOG(ERROR) << "waitpid() failed";
+  }
+  return finished;
+}
diff --git a/simpleperf/workload.h b/simpleperf/workload.h
new file mode 100644
index 0000000..4bb0ee5
--- /dev/null
+++ b/simpleperf/workload.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_WORKLOAD_H_
+#define SIMPLE_PERF_WORKLOAD_H_
+
+#include <sys/types.h>
+#include <chrono>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+class Workload {
+ private:
+  enum WorkState {
+    NotYetCreateNewProcess,
+    NotYetStartNewProcess,
+    Started,
+  };
+
+ public:
+  static std::unique_ptr<Workload> CreateWorkload(const std::vector<std::string>& args);
+
+  ~Workload();
+
+  bool Start();
+  pid_t GetPid() {
+    return work_pid_;
+  }
+
+ private:
+  Workload(const std::vector<std::string>& args)
+      : work_state_(NotYetCreateNewProcess),
+        args_(args),
+        work_pid_(-1),
+        start_signal_fd_(-1),
+        exec_child_fd_(-1) {
+  }
+
+  bool CreateNewProcess();
+  bool WaitChildProcess(bool wait_forever);
+
+  WorkState work_state_;
+  std::vector<std::string> args_;
+  pid_t work_pid_;
+  int start_signal_fd_;  // The parent process writes 1 to start workload in the child process.
+  int exec_child_fd_;    // The child process writes 1 to notify that execvp() failed.
+
+  DISALLOW_COPY_AND_ASSIGN(Workload);
+};
+
+#endif  // SIMPLE_PERF_WORKLOAD_H_
diff --git a/simpleperf/workload_test.cpp b/simpleperf/workload_test.cpp
new file mode 100644
index 0000000..f250328
--- /dev/null
+++ b/simpleperf/workload_test.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <signal.h>
+
+#include "utils.h"
+#include "workload.h"
+
+static volatile bool signaled;
+static void signal_handler(int) {
+  signaled = true;
+}
+
+TEST(workload, success) {
+  signaled = false;
+  SignalHandlerRegister signal_handler_register({SIGCHLD}, signal_handler);
+  auto workload = Workload::CreateWorkload({"sleep", "1"});
+  ASSERT_TRUE(workload != nullptr);
+  ASSERT_TRUE(workload->GetPid() != 0);
+  ASSERT_TRUE(workload->Start());
+  while (!signaled) {
+  }
+}
+
+TEST(workload, execvp_failure) {
+  auto workload = Workload::CreateWorkload({"/dev/null"});
+  ASSERT_TRUE(workload != nullptr);
+  ASSERT_FALSE(workload->Start());
+}
+
+static void run_signaled_workload() {
+  {
+    signaled = false;
+    SignalHandlerRegister signal_handler_register({SIGCHLD}, signal_handler);
+    auto workload = Workload::CreateWorkload({"sleep", "10"});
+    ASSERT_TRUE(workload != nullptr);
+    ASSERT_TRUE(workload->Start());
+    ASSERT_EQ(0, kill(workload->GetPid(), SIGABRT));
+    while (!signaled) {
+    }
+  }
+  // Make sure all destructors are called before exit().
+  exit(0);
+}
+
+TEST(workload, signaled_warning) {
+  ASSERT_EXIT(run_signaled_workload(), testing::ExitedWithCode(0),
+              "child process was terminated by signal");
+}
+
+static void run_exit_nonzero_workload() {
+  {
+    signaled = false;
+    SignalHandlerRegister signal_handler_register({SIGCHLD}, signal_handler);
+    auto workload = Workload::CreateWorkload({"ls", "nonexistdir"});
+    ASSERT_TRUE(workload != nullptr);
+    ASSERT_TRUE(workload->Start());
+    while (!signaled) {
+    }
+  }
+  // Make sure all destructors are called before exit().
+  exit(0);
+}
+
+TEST(workload, exit_nonzero_warning) {
+  ASSERT_EXIT(run_exit_nonzero_workload(), testing::ExitedWithCode(0),
+              "child process exited with exit code");
+}
diff --git a/slideshow/slideshow.cpp b/slideshow/slideshow.cpp
index c015d53..318d805 100644
--- a/slideshow/slideshow.cpp
+++ b/slideshow/slideshow.cpp
@@ -54,7 +54,7 @@
 
 static void draw(const char *resname)
 {
-    gr_surface surface;
+    GRSurface* surface;
     int w, h, x, y;
 
     if (res_create_display_surface(resname, &surface) < 0) {
diff --git a/squashfs_utils/mksquashfsimage.sh b/squashfs_utils/mksquashfsimage.sh
index dbf7166..dab80ba 100755
--- a/squashfs_utils/mksquashfsimage.sh
+++ b/squashfs_utils/mksquashfsimage.sh
@@ -5,7 +5,7 @@
 function usage() {
 cat<<EOT
 Usage:
-${0##*/} SRC_DIR OUTPUT_FILE [-m MOUNT_POINT] [-c FILE_CONTEXTS] [-b BLOCK_SIZE]
+${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-c FILE_CONTEXTS] [-b BLOCK_SIZE]
 EOT
 }
 
@@ -24,6 +24,12 @@
 OUTPUT_FILE=$2
 shift; shift
 
+SPARSE=false
+if [[ "$1" == "-s" ]]; then
+    SPARSE=true
+    shift;
+fi
+
 MOUNT_POINT=
 if [[ "$1" == "-m" ]]; then
     MOUNT_POINT=$2
@@ -56,6 +62,17 @@
 MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp lz4 -Xhc -no-exports -noappend -no-recovery -android-fs-config $OPT"
 echo $MAKE_SQUASHFS_CMD
 $MAKE_SQUASHFS_CMD
+
 if [ $? -ne 0 ]; then
     exit 4
 fi
+
+SPARSE_SUFFIX=".sparse"
+if [ "$SPARSE" = true ]; then
+    img2simg $OUTPUT_FILE $OUTPUT_FILE$SPARSE_SUFFIX
+    if [ $? -ne 0 ]; then
+        exit 4
+    fi
+    mv $OUTPUT_FILE$SPARSE_SUFFIX $OUTPUT_FILE
+fi
+
diff --git a/squashfs_utils/squashfs_utils.c b/squashfs_utils/squashfs_utils.c
index 128a3ef..6189189 100644
--- a/squashfs_utils/squashfs_utils.c
+++ b/squashfs_utils/squashfs_utils.c
@@ -58,6 +58,6 @@
         sb.bytes_used + (4096 - (sb.bytes_used & (4096 - 1)));
 
 cleanup:
-    TEMP_FAILURE_RETRY(close(data_device));
+    close(data_device);
     return ret;
 }
diff --git a/tests/iptables/qtaguid/Android.mk b/tests/iptables/qtaguid/Android.mk
index a661678..b92b662 100644
--- a/tests/iptables/qtaguid/Android.mk
+++ b/tests/iptables/qtaguid/Android.mk
@@ -23,5 +23,6 @@
 LOCAL_SHARED_LIBRARIES += libcutils libutils liblog
 LOCAL_STATIC_LIBRARIES += libtestUtil
 LOCAL_C_INCLUDES += system/extras/tests/include
+LOCAL_CFLAGS += -fno-strict-aliasing
 
 include $(BUILD_NATIVE_TEST)
diff --git a/tests/net_test/iproute.py b/tests/net_test/iproute.py
index cde1803..cf1f282 100644
--- a/tests/net_test/iproute.py
+++ b/tests/net_test/iproute.py
@@ -64,6 +64,7 @@
 RTM_GETROUTE = 26
 RTM_NEWNEIGH = 28
 RTM_DELNEIGH = 29
+RTM_GETNEIGH = 30
 RTM_NEWRULE = 32
 RTM_DELRULE = 33
 RTM_GETRULE = 34
@@ -133,12 +134,16 @@
     "family prefixlen flags scope index")
 IFACacheinfo = cstruct.Struct(
     "IFACacheinfo", "=IIII", "prefered valid cstamp tstamp")
+NDACacheinfo = cstruct.Struct(
+    "NDACacheinfo", "=IIII", "confirmed used updated refcnt")
 
 
 ### Neighbour table entry constants. See include/uapi/linux/neighbour.h.
 # Neighbour cache entry attributes.
 NDA_DST = 1
 NDA_LLADDR = 2
+NDA_CACHEINFO = 3
+NDA_PROBES = 4
 
 # Neighbour cache entry states.
 NUD_PERMANENT = 0x80
@@ -155,6 +160,7 @@
 FRA_FWMARK = 10
 FRA_SUPPRESS_PREFIXLEN = 14
 FRA_TABLE = 15
+FRA_FWMASK = 16
 FRA_OIFNAME = 17
 FRA_UID_START = 18
 FRA_UID_END = 19
@@ -180,6 +186,7 @@
 IFLA_NUM_RX_QUEUES = 32
 IFLA_CARRIER = 33
 
+
 def CommandVerb(command):
   return ["NEW", "DEL", "GET", "SET"][command % 4]
 
@@ -191,7 +198,7 @@
 def CommandName(command):
   try:
     return "RTM_%s%s" % (CommandVerb(command), CommandSubject(command))
-  except KeyError:
+  except IndexError:
     return "RTM_%d" % command
 
 
@@ -288,12 +295,12 @@
       # Don't know what this is. Leave it as an integer.
       name = nla_type
 
-    if name in ["FRA_PRIORITY", "FRA_FWMARK", "FRA_TABLE",
+    if name in ["FRA_PRIORITY", "FRA_FWMARK", "FRA_TABLE", "FRA_FWMASK",
                 "FRA_UID_START", "FRA_UID_END",
                 "RTA_OIF", "RTA_PRIORITY", "RTA_TABLE", "RTA_MARK",
                 "IFLA_MTU", "IFLA_TXQLEN", "IFLA_GROUP", "IFLA_EXT_MASK",
                 "IFLA_PROMISCUITY", "IFLA_NUM_RX_QUEUES",
-                "IFLA_NUM_TX_QUEUES"]:
+                "IFLA_NUM_TX_QUEUES", "NDA_PROBES"]:
       data = struct.unpack("=I", nla_data)[0]
     elif name == "FRA_SUPPRESS_PREFIXLEN":
       data = struct.unpack("=i", nla_data)[0]
@@ -311,6 +318,8 @@
       data = RTACacheinfo(nla_data)
     elif name == "IFA_CACHEINFO":
       data = IFACacheinfo(nla_data)
+    elif name == "NDA_CACHEINFO":
+      data = NDACacheinfo(nla_data)
     elif name in ["NDA_LLADDR", "IFLA_ADDRESS"]:
       data = ":".join(x.encode("hex") for x in nla_data)
     else:
@@ -511,12 +520,10 @@
       self._ExpectDone()
     return out
 
-  def MaybeDebugCommand(self, command, data):
-    subject = CommandSubject(command)
-    if "ALL" not in self.NL_DEBUG and subject not in self.NL_DEBUG:
-      return
-    name = CommandName(command)
+  def CommandToString(self, command, data):
     try:
+      name = CommandName(command)
+      subject = CommandSubject(command)
       struct_type = {
           "ADDR": IfAddrMsg,
           "LINK": IfinfoMsg,
@@ -525,14 +532,24 @@
           "RULE": RTMsg,
       }[subject]
       parsed = self._ParseNLMsg(data, struct_type)
-      print "%s %s" % (name, str(parsed))
-    except KeyError:
+      return "%s %s" % (name, str(parsed))
+    except IndexError:
       raise ValueError("Don't know how to print command type %s" % name)
 
+  def MaybeDebugCommand(self, command, data):
+    subject = CommandSubject(command)
+    if "ALL" not in self.NL_DEBUG and subject not in self.NL_DEBUG:
+      return
+    print self.CommandToString(command, data)
+
   def MaybeDebugMessage(self, message):
     hdr = NLMsgHdr(message)
     self.MaybeDebugCommand(hdr.type, message)
 
+  def PrintMessage(self, message):
+    hdr = NLMsgHdr(message)
+    print self.CommandToString(hdr.type, message)
+
   def _Dump(self, command, msg, msgtype):
     """Sends a dump request and returns a list of decoded messages."""
     # Create a netlink dump request containing the msg.
@@ -664,6 +681,10 @@
   def DelNeighbour(self, version, addr, lladdr, dev):
     self._Neighbour(version, False, addr, lladdr, dev, 0)
 
+  def DumpNeighbours(self, version):
+    ndmsg = NdMsg((self._AddressFamily(version), 0, 0, 0, 0))
+    return self._Dump(RTM_GETNEIGH, ndmsg, NdMsg)
+
 
 if __name__ == "__main__":
   iproute = IPRoute()
diff --git a/tests/net_test/multinetwork_base.py b/tests/net_test/multinetwork_base.py
index 8940258..5952d56 100644
--- a/tests/net_test/multinetwork_base.py
+++ b/tests/net_test/multinetwork_base.py
@@ -430,13 +430,12 @@
       raise ValueError("Unknown interface selection mode %s" % mode)
 
   def BuildSocket(self, version, constructor, netid, routing_mode):
-    uid = self.UidForNetid(netid) if routing_mode == "uid" else None
-    with net_test.RunAsUid(uid):
-      family = self.GetProtocolFamily(version)
-      s = constructor(family)
+    s = constructor(self.GetProtocolFamily(version))
 
     if routing_mode not in [None, "uid"]:
       self.SelectInterface(s, netid, routing_mode)
+    elif routing_mode == "uid":
+      os.fchown(s.fileno(), self.UidForNetid(netid), -1)
 
     return s
 
diff --git a/tests/net_test/multinetwork_test.py b/tests/net_test/multinetwork_test.py
index b66d765..6ffb5d0 100755
--- a/tests/net_test/multinetwork_test.py
+++ b/tests/net_test/multinetwork_test.py
@@ -47,9 +47,6 @@
 SYNCOOKIES_SYSCTL = "/proc/sys/net/ipv4/tcp_syncookies"
 TCP_MARK_ACCEPT_SYSCTL = "/proc/sys/net/ipv4/tcp_fwmark_accept"
 
-HAVE_MARK_REFLECT = os.path.isfile(IPV4_MARK_REFLECT_SYSCTL)
-HAVE_TCP_MARK_ACCEPT = os.path.isfile(TCP_MARK_ACCEPT_SYSCTL)
-
 # The IP[V6]UNICAST_IF socket option was added between 3.1 and 3.4.
 HAVE_UNICAST_IF = net_test.LINUX_VERSION >= (3, 4, 0)
 
@@ -581,27 +578,21 @@
   def SYNToClosedPort(self, *args):
     return Packets.SYN(999, *args)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv4ICMPErrorsReflectMark(self):
     self.CheckReflection(4, Packets.UDP, Packets.ICMPPortUnreachable)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv6ICMPErrorsReflectMark(self):
     self.CheckReflection(6, Packets.UDP, Packets.ICMPPortUnreachable)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv4PingRepliesReflectMarkAndTos(self):
     self.CheckReflection(4, Packets.ICMPEcho, Packets.ICMPReply)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv6PingRepliesReflectMarkAndTos(self):
     self.CheckReflection(6, Packets.ICMPEcho, Packets.ICMPReply)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv4RSTsReflectMark(self):
     self.CheckReflection(4, self.SYNToClosedPort, Packets.RST)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv6RSTsReflectMark(self):
     self.CheckReflection(6, self.SYNToClosedPort, Packets.RST)
 
@@ -723,9 +714,8 @@
 
           listenport = listensocket.getsockname()[1]
 
-          if HAVE_TCP_MARK_ACCEPT:
-            accept_sysctl = 1 if mode == self.MODE_INCOMING_MARK else 0
-            self._SetTCPMarkAcceptSysctl(accept_sysctl)
+          accept_sysctl = 1 if mode == self.MODE_INCOMING_MARK else 0
+          self._SetTCPMarkAcceptSysctl(accept_sysctl)
 
           bound_dev = iif if mode == self.MODE_BINDTODEVICE else None
           self.BindToDevice(listensocket, bound_dev)
@@ -756,11 +746,9 @@
     self.CheckTCP(4, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
     self.CheckTCP(6, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
 
-  @unittest.skipUnless(HAVE_TCP_MARK_ACCEPT, "fwmark writeback not supported")
   def testIPv4MarkAccept(self):
     self.CheckTCP(4, [self.MODE_INCOMING_MARK])
 
-  @unittest.skipUnless(HAVE_TCP_MARK_ACCEPT, "fwmark writeback not supported")
   def testIPv6MarkAccept(self):
     self.CheckTCP(6, [self.MODE_INCOMING_MARK])
 
@@ -975,7 +963,6 @@
   # table the original packet used, and thus it won't be able to clone the
   # correct route.
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv4UnmarkedSocketPMTU(self):
     self.SetMarkReflectSysctls(1)
     try:
@@ -983,7 +970,6 @@
     finally:
       self.SetMarkReflectSysctls(0)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv6UnmarkedSocketPMTU(self):
     self.SetMarkReflectSysctls(1)
     try:
diff --git a/tests/net_test/run_net_test.sh b/tests/net_test/run_net_test.sh
index fae1145..a12f3cf 100755
--- a/tests/net_test/run_net_test.sh
+++ b/tests/net_test/run_net_test.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Kernel configration options.
+# Kernel configuration options.
 OPTIONS=" IPV6 IPV6_ROUTER_PREF IPV6_MULTIPLE_TABLES IPV6_ROUTE_INFO"
 OPTIONS="$OPTIONS TUN SYN_COOKIES IP_ADVANCED_ROUTER IP_MULTIPLE_TABLES"
 OPTIONS="$OPTIONS NETFILTER NETFILTER_ADVANCED NETFILTER_XTABLES"
@@ -11,6 +11,9 @@
 # For 3.1 kernels, where devtmpfs is not on by default.
 OPTIONS="$OPTIONS DEVTMPFS DEVTMPFS_MOUNT"
 
+# These two break the flo kernel due to differences in -Werror on recent GCC.
+DISABLE_OPTIONS=" CONFIG_REISERFS_FS CONFIG_ANDROID_PMEM"
+
 # How many tap interfaces to create.
 NUMTAPINTERFACES=2
 
@@ -73,6 +76,10 @@
 cmdline=${OPTIONS// / -e }
 ./scripts/config $cmdline
 
+# Disable the kernel config options listed in $DISABLE_OPTIONS.
+cmdline=${DISABLE_OPTIONS// / -d }
+./scripts/config $cmdline
+
 # olddefconfig doesn't work on old kernels.
 if ! make olddefconfig ARCH=um SUBARCH=x86_64 CROSS_COMPILE= ; then
   cat >&2 << EOF
diff --git a/verity/Android.mk b/verity/Android.mk
index 46396ca..bbe74bb 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -98,6 +98,6 @@
 LOCAL_SRC_FILES := build_verity_tree.cpp
 LOCAL_MODULE_TAGS := optional
 LOCAL_STATIC_LIBRARIES := libsparse_host libz
-LOCAL_SHARED_LIBRARIES := libcrypto-host
+LOCAL_SHARED_LIBRARIES := libcrypto-host libbase
 LOCAL_CFLAGS += -Wall -Werror
 include $(BUILD_HOST_EXECUTABLE)
diff --git a/verity/build_verity_tree.cpp b/verity/build_verity_tree.cpp
index e7bfa40..5a6a6ee 100644
--- a/verity/build_verity_tree.cpp
+++ b/verity/build_verity_tree.cpp
@@ -16,6 +16,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <base/file.h>
+
 struct sparse_hash_ctx {
     unsigned char *hashes;
     const unsigned char *salt;
@@ -353,7 +355,9 @@
     if (fd < 0) {
         FATAL("failed to open output file '%s'\n", verity_filename);
     }
-    write(fd, verity_tree, verity_blocks * block_size);
+    if (!android::base::WriteFully(fd, verity_tree, verity_blocks * block_size)) {
+        FATAL("failed to write '%s'\n", verity_filename);
+    }
     close(fd);
 
     delete[] verity_tree_levels;
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
index a55600c..0da978f 100644
--- a/verity/generate_verity_key.c
+++ b/verity/generate_verity_key.c
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
+#define _GNU_SOURCE  /* needed for asprintf */
+
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 /* HACK: we need the RSAPublicKey struct
diff --git a/verity/verify_boot_signature.c b/verity/verify_boot_signature.c
index 55591aa..58b7a19 100644
--- a/verity/verify_boot_signature.c
+++ b/verity/verify_boot_signature.c
@@ -19,6 +19,7 @@
 #include <endian.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -28,6 +29,7 @@
 
 #include <openssl/asn1.h>
 #include <openssl/asn1t.h>
+#include <openssl/crypto.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
 #include <openssl/rsa.h>