OSDN Git Service

ANRdaemon: move trace result from /sdcard to /data am: d93aa41807
authorZhengyin Qian <qianzy@google.com>
Wed, 29 Jun 2016 17:53:11 +0000 (17:53 +0000)
committerandroid-build-merger <android-build-merger@google.com>
Wed, 29 Jun 2016 17:53:11 +0000 (17:53 +0000)
am: adfc967454

Change-Id: I4f8d4551c3ab2f5430600af833ced564aa328a55

343 files changed:
alloc-stress/Android.mk [new file with mode: 0644]
alloc-stress/alloc-stress.cpp [new file with mode: 0644]
boot_control_copy/Android.mk [new file with mode: 0644]
boot_control_copy/NOTICE [new file with mode: 0644]
boot_control_copy/boot_control_copy.c [new file with mode: 0644]
boot_control_copy/bootinfo.c [new file with mode: 0644]
boot_control_copy/bootinfo.h [new file with mode: 0644]
bootctl/Android.mk [new file with mode: 0644]
bootctl/NOTICE [new file with mode: 0644]
bootctl/bootctl.c [new file with mode: 0644]
brillo_config/Android.mk [new file with mode: 0644]
cpustats/NOTICE [new file with mode: 0644]
cpustats/cpustats.c
crypto-perf/Android.mk [new file with mode: 0644]
crypto-perf/NOTICE [new file with mode: 0644]
crypto-perf/crypto.cpp [new file with mode: 0644]
ext4_utils/Android.mk
ext4_utils/allocate.c
ext4_utils/allocate.h
ext4_utils/blk_alloc_to_base_fs.c [new file with mode: 0644]
ext4_utils/canned_fs_config.c [deleted file]
ext4_utils/contents.c
ext4_utils/ext4_crypt.cpp
ext4_utils/ext4_crypt.h [new file with mode: 0644]
ext4_utils/ext4_crypt_init_extensions.cpp
ext4_utils/ext4_crypt_init_extensions.h
ext4_utils/ext4_sb.c
ext4_utils/ext4_sb.h
ext4_utils/ext4_utils.c
ext4_utils/ext4_utils.h
ext4_utils/extent.c
ext4_utils/key_control.cpp
ext4_utils/make_ext4fs.c
ext4_utils/make_ext4fs.h
ext4_utils/make_ext4fs_main.c
ext4_utils/mkuserimg.sh
ext4_utils/unencrypted_properties.cpp [deleted file]
ext4_utils/unencrypted_properties.h [deleted file]
ext4_utils/wipe.h
f2fs_utils/Android.mk
f2fs_utils/f2fs_ioutils.c
f2fs_utils/f2fs_utils.c
f2fs_utils/make_f2fs_main.c
iotop/Android.mk [moved from timeinfo/Android.mk with 65% similarity]
iotop/MODULE_LICENSE_APACHE2 [moved from tests/bionic/libc/MODULE_LICENSE_BSD_AND_GPL with 100% similarity]
iotop/NOTICE [new file with mode: 0644]
iotop/iotop.cpp [new file with mode: 0644]
iotop/tasklist.cpp [new file with mode: 0644]
iotop/tasklist.h [new file with mode: 0644]
iotop/taskstats.cpp [new file with mode: 0644]
iotop/taskstats.h [new file with mode: 0644]
kexec_tools/NOTICE [new file with mode: 0644]
ksmutils/Android.mk
ksmutils/ksminfo.c
latencytop/Android.mk
libfec/Android.mk [new file with mode: 0644]
libfec/NOTICE [new file with mode: 0644]
libfec/fec_open.cpp [new file with mode: 0644]
libfec/fec_private.h [new file with mode: 0644]
libfec/fec_process.cpp [new file with mode: 0644]
libfec/fec_read.cpp [new file with mode: 0644]
libfec/fec_verity.cpp [new file with mode: 0644]
libfec/include/fec/ecc.h [new file with mode: 0644]
libfec/include/fec/io.h [new file with mode: 0644]
libfec/test/Android.mk [new file with mode: 0644]
libfec/test/test_read.cpp [new file with mode: 0644]
libfec/test/test_rs.c [new file with mode: 0644]
libpagemap/Android.mk
libpagemap/pagemap_test.cpp [new file with mode: 0644]
libpagemap/pm_process.c
librank/Android.mk
memcpy-perf/Android.mk [new file with mode: 0644]
memcpy-perf/NOTICE [new file with mode: 0644]
memcpy-perf/graph_memcpy.py [new file with mode: 0644]
memcpy-perf/memcpy-perf.cpp [new file with mode: 0644]
memcpy-perf/test-funcs.cpp [new file with mode: 0644]
memory_replay/Action.cpp [new file with mode: 0644]
memory_replay/Action.h [new file with mode: 0644]
memory_replay/Android.mk [new file with mode: 0644]
memory_replay/LineBuffer.cpp [new file with mode: 0644]
memory_replay/LineBuffer.h [new file with mode: 0644]
memory_replay/NOTICE [new file with mode: 0644]
memory_replay/NativeInfo.cpp [new file with mode: 0644]
memory_replay/NativeInfo.h [moved from ext4_utils/canned_fs_config.h with 67% similarity]
memory_replay/Pointers.cpp [new file with mode: 0644]
memory_replay/Pointers.h [new file with mode: 0644]
memory_replay/Thread.cpp [new file with mode: 0644]
memory_replay/Thread.h [new file with mode: 0644]
memory_replay/Threads.cpp [new file with mode: 0644]
memory_replay/Threads.h [new file with mode: 0644]
memory_replay/dumps/README [new file with mode: 0644]
memory_replay/dumps/camera.zip [new file with mode: 0644]
memory_replay/dumps/gmail.zip [new file with mode: 0644]
memory_replay/dumps/maps.zip [new file with mode: 0644]
memory_replay/dumps/surfaceflinger.zip [new file with mode: 0644]
memory_replay/dumps/system_server.zip [new file with mode: 0644]
memory_replay/dumps/systemui.zip [new file with mode: 0644]
memory_replay/dumps/youtube.zip [new file with mode: 0644]
memory_replay/fast/.clang-format [new file with mode: 0644]
memory_replay/main.cpp [new file with mode: 0644]
memory_replay/tests/ActionTest.cpp [new file with mode: 0644]
memory_replay/tests/LineBufferTest.cpp [new file with mode: 0644]
memory_replay/tests/NativeInfoTest.cpp [new file with mode: 0644]
memory_replay/tests/PointersTest.cpp [new file with mode: 0644]
memory_replay/tests/ThreadTest.cpp [new file with mode: 0644]
memory_replay/tests/ThreadsTest.cpp [new file with mode: 0644]
memtrack/NOTICE [new file with mode: 0644]
memtrack/memtrack.cpp
micro_bench/NOTICE [new file with mode: 0644]
micro_bench/micro_bench.cpp
mmap-perf/Android.mk [new file with mode: 0644]
mmap-perf/NOTICE [new file with mode: 0644]
mmap-perf/mmapPerf.cpp [new file with mode: 0644]
mmap-perf/unsupported.cpp [new file with mode: 0644]
multinetwork/Android.mk [new file with mode: 0644]
multinetwork/common.cpp [new file with mode: 0644]
multinetwork/common.h [new file with mode: 0644]
multinetwork/dnschk.cpp [new file with mode: 0644]
multinetwork/httpurl.cpp [new file with mode: 0644]
multinetwork/quick_test.sh [new file with mode: 0755]
pagecache/Android.mk [new file with mode: 0644]
pagecache/MODULE_LICENSE_APACHE2 [new file with mode: 0644]
pagecache/NOTICE [new file with mode: 0644]
pagecache/README [new file with mode: 0644]
pagecache/dumpcache.c [new file with mode: 0644]
pagecache/pagecache.py [new file with mode: 0755]
perfprofd/Android.mk
perfprofd/NOTICE [new file with mode: 0644]
perfprofd/configreader.cc [new file with mode: 0644]
perfprofd/configreader.h [new file with mode: 0644]
perfprofd/perf_profile.proto
perfprofd/perfprofd.rc [new file with mode: 0644]
perfprofd/perfprofdcore.cc
perfprofd/perfprofdcore.h
perfprofd/tests/Android.mk
perfprofd/tests/perfprofd_test.cc
postinst/Android.mk [new file with mode: 0644]
postinst/MODULE_LICENSE_APACHE2 [new file with mode: 0644]
postinst/NOTICE [new file with mode: 0644]
postinst/postinst.sh [new file with mode: 0644]
procmem/Android.mk
procrank/Android.mk
procrank/procrank.c
puncture_fs/NOTICE [new file with mode: 0644]
puncture_fs/puncture_fs.c
sane_schedstat/Android.mk
sane_schedstat/NOTICE [new file with mode: 0644]
showmap/Android.mk
showmap/showmap.cpp [moved from showmap/showmap.c with 79% similarity]
showslab/showslab.c
simpleperf/Android.mk
simpleperf/NOTICE [new file with mode: 0644]
simpleperf/build_id.h
simpleperf/callchain.cpp [new file with mode: 0644]
simpleperf/callchain.h [new file with mode: 0644]
simpleperf/cmd_dumprecord.cpp
simpleperf/cmd_dumprecord_test.cpp
simpleperf/cmd_help.cpp
simpleperf/cmd_list.cpp
simpleperf/cmd_list_test.cpp
simpleperf/cmd_record.cpp
simpleperf/cmd_record_test.cpp
simpleperf/cmd_report.cpp [new file with mode: 0644]
simpleperf/cmd_report_test.cpp [new file with mode: 0644]
simpleperf/cmd_stat.cpp
simpleperf/cmd_stat_test.cpp
simpleperf/command.cpp
simpleperf/command.h
simpleperf/command_test.cpp
simpleperf/cpu_hotplug_test.cpp [new file with mode: 0644]
simpleperf/dso.cpp [new file with mode: 0644]
simpleperf/dso.h [new file with mode: 0644]
simpleperf/dwarf_unwind.cpp [new file with mode: 0644]
simpleperf/dwarf_unwind.h [new file with mode: 0644]
simpleperf/environment.cpp
simpleperf/environment.h
simpleperf/environment_fake.cpp [new file with mode: 0644]
simpleperf/environment_test.cpp
simpleperf/event_attr.cpp
simpleperf/event_attr.h
simpleperf/event_fd.cpp
simpleperf/event_fd.h
simpleperf/event_selection_set.cpp
simpleperf/event_selection_set.h
simpleperf/event_type.cpp
simpleperf/event_type.h
simpleperf/event_type_table.h
simpleperf/generate_event_type_table.py
simpleperf/get_test_data.h [new file with mode: 0644]
simpleperf/gtest_main.cpp
simpleperf/main.cpp
simpleperf/nonlinux_support/include/asm/byteorder.h [new file with mode: 0644]
simpleperf/nonlinux_support/include/linux/ioctl.h [new file with mode: 0644]
simpleperf/nonlinux_support/include/linux/types.h [new file with mode: 0644]
simpleperf/nonlinux_support/nonlinux_support.cpp [new file with mode: 0644]
simpleperf/perf_event.h
simpleperf/perf_regs.cpp [new file with mode: 0644]
simpleperf/perf_regs.h [new file with mode: 0644]
simpleperf/read_apk.cpp [new file with mode: 0644]
simpleperf/read_apk.h [new file with mode: 0644]
simpleperf/read_apk_test.cpp [new file with mode: 0644]
simpleperf/read_elf.cpp
simpleperf/read_elf.h
simpleperf/read_elf_test.cpp [new file with mode: 0644]
simpleperf/record.cpp
simpleperf/record.h
simpleperf/record_file.cpp [deleted file]
simpleperf/record_file.h
simpleperf/record_file_format.h
simpleperf/record_file_reader.cpp [new file with mode: 0644]
simpleperf/record_file_test.cpp
simpleperf/record_file_writer.cpp [new file with mode: 0644]
simpleperf/record_test.cpp
simpleperf/report.py [new file with mode: 0644]
simpleperf/runtest/Android.build.mk [new file with mode: 0644]
simpleperf/runtest/Android.mk [new file with mode: 0644]
simpleperf/runtest/comm_change.cpp [new file with mode: 0644]
simpleperf/runtest/function_fork.cpp [new file with mode: 0644]
simpleperf/runtest/function_indirect_recursive.cpp [new file with mode: 0644]
simpleperf/runtest/function_pthread.cpp [new file with mode: 0644]
simpleperf/runtest/function_recursive.cpp [new file with mode: 0644]
simpleperf/runtest/one_function.cpp [new file with mode: 0644]
simpleperf/runtest/runtest.conf [new file with mode: 0644]
simpleperf/runtest/runtest.py [new file with mode: 0644]
simpleperf/runtest/two_functions.cpp [new file with mode: 0644]
simpleperf/sample_tree.cpp [new file with mode: 0644]
simpleperf/sample_tree.h [new file with mode: 0644]
simpleperf/sample_tree_test.cpp [new file with mode: 0644]
simpleperf/scoped_signal_handler.h [new file with mode: 0644]
simpleperf/test_util.h [new file with mode: 0644]
simpleperf/testdata/data/app/com.example.hellojni-1/base.apk [new file with mode: 0644]
simpleperf/testdata/elf [new file with mode: 0644]
simpleperf/testdata/elf_file_source.cpp [new file with mode: 0644]
simpleperf/testdata/has_embedded_native_libs_apk_perf.data [new file with mode: 0644]
simpleperf/testdata/perf.data [new file with mode: 0644]
simpleperf/testdata/perf_b.data [new file with mode: 0644]
simpleperf/testdata/perf_g_fp.data [new file with mode: 0644]
simpleperf/thread_tree.cpp [new file with mode: 0644]
simpleperf/thread_tree.h [new file with mode: 0644]
simpleperf/utils.cpp
simpleperf/utils.h
simpleperf/workload.cpp
simpleperf/workload.h
simpleperf/workload_test.cpp
slideshow/NOTICE [new file with mode: 0644]
slideshow/slideshow.cpp
sound/Android.mk
sound/NOTICE [new file with mode: 0644]
squashfs_utils/Android.mk
squashfs_utils/NOTICE [new file with mode: 0644]
squashfs_utils/mksquashfsimage.sh
squashfs_utils/squashfs_utils.c
squashfs_utils/squashfs_utils.h
su/su.c
systrace_analysis/analysis.html [new file with mode: 0644]
systrace_analysis/analyze_trace.py [new file with mode: 0755]
taskstats/taskstats.c
tests/NOTICE [moved from timeinfo/NOTICE with 100% similarity]
tests/audio/alsa/pcmtest.cpp
tests/binder/benchmarks/Android.mk
tests/binder/benchmarks/binderAddInts.cpp
tests/bionic/libc/Android.mk [deleted file]
tests/bionic/libc/README.TXT [deleted file]
tests/bionic/libc/bionic/test_cond.c [deleted file]
tests/bionic/libc/bionic/test_pthread_cond.c [deleted file]
tests/bionic/libc/common/test_pthread_mutex.c [deleted file]
tests/bionic/libc/common/test_pthread_rwlock.c [deleted file]
tests/bionic/libc/other/test_jpeg.c [deleted file]
tests/bionic/libc/other/test_zlib.c [deleted file]
tests/bionic/libc/run-test.sh [deleted file]
tests/cpueater/Android.mk
tests/crypto/Android.mk
tests/framebuffer/Android.mk
tests/fstest/recovery_test.cpp
tests/iptables/qtaguid/Android.mk
tests/kernel.config/Android.mk [new file with mode: 0644]
tests/kernel.config/AndroidTest.xml [new file with mode: 0644]
tests/kernel.config/aslr_rec_test.cpp [new file with mode: 0644]
tests/kernel.config/aslr_test.cpp [new file with mode: 0644]
tests/kernel.config/aslr_test.h [new file with mode: 0644]
tests/kernel.config/logger_test.cpp [new file with mode: 0644]
tests/kernel.config/mmc_max_speed_test.cpp [new file with mode: 0644]
tests/kernel.config/multicast_test.cpp [new file with mode: 0644]
tests/kernel.config/pstore_test.cpp [new file with mode: 0644]
tests/kernel.config/scrape_mmap_addr.cpp [new file with mode: 0644]
tests/kernel.config/sysvipc_test.cpp [new file with mode: 0644]
tests/memtest/Android.mk
tests/memtest/bandwidth.cpp
tests/memtest/bandwidth.h
tests/memtest/fptest.cpp
tests/memtest/memtest.cpp
tests/memtest/thumb.cpp
tests/net_test/anycast_test.py [new file with mode: 0755]
tests/net_test/csocket.py
tests/net_test/cstruct.py
tests/net_test/cstruct_test.py [new file with mode: 0755]
tests/net_test/forwarding_test.py [new file with mode: 0755]
tests/net_test/iproute.py
tests/net_test/multinetwork_base.py
tests/net_test/multinetwork_test.py
tests/net_test/neighbour_test.py [new file with mode: 0755]
tests/net_test/net_test.py
tests/net_test/netlink.py [new file with mode: 0644]
tests/net_test/packets.py [new file with mode: 0644]
tests/net_test/ping6_test.py
tests/net_test/run_net_test.sh
tests/net_test/sock_diag.py [new file with mode: 0755]
tests/net_test/sock_diag_test.py [new file with mode: 0755]
tests/net_test/srcaddr_selection_test.py
tests/net_test/tcp_nuke_addr_test.py [new file with mode: 0755]
tests/net_test/tcp_test.py [new file with mode: 0644]
tests/pagingtest/Android.mk
tests/pagingtest/thrashing_test.c
tests/schedtest/Android.mk
tests/tcp_nuke_addr/Android.mk [new file with mode: 0644]
tests/tcp_nuke_addr/tcp_nuke_addr_test.cpp [new file with mode: 0644]
tests/timetest/Android.mk
tests/timetest/rtc_test.cpp [new file with mode: 0644]
tests/uevents/Android.mk
tests/workloads/capture.sh
tests/workloads/chromefling.sh [new file with mode: 0755]
tests/workloads/defs.sh
tests/workloads/powerave.py [new file with mode: 0755]
tests/workloads/pwrsummary.sh [new file with mode: 0755]
tests/workloads/pwrtest.sh [new file with mode: 0755]
tests/workloads/recentfling.sh
tests/workloads/systemapps.sh
tests/workloads/youtube.sh [new file with mode: 0755]
timeinfo/timeinfo.cpp [deleted file]
verity/Android.mk
verity/BootSignature.java
verity/NOTICE [new file with mode: 0644]
verity/Utils.java
verity/build_verity_metadata.py
verity/build_verity_tree.cpp
verity/fec/Android.mk [new file with mode: 0644]
verity/fec/image.cpp [new file with mode: 0644]
verity/fec/image.h [new file with mode: 0644]
verity/fec/main.cpp [new file with mode: 0644]
verity/fec/tests/fec.py [new file with mode: 0644]
verity/verify_boot_signature.c
zram-perf/Android.mk [new file with mode: 0644]
zram-perf/zram-perf.cpp [new file with mode: 0644]

diff --git a/alloc-stress/Android.mk b/alloc-stress/Android.mk
new file mode 100644 (file)
index 0000000..06b5818
--- /dev/null
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_MODULE := alloc-stress
+LOCAL_CFLAGS += -g -Wall -Werror -std=gnu++11 -Wno-missing-field-initializers -Wno-sign-compare
+ifneq ($(ENABLE_MEM_CGROUPS),)
+    LOCAL_CFLAGS += -DENABLE_MEM_CGROUPS
+endif
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+LOCAL_SHARED_LIBRARIES := libhardware libcutils
+LOCAL_SRC_FILES := \
+    alloc-stress.cpp
+include $(BUILD_EXECUTABLE)
diff --git a/alloc-stress/alloc-stress.cpp b/alloc-stress/alloc-stress.cpp
new file mode 100644 (file)
index 0000000..726ea96
--- /dev/null
@@ -0,0 +1,227 @@
+#include <arpa/inet.h>
+#include <iostream>
+#include <chrono>
+#include <cutils/sockets.h>
+#include <hardware/gralloc.h>
+#include <vector>
+#include <tuple>
+#include <algorithm>
+#include <tuple>
+#include <numeric>
+#include <fcntl.h>
+#include <string>
+#include <fstream>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+using namespace std;
+
+#define ASSERT_TRUE(cond) \
+do { \
+    if (!(cond)) {\
+       cerr << __func__ << "( " << getpid() << "):" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \
+       exit(EXIT_FAILURE); \
+    } \
+} while (0)
+
+class Pipe {
+    int m_readFd;
+    int m_writeFd;
+    Pipe(const Pipe &) = delete;
+    Pipe& operator=(const Pipe &) = delete;
+    Pipe& operator=(const Pipe &&) = delete;
+public:
+    Pipe(int readFd, int writeFd) : m_readFd{readFd}, m_writeFd{writeFd} {
+        fcntl(m_readFd, F_SETFD, FD_CLOEXEC);
+        fcntl(m_writeFd, F_SETFD, FD_CLOEXEC);
+    }
+    Pipe(Pipe&& rval) noexcept {
+        m_readFd = rval.m_readFd;
+        m_writeFd = rval.m_writeFd;
+        rval.m_readFd = 0;
+        rval.m_writeFd = 0;
+    }
+    ~Pipe() {
+        if (m_readFd)
+            close(m_readFd);
+        if (m_writeFd)
+            close(m_writeFd);
+    }
+    void preserveOverFork(bool preserve) {
+        if (preserve) {
+            fcntl(m_readFd, F_SETFD, 0);
+            fcntl(m_writeFd, F_SETFD,0);
+        } else {
+            fcntl(m_readFd, F_SETFD, FD_CLOEXEC);
+            fcntl(m_writeFd, F_SETFD, FD_CLOEXEC);
+        }
+    }
+    int getReadFd() {
+        return m_readFd;
+    }
+    int getWriteFd() {
+        return m_writeFd;
+    }
+    void signal() {
+        bool val = true;
+        int error = write(m_writeFd, &val, sizeof(val));
+        ASSERT_TRUE(error == sizeof(val));
+    };
+    void wait() {
+        bool val = false;
+        int error = read(m_readFd, &val, sizeof(val));
+        ASSERT_TRUE(error == sizeof(val));
+    }
+    bool wait_ret_error() {
+        bool val = false;
+        int error = read(m_readFd, &val, sizeof(val));
+        return (error != 1);
+    }
+    template <typename T> void send(const T& v) {
+        int error = write(m_writeFd, &v, sizeof(T));
+        ASSERT_TRUE(error >= 0);
+    }
+    template <typename T> void recv(T& v) {
+        int error = read(m_readFd, &v, sizeof(T));
+        ASSERT_TRUE(error >= 0);
+    }
+    static Pipe makePipeFromFds(int readFd, int writeFd) {
+        return Pipe(readFd, writeFd);
+    }
+    static tuple<Pipe, Pipe> createPipePair() {
+        int a[2];
+        int b[2];
+
+        int error1 = pipe(a);
+        int error2 = pipe(b);
+        ASSERT_TRUE(error1 >= 0);
+        ASSERT_TRUE(error2 >= 0);
+
+        return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1]));
+    }
+};
+
+void createProcess(Pipe pipe, const char *exName, const char *arg)
+{
+    pipe.preserveOverFork(true);
+    pid_t pid = fork();
+    // child proc
+    if (pid == 0) {
+        char readFdStr[16];
+        char writeFdStr[16];
+        snprintf(readFdStr, sizeof(readFdStr), "%d", pipe.getReadFd());
+        snprintf(writeFdStr, sizeof(writeFdStr), "%d", pipe.getWriteFd());
+        execl(exName, exName, "--worker", arg, readFdStr, writeFdStr, 0);
+        ASSERT_TRUE(0);
+    }
+    // parent process
+    else if (pid > 0) {
+        pipe.preserveOverFork(false);
+        return;
+    }
+    else {
+        ASSERT_TRUE(0);
+    }
+}
+
+
+static void write_oomadj_to_lmkd(int oomadj) {
+    // Connect to lmkd and store our oom_adj
+    int lmk_procprio_cmd[4];
+    int sock;
+    int tries = 10;
+    while ((sock = socket_local_client("lmkd",
+                    ANDROID_SOCKET_NAMESPACE_RESERVED,
+                    SOCK_SEQPACKET)) < 0) {
+        usleep(100000);
+        if (tries-- < 0) break;
+    }
+    if (sock < 0) {
+        cout << "Failed to connect to lmkd, errno " << errno << endl;
+        exit(1);
+    }
+    lmk_procprio_cmd[0] = htonl(1);
+    lmk_procprio_cmd[1] = htonl(getpid());
+    lmk_procprio_cmd[2] = htonl(getuid());
+    lmk_procprio_cmd[3] = htonl(oomadj);
+
+    int written = write(sock, lmk_procprio_cmd, sizeof(lmk_procprio_cmd));
+    cout << "Wrote " << written << " bytes to lmkd control socket." << endl;
+}
+
+#ifdef ENABLE_MEM_CGROUPS
+static void create_memcg() {
+    char buf[256];
+    pid_t pid = getpid();
+    snprintf(buf, sizeof(buf), "/dev/memctl/apps/%u", pid);
+
+    int tasks = mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+    if (tasks < 0) {
+        cout << "Failed to create memory cgroup" << endl;
+        return;
+    }
+    snprintf(buf, sizeof(buf), "/dev/memctl/apps/%u/tasks", pid);
+    tasks = open(buf, O_WRONLY);
+    if (tasks < 0) {
+        cout << "Unable to add process to memory cgroup" << endl;
+        return;
+    }
+    snprintf(buf, sizeof(buf), "%u", pid);
+    write(tasks, buf, strlen(buf));
+    close(tasks);
+}
+#endif
+
+size_t s = 4 * (1 << 20);
+void *gptr;
+int main(int argc, char *argv[])
+{
+    if ((argc > 1) && (std::string(argv[1]) == "--worker")) {
+#ifdef ENABLE_MEM_CGROUPS
+        create_memcg();
+#endif
+        write_oomadj_to_lmkd(atoi(argv[2]));
+        Pipe p{atoi(argv[3]), atoi(argv[4])};
+
+        long long allocCount = 0;
+        while (1) {
+            p.wait();
+            char *ptr = (char*)malloc(s);
+            memset(ptr, (int)allocCount >> 10, s);
+            for (int i = 0; i < s; i+= 4096) {
+                *((long long*)&ptr[i]) = allocCount + i;
+            }
+            usleep(10 * 1000);
+            gptr = ptr;
+            //cout << "total alloc: " << allocCount / (1<<20)<< " adj: " << argv[2]<< endl;;
+            //cout << "ptr: " << (long long)(void*)ptr << endl;;
+            p.signal();
+            allocCount += s;
+        }
+    } else {
+        cout << "parent:" << argc << endl;
+
+        write_oomadj_to_lmkd(-1000);
+        for (int i = 1000; i >= 0; i -= 100) {
+            auto pipes = Pipe::createPipePair();
+            char arg[16];
+            snprintf(arg, sizeof(arg), "%d", i);
+            createProcess(std::move(std::get<1>(pipes)), argv[0], arg);
+            Pipe &p = std::get<0>(pipes);
+
+            size_t t = 0;
+            while (1) {
+                //;cout << getpid() << ":" << "parent signal" << endl;
+                p.signal();
+                if (p.wait_ret_error()) {
+                    int status;
+                    waitpid(0, &status, 0);
+                    break;
+                }
+                t += s;
+            }
+            cout << "adj: " << i << " sz: " << t / (1 << 20) << endl;
+        }
+    }
+    return 0;
+}
diff --git a/boot_control_copy/Android.mk b/boot_control_copy/Android.mk
new file mode 100644 (file)
index 0000000..3125d6e
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright 2015 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := boot_control_copy.c bootinfo.c
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers -Wno-unused-parameter
+LOCAL_C_INCLUDES := system/core/mkbootimg bootable/recovery
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_STATIC_LIBRARIES := libfs_mgr
+
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_MODULE:= bootctrl.default
+include $(BUILD_SHARED_LIBRARY)
diff --git a/boot_control_copy/NOTICE b/boot_control_copy/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/boot_control_copy/boot_control_copy.c b/boot_control_copy/boot_control_copy.c
new file mode 100644 (file)
index 0000000..644e7de
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <fs_mgr.h>
+#include <hardware/hardware.h>
+#include <hardware/boot_control.h>
+
+#include "bootinfo.h"
+
+void module_init(boot_control_module_t *module)
+{
+}
+
+unsigned module_getNumberSlots(boot_control_module_t *module)
+{
+  return 2;
+}
+
+static bool get_dev_t_for_partition(const char *name, dev_t *out_device)
+{
+  int fd;
+  struct stat statbuf;
+
+  fd = boot_info_open_partition(name, NULL, O_RDONLY);
+  if (fd == -1)
+    return false;
+  if (fstat(fd, &statbuf) != 0) {
+    fprintf(stderr, "WARNING: Error getting information about part %s: %s\n",
+            name, strerror(errno));
+    close(fd);
+    return false;
+  }
+  close(fd);
+  *out_device = statbuf.st_rdev;
+  return true;
+}
+
+unsigned module_getCurrentSlot(boot_control_module_t *module)
+{
+  struct stat statbuf;
+  dev_t system_a_dev, system_b_dev;
+
+  if (stat("/system", &statbuf) != 0) {
+    fprintf(stderr, "WARNING: Error getting information about /system: %s\n",
+            strerror(errno));
+    return 0;
+  }
+
+  if (!get_dev_t_for_partition("system_a", &system_a_dev) ||
+      !get_dev_t_for_partition("system_b", &system_b_dev))
+    return 0;
+
+  if (statbuf.st_dev == system_a_dev) {
+    return 0;
+  } else if (statbuf.st_dev == system_b_dev) {
+    return 1;
+  } else {
+    fprintf(stderr, "WARNING: Error determining current slot "
+            "(/system dev_t of %d:%d does not match a=%d:%d or b=%d:%d)\n",
+            major(statbuf.st_dev), minor(statbuf.st_dev),
+            major(system_a_dev), minor(system_a_dev),
+            major(system_b_dev), minor(system_b_dev));
+    return 0;
+  }
+}
+
+int module_markBootSuccessful(boot_control_module_t *module)
+{
+  return 0;
+}
+
+#define COPY_BUF_SIZE 1024*1024
+
+static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
+{
+  char copy_buf[COPY_BUF_SIZE];
+  size_t remaining;
+
+  remaining = num_bytes;
+  while (remaining > 0) {
+    size_t num_to_read = remaining > COPY_BUF_SIZE ? COPY_BUF_SIZE : remaining;
+    ssize_t num_read;
+    do {
+      num_read = read(src_fd, copy_buf, num_to_read);
+    } while (num_read == -1 && errno == EINTR);
+    if (num_read <= 0) {
+      fprintf(stderr, "Error reading %zd bytes from source: %s\n",
+              num_to_read, strerror(errno));
+      return false;
+    }
+    size_t num_to_write = num_read;
+    while (num_to_write > 0) {
+      size_t offset = num_read - num_to_write;
+      ssize_t num_written;
+      do {
+        num_written = write(dst_fd, copy_buf + offset, num_to_write);
+      } while (num_written == -1 && errno == EINTR);
+      if (num_written <= 0) {
+        fprintf(stderr, "Error writing %zd bytes to destination: %s\n",
+                num_to_write, strerror(errno));
+        return false;
+      }
+      num_to_write -= num_written;
+    }
+    remaining -= num_read;
+  }
+
+  return true;
+}
+
+int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
+{
+  BrilloBootInfo info;
+  int src_fd, dst_fd;
+  uint64_t src_size, dst_size;
+  char src_name[32];
+
+  if (slot >= 2)
+    return -EINVAL;
+
+  if (!boot_info_load(&info)) {
+    fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
+    boot_info_reset(&info);
+  } else {
+    if (!boot_info_validate(&info)) {
+      fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
+      boot_info_reset(&info);
+    }
+  }
+
+  info.active_slot = slot;
+  info.slot_info[slot].bootable = true;
+  snprintf(info.bootctrl_suffix,
+           sizeof(info.bootctrl_suffix),
+           "_%c", slot + 'a');
+
+  if (!boot_info_save(&info)) {
+    fprintf(stderr, "Error saving boot-info.\n");
+    return -errno;
+  }
+
+  // Finally copy the contents of boot_X into boot.
+  snprintf(src_name, sizeof(src_name), "boot_%c", slot + 'a');
+  src_fd = boot_info_open_partition(src_name, &src_size, O_RDONLY);
+  if (src_fd == -1) {
+    fprintf(stderr, "Error opening \"%s\" partition.\n", src_name);
+    return -errno;
+  }
+
+  dst_fd = boot_info_open_partition("boot", &dst_size, O_RDWR);
+  if (dst_fd == -1) {
+    fprintf(stderr, "Error opening \"boot\" partition.\n");
+    close(src_fd);
+    return -errno;
+  }
+
+  if (src_size != dst_size) {
+    fprintf(stderr,
+            "src (%" PRIu64 " bytes) and dst (%" PRIu64 " bytes) "
+            "have different sizes.\n",
+            src_size, dst_size);
+    close(src_fd);
+    close(dst_fd);
+    return -EINVAL;
+  }
+
+  if (!copy_data(src_fd, dst_fd, src_size)) {
+    close(src_fd);
+    close(dst_fd);
+    return -errno;
+  }
+
+  if (fsync(dst_fd) != 0) {
+    fprintf(stderr, "Error calling fsync on destination: %s\n",
+            strerror(errno));
+    return -errno;
+  }
+
+  close(src_fd);
+  close(dst_fd);
+  return 0;
+}
+
+int module_setSlotAsUnbootable(struct boot_control_module *module, unsigned slot)
+{
+  BrilloBootInfo info;
+
+  if (slot >= 2)
+    return -EINVAL;
+
+  if (!boot_info_load(&info)) {
+    fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
+    boot_info_reset(&info);
+  } else {
+    if (!boot_info_validate(&info)) {
+      fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
+      boot_info_reset(&info);
+    }
+  }
+
+  info.slot_info[slot].bootable = false;
+
+  if (!boot_info_save(&info)) {
+    fprintf(stderr, "Error saving boot-info.\n");
+    return -errno;
+  }
+
+  return 0;
+}
+
+int module_isSlotBootable(struct boot_control_module *module, unsigned slot)
+{
+  BrilloBootInfo info;
+
+  if (slot >= 2)
+    return -EINVAL;
+
+  if (!boot_info_load(&info)) {
+    fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
+    boot_info_reset(&info);
+  } else {
+    if (!boot_info_validate(&info)) {
+      fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
+      boot_info_reset(&info);
+    }
+  }
+
+  return info.slot_info[slot].bootable;
+}
+
+const char* module_getSuffix(boot_control_module_t *module, unsigned slot)
+{
+  static const char* suffix[2] = {"_a", "_b"};
+  if (slot >= 2)
+    return NULL;
+  return suffix[slot];
+}
+
+static struct hw_module_methods_t module_methods = {
+  .open  = NULL,
+};
+
+
+/* This boot_control HAL implementation emulates A/B by copying the
+ * contents of the boot partition of the requested slot to the boot
+ * partition. It hence works with bootloaders that are not yet aware
+ * of A/B. This code is only intended to be used for development.
+ */
+
+boot_control_module_t HAL_MODULE_INFO_SYM = {
+  .common = {
+    .tag                 = HARDWARE_MODULE_TAG,
+    .module_api_version  = BOOT_CONTROL_MODULE_API_VERSION_0_1,
+    .hal_api_version     = HARDWARE_HAL_API_VERSION,
+    .id                  = BOOT_CONTROL_HARDWARE_MODULE_ID,
+    .name                = "Copy Implementation of boot_control HAL",
+    .author              = "The Android Open Source Project",
+    .methods             = &module_methods,
+  },
+  .init                 = module_init,
+  .getNumberSlots       = module_getNumberSlots,
+  .getCurrentSlot       = module_getCurrentSlot,
+  .markBootSuccessful   = module_markBootSuccessful,
+  .setActiveBootSlot    = module_setActiveBootSlot,
+  .setSlotAsUnbootable  = module_setSlotAsUnbootable,
+  .isSlotBootable       = module_isSlotBootable,
+  .getSuffix            = module_getSuffix,
+};
diff --git a/boot_control_copy/bootinfo.c b/boot_control_copy/bootinfo.c
new file mode 100644 (file)
index 0000000..396dd81
--- /dev/null
@@ -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 <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cutils/properties.h>
+
+#include <bootloader.h>
+#include <fs_mgr.h>
+
+#include "bootinfo.h"
+
+// Open the appropriate fstab file and fallback to /fstab.device if
+// that's what's being used.
+static struct fstab *open_fstab(void)
+{
+  char propbuf[PROPERTY_VALUE_MAX];
+  char fstab_name[PROPERTY_VALUE_MAX + 32];
+  struct fstab *fstab;
+
+  property_get("ro.hardware", propbuf, "");
+  snprintf(fstab_name, sizeof(fstab_name), "/fstab.%s", propbuf);
+  fstab = fs_mgr_read_fstab(fstab_name);
+  if (fstab != NULL)
+    return fstab;
+
+  fstab = fs_mgr_read_fstab("/fstab.device");
+  return fstab;
+}
+
+int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
+{
+  char *path;
+  int fd;
+  struct fstab *fstab;
+  struct fstab_rec *record;
+
+  // We can't use fs_mgr to look up |name| because fstab doesn't list
+  // every slot partition (it uses the slotselect option to mask the
+  // suffix) and |slot| is expected to be of that form, e.g. boot_a.
+  //
+  // We can however assume that there's an entry for the /misc mount
+  // point and use that to get the device file for the misc
+  // partition. From there we'll assume that a by-name scheme is used
+  // so we can just replace the trailing "misc" by the given |name|,
+  // e.g.
+  //
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+  //
+  // If needed, it's possible to relax this assumption in the future
+  // by trawling /sys/block looking for the appropriate sibling of
+  // misc and then finding an entry in /dev matching the sysfs entry.
+
+  fstab = open_fstab();
+  if (fstab == NULL)
+    return -1;
+  record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+  if (record == NULL) {
+    fs_mgr_free_fstab(fstab);
+    return -1;
+  }
+  if (strcmp(name, "misc") == 0) {
+    path = strdup(record->blk_device);
+  } else {
+    size_t trimmed_len, name_len;
+    const char *end_slash = strrchr(record->blk_device, '/');
+    if (end_slash == NULL) {
+      fs_mgr_free_fstab(fstab);
+      return -1;
+    }
+    trimmed_len = end_slash - record->blk_device + 1;
+    name_len = strlen(name);
+    path = calloc(trimmed_len + name_len + 1, 1);
+    strncpy(path, record->blk_device, trimmed_len);
+    strncpy(path + trimmed_len, name, name_len);
+  }
+  fs_mgr_free_fstab(fstab);
+
+  fd = open(path, flags);
+  free(path);
+
+  // If we successfully opened the device, get size if requested.
+  if (fd != -1 && out_size != NULL) {
+    if (ioctl(fd, BLKGETSIZE64, out_size) != 0) {
+      close(fd);
+      return -1;
+    }
+  }
+
+  return fd;
+}
+
+// As per struct bootloader_message which is defined in
+// bootable/recovery/bootloader.h we can use the 32 bytes in the
+// bootctrl_suffix field provided that they start with the active slot
+// suffix terminated by NUL. It just so happens that BrilloBootInfo is
+// laid out this way.
+#define BOOTINFO_OFFSET offsetof(struct bootloader_message, slot_suffix)
+
+bool boot_info_load(BrilloBootInfo *out_info)
+{
+  int fd;
+
+  memset(out_info, '\0', sizeof(BrilloBootInfo));
+
+  fd = boot_info_open_partition("misc", NULL, O_RDONLY);
+  if (fd == -1)
+    return false;
+  if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
+    close(fd);
+    return false;
+  }
+  ssize_t num_read;
+  do {
+    num_read = read(fd, (void*) out_info, sizeof(BrilloBootInfo));
+  } while (num_read == -1 && errno == EINTR);
+  close(fd);
+  if (num_read != sizeof(BrilloBootInfo))
+    return false;
+  return true;
+}
+
+bool boot_info_save(BrilloBootInfo *info)
+{
+  int fd;
+
+  fd = boot_info_open_partition("misc", NULL, O_RDWR);
+  if (fd == -1)
+    return false;
+  if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
+    close(fd);
+    return false;
+  }
+  ssize_t num_written;
+  do {
+    num_written = write(fd, (void*) info, sizeof(BrilloBootInfo));
+  } while (num_written == -1 && errno == EINTR);
+  close(fd);
+  if (num_written != sizeof(BrilloBootInfo))
+    return false;
+  return true;
+}
+
+bool boot_info_validate(BrilloBootInfo* info)
+{
+  if (info->magic[0] != 'B' ||
+      info->magic[1] != 'C' ||
+      info->magic[2] != 'c')
+    return false;
+  if (info->active_slot >= 2)
+    return false;
+  return true;
+}
+
+void boot_info_reset(BrilloBootInfo* info)
+{
+  memset(info, '\0', sizeof(BrilloBootInfo));
+  info->magic[0] = 'B';
+  info->magic[1] = 'C';
+  info->magic[2] = 'c';
+}
diff --git a/boot_control_copy/bootinfo.h b/boot_control_copy/bootinfo.h
new file mode 100644 (file)
index 0000000..4b36b2c
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 BOOTINFO_H_
+#define BOOTINFO_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct BrilloSlotInfo {
+  uint8_t bootable : 1;
+  uint8_t reserved[3];
+} BrilloSlotInfo;
+
+typedef struct BrilloBootInfo {
+  // Used by fs_mgr. Must be NUL terminated.
+  char bootctrl_suffix[4];
+
+  // Magic for identification - must be 'B', 'C', 'c' (short for
+  // "boot_control copy" implementation).
+  uint8_t magic[3];
+
+  // Version of BrilloBootInfo struct, must be 0 or larger.
+  uint8_t version;
+
+  // Currently active slot.
+  uint8_t active_slot;
+
+  // Information about each slot.
+  BrilloSlotInfo slot_info[2];
+
+  uint8_t reserved[15];
+} BrilloBootInfo;
+
+// Loading and saving BrillBootInfo instances.
+bool boot_info_load(BrilloBootInfo *out_info);
+bool boot_info_save(BrilloBootInfo *info);
+
+// Returns non-zero if valid.
+bool boot_info_validate(BrilloBootInfo* info);
+void boot_info_reset(BrilloBootInfo* info);
+
+// Opens partition by |name|, e.g. "misc" or "boot_a" with |flags|
+// (e.g. O_RDONLY or O_RDWR) passed directly to open(2). Returns fd on
+// success and -1 on error.
+int boot_info_open_partition(const char *name, uint64_t *out_size, int flags);
+
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
+_Static_assert(sizeof(BrilloBootInfo) == 32, "BrilloBootInfo has wrong size");
+#endif
+
+#endif  // BOOTINFO_H
diff --git a/bootctl/Android.mk b/bootctl/Android.mk
new file mode 100644 (file)
index 0000000..f0f5a06
--- /dev/null
@@ -0,0 +1,12 @@
+# Copyright 2015 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := bootctl.c
+LOCAL_SHARED_LIBRARIES := libhardware
+LOCAL_MODULE := bootctl
+LOCAL_C_INCLUDES = hardware/libhardware/include
+LOCAL_CFLAGS := -Wno-unused-parameter
+
+include $(BUILD_EXECUTABLE)
diff --git a/bootctl/NOTICE b/bootctl/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/bootctl/bootctl.c b/bootctl/bootctl.c
new file mode 100644 (file)
index 0000000..ee8378b
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sysexits.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hardware/hardware.h>
+#include <hardware/boot_control.h>
+
+static void usage(FILE* where, int argc, char* argv[])
+{
+    fprintf(where,
+            "%s - command-line wrapper for the boot_control HAL.\n"
+            "\n"
+            "Usage:\n"
+            "  %s COMMAND\n"
+            "\n"
+            "Commands:\n"
+            "  %s hal-info                       - Show info about boot_control HAL used.\n"
+            "  %s get-number-slots               - Prints number of slots.\n"
+            "  %s get-current-slot               - Prints currently running SLOT.\n"
+            "  %s mark-boot-successful           - Mark current slot as GOOD.\n"
+            "  %s set-active-boot-slot SLOT      - On next boot, load and execute SLOT.\n"
+            "  %s set-slot-as-unbootable SLOT    - Mark SLOT as invalid.\n"
+            "  %s is-slot-bootable SLOT          - Returns 0 only if SLOT is bootable.\n"
+            "  %s is-slot-marked-successful SLOT - Returns 0 only if SLOT is marked GOOD.\n"
+            "  %s get-suffix SLOT                - Prints suffix for SLOT.\n"
+            "\n"
+            "SLOT parameter is the zero-based slot-number.\n",
+            argv[0], argv[0], argv[0], argv[0], argv[0], argv[0],
+            argv[0], argv[0], argv[0], argv[0], argv[0]);
+}
+
+static int do_hal_info(const hw_module_t *hw_module)
+{
+    fprintf(stdout,
+            "HAL name:            %s\n"
+            "HAL author:          %s\n"
+            "HAL module version:  %d.%d\n",
+            hw_module->name,
+            hw_module->author,
+            hw_module->module_api_version>>8,
+            hw_module->module_api_version&0xff);
+    return EX_OK;
+}
+
+static int do_get_number_slots(boot_control_module_t *module)
+{
+    int num_slots = module->getNumberSlots(module);
+    fprintf(stdout, "%d\n", num_slots);
+    return EX_OK;
+}
+
+static int do_get_current_slot(boot_control_module_t *module)
+{
+    int cur_slot = module->getCurrentSlot(module);
+    fprintf(stdout, "%d\n", cur_slot);
+    return EX_OK;
+}
+
+static int do_mark_boot_successful(boot_control_module_t *module)
+{
+    int ret = module->markBootSuccessful(module);
+    if (ret == 0)
+        return EX_OK;
+    fprintf(stderr, "Error marking as having booted successfully: %s\n",
+            strerror(-ret));
+    return EX_SOFTWARE;
+}
+
+static int do_set_active_boot_slot(boot_control_module_t *module,
+                                   int slot_number)
+{
+    int ret = module->setActiveBootSlot(module, slot_number);
+    if (ret == 0)
+        return EX_OK;
+    fprintf(stderr, "Error setting active boot slot: %s\n", strerror(-ret));
+    return EX_SOFTWARE;
+}
+
+static int do_set_slot_as_unbootable(boot_control_module_t *module,
+                                     int slot_number)
+{
+    int ret = module->setSlotAsUnbootable(module, slot_number);
+    if (ret == 0)
+        return EX_OK;
+    fprintf(stderr, "Error setting slot as unbootable: %s\n", strerror(-ret));
+    return EX_SOFTWARE;
+}
+
+
+static int do_is_slot_bootable(boot_control_module_t *module, int slot_number)
+{
+    int ret = module->isSlotBootable(module, slot_number);
+    if (ret == 0) {
+        return EX_SOFTWARE;
+    } else if (ret < 0) {
+        fprintf(stderr, "Error calling isSlotBootable(): %s\n",
+                strerror(-ret));
+        return EX_SOFTWARE;
+    }
+    return EX_OK;
+}
+
+
+static int do_get_suffix(boot_control_module_t *module, int slot_number)
+{
+    const char* suffix = module->getSuffix(module, slot_number);
+    fprintf(stdout, "%s\n", suffix);
+    return EX_OK;
+}
+
+static int do_is_slot_marked_successful(boot_control_module_t *module,
+                                        int slot_number)
+{
+    if (module->isSlotMarkedSuccessful == NULL) {
+        fprintf(stderr, "isSlotMarkedSuccessful() is not implemented by HAL.\n");
+        return EX_UNAVAILABLE;
+    }
+    int ret = module->isSlotMarkedSuccessful(module, slot_number);
+    if (ret == 0) {
+        return EX_SOFTWARE;
+    } else if (ret < 0) {
+        fprintf(stderr, "Error calling isSlotMarkedSuccessful(): %s\n",
+                strerror(-ret));
+        return EX_SOFTWARE;
+    }
+    return EX_OK;
+}
+
+static int parse_slot(int pos, int argc, char *argv[])
+{
+    if (pos > argc - 1) {
+        usage(stderr, argc, argv);
+        exit(EX_USAGE);
+        return -1;
+    }
+    errno = 0;
+    long int ret = strtol(argv[pos], NULL, 10);
+    if (errno != 0 || ret > INT_MAX || ret < 0) {
+        usage(stderr, argc, argv);
+        exit(EX_USAGE);
+        return -1;
+    }
+    return (int)ret;
+}
+
+int main(int argc, char *argv[])
+{
+    const hw_module_t *hw_module;
+    boot_control_module_t *module;
+    int ret;
+
+    if (argc < 2) {
+        usage(stderr, argc, argv);
+        return EX_USAGE;
+    }
+
+    ret = hw_get_module("bootctrl", &hw_module);
+    if (ret != 0) {
+        fprintf(stderr, "Error getting bootctrl module.\n");
+        return EX_SOFTWARE;
+    }
+    module = (boot_control_module_t*) hw_module;
+    module->init(module);
+
+    if (strcmp(argv[1], "hal-info") == 0) {
+        return do_hal_info(hw_module);
+    } else if (strcmp(argv[1], "get-number-slots") == 0) {
+        return do_get_number_slots(module);
+    } else if (strcmp(argv[1], "get-current-slot") == 0) {
+        return do_get_current_slot(module);
+    } else if (strcmp(argv[1], "mark-boot-successful") == 0) {
+        return do_mark_boot_successful(module);
+    } else if (strcmp(argv[1], "set-active-boot-slot") == 0) {
+        return do_set_active_boot_slot(module, parse_slot(2, argc, argv));
+    } else if (strcmp(argv[1], "set-slot-as-unbootable") == 0) {
+        return do_set_slot_as_unbootable(module, parse_slot(2, argc, argv));
+    } else if (strcmp(argv[1], "is-slot-bootable") == 0) {
+        return do_is_slot_bootable(module, parse_slot(2, argc, argv));
+    } else if (strcmp(argv[1], "get-suffix") == 0) {
+        return do_get_suffix(module, parse_slot(2, argc, argv));
+    } else if (strcmp(argv[1], "is-slot-marked-successful") == 0) {
+        return do_is_slot_marked_successful(module, parse_slot(2, argc, argv));
+    } else {
+        usage(stderr, argc, argv);
+        return EX_USAGE;
+    }
+
+    return 0;
+}
diff --git a/brillo_config/Android.mk b/brillo_config/Android.mk
new file mode 100644 (file)
index 0000000..8946e78
--- /dev/null
@@ -0,0 +1,73 @@
+#
+# 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.
+#
+
+# Build time settings used by system services
+# ========================================================
+ifdef OSRELEASED_DIRECTORY
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := product_id
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/$(OSRELEASED_DIRECTORY)
+include $(BUILD_SYSTEM)/base_rules.mk
+
+# Attempt to populate the product id from a file in the product path.
+LOADED_BRILLO_PRODUCT_ID := $(call cfgtree-get-if-exists,brillo/product_id)
+
+# We don't really have a default value for the product id as the backend
+# interaction will not work if this is not set correctly.
+$(LOCAL_BUILT_MODULE): BRILLO_PRODUCT_ID ?= "$(LOADED_BRILLO_PRODUCT_ID)"
+$(LOCAL_BUILT_MODULE):
+       $(hide)mkdir -p $(dir $@)
+       echo $(BRILLO_PRODUCT_ID) > $@
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := product_version
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/$(OSRELEASED_DIRECTORY)
+include $(BUILD_SYSTEM)/base_rules.mk
+
+
+# The version is set to 0.0.0 if the user did not set the actual version and
+# a version cannot be loaded from the product cfgtree.
+# This allows us to have a valid version number while being easy to filter.
+ifeq ($(BRILLO_PRODUCT_VERSION),)
+# Load from file first
+BRILLO_PRODUCT_VERSION := $(call cfgtree-get-if-exists,brillo/product_version)
+endif
+# If the version is still empty, override it with 0.0.0
+ifeq ($(BRILLO_PRODUCT_VERSION),)
+BRILLO_PRODUCT_VERSION := "0.0.0"
+endif
+ifeq ($(shell echo $(BRILLO_PRODUCT_VERSION) | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$$'),)
+$(error Invalid BRILLO_PRODUCT_VERSION "$(BRILLO_PRODUCT_VERSION)", must be \
+  three numbers separated by dots. Example: "1.2.0")
+endif
+
+# Append BUILD_NUMBER if it is a number or a build timestamp otherwise.
+# We don't want to use BUILD_DATETIME_FROM_FILE as this timestamp must be
+# different at every build.
+# If you don' want this to change at every build, you can define BUILD_NUMBER in
+# your product makefile and increase it manually.
+$(LOCAL_BUILT_MODULE):
+       $(hide)mkdir -p $(dir $@)
+ifeq ($(shell echo $(BUILD_NUMBER) | grep -E '[^0-9]'),)
+       echo $(BRILLO_PRODUCT_VERSION).$(BUILD_NUMBER) > $@
+else
+       echo $(BRILLO_PRODUCT_VERSION).$(BUILD_DATETIME) > $@
+endif
+
+endif
diff --git a/cpustats/NOTICE b/cpustats/NOTICE
new file mode 100644 (file)
index 0000000..c77f135
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2012, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index 32d75b2..0042caf 100644 (file)
@@ -267,16 +267,22 @@ static void read_freq_stats(int cpu) {
 
     sprintf(filename, "/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state", cpu);
     file = fopen(filename, "r");
-    if (!file) die("Could not open %s\n", filename);
     for (i = 0; i < new_cpus[cpu].freq_count; i++) {
-        fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq,
+        if (file) {
+            fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq,
                &new_cpus[cpu].freqs[i].time);
+        } else {
+            /* The CPU has been off lined for some reason */
+            new_cpus[cpu].freqs[i].freq = old_cpus[cpu].freqs[i].freq;
+            new_cpus[cpu].freqs[i].time = old_cpus[cpu].freqs[i].time;
+        }
         if (aggregate_freq_stats) {
             new_total_cpu.freqs[i].freq = new_cpus[cpu].freqs[i].freq;
             new_total_cpu.freqs[i].time += new_cpus[cpu].freqs[i].time;
         }
     }
-    fclose(file);
+    if (file)
+        fclose(file);
 }
 
 /*
diff --git a/crypto-perf/Android.mk b/crypto-perf/Android.mk
new file mode 100644 (file)
index 0000000..291aca1
--- /dev/null
@@ -0,0 +1,13 @@
+LOCAL_PATH := $(call my-dir)
+ifeq ($(TARGET_ARCH),arm64)
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS := -O0 -march=armv8-a+crypto
+LOCAL_SRC_FILES := crypto.cpp
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := crypto
+
+include $(BUILD_EXECUTABLE)
+endif
diff --git a/crypto-perf/NOTICE b/crypto-perf/NOTICE
new file mode 100644 (file)
index 0000000..c77f135
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2012, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/crypto-perf/crypto.cpp b/crypto-perf/crypto.cpp
new file mode 100644 (file)
index 0000000..2cd2709
--- /dev/null
@@ -0,0 +1,170 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <ctype.h>
+#define USEC_PER_SEC 1000000ULL
+#define MAX_COUNT 1000000000ULL
+#define NUM_INSTS_GARBAGE 18
+
+// Contains information about benchmark options.
+typedef struct {
+    int cpu_to_lock;
+    int locked_freq;
+} command_data_t;
+
+void usage() {
+    printf("--------------------------------------------------------------------------------\n");
+    printf("Usage:");
+    printf("   crypto [--cpu_to_lock CPU] [--locked_freq FREQ_IN_KHZ]\n\n");
+    printf("!!!!!!Lock the desired core to a desired frequency before invoking this benchmark.\n");
+    printf(
+          "Hint: Set scaling_max_freq=scaling_min_freq=FREQ_IN_KHZ. FREQ_IN_KHZ "
+          "can be obtained from scaling_available_freq\n");
+    printf("--------------------------------------------------------------------------------\n");
+}
+
+int processOptions(int argc, char **argv, command_data_t *cmd_data) {
+    // Initialize the command_flags.
+    cmd_data->cpu_to_lock = 0;
+    cmd_data->locked_freq = 1;
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            int *save_value = NULL;
+            if (strcmp(argv[i], "--cpu_to_lock") == 0) {
+                save_value = &cmd_data->cpu_to_lock;
+           } else if (strcmp(argv[i], "--locked_freq") == 0) {
+                save_value = &cmd_data->locked_freq;
+            } else {
+                printf("Unknown option %s\n", argv[i]);
+                return -1;
+            }
+            if (save_value) {
+                // Checking both characters without a strlen() call should be
+                // safe since as long as the argument exists, one character will
+                // be present (\0). And if the first character is '-', then
+                // there will always be a second character (\0 again).
+                if (i == argc - 1 ||
+                    (argv[i + 1][0] == '-' && !isdigit(argv[i + 1][1]))) {
+                    printf("The option %s requires one argument.\n", argv[i]);
+                    return -1;
+                }
+                *save_value = (int)strtol(argv[++i], NULL, 0);
+            }
+       }
+    }
+    return 0;
+}
+/* Performs encryption on garbage values. In Cortex-A57 r0p1 and later
+ * revisions, pairs of dependent AESE/AESMC and AESD/AESIMC instructions are
+ * higher performance when adjacent, and in the described order below. */
+void garbage_encrypt() {
+    __asm__ __volatile__(
+       "aese  v0.16b, v4.16b ;"
+        "aesmc v0.16b, v0.16b ;"
+        "aese  v1.16b, v4.16b ;"
+        "aesmc v1.16b, v1.16b ;"
+        "aese  v2.16b, v4.16b ;"
+        "aesmc v2.16b, v2.16b ;"
+        "aese  v0.16b, v5.16b ;"
+        "aesmc v0.16b, v0.16b ;"
+        "aese  v1.16b, v5.16b ;"
+        "aesmc v1.16b, v1.16b ;"
+        "aese  v2.16b, v5.16b ;"
+        "aesmc v2.16b, v2.16b ;"
+        "aese  v0.16b, v6.16b ;"
+        "aesmc v0.16b, v0.16b ;"
+        "aese  v1.16b, v6.16b ;"
+        "aesmc v1.16b, v1.16b ;"
+        "aese  v2.16b, v6.16b ;"
+        "aesmc v2.16b, v2.16b ;");
+}
+
+void garbage_decrypt() {
+    __asm__ __volatile__(
+       "aesd  v0.16b, v4.16b ;"
+        "aesimc        v0.16b, v0.16b ;"
+        "aesd  v1.16b, v4.16b ;"
+        "aesimc        v1.16b, v1.16b ;"
+        "aesd  v2.16b, v4.16b ;"
+        "aesimc        v2.16b, v2.16b ;"
+        "aesd  v0.16b, v5.16b ;"
+        "aesimc        v0.16b, v0.16b ;"
+        "aesd  v1.16b, v5.16b ;"
+        "aesimc        v1.16b, v1.16b ;"
+        "aesd  v2.16b, v5.16b ;"
+        "aesimc        v2.16b, v2.16b ;"
+        "aesd  v0.16b, v6.16b ;"
+        "aesimc        v0.16b, v0.16b ;"
+        "aesd  v1.16b, v6.16b ;"
+        "aesimc        v1.16b, v1.16b ;"
+        "aesd  v2.16b, v6.16b ;"
+        "aesimc        v2.16b, v2.16b ;");
+}
+
+
+int main(int argc, char **argv) {
+    usage();
+    command_data_t cmd_data;
+
+    if(processOptions(argc, argv, &cmd_data) == -1) {
+        usage();
+        return -1;
+    }
+    unsigned long long count = 0;
+    struct timeval begin_time, end_time, elapsed_time;
+    cpu_set_t cpuset;
+    CPU_ZERO(&cpuset);
+    CPU_SET(cmd_data.cpu_to_lock, &cpuset);
+    if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
+       perror("sched_setaffinity failed");
+       return false;
+    }
+    gettimeofday(&begin_time, NULL);
+    while (count < MAX_COUNT) {
+      garbage_encrypt();
+      count++;
+    }
+    gettimeofday(&end_time, NULL);
+    timersub(&end_time, &begin_time, &elapsed_time);
+    fprintf(stderr, "encrypt: %llu us\n",
+            elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
+    fprintf(stderr, "encrypt instructions: %llu\n",
+            MAX_COUNT * NUM_INSTS_GARBAGE);
+    fprintf(stderr, "encrypt instructions per second: %f\n",
+            (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+                (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
+    if (cmd_data.locked_freq != 0) {
+       fprintf(stderr, "encrypt instructions per cycle: %f\n",
+               (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+               ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) *
+                1000 * cmd_data.locked_freq));
+    }
+    printf("--------------------------------------------------------------------------------\n");
+
+    count = 0;
+    gettimeofday(&begin_time, NULL);
+    while (count < MAX_COUNT) {
+      garbage_decrypt();
+      count++;
+    }
+    gettimeofday(&end_time, NULL);
+    timersub(&end_time, &begin_time, &elapsed_time);
+    fprintf(stderr, "decrypt: %llu us\n",
+            elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
+    fprintf(stderr, "decrypt instructions: %llu\n",
+            MAX_COUNT * NUM_INSTS_GARBAGE);
+    fprintf(stderr, "decrypt instructions per second: %f\n",
+            (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+                (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
+    if (cmd_data.locked_freq != 0) {
+       fprintf(stderr, "decrypt instructions per cycle: %f\n",
+               (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
+               ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) *
+                1000 * cmd_data.locked_freq));
+    }
+    return 0;
+}
index 31a4b71..a8362b2 100644 (file)
@@ -22,31 +22,39 @@ libext4_utils_src_files := \
 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
-ifneq ($(HOST_OS),windows)
-  LOCAL_STATIC_LIBRARIES += libselinux
-endif
+LOCAL_STATIC_LIBRARIES_darwin += libselinux
+LOCAL_STATIC_LIBRARIES_linux += libselinux
+LOCAL_MODULE_HOST_OS := darwin linux windows
 include $(BUILD_HOST_STATIC_LIBRARY)
 
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := make_ext4fs_main.c canned_fs_config.c
+LOCAL_SRC_FILES := make_ext4fs_main.c
 LOCAL_MODULE := make_ext4fs
 LOCAL_SHARED_LIBRARIES += libcutils
 LOCAL_STATIC_LIBRARIES += \
     libext4_utils_host \
     libsparse_host \
     libz
-ifeq ($(HOST_OS),windows)
-  LOCAL_LDLIBS += -lws2_32
-else
-  LOCAL_SHARED_LIBRARIES += libselinux
-  LOCAL_CFLAGS := -DHOST
-endif
+LOCAL_LDLIBS_windows += -lws2_32
+LOCAL_SHARED_LIBRARIES_darwin += libselinux
+LOCAL_SHARED_LIBRARIES_linux += libselinux
+LOCAL_CFLAGS_darwin := -DHOST
+LOCAL_CFLAGS_linux := -DHOST
 include $(BUILD_HOST_EXECUTABLE)
 
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := blk_alloc_to_base_fs.c
+LOCAL_MODULE := blk_alloc_to_base_fs
+LOCAL_SHARED_LIBRARIES += libcutils
+LOCAL_CFLAGS_darwin := -DHOST
+LOCAL_CFLAGS_linux := -DHOST
+include $(BUILD_HOST_EXECUTABLE)
 
 #
 # -- All host/targets excluding windows
@@ -54,8 +62,7 @@ include $(BUILD_HOST_EXECUTABLE)
 
 libext4_utils_src_files += \
     key_control.cpp \
-    ext4_crypt.cpp \
-    unencrypted_properties.cpp
+    ext4_crypt.cpp
 
 ifneq ($(HOST_OS),windows)
 
@@ -63,7 +70,10 @@ include $(CLEAR_VARS)
 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 := \
+    libbase \
     libcutils \
     libext2_uuid \
     libselinux \
@@ -77,13 +87,19 @@ include $(CLEAR_VARS)
 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
+    libbase \
+    liblogwrap \
+    libsparse_static \
+    libselinux \
+    libbase
 include $(BUILD_STATIC_LIBRARY)
 
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := make_ext4fs_main.c canned_fs_config.c
+LOCAL_SRC_FILES := make_ext4fs_main.c
 LOCAL_MODULE := make_ext4fs
 LOCAL_SHARED_LIBRARIES := \
     libcutils \
index cca3dc1..497f580 100644 (file)
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
 #include <stdio.h>
 #include <stdlib.h>
 
-struct region {
-       u32 block;
-       u32 len;
-       int bg;
-       struct region *next;
-       struct region *prev;
-};
-
-struct block_group_info {
-       u32 first_block;
-       int header_blocks;
-       int data_blocks_used;
-       int has_superblock;
-       u8 *bitmaps;
-       u8 *block_bitmap;
-       u8 *inode_bitmap;
-       u8 *inode_table;
-       u32 free_blocks;
-       u32 first_free_block;
-       u32 free_inodes;
-       u32 first_free_inode;
-       u16 flags;
-       u16 used_dirs;
-};
-
 struct xattr_list_element {
        struct ext4_inode *inode;
        struct ext4_xattr_header *header;
@@ -106,7 +81,7 @@ static void region_list_remove(struct region_list *list, struct region *reg)
        reg->prev = NULL;
 }
 
-static void region_list_append(struct region_list *list, struct region *reg)
+void region_list_append(struct region_list *list, struct region *reg)
 {
        if (list->first == NULL) {
                list->first = reg;
@@ -122,6 +97,20 @@ static void region_list_append(struct region_list *list, struct region *reg)
        reg->next = NULL;
 }
 
+void region_list_merge(struct region_list *list1, struct region_list *list2)
+{
+       if (list1->first == NULL) {
+               list1->first = list2->first;
+               list1->last = list2->last;
+               list1->iter = list2->first;
+               list1->partial_iter = 0;
+               list1->first->prev = NULL;
+       } else {
+               list1->last->next = list2->first;
+               list2->first->prev = list1->last;
+               list1->last = list2->last;
+       }
+}
 #if 0
 static void dump_starting_from(struct region *reg)
 {
@@ -141,15 +130,17 @@ static void dump_region_lists(struct block_allocation *alloc) {
 }
 #endif
 
-void print_blocks(FILE* f, struct block_allocation *alloc)
+void print_blocks(FILE* f, struct block_allocation *alloc, char separator)
 {
        struct region *reg;
+       fputc(' ', f);
        for (reg = alloc->list.first; reg; reg = reg->next) {
                if (reg->len == 1) {
-                       fprintf(f, " %d", reg->block);
+                       fprintf(f, "%d", reg->block);
                } else {
-                       fprintf(f, " %d-%d", reg->block, reg->block + reg->len - 1);
+                       fprintf(f, "%d-%d", reg->block, reg->block + reg->len - 1);
                }
+               fputc(separator, f);
        }
        fputc('\n', f);
 }
@@ -205,50 +196,43 @@ static int bitmap_set_8_bits(u8 *bitmap, u32 bit)
 
 /* Marks a the first num_blocks blocks in a block group as used, and accounts
  for them in the block group free block info. */
-static int reserve_blocks(struct block_group_info *bg, u32 start, u32 num)
+static int reserve_blocks(struct block_group_info *bg, u32 bg_num, u32 start, u32 num)
 {
        unsigned int i = 0;
 
        u32 block = start;
-       if (num > bg->free_blocks)
-               return -1;
-
        for (i = 0; i < num && block % 8 != 0; i++, block++) {
                if (bitmap_set_bit(bg->block_bitmap, block)) {
-                       error("attempted to reserve already reserved block");
+                       error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
                        return -1;
                }
        }
 
        for (; i + 8 <= (num & ~7); i += 8, block += 8) {
                if (bitmap_set_8_bits(bg->block_bitmap, block)) {
-                       error("attempted to reserve already reserved block");
+                       error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
                        return -1;
                }
        }
 
        for (; i < num; i++, block++) {
                if (bitmap_set_bit(bg->block_bitmap, block)) {
-                       error("attempted to reserve already reserved block");
+                       error("attempted to reserve already reserved block %d in block group %d", block, bg_num);
                        return -1;
                }
        }
 
        bg->free_blocks -= num;
-       if (start == bg->first_free_block)
-               bg->first_free_block = start + num;
 
        return 0;
 }
 
-static void free_blocks(struct block_group_info *bg, u32 num_blocks)
+static void free_blocks(struct block_group_info *bg, u32 block, u32 num_blocks)
 {
        unsigned int i;
-       u32 block = bg->first_free_block - 1;
        for (i = 0; i < num_blocks; i++, block--)
                bg->block_bitmap[block / 8] &= ~(1 << (block % 8));
        bg->free_blocks += num_blocks;
-       bg->first_free_block -= num_blocks;
 }
 
 /* Reduces an existing allocation by len blocks by return the last blocks
@@ -258,14 +242,15 @@ void reduce_allocation(struct block_allocation *alloc, u32 len)
 {
        while (len) {
                struct region *last_reg = alloc->list.last;
+               struct block_group_info *bg = &aux_info.bgs[last_reg->bg];
 
                if (last_reg->len > len) {
-                       free_blocks(&aux_info.bgs[last_reg->bg], len);
+                       free_blocks(bg, last_reg->block + last_reg->len - bg->first_block - 1, len);
                        last_reg->len -= len;
                        len = 0;
                } else {
                        struct region *reg = alloc->list.last->prev;
-                       free_blocks(&aux_info.bgs[last_reg->bg], last_reg->len);
+                       free_blocks(bg, last_reg->block + last_reg->len - bg->first_block - 1, last_reg->len);
                        len -= last_reg->len;
                        if (reg) {
                                reg->next = NULL;
@@ -304,18 +289,28 @@ static void init_bg(struct block_group_info *bg, unsigned int i)
 
        bg->data_blocks_used = 0;
        bg->free_blocks = info.blocks_per_group;
-       bg->first_free_block = 0;
        bg->free_inodes = info.inodes_per_group;
        bg->first_free_inode = 1;
        bg->flags = EXT4_BG_INODE_UNINIT;
 
-       if (reserve_blocks(bg, bg->first_free_block, bg->header_blocks) < 0)
+       bg->chunk_count = 0;
+       bg->max_chunk_count = 1;
+       bg->chunks = (struct region*) calloc(bg->max_chunk_count, sizeof(struct region));
+
+       if (reserve_blocks(bg, i, 0, bg->header_blocks) < 0)
                error("failed to reserve %u blocks in block group %u\n", bg->header_blocks, i);
+       // Add empty starting delimiter chunk
+       reserve_bg_chunk(i, bg->header_blocks, 0);
 
        if (bg->first_block + info.blocks_per_group > aux_info.len_blocks) {
                u32 overrun = bg->first_block + info.blocks_per_group - aux_info.len_blocks;
-               reserve_blocks(bg, info.blocks_per_group - overrun, overrun);
+               reserve_blocks(bg, i, info.blocks_per_group - overrun, overrun);
+               // Add empty ending delimiter chunk
+               reserve_bg_chunk(i, info.blocks_per_group - overrun, 0);
+       } else {
+               reserve_bg_chunk(i, info.blocks_per_group - 1, 0);
        }
+
 }
 
 void block_allocator_init()
@@ -341,73 +336,80 @@ void block_allocator_free()
        free(aux_info.bgs);
 }
 
-static u32 ext4_allocate_blocks_from_block_group(u32 len, int bg_num)
-{
-       if (get_free_blocks(bg_num) < len)
-               return EXT4_ALLOCATE_FAILED;
-
-       u32 block = aux_info.bgs[bg_num].first_free_block;
-       struct block_group_info *bg = &aux_info.bgs[bg_num];
-       if (reserve_blocks(bg, bg->first_free_block, len) < 0) {
-               error("failed to reserve %u blocks in block group %u\n", len, bg_num);
-               return EXT4_ALLOCATE_FAILED;
-       }
-
-       aux_info.bgs[bg_num].data_blocks_used += len;
-
-       return bg->first_block + block;
-}
-
 /* Allocate a single block and return its block number */
 u32 allocate_block()
 {
-       unsigned int i;
-       for (i = 0; i < aux_info.groups; i++) {
-               u32 block = ext4_allocate_blocks_from_block_group(1, i);
-
-               if (block != EXT4_ALLOCATE_FAILED)
-                       return block;
+       u32 block;
+       struct block_allocation *blk_alloc = allocate_blocks(1);
+       if (!blk_alloc) {
+               return EXT4_ALLOCATE_FAILED;
        }
-
-       return EXT4_ALLOCATE_FAILED;
+       block = blk_alloc->list.first->block;
+       free_alloc(blk_alloc);
+       return block;
 }
 
 static struct region *ext4_allocate_best_fit_partial(u32 len)
 {
-       unsigned int i;
-       unsigned int found_bg = 0;
-       u32 found_bg_len = 0;
+       unsigned int i, j;
+       unsigned int found_bg = 0, found_prev_chunk = 0, found_block = 0;
+       u32 found_allocate_len = 0;
+       bool minimize = false;
+       struct block_group_info *bgs = aux_info.bgs;
+       struct region *reg;
 
        for (i = 0; i < aux_info.groups; i++) {
-               u32 bg_len = aux_info.bgs[i].free_blocks;
-
-               if ((len <= bg_len && (found_bg_len == 0 || bg_len < found_bg_len)) ||
-                   (len > found_bg_len && bg_len > found_bg_len)) {
-                       found_bg = i;
-                       found_bg_len = bg_len;
+               for (j = 1; j < bgs[i].chunk_count; j++) {
+                       u32 hole_start, hole_size;
+                       hole_start = bgs[i].chunks[j-1].block + bgs[i].chunks[j-1].len;
+                       hole_size =  bgs[i].chunks[j].block - hole_start;
+                       if (hole_size == len) {
+                               // Perfect fit i.e. right between 2 chunks no need to keep searching
+                               found_bg = i;
+                               found_prev_chunk = j - 1;
+                               found_block = hole_start;
+                               found_allocate_len = hole_size;
+                               goto done;
+                       } else if (hole_size > len && (found_allocate_len == 0 || (found_allocate_len > hole_size))) {
+                               found_bg = i;
+                               found_prev_chunk = j - 1;
+                               found_block = hole_start;
+                               found_allocate_len = hole_size;
+                               minimize = true;
+                       } else if (!minimize) {
+                               if (found_allocate_len < hole_size) {
+                                       found_bg = i;
+                                       found_prev_chunk = j - 1;
+                                       found_block = hole_start;
+                                       found_allocate_len = hole_size;
+                               }
+                       }
                }
        }
 
-       if (found_bg_len) {
-               u32 allocate_len = min(len, found_bg_len);
-               struct region *reg;
-               u32 block = ext4_allocate_blocks_from_block_group(allocate_len, found_bg);
-               if (block == EXT4_ALLOCATE_FAILED) {
-                       error("failed to allocate %d blocks in block group %d", allocate_len, found_bg);
-                       return NULL;
-               }
-               reg = malloc(sizeof(struct region));
-               reg->block = block;
-               reg->len = allocate_len;
-               reg->next = NULL;
-               reg->prev = NULL;
-               reg->bg = found_bg;
-               return reg;
-       } else {
+       if (found_allocate_len == 0) {
                error("failed to allocate %u blocks, out of space?", len);
+               return NULL;
        }
-
-       return NULL;
+       if (found_allocate_len > len) found_allocate_len = len;
+done:
+       // reclaim allocated space in chunk
+       bgs[found_bg].chunks[found_prev_chunk].len += found_allocate_len;
+       if (reserve_blocks(&bgs[found_bg],
+                               found_bg,
+                               found_block,
+                               found_allocate_len) < 0) {
+               error("failed to reserve %u blocks in block group %u\n", found_allocate_len, found_bg);
+               return NULL;
+       }
+       bgs[found_bg].data_blocks_used += found_allocate_len;
+       reg = malloc(sizeof(struct region));
+       reg->block = found_block + bgs[found_bg].first_block;
+       reg->len = found_allocate_len;
+       reg->next = NULL;
+       reg->prev = NULL;
+       reg->bg = found_bg;
+       return reg;
 }
 
 static struct region *ext4_allocate_best_fit(u32 len)
@@ -439,9 +441,9 @@ static struct region *ext4_allocate_best_fit(u32 len)
 /* Allocate len blocks.  The blocks may be spread across multiple block groups,
    and are returned in a linked list of the blocks in each block group.  The
    allocation algorithm is:
-      1.  If the remaining allocation is larger than any available contiguous region,
-          allocate the largest contiguous region and loop
-      2.  Otherwise, allocate the smallest contiguous region that it fits in
+         1.  If the remaining allocation is larger than any available contiguous region,
+                 allocate the largest contiguous region and loop
+         2.  Otherwise, allocate the smallest contiguous region that it fits in
 */
 struct block_allocation *allocate_blocks(u32 len)
 {
@@ -452,6 +454,8 @@ struct block_allocation *allocate_blocks(u32 len)
 
        struct block_allocation *alloc = create_allocation();
        alloc->list.first = reg;
+       while (reg->next != NULL)
+               reg = reg->next;
        alloc->list.last = reg;
        alloc->list.iter = alloc->list.first;
        alloc->list.partial_iter = 0;
@@ -779,3 +783,35 @@ void free_alloc(struct block_allocation *alloc)
 
        free(alloc);
 }
+
+void reserve_bg_chunk(int bg, u32 start_block, u32 size) {
+       struct block_group_info *bgs = aux_info.bgs;
+       int chunk_count;
+       if (bgs[bg].chunk_count == bgs[bg].max_chunk_count) {
+               bgs[bg].max_chunk_count *= 2;
+               bgs[bg].chunks = realloc(bgs[bg].chunks, bgs[bg].max_chunk_count * sizeof(struct region));
+               if (!bgs[bg].chunks)
+                       critical_error("realloc failed");
+       }
+       chunk_count = bgs[bg].chunk_count;
+       bgs[bg].chunks[chunk_count].block = start_block;
+       bgs[bg].chunks[chunk_count].len = size;
+       bgs[bg].chunks[chunk_count].bg = bg;
+       bgs[bg].chunk_count++;
+}
+
+int reserve_blocks_for_allocation(struct block_allocation *alloc) {
+       struct region *reg;
+       struct block_group_info *bgs = aux_info.bgs;
+
+       if (!alloc) return 0;
+       reg = alloc->list.first;
+       while (reg != NULL) {
+               if (reserve_blocks(&bgs[reg->bg], reg->bg, reg->block - bgs[reg->bg].first_block, reg->len) < 0) {
+                       return -1;
+               }
+               reg = reg->next;
+       }
+       return 0;
+}
+
index 5c26792..4a733d0 100644 (file)
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
 
 #include "ext4_utils.h"
 
-struct region;
+struct region {
+       u32 block;
+       u32 len;
+       int bg;
+       struct region *next;
+       struct region *prev;
+};
 
 struct region_list {
        struct region *first;
@@ -37,6 +43,24 @@ struct block_allocation {
        struct block_allocation* next;
 };
 
+struct block_group_info {
+       u32 first_block;
+       int header_blocks;
+       int data_blocks_used;
+       int has_superblock;
+       u8 *bitmaps;
+       u8 *block_bitmap;
+       u8 *inode_bitmap;
+       u8 *inode_table;
+       u32 free_blocks;
+       u32 free_inodes;
+       u32 first_free_inode;
+       u16 flags;
+       u16 used_dirs;
+       int chunk_count;
+       int max_chunk_count;
+       struct region *chunks;
+};
 
 void block_allocator_init();
 void block_allocator_free();
@@ -69,6 +93,9 @@ void append_region(struct block_allocation *alloc,
        u32 block, u32 len, int bg);
 struct block_allocation *create_allocation();
 int append_oob_allocation(struct block_allocation *alloc, u32 len);
-void print_blocks(FILE* f, struct block_allocation *alloc);
-
+void region_list_append(struct region_list *list, struct region *reg);
+void region_list_merge(struct region_list *list1, struct region_list *list2);
+void print_blocks(FILE* f, struct block_allocation *alloc, char separator);
+void reserve_bg_chunk(int bg, u32 start_block, u32 size);
+int reserve_blocks_for_allocation(struct block_allocation *alloc);
 #endif
diff --git a/ext4_utils/blk_alloc_to_base_fs.c b/ext4_utils/blk_alloc_to_base_fs.c
new file mode 100644 (file)
index 0000000..1761fda
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_PATH 4096
+#define MAX_FILE_VERSION 100
+
+static void usage(char *filename)
+{
+    fprintf(stderr, "Usage: %s input_blk_alloc_file output_base_fs_file \n", filename);
+}
+
+int main(int argc, char **argv)
+{
+    FILE *blk_alloc_file = NULL, *base_fs_file = NULL;
+    char filename[MAX_PATH], file_version[MAX_FILE_VERSION], *spaced_allocs = NULL;
+    size_t spaced_allocs_len = 0;
+
+    if (argc != 3) {
+        usage(argv[0]);
+        exit(EXIT_FAILURE);
+    }
+    blk_alloc_file = fopen(argv[1], "r");
+    if (blk_alloc_file == NULL) {
+        fprintf(stderr, "failed to open %s: %s\n", argv[1], strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    base_fs_file = fopen(argv[2], "w");
+    if (base_fs_file == NULL) {
+        fprintf(stderr, "failed to open %s: %s\n", argv[2], strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    if (fscanf(blk_alloc_file, "Base EXT4 version %s", file_version) > 0) {
+        char c;
+        printf("%s is already in *.base_fs format, just copying into %s...\n", argv[1], argv[2]);
+        rewind(blk_alloc_file);
+        while ((c = fgetc(blk_alloc_file)) != EOF) {
+            fputc(c, base_fs_file);
+        }
+        return 0;
+    } else {
+        printf("Converting %s into *.base_fs format as %s...\n", argv[1], argv[2]);
+        rewind(blk_alloc_file);
+    }
+    fprintf(base_fs_file, "Base EXT4 version 1.0\n");
+    while(fscanf(blk_alloc_file, "%s ", filename) != EOF) {
+        int i;
+        fprintf(base_fs_file, "%s ", filename);
+        if (getline(&spaced_allocs, &spaced_allocs_len, blk_alloc_file) == -1) {
+            fprintf(stderr, "Bad blk_alloc format\n");
+            exit(EXIT_FAILURE);
+        }
+        for (i = 0; spaced_allocs[i]; i++) {
+            if (spaced_allocs[i] == ' ') {
+                if (!isspace(spaced_allocs[i + 1])) fputc(',', base_fs_file);
+            } else fputc(spaced_allocs[i], base_fs_file);
+        }
+    }
+    free(spaced_allocs);
+    fclose(blk_alloc_file);
+    fclose(base_fs_file);
+    return 0;
+}
diff --git a/ext4_utils/canned_fs_config.c b/ext4_utils/canned_fs_config.c
deleted file mode 100644 (file)
index 2165feb..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdlib.h>
-
-#include "private/android_filesystem_config.h"
-
-#include "canned_fs_config.h"
-
-typedef struct {
-       const char* path;
-       unsigned uid;
-       unsigned gid;
-       unsigned mode;
-       uint64_t capabilities;
-} Path;
-
-static Path* canned_data = NULL;
-static int canned_alloc = 0;
-static int canned_used = 0;
-
-static int path_compare(const void* a, const void* b) {
-       return strcmp(((Path*)a)->path, ((Path*)b)->path);
-}
-
-int load_canned_fs_config(const char* fn) {
-       FILE* f = fopen(fn, "r");
-       if (f == NULL) {
-               fprintf(stderr, "failed to open %s: %s\n", fn, strerror(errno));
-               return -1;
-       }
-
-       char line[PATH_MAX + 200];
-       while (fgets(line, sizeof(line), f)) {
-               while (canned_used >= canned_alloc) {
-                       canned_alloc = (canned_alloc+1) * 2;
-                       canned_data = (Path*) realloc(canned_data, canned_alloc * sizeof(Path));
-               }
-               Path* p = canned_data + canned_used;
-               p->path = strdup(strtok(line, " "));
-               p->uid = atoi(strtok(NULL, " "));
-               p->gid = atoi(strtok(NULL, " "));
-               p->mode = strtol(strtok(NULL, " "), NULL, 8);   // mode is in octal
-               p->capabilities = 0;
-
-               char* token = NULL;
-               do {
-                       token = strtok(NULL, " ");
-                       if (token && strncmp(token, "capabilities=", 13) == 0) {
-                               p->capabilities = strtoll(token+13, NULL, 0);
-                               break;
-                       }
-               } while (token);
-
-               canned_used++;
-       }
-
-       fclose(f);
-
-       qsort(canned_data, canned_used, sizeof(Path), path_compare);
-       printf("loaded %d fs_config entries\n", canned_used);
-
-       return 0;
-}
-
-void canned_fs_config(const char* path, int dir, const char* target_out_path,
-                                         unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities) {
-       Path key;
-       key.path = path+1;   // canned paths lack the leading '/'
-       Path* p = (Path*) bsearch(&key, canned_data, canned_used, sizeof(Path), path_compare);
-       if (p == NULL) {
-               fprintf(stderr, "failed to find [%s] in canned fs_config\n", path);
-               exit(1);
-       }
-       *uid = p->uid;
-       *gid = p->gid;
-       *mode = p->mode;
-       *capabilities = p->capabilities;
-
-#if 0
-       // for debugging, run the built-in fs_config and compare the results.
-
-       unsigned c_uid, c_gid, c_mode;
-       uint64_t c_capabilities;
-       fs_config(path, dir, target_out_path, &c_uid, &c_gid, &c_mode, &c_capabilities);
-
-       if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid);
-       if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid);
-       if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode);
-       if (c_capabilities != *capabilities) printf("%s capabilities %llx %llx\n", path, *capabilities, c_capabilities);
-#endif
-}
index 8b2b0fd..2e3d903 100644 (file)
@@ -18,7 +18,7 @@
 #include <string.h>
 #include <stdio.h>
 
-#ifdef HAVE_ANDROID_OS
+#ifdef __ANDROID__
 #include <linux/capability.h>
 #else
 #include <private/android_filesystem_capability.h>
index 886d17a..be77b79 100644 (file)
@@ -1,10 +1,20 @@
 /*
- * Copyright (c) 2015 Google, Inc.
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-#define TAG "ext4_utils"
-
-#include "ext4_crypt_init_extensions.h"
+#include "ext4_crypt.h"
 
 #include <dirent.h>
 #include <errno.h>
@@ -17,9 +27,8 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <cutils/klog.h>
-
-#include "unencrypted_properties.h"
+#include <android-base/logging.h>
+#include <cutils/properties.h>
 
 #define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy"
 #define EXT4_KEYREF_DELIMITER ((char)'.')
@@ -27,6 +36,8 @@
 // ext4enc:TODO Include structure from somewhere sensible
 // MUST be in sync with ext4_crypto.c in kernel
 #define EXT4_KEY_DESCRIPTOR_SIZE 8
+#define EXT4_KEY_DESCRIPTOR_SIZE_HEX 17
+
 struct ext4_encryption_policy {
     char version;
     char contents_encryption_mode;
@@ -39,93 +50,148 @@ struct ext4_encryption_policy {
 #define EXT4_ENCRYPTION_MODE_AES_256_CTS    4
 
 // ext4enc:TODO Get value from somewhere sensible
-#define EXT4_IOC_SET_ENCRYPTION_POLICY \
-    _IOR('f', 19, struct ext4_encryption_policy)
+#define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy)
+#define EXT4_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct ext4_encryption_policy)
 
-/* Validate that all path items are available and accessible. */
-static int is_path_valid(const char *path)
-{
-    if (access(path, W_OK)) {
-        KLOG_ERROR(TAG, "Can't access %s: %s\n",strerror(errno), path);
-        return 0;
-    }
+#define HEX_LOOKUP "0123456789abcdef"
 
-    return 1;
+bool e4crypt_is_native() {
+    char value[PROPERTY_VALUE_MAX];
+    property_get("ro.crypto.type", value, "none");
+    return !strcmp(value, "file");
 }
 
-static int is_dir_empty(const char *dirname)
+static void policy_to_hex(const char* policy, char* hex) {
+    for (size_t i = 0, j = 0; i < EXT4_KEY_DESCRIPTOR_SIZE; i++) {
+        hex[j++] = HEX_LOOKUP[(policy[i] & 0xF0) >> 4];
+        hex[j++] = HEX_LOOKUP[policy[i] & 0x0F];
+    }
+    hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX - 1] = '\0';
+}
+
+static bool is_dir_empty(const char *dirname, bool *is_empty)
 {
     int n = 0;
-    struct dirent *d;
-    DIR *dir;
-
-    dir = opendir(dirname);
-    while ((d = readdir(dir)) != NULL) {
-        if (strcmp(d->d_name, "lost+found") == 0) {
-            // Skip lost+found directory
-        } else if (++n > 2) {
+    auto dirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(dirname), closedir);
+    if (!dirp) {
+        PLOG(ERROR) << "Unable to read directory: " << dirname;
+        return false;
+    }
+    for (;;) {
+        errno = 0;
+        auto entry = readdir(dirp.get());
+        if (!entry) {
+            if (errno) {
+                PLOG(ERROR) << "Unable to read directory: " << dirname;
+                return false;
+            }
             break;
         }
+        if (strcmp(entry->d_name, "lost+found") != 0) { // Skip lost+found
+            ++n;
+            if (n > 2) {
+                *is_empty = false;
+                return true;
+            }
+        }
     }
-    closedir(dir);
-    return n <= 2;
+    *is_empty = true;
+    return true;
 }
 
-int do_policy_set(const char *directory, const char *policy, int policy_length)
-{
-    struct stat st;
-    ssize_t ret;
-
+static bool e4crypt_policy_set(const char *directory, const char *policy, size_t policy_length) {
     if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
-        KLOG_ERROR("Policy wrong length\n");
-        return -EINVAL;
+        LOG(ERROR) << "Policy wrong length: " << policy_length;
+        return false;
     }
-
-    if (!is_path_valid(directory)) {
-        return -EINVAL;
+    int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+    if (fd == -1) {
+        PLOG(ERROR) << "Failed to open directory " << directory;
+        return false;
     }
 
-    stat(directory, &st);
-    if (!S_ISDIR(st.st_mode)) {
-        KLOG_ERROR(TAG, "Can only set policy on a directory (%s)\n", directory);
-        return -EINVAL;
+    ext4_encryption_policy eep;
+    eep.version = 0;
+    eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
+    eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
+    eep.flags = 0;
+    memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
+    if (ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep)) {
+        PLOG(ERROR) << "Failed to set encryption policy for " << directory;
+        close(fd);
+        return false;
     }
+    close(fd);
+
+    char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+    policy_to_hex(policy, policy_hex);
+    LOG(INFO) << "Policy for " << directory << " set to " << policy_hex;
+    return true;
+}
 
-    if (!is_dir_empty(directory)) {
-        KLOG_ERROR(TAG, "Can only set policy on an empty directory (%s)\n",
-                   directory);
-        return -EINVAL;
+static bool e4crypt_policy_get(const char *directory, char *policy, size_t policy_length) {
+    if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
+        LOG(ERROR) << "Policy wrong length: " << policy_length;
+        return false;
     }
 
-    int fd = open(directory, O_DIRECTORY);
+    int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
     if (fd == -1) {
-        KLOG_ERROR(TAG, "Failed to open directory (%s)\n", directory);
-        return -EINVAL;
+        PLOG(ERROR) << "Failed to open directory " << directory;
+        return false;
     }
 
     ext4_encryption_policy eep;
-    eep.version = 0;
-    eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
-    eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
-    eep.flags = 0;
-    memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
-    ret = ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep);
-    auto preserve_errno = errno;
+    memset(&eep, 0, sizeof(ext4_encryption_policy));
+    if (ioctl(fd, EXT4_IOC_GET_ENCRYPTION_POLICY, &eep) != 0) {
+        PLOG(ERROR) << "Failed to get encryption policy for " << directory;
+        close(fd);
+        return -1;
+    }
     close(fd);
 
-    if (ret) {
-        KLOG_ERROR(TAG, "Failed to set encryption policy for %s: %s\n",
-                   directory, strerror(preserve_errno));
-        return -EINVAL;
+    if ((eep.version != 0)
+            || (eep.contents_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_XTS)
+            || (eep.filenames_encryption_mode != EXT4_ENCRYPTION_MODE_AES_256_CTS)
+            || (eep.flags != 0)) {
+        LOG(ERROR) << "Failed to find matching encryption policy for " << directory;
+        return false;
     }
+    memcpy(policy, eep.master_key_descriptor, EXT4_KEY_DESCRIPTOR_SIZE);
 
-    KLOG_INFO(TAG, "Encryption policy for %s is set to %02x%02x%02x%02x\n",
-              directory, policy[0], policy[1], policy[2], policy[3]);
-    return 0;
+    return true;
 }
 
-bool e4crypt_non_default_key(const char* dir)
-{
-    UnencryptedProperties props(dir);
-    return props.Get<int>(properties::is_default, 1) != 1;
+static bool e4crypt_policy_check(const char *directory, const char *policy, size_t policy_length) {
+    if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
+        LOG(ERROR) << "Policy wrong length: " << policy_length;
+        return false;
+    }
+    char existing_policy[EXT4_KEY_DESCRIPTOR_SIZE];
+    if (!e4crypt_policy_get(directory, existing_policy, EXT4_KEY_DESCRIPTOR_SIZE)) return false;
+    char existing_policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+
+    policy_to_hex(existing_policy, existing_policy_hex);
+
+    if (memcmp(policy, existing_policy, EXT4_KEY_DESCRIPTOR_SIZE) != 0) {
+        char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
+        policy_to_hex(policy, policy_hex);
+        LOG(ERROR) << "Found policy " << existing_policy_hex << " at " << directory
+                   << " which doesn't match expected value " << policy_hex;
+        return false;
+    }
+    LOG(INFO) << "Found policy " << existing_policy_hex << " at " << directory
+              << " which matches expected value";
+    return true;
+}
+
+int e4crypt_policy_ensure(const char *directory, const char *policy, size_t policy_length) {
+    bool is_empty;
+    if (!is_dir_empty(directory, &is_empty)) return -1;
+    if (is_empty) {
+        if (!e4crypt_policy_set(directory, policy, policy_length)) return -1;
+    } else {
+        if (!e4crypt_policy_check(directory, policy, policy_length)) return -1;
+    }
+    return 0;
 }
diff --git a/ext4_utils/ext4_crypt.h b/ext4_utils/ext4_crypt.h
new file mode 100644 (file)
index 0000000..ddc09a7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/cdefs.h>
+#include <stdbool.h>
+#include <cutils/multiuser.h>
+
+__BEGIN_DECLS
+
+bool e4crypt_is_native();
+
+int e4crypt_policy_ensure(const char *directory, const char* policy, size_t policy_length);
+
+static const char* e4crypt_unencrypted_folder = "/unencrypted";
+static const char* e4crypt_key_ref = "/unencrypted/ref";
+
+__END_DECLS
index 5d66419..c6baea7 100644 (file)
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 #define TAG "ext4_utils"
 
 #include "ext4_crypt_init_extensions.h"
+#include "ext4_crypt.h"
+
+#include <android-base/logging.h>
 
 #include <string>
+#include <vector>
 
 #include <dirent.h>
 #include <errno.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
 
 #include <cutils/klog.h>
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
-#include <poll.h>
+#include <logwrap/logwrap.h>
 
 #include "key_control.h"
-#include "unencrypted_properties.h"
 
 static const std::string arbitrary_sequence_number = "42";
 static const int vold_command_timeout_ms = 60 * 1000;
 
-static std::string vold_command(std::string const& command)
-{
-    KLOG_INFO(TAG, "Running command %s\n", command.c_str());
-    int sock = -1;
-
-    while (true) {
-        sock = socket_local_client("cryptd",
-                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                   SOCK_STREAM);
-        if (sock >= 0) {
-            break;
-        }
-        usleep(10000);
-    }
-
-    if (sock < 0) {
-        KLOG_INFO(TAG, "Cannot open vold, failing command (%s)\n", strerror(errno));
-        return "";
-    }
-
-    class CloseSocket
-    {
-        int sock_;
-    public:
-        CloseSocket(int sock) : sock_(sock) {}
-        ~CloseSocket() { close(sock_); }
-    };
-
-    CloseSocket cs(sock);
-
-    // Use arbitrary sequence number. This should only be used when the
-    // framework is down, so this is (mostly) OK.
-    std::string actual_command = arbitrary_sequence_number + " " + command;
-    if (write(sock, actual_command.c_str(), actual_command.size() + 1) < 0) {
-        KLOG_ERROR(TAG, "Cannot write command (%s)\n", strerror(errno));
-        return "";
-    }
-
-    struct pollfd poll_sock = {sock, POLLIN, 0};
-
-    int rc = TEMP_FAILURE_RETRY(poll(&poll_sock, 1, vold_command_timeout_ms));
-    if (rc < 0) {
-        KLOG_ERROR(TAG, "Error in poll (%s)\n", strerror(errno));
-        return "";
-    }
-
-    if (!(poll_sock.revents & POLLIN)) {
-        KLOG_ERROR(TAG, "Timeout\n");
-        return "";
-    }
-    char buffer[4096];
-    memset(buffer, 0, sizeof(buffer));
-    rc = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
-    if (rc <= 0) {
-        if (rc == 0) {
-            KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
-        } else {
-            KLOG_ERROR(TAG, "Error reading data (%s)\n", strerror(errno));
-        }
-        return "";
+static void kernel_logger(android::base::LogId, android::base::LogSeverity severity, const char*,
+        const char*, unsigned int, const char* message) {
+    if (severity == android::base::ERROR || severity == android::base::FATAL) {
+        KLOG_ERROR(TAG, "%s\n", message);
+    } else if (severity == android::base::WARNING) {
+        KLOG_WARNING(TAG, "%s\n", message);
+    } else {
+        KLOG_INFO(TAG, "%s\n", message);
     }
+}
 
-    // We don't truly know that this is the correct result. However,
-    // since this will only be used when the framework is down,
-    // it should be OK unless someone is running vdc at the same time.
-    // Worst case we force a reboot in the very rare synchronization
-    // error
-    return std::string(buffer, rc);
+static void init_logging() {
+    android::base::SetLogger(kernel_logger);
 }
 
 int e4crypt_create_device_key(const char* dir,
                               int ensure_dir_exists(const char*))
 {
-    // Already encrypted with password? If so bail
-    std::string temp_folder = std::string() + dir + "/tmp_mnt";
-    DIR* temp_dir = opendir(temp_folder.c_str());
-    if (temp_dir) {
-        closedir(temp_dir);
-        return 0;
-    }
+    init_logging();
 
     // Make sure folder exists. Use make_dir to set selinux permissions.
-    if (ensure_dir_exists(UnencryptedProperties::GetPath(dir).c_str())) {
+    std::string unencrypted_dir = std::string(dir) + e4crypt_unencrypted_folder;
+    if (ensure_dir_exists(unencrypted_dir.c_str())) {
         KLOG_ERROR(TAG, "Failed to create %s (%s)\n",
-                   UnencryptedProperties::GetPath(dir).c_str(),
+                   unencrypted_dir.c_str(),
                    strerror(errno));
         return -1;
     }
 
-    auto result = vold_command("cryptfs enablefilecrypto");
-    // ext4enc:TODO proper error handling
-    KLOG_INFO(TAG, "enablefilecrypto returned with result %s\n",
-              result.c_str());
-
-    return 0;
+    const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "enablefilecrypto" };
+    int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
+    LOG(INFO) << "enablefilecrypto result: " << rc;
+    return rc;
 }
 
 int e4crypt_install_keyring()
 {
+    init_logging();
+
     key_serial_t device_keyring = add_key("keyring", "e4crypt", 0, 0,
                                           KEY_SPEC_SESSION_KEYRING);
 
@@ -127,14 +89,26 @@ int e4crypt_install_keyring()
         return -1;
     }
 
-    KLOG_INFO(TAG, "Keyring created wth id %d in process %d\n",
+    KLOG_INFO(TAG, "Keyring created with id %d in process %d\n",
               device_keyring, getpid());
 
     return 0;
 }
 
+int e4crypt_do_init_user0()
+{
+    init_logging();
+
+    const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "init_user0" };
+    int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
+    LOG(INFO) << "init_user0 result: " << rc;
+    return rc;
+}
+
 int e4crypt_set_directory_policy(const char* dir)
 {
+    init_logging();
+
     // Only set policy on first level /data directories
     // To make this less restrictive, consider using a policy file.
     // However this is overkill for as long as the policy is simply
@@ -143,25 +117,32 @@ int e4crypt_set_directory_policy(const char* dir)
         return 0;
     }
 
-    // Don't encrypt lost+found - ext4 doesn't like it
-    if (!strcmp(dir, "/data/lost+found")) {
-        return 0;
-    }
-
-    // ext4enc:TODO exclude /data/user with a horrible special case.
-    if (!strcmp(dir, "/data/user")) {
-        return 0;
+    // Special case various directories that must not be encrypted,
+    // often because their subdirectories must be encrypted.
+    // This isn't a nice way to do this, see b/26641735
+    std::vector<std::string> directories_to_exclude = {
+        "lost+found",
+        "system_ce", "system_de",
+        "misc_ce", "misc_de",
+        "media",
+        "data", "user", "user_de",
+    };
+    std::string prefix = "/data/";
+    for (auto d: directories_to_exclude) {
+        if ((prefix + d) == dir) {
+            KLOG_INFO(TAG, "Not setting policy on %s\n", dir);
+            return 0;
+        }
     }
 
-    UnencryptedProperties props("/data");
-    std::string policy = props.Get<std::string>(properties::ref);
-    if (policy.empty()) {
-        // ext4enc:TODO why is this OK?
-        return 0;
+    std::string ref_filename = std::string("/data") + e4crypt_key_ref;
+    std::string policy;
+    if (!android::base::ReadFileToString(ref_filename, &policy)) {
+        KLOG_ERROR(TAG, "Unable to read system policy to set on %s\n", dir);
+        return -1;
     }
-
     KLOG_INFO(TAG, "Setting policy on %s\n", dir);
-    int result = do_policy_set(dir, policy.c_str(), policy.size());
+    int result = e4crypt_policy_ensure(dir, policy.c_str(), policy.size());
     if (result) {
         KLOG_ERROR(TAG, "Setting %02x%02x%02x%02x policy on %s failed!\n",
                    policy[0], policy[1], policy[2], policy[3], dir);
@@ -170,13 +151,3 @@ int e4crypt_set_directory_policy(const char* dir)
 
     return 0;
 }
-
-int e4crypt_set_user_crypto_policies(const char* dir)
-{
-    auto command = std::string() + "cryptfs setusercryptopolicies " + dir;
-    auto result = vold_command(command);
-    // ext4enc:TODO proper error handling
-    KLOG_INFO(TAG, "setusercryptopolicies returned with result %s\n",
-              result.c_str());
-    return 0;
-}
index d02d181..63f6d88 100644 (file)
@@ -1,5 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 #include <sys/cdefs.h>
 #include <stdbool.h>
+#include <cutils/multiuser.h>
 
 __BEGIN_DECLS
 
@@ -9,8 +26,6 @@ int e4crypt_install_keyring();
 int e4crypt_create_device_key(const char* path,
                               int ensure_dir_exists(const char* dir));
 int e4crypt_set_directory_policy(const char* path);
-bool e4crypt_non_default_key(const char* path);
-int do_policy_set(const char *directory, const char *policy, int policy_length);
-int e4crypt_set_user_crypto_policies(const char* path);
+int e4crypt_do_init_user0();
 
 __END_DECLS
index 1d527a1..7bbbcfe 100644 (file)
@@ -25,9 +25,6 @@ int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info)
         if (sb->s_magic != EXT4_SUPER_MAGIC)
                 return -EINVAL;
 
-        if ((sb->s_state & EXT4_VALID_FS) != EXT4_VALID_FS)
-                return -EINVAL;
-
        info->block_size = 1024 << sb->s_log_block_size;
        info->blocks_per_group = sb->s_blocks_per_group;
        info->inodes_per_group = sb->s_inodes_per_group;
index 832fa33..159580d 100644 (file)
@@ -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 @@ struct fs_info {
        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);
index 3b22b81..fba4f9f 100644 (file)
@@ -49,6 +49,7 @@ int force = 0;
 struct fs_info info;
 struct fs_aux_info aux_info;
 struct sparse_file *ext4_sparse_file;
+struct block_allocation *base_fs_allocations = NULL;
 
 jmp_buf setjmp_env;
 
@@ -168,10 +169,31 @@ void write_sb(int fd, unsigned long long offset, struct ext4_super_block *sb)
                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 */
@@ -203,7 +225,27 @@ void ext4_create_fs_aux_info()
                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 *));
 
@@ -224,7 +266,8 @@ void ext4_free_fs_aux_info()
                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);
 }
 
@@ -248,7 +291,7 @@ void ext4_fill_in_sb(int real_uuid)
        sb->s_mtime = 0;
        sb->s_wtime = 0;
        sb->s_mnt_count = 0;
-       sb->s_max_mnt_count = 0xFFFF;
+       sb->s_max_mnt_count = 10;
        sb->s_magic = EXT4_SUPER_MAGIC;
        sb->s_state = EXT4_VALID_FS;
        sb->s_errors = EXT4_ERRORS_RO;
@@ -321,11 +364,11 @@ void ext4_fill_in_sb(int real_uuid)
                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,
@@ -341,22 +384,23 @@ void ext4_fill_in_sb(int real_uuid)
                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)
index 8a4ad8f..0fbbdd3 100644 (file)
@@ -99,6 +99,8 @@ struct ext2_group_desc {
 
 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;
@@ -117,6 +119,7 @@ struct fs_aux_info {
 extern struct fs_info info;
 extern struct fs_aux_info aux_info;
 extern struct sparse_file *ext4_sparse_file;
+extern struct block_allocation *base_fs_allocations;
 
 extern jmp_buf setjmp_env;
 
@@ -142,7 +145,7 @@ 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);
@@ -159,7 +162,7 @@ int make_ext4fs_internal(int fd, const char *directory, const char *_target_out_
                                                 const char *mountpoint, fs_config_func_t fs_config_func, int gzip,
                                                 int sparse, int crc, int wipe, int real_uuid,
                                                 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
-                                                FILE* block_list_file);
+                                                FILE* block_list_file, FILE* base_alloc_file_in, FILE* base_alloc_file_out);
 
 int read_ext(int fd, int verbose);
 
index 1900b10..7887488 100644 (file)
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -72,23 +72,43 @@ static void extent_create_backing_file(struct block_allocation *alloc,
 }
 
 static struct block_allocation *do_inode_allocate_extents(
-       struct ext4_inode *inode, u64 len)
+       struct ext4_inode *inode, u64 len, struct block_allocation *prealloc)
 {
-       u32 block_len = DIV_ROUND_UP(len, info.block_size);
-       struct block_allocation *alloc = allocate_blocks(block_len + 1);
+       u32 block_len = DIV_ROUND_UP(len, info.block_size), prealloc_block_len;
+       struct block_allocation *alloc;
        u32 extent_block = 0;
        u32 file_block = 0;
        struct ext4_extent *extent;
        u64 blocks;
 
-       if (alloc == NULL) {
-               error("Failed to allocate %d blocks\n", block_len + 1);
-               return NULL;
+       if (!prealloc) {
+               alloc = allocate_blocks(block_len + 1);
+               if (alloc == NULL) {
+                       error("Failed to allocate %d blocks\n", block_len + 1);
+                       return NULL;
+               }
+       } else {
+               prealloc_block_len = block_allocation_len(prealloc);
+               if (block_len + 1 > prealloc_block_len) {
+                       alloc = allocate_blocks(block_len + 1 - prealloc_block_len);
+                       if (alloc == NULL) {
+                               error("Failed to allocate %d blocks\n",
+                                               block_len + 1 - prealloc_block_len);
+                               return NULL;
+                       }
+                       region_list_merge(&prealloc->list, &alloc->list);
+                       free(alloc);
+               }
+               alloc = prealloc;
        }
 
        int allocation_len = block_allocation_num_regions(alloc);
        if (allocation_len <= 3) {
                reduce_allocation(alloc, 1);
+               // IMPORTANT: reduce_allocation may have changed allocation
+               // length, otherwise file corruption happens when fs thinks
+               // a block is missing from extent header.
+               allocation_len = block_allocation_num_regions(alloc);
        } else {
                reserve_oob_blocks(alloc, 1);
                extent_block = get_oob_block(alloc, 0);
@@ -183,7 +203,7 @@ u8 *inode_allocate_data_extents(struct ext4_inode *inode, u64 len,
        struct block_allocation *alloc;
        u8 *data = NULL;
 
-       alloc = do_inode_allocate_extents(inode, len);
+       alloc = do_inode_allocate_extents(inode, len, NULL);
        if (alloc == NULL) {
                error("failed to allocate extents for %"PRIu64" bytes", len);
                return NULL;
@@ -205,9 +225,26 @@ u8 *inode_allocate_data_extents(struct ext4_inode *inode, u64 len,
 struct block_allocation* inode_allocate_file_extents(struct ext4_inode *inode, u64 len,
        const char *filename)
 {
-       struct block_allocation *alloc;
+       struct block_allocation *alloc, *prealloc = base_fs_allocations, *prev_prealloc = NULL;
+       // TODO(mkayyash): base_fs_allocations is sorted by filename, consider
+       // storing it in an array and then binary searching for a filename match instead
+       while (prealloc && prealloc->filename != NULL) {
+               if (!strcmp(filename, prealloc->filename)) {
+                       break;
+               }
+               prev_prealloc = prealloc;
+               prealloc = prealloc->next;
+       }
+       if (prealloc) {
+               if (!prev_prealloc) {
+                       base_fs_allocations = base_fs_allocations->next;
+               } else {
+                       prev_prealloc->next = prealloc->next;
+               }
+               prealloc->next = NULL;
+       }
 
-       alloc = do_inode_allocate_extents(inode, len);
+       alloc = do_inode_allocate_extents(inode, len, prealloc);
        if (alloc == NULL) {
                error("failed to allocate extents for %"PRIu64" bytes", len);
                return NULL;
@@ -222,7 +259,7 @@ void inode_allocate_extents(struct ext4_inode *inode, u64 len)
 {
        struct block_allocation *alloc;
 
-       alloc = do_inode_allocate_extents(inode, len);
+       alloc = do_inode_allocate_extents(inode, len, NULL);
        if (alloc == NULL) {
                error("failed to allocate extents for %"PRIu64" bytes", len);
                return;
index 39bd140..64f230a 100644 (file)
@@ -3,11 +3,7 @@
 #include <stdarg.h>
 #include <unistd.h>
 #include <sys/syscall.h>
-
-/* keyring keyctl commands */
-#define KEYCTL_REVOKE         3 /* revoke a key */
-#define KEYCTL_SETPERM        5 /* set permissions for a key in a keyring */
-#define KEYCTL_SEARCH        10 /* search for a key in a keyring */
+#include <linux/keyctl.h>
 
 static long keyctl(int cmd, ...)
 {
index 669d080..f45a699 100644 (file)
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
 /* These match the Linux definitions of these flags.
    L_xx is defined to avoid conflicting with the win32 versions.
 */
+#undef S_IRWXU
+#undef S_IRGRP
+#undef S_IWGRP
+#undef S_IXGRP
+#undef S_IRWXG
+#undef S_IROTH
+#undef S_IWOTH
+#undef S_IXOTH
+#undef S_IRWXO
+#undef S_ISUID
+#undef S_ISGID
+#undef S_ISVTX
+
 #define L_S_IRUSR 00400
 #define L_S_IWUSR 00200
 #define L_S_IXUSR 00100
 
 #endif
 
+#define MAX_PATH 4096
+#define MAX_BLK_MAPPING_STR 1000
+
+const int blk_file_major_ver = 1;
+const int blk_file_minor_ver = 0;
+const char *blk_file_header_fmt = "Base EXT4 version %d.%d";
+
 /* TODO: Not implemented:
    Allocating blocks in the same block group as the file inode
    Hash or binary tree directories
@@ -78,7 +98,7 @@ static int filter_dot(const struct dirent *d)
 }
 
 static u32 build_default_directory_structure(const char *dir_path,
-                                            struct selabel_handle *sehnd)
+                                                struct selabel_handle *sehnd)
 {
        u32 inode;
        u32 root_inode;
@@ -401,15 +421,31 @@ void reset_ext4fs_info() {
 int make_ext4fs_sparse_fd(int fd, long long len,
                                const char *mountpoint, struct selabel_handle *sehnd)
 {
+       return make_ext4fs_sparse_fd_directory(fd, len, mountpoint, sehnd, NULL);
+}
+
+int make_ext4fs_sparse_fd_directory(int fd, long long len,
+                               const char *mountpoint, struct selabel_handle *sehnd,
+                               const char *directory)
+{
        reset_ext4fs_info();
        info.len = len;
 
-       return make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 1, 0, 0, 0, sehnd, 0, -1, NULL);
+       return make_ext4fs_internal(fd, directory, NULL, mountpoint, NULL,
+                                                               0, 1, 0, 0, 0,
+                                                               sehnd, 0, -1, NULL, NULL, NULL);
 }
 
 int make_ext4fs(const char *filename, long long len,
                                const char *mountpoint, struct selabel_handle *sehnd)
 {
+       return make_ext4fs_directory(filename, len, mountpoint, sehnd, NULL);
+}
+
+int make_ext4fs_directory(const char *filename, long long len,
+                                                 const char *mountpoint, struct selabel_handle *sehnd,
+                                                 const char *directory)
+{
        int fd;
        int status;
 
@@ -422,7 +458,9 @@ int make_ext4fs(const char *filename, long long len,
                return EXIT_FAILURE;
        }
 
-       status = make_ext4fs_internal(fd, NULL, NULL, mountpoint, NULL, 0, 0, 0, 1, 0, sehnd, 0, -1, NULL);
+       status = make_ext4fs_internal(fd, directory, NULL, mountpoint, NULL,
+                                                                 0, 0, 0, 1, 0,
+                                                                 sehnd, 0, -1, NULL, NULL, NULL);
        close(fd);
 
        return status;
@@ -488,21 +526,181 @@ static char *canonicalize_rel_slashes(const char *str)
        return canonicalize_slashes(str, false);
 }
 
+static int compare_chunks(const void* chunk1, const void* chunk2) {
+       struct region* c1 = (struct region*) chunk1;
+       struct region* c2 = (struct region*) chunk2;
+       return c1->block - c2->block;
+}
+
+static int get_block_group(u32 block) {
+       int i, group = 0;
+       for(i = 0; i < aux_info.groups; i++) {
+               if (block >= aux_info.bgs[i].first_block)
+                       group = i;
+               else
+                       break;
+       }
+       return group;
+}
+
+static void extract_base_fs_allocations(const char *directory, const char *mountpoint,
+                                                                               FILE* base_alloc_file_in) {
+#define err_msg "base file badly formatted"
+#ifndef USE_MINGW
+       // FORMAT Version 1.0: filename blk_mapping
+       const char *base_alloc_file_in_format = "%s %s";
+       const int base_file_format_param_count = 2;
+
+       char stored_file_name[MAX_PATH], real_file_name[MAX_PATH], file_map[MAX_BLK_MAPPING_STR];
+       struct block_allocation *fs_alloc;
+       struct block_group_info *bgs = aux_info.bgs;
+       int i, major_version = 0, minor_version = 0;
+       char *base_file_line = NULL;
+       size_t base_file_line_len = 0;
+
+       printf("[v%d.%d] Generating an Incremental EXT4 image\n",
+                       blk_file_major_ver, blk_file_minor_ver);
+       if (base_fs_allocations == NULL)
+               base_fs_allocations = create_allocation();
+       fs_alloc = base_fs_allocations;
+
+       fscanf(base_alloc_file_in, blk_file_header_fmt, &major_version, &minor_version);
+       if (major_version == 0) {
+               critical_error("Invalid base file");
+       }
+
+       if (major_version != blk_file_major_ver) {
+               critical_error("Incompatible base file: version required is %d.X",
+                               blk_file_major_ver);
+       }
+
+       if (minor_version < blk_file_minor_ver) {
+               critical_error("Incompatible base file: version required is %d.%d or above",
+                               blk_file_major_ver, blk_file_minor_ver);
+       }
+
+       while (getline(&base_file_line, &base_file_line_len, base_alloc_file_in) != -1) {
+               if (sscanf(base_file_line, base_alloc_file_in_format, &stored_file_name, &file_map)
+                               != base_file_format_param_count) {
+                       continue;
+               }
+               if (strlen(stored_file_name) < strlen(mountpoint)) {
+                       continue;
+               }
+               snprintf(real_file_name, MAX_PATH, "%s%s", directory, stored_file_name + strlen(mountpoint));
+               if (!access(real_file_name, R_OK)) {
+                       char *block_range, *end_string;
+                       int real_file_fd;
+                       u32 start_block, end_block, block_file_size;
+                       u32 real_file_block_size;
+
+                       real_file_fd = open(real_file_name, O_RDONLY);
+                       if (real_file_fd == -1) {
+                               critical_error(err_msg);
+                       }
+                       real_file_block_size = get_file_size(real_file_fd);
+                       close(real_file_fd);
+                       real_file_block_size = DIV_ROUND_UP(real_file_block_size, info.block_size);
+                       fs_alloc->filename = strdup(real_file_name);
+                       block_range = strtok_r(file_map, ",", &end_string);
+                       while (block_range && real_file_block_size) {
+                               int block_group;
+                               char *range, *end_token = NULL;
+                               range = strtok_r(block_range, "-", &end_token);
+                               if (!range) {
+                                       critical_error(err_msg);
+                               }
+                               start_block = parse_num(range);
+                               range = strtok_r(NULL, "-", &end_token);
+                               if (!range) {
+                                       end_block = start_block;
+                               } else {
+                                       end_block = parse_num(range);
+                               }
+                               // Assummption is that allocations are within the same block group
+                               block_group = get_block_group(start_block);
+                               if (block_group != get_block_group(end_block)) {
+                                       critical_error("base file allocation's end block is in a different "
+                                                                  "block group than start block. did you change fs params?");
+                               }
+                               block_range = strtok_r(NULL, ",", &end_string);
+                               int bg_first_block = bgs[block_group].first_block;
+                               int min_bg_bound = bgs[block_group].chunks[0].block + bgs[block_group].chunks[0].len;
+                               int max_bg_bound = bgs[block_group].chunks[bgs[block_group].chunk_count - 1].block;
+
+                               if (min_bg_bound >= start_block - bg_first_block ||
+                                       max_bg_bound <= end_block - bg_first_block) {
+                                       continue;
+                               }
+                               block_file_size = end_block - start_block + 1;
+                               if (block_file_size > real_file_block_size) {
+                                       block_file_size = real_file_block_size;
+                               }
+                               append_region(fs_alloc, start_block, block_file_size, block_group);
+                               reserve_bg_chunk(block_group, start_block - bgs[block_group].first_block, block_file_size);
+                               real_file_block_size -= block_file_size;
+                       }
+                       if (reserve_blocks_for_allocation(fs_alloc) < 0)
+                               critical_error("failed to reserve base fs allocation");
+                       fs_alloc->next = create_allocation();
+                       fs_alloc = fs_alloc->next;
+               }
+       }
+
+       for (i = 0; i < aux_info.groups; i++) {
+               qsort(bgs[i].chunks, bgs[i].chunk_count, sizeof(struct region), compare_chunks);
+       }
+
+       free(base_file_line);
+
+#else
+    return;
+#endif
+#undef err_msg
+}
+
+void generate_base_alloc_file_out(FILE* base_alloc_file_out, char* dir, char* mountpoint,
+                                                                 struct block_allocation* p)
+{
+       size_t dirlen = dir ? strlen(dir) : 0;
+       fprintf(base_alloc_file_out, blk_file_header_fmt, blk_file_major_ver, blk_file_minor_ver);
+       fputc('\n', base_alloc_file_out);
+       while (p) {
+               if (dir && strncmp(p->filename, dir, dirlen) == 0) {
+                       // substitute mountpoint for the leading directory in the filename, in the output file
+                       fprintf(base_alloc_file_out, "%s%s", mountpoint, p->filename + dirlen);
+               } else {
+                       fprintf(base_alloc_file_out, "%s", p->filename);
+               }
+               print_blocks(base_alloc_file_out, p, ',');
+               struct block_allocation* pn = p->next;
+               p = pn;
+       }
+}
+
 int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out_directory,
                                                 const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
                                                 int sparse, int crc, int wipe, int real_uuid,
                                                 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
-                                                FILE* block_list_file)
+                                                FILE* block_list_file, FILE* base_alloc_file_in, FILE* base_alloc_file_out)
 {
        u32 root_inode_num;
        u16 root_mode;
        char *mountpoint;
        char *directory = NULL;
        char *target_out_directory = NULL;
+       struct block_allocation* p;
 
        if (setjmp(setjmp_env))
                return EXIT_FAILURE; /* Handle a call to longjmp() */
 
+       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 {
@@ -590,6 +788,9 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
 
        ext4_fill_in_sb(real_uuid);
 
+       if (base_alloc_file_in) {
+               extract_base_fs_allocations(directory, mountpoint, base_alloc_file_in);
+       }
        if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED)
                error("failed to reserve first 10 inodes");
 
@@ -633,8 +834,8 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
 
        ext4_update_free();
 
-       ext4_queue_sb();
-
+       // TODO: Consider migrating the OTA tools to the new base alloc file format
+       // used for generating incremental images (see go/incremental-ext4)
        if (block_list_file) {
                size_t dirlen = directory ? strlen(directory) : 0;
                struct block_allocation* p = get_saved_allocation_chain();
@@ -645,13 +846,17 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
                        } else {
                                fprintf(block_list_file, "%s", p->filename);
                        }
-                       print_blocks(block_list_file, p);
+                       print_blocks(block_list_file, p, ' ');
                        struct block_allocation* pn = p->next;
-                       free_alloc(p);
                        p = pn;
                }
        }
 
+       if (base_alloc_file_out) {
+               struct block_allocation* p = get_saved_allocation_chain();
+               generate_base_alloc_file_out(base_alloc_file_out, directory, mountpoint, p);
+       }
+
        printf("Created filesystem with %d/%d inodes and %d/%d blocks\n",
                        aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count,
                        aux_info.sb->s_inodes_count,
@@ -667,6 +872,13 @@ int make_ext4fs_internal(int fd, const char *_directory, const char *_target_out
        sparse_file_destroy(ext4_sparse_file);
        ext4_sparse_file = NULL;
 
+       p = get_saved_allocation_chain();
+       while (p) {
+               struct block_allocation* pn = p->next;
+               free_alloc(p);
+               p = pn;
+       }
+
        free(mountpoint);
        free(directory);
 
index 3784a9e..4498e62 100644 (file)
@@ -25,8 +25,14 @@ struct selabel_handle;
 
 int make_ext4fs(const char *filename, long long len,
                 const char *mountpoint, struct selabel_handle *sehnd);
+int make_ext4fs_directory(const char *filename, long long len,
+                const char *mountpoint, struct selabel_handle *sehnd,
+                const char *directory);
 int make_ext4fs_sparse_fd(int fd, long long len,
                 const char *mountpoint, struct selabel_handle *sehnd);
+int make_ext4fs_sparse_fd_directory(int fd, long long len,
+                const char *mountpoint, struct selabel_handle *sehnd,
+                const char *directory);
 
 #ifdef __cplusplus
 }
index 03872db..323a445 100644 (file)
@@ -27,6 +27,7 @@
 
 #ifdef ANDROID
 #include <private/android_filesystem_config.h>
+#include <private/canned_fs_config.h>
 #endif
 
 #ifndef USE_MINGW
@@ -41,7 +42,6 @@ struct selabel_handle;
 
 #include "make_ext4fs.h"
 #include "ext4_utils.h"
-#include "canned_fs_config.h"
 
 #ifndef USE_MINGW /* O_BINARY is windows-specific flag */
 #define O_BINARY 0
@@ -57,6 +57,7 @@ static void usage(char *path)
        fprintf(stderr, "    [ -L <label> ] [ -f ] [ -a <android mountpoint> ] [ -u ]\n");
        fprintf(stderr, "    [ -S file_contexts ] [ -C fs_config ] [ -T timestamp ]\n");
        fprintf(stderr, "    [ -z | -s ] [ -w ] [ -c ] [ -J ] [ -v ] [ -B <block_list_file> ]\n");
+       fprintf(stderr, "    [ -d <base_alloc_file_in> ] [ -D <base_alloc_file_out> ]\n");
        fprintf(stderr, "    <filename> [[<directory>] <target_out_directory>]\n");
 }
 
@@ -80,11 +81,13 @@ int main(int argc, char **argv)
        time_t fixed_time = -1;
        struct selabel_handle *sehnd = NULL;
        FILE* block_list_file = NULL;
+       FILE* base_alloc_file_in = NULL;
+       FILE* base_alloc_file_out = NULL;
 #ifndef USE_MINGW
        struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "" } };
 #endif
 
-       while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:fwzJsctvu")) != -1) {
+       while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:d:D:fwzJsctvu")) != -1) {
                switch (opt) {
                case 'l':
                        info.len = parse_num(optarg);
@@ -166,6 +169,20 @@ int main(int argc, char **argv)
                                exit(EXIT_FAILURE);
                        }
                        break;
+               case 'd':
+                       base_alloc_file_in = fopen(optarg, "r");
+                       if (base_alloc_file_in == NULL) {
+                               fprintf(stderr, "failed to open base_alloc_file_in: %s\n", strerror(errno));
+                               exit(EXIT_FAILURE);
+                       }
+                       break;
+               case 'D':
+                       base_alloc_file_out = fopen(optarg, "w");
+                       if (base_alloc_file_out == NULL) {
+                               fprintf(stderr, "failed to open base_alloc_file_out: %s\n", strerror(errno));
+                               exit(EXIT_FAILURE);
+                       }
+                       break;
                default: /* '?' */
                        usage(argv[0]);
                        exit(EXIT_FAILURE);
@@ -237,10 +254,15 @@ int main(int argc, char **argv)
        }
 
        exitcode = make_ext4fs_internal(fd, directory, target_out_directory, mountpoint, fs_config_func, gzip,
-               sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time, block_list_file);
+               sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time,
+               block_list_file, base_alloc_file_in, base_alloc_file_out);
        close(fd);
        if (block_list_file)
                fclose(block_list_file);
+       if (base_alloc_file_out)
+               fclose(base_alloc_file_out);
+       if (base_alloc_file_in)
+               fclose(base_alloc_file_in);
        if (exitcode && strcmp(filename, "-"))
                unlink(filename);
        return exitcode;
index 8667013..b79baf9 100755 (executable)
@@ -6,7 +6,7 @@ function usage() {
 cat<<EOT
 Usage:
 mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>]
-             [-T TIMESTAMP] [-C FS_CONFIG] [-D PRODUCT_OUT] [-B BLOCK_LIST_FILE] [-L LABEL] [FILE_CONTEXTS]
+             [-T TIMESTAMP] [-C FS_CONFIG] [-D PRODUCT_OUT] [-B BLOCK_LIST_FILE] [-d BASE_ALLOC_FILE_IN ] [-A BASE_ALLOC_FILE_OUT ] [-L LABEL] [FILE_CONTEXTS]
 EOT
 }
 
@@ -67,6 +67,18 @@ if [[ "$1" == "-B" ]]; then
   shift; shift
 fi
 
+BASE_ALLOC_FILE_IN=
+if [[ "$1" == "-d" ]]; then
+  BASE_ALLOC_FILE_IN=$2
+  shift; shift
+fi
+
+BASE_ALLOC_FILE_OUT=
+if [[ "$1" == "-A" ]]; then
+  BASE_ALLOC_FILE_OUT=$2
+  shift; shift
+fi
+
 LABEL=
 if [[ "$1" == "-L" ]]; then
   LABEL=$2
@@ -100,6 +112,12 @@ fi
 if [ -n "$BLOCK_LIST" ]; then
   OPT="$OPT -B $BLOCK_LIST"
 fi
+if [ -n "$BASE_ALLOC_FILE_IN" ]; then
+  OPT="$OPT -d $BASE_ALLOC_FILE_IN"
+fi
+if [ -n "$BASE_ALLOC_FILE_OUT" ]; then
+  OPT="$OPT -D $BASE_ALLOC_FILE_OUT"
+fi
 if [ -n "$LABEL" ]; then
   OPT="$OPT -L $LABEL"
 fi
diff --git a/ext4_utils/unencrypted_properties.cpp b/ext4_utils/unencrypted_properties.cpp
deleted file mode 100644 (file)
index ed36e20..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#include "unencrypted_properties.h"
-
-#include <sys/stat.h>
-#include <dirent.h>
-
-namespace properties {
-    const char* key = "key";
-    const char* ref = "ref";
-    const char* props = "props";
-    const char* is_default = "is_default";
-}
-
-namespace
-{
-    const char* unencrypted_folder = "unencrypted";
-}
-
-std::string UnencryptedProperties::GetPath(const char* device)
-{
-    return std::string() + device + "/" + unencrypted_folder;
-}
-
-UnencryptedProperties::UnencryptedProperties(const char* device)
-  : folder_(GetPath(device))
-{
-    DIR* dir = opendir(folder_.c_str());
-    if (dir) {
-        closedir(dir);
-    } else {
-        folder_.clear();
-    }
-}
-
-UnencryptedProperties::UnencryptedProperties()
-{
-}
-
-template<> std::string UnencryptedProperties::Get(const char* name,
-                                      std::string default_value) const
-{
-    if (!OK()) return default_value;
-    std::ifstream i(folder_ + "/" + name, std::ios::binary);
-    if (!i) {
-        return default_value;
-    }
-
-    i.seekg(0, std::ios::end);
-    int length = i.tellg();
-    i.seekg(0, std::ios::beg);
-    if (length == -1) {
-        return default_value;
-    }
-
-    std::string s(length, 0);
-    i.read(&s[0], length);
-    if (!i) {
-        return default_value;
-    }
-
-    return s;
-}
-
-template<> bool UnencryptedProperties::Set(const char* name, std::string const& value)
-{
-    if (!OK()) return false;
-    std::ofstream o(folder_ + "/" + name, std::ios::binary);
-    o << value;
-    return !o.fail();
-}
-
-UnencryptedProperties UnencryptedProperties::GetChild(const char* name) const
-{
-    UnencryptedProperties up;
-    if (!OK()) return up;
-
-    std::string directory(folder_ + "/" + name);
-    if (mkdir(directory.c_str(), 700) == -1 && errno != EEXIST) {
-        return up;
-    }
-
-    up.folder_ = directory;
-    return up;
-}
-
-bool UnencryptedProperties::Remove(const char* name)
-{
-    if (!OK()) return false;
-    if (remove((folder_ + "/" + name).c_str())
-        && errno != ENOENT) {
-        return false;
-    }
-
-    return true;
-}
-
-bool UnencryptedProperties::OK() const
-{
-    return !folder_.empty();
-}
diff --git a/ext4_utils/unencrypted_properties.h b/ext4_utils/unencrypted_properties.h
deleted file mode 100644 (file)
index b2d1295..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-#include <string>
-#include <fstream>
-
-// key names for properties we use
-namespace properties {
-    extern const char* key;
-    extern const char* ref;
-    extern const char* props;
-    extern const char* is_default;
-}
-
-/**
- * Class to store data on the unencrypted folder of a device.
- * Note that the folder must exist before this class is constructed.
- * All names must be valid single level (no '/') file or directory names
- * Data is organized hierarchically so we can get a child folder
- */
-class UnencryptedProperties
-{
-public:
-    // Get path of folder. Must create before using any properties
-    // This is to allow proper setting of SELinux policy
-    static std::string GetPath(const char* device);
-
-    // Opens properties folder on named device.
-    // If folder does not exist, OK will return false, all
-    // getters will return default properties and setters will fail.
-    UnencryptedProperties(const char* device);
-
-    // Get named object. Return default if object does not exist or error.
-    template<typename t> t Get(const char* name, t default_value = t()) const;
-
-    // Set named object. Return true if success, false otherwise
-    template<typename t> bool Set(const char* name, t const& value);
-
-    // Get child properties
-    UnencryptedProperties GetChild(const char* name) const;
-
-    // Remove named object
-    bool Remove(const char* name);
-
-    // Does folder exist?
-    bool OK() const;
-
-private:
-    UnencryptedProperties();
-    std::string folder_;
-};
-
-
-template<typename t> t UnencryptedProperties::Get(const char* name,
-                                                  t default_value) const
-{
-    if (!OK()) return default_value;
-    t value = default_value;
-    std::ifstream(folder_ + "/" + name) >> value;
-    return value;
-}
-
-template<typename t> bool UnencryptedProperties::Set(const char* name,
-                                                     t const& value)
-{
-    if (!OK()) return false;
-    std::ofstream o(folder_ + "/" + name);
-    o << value;
-    return !o.fail();
-}
-
-// Specialized getters/setters for strings
-template<> std::string UnencryptedProperties::Get(const char* name,
-                                      std::string default_value) const;
-
-template<> bool UnencryptedProperties::Set(const char* name,
-                                           std::string const& value);
index bd119e3..c7a86fa 100644 (file)
 #ifndef _WIPE_H_
 #define _WIPE_H_
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #include "ext4_utils.h"
 
 /* Set WIPE_IS_SUPPORTED to 1 if the current platform supports
@@ -30,4 +34,8 @@
 
 int wipe_block_device(int fd, s64 len);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
index 22e1200..647c390 100644 (file)
@@ -11,14 +11,17 @@ LOCAL_STATIC_LIBRARIES := \
     libsparse_host \
     libz
 LOCAL_C_INCLUDES := external/f2fs-tools/include external/f2fs-tools/mkfs
+LOCAL_CFLAGS := -Wno-unused-parameter
 include $(BUILD_HOST_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := f2fs_ioutils.c
 LOCAL_C_INCLUDES := external/f2fs-tools/include external/f2fs-tools/mkfs
+LOCAL_CFLAGS := -Wno-unused-parameter
 LOCAL_STATIC_LIBRARIES := \
+    libselinux \
     libsparse_host \
-    libext2_uuid_host \
+    libext2_uuid-host \
     libz
 LOCAL_MODULE := libf2fs_ioutils_host
 include $(BUILD_HOST_STATIC_LIBRARY)
@@ -63,6 +66,7 @@ include $(CLEAR_VARS)
 LOCAL_MODULE := libf2fs_utils_static
 LOCAL_SRC_FILES := f2fs_utils.c
 LOCAL_C_INCLUDES := external/f2fs-tools/include external/f2fs-tools/mkfs
+LOCAL_CFLAGS := -Wno-unused-parameter
 LOCAL_STATIC_LIBRARIES := \
     libsparse_static
 include $(BUILD_STATIC_LIBRARY)
index a050cf8..01efd53 100644 (file)
 
 #define _LARGEFILE64_SOURCE
 
+#include <assert.h>
 #include <asm/types.h>
+#include <dlfcn.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <linux/fs.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <fcntl.h>
-#include <dlfcn.h>
-
-#include <assert.h>
+#include <unistd.h>
 
 #include <f2fs_fs.h>
 #include <f2fs_format_utils.h>
@@ -98,7 +98,10 @@ static int dev_write_fd(void *buf, __u64 offset, size_t len)
 {
        if (lseek64(config.fd, (off64_t)offset, SEEK_SET) < 0)
                return -1;
-       if (write(config.fd, buf, len) != len)
+       ssize_t written = write(config.fd, buf, len);
+       if (written == -1)
+               return -1;
+       if ((size_t)written != len)
                return -1;
        return 0;
 }
index 4c92622..6254c08 100644 (file)
@@ -65,7 +65,7 @@ int make_f2fs_sparse_fd(int fd, long long len,
        }
        reset_f2fs_info();
        f2fs_init_configuration(&config);
-       len &= ~((__u64)F2FS_BLKSIZE);
+       len &= ~((__u64)(F2FS_BLKSIZE - 1));
        config.total_sectors = len / config.sector_size;
        config.start_sector = 0;
        f2fs_sparse_file = sparse_file_new(F2FS_BLKSIZE, len);
index 8404266..1e0dd87 100644 (file)
@@ -17,8 +17,9 @@
 #include <fcntl.h>
 #include <libgen.h>
 #include <stdio.h>
-#include <unistd.h>
 #include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 #if defined(__linux__)
 #include <linux/fs.h>
@@ -26,6 +27,8 @@
 #include <sys/disk.h>
 #endif
 
+#include "make_f2fs.h"
+
 #ifndef USE_MINGW /* O_BINARY is windows-specific flag */
 #define O_BINARY 0
 #endif
similarity index 65%
rename from timeinfo/Android.mk
rename to iotop/Android.mk
index 7651762..414967c 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 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.
 # 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.
-# Copyright The Android Open Source Project
-
-LOCAL_PATH := $(call my-dir)
 
+LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
-LOCAL_MODULE := timeinfo
-LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := eng
-LOCAL_SRC_FILES := timeinfo.cpp
-LOCAL_SHARED_LIBRARIES := libhardware_legacy libutils
+
+LOCAL_CLANG := true
+
+LOCAL_SRC_FILES := iotop.cpp tasklist.cpp taskstats.cpp
+
+LOCAL_MODULE := iotop
+
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_SHARED_LIBRARIES := libnl libbase
+
+LOCAL_CFLAGS := -Wall -Werror
+
 include $(BUILD_EXECUTABLE)
diff --git a/iotop/NOTICE b/iotop/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/iotop/iotop.cpp b/iotop/iotop.cpp
new file mode 100644 (file)
index 0000000..828fb32
--- /dev/null
@@ -0,0 +1,282 @@
+// 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 <getopt.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "tasklist.h"
+#include "taskstats.h"
+
+constexpr uint64_t NSEC_PER_SEC = 1000000000;
+
+static uint64_t BytesToKB(uint64_t bytes) {
+  return (bytes + 1024-1) / 1024;
+}
+
+static float TimeToTgidPercent(uint64_t ns, int time, const TaskStatistics& stats) {
+  float percent = ns / stats.threads() / (time * NSEC_PER_SEC / 100.0f);
+  return std::min(percent, 99.99f);
+}
+
+static void usage(char* myname) {
+  printf(
+      "Usage: %s [-h] [-P] [-d <delay>] [-n <cycles>] [-s <column>]\n"
+      "   -a  Show byte count instead of rate\n"
+      "   -d  Set the delay between refreshes in seconds.\n"
+      "   -h  Display this help screen.\n"
+      "   -m  Set the number of processes or threads to show\n"
+      "   -n  Set the number of refreshes before exiting.\n"
+      "   -P  Show processes instead of the default threads.\n"
+      "   -s  Set the column to sort by:\n"
+      "       pid, read, write, total, io, swap, sched, mem or delay.\n",
+      myname);
+}
+
+using Sorter = std::function<void(std::vector<TaskStatistics>&)>;
+static Sorter GetSorter(const std::string field) {
+  // Generic comparator
+  static auto comparator = [](auto& lhs, auto& rhs, auto field, bool ascending) -> bool {
+    auto a = (lhs.*field)();
+    auto b = (rhs.*field)();
+    if (a != b) {
+      // Sort by selected field
+      return ascending ^ (a < b);
+    } else {
+      // And then fall back to sorting by pid
+      return lhs.pid() < rhs.pid();
+    }
+  };
+
+  auto make_sorter = [](auto field, bool ascending) {
+    // Make closure for comparator on a specific field
+    using namespace std::placeholders;
+    auto bound_comparator = std::bind(comparator, _1, _2, field, ascending);
+
+    // Return closure to std::sort with specialized comparator
+    return [bound_comparator](auto& vector) {
+      return std::sort(vector.begin(), vector.end(), bound_comparator);
+    };
+  };
+
+  static const std::map<std::string, Sorter> sorters{
+      {"pid", make_sorter(&TaskStatistics::pid, false)},
+      {"read", make_sorter(&TaskStatistics::read, true)},
+      {"write", make_sorter(&TaskStatistics::write, true)},
+      {"total", make_sorter(&TaskStatistics::read_write, true)},
+      {"io", make_sorter(&TaskStatistics::delay_io, true)},
+      {"swap", make_sorter(&TaskStatistics::delay_swap, true)},
+      {"sched", make_sorter(&TaskStatistics::delay_sched, true)},
+      {"mem", make_sorter(&TaskStatistics::delay_mem, true)},
+      {"delay", make_sorter(&TaskStatistics::delay_total, true)},
+  };
+
+  auto it = sorters.find(field);
+  if (it == sorters.end()) {
+    return nullptr;
+  }
+  return it->second;
+}
+
+int main(int argc, char* argv[]) {
+  bool accumulated = false;
+  bool processes = false;
+  int delay = 1;
+  int cycles = -1;
+  int limit = -1;
+  Sorter sorter = GetSorter("total");
+
+  android::base::InitLogging(argv, android::base::StderrLogger);
+
+  while (1) {
+    int c;
+    static const option longopts[] = {
+        {"accumulated", 0, 0, 'a'},
+        {"delay", required_argument, 0, 'd'},
+        {"help", 0, 0, 'h'},
+        {"limit", required_argument, 0, 'm'},
+        {"iter", required_argument, 0, 'n'},
+        {"sort", required_argument, 0, 's'},
+        {"processes", 0, 0, 'P'},
+        {0, 0, 0, 0},
+    };
+    c = getopt_long(argc, argv, "ad:hm:n:Ps:", longopts, NULL);
+    if (c < 0) {
+      break;
+    }
+    switch (c) {
+    case 'a':
+      accumulated = true;
+      break;
+    case 'd':
+      delay = atoi(optarg);
+      break;
+    case 'h':
+      usage(argv[0]);
+      return(EXIT_SUCCESS);
+    case 'm':
+      limit = atoi(optarg);
+      break;
+    case 'n':
+      cycles = atoi(optarg);
+      break;
+    case 's': {
+      sorter = GetSorter(optarg);
+      if (sorter == nullptr) {
+        LOG(ERROR) << "Invalid sort column \"" << optarg << "\"";
+        usage(argv[0]);
+        return(EXIT_FAILURE);
+      }
+      break;
+    }
+    case 'P':
+      processes = true;
+      break;
+    case '?':
+      usage(argv[0]);
+      return(EXIT_FAILURE);
+    default:
+      abort();
+    }
+  }
+
+  std::map<pid_t, std::vector<pid_t>> tgid_map;
+
+  TaskstatsSocket taskstats_socket;
+  taskstats_socket.Open();
+
+  std::unordered_map<pid_t, TaskStatistics> pid_stats;
+  std::unordered_map<pid_t, TaskStatistics> tgid_stats;
+  std::vector<TaskStatistics> stats;
+
+  bool first = true;
+  bool second = true;
+
+  while (true) {
+    stats.clear();
+    if (!TaskList::Scan(tgid_map)) {
+      LOG(FATAL) << "failed to scan tasks";
+    }
+    for (auto& tgid_it : tgid_map) {
+      pid_t tgid = tgid_it.first;
+      std::vector<pid_t>& pid_list = tgid_it.second;
+
+      TaskStatistics tgid_stats_new;
+      TaskStatistics tgid_stats_delta;
+
+      if (processes) {
+        // If printing processes, collect stats for the tgid which will
+        // hold delay accounting data across all threads, including
+        // ones that have exited.
+        if (!taskstats_socket.GetTgidStats(tgid, tgid_stats_new)) {
+          continue;
+        }
+        tgid_stats_delta = tgid_stats[tgid].Update(tgid_stats_new);
+      }
+
+      // Collect per-thread stats
+      for (pid_t pid : pid_list) {
+        TaskStatistics pid_stats_new;
+        if (!taskstats_socket.GetPidStats(pid, pid_stats_new)) {
+          continue;
+        }
+
+        TaskStatistics pid_stats_delta = pid_stats[pid].Update(pid_stats_new);
+
+        if (processes) {
+          tgid_stats_delta.AddPidToTgid(pid_stats_delta);
+        } else {
+          stats.push_back(pid_stats_delta);
+        }
+      }
+
+      if (processes) {
+        stats.push_back(tgid_stats_delta);
+      }
+    }
+
+    if (!first) {
+      sorter(stats);
+      if (!second) {
+        printf("\n");
+      }
+      if (accumulated) {
+        printf("%6s %-16s %20s %34s\n", "", "",
+            "---- IO (KiB) ----", "----------- delayed on ----------");
+      } else {
+        printf("%6s %-16s %20s %34s\n", "", "",
+            "--- IO (KiB/s) ---", "----------- delayed on ----------");
+      }
+      printf("%6s %-16s %6s %6s %6s  %-5s  %-5s  %-5s  %-5s  %-5s\n",
+          "PID",
+          "Command",
+          "read",
+          "write",
+          "total",
+          "IO",
+          "swap",
+          "sched",
+          "mem",
+          "total");
+      int n = limit;
+      const int delay_div = accumulated ? 1 : delay;
+      uint64_t total_read = 0;
+      uint64_t total_write = 0;
+      uint64_t total_read_write = 0;
+      for (const TaskStatistics& statistics : stats) {
+        total_read += statistics.read();
+        total_write += statistics.write();
+        total_read_write += statistics.read_write();
+
+        if (n == 0) {
+          continue;
+        } else if (n > 0) {
+          n--;
+        }
+
+        printf("%6d %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %5.2f%% %5.2f%% %5.2f%% %5.2f%% %5.2f%%\n",
+            statistics.pid(),
+            statistics.comm().c_str(),
+            BytesToKB(statistics.read()) / delay_div,
+            BytesToKB(statistics.write()) / delay_div,
+            BytesToKB(statistics.read_write()) / delay_div,
+            TimeToTgidPercent(statistics.delay_io(), delay, statistics),
+            TimeToTgidPercent(statistics.delay_swap(), delay, statistics),
+            TimeToTgidPercent(statistics.delay_sched(), delay, statistics),
+            TimeToTgidPercent(statistics.delay_mem(), delay, statistics),
+            TimeToTgidPercent(statistics.delay_total(), delay, statistics));
+      }
+      printf("%6s %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 "\n", "", "TOTAL",
+          BytesToKB(total_read) / delay_div,
+          BytesToKB(total_write) / delay_div,
+          BytesToKB(total_read_write) / delay_div);
+
+      second = false;
+
+      if (cycles > 0 && --cycles == 0) break;
+    }
+    first = false;
+    sleep(delay);
+  }
+
+  return 0;
+}
diff --git a/iotop/tasklist.cpp b/iotop/tasklist.cpp
new file mode 100644 (file)
index 0000000..f81143f
--- /dev/null
@@ -0,0 +1,65 @@
+// 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 <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+
+#include "tasklist.h"
+
+template<typename Func>
+static bool ScanPidsInDir(std::string name, Func f) {
+  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(name.c_str()), closedir);
+  if (!dir) {
+    return false;
+  }
+
+  dirent* entry;
+  while ((entry = readdir(dir.get())) != nullptr) {
+    if (isdigit(entry->d_name[0])) {
+      pid_t pid = atoi(entry->d_name);
+      f(pid);
+    }
+  }
+
+  return true;
+}
+
+bool TaskList::Scan(std::map<pid_t, std::vector<pid_t>>& tgid_map) {
+  tgid_map.clear();
+
+  return ScanPidsInDir("/proc", [&tgid_map](pid_t tgid) {
+    std::vector<pid_t> pid_list;
+    if (ScanPid(tgid, pid_list)) {
+      tgid_map.insert({tgid, pid_list});
+    }
+  });
+}
+
+bool TaskList::ScanPid(pid_t tgid, std::vector<pid_t>& pid_list) {
+  std::string filename = android::base::StringPrintf("/proc/%d/task", tgid);
+
+  return ScanPidsInDir(filename, [&pid_list](pid_t pid) {
+    pid_list.push_back(pid);
+  });
+}
diff --git a/iotop/tasklist.h b/iotop/tasklist.h
new file mode 100644 (file)
index 0000000..1a19c8f
--- /dev/null
@@ -0,0 +1,30 @@
+// 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 <map>
+#include <vector>
+
+#ifndef _IOTOP_TASKLIST_H
+#define _IOTOP_TASKLIST_H
+
+class TaskList {
+public:
+  static bool Scan(std::map<pid_t, std::vector<pid_t>>&);
+
+private:
+  TaskList() {}
+  static bool ScanPid(pid_t pid, std::vector<pid_t>&);
+};
+
+#endif // _IOTOP_TASKLIST_H
diff --git a/iotop/taskstats.cpp b/iotop/taskstats.cpp
new file mode 100644 (file)
index 0000000..60c933d
--- /dev/null
@@ -0,0 +1,238 @@
+// 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 <linux/taskstats.h>
+#include <netlink/socket.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+
+#include <algorithm>
+#include <memory>
+
+#include <android-base/logging.h>
+
+#include "taskstats.h"
+
+TaskstatsSocket::TaskstatsSocket()
+    : nl_(nullptr, nl_socket_free), family_id_(0) {
+}
+
+bool TaskstatsSocket::Open() {
+  std::unique_ptr<nl_sock, decltype(&nl_socket_free)> nl(
+      nl_socket_alloc(), nl_socket_free);
+  if (!nl.get()) {
+    LOG(FATAL) << "Failed to allocate netlink socket";
+  }
+
+  int ret = genl_connect(nl.get());
+  if (ret < 0) {
+    LOG(FATAL) << nl_geterror(ret) << std::endl << "Unable to open netlink socket (are you root?)";
+  }
+
+  int family_id = genl_ctrl_resolve(nl.get(), TASKSTATS_GENL_NAME);
+  if (family_id < 0) {
+    LOG(FATAL) << nl_geterror(family_id) << std::endl << "Unable to determine taskstats family id (does your kernel support taskstats?)";
+  }
+
+  nl_ = std::move(nl);
+  family_id_ = family_id;
+
+  return true;
+}
+
+void TaskstatsSocket::Close() {
+  nl_.reset();
+}
+
+struct TaskStatsRequest {
+  pid_t requested_pid;
+  taskstats stats;
+};
+
+static pid_t ParseAggregateTaskStats(nlattr* attr, int attr_size,
+                                     taskstats* stats) {
+  pid_t received_pid = -1;
+  nla_for_each_attr(attr, attr, attr_size, attr_size) {
+    switch (nla_type(attr)) {
+    case TASKSTATS_TYPE_PID:
+    case TASKSTATS_TYPE_TGID:
+      received_pid = nla_get_u32(attr);
+      break;
+    case TASKSTATS_TYPE_STATS:
+    {
+      int len = static_cast<int>(sizeof(*stats));
+      len = std::min(len, nla_len(attr));
+      nla_memcpy(stats, attr, len);
+      return received_pid;
+    }
+    default:
+      LOG(ERROR) << "unexpected attribute inside AGGR";
+      return -1;
+    }
+  }
+
+  return -1;
+}
+
+static int ParseTaskStats(nl_msg* msg, void* arg) {
+  TaskStatsRequest* taskstats_request = static_cast<TaskStatsRequest*>(arg);
+  genlmsghdr* gnlh = static_cast<genlmsghdr*>(nlmsg_data(nlmsg_hdr(msg)));
+  nlattr* attr = genlmsg_attrdata(gnlh, 0);
+  int remaining = genlmsg_attrlen(gnlh, 0);
+
+  nla_for_each_attr(attr, attr, remaining, remaining) {
+    switch (nla_type(attr)) {
+    case TASKSTATS_TYPE_AGGR_PID:
+    case TASKSTATS_TYPE_AGGR_TGID:
+    {
+      nlattr* nested_attr = static_cast<nlattr*>(nla_data(attr));
+      taskstats stats;
+      pid_t ret;
+
+      ret = ParseAggregateTaskStats(nested_attr, nla_len(attr), &stats);
+      if (ret < 0) {
+        LOG(ERROR) << "Bad AGGR_PID contents";
+      } else if (ret == taskstats_request->requested_pid) {
+        taskstats_request->stats = stats;
+      } else {
+        LOG(WARNING) << "got taskstats for unexpected pid " << ret <<
+            " (expected " << taskstats_request->requested_pid << ", continuing...";
+      }
+      break;
+    }
+    case TASKSTATS_TYPE_NULL:
+      break;
+    default:
+      LOG(ERROR) << "unexpected attribute in taskstats";
+    }
+  }
+  return NL_OK;
+}
+
+bool TaskstatsSocket::GetStats(int pid, int type, TaskStatistics& stats) {
+  TaskStatsRequest taskstats_request = TaskStatsRequest();
+  taskstats_request.requested_pid = pid;
+
+  std::unique_ptr<nl_msg, decltype(&nlmsg_free)> message(nlmsg_alloc(),
+                                                         nlmsg_free);
+
+  genlmsg_put(message.get(), NL_AUTO_PID, NL_AUTO_SEQ, family_id_, 0, 0,
+              TASKSTATS_CMD_GET, TASKSTATS_VERSION);
+  nla_put_u32(message.get(), type, pid);
+
+  int result = nl_send_auto_complete(nl_.get(), message.get());
+  if (result < 0) {
+    return false;
+  }
+
+  std::unique_ptr<nl_cb, decltype(&nl_cb_put)> callbacks(
+      nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
+  nl_cb_set(callbacks.get(), NL_CB_VALID, NL_CB_CUSTOM, &ParseTaskStats,
+            static_cast<void*>(&taskstats_request));
+
+  result = nl_recvmsgs(nl_.get(), callbacks.get());
+  if (result < 0) {
+    return false;
+  }
+  nl_wait_for_ack(nl_.get());
+
+  stats = TaskStatistics(taskstats_request.stats);
+
+  return true;
+}
+
+bool TaskstatsSocket::GetPidStats(int pid, TaskStatistics& stats) {
+  return GetStats(pid, TASKSTATS_CMD_ATTR_PID, stats);
+}
+
+bool TaskstatsSocket::GetTgidStats(int tgid, TaskStatistics& stats) {
+  bool ret = GetStats(tgid, TASKSTATS_CMD_ATTR_TGID, stats);
+  if (ret) {
+    stats.set_pid(tgid);
+  }
+  return ret;
+}
+
+TaskStatistics::TaskStatistics(const taskstats& taskstats_stats) {
+  comm_ = std::string(taskstats_stats.ac_comm);
+  pid_ = taskstats_stats.ac_pid;
+
+  uid_ = taskstats_stats.ac_uid;
+  gid_ = taskstats_stats.ac_gid;
+  pid_ = taskstats_stats.ac_pid;
+  ppid_ = taskstats_stats.ac_ppid;
+
+  cpu_delay_count_ = taskstats_stats.cpu_count;
+  cpu_delay_ns_ = taskstats_stats.cpu_delay_total;
+
+  block_io_delay_count_ = taskstats_stats.blkio_count;
+  block_io_delay_ns_ = taskstats_stats.blkio_delay_total;
+
+  swap_in_delay_count_ = taskstats_stats.swapin_count;
+  swap_in_delay_ns_ = taskstats_stats.swapin_delay_total;
+
+  reclaim_delay_count_ = taskstats_stats.freepages_count;
+  reclaim_delay_ns_ = taskstats_stats.freepages_delay_total;
+
+  total_delay_ns_ =
+      cpu_delay_ns_ + block_io_delay_ns_ + swap_in_delay_ns_ + reclaim_delay_ns_;
+
+  cpu_time_real_ = taskstats_stats.cpu_run_real_total;
+  cpu_time_virtual_ = taskstats_stats.cpu_run_virtual_total;
+
+  read_bytes_ = taskstats_stats.read_bytes;
+  write_bytes_ = taskstats_stats.write_bytes;
+  read_write_bytes_ = read_bytes_ + write_bytes_;
+  cancelled_write_bytes_ = taskstats_stats.cancelled_write_bytes;
+  threads_ = 1;
+}
+
+void TaskStatistics::AddPidToTgid(const TaskStatistics& pid_statistics) {
+  // tgid statistics already contain delay values totalled across all pids
+  // only add IO statistics
+  read_bytes_            += pid_statistics.read_bytes_;
+  write_bytes_           += pid_statistics.write_bytes_;
+  read_write_bytes_      += pid_statistics.read_write_bytes_;
+  cancelled_write_bytes_ += pid_statistics.cancelled_write_bytes_;
+  if (pid_ == pid_statistics.pid_) {
+    comm_ = pid_statistics.comm_;
+    uid_ = pid_statistics.uid_;
+    gid_ = pid_statistics.pid_;
+    ppid_ = pid_statistics.ppid_;
+  } else {
+    threads_++;
+  }
+}
+
+// Store new statistics and return the delta from the old statistics
+TaskStatistics TaskStatistics::Update(const TaskStatistics& new_statistics) {
+  TaskStatistics delta = new_statistics;
+  delta.cpu_delay_count_       -= cpu_delay_count_;
+  delta.cpu_delay_ns_          -= cpu_delay_ns_;
+  delta.block_io_delay_count_  -= block_io_delay_count_;
+  delta.block_io_delay_ns_     -= block_io_delay_ns_;
+  delta.swap_in_delay_count_   -= swap_in_delay_count_;
+  delta.swap_in_delay_ns_      -= swap_in_delay_ns_;
+  delta.reclaim_delay_count_   -= reclaim_delay_count_;
+  delta.reclaim_delay_ns_      -= reclaim_delay_ns_;
+  delta.total_delay_ns_        -= total_delay_ns_;
+  delta.cpu_time_real_         -= cpu_time_real_;
+  delta.cpu_time_virtual_      -= cpu_time_virtual_;
+  delta.read_bytes_            -= read_bytes_;
+  delta.write_bytes_           -= write_bytes_;
+  delta.read_write_bytes_      -= read_write_bytes_;
+  delta.cancelled_write_bytes_ -= cancelled_write_bytes_;
+  *this = new_statistics;
+  return delta;
+}
diff --git a/iotop/taskstats.h b/iotop/taskstats.h
new file mode 100644 (file)
index 0000000..f5ad2d2
--- /dev/null
@@ -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 <memory>
+#include <string>
+
+#include <stdint.h>
+
+#ifndef _IOTOP_TASKSTATS_H
+#define _IOTOP_TASKSTATS_H
+
+struct nl_sock;
+struct taskstats;
+
+class TaskStatistics {
+public:
+  TaskStatistics(const taskstats&);
+  TaskStatistics() = default;
+  TaskStatistics(const TaskStatistics&) = default;
+  void AddPidToTgid(const TaskStatistics&);
+  TaskStatistics Update(const TaskStatistics&);
+
+  pid_t pid() const { return pid_; }
+  const std::string& comm() const { return comm_; }
+  uint64_t read() const { return read_bytes_; }
+  uint64_t write() const { return write_bytes_; }
+  uint64_t read_write() const { return read_write_bytes_; }
+  uint64_t delay_io() const { return block_io_delay_ns_; }
+  uint64_t delay_swap() const { return swap_in_delay_ns_; }
+  uint64_t delay_sched() const { return cpu_delay_ns_; }
+  uint64_t delay_mem() const { return reclaim_delay_ns_; }
+  uint64_t delay_total() const { return total_delay_ns_; }
+  int threads() const { return threads_; }
+
+  void set_pid(pid_t pid) { pid_ = pid; }
+
+private:
+  std::string comm_;
+  uid_t uid_;
+  gid_t gid_;
+  pid_t pid_;
+  pid_t ppid_;
+
+  uint64_t cpu_delay_count_;
+  uint64_t cpu_delay_ns_;
+
+  uint64_t block_io_delay_count_;
+  uint64_t block_io_delay_ns_;
+
+  uint64_t swap_in_delay_count_;
+  uint64_t swap_in_delay_ns_;
+
+  uint64_t reclaim_delay_count_;
+  uint64_t reclaim_delay_ns_;
+
+  uint64_t total_delay_ns_;
+
+  uint64_t cpu_time_real_;
+  uint64_t cpu_time_virtual_;
+
+  uint64_t read_bytes_;
+  uint64_t write_bytes_;
+  uint64_t read_write_bytes_;
+  uint64_t cancelled_write_bytes_;
+
+  int threads_;
+};
+
+class TaskstatsSocket {
+public:
+  TaskstatsSocket();
+  bool Open();
+  void Close();
+
+  bool GetPidStats(int, TaskStatistics&);
+  bool GetTgidStats(int, TaskStatistics&);
+private:
+  bool GetStats(int, int, TaskStatistics& stats);
+  std::unique_ptr<nl_sock, void(*)(nl_sock*)> nl_;
+  int family_id_;
+};
+
+#endif // _IOTOP_TASKSTATS_H
diff --git a/kexec_tools/NOTICE b/kexec_tools/NOTICE
new file mode 100644 (file)
index 0000000..9109eed
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2013-2015, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index f34576d..b7825c4 100644 (file)
 # limitations under the License.
 
 LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
 
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := ksminfo.c lookup3.c
-
-LOCAL_C_INCLUDES := $(call include-path-for, libpagemap)
-
 LOCAL_SHARED_LIBRARIES := libpagemap
-
 LOCAL_MODULE := ksminfo
-
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-
 LOCAL_MODULE_TAGS := debug
-
 include $(BUILD_EXECUTABLE)
index 4ac39bc..278692f 100644 (file)
@@ -189,7 +189,6 @@ exit:
 
 static int read_pages(struct ksm_pages *kp, pm_map_t **maps, size_t num_maps, uint8_t pr_flags) {
     size_t i, j, k;
-    size_t len;
     uint64_t *pagemap;
     size_t map_len;
     uint64_t flags;
@@ -251,7 +250,7 @@ static int read_pages(struct ksm_pages *kp, pm_map_t **maps, size_t num_maps, ui
                 fprintf(stderr, "warning: could not lseek to 0x%08lx\n", vaddr);
                 continue;
             }
-            len = read(fd, data, pm_kernel_pagesize(ker));
+            ssize_t len = read(fd, data, pm_kernel_pagesize(ker));
             if (len != pm_kernel_pagesize(ker)) {
                 fprintf(stderr, "warning: could not read page at 0x%08lx\n", vaddr);
                 continue;
index 2c96672..220f4e3 100644 (file)
@@ -23,4 +23,6 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 
 LOCAL_MODULE_TAGS := debug
 
+LOCAL_CFLAGS := -Wno-unused-parameter
+
 include $(BUILD_EXECUTABLE)
diff --git a/libfec/Android.mk b/libfec/Android.mk
new file mode 100644 (file)
index 0000000..45fb19e
--- /dev/null
@@ -0,0 +1,58 @@
+# Copyright 2015 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+
+common_cflags := -Wall -Werror -O3
+
+common_c_includes := \
+    $(LOCAL_PATH)/include \
+    external/fec \
+    system/extras/ext4_utils \
+    system/extras/squashfs_utils
+
+common_src_files := \
+    fec_open.cpp \
+    fec_read.cpp \
+    fec_verity.cpp \
+    fec_process.cpp
+
+common_static_libraries := \
+    libmincrypt \
+    libcrypto_static \
+    libcutils \
+    libbase
+
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := $(common_cflags)
+LOCAL_C_INCLUDES := $(common_c_includes)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_MODULE := libfec
+LOCAL_SRC_FILES := $(common_src_files)
+LOCAL_STATIC_LIBRARIES := \
+    libfec_rs \
+    libext4_utils_static \
+    libsquashfs_utils \
+    libcutils \
+    $(common_static_libraries)
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := $(common_cflags) -D_GNU_SOURCE -DFEC_NO_KLOG
+LOCAL_C_INCLUDES := $(common_c_includes)
+LOCAL_CLANG := true
+ifeq ($(HOST_OS),linux)
+LOCAL_SANITIZE := integer
+endif
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_MODULE := libfec_host
+LOCAL_SRC_FILES := $(common_src_files)
+LOCAL_STATIC_LIBRARIES := \
+    libfec_rs_host \
+    libext4_utils_host \
+    libsquashfs_utils_host \
+    $(common_static_libraries)
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(LOCAL_PATH)/test/Android.mk
diff --git a/libfec/NOTICE b/libfec/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/libfec/fec_open.cpp b/libfec/fec_open.cpp
new file mode 100644 (file)
index 0000000..0e41bf4
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+extern "C" {
+    #include <squashfs_utils.h>
+    #include <ext4_sb.h>
+}
+
+#if defined(__linux__)
+    #include <linux/fs.h>
+#elif defined(__APPLE__)
+    #include <sys/disk.h>
+    #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
+    #define fdatasync(fd) fcntl((fd), F_FULLFSYNC)
+#endif
+
+#include "fec_private.h"
+
+/* used by `find_offset'; returns metadata size for a file size `size' and
+   `roots' Reed-Solomon parity bytes */
+using size_func = uint64_t (*)(uint64_t size, int roots);
+
+/* performs a binary search to find a metadata offset from a file so that
+   the metadata size matches function `get_real_size(size, roots)', using
+   the approximate size returned by `get_appr_size' as a starting point */
+static int find_offset(uint64_t file_size, int roots, uint64_t *offset,
+        size_func get_appr_size, size_func get_real_size)
+{
+    check(offset);
+    check(get_appr_size);
+    check(get_real_size);
+
+    if (file_size % FEC_BLOCKSIZE) {
+        /* must be a multiple of block size */
+        error("file size not multiple of " stringify(FEC_BLOCKSIZE));
+        errno = EINVAL;
+        return -1;
+    }
+
+    uint64_t mi = get_appr_size(file_size, roots);
+    uint64_t lo = file_size - mi * 2;
+    uint64_t hi = file_size - mi / 2;
+
+    while (lo < hi) {
+        mi = ((hi + lo) / (2 * FEC_BLOCKSIZE)) * FEC_BLOCKSIZE;
+        uint64_t total = mi + get_real_size(mi, roots);
+
+        if (total < file_size) {
+            lo = mi + FEC_BLOCKSIZE;
+        } else if (total > file_size) {
+            hi = mi;
+        } else {
+            *offset = mi;
+            debug("file_size = %" PRIu64 " -> offset = %" PRIu64, file_size,
+                mi);
+            return 0;
+        }
+    }
+
+    warn("could not determine offset");
+    errno = ERANGE;
+    return -1;
+}
+
+/* returns verity metadata size for a `size' byte file */
+static uint64_t get_verity_size(uint64_t size, int)
+{
+    return VERITY_METADATA_SIZE + verity_get_size(size, NULL, NULL);
+}
+
+/* computes the verity metadata offset for a file with size `f->size' */
+static int find_verity_offset(fec_handle *f, uint64_t *offset)
+{
+    check(f);
+    check(offset);
+
+    return find_offset(f->data_size, 0, offset, get_verity_size,
+                get_verity_size);
+}
+
+/* attempts to read and validate an ecc header from file position `offset' */
+static int parse_ecc_header(fec_handle *f, uint64_t offset)
+{
+    check(f);
+    check(f->ecc.rsn > 0 && f->ecc.rsn < FEC_RSM);
+    check(f->size > sizeof(fec_header));
+
+    debug("offset = %" PRIu64, offset);
+
+    if (offset > f->size - sizeof(fec_header)) {
+        return -1;
+    }
+
+    fec_header header;
+
+    /* there's obviously no ecc data at this point, so there is no need to
+       call fec_pread to access this data */
+    if (!raw_pread(f, &header, sizeof(fec_header), offset)) {
+        error("failed to read: %s", strerror(errno));
+        return -1;
+    }
+
+    /* move offset back to the beginning of the block for validating header */
+    offset -= offset % FEC_BLOCKSIZE;
+
+    if (header.magic != FEC_MAGIC) {
+        return -1;
+    }
+    if (header.version != FEC_VERSION) {
+        error("unsupported ecc version: %u", header.version);
+        return -1;
+    }
+    if (header.size != sizeof(fec_header)) {
+        error("unexpected ecc header size: %u", header.size);
+        return -1;
+    }
+    if (header.roots == 0 || header.roots >= FEC_RSM) {
+        error("invalid ecc roots: %u", header.roots);
+        return -1;
+    }
+    if (f->ecc.roots != (int)header.roots) {
+        error("unexpected number of roots: %d vs %u", f->ecc.roots,
+            header.roots);
+        return -1;
+    }
+    if (header.fec_size % header.roots ||
+            header.fec_size % FEC_BLOCKSIZE) {
+        error("inconsistent ecc size %u", header.fec_size);
+        return -1;
+    }
+    /* structure: data | ecc | header */
+    if (offset < header.fec_size ||
+            offset - header.fec_size != header.inp_size) {
+        error("unexpected input size: %" PRIu64 " vs %" PRIu64, offset,
+            header.inp_size);
+        return -1;
+    }
+
+    f->data_size = header.inp_size;
+    f->ecc.blocks = fec_div_round_up(f->data_size, FEC_BLOCKSIZE);
+    f->ecc.rounds = fec_div_round_up(f->ecc.blocks, f->ecc.rsn);
+
+    if (header.fec_size !=
+            (uint32_t)f->ecc.rounds * f->ecc.roots * FEC_BLOCKSIZE) {
+        error("inconsistent ecc size %u", header.fec_size);
+        return -1;
+    }
+
+    f->ecc.size = header.fec_size;
+    f->ecc.start = header.inp_size;
+
+    /* validate encoding data; caller may opt not to use it if invalid */
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+
+    uint8_t buf[FEC_BLOCKSIZE];
+    uint32_t n = 0;
+    uint32_t len = FEC_BLOCKSIZE;
+
+    while (n < f->ecc.size) {
+        if (len > f->ecc.size - n) {
+            len = f->ecc.size - n;
+        }
+
+        if (!raw_pread(f, buf, len, f->ecc.start + n)) {
+            error("failed to read ecc: %s", strerror(errno));
+            return -1;
+        }
+
+        SHA256_Update(&ctx, buf, len);
+        n += len;
+    }
+
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+    SHA256_Final(hash, &ctx);
+
+    f->ecc.valid = !memcmp(hash, header.hash, SHA256_DIGEST_LENGTH);
+
+    if (!f->ecc.valid) {
+        warn("ecc data not valid");
+    }
+
+    return 0;
+}
+
+/* attempts to read an ecc header from `offset', and checks for a backup copy
+   at the end of the block if the primary header is not valid */
+static int parse_ecc(fec_handle *f, uint64_t offset)
+{
+    check(f);
+    check(offset % FEC_BLOCKSIZE == 0);
+    check(offset < UINT64_MAX - FEC_BLOCKSIZE);
+
+    /* check the primary header at the beginning of the block */
+    if (parse_ecc_header(f, offset) == 0) {
+        return 0;
+    }
+
+    /* check the backup header at the end of the block */
+    if (parse_ecc_header(f, offset + FEC_BLOCKSIZE - sizeof(fec_header)) == 0) {
+        warn("using backup ecc header");
+        return 0;
+    }
+
+    return -1;
+}
+
+/* reads the squashfs superblock and returns the size of the file system in
+   `offset' */
+static int get_squashfs_size(fec_handle *f, uint64_t *offset)
+{
+    check(f);
+    check(offset);
+
+    size_t sb_size = squashfs_get_sb_size();
+    check(sb_size <= SSIZE_MAX);
+
+    uint8_t buffer[sb_size];
+
+    if (fec_pread(f, buffer, sizeof(buffer), 0) != (ssize_t)sb_size) {
+        error("failed to read superblock: %s", strerror(errno));
+        return -1;
+    }
+
+    squashfs_info sq;
+
+    if (squashfs_parse_sb_buffer(buffer, &sq) < 0) {
+        error("failed to parse superblock: %s", strerror(errno));
+        return -1;
+    }
+
+    *offset = sq.bytes_used_4K_padded;
+    return 0;
+}
+
+/* reads the ext4 superblock and returns the size of the file system in
+   `offset' */
+static int get_ext4_size(fec_handle *f, uint64_t *offset)
+{
+    check(f);
+    check(f->size > 1024 + sizeof(ext4_super_block));
+    check(offset);
+
+    ext4_super_block sb;
+
+    if (fec_pread(f, &sb, sizeof(sb), 1024) != sizeof(sb)) {
+        error("failed to read superblock: %s", strerror(errno));
+        return -1;
+    }
+
+    fs_info info;
+    info.len = 0;  /* only len is set to 0 to ask the device for real size. */
+
+    if (ext4_parse_sb(&sb, &info) != 0) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    *offset = info.len;
+    return 0;
+}
+
+/* attempts to determine file system size, if no fs type is specified in
+   `f->flags', tries all supported types, and returns the size in `offset' */
+static int get_fs_size(fec_handle *f, uint64_t *offset)
+{
+    check(f);
+    check(offset);
+
+    if (f->flags & FEC_FS_EXT4) {
+        return get_ext4_size(f, offset);
+    } else if (f->flags & FEC_FS_SQUASH) {
+        return get_squashfs_size(f, offset);
+    } else {
+        /* try all alternatives */
+        int rc = get_ext4_size(f, offset);
+
+        if (rc == 0) {
+            debug("found ext4fs");
+            return rc;
+        }
+
+        rc = get_squashfs_size(f, offset);
+
+        if (rc == 0) {
+            debug("found squashfs");
+        }
+
+        return rc;
+    }
+}
+
+/* locates, validates, and loads verity metadata from `f->fd' */
+static int load_verity(fec_handle *f)
+{
+    check(f);
+    debug("size = %" PRIu64 ", flags = %d", f->data_size, f->flags);
+
+    uint64_t offset = f->data_size - VERITY_METADATA_SIZE;
+
+    /* verity header is at the end of the data area */
+    if (verity_parse_header(f, offset) == 0) {
+        debug("found at %" PRIu64 " (start %" PRIu64 ")", offset,
+            f->verity.hash_start);
+        return 0;
+    }
+
+    debug("trying legacy formats");
+
+    /* legacy format at the end of the partition */
+    if (find_verity_offset(f, &offset) == 0 &&
+            verity_parse_header(f, offset) == 0) {
+        debug("found at %" PRIu64 " (start %" PRIu64 ")", offset,
+            f->verity.hash_start);
+        return 0;
+    }
+
+    /* legacy format after the file system, but not at the end */
+    int rc = get_fs_size(f, &offset);
+
+    if (rc == 0) {
+        debug("file system size = %" PRIu64, offset);
+        rc = verity_parse_header(f, offset);
+
+        if (rc == 0) {
+            debug("found at %" PRIu64 " (start %" PRIu64 ")", offset,
+                f->verity.hash_start);
+        }
+    }
+
+    return rc;
+}
+
+/* locates, validates, and loads ecc data from `f->fd' */
+static int load_ecc(fec_handle *f)
+{
+    check(f);
+    debug("size = %" PRIu64, f->data_size);
+
+    uint64_t offset = f->data_size - FEC_BLOCKSIZE;
+
+    if (parse_ecc(f, offset) == 0) {
+        debug("found at %" PRIu64 " (start %" PRIu64 ")", offset,
+            f->ecc.start);
+        return 0;
+    }
+
+    return -1;
+}
+
+/* sets `f->size' to the size of the file or block device */
+static int get_size(fec_handle *f)
+{
+    check(f);
+
+    struct stat st;
+
+    if (fstat(f->fd, &st) == -1) {
+        error("fstat failed: %s", strerror(errno));
+        return -1;
+    }
+
+    if (S_ISBLK(st.st_mode)) {
+        debug("block device");
+
+        if (ioctl(f->fd, BLKGETSIZE64, &f->size) == -1) {
+            error("ioctl failed: %s", strerror(errno));
+            return -1;
+        }
+    } else if (S_ISREG(st.st_mode)) {
+        debug("file");
+        f->size = st.st_size;
+    } else {
+        error("unsupported type %d", (int)st.st_mode);
+        errno = EACCES;
+        return -1;
+    }
+
+    return 0;
+}
+
+/* clears fec_handle fiels to safe values */
+static void reset_handle(fec_handle *f)
+{
+    f->fd = -1;
+    f->flags = 0;
+    f->mode = 0;
+    f->errors = 0;
+    f->data_size = 0;
+    f->pos = 0;
+    f->size = 0;
+
+    memset(&f->ecc, 0, sizeof(f->ecc));
+    memset(&f->verity, 0, sizeof(f->verity));
+}
+
+/* closes and flushes `f->fd' and releases any memory allocated for `f' */
+int fec_close(struct fec_handle *f)
+{
+    check(f);
+
+    if (f->fd != -1) {
+        if (f->mode & O_RDWR && fdatasync(f->fd) == -1) {
+            warn("fdatasync failed: %s", strerror(errno));
+        }
+
+        TEMP_FAILURE_RETRY(close(f->fd));
+    }
+
+    if (f->verity.hash) {
+        delete[] f->verity.hash;
+    }
+    if (f->verity.salt) {
+        delete[] f->verity.salt;
+    }
+    if (f->verity.table) {
+        delete[] f->verity.table;
+    }
+
+    pthread_mutex_destroy(&f->mutex);
+
+    reset_handle(f);
+    delete f;
+
+    return 0;
+}
+
+/* populates `data' from the internal data in `f', returns a value <0 if verity
+   metadata is not available in `f->fd' */
+int fec_verity_get_metadata(struct fec_handle *f, struct fec_verity_metadata *data)
+{
+    check(f);
+    check(data);
+
+    if (!f->verity.metadata_start) {
+        return -1;
+    }
+
+    check(f->data_size < f->size);
+    check(f->data_size <= f->verity.hash_start);
+    check(f->data_size <= f->verity.metadata_start);
+    check(f->verity.table);
+
+    data->disabled = f->verity.disabled;
+    data->data_size = f->data_size;
+    memcpy(data->signature, f->verity.header.signature,
+        sizeof(data->signature));
+    memcpy(data->ecc_signature, f->verity.ecc_header.signature,
+        sizeof(data->ecc_signature));
+    data->table = f->verity.table;
+    data->table_length = f->verity.header.length;
+
+    return 0;
+}
+
+/* populates `data' from the internal data in `f', returns a value <0 if ecc
+   metadata is not available in `f->fd' */
+int fec_ecc_get_metadata(struct fec_handle *f, struct fec_ecc_metadata *data)
+{
+    check(f);
+    check(data);
+
+    if (!f->ecc.start) {
+        return -1;
+    }
+
+    check(f->data_size < f->size);
+    check(f->ecc.start >= f->data_size);
+    check(f->ecc.start < f->size);
+    check(f->ecc.start % FEC_BLOCKSIZE == 0)
+
+    data->valid = f->ecc.valid;
+    data->roots = f->ecc.roots;
+    data->blocks = f->ecc.blocks;
+    data->rounds = f->ecc.rounds;
+    data->start = f->ecc.start;
+
+    return 0;
+}
+
+/* populates `data' from the internal status in `f' */
+int fec_get_status(struct fec_handle *f, struct fec_status *s)
+{
+    check(f);
+    check(s);
+
+    s->flags = f->flags;
+    s->mode = f->mode;
+    s->errors = f->errors;
+    s->data_size = f->data_size;
+    s->size = f->size;
+
+    return 0;
+}
+
+/* opens `path' using given options and returns a fec_handle in `handle' if
+   successful */
+int fec_open(struct fec_handle **handle, const char *path, int mode, int flags,
+        int roots)
+{
+    check(path);
+    check(handle);
+    check(roots > 0 && roots < FEC_RSM);
+
+    debug("path = %s, mode = %d, flags = %d, roots = %d", path, mode, flags,
+        roots);
+
+    if (mode & (O_CREAT | O_TRUNC | O_EXCL | O_WRONLY)) {
+        /* only reading and updating existing files is supported */
+        error("failed to open '%s': (unsupported mode %d)", path, mode);
+        errno = EACCES;
+        return -1;
+    }
+
+    fec::handle f(new (std::nothrow) fec_handle, fec_close);
+
+    if (unlikely(!f)) {
+        error("failed to allocate file handle");
+        errno = ENOMEM;
+        return -1;
+    }
+
+    reset_handle(f.get());
+
+    f->mode = mode;
+    f->ecc.roots = roots;
+    f->ecc.rsn = FEC_RSM - roots;
+    f->flags = flags;
+
+    if (unlikely(pthread_mutex_init(&f->mutex, NULL) != 0)) {
+        error("failed to create a mutex: %s", strerror(errno));
+        return -1;
+    }
+
+    f->fd = TEMP_FAILURE_RETRY(open(path, mode | O_CLOEXEC));
+
+    if (f->fd == -1) {
+        error("failed to open '%s': %s", path, strerror(errno));
+        return -1;
+    }
+
+    if (get_size(f.get()) == -1) {
+        error("failed to get size for '%s': %s", path, strerror(errno));
+        return -1;
+    }
+
+    f->data_size = f->size; /* until ecc and/or verity are loaded */
+
+    if (load_ecc(f.get()) == -1) {
+        debug("error-correcting codes not found from '%s'", path);
+    }
+
+    if (load_verity(f.get()) == -1) {
+        debug("verity metadata not found from '%s'", path);
+    }
+
+    *handle = f.release();
+    return 0;
+}
diff --git a/libfec/fec_private.h b/libfec/fec_private.h
new file mode 100644 (file)
index 0000000..238c4e2
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * 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 __FEC_PRIVATE_H__
+#define __FEC_PRIVATE_H__
+
+#include <errno.h>
+#include <fcntl.h>
+#include <memory>
+#include <new>
+#include <pthread.h>
+#include <stdio.h>
+#include <string>
+#include <string.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <vector>
+
+#include <utils/Compat.h>
+#include <mincrypt/rsa.h>
+#include <openssl/sha.h>
+#include <fec/io.h>
+#include <fec/ecc.h>
+
+/* processing parameters */
+#define WORK_MIN_THREADS 1
+#define WORK_MAX_THREADS 64
+
+/* verity parameters */
+#define VERITY_CACHE_BLOCKS 4096
+#define VERITY_NO_CACHE UINT64_MAX
+
+/* verity definitions */
+#define VERITY_METADATA_SIZE (8 * FEC_BLOCKSIZE)
+#define VERITY_TABLE_ARGS 10 /* mandatory arguments */
+#define VERITY_MIN_TABLE_SIZE (VERITY_TABLE_ARGS * 2) /* for a sanity check */
+#define VERITY_MAX_TABLE_SIZE (VERITY_METADATA_SIZE - sizeof(verity_header))
+
+/* verity header and metadata */
+#define VERITY_MAGIC 0xB001B001
+#define VERITY_MAGIC_DISABLE 0x46464F56
+#define VERITY_VERSION 0
+#define VERITY_TABLE_FIELDS 10
+#define VERITY_TABLE_VERSION 1
+
+struct verity_header {
+    uint32_t magic;
+    uint32_t version;
+    uint8_t signature[RSANUMBYTES];
+    uint32_t length;
+};
+
+/* file handle */
+struct ecc_info {
+    bool valid;
+    int roots;
+    int rsn;
+    uint32_t size;
+    uint64_t blocks;
+    uint64_t rounds;
+    uint64_t start; /* offset in file */
+};
+
+struct verity_info {
+    bool disabled;
+    char *table;
+    uint32_t hash_data_blocks;
+    uint32_t hash_size;
+    uint64_t hash_data_offset;
+    uint64_t hash_start;
+    uint8_t *hash;
+    uint32_t salt_size;
+    uint8_t *salt;
+    uint64_t data_blocks;
+    uint64_t metadata_start; /* offset in file */
+    uint8_t zero_hash[SHA256_DIGEST_LENGTH];
+    verity_header header;
+    verity_header ecc_header;
+};
+
+struct verity_block_info {
+    uint64_t index;
+    bool valid;
+};
+
+struct fec_handle {
+    ecc_info ecc;
+    int fd;
+    int flags; /* additional flags passed to fec_open */
+    int mode; /* mode for open(2) */
+    pthread_mutex_t mutex;
+    uint64_t errors;
+    uint64_t data_size;
+    uint64_t pos;
+    uint64_t size;
+    verity_info verity;
+};
+
+/* I/O helpers */
+extern bool raw_pread(fec_handle *f, void *buf, size_t count,
+        uint64_t offset);
+extern bool raw_pwrite(fec_handle *f, const void *buf, size_t count,
+        uint64_t offset);
+
+/* processing functions */
+typedef ssize_t (*read_func)(fec_handle *f, uint8_t *dest, size_t count,
+        uint64_t offset, size_t *errors);
+
+extern ssize_t process(fec_handle *f, uint8_t *buf, size_t count,
+        uint64_t offset, read_func func);
+
+/* verity functions */
+extern uint64_t verity_get_size(uint64_t file_size, uint32_t *verity_levels,
+        uint32_t *level_hashes);
+
+extern int verity_parse_header(fec_handle *f, uint64_t offset);
+
+extern bool verity_check_block(fec_handle *f, const uint8_t *expected,
+        const uint8_t *block);
+
+/* helper macros */
+#ifndef unlikely
+    #define unlikely(x) __builtin_expect(!!(x), 0)
+    #define likely(x)   __builtin_expect(!!(x), 1)
+#endif
+
+#ifndef stringify
+    #define __stringify(x) #x
+    #define stringify(x) __stringify(x)
+#endif
+
+/*  warnings, errors, debug output */
+#ifdef FEC_NO_KLOG
+    #define __log(func, type, format, args...) \
+        fprintf(stderr, "fec: <%d> " type ": %s: " format "\n", \
+            (int)syscall(SYS_gettid), __FUNCTION__,  ##args)
+#else
+    #include <cutils/klog.h>
+
+    #define __log(func, type, format, args...) \
+        KLOG_##func("fec", "<%d> " type ": %s: " format "\n", \
+            (int)syscall(SYS_gettid), __FUNCTION__, ##args)
+#endif
+
+#ifdef NDEBUG
+    #define debug(format, args...)
+#else
+    #define debug(format, args...) __log(DEBUG, "debug", format, ##args)
+#endif
+
+#define warn(format, args...) __log(WARNING, "warning", format, ##args)
+#define error(format, args...) __log(ERROR, "error", format, ##args)
+
+#define check(p) \
+    if (unlikely(!(p))) { \
+        error("`%s' failed", #p); \
+        errno = EFAULT; \
+        return -1; \
+    }
+
+#endif /* __FEC_PRIVATE_H__ */
diff --git a/libfec/fec_process.cpp b/libfec/fec_process.cpp
new file mode 100644 (file)
index 0000000..3b2846c
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * 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 "fec_private.h"
+
+struct process_info {
+    int id;
+    fec_handle *f;
+    uint8_t *buf;
+    size_t count;
+    uint64_t offset;
+    read_func func;
+    ssize_t rc;
+    size_t errors;
+};
+
+/* thread function  */
+static void * __process(void *cookie)
+{
+    process_info *p = static_cast<process_info *>(cookie);
+
+    debug("thread %d: [%" PRIu64 ", %" PRIu64 ")", p->id, p->offset,
+        p->offset + p->count);
+
+    p->rc = p->func(p->f, p->buf, p->count, p->offset, &p->errors);
+    return p;
+}
+
+/* launches a maximum number of threads to process a read */
+ssize_t process(fec_handle *f, uint8_t *buf, size_t count, uint64_t offset,
+        read_func func)
+{
+    check(f);
+    check(buf)
+    check(func);
+
+    if (count == 0) {
+        return 0;
+    }
+
+    int threads = sysconf(_SC_NPROCESSORS_ONLN);
+
+    if (threads < WORK_MIN_THREADS) {
+        threads = WORK_MIN_THREADS;
+    } else if (threads > WORK_MAX_THREADS) {
+        threads = WORK_MAX_THREADS;
+    }
+
+    uint64_t start = (offset / FEC_BLOCKSIZE) * FEC_BLOCKSIZE;
+    size_t blocks = fec_div_round_up(count, FEC_BLOCKSIZE);
+
+    if ((size_t)threads > blocks) {
+        threads = (int)blocks;
+    }
+
+    size_t count_per_thread = fec_div_round_up(blocks, threads) * FEC_BLOCKSIZE;
+    size_t left = count;
+    uint64_t pos = offset;
+    uint64_t end = start + count_per_thread;
+
+    debug("%d threads, %zu bytes per thread (total %zu)", threads,
+        count_per_thread, count);
+
+    std::vector<pthread_t> handles;
+    process_info info[threads];
+    ssize_t rc = 0;
+
+    /* start threads to process queue */
+    for (int i = 0; i < threads; ++i) {
+        check(left > 0);
+
+        info[i].id = i;
+        info[i].f = f;
+        info[i].buf = &buf[pos - offset];
+        info[i].count = (size_t)(end - pos);
+        info[i].offset = pos;
+        info[i].func = func;
+        info[i].rc = -1;
+        info[i].errors = 0;
+
+        if (info[i].count > left) {
+            info[i].count = left;
+        }
+
+        pthread_t thread;
+
+        if (pthread_create(&thread, NULL, __process, &info[i]) != 0) {
+            error("failed to create thread: %s", strerror(errno));
+            rc = -1;
+        } else {
+            handles.push_back(thread);
+        }
+
+        pos = end;
+        end  += count_per_thread;
+        left -= info[i].count;
+    }
+
+    check(left == 0);
+
+    ssize_t nread = 0;
+
+    /* wait for all threads to complete */
+    for (auto thread : handles) {
+        process_info *p = NULL;
+
+        if (pthread_join(thread, (void **)&p) != 0) {
+            error("failed to join thread: %s", strerror(errno));
+            rc = -1;
+        } else if (!p || p->rc == -1) {
+            rc = -1;
+        } else {
+            nread += p->rc;
+            f->errors += p->errors;
+        }
+    }
+
+    if (rc == -1) {
+        errno = EIO;
+        return -1;
+    }
+
+    return nread;
+}
diff --git a/libfec/fec_read.cpp b/libfec/fec_read.cpp
new file mode 100644 (file)
index 0000000..2d29da8
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ * 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 <fcntl.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+extern "C" {
+    #include <fec.h>
+}
+
+#include "fec_private.h"
+
+using rs_unique_ptr = std::unique_ptr<void, decltype(&free_rs_char)>;
+
+/* prints a hexdump of `data' using warn(...) */
+static void dump(const char *name, uint64_t value, const uint8_t *data,
+        size_t size)
+{
+    const int bytes_per_line = 16;
+    char hex[bytes_per_line * 3 + 1];
+    char prn[bytes_per_line + 1];
+
+    warn("%s (%" PRIu64 ") (%zu bytes):", name ? name : "", value, size);
+
+    if (!data) {
+        warn("    (null)");
+        return;
+    }
+
+    for (size_t n = 0; n < size; n += bytes_per_line) {
+        memset(hex, 0, sizeof(hex));
+        memset(prn, 0, sizeof(prn));
+
+        for (size_t m = 0; m < bytes_per_line; ++m) {
+            if (n + m < size) {
+                sprintf(&hex[m * 3], "%02x ", data[n + m]);
+
+                if (isprint(data[n + m])) {
+                    prn[m] = data[n + m];
+                } else {
+                    prn[m] = '.';
+                }
+            } else {
+                strcpy(&hex[m * 3], "   ");
+            }
+        }
+
+        warn("    %04zu   %s  %s", n, hex, prn);
+    }
+}
+
+/* checks if `offset' is within a corrupted block */
+static inline bool is_erasure(fec_handle *f, uint64_t offset,
+        const uint8_t *data)
+{
+    if (unlikely(offset >= f->data_size)) {
+        return false;
+    }
+
+    /* ideally, we would like to know if a specific byte on this block has
+       been corrupted, but knowing whether any of them is can be useful as
+       well, because often the entire block is corrupted */
+
+    uint64_t n = offset / FEC_BLOCKSIZE;
+
+    return !verity_check_block(f, &f->verity.hash[n * SHA256_DIGEST_LENGTH],
+                data);
+}
+
+/* check if `offset' is within a block expected to contain zeros */
+static inline bool is_zero(fec_handle *f, uint64_t offset)
+{
+    verity_info *v = &f->verity;
+
+    if (!v->hash || unlikely(offset >= f->data_size)) {
+        return false;
+    }
+
+    uint64_t hash_offset = (offset / FEC_BLOCKSIZE) * SHA256_DIGEST_LENGTH;
+
+    if (unlikely(hash_offset >
+            v->hash_data_blocks * FEC_BLOCKSIZE - SHA256_DIGEST_LENGTH)) {
+        return false;
+    }
+
+    return !memcmp(v->zero_hash, &v->hash[hash_offset], SHA256_DIGEST_LENGTH);
+}
+
+/* reads and decodes a single block starting from `offset', returns the number
+   of bytes corrected in `errors' */
+static int __ecc_read(fec_handle *f, void *rs, uint8_t *dest, uint64_t offset,
+        bool use_erasures, uint8_t *ecc_data, size_t *errors)
+{
+    check(offset % FEC_BLOCKSIZE == 0);
+    ecc_info *e = &f->ecc;
+
+    /* reverse interleaving: calculate the RS block that includes the requested
+       offset */
+    uint64_t rsb = offset - (offset / (e->rounds * FEC_BLOCKSIZE)) *
+                        e->rounds * FEC_BLOCKSIZE;
+    int data_index = -1;
+    int erasures[e->rsn];
+    int neras = 0;
+
+    /* verity is required to check for erasures */
+    check(!use_erasures || f->verity.hash);
+
+    for (int i = 0; i < e->rsn; ++i) {
+        uint64_t interleaved = fec_ecc_interleave(rsb * e->rsn + i, e->rsn,
+                                    e->rounds);
+
+        if (interleaved == offset) {
+            data_index = i;
+        }
+
+        /* to improve our chances of correcting IO errors, initialize the
+           buffer to zeros even if we are going to read to it later */
+        uint8_t bbuf[FEC_BLOCKSIZE] = {0};
+
+        if (likely(interleaved < e->start) && !is_zero(f, interleaved)) {
+            /* copy raw data to reconstruct the RS block */
+            if (!raw_pread(f, bbuf, FEC_BLOCKSIZE, interleaved)) {
+                warn("failed to read: %s", strerror(errno));
+
+                /* treat errors as corruption */
+                if (use_erasures && neras <= e->roots) {
+                    erasures[neras++] = i;
+                }
+            } else if (use_erasures && neras <= e->roots &&
+                            is_erasure(f, interleaved, bbuf)) {
+                erasures[neras++] = i;
+            }
+        }
+
+        for (int j = 0; j < FEC_BLOCKSIZE; ++j) {
+            ecc_data[j * FEC_RSM + i] = bbuf[j];
+        }
+    }
+
+    check(data_index >= 0);
+
+    size_t nerrs = 0;
+    uint8_t copy[FEC_RSM];
+
+    for (int i = 0; i < FEC_BLOCKSIZE; ++i) {
+        /* copy parity data */
+        if (!raw_pread(f, &ecc_data[i * FEC_RSM + e->rsn], e->roots,
+                e->start + (i + rsb) * e->roots)) {
+            error("failed to read ecc data: %s", strerror(errno));
+            return -1;
+        }
+
+        /* for debugging decoding failures, because decode_rs_char can mangle
+           ecc_data */
+        if (unlikely(use_erasures)) {
+            memcpy(copy, &ecc_data[i * FEC_RSM], FEC_RSM);
+        }
+
+        /* decode */
+        int rc = decode_rs_char(rs, &ecc_data[i * FEC_RSM], erasures, neras);
+
+        if (unlikely(rc < 0)) {
+            if (use_erasures) {
+                error("RS block %" PRIu64 ": decoding failed (%d erasures)",
+                    rsb, neras);
+                dump("raw RS block", rsb, copy, FEC_RSM);
+            } else if (!f->verity.hash) {
+                warn("RS block %" PRIu64 ": decoding failed", rsb);
+            } else {
+                debug("RS block %" PRIu64 ": decoding failed", rsb);
+            }
+
+            errno = EIO;
+            return -1;
+        } else if (unlikely(rc > 0)) {
+            check(rc <= (use_erasures ? e->roots : e->roots / 2));
+            nerrs += rc;
+        }
+
+        dest[i] = ecc_data[i * FEC_RSM + data_index];
+    }
+
+    if (nerrs) {
+        warn("RS block %" PRIu64 ": corrected %zu errors", rsb, nerrs);
+        *errors += nerrs;
+    }
+
+    return FEC_BLOCKSIZE;
+}
+
+/* initializes RS decoder and allocates memory for interleaving */
+static int ecc_init(fec_handle *f, rs_unique_ptr& rs,
+        std::unique_ptr<uint8_t[]>& ecc_data)
+{
+    check(f);
+
+    rs.reset(init_rs_char(FEC_PARAMS(f->ecc.roots)));
+
+    if (unlikely(!rs)) {
+        error("failed to initialize RS");
+        errno = ENOMEM;
+        return -1;
+    }
+
+    ecc_data.reset(new (std::nothrow) uint8_t[FEC_RSM * FEC_BLOCKSIZE]);
+
+    if (unlikely(!ecc_data)) {
+        error("failed to allocate ecc buffer");
+        errno = ENOMEM;
+        return -1;
+    }
+
+    return 0;
+}
+
+/* reads `count' bytes from `offset' and corrects possible errors without
+   erasure detection, returning the number of corrected bytes in `errors' */
+static ssize_t ecc_read(fec_handle *f, uint8_t *dest, size_t count,
+        uint64_t offset, size_t *errors)
+{
+    check(f);
+    check(dest);
+    check(offset < f->data_size);
+    check(offset + count <= f->data_size);
+    check(errors);
+
+    debug("[%" PRIu64 ", %" PRIu64 ")", offset, offset + count);
+
+    rs_unique_ptr rs(NULL, free_rs_char);
+    std::unique_ptr<uint8_t[]> ecc_data;
+
+    if (ecc_init(f, rs, ecc_data) == -1) {
+        return -1;
+    }
+
+    uint64_t curr = offset / FEC_BLOCKSIZE;
+    size_t coff = (size_t)(offset - curr * FEC_BLOCKSIZE);
+    size_t left = count;
+
+    uint8_t data[FEC_BLOCKSIZE];
+
+    while (left > 0) {
+        /* there's no erasure detection without verity metadata */
+        if (__ecc_read(f, rs.get(), data, curr * FEC_BLOCKSIZE, false,
+                ecc_data.get(), errors) == -1) {
+            return -1;
+        }
+
+        size_t copy = FEC_BLOCKSIZE - coff;
+
+        if (copy > left) {
+            copy = left;
+        }
+
+        memcpy(dest, &data[coff], copy);
+
+        dest += copy;
+        left -= copy;
+        coff = 0;
+        ++curr;
+    }
+
+    return count;
+}
+
+/* reads `count' bytes from `offset', corrects possible errors with
+   erasure detection, and verifies the integrity of read data using
+   verity hash tree; returns the number of corrections in `errors' */
+static ssize_t verity_read(fec_handle *f, uint8_t *dest, size_t count,
+        uint64_t offset, size_t *errors)
+{
+    check(f);
+    check(dest);
+    check(offset < f->data_size);
+    check(offset + count <= f->data_size);
+    check(f->verity.hash);
+    check(errors);
+
+    debug("[%" PRIu64 ", %" PRIu64 ")", offset, offset + count);
+
+    rs_unique_ptr rs(NULL, free_rs_char);
+    std::unique_ptr<uint8_t[]> ecc_data;
+
+    if (f->ecc.start && ecc_init(f, rs, ecc_data) == -1) {
+        return -1;
+    }
+
+    uint64_t curr = offset / FEC_BLOCKSIZE;
+    size_t coff = (size_t)(offset - curr * FEC_BLOCKSIZE);
+    size_t left = count;
+    uint8_t data[FEC_BLOCKSIZE];
+
+    uint64_t max_hash_block = (f->verity.hash_data_blocks * FEC_BLOCKSIZE -
+                                SHA256_DIGEST_LENGTH) / SHA256_DIGEST_LENGTH;
+
+    while (left > 0) {
+        check(curr <= max_hash_block);
+
+        uint8_t *hash = &f->verity.hash[curr * SHA256_DIGEST_LENGTH];
+        uint64_t curr_offset = curr * FEC_BLOCKSIZE;
+
+        bool expect_zeros = is_zero(f, curr_offset);
+
+        /* if we are in read-only mode and expect to read a zero block,
+           skip reading and just return zeros */
+        if (f->mode & O_RDONLY && expect_zeros) {
+            memset(data, 0, FEC_BLOCKSIZE);
+            goto valid;
+        }
+
+        /* copy raw data without error correction */
+        if (!raw_pread(f, data, FEC_BLOCKSIZE, curr_offset)) {
+            error("failed to read: %s", strerror(errno));
+            return -1;
+        }
+
+        if (likely(verity_check_block(f, hash, data))) {
+            goto valid;
+        }
+
+        /* we know the block is supposed to contain zeros, so return zeros
+           instead of trying to correct it */
+        if (expect_zeros) {
+            memset(data, 0, FEC_BLOCKSIZE);
+            goto corrected;
+        }
+
+        if (!f->ecc.start) {
+            /* fatal error without ecc */
+            error("[%" PRIu64 ", %" PRIu64 "): corrupted block %" PRIu64,
+                offset, offset + count, curr);
+            return -1;
+        } else {
+            debug("[%" PRIu64 ", %" PRIu64 "): corrupted block %" PRIu64,
+                offset, offset + count, curr);
+        }
+
+        /* try to correct without erasures first, because checking for
+           erasure locations is slower */
+        if (__ecc_read(f, rs.get(), data, curr_offset, false, ecc_data.get(),
+                errors) == FEC_BLOCKSIZE &&
+            verity_check_block(f, hash, data)) {
+            goto corrected;
+        }
+
+        /* try to correct with erasures */
+        if (__ecc_read(f, rs.get(), data, curr_offset, true, ecc_data.get(),
+                errors) == FEC_BLOCKSIZE &&
+            verity_check_block(f, hash, data)) {
+            goto corrected;
+        }
+
+        error("[%" PRIu64 ", %" PRIu64 "): corrupted block %" PRIu64
+            " (offset %" PRIu64 ") cannot be recovered",
+            offset, offset + count, curr, curr_offset);
+        dump("decoded block", curr, data, FEC_BLOCKSIZE);
+
+        errno = EIO;
+        return -1;
+
+corrected:
+        /* update the corrected block to the file if we are in r/w mode */
+        if (f->mode & O_RDWR &&
+                !raw_pwrite(f, data, FEC_BLOCKSIZE, curr_offset)) {
+            error("failed to write: %s", strerror(errno));
+            return -1;
+        }
+
+valid:
+        size_t copy = FEC_BLOCKSIZE - coff;
+
+        if (copy > left) {
+            copy = left;
+        }
+
+        memcpy(dest, &data[coff], copy);
+
+        dest += copy;
+        left -= copy;
+        coff = 0;
+        ++curr;
+    }
+
+    return count;
+}
+
+/* sets the internal file position to `offset' relative to `whence' */
+int fec_seek(struct fec_handle *f, int64_t offset, int whence)
+{
+    check(f);
+
+    if (whence == SEEK_SET) {
+        if (offset < 0) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        f->pos = offset;
+    } else if (whence == SEEK_CUR) {
+        if (offset < 0 && f->pos < (uint64_t)-offset) {
+            errno = EOVERFLOW;
+            return -1;
+        } else if (offset > 0 && (uint64_t)offset > UINT64_MAX - f->pos) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        f->pos += offset;
+    } else if (whence == SEEK_END) {
+        if (offset >= 0) {
+            errno = ENXIO;
+            return -1;
+        } else if ((uint64_t)-offset > f->size) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        f->pos = f->size + offset;
+    } else {
+        errno = EINVAL;
+        return -1;
+    }
+
+    return 0;
+}
+
+/* reads up to `count' bytes starting from the internal file position using
+   error correction and integrity validation, if available */
+ssize_t fec_read(struct fec_handle *f, void *buf, size_t count)
+{
+    ssize_t rc = fec_pread(f, buf, count, f->pos);
+
+    if (rc > 0) {
+        check(f->pos < UINT64_MAX - rc);
+        f->pos += rc;
+    }
+
+    return rc;
+}
+
+/* for a file with size `max', returns the number of bytes we can read starting
+   from `offset', up to `count' bytes */
+static inline size_t get_max_count(uint64_t offset, size_t count, uint64_t max)
+{
+    if (offset >= max) {
+        return 0;
+    } else if (offset > max - count) {
+        return (size_t)(max - offset);
+    }
+
+    return count;
+}
+
+/* reads `count' bytes from `f->fd' starting from `offset', and copies the
+   data to `buf' */
+bool raw_pread(fec_handle *f, void *buf, size_t count, uint64_t offset)
+{
+    check(f);
+    check(buf);
+
+    uint8_t *p = (uint8_t *)buf;
+    size_t remaining = count;
+
+    while (remaining > 0) {
+        ssize_t n = TEMP_FAILURE_RETRY(pread64(f->fd, p, remaining, offset));
+
+        if (n <= 0) {
+            return false;
+        }
+
+        p += n;
+        remaining -= n;
+        offset += n;
+    }
+
+    return true;
+}
+
+/* writes `count' bytes from `buf' to `f->fd' to a file position `offset' */
+bool raw_pwrite(fec_handle *f, const void *buf, size_t count, uint64_t offset)
+{
+    check(f);
+    check(buf);
+
+    const uint8_t *p = (const uint8_t *)buf;
+    size_t remaining = count;
+
+    while (remaining > 0) {
+        ssize_t n = TEMP_FAILURE_RETRY(pwrite64(f->fd, p, remaining, offset));
+
+        if (n <= 0) {
+            return false;
+        }
+
+        p += n;
+        remaining -= n;
+        offset += n;
+    }
+
+    return true;
+}
+
+/* reads up to `count' bytes starting from `offset' using error correction and
+   integrity validation, if available */
+ssize_t fec_pread(struct fec_handle *f, void *buf, size_t count,
+        uint64_t offset)
+{
+    check(f);
+    check(buf);
+
+    if (unlikely(offset > UINT64_MAX - count)) {
+        errno = EOVERFLOW;
+        return -1;
+    }
+
+    if (f->verity.hash) {
+        return process(f, (uint8_t *)buf,
+                    get_max_count(offset, count, f->data_size), offset,
+                    verity_read);
+    } else if (f->ecc.start) {
+        check(f->ecc.start < f->size);
+
+        count = get_max_count(offset, count, f->data_size);
+        ssize_t rc = process(f, (uint8_t *)buf, count, offset, ecc_read);
+
+        if (rc >= 0) {
+            return rc;
+        }
+
+        /* return raw data if pure ecc read fails; due to interleaving
+           the specific blocks the caller wants may still be fine */
+    } else {
+        count = get_max_count(offset, count, f->size);
+    }
+
+    if (raw_pread(f, buf, count, offset)) {
+        return count;
+    }
+
+    return -1;
+}
diff --git a/libfec/fec_verity.cpp b/libfec/fec_verity.cpp
new file mode 100644 (file)
index 0000000..393e962
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+ * 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 <ctype.h>
+#include <stdlib.h>
+#include <android-base/strings.h>
+#include "fec_private.h"
+
+/* converts a hex nibble into an int */
+static inline int hextobin(char c)
+{
+    if (c >= '0' && c <= '9') {
+        return c - '0';
+    } else if (c >= 'a' && c <= 'f') {
+        return c - 'a' + 10;
+    } else {
+        errno = EINVAL;
+        return -1;
+    }
+}
+
+/* converts a hex string `src' of `size' characters to binary and copies the
+   the result into `dst' */
+static int parse_hex(uint8_t *dst, uint32_t size, const char *src)
+{
+    int l, h;
+
+    check(dst);
+    check(src);
+    check(2 * size == strlen(src));
+
+    while (size) {
+        h = hextobin(tolower(*src++));
+        l = hextobin(tolower(*src++));
+
+        check(l >= 0);
+        check(h >= 0);
+
+        *dst++ = (h << 4) | l;
+        --size;
+    }
+
+    return 0;
+}
+
+/* parses a 64-bit unsigned integer from string `src' into `dst' and if
+   `maxval' is >0, checks that `dst' <= `maxval' */
+static int parse_uint64(const char *src, uint64_t maxval, uint64_t *dst)
+{
+    char *end;
+    unsigned long long int value;
+
+    check(src);
+    check(dst);
+
+    errno = 0;
+    value = strtoull(src, &end, 0);
+
+    if (*src == '\0' || *end != '\0' ||
+            (errno == ERANGE && value == ULLONG_MAX)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (maxval && value > maxval) {
+        errno = EINVAL;
+        return -1;
+    }
+
+   *dst = (uint64_t)value;
+    return 0;
+}
+
+/* computes the size of verity hash tree for `file_size' bytes and returns the
+   number of hash tree levels in `verity_levels,' and the number of hashes per
+   level in `level_hashes', if the parameters are non-NULL */
+uint64_t verity_get_size(uint64_t file_size, uint32_t *verity_levels,
+        uint32_t *level_hashes)
+{
+    /* we assume a known metadata size, 4 KiB block size, and SHA-256 to avoid
+       relying on disk content */
+
+    uint32_t level = 0;
+    uint64_t total = 0;
+    uint64_t hashes = file_size / FEC_BLOCKSIZE;
+
+    do {
+        if (level_hashes) {
+            level_hashes[level] = hashes;
+        }
+
+        hashes = fec_div_round_up(hashes * SHA256_DIGEST_LENGTH, FEC_BLOCKSIZE);
+        total += hashes;
+
+        ++level;
+    } while (hashes > 1);
+
+    if (verity_levels) {
+        *verity_levels = level;
+    }
+
+    return total * FEC_BLOCKSIZE;
+}
+
+/* computes a SHA-256 salted with `f->verity.salt' from a FEC_BLOCKSIZE byte
+   buffer `block', and copies the hash to `hash' */
+static inline int verity_hash(fec_handle *f, const uint8_t *block,
+        uint8_t *hash)
+{
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+
+    check(f);
+    check(f->verity.salt);
+    SHA256_Update(&ctx, f->verity.salt, f->verity.salt_size);
+
+    check(block);
+    SHA256_Update(&ctx, block, FEC_BLOCKSIZE);
+
+    check(hash);
+    SHA256_Final(hash, &ctx);
+    return 0;
+}
+
+/* computes a verity hash for FEC_BLOCKSIZE bytes from buffer `block' and
+   compares it to the expected value in `expected' */
+bool verity_check_block(fec_handle *f, const uint8_t *expected,
+        const uint8_t *block)
+{
+    check(f);
+    check(block);
+
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+
+    if (unlikely(verity_hash(f, block, hash) == -1)) {
+        error("failed to hash");
+        return false;
+    }
+
+    check(expected);
+    return !memcmp(expected, hash, SHA256_DIGEST_LENGTH);
+}
+
+/* reads a verity hash and the corresponding data block using error correction,
+   if available */
+static bool ecc_read_hashes(fec_handle *f, uint64_t hash_offset,
+        uint8_t *hash, uint64_t data_offset, uint8_t *data)
+{
+    check(f);
+
+    if (hash && fec_pread(f, hash, SHA256_DIGEST_LENGTH, hash_offset) !=
+                    SHA256_DIGEST_LENGTH) {
+        error("failed to read hash tree: offset %" PRIu64 ": %s", hash_offset,
+            strerror(errno));
+        return false;
+    }
+
+    check(data);
+
+    if (fec_pread(f, data, FEC_BLOCKSIZE, data_offset) != FEC_BLOCKSIZE) {
+        error("failed to read hash tree: data_offset %" PRIu64 ": %s",
+            data_offset, strerror(errno));
+        return false;
+    }
+
+    return true;
+}
+
+/* reads the verity hash tree, validates it against the root hash in `root',
+   corrects errors if necessary, and copies valid data blocks for later use
+   to `f->verity.hash' */
+static int verify_tree(fec_handle *f, const uint8_t *root)
+{
+    uint8_t data[FEC_BLOCKSIZE];
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+
+    check(f);
+    check(root);
+
+    verity_info *v = &f->verity;
+    uint32_t levels = 0;
+
+    /* calculate the size and the number of levels in the hash tree */
+    v->hash_size =
+        verity_get_size(v->data_blocks * FEC_BLOCKSIZE, &levels, NULL);
+
+    check(v->hash_start < UINT64_MAX - v->hash_size);
+    check(v->hash_start + v->hash_size <= f->data_size);
+
+    uint64_t hash_offset = v->hash_start;
+    uint64_t data_offset = hash_offset + FEC_BLOCKSIZE;
+
+    v->hash_data_offset = data_offset;
+
+    /* validate the root hash */
+    if (!raw_pread(f, data, FEC_BLOCKSIZE, hash_offset) ||
+            !verity_check_block(f, root, data)) {
+        /* try to correct */
+        if (!ecc_read_hashes(f, 0, NULL, hash_offset, data) ||
+                !verity_check_block(f, root, data)) {
+            error("root hash invalid");
+            return -1;
+        } else if (f->mode & O_RDWR &&
+                    !raw_pwrite(f, data, FEC_BLOCKSIZE, hash_offset)) {
+            error("failed to rewrite the root block: %s", strerror(errno));
+            return -1;
+        }
+    }
+
+    debug("root hash valid");
+
+    /* calculate the number of hashes on each level */
+    uint32_t hashes[levels];
+
+    verity_get_size(v->data_blocks * FEC_BLOCKSIZE, NULL, hashes);
+
+    /* calculate the size and offset for the data hashes */
+    for (uint32_t i = 1; i < levels; ++i) {
+        uint32_t blocks = hashes[levels - i];
+        debug("%u hash blocks on level %u", blocks, levels - i);
+
+        v->hash_data_offset = data_offset;
+        v->hash_data_blocks = blocks;
+
+        data_offset += blocks * FEC_BLOCKSIZE;
+    }
+
+    check(v->hash_data_blocks);
+    check(v->hash_data_blocks <= v->hash_size / FEC_BLOCKSIZE);
+
+    check(v->hash_data_offset);
+    check(v->hash_data_offset <=
+        UINT64_MAX - (v->hash_data_blocks * FEC_BLOCKSIZE));
+    check(v->hash_data_offset < f->data_size);
+    check(v->hash_data_offset + v->hash_data_blocks * FEC_BLOCKSIZE <=
+        f->data_size);
+
+    /* copy data hashes to memory in case they are corrupted, so we don't
+       have to correct them every time they are needed */
+    std::unique_ptr<uint8_t[]> data_hashes(
+       new (std::nothrow) uint8_t[f->verity.hash_data_blocks * FEC_BLOCKSIZE]);
+
+    if (!data_hashes) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    /* validate the rest of the hash tree */
+    data_offset = hash_offset + FEC_BLOCKSIZE;
+
+    for (uint32_t i = 1; i < levels; ++i) {
+        uint32_t blocks = hashes[levels - i];
+
+        for (uint32_t j = 0; j < blocks; ++j) {
+            /* ecc reads are very I/O intensive, so read raw hash tree and do
+               error correcting only if it doesn't validate */
+            if (!raw_pread(f, hash, SHA256_DIGEST_LENGTH,
+                    hash_offset + j * SHA256_DIGEST_LENGTH) ||
+                !raw_pread(f, data, FEC_BLOCKSIZE,
+                    data_offset + j * FEC_BLOCKSIZE)) {
+                error("failed to read hashes: %s", strerror(errno));
+                return -1;
+            }
+
+            if (!verity_check_block(f, hash, data)) {
+                /* try to correct */
+                if (!ecc_read_hashes(f,
+                        hash_offset + j * SHA256_DIGEST_LENGTH, hash,
+                        data_offset + j * FEC_BLOCKSIZE, data) ||
+                    !verity_check_block(f, hash, data)) {
+                    error("invalid hash tree: hash_offset %" PRIu64 ", "
+                        "data_offset %" PRIu64 ", block %u",
+                        hash_offset, data_offset, j);
+                    return -1;
+                }
+
+                /* update the corrected blocks to the file if we are in r/w
+                   mode */
+                if (f->mode & O_RDWR) {
+                    if (!raw_pwrite(f, hash, SHA256_DIGEST_LENGTH,
+                            hash_offset + j * SHA256_DIGEST_LENGTH) ||
+                        !raw_pwrite(f, data, FEC_BLOCKSIZE,
+                            data_offset + j * FEC_BLOCKSIZE)) {
+                        error("failed to write hashes: %s", strerror(errno));
+                        return -1;
+                    }
+                }
+            }
+
+            if (blocks == v->hash_data_blocks) {
+                memcpy(data_hashes.get() + j * FEC_BLOCKSIZE, data,
+                    FEC_BLOCKSIZE);
+            }
+        }
+
+        hash_offset = data_offset;
+        data_offset += blocks * FEC_BLOCKSIZE;
+    }
+
+    debug("valid");
+
+    v->hash = data_hashes.release();
+    return 0;
+}
+
+/* reads, corrects and parses the verity table, validates parameters, and if
+   `f->flags' does not have `FEC_VERITY_DISABLE' set, calls `verify_tree' to
+   load and validate the hash tree */
+static int parse_table(fec_handle *f, uint64_t offset, uint32_t size)
+{
+    check(f);
+    check(size >= VERITY_MIN_TABLE_SIZE);
+    check(size <= VERITY_MAX_TABLE_SIZE);
+
+    debug("offset = %" PRIu64 ", size = %u", offset, size);
+
+    verity_info *v = &f->verity;
+    std::unique_ptr<char[]> table(new (std::nothrow) char[size + 1]);
+
+    if (!table) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    if (fec_pread(f, table.get(), size, offset) != (ssize_t)size) {
+        error("failed to read verity table: %s", strerror(errno));
+        return -1;
+    }
+
+    table[size] = '\0';
+    debug("verity table: '%s'", table.get());
+
+    int i = 0;
+    std::unique_ptr<uint8_t[]> salt;
+    uint8_t root[SHA256_DIGEST_LENGTH];
+
+    auto tokens = android::base::Split(table.get(), " ");
+
+    for (const auto token : tokens) {
+        switch (i++) {
+        case 0: /* version */
+            if (token != stringify(VERITY_TABLE_VERSION)) {
+                error("unsupported verity table version: %s", token.c_str());
+                return -1;
+            }
+            break;
+        case 3: /* data_block_size */
+        case 4: /* hash_block_size */
+            /* assume 4 KiB block sizes for everything */
+            if (token != stringify(FEC_BLOCKSIZE)) {
+                error("unsupported verity block size: %s", token.c_str());
+                return -1;
+            }
+            break;
+        case 5: /* num_data_blocks */
+            if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE,
+                    &v->data_blocks) == -1) {
+                error("invalid number of verity data blocks: %s",
+                    token.c_str());
+                return -1;
+            }
+            break;
+        case 6: /* hash_start_block */
+            if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE,
+                    &v->hash_start) == -1) {
+                error("invalid verity hash start block: %s", token.c_str());
+                return -1;
+            }
+
+            v->hash_start *= FEC_BLOCKSIZE;
+            break;
+        case 7: /* algorithm */
+            if (token != "sha256") {
+                error("unsupported verity hash algorithm: %s", token.c_str());
+                return -1;
+            }
+            break;
+        case 8: /* digest */
+            if (parse_hex(root, sizeof(root), token.c_str()) == -1) {
+                error("invalid verity root hash: %s", token.c_str());
+                return -1;
+            }
+            break;
+        case 9: /* salt */
+            v->salt_size = token.size();
+            check(v->salt_size % 2 == 0);
+            v->salt_size /= 2;
+
+            salt.reset(new (std::nothrow) uint8_t[v->salt_size]);
+
+            if (!salt) {
+                errno = ENOMEM;
+                return -1;
+            }
+
+            if (parse_hex(salt.get(), v->salt_size, token.c_str()) == -1) {
+                error("invalid verity salt: %s", token.c_str());
+                return -1;
+            }
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (i < VERITY_TABLE_ARGS) {
+        error("not enough arguments in verity table: %d; expected at least "
+            stringify(VERITY_TABLE_ARGS), i);
+        return -1;
+    }
+
+    check(v->hash_start < f->data_size);
+
+    if (v->metadata_start < v->hash_start) {
+        check(v->data_blocks == v->metadata_start / FEC_BLOCKSIZE);
+    } else {
+        check(v->data_blocks == v->hash_start / FEC_BLOCKSIZE);
+    }
+
+    v->salt = salt.release();
+    v->table = table.release();
+
+    if (!(f->flags & FEC_VERITY_DISABLE)) {
+        if (verify_tree(f, root) == -1) {
+            return -1;
+        }
+
+        check(v->hash);
+
+        uint8_t zero_block[FEC_BLOCKSIZE];
+        memset(zero_block, 0, FEC_BLOCKSIZE);
+
+        if (verity_hash(f, zero_block, v->zero_hash) == -1) {
+            error("failed to hash");
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+/* rewrites verity metadata block using error corrected data in `f->verity' */
+static int rewrite_metadata(fec_handle *f, uint64_t offset)
+{
+    check(f);
+    check(f->data_size > VERITY_METADATA_SIZE);
+    check(offset <= f->data_size - VERITY_METADATA_SIZE);
+
+    std::unique_ptr<uint8_t[]> metadata(
+        new (std::nothrow) uint8_t[VERITY_METADATA_SIZE]);
+
+    if (!metadata) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    memset(metadata.get(), 0, VERITY_METADATA_SIZE);
+
+    verity_info *v = &f->verity;
+    memcpy(metadata.get(), &v->header, sizeof(v->header));
+
+    check(v->table);
+    size_t len = strlen(v->table);
+
+    check(sizeof(v->header) + len <= VERITY_METADATA_SIZE);
+    memcpy(metadata.get() + sizeof(v->header), v->table, len);
+
+    return raw_pwrite(f, metadata.get(), VERITY_METADATA_SIZE, offset);
+}
+
+static int validate_header(const fec_handle *f, const verity_header *header,
+        uint64_t offset)
+{
+    check(f);
+    check(header);
+
+    if (header->magic != VERITY_MAGIC &&
+        header->magic != VERITY_MAGIC_DISABLE) {
+        return -1;
+    }
+
+    if (header->version != VERITY_VERSION) {
+        error("unsupported verity version %u", header->version);
+        return -1;
+    }
+
+    if (header->length < VERITY_MIN_TABLE_SIZE ||
+        header->length > VERITY_MAX_TABLE_SIZE) {
+        error("invalid verity table size: %u; expected ["
+            stringify(VERITY_MIN_TABLE_SIZE) ", "
+            stringify(VERITY_MAX_TABLE_SIZE) ")", header->length);
+        return -1;
+    }
+
+    /* signature is skipped, because for our purposes it won't matter from
+       where the data originates; the caller of the library is responsible
+       for signature verification */
+
+    if (offset > UINT64_MAX - header->length) {
+        error("invalid verity table length: %u", header->length);
+        return -1;
+    } else if (offset + header->length >= f->data_size) {
+        error("invalid verity table length: %u", header->length);
+        return -1;
+    }
+
+    return 0;
+}
+
+/* attempts to read verity metadata from `f->fd' position `offset'; if in r/w
+   mode, rewrites the metadata if it had errors */
+int verity_parse_header(fec_handle *f, uint64_t offset)
+{
+    check(f);
+    check(f->data_size > VERITY_METADATA_SIZE);
+
+    if (offset > f->data_size - VERITY_METADATA_SIZE) {
+        debug("failed to read verity header: offset %" PRIu64 " is too far",
+            offset);
+        return -1;
+    }
+
+    verity_info *v = &f->verity;
+    uint64_t errors = f->errors;
+
+    if (!raw_pread(f, &v->header, sizeof(v->header), offset)) {
+        error("failed to read verity header: %s", strerror(errno));
+        return -1;
+    }
+
+    /* use raw data to check for the alternative magic, because it will
+       be error corrected to VERITY_MAGIC otherwise */
+    if (v->header.magic == VERITY_MAGIC_DISABLE) {
+        /* this value is not used by us, but can be used by a caller to
+           decide whether dm-verity should be enabled */
+        v->disabled = true;
+    }
+
+    if (fec_pread(f, &v->ecc_header, sizeof(v->ecc_header), offset) !=
+            sizeof(v->ecc_header)) {
+        warn("failed to read verity header: %s", strerror(errno));
+        return -1;
+    }
+
+    if (validate_header(f, &v->header, offset)) {
+        /* raw verity header is invalid; this could be due to corruption, or
+           due to missing verity metadata */
+
+        if (validate_header(f, &v->ecc_header, offset)) {
+            return -1; /* either way, we cannot recover */
+        }
+
+        /* report mismatching fields */
+        if (!v->disabled && v->header.magic != v->ecc_header.magic) {
+            warn("corrected verity header magic");
+            v->header.magic = v->ecc_header.magic;
+        }
+
+        if (v->header.version != v->ecc_header.version) {
+            warn("corrected verity header version");
+            v->header.version = v->ecc_header.version;
+        }
+
+        if (v->header.length != v->ecc_header.length) {
+            warn("corrected verity header length");
+            v->header.length = v->ecc_header.length;
+        }
+
+        if (memcmp(v->header.signature, v->ecc_header.signature,
+                sizeof(v->header.signature))) {
+            warn("corrected verity header signature");
+            /* we have no way of knowing which signature is correct, if either
+               of them is */
+        }
+    }
+
+    v->metadata_start = offset;
+
+    if (parse_table(f, offset + sizeof(v->header), v->header.length) == -1) {
+        return -1;
+    }
+
+    /* if we corrected something while parsing metadata and we are in r/w
+       mode, rewrite the corrected metadata */
+    if (f->mode & O_RDWR && f->errors > errors &&
+            rewrite_metadata(f, offset) < 0) {
+        warn("failed to rewrite verity metadata: %s", strerror(errno));
+    }
+
+    if (v->metadata_start < v->hash_start) {
+        f->data_size = v->metadata_start;
+    } else {
+        f->data_size = v->hash_start;
+    }
+
+    return 0;
+}
+
+int fec_verity_set_status(struct fec_handle *f, bool enabled)
+{
+    check(f);
+
+    if (!(f->mode & O_RDWR)) {
+        error("cannot update verity magic: read-only handle");
+        errno = EBADF;
+        return -1;
+    }
+
+    verity_info *v = &f->verity;
+
+    if (!v->metadata_start) {
+        error("cannot update verity magic: no metadata found");
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (v->disabled == !enabled) {
+        return 0; /* nothing to do */
+    }
+
+    uint32_t magic = enabled ? VERITY_MAGIC : VERITY_MAGIC_DISABLE;
+
+    if (!raw_pwrite(f, &magic, sizeof(magic), v->metadata_start)) {
+        error("failed to update verity magic to %08x: %s", magic,
+            strerror(errno));
+        return -1;
+    }
+
+    warn("updated verity magic to %08x (%s)", magic,
+        enabled ? "enabled" : "disabled");
+    v->disabled = !enabled;
+
+    return 0;
+}
diff --git a/libfec/include/fec/ecc.h b/libfec/include/fec/ecc.h
new file mode 100644 (file)
index 0000000..c0fd9ba
--- /dev/null
@@ -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 ___FEC_ECC_H___
+#define ___FEC_ECC_H___
+
+#include <fec/io.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ecc parameters */
+#define FEC_RSM 255
+
+/* parameters to init_rs_char */
+#define FEC_PARAMS(roots) \
+    8,          /* symbol size in bits */ \
+    0x11d,      /* field generator polynomial coefficients */ \
+    0,          /* first root of the generator */ \
+    1,          /* primitive element to generate polynomial roots */ \
+    (roots),    /* polynomial degree (number of roots) */ \
+    0           /* padding bytes at the front of shortened block */
+
+/* computes ceil(x / y) */
+inline uint64_t fec_div_round_up(uint64_t x, uint64_t y)
+{
+    return (x / y) + (x % y > 0 ? 1 : 0);
+}
+
+/* rounds up x to the nearest multiple of y */
+inline uint64_t fec_round_up(uint64_t x, uint64_t y)
+{
+    return fec_div_round_up(x, y) * y;
+}
+
+/* returns a physical offset for a byte in an RS block */
+inline uint64_t fec_ecc_interleave(uint64_t offset, int rsn, uint64_t rounds)
+{
+    return (offset / rsn) + (offset % rsn) * rounds * FEC_BLOCKSIZE;
+}
+
+/* returns the size of ecc data given a file size and the number of roots */
+inline uint64_t fec_ecc_get_size(uint64_t file_size, int roots)
+{
+    return fec_div_round_up(fec_div_round_up(file_size, FEC_BLOCKSIZE),
+                FEC_RSM - roots)
+                    * roots * FEC_BLOCKSIZE
+                + FEC_BLOCKSIZE;
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ___FEC_ECC_H___ */
diff --git a/libfec/include/fec/io.h b/libfec/include/fec/io.h
new file mode 100644 (file)
index 0000000..3b5dac0
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * 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 ___FEC_IO_H___
+#define ___FEC_IO_H___
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <mincrypt/rsa.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef SHA256_DIGEST_LENGTH
+#define SHA256_DIGEST_LENGTH 32
+#endif
+
+#define FEC_BLOCKSIZE 4096
+#define FEC_DEFAULT_ROOTS 2
+
+#define FEC_MAGIC 0xFECFECFE
+#define FEC_VERSION 0
+
+/* disk format for the header */
+struct fec_header {
+    uint32_t magic;
+    uint32_t version;
+    uint32_t size;
+    uint32_t roots;
+    uint32_t fec_size;
+    uint64_t inp_size;
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+};
+
+struct fec_status {
+    int flags;
+    int mode;
+    uint64_t errors;
+    uint64_t data_size;
+    uint64_t size;
+};
+
+struct fec_ecc_metadata {
+    bool valid;
+    uint32_t roots;
+    uint64_t blocks;
+    uint64_t rounds;
+    uint64_t start;
+};
+
+struct fec_verity_metadata {
+    bool disabled;
+    uint64_t data_size;
+    uint8_t signature[RSANUMBYTES];
+    uint8_t ecc_signature[RSANUMBYTES];
+    const char *table;
+    uint32_t table_length;
+};
+
+/* flags for fec_open */
+enum {
+    FEC_FS_EXT4 = 1 << 0,
+    FEC_FS_SQUASH = 1 << 1,
+    FEC_VERITY_DISABLE = 1 << 8
+};
+
+struct fec_handle;
+
+/* file access */
+extern int fec_open(struct fec_handle **f, const char *path, int mode,
+        int flags, int roots);
+
+extern int fec_close(struct fec_handle *f);
+
+extern int fec_verity_set_status(struct fec_handle *f, bool enabled);
+
+extern int fec_verity_get_metadata(struct fec_handle *f,
+        struct fec_verity_metadata *data);
+
+extern int fec_ecc_get_metadata(struct fec_handle *f,
+        struct fec_ecc_metadata *data);
+
+extern int fec_get_status(struct fec_handle *f, struct fec_status *s);
+
+extern int fec_seek(struct fec_handle *f, int64_t offset, int whence);
+
+extern ssize_t fec_read(struct fec_handle *f, void *buf, size_t count);
+
+extern ssize_t fec_pread(struct fec_handle *f, void *buf, size_t count,
+        uint64_t offset);
+
+#ifdef __cplusplus
+} /* extern "C" */
+
+#include <memory>
+#include <string>
+
+/* C++ wrappers for fec_handle and operations */
+namespace fec {
+    using handle = std::unique_ptr<fec_handle, decltype(&fec_close)>;
+
+    class io {
+    public:
+        io() : handle_(nullptr, fec_close) {}
+
+        io(const std::string& fn, int mode = O_RDONLY, int flags = 0,
+                int roots = FEC_DEFAULT_ROOTS) : handle_(nullptr, fec_close) {
+            open(fn, mode, flags, roots);
+        }
+
+        explicit operator bool() const {
+            return !!handle_;
+        }
+
+        bool open(const std::string& fn, int mode = O_RDONLY, int flags = 0,
+                    int roots = FEC_DEFAULT_ROOTS)
+        {
+            fec_handle *fh = nullptr;
+            int rc = fec_open(&fh, fn.c_str(), mode, flags, roots);
+            if (!rc) {
+                handle_.reset(fh);
+            }
+            return !rc;
+        }
+
+        bool close() {
+            return !fec_close(handle_.release());
+        }
+
+        bool seek(int64_t offset, int whence) {
+            return !fec_seek(handle_.get(), offset, whence);
+        }
+
+        ssize_t read(void *buf, size_t count) {
+            return fec_read(handle_.get(), buf, count);
+        }
+
+        ssize_t pread(void *buf, size_t count, uint64_t offset) {
+            return fec_pread(handle_.get(), buf, count, offset);
+        }
+
+        bool get_status(fec_status& status) {
+            return !fec_get_status(handle_.get(), &status);
+        }
+
+        bool get_verity_metadata(fec_verity_metadata& data) {
+            return !fec_verity_get_metadata(handle_.get(), &data);
+        }
+
+        bool has_verity() {
+            fec_verity_metadata data;
+            return get_verity_metadata(data);
+        }
+
+        bool get_ecc_metadata(fec_ecc_metadata& data) {
+            return !fec_ecc_get_metadata(handle_.get(), &data);
+        }
+
+        bool has_ecc() {
+            fec_ecc_metadata data;
+            return get_ecc_metadata(data) && data.valid;
+        }
+
+        bool set_verity_status(bool enabled) {
+            return !fec_verity_set_status(handle_.get(), enabled);
+        }
+
+    private:
+        handle handle_;
+    };
+}
+#endif
+
+#endif /* ___FEC_IO_H___ */
diff --git a/libfec/test/Android.mk b/libfec/test/Android.mk
new file mode 100644 (file)
index 0000000..a2bba55
--- /dev/null
@@ -0,0 +1,32 @@
+LOCAL_PATH:= $(call my-dir)
+
+ifeq ($(HOST_OS),linux)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_MODULE := fec_test_read
+LOCAL_SRC_FILES := test_read.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+    libfec_host \
+    libfec_rs_host \
+    libcrypto_static \
+    libext4_utils_host \
+    libsquashfs_utils_host \
+    libbase
+LOCAL_CFLAGS := -Wall -Werror -D_GNU_SOURCE
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_MODULE := fec_test_rs
+LOCAL_SRC_FILES := test_rs.c
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := libfec_rs_host
+LOCAL_CFLAGS := -Wall -Werror -D_GNU_SOURCE
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # HOST_OS == linux
diff --git a/libfec/test/test_read.cpp b/libfec/test/test_read.cpp
new file mode 100644 (file)
index 0000000..060c727
--- /dev/null
@@ -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 <new>
+#include <memory>
+#include <fstream>
+#include <iostream>
+#include <fec/io.h>
+
+using namespace std;
+const unsigned bufsize = 2 * 1024 * FEC_BLOCKSIZE;
+
+int main(int argc, char **argv)
+{
+    if (argc != 3) {
+        cerr << "usage: " << argv[0] << " input output" << endl;
+        return 1;
+    }
+
+    unique_ptr<uint8_t[]> buffer(new (nothrow) uint8_t[bufsize]);
+
+    if (!buffer) {
+        cerr << "failed to allocate buffer" << endl;
+        return 1;
+    }
+
+    fec::io input(argv[1]);
+
+    if (!input) {
+        return 1;
+    }
+
+    ofstream output(argv[2], ios::binary | ios::trunc);
+
+    if (!output) {
+        cerr << "failed to open " << argv[2] << endl;
+        return 1;
+    }
+
+    ssize_t count;
+
+    do {
+        count = input.read(buffer.get(), bufsize);
+
+        if (count == -1) {
+            return 1;
+        } else if (count > 0) {
+            output.write(reinterpret_cast<const char *>(buffer.get()), count);
+
+            if (!output) {
+                cerr << "write" << endl;
+                return 1;
+            }
+        }
+    } while (count > 0);
+
+    return 0;
+}
diff --git a/libfec/test/test_rs.c b/libfec/test/test_rs.c
new file mode 100644 (file)
index 0000000..61ac12d
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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 <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fec.h>
+
+#define FEC_RSM 255
+#define FEC_ROOTS 16
+#define FEC_RSN (FEC_RSM - FEC_ROOTS)
+#define FEC_PARAMS(roots) \
+    8, 0x11d, 0, 1, (roots), 0
+
+int main()
+{
+    uint8_t data[FEC_RSM];
+    uint8_t dupl[FEC_RSM];
+    uint8_t corr[FEC_RSM];
+    int i, rc, neras, errors;
+    int erasures[FEC_RSM];
+    void *rs;
+
+    memset(data, 0x00, sizeof(data));
+    memset(corr, 0x00, sizeof(corr));
+
+    rs = init_rs_char(FEC_PARAMS(FEC_ROOTS));
+
+    if (!rs) {
+        perror("init_rs_char");
+        exit(1);
+    }
+
+    encode_rs_char(rs, data, &corr[FEC_RSN]);
+
+    for (neras = 1; neras <= FEC_ROOTS; ++neras) {
+        printf("%d errors\n", neras);
+
+        for (i = 0; i < neras; ++i) {
+            corr[i] = 0xFD;
+            erasures[i] = i;
+        }
+
+        memcpy(dupl, corr, sizeof(corr));
+
+        rc = decode_rs_char(rs, corr, NULL, 0);
+
+        printf("\tno erasures: %d\n", rc);
+
+        errors = 0;
+        for (i = 0; i < FEC_RSN; ++i) {
+            if (corr[i] != 0x00) {
+                printf("\t\terror at %d (%02x)\n", i, corr[i]);
+                ++errors;
+            }
+        }
+        printf("\t\t%d errors in output\n", errors);
+
+        rc = decode_rs_char(rs, dupl, erasures, neras);
+
+        printf("\terasures: %d\n", rc);
+
+        errors = 0;
+        for (i = 0; i < FEC_RSN; ++i) {
+            if (dupl[i] != 0x00) {
+                printf("\t\terror at %d (%02x)\n", i, dupl[i]);
+                ++errors;
+            }
+        }
+        printf("\t\t%d errors in output\n", errors);
+    }
+
+    exit(0);
+}
index 96a4e94..65d466c 100644 (file)
 # limitations under the License.
 
 LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
 
+pagemap_src_files := \
+    pm_kernel.c \
+    pm_process.c \
+    pm_map.c \
+    pm_memusage.c \
+
+include $(CLEAR_VARS)
 LOCAL_MODULE := libpagemap
 LOCAL_MODULE_TAGS := debug
-
-LOCAL_SRC_FILES := \
-       pm_kernel.c \
-       pm_process.c \
-       pm_map.c \
-       pm_memusage.c
-
+LOCAL_SRC_FILES := $(pagemap_src_files)
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-
+LOCAL_CFLAGS := -Wno-unused-parameter
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := pagemap_test
+LOCAL_SRC_FILES := pagemap_test.cpp
+LOCAL_SHARED_LIBRARIES := libpagemap
+include $(BUILD_NATIVE_TEST)
diff --git a/libpagemap/pagemap_test.cpp b/libpagemap/pagemap_test.cpp
new file mode 100644 (file)
index 0000000..592072c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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 <pagemap/pagemap.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+TEST(pagemap, maps) {
+  pm_kernel_t* kernel;
+  ASSERT_EQ(0, pm_kernel_create(&kernel));
+
+  pm_process_t* process;
+  ASSERT_EQ(0, pm_process_create(kernel, getpid(), &process));
+
+  pm_map_t** maps;
+  size_t num_maps;
+  ASSERT_EQ(0, pm_process_maps(process, &maps, &num_maps));
+
+  bool found_heap = false;
+  bool found_stack = false;
+  for (size_t i = 0; i < num_maps; i++) {
+    std::string name(maps[i]->name);
+    if (name == "[heap]" || name == "[anon:libc_malloc]") found_heap = true;
+    if (name == "[stack]") found_stack = true;
+  }
+
+  ASSERT_TRUE(found_heap);
+  ASSERT_TRUE(found_stack);
+
+  free(maps);
+  pm_process_destroy(process);
+  pm_kernel_destroy(kernel);
+}
index eee3464..3c5c391 100644 (file)
@@ -240,19 +240,13 @@ int pm_process_destroy(pm_process_t *proc) {
 }
 
 #define INITIAL_MAPS 10
-#define MAX_LINE 1024
 #define MAX_PERMS 5
 
-/* 
- * #define FOO 123
- * S(FOO) => "123"
- */
-#define _S(n) #n
-#define S(n) _S(n)
-
 static int read_maps(pm_process_t *proc) {
     char filename[MAX_FILENAME];
-    char line[MAX_LINE], name[MAX_LINE], perms[MAX_PERMS];
+    char *line = NULL;
+    size_t line_length = 0;
+    char perms[MAX_PERMS];
     FILE *maps_f;
     pm_map_t *map, **maps, **new_maps;
     int maps_count, maps_size;
@@ -278,12 +272,15 @@ static int read_maps(pm_process_t *proc) {
         return errno;
     }
 
-    while (fgets(line, MAX_LINE, maps_f)) {
+    while (getline(&line, &line_length, maps_f) != -1) {
+        line[strlen(line) - 1] = '\0'; // Lose the newline.
+
         if (maps_count >= maps_size) {
             new_maps = realloc(maps, 2 * maps_size * sizeof(pm_map_t*));
             if (!new_maps) {
                 error = errno;
                 free(maps);
+                free(line);
                 fclose(maps_f);
                 return error;
             }
@@ -295,20 +292,21 @@ static int read_maps(pm_process_t *proc) {
 
         map->proc = proc;
 
-        name[0] = '\0';
-        sscanf(line, "%" SCNx64 "-%" SCNx64 " %s %" SCNx64 " %*s %*d %" S(MAX_LINE) "s",
-               &map->start, &map->end, perms, &map->offset, name);
+        int name_offset;
+        sscanf(line, "%" SCNx64 "-%" SCNx64 " %4s %" SCNx64 " %*s %*d %n",
+               &map->start, &map->end, perms, &map->offset, &name_offset);
 
-        map->name = malloc(strlen(name) + 1);
+        map->name = strdup(line + name_offset);
         if (!map->name) {
             error = errno;
             for (; maps_count > 0; maps_count--)
                 pm_map_destroy(maps[maps_count]);
             free(maps);
+            free(line);
             fclose(maps_f);
             return error;
         }
-        strcpy(map->name, name);
+
         if (perms[0] == 'r') map->flags |= PM_MAP_READ;
         if (perms[1] == 'w') map->flags |= PM_MAP_WRITE;
         if (perms[2] == 'x') map->flags |= PM_MAP_EXEC;
@@ -316,6 +314,7 @@ static int read_maps(pm_process_t *proc) {
         maps_count++;
     }
 
+    free(line);
     fclose(maps_f);
 
     new_maps = realloc(maps, maps_count * sizeof(pm_map_t*));
index 300b3f5..101b94a 100644 (file)
 # limitations under the License.
 
 LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
 
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := librank.c
-
-LOCAL_C_INCLUDES := $(call include-path-for, libpagemap)
-
 LOCAL_SHARED_LIBRARIES := libpagemap
-
 LOCAL_MODULE := librank
-
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-
 LOCAL_MODULE_TAGS := debug
-
 include $(BUILD_EXECUTABLE)
diff --git a/memcpy-perf/Android.mk b/memcpy-perf/Android.mk
new file mode 100644 (file)
index 0000000..55ac927
--- /dev/null
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_MODULE := memcpy-perf
+LOCAL_CFLAGS += -g -Wall -Werror -std=c++11 -Wno-missing-field-initializers -Wno-sign-compare -O3
+LOCAL_SRC_FILES := memcpy-perf.cpp test-funcs.cpp
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_CXX_STL := libc++_static
+LOCAL_STATIC_LIBRARIES := libc
+include $(BUILD_EXECUTABLE)
diff --git a/memcpy-perf/NOTICE b/memcpy-perf/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/memcpy-perf/graph_memcpy.py b/memcpy-perf/graph_memcpy.py
new file mode 100644 (file)
index 0000000..7c64d99
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+import subprocess
+import matplotlib.pyplot as plt
+import time
+import argparse
+
+parser = argparse.ArgumentParser(description="Graph memcpy perf")
+parser.add_argument("--files", nargs='+', type=str, help="files to graph", default=None)
+args = parser.parse_args()
+
+fig, ax = plt.subplots(nrows=1)
+ax.set_xscale('log')
+
+plt.xlabel("size in bytes")
+plt.ylabel("BW in GB/s")
+plt.title("size vs. bw")
+plt.tight_layout()
+
+for arg in args.files:
+       f = open(arg)
+       size = []
+       perf = []
+       for line in f:
+               # size: 11430912, perf: 6.76051GB/s, iter: 5
+               line_split = line.split(",")
+               size.append(float(line_split[0].split(":")[1]))
+               perf.append(float(line_split[1].split(":")[1].split("G")[0]))
+
+       line, = ax.plot(size, perf, '-',  linewidth=0.2, label=arg)
+
+legend = plt.legend()
+plt.show()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/memcpy-perf/memcpy-perf.cpp b/memcpy-perf/memcpy-perf.cpp
new file mode 100644 (file)
index 0000000..20d060b
--- /dev/null
@@ -0,0 +1,105 @@
+#include <iostream>
+#include <chrono>
+#include <vector>
+#include <algorithm>
+#include <numeric>
+#include <stdlib.h>
+#include <memory>
+#include <cmath>
+#include <string>
+
+using namespace std;
+
+const size_t size_start = 64;
+const size_t size_end = 16 * (1ull << 20);
+const size_t samples = 2048;
+size_t size_per_test = 64 * (1ull << 20);
+size_t tot_sum = 0;
+
+void __attribute__((noinline)) memcpy_noinline(void *dst, void *src, size_t size);
+void __attribute__((noinline)) memset_noinline(void *dst, int value, size_t size);
+uint64_t __attribute__((noinline)) sum(volatile void *src, size_t size);
+
+enum BenchType {
+    MemcpyBench,
+    MemsetBench,
+    SumBench,
+};
+
+int main(int argc, char *argv[])
+{
+    BenchType type;
+    if (argc <= 1) {
+        cerr << "memcpy_perf [--memcpy|--memset|--sum]" << endl;
+        return 0;
+    }
+    if (string(argv[1]) == string("--memcpy")) {
+        type = MemcpyBench;
+    } else if (string(argv[1]) == string("--memset")) {
+        type = MemsetBench;
+    } else if (string(argv[1]) == string("--sum")) {
+        type = SumBench;
+    } else {
+        type = MemcpyBench;
+    }
+
+    unique_ptr<uint8_t[]> src(new uint8_t[size_end]);
+    unique_ptr<uint8_t[]> dst(new uint8_t[size_end]);
+    memset(src.get(), 1, size_end);
+
+    double start_pow = log10(size_start);
+    double end_pow = log10(size_end);
+    double pow_inc = (end_pow - start_pow) / samples;
+
+    //cout << "src: " << (uintptr_t)src.get() << endl;
+    //cout << "dst: " <<  (uintptr_t)dst.get() << endl;
+
+    for (double cur_pow = start_pow; cur_pow <= end_pow; cur_pow += pow_inc) {
+        chrono::time_point<chrono::high_resolution_clock> copy_start, copy_end;
+
+        size_t cur_size = (size_t)pow(10.0, cur_pow);
+        size_t iter_per_size = size_per_test / cur_size;
+
+        // run benchmark
+        switch (type) {
+            case MemsetBench: {
+                memcpy_noinline(src.get(), dst.get(), cur_size);
+                memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+                copy_start = chrono::high_resolution_clock::now();
+                for (int i = 0; i < iter_per_size; i++) {
+                    memset_noinline(dst.get(), 0xdeadbeef, cur_size);
+                }
+                copy_end = chrono::high_resolution_clock::now();
+                break;
+            }
+            case MemcpyBench: {
+                memcpy_noinline(dst.get(), src.get(), cur_size);
+                memcpy_noinline(src.get(), dst.get(), cur_size);
+                copy_start = chrono::high_resolution_clock::now();
+                for (int i = 0; i < iter_per_size; i++) {
+                    memcpy_noinline(dst.get(), src.get(), cur_size);
+                }
+                copy_end = chrono::high_resolution_clock::now();
+                break;
+            }
+            case SumBench: {
+                uint64_t s = 0;
+                s += sum(src.get(), cur_size);
+                copy_start = chrono::high_resolution_clock::now();
+                for (int i = 0; i < iter_per_size; i++) {
+                    s += sum(src.get(), cur_size);
+                }
+                copy_end = chrono::high_resolution_clock::now();
+                tot_sum += s;
+                break;
+            }
+        }
+
+        double ns_per_copy = chrono::duration_cast<chrono::nanoseconds>(copy_end - copy_start).count() / double(iter_per_size);
+        double gb_per_sec = ((double)cur_size / (1ull<<30)) / (ns_per_copy / 1.0E9);
+        if (type == MemcpyBench)
+            gb_per_sec *= 2.0;
+        cout << "size: " << cur_size << ", perf: " << gb_per_sec << "GB/s, iter: " << iter_per_size << endl;
+    }
+    return 0;
+}
diff --git a/memcpy-perf/test-funcs.cpp b/memcpy-perf/test-funcs.cpp
new file mode 100644 (file)
index 0000000..9c992fa
--- /dev/null
@@ -0,0 +1,21 @@
+#include <string>
+
+void __attribute__((noinline)) memcpy_noinline(void *dst, void *src, size_t size)
+{
+    memcpy(dst,src,size);
+}
+
+void __attribute__((noinline)) memset_noinline(void *dst, int value, size_t size)
+{
+    memset(dst, value, size);
+}
+
+uint64_t __attribute__((noinline)) sum(volatile void *src, size_t size)
+{
+    uint64_t *src_ptr = (uint64_t*)src;
+    uint64_t sum = 0;
+    size_t len = size / sizeof(uint64_t);
+    for (size_t i = 0; i < len; i+=1)
+        sum += src_ptr[i];
+    return sum;
+}
diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp
new file mode 100644 (file)
index 0000000..c9671e1
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * 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 <malloc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <time.h>
+
+#include <new>
+
+#include "Action.h"
+#include "Threads.h"
+#include "Pointers.h"
+
+static uint64_t nanotime() {
+  struct timespec t;
+  t.tv_sec = t.tv_nsec = 0;
+  clock_gettime(CLOCK_MONOTONIC, &t);
+  return static_cast<uint64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
+}
+
+class EndThreadAction : public Action {
+ public:
+  EndThreadAction() {}
+
+  bool EndThread() override { return true; }
+
+  uint64_t Execute(Pointers*) override { return 0; }
+};
+
+class AllocAction : public Action {
+ public:
+  AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {}
+
+ protected:
+  uintptr_t key_pointer_ = 0;
+  size_t size_ = 0;
+};
+
+class MallocAction : public AllocAction {
+ public:
+  MallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%zu", &size_) != 1) {
+      is_error_ = true;
+    }
+  }
+
+  uint64_t Execute(Pointers* pointers) override {
+    uint64_t time_nsecs = nanotime();
+    void* memory = malloc(size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+};
+
+class CallocAction : public AllocAction {
+ public:
+  CallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%zu %zu", &n_elements_, &size_) != 2) {
+      is_error_ = true;
+    }
+  }
+
+  uint64_t Execute(Pointers* pointers) override {
+    uint64_t time_nsecs = nanotime();
+    void* memory = calloc(n_elements_, size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 0, n_elements_ * size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+
+ private:
+  size_t n_elements_ = 0;
+};
+
+class ReallocAction : public AllocAction {
+ public:
+  ReallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%" SCNxPTR " %zu", &old_pointer_, &size_) != 2) {
+      is_error_ = true;
+    }
+  }
+
+  bool DoesFree() override { return old_pointer_ != 0; }
+
+  uint64_t Execute(Pointers* pointers) override {
+    void* old_memory = nullptr;
+    if (old_pointer_ != 0) {
+      old_memory = pointers->Remove(old_pointer_);
+    }
+
+    uint64_t time_nsecs = nanotime();
+    void* memory = realloc(old_memory, size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+
+ private:
+  uintptr_t old_pointer_ = 0;
+};
+
+class MemalignAction : public AllocAction {
+ public:
+  MemalignAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%zu %zu", &align_, &size_) != 2) {
+      is_error_ = true;
+    }
+  }
+
+  uint64_t Execute(Pointers* pointers) override {
+    uint64_t time_nsecs = nanotime();
+    void* memory = memalign(align_, size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+
+ private:
+  size_t align_ = 0;
+};
+
+class FreeAction : public AllocAction {
+ public:
+  FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
+  }
+
+  bool DoesFree() override { return key_pointer_ != 0; }
+
+  uint64_t Execute(Pointers* pointers) override {
+    if (key_pointer_) {
+      void* memory = pointers->Remove(key_pointer_);
+      uint64_t time_nsecs = nanotime();
+      free(memory);
+      return nanotime() - time_nsecs;
+    }
+    return 0;
+  }
+};
+
+size_t Action::MaxActionSize() {
+  size_t max = MAX(sizeof(EndThreadAction), sizeof(MallocAction));
+  max = MAX(max, sizeof(CallocAction));
+  max = MAX(max, sizeof(ReallocAction));
+  max = MAX(max, sizeof(MemalignAction));
+  return MAX(max, sizeof(FreeAction));
+}
+
+Action* Action::CreateAction(uintptr_t key_pointer, const char* type,
+                             const char* line, void* action_memory) {
+  Action* action = nullptr;
+  if (strcmp(type, "malloc") == 0) {
+    action = new (action_memory) MallocAction(key_pointer, line);
+  } else if (strcmp(type, "free") == 0) {
+    action = new (action_memory) FreeAction(key_pointer);
+  } else if (strcmp(type, "calloc") == 0) {
+    action = new (action_memory) CallocAction(key_pointer, line);
+  } else if (strcmp(type, "realloc") == 0) {
+    action = new (action_memory) ReallocAction(key_pointer, line);
+  } else if (strcmp(type, "memalign") == 0) {
+    action = new (action_memory) MemalignAction(key_pointer, line);
+  } else if (strcmp(type, "thread_done") == 0) {
+    action = new (action_memory) EndThreadAction();
+  }
+
+  if (action == nullptr || action->IsError()) {
+    return nullptr;
+  }
+  return action;
+}
diff --git a/memory_replay/Action.h b/memory_replay/Action.h
new file mode 100644 (file)
index 0000000..f498f52
--- /dev/null
@@ -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 _MEMORY_REPLAY_ACTION_H
+#define _MEMORY_REPLAY_ACTION_H
+
+#include <stdint.h>
+
+class Pointers;
+
+class Action {
+ public:
+  Action() {}
+  virtual ~Action() {}
+
+  virtual uint64_t Execute(Pointers* pointers) = 0;
+
+  bool IsError() { return is_error_; };
+
+  virtual bool EndThread() { return false; }
+
+  virtual bool DoesFree() { return false; }
+
+  static size_t MaxActionSize();
+  static Action* CreateAction(uintptr_t key_pointer, const char* type,
+                              const char* line, void* action_memory);
+
+ protected:
+  bool is_error_ = false;
+};
+
+#endif // _MEMORY_REPLAY_ACTION_H
diff --git a/memory_replay/Android.mk b/memory_replay/Android.mk
new file mode 100644 (file)
index 0000000..7179d61
--- /dev/null
@@ -0,0 +1,79 @@
+LOCAL_PATH := $(call my-dir)
+
+memory_replay_src_files := \
+       Action.cpp \
+       LineBuffer.cpp \
+       NativeInfo.cpp \
+       Pointers.cpp \
+       Thread.cpp \
+       Threads.cpp \
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(memory_replay_src_files) main.cpp
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(memory_replay_src_files) main.cpp
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_LDLIBS := -lrt
+include $(BUILD_HOST_EXECUTABLE)
+
+memory_replay_test_src_files := \
+       tests/ActionTest.cpp \
+       tests/LineBufferTest.cpp \
+       tests/NativeInfoTest.cpp \
+       tests/PointersTest.cpp \
+       tests/ThreadTest.cpp \
+       tests/ThreadsTest.cpp \
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+       $(memory_replay_src_files) \
+       $(memory_replay_test_src_files) \
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/tests
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay_tests
+
+LOCAL_SHARED_LIBRARIES := libbase
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+       $(memory_replay_src_files) \
+       $(memory_replay_test_src_files) \
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/tests
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay_tests
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SHARED_LIBRARIES := libbase
+LOCAL_LDLIBS := -lrt
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_HOST_NATIVE_TEST)
+
+memory_replay_src_files :=
+memory_replay_test_src_files :=
diff --git a/memory_replay/LineBuffer.cpp b/memory_replay/LineBuffer.cpp
new file mode 100644 (file)
index 0000000..5e65ad6
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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 <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "LineBuffer.h"
+
+LineBuffer::LineBuffer(int fd, char* buffer, size_t buffer_len) : fd_(fd), buffer_(buffer), buffer_len_(buffer_len) {
+}
+
+bool LineBuffer::GetLine(char** line, size_t* line_len) {
+  while (true) {
+    if (bytes_ > 0) {
+      char* newline = reinterpret_cast<char*>(memchr(buffer_ + start_, '\n', bytes_));
+      if (newline != nullptr) {
+        *newline = '\0';
+        *line = buffer_ + start_;
+        start_ = newline - buffer_ + 1;
+        bytes_ -= newline - *line + 1;
+        *line_len = newline - *line;
+        return true;
+      }
+    }
+    if (start_ > 0) {
+      // Didn't find anything, copy the current to the front of the buffer.
+      memmove(buffer_, buffer_ + start_, bytes_);
+      start_ = 0;
+    }
+    ssize_t bytes = TEMP_FAILURE_RETRY(read(fd_, buffer_ + bytes_, buffer_len_ - bytes_ - 1));
+    if (bytes <= 0) {
+      if (bytes_ > 0) {
+        // The read data might not contain a nul terminator, so add one.
+        buffer_[bytes_] = '\0';
+        *line = buffer_ + start_;
+        *line_len = bytes_;
+        bytes_ = 0;
+        start_ = 0;
+        return true;
+      }
+      return false;
+    }
+    bytes_ += bytes;
+  }
+}
diff --git a/memory_replay/LineBuffer.h b/memory_replay/LineBuffer.h
new file mode 100644 (file)
index 0000000..934d302
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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 _MEMORY_REPLAY_LINE_BUFFER_H
+#define _MEMORY_REPLAY_LINE_BUFFER_H
+
+#include <stdint.h>
+
+class LineBuffer {
+ public:
+  LineBuffer(int fd, char* buffer, size_t buffer_len);
+
+  bool GetLine(char** line, size_t* line_len);
+
+ private:
+  int fd_;
+  char* buffer_ = nullptr;
+  size_t buffer_len_ = 0;
+  size_t start_ = 0;
+  size_t bytes_ = 0;
+};
+
+#endif // _MEMORY_REPLAY_LINE_BUFFER_H
diff --git a/memory_replay/NOTICE b/memory_replay/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
new file mode 100644 (file)
index 0000000..b88eaf6
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "LineBuffer.h"
+#include "NativeInfo.h"
+
+// This function is not re-entrant since it uses a static buffer for
+// the line data.
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes) {
+  static char map_buffer[65535];
+  LineBuffer line_buf(smaps_fd, map_buffer, sizeof(map_buffer));
+  char* line;
+  size_t total_pss_bytes = 0;
+  size_t total_va_bytes = 0;
+  size_t line_len;
+  bool native_map = false;
+  while (line_buf.GetLine(&line, &line_len)) {
+    uintptr_t start, end;
+    int name_pos;
+    size_t native_pss_kB;
+    if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %*4s %*x %*x:%*x %*d %n",
+        &start, &end, &name_pos) == 2) {
+      if (strcmp(line + name_pos, "[anon:libc_malloc]") == 0 ||
+          strcmp(line + name_pos, "[heap]") == 0) {
+        total_va_bytes += end - start;
+        native_map = true;
+      } else {
+        native_map = false;
+      }
+    } else if (native_map && sscanf(line, "Pss: %zu", &native_pss_kB) == 1) {
+      total_pss_bytes += native_pss_kB * 1024;
+    }
+  }
+  close(smaps_fd);
+  *pss_bytes = total_pss_bytes;
+  *va_bytes = total_va_bytes;
+}
+
+void PrintNativeInfo(const char* preamble) {
+  size_t pss_bytes;
+  size_t va_bytes;
+
+  int smaps_fd = open("/proc/self/smaps", O_RDONLY);
+  if (smaps_fd == -1) {
+    err(1, "Cannot open /proc/self/smaps: %s\n", strerror(errno));
+  }
+
+  GetNativeInfo(smaps_fd, &pss_bytes, &va_bytes);
+  printf("%sNative PSS: %zu bytes %0.2fMB\n", preamble, pss_bytes, pss_bytes/(1024*1024.0));
+  printf("%sNative VA Space: %zu bytes %0.2fMB\n", preamble, va_bytes, va_bytes/(1024*1024.0));
+  fflush(stdout);
+
+  close(smaps_fd);
+}
similarity index 67%
rename from ext4_utils/canned_fs_config.h
rename to memory_replay/NativeInfo.h
index d9f51ca..5953695 100644 (file)
  * limitations under the License.
  */
 
-#ifndef _CANNED_FS_CONFIG_H
-#define _CANNED_FS_CONFIG_H
+#ifndef _MEMORY_REPLAY_NATIVE_INFO_H
+#define _MEMORY_REPLAY_NATIVE_INFO_H
 
-#include <inttypes.h>
+// This function is not re-entrant.
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes);
 
-int load_canned_fs_config(const char* fn);
-void canned_fs_config(const char* path, int dir, const char* target_out_path,
-                      unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities);
+// This function is not re-entrant.
+void PrintNativeInfo(const char* preamble);
 
-#endif
+#endif // _MEMORY_REPLAY_NATIVE_INFO_H
diff --git a/memory_replay/Pointers.cpp b/memory_replay/Pointers.cpp
new file mode 100644 (file)
index 0000000..b9604f0
--- /dev/null
@@ -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 <err.h>
+#include <inttypes.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "err.h"
+#include "Pointers.h"
+
+Pointers::Pointers(size_t max_allocs) {
+  size_t pagesize = getpagesize();
+  // Create a mmap that contains a 4:1 ratio of allocations to entries.
+  // Align to a page.
+  pointers_size_ = (max_allocs * 4 * sizeof(pointer_data) + pagesize - 1) & ~(pagesize - 1);
+  max_pointers_ = pointers_size_ / sizeof(pointer_data);
+  void* memory = mmap(nullptr, pointers_size_, PROT_READ | PROT_WRITE,
+                      MAP_ANON | MAP_PRIVATE, -1, 0);
+  if (memory == MAP_FAILED) {
+    err(1, "Unable to allocate data for pointer hash: %zu total_allocs\n", max_allocs);
+  }
+  // Make sure that all of the PSS for this is counted right away.
+  memset(memory, 0, pointers_size_);
+  pointers_ = reinterpret_cast<pointer_data*>(memory);
+}
+
+Pointers::~Pointers() {
+  if (pointers_ != nullptr) {
+    munmap(pointers_, pointers_size_);
+    pointers_ = nullptr;
+  }
+}
+
+void Pointers::Add(uintptr_t key_pointer, void* pointer) {
+  pointer_data* data = FindEmpty(key_pointer);
+  if (data == nullptr) {
+    err(1, "No empty entry found for 0x%" PRIxPTR "\n", key_pointer);
+  }
+  atomic_store(&data->key_pointer, key_pointer);
+  data->pointer = pointer;
+}
+
+void* Pointers::Remove(uintptr_t key_pointer) {
+  if (key_pointer == 0) {
+    err(1, "Illegal zero value passed to Remove\n");
+  }
+
+  pointer_data* data = Find(key_pointer);
+  if (data == nullptr) {
+    err(1, "No pointer value found for 0x%" PRIxPTR "\n", key_pointer);
+  }
+
+  void* pointer = data->pointer;
+  atomic_store(&data->key_pointer, uintptr_t(0));
+
+  return pointer;
+}
+
+pointer_data* Pointers::Find(uintptr_t key_pointer) {
+  size_t index = GetHash(key_pointer);
+  for (size_t entries = max_pointers_; entries != 0; entries--) {
+    if (atomic_load(&pointers_[index].key_pointer) == key_pointer) {
+      return pointers_ + index;
+    }
+    if (++index == max_pointers_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+pointer_data* Pointers::FindEmpty(uintptr_t key_pointer) {
+  size_t index = GetHash(key_pointer);
+  for (size_t entries = 0; entries < max_pointers_; entries++) {
+    uintptr_t empty = 0;
+    if (atomic_compare_exchange_strong(&pointers_[index].key_pointer, &empty,
+        uintptr_t(1))) {
+      return pointers_ + index;
+    }
+    if (++index == max_pointers_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+size_t Pointers::GetHash(uintptr_t key_pointer) {
+  return key_pointer % max_pointers_;
+}
+
+void Pointers::FreeAll() {
+  for (size_t i = 0; i < max_pointers_; i++) {
+    if (atomic_load(&pointers_[i].key_pointer) != 0) {
+      free(pointers_[i].pointer);
+    }
+  }
+}
diff --git a/memory_replay/Pointers.h b/memory_replay/Pointers.h
new file mode 100644 (file)
index 0000000..2491716
--- /dev/null
@@ -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.
+ */
+
+#ifndef _MEMORY_REPLAY_POINTERS_H
+#define _MEMORY_REPLAY_POINTERS_H
+
+#include <stdatomic.h>
+#include <stdint.h>
+
+struct pointer_data {
+  std::atomic_uintptr_t key_pointer;
+  void* pointer;
+};
+
+class Pointers {
+ public:
+  Pointers(size_t max_allocs);
+  virtual ~Pointers();
+
+  void Add(uintptr_t key_pointer, void* pointer);
+
+  void* Remove(uintptr_t key_pointer);
+
+  size_t max_pointers() { return max_pointers_; }
+
+  void FreeAll();
+
+ private:
+  pointer_data* FindEmpty(uintptr_t key_pointer);
+  pointer_data* Find(uintptr_t key_pointer);
+  size_t GetHash(uintptr_t key_pointer);
+
+  pointer_data* pointers_ = nullptr;
+  size_t pointers_size_ = 0;
+  size_t max_pointers_ = 0;
+};
+
+#endif // _MEMORY_REPLAY_POINTERS_H
diff --git a/memory_replay/Thread.cpp b/memory_replay/Thread.cpp
new file mode 100644 (file)
index 0000000..497b288
--- /dev/null
@@ -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 <pthread.h>
+
+#include "Action.h"
+#include "Thread.h"
+
+Thread::Thread() {
+  pthread_cond_init(&cond_, nullptr);
+}
+
+Thread::~Thread() {
+  pthread_cond_destroy(&cond_);
+}
+
+void Thread::WaitForReady() {
+  pthread_mutex_lock(&mutex_);
+  while (pending_) {
+    pthread_cond_wait(&cond_, &mutex_);
+  }
+  pthread_mutex_unlock(&mutex_);
+}
+
+void Thread::WaitForPending() {
+  pthread_mutex_lock(&mutex_);
+  while (!pending_) {
+    pthread_cond_wait(&cond_, &mutex_);
+  }
+  pthread_mutex_unlock(&mutex_);
+}
+
+void Thread::SetPending() {
+  pthread_mutex_lock(&mutex_);
+  pending_ = true;
+  pthread_mutex_unlock(&mutex_);
+  pthread_cond_signal(&cond_);
+}
+
+void Thread::ClearPending() {
+  pthread_mutex_lock(&mutex_);
+  pending_ = false;
+  pthread_mutex_unlock(&mutex_);
+  pthread_cond_signal(&cond_);
+}
+
+Action* Thread::CreateAction(uintptr_t key_pointer, const char* type, const char* line) {
+  return Action::CreateAction(key_pointer, type, line, action_memory_);
+}
diff --git a/memory_replay/Thread.h b/memory_replay/Thread.h
new file mode 100644 (file)
index 0000000..7724c12
--- /dev/null
@@ -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 _MEMORY_REPLAY_THREAD_H
+#define _MEMORY_REPLAY_THREAD_H
+
+#include <pthread.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+class Action;
+class Pointers;
+
+constexpr size_t ACTION_MEMORY_SIZE = 128;
+
+class Thread {
+ public:
+  Thread();
+  virtual ~Thread();
+
+  void WaitForReady();
+  void WaitForPending();
+  void SetPending();
+  void ClearPending();
+
+  Action* CreateAction(uintptr_t key_pointer, const char* type, const char* line);
+  void AddTimeNsecs(uint64_t nsecs) { total_time_nsecs_ += nsecs; }
+
+  void set_pointers(Pointers* pointers) { pointers_ = pointers; }
+  Pointers* pointers() { return pointers_; }
+
+  Action* GetAction() { return reinterpret_cast<Action*>(action_memory_); }
+
+ private:
+  pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
+  pthread_cond_t cond_;
+  bool pending_ = false;
+
+  pthread_t thread_id_;
+  pid_t tid_ = 0;
+  uint64_t total_time_nsecs_ = 0;
+
+  Pointers* pointers_ = nullptr;
+
+  // Per thread memory for an Action. Only one action can be processed.
+  // at a time.
+  static constexpr size_t ACTION_SIZE = 128;
+  uint8_t action_memory_[ACTION_SIZE];
+
+  friend class Threads;
+};
+
+#endif // _MEMORY_REPLAY_THREAD_H
diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp
new file mode 100644 (file)
index 0000000..81a679e
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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 <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <new>
+
+#include "Action.h"
+#include "Thread.h"
+#include "Threads.h"
+
+void* ThreadRunner(void* data) {
+  Thread* thread = reinterpret_cast<Thread*>(data);
+  while (true) {
+    thread->WaitForPending();
+    Action* action = thread->GetAction();
+    thread->AddTimeNsecs(action->Execute(thread->pointers()));
+    bool end_thread = action->EndThread();
+    thread->ClearPending();
+    if (end_thread) {
+      break;
+    }
+  }
+  return nullptr;
+}
+
+Threads::Threads(Pointers* pointers, size_t max_threads)
+    : pointers_(pointers), max_threads_(max_threads) {
+  size_t pagesize = getpagesize();
+  data_size_ = (max_threads_ * sizeof(Thread) + pagesize - 1) & ~(pagesize - 1);
+  max_threads_ = data_size_ / sizeof(Thread);
+
+  void* memory = mmap(nullptr, data_size_, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (memory == MAP_FAILED) {
+    err(1, "Failed to map in memory for Threads: map size %zu, max threads %zu\n",
+        data_size_, max_threads_);
+  }
+
+  if (Thread::ACTION_SIZE < Action::MaxActionSize()) {
+    err(1, "Thread action size is too small: ACTION_SIZE %zu, max size %zu\n",
+        Thread::ACTION_SIZE, Action::MaxActionSize());
+  }
+
+  threads_ = new (memory) Thread[max_threads_];
+}
+
+Threads::~Threads() {
+  if (threads_) {
+    munmap(threads_, data_size_);
+    threads_ = nullptr;
+    data_size_ = 0;
+  }
+}
+
+Thread* Threads::CreateThread(pid_t tid) {
+  if (num_threads_ == max_threads_) {
+    err(1, "Too many threads created, current max %zu.\n", num_threads_);
+  }
+  Thread* thread = FindEmptyEntry(tid);
+  if (thread == nullptr) {
+    err(1, "No empty entries found, current max %zu, num threads %zu\n",
+          max_threads_, num_threads_);
+  }
+  thread->tid_ = tid;
+  thread->pointers_ = pointers_;
+  thread->total_time_nsecs_ = 0;
+  if (pthread_create(&thread->thread_id_, nullptr, ThreadRunner, thread) == -1) {
+    err(1, "Failed to create thread %d: %s\n", tid, strerror(errno));
+  }
+
+  num_threads_++;
+  return thread;
+}
+
+Thread* Threads::FindThread(pid_t tid) {
+  size_t index = GetHashEntry(tid);
+  for (size_t entries = num_threads_; entries != 0; ) {
+    pid_t cur_tid = threads_[index].tid_;
+    if (cur_tid == tid) {
+      return threads_ + index;
+    }
+    if (cur_tid != 0) {
+      entries--;
+    }
+    if (++index == max_threads_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+void Threads::WaitForAllToQuiesce() {
+  for (size_t i = 0, threads = 0; threads < num_threads_; i++) {
+    pid_t cur_tid = threads_[i].tid_;
+    if (cur_tid != 0) {
+      threads++;
+      threads_[i].WaitForReady();
+    }
+  }
+}
+
+size_t Threads::GetHashEntry(pid_t tid) {
+  return tid % max_threads_;
+}
+
+Thread* Threads::FindEmptyEntry(pid_t tid) {
+  size_t index = GetHashEntry(tid);
+  for (size_t entries = 0; entries < max_threads_; entries++) {
+    if (threads_[index].tid_ == 0) {
+      return threads_ + index;
+    }
+    if (++index == max_threads_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+void Threads::Finish(Thread* thread) {
+  int ret = pthread_join(thread->thread_id_, nullptr);
+  if (ret != 0) {
+    fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
+    exit(1);
+  }
+  total_time_nsecs_ += thread->total_time_nsecs_;
+  thread->tid_ = 0;
+  num_threads_--;
+}
+
+void Threads::FinishAll() {
+  for (size_t i = 0; i < max_threads_; i++) {
+    if (threads_[i].tid_ != 0) {
+      threads_[i].CreateAction(0, "thread_done", nullptr);
+      threads_[i].SetPending();
+      Finish(threads_ + i);
+    }
+  }
+}
diff --git a/memory_replay/Threads.h b/memory_replay/Threads.h
new file mode 100644 (file)
index 0000000..4778bff
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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 _MEMORY_REPLAY_THREADS_H
+#define _MEMORY_REPLAY_THREADS_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+class Pointers;
+class Thread;
+
+class Threads {
+ public:
+  Threads(Pointers* pointers, size_t max_threads);
+  virtual ~Threads();
+
+  Thread* CreateThread(pid_t tid);
+  Thread* FindThread(pid_t tid);
+  void WaitForAllToQuiesce();
+  void Finish(Thread* thread);
+  void FinishAll();
+
+  size_t num_threads() { return num_threads_; }
+  size_t max_threads() { return max_threads_; }
+  uint64_t total_time_nsecs() { return total_time_nsecs_; }
+
+ private:
+  Pointers* pointers_ = nullptr;
+  Thread* threads_ = nullptr;
+  size_t data_size_ = 0;
+  size_t max_threads_ = 0;
+  size_t num_threads_= 0;
+  uint64_t total_time_nsecs_ = 0;
+
+  Thread* FindEmptyEntry(pid_t tid);
+  size_t GetHashEntry(pid_t tid);
+
+  void ClearData();
+
+  friend Thread;
+};
+
+#endif // _MEMORY_REPLAY_THREADS_H
diff --git a/memory_replay/dumps/README b/memory_replay/dumps/README
new file mode 100644 (file)
index 0000000..d306b9a
--- /dev/null
@@ -0,0 +1,71 @@
+The files in this directory are a collection of recordings of
+the memory allocations of a set of apps.
+
+In order to run these files through the tool, they will need to be placed
+unzipped on the device.
+
+Format of dumps:
+
+<tid>: <action_name> <ptr> [<optional_arguments>]
+
+<tid>
+  The pid_t value that is the gettid() value recorded during the run.
+
+<action_name> 
+  One of:
+    malloc - Allocate memory using the malloc function.
+    calloc - Allocate memory using the calloc function.
+    memalign - Allocate memory using the memalign function. This is used
+               during recording for either memalign or posix_memalign.
+    realloc - Allocate memory using the realloc function.
+    free - Free memory allocated using one of the above actions.
+    thread_done - Terminate the thread with the given tid.
+
+Format of each action:
+
+<tid>: malloc <ptr> <size>
+ Allocation made by malloc(<size>).  <ptr> is the value returned by malloc.
+
+Example:
+
+100: malloc 0xb48390a0 48
+
+<tid>: calloc <ptr> <nmemb> <size>
+  Allocation made by calloc(<nmemb>, <size>. <ptr> is the value returned
+  by calloc.
+
+Example:
+
+200: calloc 0xb48c1100 32 8
+
+<tid>:realloc <new_ptr> <old_ptr> <size>
+  Allocation made by realloc(<old_ptr>, <size>). <old_ptr> can be 0x0
+  to indicate a realloc with a nullptr. <new_ptr> is the value returned
+  by realloc.
+
+Example:
+
+300: realloc 0x96b90920 0x93605280 150
+
+<tid>:memalign <ptr> <alignment> <size>
+  Allocation made by memalign(<alignment>, <size>). <ptr> is the value
+  returned by memalign.
+
+Example:
+
+400: memalign 0xae42d080 16 104
+
+<tid>: free <ptr>
+  Find a previously allocated pointer <ptr> and call free(<ptr>).
+  <ptr> can be 0x0 to indicate the freeing of a nullptr.
+
+Example:
+
+500: free 0xb4827400
+
+<tid>: thread_done 0x0
+  Indicates that the thread <tid> has completed.
+
+Example:
+
+600: thread_done 0x0
diff --git a/memory_replay/dumps/camera.zip b/memory_replay/dumps/camera.zip
new file mode 100644 (file)
index 0000000..b9d1fb1
Binary files /dev/null and b/memory_replay/dumps/camera.zip differ
diff --git a/memory_replay/dumps/gmail.zip b/memory_replay/dumps/gmail.zip
new file mode 100644 (file)
index 0000000..45e0d04
Binary files /dev/null and b/memory_replay/dumps/gmail.zip differ
diff --git a/memory_replay/dumps/maps.zip b/memory_replay/dumps/maps.zip
new file mode 100644 (file)
index 0000000..17cf4e7
Binary files /dev/null and b/memory_replay/dumps/maps.zip differ
diff --git a/memory_replay/dumps/surfaceflinger.zip b/memory_replay/dumps/surfaceflinger.zip
new file mode 100644 (file)
index 0000000..495f2f2
Binary files /dev/null and b/memory_replay/dumps/surfaceflinger.zip differ
diff --git a/memory_replay/dumps/system_server.zip b/memory_replay/dumps/system_server.zip
new file mode 100644 (file)
index 0000000..bb43baf
Binary files /dev/null and b/memory_replay/dumps/system_server.zip differ
diff --git a/memory_replay/dumps/systemui.zip b/memory_replay/dumps/systemui.zip
new file mode 100644 (file)
index 0000000..8909fac
Binary files /dev/null and b/memory_replay/dumps/systemui.zip differ
diff --git a/memory_replay/dumps/youtube.zip b/memory_replay/dumps/youtube.zip
new file mode 100644 (file)
index 0000000..bea68ab
Binary files /dev/null and b/memory_replay/dumps/youtube.zip differ
diff --git a/memory_replay/fast/.clang-format b/memory_replay/fast/.clang-format
new file mode 100644 (file)
index 0000000..9b7478c
--- /dev/null
@@ -0,0 +1,15 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 2
+ContinuationIndentWidth: 2
+PointerAlignment: Left
+TabWidth: 2
+UseTab: Never
+PenaltyExcessCharacter: 32
+
+Cpp11BracedListStyle: false
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
new file mode 100644 (file)
index 0000000..42b5298
--- /dev/null
@@ -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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "Action.h"
+#include "LineBuffer.h"
+#include "NativeInfo.h"
+#include "Pointers.h"
+#include "Thread.h"
+#include "Threads.h"
+
+static char g_buffer[65535];
+
+size_t GetMaxAllocs(int fd) {
+  lseek(fd, 0, SEEK_SET);
+  LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
+  char* line;
+  size_t line_len;
+  size_t num_allocs = 0;
+  while (line_buf.GetLine(&line, &line_len)) {
+    char* word = reinterpret_cast<char*>(memchr(line, ':', line_len));
+    if (word == nullptr) {
+      continue;
+    }
+
+    word++;
+    while (*word++ == ' ');
+    // This will treat a realloc as an allocation, even if it frees
+    // another allocation. Since reallocs are relatively rare, this
+    // shouldn't inflate the numbers that much.
+    if (*word == 'f') {
+      // Check if this is a free of zero.
+      uintptr_t pointer;
+      if (sscanf(word, "free %" SCNxPTR, &pointer) == 1 && pointer != 0) {
+        num_allocs--;
+      }
+    } else if (*word != 't') {
+      // Skip the thread_done message.
+      num_allocs++;
+    }
+  }
+  return num_allocs;
+}
+
+void ProcessDump(int fd, size_t max_allocs, size_t max_threads) {
+  lseek(fd, 0, SEEK_SET);
+  Pointers pointers(max_allocs);
+  Threads threads(&pointers, max_threads);
+
+  printf("Maximum threads available:   %zu\n", threads.max_threads());
+  printf("Maximum allocations in dump: %zu\n", max_allocs);
+  printf("Total pointers available:    %zu\n", pointers.max_pointers());
+  printf("\n");
+
+  PrintNativeInfo("Initial ");
+
+  LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
+  char* line;
+  size_t line_len;
+  size_t line_number = 0;
+  while (line_buf.GetLine(&line, &line_len)) {
+    pid_t tid;
+    int line_pos = 0;
+    char type[128];
+    uintptr_t key_pointer;
+
+    // Every line is of this format:
+    //   <tid>: <action_type> <pointer>
+    // Some actions have extra arguments which will be used and verified
+    // when creating the Action object.
+    if (sscanf(line, "%d: %s %" SCNxPTR " %n", &tid, type, &key_pointer, &line_pos) != 3) {
+      err(1, "Unparseable line found: %s\n", line);
+    }
+    line_number++;
+    if ((line_number % 100000) == 0) {
+      printf("  At line %zu:\n", line_number);
+      PrintNativeInfo("    ");
+    }
+    Thread* thread = threads.FindThread(tid);
+    if (thread == nullptr) {
+      thread = threads.CreateThread(tid);
+    }
+
+    // Wait for the thread to complete any previous actions before handling
+    // the next action.
+    thread->WaitForReady();
+
+    Action* action = thread->CreateAction(key_pointer, type, line + line_pos);
+    if (action == nullptr) {
+      err(1, "Cannot create action from line: %s\n", line);
+    }
+
+    bool does_free = action->DoesFree();
+    if (does_free) {
+      // Make sure that any other threads doing allocations are complete
+      // before triggering the action. Otherwise, another thread could
+      // be creating the allocation we are going to free.
+      threads.WaitForAllToQuiesce();
+    }
+
+    // Tell the thread to execute the action.
+    thread->SetPending();
+
+    if (action->EndThread()) {
+      // Wait for the thread to finish and clear the thread entry.
+      threads.Finish(thread);
+    }
+
+    // Wait for this action to complete. This avoids a race where
+    // another thread could be creating the same allocation where are
+    // trying to free.
+    if (does_free) {
+      thread->WaitForReady();
+    }
+  }
+  // Wait for all threads to stop processing actions.
+  threads.WaitForAllToQuiesce();
+
+  PrintNativeInfo("Final ");
+
+  // Free any outstanding pointers.
+  // This allows us to run a tool like valgrind to verify that no memory
+  // is leaked and everything is accounted for during a run.
+  threads.FinishAll();
+  pointers.FreeAll();
+
+  // Print out the total time making all allocation calls.
+  printf("Total Allocation/Free Time: %" PRIu64 "ns %0.2fs\n",
+         threads.total_time_nsecs(), threads.total_time_nsecs()/1000000000.0);
+}
+
+constexpr size_t DEFAULT_MAX_THREADS = 512;
+
+int main(int argc, char** argv) {
+  if (argc != 2 && argc != 3) {
+    if (argc > 3) {
+      fprintf(stderr, "Only two arguments are expected.\n");
+    } else {
+      fprintf(stderr, "Requires at least one argument.\n");
+    }
+    fprintf(stderr, "Usage: %s MEMORY_LOG_FILE [MAX_THREADS]\n", basename(argv[0]));
+    return 1;
+  }
+
+  size_t max_threads = DEFAULT_MAX_THREADS;
+  if (argc == 3) {
+    max_threads = atoi(argv[2]);
+  }
+
+  int dump_fd = open(argv[1], O_RDONLY);
+  if (dump_fd == -1) {
+    fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno));
+    return 1;
+  }
+
+  printf("Processing: %s\n", argv[1]);
+
+  // Do a first pass to get the total number of allocations used at one
+  // time to allow a single mmap that can hold the maximum number of
+  // pointers needed at once.
+  size_t max_allocs = GetMaxAllocs(dump_fd);
+  ProcessDump(dump_fd, max_allocs, max_threads);
+
+  close(dump_fd);
+
+  return 0;
+}
diff --git a/memory_replay/tests/ActionTest.cpp b/memory_replay/tests/ActionTest.cpp
new file mode 100644 (file)
index 0000000..cd72c24
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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 <stdint.h>
+#include <string.h>
+
+#include "Action.h"
+#include "Pointers.h"
+
+TEST(ActionTest, malloc) {
+  uint8_t memory[Action::MaxActionSize()];
+  const char* line = "1024";
+  Action* action = Action::CreateAction(0x1234, "malloc", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, malloc_malformed) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0x1234, "malloc", line, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, free) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0x1234, "free", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_TRUE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  pointers.Add(0x1234, malloc(10));
+  action->Execute(&pointers);
+}
+
+TEST(ActionTest, calloc) {
+  uint8_t memory[128];
+  const char* line = "100 10";
+  Action* action = Action::CreateAction(0x1234, "calloc", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, free_zero) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0, "free", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+  // Should be a nop.
+  action->Execute(nullptr);
+}
+
+TEST(ActionTest, calloc_malformed) {
+  uint8_t memory[128];
+  const char* line1 = "100";
+  Action* action = Action::CreateAction(0x1234, "calloc", line1, memory);
+  ASSERT_FALSE(action != NULL);
+
+  const char* line2 = "";
+  action = Action::CreateAction(0x1234, "calloc", line2, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, realloc) {
+  uint8_t memory[128];
+  const char* line = "0xabcd 100";
+  Action* action = Action::CreateAction(0x1234, "realloc", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_TRUE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  pointers.Add(0xabcd, malloc(10));
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+
+  const char* null_line = "0x0 100";
+  action = Action::CreateAction(0x1234, "realloc", null_line, memory);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  action->Execute(&pointers);
+  pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, realloc_malformed) {
+  uint8_t memory[128];
+  const char* line1 = "0x100";
+  Action* action = Action::CreateAction(0x1234, "realloc", line1, memory);
+  ASSERT_FALSE(action != NULL);
+
+  const char* line2 = "";
+  action = Action::CreateAction(0x1234, "realloc", line2, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, memalign) {
+  uint8_t memory[128];
+  const char* line = "16 300";
+  Action* action = Action::CreateAction(0x1234, "memalign", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, memalign_malformed) {
+  uint8_t memory[128];
+  const char* line1 = "100";
+  Action* action = Action::CreateAction(0x1234, "memalign", line1, memory);
+  ASSERT_FALSE(action != NULL);
+
+  const char* line2 = "";
+  action = Action::CreateAction(0x1234, "memalign", line2, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, endthread) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0x0, "thread_done", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_TRUE(action->EndThread());
+
+  action->Execute(nullptr);
+}
diff --git a/memory_replay/tests/LineBufferTest.cpp b/memory_replay/tests/LineBufferTest.cpp
new file mode 100644 (file)
index 0000000..29d1b53
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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>
+
+#include <android-base/test_utils.h>
+
+#include "LineBuffer.h"
+
+class LineBufferTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    tmp_file_ = new TemporaryFile();
+    ASSERT_TRUE(tmp_file_->fd != -1);
+  }
+
+  void TearDown() override {
+    delete tmp_file_;
+  }
+
+ TemporaryFile* tmp_file_ = nullptr;
+};
+
+TEST_F(LineBufferTest, single_line) {
+  std::string line_data;
+  line_data += "Single line with newline.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Single line with newline.", line);
+  ASSERT_EQ(sizeof("Single line with newline.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_line_no_newline) {
+  std::string line_data;
+  line_data += "Single line with no newline.";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Single line with no newline.", line);
+  ASSERT_EQ(sizeof("Single line with no newline.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_read) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last.", line);
+  ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_read_no_end_newline) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last no newline.";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last no newline.", line);
+  ASSERT_EQ(sizeof("Third line is last no newline.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, one_line_per_read) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last.\n";
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[24];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last.", line);
+  ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The fourth line.", line);
+  ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, multiple_line_per_read_multiple_reads) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last.\n";
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[60];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last.", line);
+  ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The fourth line.", line);
+  ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, line_larger_than_buffer) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "This is a really, really, really, kind of long.\n";
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[25];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("This is a really, really", line);
+  ASSERT_EQ(sizeof(buffer) - 1, line_len);
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ(", really, kind of long.", line);
+  ASSERT_EQ(sizeof(", really, kind of long.") - 1, line_len);
+
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The fourth line.", line);
+  ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
diff --git a/memory_replay/tests/NativeInfoTest.cpp b/memory_replay/tests/NativeInfoTest.cpp
new file mode 100644 (file)
index 0000000..59dbbd9
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * 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 <stdint.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+
+#include "NativeInfo.h"
+
+class NativeInfoTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    tmp_file_ = new TemporaryFile();
+    ASSERT_TRUE(tmp_file_->fd != -1);
+  }
+
+  void TearDown() override {
+    delete tmp_file_;
+  }
+
+ TemporaryFile* tmp_file_ = nullptr;
+};
+
+TEST_F(NativeInfoTest, no_matching) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [anon:thread signal stack]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   12 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:thread signal stack]\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(0U, pss_bytes);
+  ASSERT_EQ(0U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, multiple_anons) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   12 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   20 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(32768U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, multiple_heaps) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   20 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(45056U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, mix_heap_anon) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   32 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [anon:skip]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   32 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:skip]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   40 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f3e000-b6f3f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(73728U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
diff --git a/memory_replay/tests/PointersTest.cpp b/memory_replay/tests/PointersTest.cpp
new file mode 100644 (file)
index 0000000..a39f018
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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 "Pointers.h"
+
+TEST(PointersTest, smoke) {
+  Pointers pointers(1);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  void* memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+}
+
+TEST(PointersTest, readd_pointer) {
+  Pointers pointers(1);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  void* memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+  pointers.Add(0x1234, reinterpret_cast<void*>(0x5555));
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0x5555), memory_pointer);
+}
+
+
+TEST(PointersTest, expect_collision) {
+  Pointers pointers(2);
+
+  // This assumes the simple hash being used will result in a collision
+  // hitting the same entry.
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  pointers.Add(0x11234, reinterpret_cast<void*>(0xabcf));
+  void* memory_pointer = pointers.Remove(0x11234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcf), memory_pointer);
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+}
+
+TEST(PointersTest, multiple_add_removes) {
+  Pointers pointers(4);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  pointers.Add(0x1235, reinterpret_cast<void*>(0xabcf));
+  pointers.Add(0x1236, reinterpret_cast<void*>(0xabc1));
+  pointers.Add(0x1237, reinterpret_cast<void*>(0xabc2));
+
+  void* memory_pointer = pointers.Remove(0x1236);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabc1), memory_pointer);
+
+  pointers.Add(0x2349, reinterpret_cast<void*>(0x2abcd));
+
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+  memory_pointer = pointers.Remove(0x1237);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabc2), memory_pointer);
+
+  pointers.Add(0x3500, reinterpret_cast<void*>(0x3abcd));
+
+  memory_pointer = pointers.Remove(0x3500);
+  ASSERT_EQ(reinterpret_cast<void*>(0x3abcd), memory_pointer);
+  memory_pointer = pointers.Remove(0x2349);
+  ASSERT_EQ(reinterpret_cast<void*>(0x2abcd), memory_pointer);
+}
+
+static void TestNoEntriesLeft() {
+  Pointers pointers(1);
+
+  // Even though we've requested only one pointer, we get more due
+  // to the way the data is allocated.
+  for (size_t i = 0; i <= pointers.max_pointers(); i++) {
+    pointers.Add(0x1234 + i, reinterpret_cast<void*>(0xabcd + i));
+  }
+}
+
+TEST(PointersTest_DeathTest, no_entries_left) {
+  ASSERT_EXIT(TestNoEntriesLeft(), ::testing::ExitedWithCode(1), "");
+}
+
+static void TestFindNoPointer() {
+  Pointers pointers(1);
+
+  pointers.Remove(0x1234);
+}
+
+TEST(PointersTest_DeathTest, find_no_pointer) {
+  ASSERT_EXIT(TestFindNoPointer(), ::testing::ExitedWithCode(1), "");
+}
+
+static void TestRemoveZeroValue() {
+  Pointers pointers(1);
+
+  void* memory = pointers.Remove(0);
+  if (memory) {}
+}
+
+TEST(PointersTest_DeathTest, remove_zero_value) {
+  ASSERT_EXIT(TestRemoveZeroValue(), ::testing::ExitedWithCode(1), "");
+}
diff --git a/memory_replay/tests/ThreadTest.cpp b/memory_replay/tests/ThreadTest.cpp
new file mode 100644 (file)
index 0000000..7249290
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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 <pthread.h>
+#include <unistd.h>
+
+#include <utility>
+
+#include "Action.h"
+#include "Pointers.h"
+#include "Thread.h"
+
+typedef std::pair<Thread*, volatile bool*> thread_data_t;
+
+TEST(ThreadTest, ready) {
+  Thread thread;
+
+  // A thread should be ready immediately. If not, this will hang forever.
+  thread.WaitForReady();
+}
+
+void* ThreadWaitForReady(void* data) {
+  thread_data_t* thread_data = reinterpret_cast<thread_data_t*>(data);
+  Thread* thread = thread_data->first;
+  volatile bool* finish = thread_data->second;
+
+  thread->WaitForReady();
+  *finish = true;
+
+  return nullptr;
+}
+
+TEST(ThreadTest, ready_thread) {
+  Thread thread;
+  volatile bool finish = false;
+  thread_data_t thread_data = std::make_pair(&thread, &finish);
+
+  thread.SetPending();
+
+  pthread_t thread_id;
+  ASSERT_TRUE(pthread_create(&thread_id, nullptr, ThreadWaitForReady, &thread_data) == 0);
+
+  ASSERT_FALSE(finish);
+  sleep(1);
+  ASSERT_FALSE(finish);
+
+  thread.ClearPending();
+  ASSERT_TRUE(pthread_join(thread_id, nullptr) == 0);
+  ASSERT_TRUE(finish);
+}
+
+void* ThreadWaitForPending(void* data) {
+  thread_data_t* thread_data = reinterpret_cast<thread_data_t*>(data);
+  Thread* thread = thread_data->first;
+  volatile bool* finish = thread_data->second;
+
+  thread->WaitForPending();
+  *finish = true;
+
+  return nullptr;
+}
+
+TEST(ThreadTest, pending) {
+  Thread thread;
+  volatile bool finish = false;
+  thread_data_t thread_data = std::make_pair(&thread, &finish);
+
+  pthread_t thread_id;
+  ASSERT_TRUE(pthread_create(&thread_id, nullptr, ThreadWaitForPending, &thread_data) == 0);
+
+  ASSERT_FALSE(finish);
+  sleep(1);
+  ASSERT_FALSE(finish);
+
+  thread.SetPending();
+  ASSERT_TRUE(pthread_join(thread_id, nullptr) == 0);
+  ASSERT_TRUE(finish);
+}
+
+TEST(ThreadTest, pointers) {
+  Pointers pointers(2);
+  Thread thread;
+
+  ASSERT_TRUE(thread.pointers() == nullptr);
+  thread.set_pointers(&pointers);
+  ASSERT_TRUE(thread.pointers() == &pointers);
+}
+
+TEST(ThreadTest, action) {
+  Thread thread;
+
+  Action* action = thread.CreateAction(0x1234, "thread_done", "");
+  ASSERT_EQ(action, thread.GetAction());
+
+  // Verify the action object is not garbage.
+  action->Execute(nullptr);
+
+  ASSERT_TRUE(action->EndThread());
+  ASSERT_FALSE(action->DoesFree());
+}
diff --git a/memory_replay/tests/ThreadsTest.cpp b/memory_replay/tests/ThreadsTest.cpp
new file mode 100644 (file)
index 0000000..c2ba023
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * 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 "Action.h"
+#include "Pointers.h"
+#include "Thread.h"
+#include "Threads.h"
+
+TEST(ThreadsTest, single_thread) {
+  Pointers pointers(2);
+
+  Threads threads(&pointers, 1);
+  Thread* thread = threads.CreateThread(900);
+  ASSERT_TRUE(thread != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  Thread* found_thread = threads.FindThread(900);
+  ASSERT_EQ(thread, found_thread);
+
+  thread->CreateAction(0x1234, "thread_done", "");
+
+  thread->SetPending();
+
+  threads.Finish(thread);
+
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+TEST(ThreadsTest, multiple_threads) {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  Thread* thread1 = threads.CreateThread(900);
+  ASSERT_TRUE(thread1 != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  Thread* thread2 = threads.CreateThread(901);
+  ASSERT_TRUE(thread2 != nullptr);
+  ASSERT_EQ(2U, threads.num_threads());
+
+  Thread* thread3 = threads.CreateThread(902);
+  ASSERT_TRUE(thread3 != nullptr);
+  ASSERT_EQ(3U, threads.num_threads());
+
+  Thread* found_thread1 = threads.FindThread(900);
+  ASSERT_EQ(thread1, found_thread1);
+
+  Thread* found_thread2 = threads.FindThread(901);
+  ASSERT_EQ(thread2, found_thread2);
+
+  Thread* found_thread3 = threads.FindThread(902);
+  ASSERT_EQ(thread3, found_thread3);
+
+  thread1->CreateAction(0x1234, "thread_done", "");
+  thread2->CreateAction(0x1235, "thread_done", "");
+  thread3->CreateAction(0x1236, "thread_done", "");
+
+  thread1->SetPending();
+  threads.Finish(thread1);
+  ASSERT_EQ(2U, threads.num_threads());
+
+  thread3->SetPending();
+  threads.Finish(thread3);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  thread2->SetPending();
+  threads.Finish(thread2);
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+TEST(ThreadsTest, verify_quiesce) {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  Thread* thread = threads.CreateThread(900);
+  ASSERT_TRUE(thread != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  // If WaitForAllToQuiesce is not correct, then this should provoke an error
+  // since we are overwriting the action data while it's being used.
+  for (size_t i = 0; i < 512; i++) {
+    thread->CreateAction(0x1234 + i, "malloc", "100");
+    thread->SetPending();
+    threads.WaitForAllToQuiesce();
+
+    thread->CreateAction(0x1234 + i, "free", "");
+    thread->SetPending();
+    threads.WaitForAllToQuiesce();
+  }
+
+  thread->CreateAction(0x1236, "thread_done", "");
+  thread->SetPending();
+  threads.Finish(thread);
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+static void TestTooManyThreads() {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  for (size_t i = 0; i <= threads.max_threads(); i++) {
+    Thread* thread = threads.CreateThread(900+i);
+    ASSERT_EQ(thread, threads.FindThread(900+i));
+  }
+}
+
+TEST(ThreadsTest, too_many_threads) {
+  ASSERT_EXIT(TestTooManyThreads(), ::testing::ExitedWithCode(1), "");
+}
diff --git a/memtrack/NOTICE b/memtrack/NOTICE
new file mode 100644 (file)
index 0000000..ad6ed94
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2013, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index a451d5c..2c4d7c0 100644 (file)
@@ -248,7 +248,7 @@ void ProcessInfo::dumpToLog() {
   for (std::vector<const process_info_t *>::const_iterator it = list_.begin();
        it != list_.end(); ++it) {
     ALOGI("  Name: %s", (*it)->name.c_str());
-    ALOGI("    Max running processes: %d", (*it)->max_num_pids);
+    ALOGI("    Max running processes: %zu", (*it)->max_num_pids);
     if ((*it)->pids.size() > 0) {
       ALOGI("    Currently running pids:");
       for (std::vector<int>::const_iterator pid_it = (*it)->pids.begin();
diff --git a/micro_bench/NOTICE b/micro_bench/NOTICE
new file mode 100644 (file)
index 0000000..5d14293
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index b758779..c20f7d8 100644 (file)
@@ -91,11 +91,19 @@ uint64_t nanoTime() {
   return static_cast<uint64_t>(t.tv_sec) * NS_PER_SEC + t.tv_nsec;
 }
 
+// Static analyzer warns about potential memory leak of orig_ptr
+// in getAlignedMemory. That is true and the callers in this program
+// do not free orig_ptr. But, we don't care about that in this
+// going-obsolete test program. So, here is a hack to trick the
+// static analyzer.
+static void *saved_orig_ptr;
+
 // Allocate memory with a specific alignment and return that pointer.
 // This function assumes an alignment value that is a power of 2.
 // If the alignment is 0, then use the pointer returned by malloc.
 uint8_t *getAlignedMemory(uint8_t *orig_ptr, int alignment, int or_mask) {
   uint64_t ptr = reinterpret_cast<uint64_t>(orig_ptr);
+  saved_orig_ptr = orig_ptr;
   if (alignment > 0) {
       // When setting the alignment, set it to exactly the alignment chosen.
       // The pointer returned will be guaranteed not to be aligned to anything
@@ -382,20 +390,6 @@ int benchmarkSleep(const char* /*name*/, const command_data_t &cmd_data, void_fu
     return 0;
 }
 
-int benchmarkCpu(const char* /*name*/, const command_data_t &cmd_data, void_func_t /*func*/) {
-    // Use volatile so that the loop is not optimized away by the compiler.
-    volatile int cpu_foo;
-
-    MAINLOOP(cmd_data,
-             for (cpu_foo = 0; cpu_foo < 100000000; cpu_foo++),
-             (double)time_ns/NS_PER_SEC,
-             printf("cpu took %.06f seconds\n", avg),
-             printf("  cpu average %.06f seconds std dev %f min %0.6f seconds max %0.6f seconds\n", \
-                    running_avg, computeStdDev(square_avg, running_avg), min, max));
-
-    return 0;
-}
-
 int benchmarkMemset(const char *name, const command_data_t &cmd_data, void_func_t func) {
     memset_func_t memset_func = reinterpret_cast<memset_func_t>(func);
     BENCH_ONE_BUF(name, cmd_data, ;, memset_func(buf, i, size));
@@ -461,6 +455,7 @@ int benchmarkMemread(const char *name, const command_data_t &cmd_data, void_func
     size_t k;
     MAINLOOP_DATA(name, cmd_data, size,
                   for (k = 0; k < size/sizeof(uint32_t); k++) foo = src[k]);
+    free(src);
 
     return 0;
 }
@@ -591,7 +586,6 @@ int benchmarkStrcpyCold(const char *name, const command_data_t &cmd_data, void_f
 
 // Create the mapping structure.
 function_t function_table[] = {
-    { "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) },
diff --git a/mmap-perf/Android.mk b/mmap-perf/Android.mk
new file mode 100644 (file)
index 0000000..bf6e00e
--- /dev/null
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES :=  mmapPerf.cpp
+LOCAL_MODULE := mmapPerf
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+LOCAL_CLANG := true
+LOCAL_CFLAGS += -g -Wall -Werror -std=c++11 -Wno-missing-field-initializers -Wno-sign-compare -O3
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_CXX_STL := libc++_static
+LOCAL_STATIC_LIBRARIES := libc
+include $(BUILD_NATIVE_BENCHMARK)
diff --git a/mmap-perf/NOTICE b/mmap-perf/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/mmap-perf/mmapPerf.cpp b/mmap-perf/mmapPerf.cpp
new file mode 100644 (file)
index 0000000..c3bacf5
--- /dev/null
@@ -0,0 +1,155 @@
+#include "benchmark/benchmark_api.h"
+#include <string>
+#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+#include <iostream>
+#include <vector>
+#include <tuple>
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+using namespace std;
+static const size_t pageSize = PAGE_SIZE;
+static size_t fsize = 1024 * (1ull << 20);
+static size_t pagesTotal = fsize / pageSize;
+
+class Fd {
+    int m_fd = -1;
+public:
+    int get() { return m_fd; }
+    void set(int fd) { m_fd = fd; }
+    Fd() {}
+    Fd(int fd) : m_fd{fd} {}
+    ~Fd() {
+        if (m_fd >= 0)
+            close(m_fd);
+    }
+};
+
+int dummy = 0;
+
+void fillPageJunk(void *ptr)
+{
+    uint64_t seed = (unsigned long long)rand() | ((unsigned long long)rand() << 32);
+    uint64_t *target = (uint64_t*)ptr;
+    for (int i = 0; i < pageSize / sizeof(uint64_t); i++) {
+        *target = seed ^ (uint64_t)(uintptr_t)target;
+        seed = (seed << 1) | ((seed >> 63) & 1);
+        target++;
+    }
+}
+
+class FileMap {
+    string m_name;
+    size_t m_size;
+    void *m_ptr = nullptr;
+    Fd m_fileFd;
+public:
+    enum Hint {
+       FILE_MAP_HINT_NONE,
+       FILE_MAP_HINT_RAND,
+       FILE_MAP_HINT_LINEAR,
+    };
+    FileMap(const string &name, size_t size, Hint hint = FILE_MAP_HINT_NONE) : m_name{name}, m_size{size} {
+        int fd = open(name.c_str(), O_CREAT | O_RDWR, S_IRWXU);
+        if (fd < 0) {
+            cout << "Error: open failed for " << name << ": " << strerror(errno) << endl;
+            exit(1);
+        }
+        m_fileFd.set(fd);
+        fallocate(m_fileFd.get(), 0, 0, size);
+        unlink(name.c_str());
+        m_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileFd.get(), 0);
+        if ((int)(uintptr_t)m_ptr == -1) {
+            cout << "Error: mmap failed: " << (int)(uintptr_t)m_ptr << ": " << strerror(errno) << endl;
+            exit(1);
+        }
+        switch (hint) {
+        case FILE_MAP_HINT_NONE: break;
+        case FILE_MAP_HINT_RAND:
+            madvise(m_ptr, m_size, MADV_RANDOM);
+            break;
+        case FILE_MAP_HINT_LINEAR:
+            madvise(m_ptr, m_size, MADV_SEQUENTIAL);
+            break;
+        }
+        for (int i = 0; i < m_size / pageSize; i++) {
+            uint8_t *targetPtr = (uint8_t*)m_ptr + 4096ull * i;
+            fillPageJunk(targetPtr);
+        }
+    }
+    void benchRandomRead(unsigned int targetPage) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
+        dummy += *targetPtr;
+    }
+    void benchRandomWrite(unsigned int targetPage) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
+        *targetPtr = dummy;
+    }
+    void benchLinearRead(unsigned int j) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
+        dummy += *targetPtr;
+    }
+    void benchLinearWrite(unsigned int j) {
+        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
+        *targetPtr = dummy;
+    }
+    void dropCache() {
+        int ret1 = msync(m_ptr, m_size, MS_SYNC | MS_INVALIDATE);
+        madvise(m_ptr, m_size, MADV_DONTNEED);
+        (void)ret1;
+    }
+    ~FileMap() {
+        if (m_ptr)
+            munmap(m_ptr, m_size);
+    }
+
+};
+
+static void benchRandomRead(benchmark::State& state) {
+    FileMap file{"/data/local/tmp/mmap_test", fsize};
+    while (state.KeepRunning()) {
+        unsigned int targetPage = rand() % pagesTotal;
+        file.benchRandomRead(targetPage);
+    }
+    state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchRandomRead);
+
+static void benchRandomWrite(benchmark::State& state) {
+    FileMap file{"/data/local/tmp/mmap_test", fsize};
+    while (state.KeepRunning()) {
+        unsigned int targetPage = rand() % pagesTotal;
+        file.benchRandomWrite(targetPage);
+    }
+    state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchRandomWrite);
+
+static void benchLinearRead(benchmark::State& state) {
+   FileMap file{"/data/local/tmp/mmap_test", fsize};
+   unsigned int j = 0;
+   while (state.KeepRunning()) {
+       file.benchLinearRead(j);
+       j = (j + 1) % pagesTotal;
+   }
+   state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchLinearRead);
+
+static void benchLinearWrite(benchmark::State& state) {
+   FileMap file{"/data/local/tmp/mmap_test", fsize};
+   unsigned int j = 0;
+   while (state.KeepRunning()) {
+       file.benchLinearWrite(j);
+       j = (j + 1) % pagesTotal;
+   }
+   state.SetBytesProcessed(state.iterations() * pageSize);
+}
+BENCHMARK(benchLinearWrite);
+
+BENCHMARK_MAIN()
diff --git a/mmap-perf/unsupported.cpp b/mmap-perf/unsupported.cpp
new file mode 100644 (file)
index 0000000..77d633e
--- /dev/null
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main() {
+    fprintf(stderr, "mmap-perf is unsupported for 32-bit architectures\n");
+    return -1;
+}
diff --git a/multinetwork/Android.mk b/multinetwork/Android.mk
new file mode 100644 (file)
index 0000000..28e56d4
--- /dev/null
@@ -0,0 +1,29 @@
+LOCAL_PATH := $(call my-dir)
+
+# The PDK build does not have access to frameworks/native elements.
+ifneq ($(TARGET_BUILD_PDK), true)
+
+# Sample util binaries.
+include $(CLEAR_VARS)
+LOCAL_MODULE := dnschk
+
+LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
+LOCAL_SRC_FILES := dnschk.cpp common.cpp
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := httpurl
+
+LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
+LOCAL_SRC_FILES := httpurl.cpp common.cpp
+include $(BUILD_EXECUTABLE)
+
+endif  # ifneq ($(TARGET_BUILD_PDK), true)
diff --git a/multinetwork/common.cpp b/multinetwork/common.cpp
new file mode 100644 (file)
index 0000000..7a5e7be
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "common.h"
+
+#include <android/api-level.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iostream>
+
+
+namespace {
+
+bool strEqual(const char *a, const char *b) {
+    return strcmp(a, b) == 0;
+}
+
+// Allow specifying network handles in decimal and hexadecimal.
+bool parseNetworkHandle(const char *arg, net_handle_t *nethandle) {
+    if (arg == nullptr || !isdigit(arg[0]) || nethandle == nullptr) {
+        return false;
+    }
+
+    net_handle_t nh;
+    char *end = nullptr;
+
+    nh = strtoull(arg, &end, 0);
+    if (end != nullptr && *end == '\0') {
+        *nethandle = nh;
+        return true;
+    }
+    return false;
+}
+
+}  // namespace
+
+
+void printUsage(const char *progname) {
+    std::cerr << "Usage: " << progname
+              << " [--nethandle <nethandle>]"
+              << " [--mode explicit|process]"
+              << " [--family unspec|ipv4|ipv6]"
+              << " <argument>"
+              << std::endl;
+    std::cerr << std::endl;
+    std::cerr << "Learn nethandle values from 'dumpsys connectivity --short' "
+              << "or 'dumpsys connectivity --diag'"
+              << std::endl;
+}
+
+Arguments::~Arguments() {}
+
+bool Arguments::parseArguments(int argc, const char* argv[]) {
+    if (argc < 1 || argv == nullptr) { return false; }
+
+    for (int i = 1; i < argc; i++) {
+        if (strEqual(argv[i], "--nethandle")) {
+            i++;
+            if (argc == i) break;
+            if (!parseNetworkHandle(argv[i], &nethandle)) {
+                std::cerr << "Failed to parse nethandle: '" << argv[i] << "'"
+                          << std::endl;
+                break;
+            }
+        } else if (strEqual(argv[i], "--family")) {
+            i++;
+            if (argc == i) break;
+            if (strEqual(argv[i], "unspec")) {
+                family = AF_UNSPEC;
+            } else if (strEqual(argv[i], "ipv4")) {
+                family = AF_INET;
+            } else if (strEqual(argv[i], "ipv6")) {
+                family = AF_INET6;
+            } else {
+                break;
+            }
+        } else if (strEqual(argv[i], "--mode")) {
+            i++;
+            if (argc == i) break;
+            if (strEqual(argv[i], "explicit")) {
+                api_mode = ApiMode::EXPLICIT;
+            } else if (strEqual(argv[i], "process")) {
+                api_mode = ApiMode::PROCESS;
+            } else {
+                break;
+            }
+        } else if (arg1 == nullptr) {
+            arg1 = argv[i];
+        } else {
+            arg1 = nullptr;
+            break;
+        }
+    }
+
+    if (arg1 != nullptr) {
+        return true;
+    }
+
+    printUsage(argv[0]);
+    return false;
+}
+
+
+std::string inetSockaddrToString(const sockaddr* sa) {
+    const bool is_ipv6 = (sa->sa_family == AF_INET6);
+    char host[INET6_ADDRSTRLEN];
+    char port[sizeof("65535")];
+    getnameinfo(sa, is_ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in),
+                host, sizeof(host),
+                port, sizeof(port),
+                NI_NUMERICHOST | NI_NUMERICSERV);
+
+    if (port[0] == '0' || port[0] == '\0') {
+        return std::string(host);
+    }
+    return (is_ipv6 ? "[" : "") + std::string(host) + (is_ipv6 ? "]:" : ":") + std::string(port);
+}
diff --git a/multinetwork/common.h b/multinetwork/common.h
new file mode 100644 (file)
index 0000000..f431ea9
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
+#define SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
+
+#include <sys/cdefs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string>
+#include <android/multinetwork.h>
+
+enum class ApiMode {
+    EXPLICIT,
+    PROCESS,
+};
+
+
+struct Arguments {
+    Arguments() : nethandle(NETWORK_UNSPECIFIED),
+                  api_mode(ApiMode::EXPLICIT),
+                  family(AF_UNSPEC),
+                  arg1(nullptr) {}
+    ~Arguments();
+
+    bool parseArguments(int argc, const char* argv[]);
+
+    net_handle_t nethandle;
+    ApiMode api_mode;
+    sa_family_t family;
+    const char* arg1;
+};
+
+
+void printUsage(const char *progname);
+
+// If port is non-zero returns strings of the form "192.0.2.1:port" or
+// "[2001:db8::1]:port", else it returns the bare IP string literal.
+std::string inetSockaddrToString(const sockaddr* sa);
+
+
+struct FdAutoCloser {
+    FdAutoCloser() : fd(-1) {}
+    /* not explicit */ FdAutoCloser(int fd) : fd(fd) {}
+    ~FdAutoCloser() {
+        if (fd > -1) {
+            close(fd);
+        }
+        fd = -1;
+    }
+
+    int fd;
+};
+
+#endif  // SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_
diff --git a/multinetwork/dnschk.cpp b/multinetwork/dnschk.cpp
new file mode 100644 (file)
index 0000000..a2c42d4
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <iostream>
+#include <string>
+
+#include <android/multinetwork.h>
+#include "common.h"
+
+
+int main(int argc, const char* argv[]) {
+    int rval = -1;
+
+    struct Arguments args;
+    if (!args.parseArguments(argc, argv)) { return rval; }
+
+    const struct addrinfo hints = {
+            .ai_family = args.family,
+            .ai_socktype = SOCK_DGRAM,
+    };
+    struct addrinfo *result = nullptr;
+
+    std::cout << "# " << args.arg1
+              << " (via nethandle " << args.nethandle << "):"
+              << std::endl;
+
+    switch (args.api_mode) {
+        case ApiMode::EXPLICIT:
+            rval = android_getaddrinfofornetwork(args.nethandle,
+                    args.arg1, nullptr, &hints, &result);
+            break;
+        case ApiMode::PROCESS:
+            if (args.nethandle != NETWORK_UNSPECIFIED) {
+                rval = android_setprocnetwork(args.nethandle);
+                if (rval != 0) {
+                    std::cerr << "android_setprocnetwork returned " << rval
+                              << std::endl;
+                    return rval;
+                }
+            }
+            rval = getaddrinfo(args.arg1, nullptr, &hints, &result);
+            break;
+        default:
+            // Unreachable.
+            std::cerr << "Unknown api mode." << std::endl;
+            return -1;
+    }
+
+    if (rval != 0) {
+        std::cerr << "DNS resolution failure; gaierror=" << rval
+                  << " [" << gai_strerror(rval) << "]"
+                  << std::endl;
+        return rval;
+    }
+
+    for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
+        std::cout << inetSockaddrToString(rp->ai_addr) << std::endl;
+    }
+
+    freeaddrinfo(result);
+    return 0;
+}
diff --git a/multinetwork/httpurl.cpp b/multinetwork/httpurl.cpp
new file mode 100644 (file)
index 0000000..e079c1d
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <iostream>
+#include <string>
+
+#include <android/multinetwork.h>
+#include <android-base/stringprintf.h>
+#include "common.h"
+
+
+
+struct Parameters {
+    Parameters() : ss({}), port("80"), path("/") {}
+
+    struct sockaddr_storage ss;
+    std::string host;
+    std::string hostname;
+    std::string port;
+    std::string path;
+};
+
+
+bool parseUrl(const struct Arguments& args, struct Parameters* parameters) {
+    if (parameters == nullptr) { return false; }
+
+    static const char HTTP_PREFIX[] = "http://";
+    if (strncmp(args.arg1, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0) {
+        std::cerr << "Only " << HTTP_PREFIX << " URLs supported." << std::endl;
+        return false;
+    }
+
+    parameters->host = std::string(args.arg1).substr(strlen(HTTP_PREFIX));
+    const auto first_slash = parameters->host.find_first_of("/");
+    if (first_slash != std::string::npos) {
+        parameters->path = parameters->host.substr(first_slash);
+        parameters->host.erase(first_slash);
+    }
+
+    if (parameters->host.size() == 0) {
+        std::cerr << "Host portion cannot be empty." << std::endl;
+        return false;
+    }
+
+    if (parameters->host[0] == '[') {
+        const auto closing_bracket = parameters->host.find_first_of("]");
+        if (closing_bracket == std::string::npos) {
+            std::cerr << "Missing closing bracket." << std::endl;
+            return false;
+        }
+        parameters->hostname = parameters->host.substr(1, closing_bracket - 1);
+
+        const auto colon_port = closing_bracket + 1;
+        if (colon_port < parameters->host.size()) {
+            if (parameters->host[colon_port] != ':') {
+                std::cerr << "Malformed port portion." << std::endl;
+                return false;
+            }
+            parameters->port = parameters->host.substr(closing_bracket + 2);
+        }
+    } else {
+        const auto first_colon = parameters->host.find_first_of(":");
+        if (first_colon != std::string::npos) {
+            parameters->port = parameters->host.substr(first_colon + 1);
+            parameters->hostname = parameters->host.substr(0, first_colon);
+        } else {
+            parameters->hostname = parameters->host;
+        }
+    }
+
+    // TODO: find the request portion to send (before '#...').
+
+    std::cerr << "Resolving hostname=" << parameters->hostname
+              << ", port=" << parameters->port
+              << std::endl;
+
+    struct addrinfo hints = {
+            .ai_family = args.family,
+            .ai_socktype = SOCK_STREAM,
+    };
+    struct addrinfo *result = nullptr;
+
+    int rval = -1;
+    switch (args.api_mode) {
+        case ApiMode::EXPLICIT:
+            rval = android_getaddrinfofornetwork(args.nethandle,
+                                                 parameters->hostname.c_str(),
+                                                 parameters->port.c_str(),
+                                                 &hints, &result);
+            break;
+        case ApiMode::PROCESS:
+            rval = getaddrinfo(parameters->hostname.c_str(),
+                               parameters->port.c_str(),
+                               &hints, &result);
+            break;
+        default:
+            // Unreachable.
+            std::cerr << "Unknown api mode." << std::endl;
+            return false;
+    }
+
+    if (rval != 0) {
+        std::cerr << "DNS resolution failure; gaierror=" << rval
+                  << " [" << gai_strerror(rval) << "]"
+                  << std::endl;
+        return rval;
+    }
+
+    memcpy(&(parameters->ss), result[0].ai_addr, result[0].ai_addrlen);
+    std::cerr << "Connecting to: "
+              << inetSockaddrToString(result[0].ai_addr)
+              << std::endl;
+
+    freeaddrinfo(result);
+    return true;
+}
+
+
+int makeTcpSocket(sa_family_t address_family, net_handle_t nethandle) {
+    int fd = socket(address_family, SOCK_STREAM, IPPROTO_TCP);
+    if (fd < 0) {
+        std::cerr << "failed to create TCP socket" << std::endl;
+        return -1;
+    }
+
+    // Don't let reads or writes block indefinitely. We cannot control
+    // connect() timeouts without nonblocking sockets and select/poll/epoll.
+    const struct timeval timeo = { 5, 0 };  // 5 seconds
+    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
+
+    if (nethandle != NETWORK_UNSPECIFIED) {
+        if (android_setsocknetwork(nethandle, fd) != 0) {
+            int errnum = errno;
+            std::cerr << "android_setsocknetwork() failed;"
+                      << " errno: " << errnum << " [" << strerror(errnum) << "]"
+                      << std::endl;
+            close(fd);
+            return -1;
+        }
+    }
+    return fd;
+}
+
+
+int doHttpQuery(int fd, const struct Parameters& parameters) {
+    int rval = -1;
+    if (connect(fd,
+                reinterpret_cast<const struct sockaddr *>(&(parameters.ss)),
+                (parameters.ss.ss_family == AF_INET6)
+                        ? sizeof(struct sockaddr_in6)
+                        : sizeof(struct sockaddr_in)) != 0) {
+        int errnum = errno;
+        std::cerr << "Failed to connect; errno=" << errnum
+                  << " [" << strerror(errnum) << "]"
+                  << std::endl;
+        return -1;
+    }
+
+    const std::string request(android::base::StringPrintf(
+            "GET %s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "Accept: */*\r\n"
+            "Connection: close\r\n"
+            "User-Agent: httpurl/0.0\r\n"
+            "\r\n",
+            parameters.path.c_str(), parameters.host.c_str()));
+    const ssize_t sent = write(fd, request.c_str(), request.size());
+    if (sent != static_cast<ssize_t>(request.size())) {
+        std::cerr << "Sent only " << sent << "/" << request.size() << " bytes"
+                  << std::endl;
+        return -1;
+    }
+
+    char buf[4*1024];
+    do {
+        rval = recv(fd, buf, sizeof(buf), 0);
+
+        if (rval < 0) {
+            const int saved_errno = errno;
+            std::cerr << "Failed to recv; errno=" << saved_errno
+                      << " [" << strerror(saved_errno) << "]"
+                      << std::endl;
+        } else if (rval > 0) {
+            std::cout.write(buf, rval);
+            std::cout.flush();
+        }
+    } while (rval > 0);
+    std::cout << std::endl;
+
+    return 0;
+}
+
+
+int main(int argc, const char* argv[]) {
+    int rval = -1;
+
+    struct Arguments args;
+    if (!args.parseArguments(argc, argv)) { return rval; }
+
+    if (args.api_mode == ApiMode::PROCESS) {
+        rval = android_setprocnetwork(args.nethandle);
+        if (rval != 0) {
+            int errnum = errno;
+            std::cerr << "android_setprocnetwork(" << args.nethandle << ") failed;"
+                      << " errno: " << errnum << " [" << strerror(errnum) << "]"
+                      << std::endl;
+            return rval;
+        }
+    }
+
+    struct Parameters parameters;
+    if (!parseUrl(args, &parameters)) { return -1; }
+
+    // TODO: Fall back from IPv6 to IPv4 if ss.ss_family is AF_UNSPEC.
+    // This will involve changes to parseUrl() as well.
+    struct FdAutoCloser closer = makeTcpSocket(
+            parameters.ss.ss_family,
+            (args.api_mode == ApiMode::EXPLICIT) ? args.nethandle
+                                                 : NETWORK_UNSPECIFIED);
+    if (closer.fd < 0) { return closer.fd; }
+
+    return doHttpQuery(closer.fd, parameters);
+}
diff --git a/multinetwork/quick_test.sh b/multinetwork/quick_test.sh
new file mode 100755 (executable)
index 0000000..f586bae
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+nethandle=0
+
+readonly TEST_HOST="connectivitycheck.gstatic.com"
+readonly TEST_PATH="/generate_204"
+readonly PREFIX=">>>"
+
+function getUrls() {
+    if [ ! -z $(echo "$1" | sed -e 's/[^:]//g') ]; then
+        echo "http://[$1]$TEST_PATH"
+        echo "http://[$1]:80$TEST_PATH"
+    else
+        echo "http://$1$TEST_PATH"
+        echo "http://$1:80$TEST_PATH"
+    fi
+}
+
+function toHex() {
+    readonly local hexValue=$(bc -q 2>/dev/null << EOT
+obase=16
+$1
+EOT
+)
+    if [ ! -z "$hexValue" ]; then
+        echo "0x$hexValue"
+    fi
+}
+
+
+if [ ! -z "$1" ]; then
+    nethandle="$1"
+fi
+echo "$PREFIX Using nethandle $nethandle ($(toHex $nethandle))"
+echo ""
+
+readonly IPADDRESSES=$(
+    adb shell /system/xbin/dnschk --nethandle $nethandle $TEST_HOST |
+    sed -e 's/#.*//' -e '/^$/d')
+
+
+for host in $TEST_HOST $IPADDRESSES; do
+    urls=$(getUrls $host)
+    for url in $urls; do
+        echo "$PREFIX Checking $url" >&2
+        adb shell /system/xbin/httpurl --nethandle $nethandle "$url"
+    done
+done
diff --git a/pagecache/Android.mk b/pagecache/Android.mk
new file mode 100644 (file)
index 0000000..fe06410
--- /dev/null
@@ -0,0 +1,13 @@
+# Copyright 2015 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= dumpcache.c
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE:= dumpcache
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/pagecache/MODULE_LICENSE_APACHE2 b/pagecache/MODULE_LICENSE_APACHE2
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pagecache/NOTICE b/pagecache/NOTICE
new file mode 100644 (file)
index 0000000..34bdaf1
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2015, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/pagecache/README b/pagecache/README
new file mode 100644 (file)
index 0000000..08f4b53
--- /dev/null
@@ -0,0 +1,4 @@
+Pagecache tools.
+
+dumpcache.c: dumps complete pagecache of device.
+pagecache.py: shows live info on files going in/out of pagecache.
diff --git a/pagecache/dumpcache.c b/pagecache/dumpcache.c
new file mode 100644 (file)
index 0000000..eb11bba
--- /dev/null
@@ -0,0 +1,153 @@
+#include <ftw.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <ctype.h>
+#include <stddef.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+// Initial size of the array holding struct file_info
+#define INITIAL_NUM_FILES 512
+
+// Max number of file descriptors to use for ntfw
+#define MAX_NUM_FD 1
+
+struct file_info {
+    char *name;
+    size_t file_size;
+    size_t num_cached_pages;
+};
+
+// Size of pages on this system
+static int g_page_size;
+
+// Total number of cached pages found so far
+static size_t g_total_cached = 0;
+
+// Total number of files scanned so far
+static size_t g_num_files = 0;
+
+// Scanned files and their associated cached page counts
+static struct file_info **g_files;
+
+// Current size of files array
+size_t g_files_size;
+
+static struct file_info *get_file_info(const char* fpath, size_t file_size) {
+    struct file_info *info;
+    if (g_num_files >= g_files_size) {
+        g_files = realloc(g_files, 2 * g_files_size * sizeof(struct file_info*));
+        if (!g_files) {
+            fprintf(stderr, "Couldn't allocate space for files array: %s\n", strerror(errno));
+            exit(EXIT_FAILURE);
+        }
+        g_files_size = 2 * g_files_size;
+    }
+
+    info = calloc(1, sizeof(*info));
+    if (!info) {
+        fprintf(stderr, "Couldn't allocate space for file struct: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    info->name = malloc(strlen(fpath) + 1);
+    if (!info->name) {
+        fprintf(stderr, "Couldn't allocate space for file struct: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+    strcpy(info->name, fpath);
+
+    info->num_cached_pages = 0;
+    info->file_size = file_size;
+
+    g_files[g_num_files++] = info;
+
+    return info;
+}
+
+static int store_num_cached(const char* fpath, const struct stat *sb) {
+    int fd;
+    fd = open (fpath, O_RDONLY);
+
+    if (fd == -1) {
+        printf("Could not open file.");
+        return -1;
+    }
+
+    void* mapped_addr = mmap(NULL, sb->st_size, PROT_NONE, MAP_SHARED, fd, 0);
+
+    if (mapped_addr != MAP_FAILED) {
+        // Calculate bit-vector size
+        size_t num_file_pages = (sb->st_size + g_page_size - 1) / g_page_size;
+        unsigned char* mincore_data = calloc(1, num_file_pages);
+        int ret = mincore(mapped_addr, sb->st_size, mincore_data);
+        int num_cached = 0;
+        unsigned int page = 0;
+        for (page = 0; page < num_file_pages; page++) {
+           if (mincore_data[page]) num_cached++;
+        }
+        if (num_cached > 0) {
+            struct file_info *info = get_file_info(fpath, sb->st_size);
+            info->num_cached_pages += num_cached;
+            g_total_cached += num_cached;
+        }
+        munmap(mapped_addr, sb->st_size);
+    }
+
+    close(fd);
+    return 0;
+}
+
+static int scan_entry(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
+    if (typeflag == FTW_F) {
+        store_num_cached(fpath, sb);
+    }
+    return 0;
+}
+
+static int cmpsize(size_t a, size_t b) {
+    if (a < b) return -1;
+    if (a > b) return 1;
+    return 0;
+}
+
+static int cmpfiles(const void *a, const void *b) {
+    return cmpsize((*((struct file_info**)a))->num_cached_pages,
+            (*((struct file_info**)b))->num_cached_pages);
+}
+
+int main()
+{
+    size_t i;
+    g_page_size = getpagesize();
+
+    g_files = malloc(INITIAL_NUM_FILES * sizeof(struct file_info*));
+    g_files_size = INITIAL_NUM_FILES;
+
+    // Walk filesystem trees
+    nftw("/system/", &scan_entry, MAX_NUM_FD, 0);
+    nftw("/vendor/", &scan_entry, MAX_NUM_FD, 0);
+    nftw("/data/", &scan_entry, MAX_NUM_FD, 0);
+
+    // Sort entries
+    qsort(g_files, g_num_files, sizeof(g_files[0]), &cmpfiles);
+
+    // Dump entries
+    for (i = 0; i < g_num_files; i++) {
+        struct file_info *info = g_files[i];
+        fprintf(stdout, "%s: %zu cached pages (%.2f MB, %zu%% of total file size.)\n", info->name,
+                info->num_cached_pages,
+                (float) (info->num_cached_pages * g_page_size) / 1024 / 1024,
+                (100 * info->num_cached_pages * g_page_size) / info->file_size);
+    }
+
+    fprintf(stdout, "TOTAL CACHED: %zu pages (%f MB)\n", g_total_cached,
+            (float) (g_total_cached * 4096) / 1024 / 1024);
+    return 0;
+}
diff --git a/pagecache/pagecache.py b/pagecache/pagecache.py
new file mode 100755 (executable)
index 0000000..c822ff0
--- /dev/null
@@ -0,0 +1,404 @@
+#!/usr/bin/env python
+
+import curses
+import operator
+import optparse
+import os
+import re
+import subprocess
+import sys
+import threading
+import Queue
+
+STATS_UPDATE_INTERVAL = 0.2
+PAGE_SIZE = 4096
+
+class PagecacheStats():
+  """Holds pagecache stats by accounting for pages added and removed.
+
+  """
+  def __init__(self, inode_to_filename):
+    self._inode_to_filename = inode_to_filename
+    self._file_size = {}
+    self._file_pages = {}
+    self._total_pages_added = 0
+    self._total_pages_removed = 0
+
+  def add_page(self, device_number, inode, offset):
+    # See if we can find the page in our lookup table
+    if (device_number, inode) in self._inode_to_filename:
+      filename, filesize = self._inode_to_filename[(device_number, inode)]
+      if filename not in self._file_pages:
+        self._file_pages[filename] = [1, 0]
+      else:
+        self._file_pages[filename][0] += 1
+
+      self._total_pages_added += 1
+
+      if filename not in self._file_size:
+        self._file_size[filename] = filesize
+
+  def remove_page(self, device_number, inode, offset):
+    if (device_number, inode) in self._inode_to_filename:
+      filename, filesize = self._inode_to_filename[(device_number, inode)]
+      if filename not in self._file_pages:
+        self._file_pages[filename] = [0, 1]
+      else:
+        self._file_pages[filename][1] += 1
+
+      self._total_pages_removed += 1
+
+      if filename not in self._file_size:
+        self._file_size[filename] = filesize
+
+  def pages_to_mb(self, num_pages):
+    return "%.2f" % round(num_pages * PAGE_SIZE / 1024.0 / 1024.0, 2)
+
+  def bytes_to_mb(self, num_bytes):
+    return "%.2f" % round(int(num_bytes) / 1024.0 / 1024.0, 2)
+
+  def print_pages_and_mb(self, num_pages):
+    pages_string = str(num_pages) + ' (' + str(self.pages_to_mb(num_pages)) + ' MB)'
+    return pages_string
+
+  def reset_stats(self):
+    self._file_pages.clear()
+    self._total_pages_added = 0;
+    self._total_pages_removed = 0;
+
+  def print_stats(self):
+    # Create new merged dict
+    sorted_added = sorted(self._file_pages.items(), key=operator.itemgetter(1), reverse=True)
+    row_format = "{:<70}{:<12}{:<14}{:<9}"
+    print row_format.format('NAME', 'ADDED (MB)', 'REMOVED (MB)', 'SIZE (MB)')
+    for filename, added in sorted_added:
+      filesize = self._file_size[filename]
+      added = self._file_pages[filename][0]
+      removed = self._file_pages[filename][1]
+      if (filename > 64):
+        filename = filename[-64:]
+      print row_format.format(filename, self.pages_to_mb(added), self.pages_to_mb(removed), self.bytes_to_mb(filesize))
+
+    print row_format.format('TOTAL', self.pages_to_mb(self._total_pages_added), self.pages_to_mb(self._total_pages_removed), '')
+
+  def print_stats_curses(self, pad):
+    sorted_added = sorted(self._file_pages.items(), key=operator.itemgetter(1), reverse=True)
+    height, width = pad.getmaxyx()
+    pad.clear()
+    pad.addstr(0, 2, 'NAME'.ljust(68), curses.A_REVERSE)
+    pad.addstr(0, 70, 'ADDED (MB)'.ljust(12), curses.A_REVERSE)
+    pad.addstr(0, 82, 'REMOVED (MB)'.ljust(14), curses.A_REVERSE)
+    pad.addstr(0, 96, 'SIZE (MB)'.ljust(9), curses.A_REVERSE)
+    y = 1
+    for filename, added_removed in sorted_added:
+      filesize = self._file_size[filename]
+      added  = self._file_pages[filename][0]
+      removed = self._file_pages[filename][1]
+      if (filename > 64):
+        filename = filename[-64:]
+      pad.addstr(y, 2, filename)
+      pad.addstr(y, 70, self.pages_to_mb(added).rjust(10))
+      pad.addstr(y, 80, self.pages_to_mb(removed).rjust(14))
+      pad.addstr(y, 96, self.bytes_to_mb(filesize).rjust(9))
+      y += 1
+      if y == height - 2:
+        pad.addstr(y, 4, "<more...>")
+        break
+    y += 1
+    pad.addstr(y, 2, 'TOTAL'.ljust(74), curses.A_REVERSE)
+    pad.addstr(y, 70, str(self.pages_to_mb(self._total_pages_added)).rjust(10), curses.A_REVERSE)
+    pad.addstr(y, 80, str(self.pages_to_mb(self._total_pages_removed)).rjust(14), curses.A_REVERSE)
+    pad.refresh(0,0, 0,0, height,width)
+
+class FileReaderThread(threading.Thread):
+  """Reads data from a file/pipe on a worker thread.
+
+  Use the standard threading. Thread object API to start and interact with the
+  thread (start(), join(), etc.).
+  """
+
+  def __init__(self, file_object, output_queue, text_file, chunk_size=-1):
+    """Initializes a FileReaderThread.
+
+    Args:
+      file_object: The file or pipe to read from.
+      output_queue: A Queue.Queue object that will receive the data
+      text_file: If True, the file will be read one line at a time, and
+          chunk_size will be ignored.  If False, line breaks are ignored and
+          chunk_size must be set to a positive integer.
+      chunk_size: When processing a non-text file (text_file = False),
+          chunk_size is the amount of data to copy into the queue with each
+          read operation.  For text files, this parameter is ignored.
+    """
+    threading.Thread.__init__(self)
+    self._file_object = file_object
+    self._output_queue = output_queue
+    self._text_file = text_file
+    self._chunk_size = chunk_size
+    assert text_file or chunk_size > 0
+
+  def run(self):
+    """Overrides Thread's run() function.
+
+    Returns when an EOF is encountered.
+    """
+    if self._text_file:
+      # Read a text file one line at a time.
+      for line in self._file_object:
+        self._output_queue.put(line)
+    else:
+      # Read binary or text data until we get to EOF.
+      while True:
+        chunk = self._file_object.read(self._chunk_size)
+        if not chunk:
+          break
+        self._output_queue.put(chunk)
+
+  def set_chunk_size(self, chunk_size):
+    """Change the read chunk size.
+
+    This function can only be called if the FileReaderThread object was
+    created with an initial chunk_size > 0.
+    Args:
+      chunk_size: the new chunk size for this file.  Must be > 0.
+    """
+    # The chunk size can be changed asynchronously while a file is being read
+    # in a worker thread.  However, type of file can not be changed after the
+    # the FileReaderThread has been created.  These asserts verify that we are
+    # only changing the chunk size, and not the type of file.
+    assert not self._text_file
+    assert chunk_size > 0
+    self._chunk_size = chunk_size
+
+class AdbUtils():
+  @staticmethod
+  def add_adb_serial(adb_command, device_serial):
+    if device_serial is not None:
+      adb_command.insert(1, device_serial)
+      adb_command.insert(1, '-s')
+
+  @staticmethod
+  def construct_adb_shell_command(shell_args, device_serial):
+    adb_command = ['adb', 'shell', ' '.join(shell_args)]
+    AdbUtils.add_adb_serial(adb_command, device_serial)
+    return adb_command
+
+  @staticmethod
+  def run_adb_shell(shell_args, device_serial):
+    """Runs "adb shell" with the given arguments.
+
+    Args:
+      shell_args: array of arguments to pass to adb shell.
+      device_serial: if not empty, will add the appropriate command-line
+          parameters so that adb targets the given device.
+    Returns:
+      A tuple containing the adb output (stdout & stderr) and the return code
+      from adb.  Will exit if adb fails to start.
+    """
+    adb_command = AdbUtils.construct_adb_shell_command(shell_args, device_serial)
+
+    adb_output = []
+    adb_return_code = 0
+    try:
+      adb_output = subprocess.check_output(adb_command, stderr=subprocess.STDOUT,
+                                           shell=False, universal_newlines=True)
+    except OSError as error:
+      # This usually means that the adb executable was not found in the path.
+      print >> sys.stderr, ('\nThe command "%s" failed with the following error:'
+                            % ' '.join(adb_command))
+      print >> sys.stderr, '    %s' % str(error)
+      print >> sys.stderr, 'Is adb in your path?'
+      adb_return_code = error.errno
+      adb_output = error
+    except subprocess.CalledProcessError as error:
+      # The process exited with an error.
+      adb_return_code = error.returncode
+      adb_output = error.output
+
+    return (adb_output, adb_return_code)
+
+  @staticmethod
+  def do_preprocess_adb_cmd(command, serial):
+    args = [command]
+    dump, ret_code = AdbUtils.run_adb_shell(args, serial)
+    if ret_code != 0:
+      return None
+
+    dump = ''.join(dump)
+    return dump
+
+def parse_atrace_line(line, pagecache_stats, app_name):
+  # Find a mm_filemap_add_to_page_cache entry
+  m = re.match('.* (mm_filemap_add_to_page_cache|mm_filemap_delete_from_page_cache): dev (\d+):(\d+) ino ([0-9a-z]+) page=([0-9a-z]+) pfn=\d+ ofs=(\d+).*', line)
+  if m != None:
+    # Get filename
+    device_number = int(m.group(2)) << 8 | int(m.group(3))
+    if device_number == 0:
+      return
+    inode = int(m.group(4), 16)
+    if app_name != None and not (app_name in m.group(0)):
+      return
+    if m.group(1) == 'mm_filemap_add_to_page_cache':
+      pagecache_stats.add_page(device_number, inode, m.group(4))
+    elif m.group(1) == 'mm_filemap_delete_from_page_cache':
+      pagecache_stats.remove_page(device_number, inode, m.group(4))
+
+def build_inode_lookup_table(inode_dump):
+  inode2filename = {}
+  text = inode_dump.splitlines()
+  for line in text:
+    result = re.match('([0-9]+)d? ([0-9]+) ([0-9]+) (.*)', line)
+    if result:
+      inode2filename[(int(result.group(1)), int(result.group(2)))] = (result.group(4), result.group(3))
+
+  return inode2filename;
+
+def get_inode_data(datafile, dumpfile, adb_serial):
+  if datafile is not None and os.path.isfile(datafile):
+    print('Using cached inode data from ' + datafile)
+    f = open(datafile, 'r')
+    stat_dump = f.read();
+  else:
+    # Build inode maps if we were tracing page cache
+    print('Downloading inode data from device')
+    stat_dump = AdbUtils.do_preprocess_adb_cmd('find /system /data /vendor ' +
+                                    '-exec stat -c "%d %i %s %n" {} \;', adb_serial)
+    if stat_dump is None:
+      print 'Could not retrieve inode data from device.'
+      sys.exit(1)
+
+    if dumpfile is not None:
+      print 'Storing inode data in ' + dumpfile
+      f = open(dumpfile, 'w')
+      f.write(stat_dump)
+      f.close()
+
+    sys.stdout.write('Done.\n')
+
+  return stat_dump
+
+def read_and_parse_trace_file(trace_file, pagecache_stats, app_name):
+  for line in trace_file:
+    parse_atrace_line(line, pagecache_stats, app_name)
+  pagecache_stats.print_stats();
+
+def read_and_parse_trace_data_live(stdout, stderr, pagecache_stats, app_name):
+  # Start reading trace data
+  stdout_queue = Queue.Queue(maxsize=128)
+  stderr_queue = Queue.Queue()
+
+  stdout_thread = FileReaderThread(stdout, stdout_queue,
+                                   text_file=True, chunk_size=64)
+  stderr_thread = FileReaderThread(stderr, stderr_queue,
+                                   text_file=True)
+  stdout_thread.start()
+  stderr_thread.start()
+
+  stdscr = curses.initscr()
+
+  try:
+    height, width = stdscr.getmaxyx()
+    curses.noecho()
+    curses.cbreak()
+    stdscr.keypad(True)
+    stdscr.nodelay(True)
+    stdscr.refresh()
+    # We need at least a 30x100 window
+    used_width = max(width, 100)
+    used_height = max(height, 30)
+
+    # Create a pad for pagecache stats
+    pagecache_pad = curses.newpad(used_height - 2, used_width)
+
+    stdscr.addstr(used_height - 1, 0, 'KEY SHORTCUTS: (r)eset stats, CTRL-c to quit')
+    while (stdout_thread.isAlive() or stderr_thread.isAlive() or
+           not stdout_queue.empty() or not stderr_queue.empty()):
+      while not stderr_queue.empty():
+        # Pass along errors from adb.
+        line = stderr_queue.get()
+        sys.stderr.write(line)
+      while True:
+        try:
+          line = stdout_queue.get(True, STATS_UPDATE_INTERVAL)
+          parse_atrace_line(line, pagecache_stats, app_name)
+        except Queue.Empty:
+          break
+
+      key = ''
+      try:
+        key = stdscr.getkey()
+      except:
+        pass
+
+      if key == 'r':
+        pagecache_stats.reset_stats()
+
+      pagecache_stats.print_stats_curses(pagecache_pad)
+  except Exception, e:
+    curses.endwin()
+    print e
+  finally:
+    curses.endwin()
+    # The threads should already have stopped, so this is just for cleanup.
+    stdout_thread.join()
+    stderr_thread.join()
+
+    stdout.close()
+    stderr.close()
+
+def parse_options(argv):
+  usage = 'Usage: %prog [options]'
+  desc = 'Example: %prog'
+  parser = optparse.OptionParser(usage=usage, description=desc)
+  parser.add_option('-d', dest='inode_dump_file', metavar='FILE',
+                    help='Dump the inode data read from a device to a file.'
+                    ' This file can then be reused with the -i option to speed'
+                    ' up future invocations of this script.')
+  parser.add_option('-i', dest='inode_data_file', metavar='FILE',
+                    help='Read cached inode data from a file saved arlier with the'
+                    ' -d option.')
+  parser.add_option('-s', '--serial', dest='device_serial', type='string',
+                    help='adb device serial number')
+  parser.add_option('-f', dest='trace_file', metavar='FILE',
+                    help='Show stats from a trace file, instead of running live.')
+  parser.add_option('-a', dest='app_name', type='string',
+                    help='filter a particular app')
+
+  options, categories = parser.parse_args(argv[1:])
+  if options.inode_dump_file and options.inode_data_file:
+    parser.error('options -d and -i can\'t be used at the same time')
+  return (options, categories)
+
+def main():
+  options, categories = parse_options(sys.argv)
+
+  # Load inode data for this device
+  inode_data = get_inode_data(options.inode_data_file, options.inode_dump_file,
+      options.device_serial)
+  # Build (dev, inode) -> filename hash
+  inode_lookup_table = build_inode_lookup_table(inode_data)
+  # Init pagecache stats
+  pagecache_stats = PagecacheStats(inode_lookup_table)
+
+  if options.trace_file is not None:
+    if not os.path.isfile(options.trace_file):
+      print >> sys.stderr, ('Couldn\'t load trace file.')
+      sys.exit(1)
+    trace_file = open(options.trace_file, 'r')
+    read_and_parse_trace_file(trace_file, pagecache_stats, options.app_name)
+  else:
+    # Construct and execute trace command
+    trace_cmd = AdbUtils.construct_adb_shell_command(['atrace', '--stream', 'pagecache'],
+        options.device_serial)
+
+    try:
+      atrace = subprocess.Popen(trace_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+    except OSError as error:
+      print >> sys.stderr, ('The command failed')
+      sys.exit(1)
+
+    read_and_parse_trace_data_live(atrace.stdout, atrace.stderr, pagecache_stats, options.app_name)
+
+if __name__ == "__main__":
+  main()
index fd1d517..99d6c66 100644 (file)
@@ -29,6 +29,7 @@ LOCAL_SRC_FILES :=  \
        quipper/perf_reader.cc \
        quipper/perf_parser.cc \
        perf_data_converter.cc \
+       configreader.cc \
        cpuconfig.cc \
        perfprofdcore.cc \
 
@@ -66,6 +67,7 @@ LOCAL_MODULE := perfprofd
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := debug
 LOCAL_SHARED_LIBRARIES += libcutils
+LOCAL_INIT_RC := perfprofd.rc
 include $(BUILD_EXECUTABLE)
 
 # Clean temp vars
diff --git a/perfprofd/NOTICE b/perfprofd/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/perfprofd/configreader.cc b/perfprofd/configreader.cc
new file mode 100644 (file)
index 0000000..e9dbdec
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+**
+** 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 <sstream>
+
+#include <android-base/file.h>
+
+#include "configreader.h"
+#include "perfprofdutils.h"
+
+//
+// Config file path
+//
+static const char *config_file_path =
+    "/data/data/com.google.android.gms/files/perfprofd.conf";
+
+ConfigReader::ConfigReader()
+    : trace_config_read(false)
+{
+  addDefaultEntries();
+}
+
+ConfigReader::~ConfigReader()
+{
+}
+
+const char *ConfigReader::getConfigFilePath()
+{
+  return config_file_path;
+}
+
+void ConfigReader::setConfigFilePath(const char *path)
+{
+  config_file_path = strdup(path);
+  W_ALOGI("config file path set to %s", config_file_path);
+}
+
+//
+// Populate the reader with the set of allowable entries
+//
+void ConfigReader::addDefaultEntries()
+{
+  // Average number of seconds between perf profile collections (if
+  // 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", 14400, 100, UINT32_MAX);
+
+  // Use the specified fixed seed for random number generation (unit
+  // testing)
+  addUnsignedEntry("use_fixed_seed", 0, 0, UINT32_MAX);
+
+  // For testing purposes, number of times to iterate through main
+  // loop.  Value of zero indicates that we should loop forever.
+  addUnsignedEntry("main_loop_iterations", 0, 0, UINT32_MAX);
+
+  // Destination directory (where to write profiles). This location
+  // chosen since it is accessible to the uploader service.
+  addStringEntry("destination_directory", "/data/misc/perfprofd");
+
+  // 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
+  // min/max.
+  addUnsignedEntry("sampling_period", 500000, 5000, UINT32_MAX);
+
+  // Length of time to collect samples (number of seconds for 'perf
+  // record -a' run).
+  addUnsignedEntry("sample_duration", 3, 2, 600);
+
+  // If this parameter is non-zero it will cause perfprofd to
+  // exit immediately if the build type is not userdebug or eng.
+  // Currently defaults to 1 (true).
+  addUnsignedEntry("only_debug_build", 1, 0, 1);
+
+  // 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);
+
+  // For unit testing only: if set to 1, emit info messages on config
+  // file parsing.
+  addUnsignedEntry("trace_config_read", 0, 0, 1);
+
+  // Control collection of various additional profile tags
+  addUnsignedEntry("collect_cpu_utilization", 1, 0, 1);
+  addUnsignedEntry("collect_charging_state", 1, 0, 1);
+  addUnsignedEntry("collect_booting", 1, 0, 1);
+  addUnsignedEntry("collect_camera_active", 0, 0, 1);
+}
+
+void ConfigReader::addUnsignedEntry(const char *key,
+                                    unsigned default_value,
+                                    unsigned min_value,
+                                    unsigned max_value)
+{
+  std::string ks(key);
+  if (u_entries.find(ks) != u_entries.end() ||
+      s_entries.find(ks) != s_entries.end()) {
+    W_ALOGE("internal error -- duplicate entry for key %s", key);
+    exit(9);
+  }
+  values vals;
+  vals.minv = min_value;
+  vals.maxv = max_value;
+  u_info[ks] = vals;
+  u_entries[ks] = default_value;
+}
+
+void ConfigReader::addStringEntry(const char *key, const char *default_value)
+{
+  std::string ks(key);
+  if (u_entries.find(ks) != u_entries.end() ||
+      s_entries.find(ks) != s_entries.end()) {
+    W_ALOGE("internal error -- duplicate entry for key %s", key);
+    exit(9);
+  }
+  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) const
+{
+  std::string ks(key);
+  auto it = u_entries.find(ks);
+  assert(it != u_entries.end());
+  return it->second;
+}
+
+std::string ConfigReader::getStringValue(const char *key) const
+{
+  std::string ks(key);
+  auto it = s_entries.find(ks);
+  assert(it != s_entries.end());
+  return it->second;
+}
+
+void ConfigReader::overrideUnsignedEntry(const char *key, unsigned new_value)
+{
+  std::string ks(key);
+  auto it = u_entries.find(ks);
+  assert(it != u_entries.end());
+  values vals;
+  auto iit = u_info.find(key);
+  assert(iit != u_info.end());
+  vals = iit->second;
+  assert(new_value >= vals.minv && new_value <= vals.maxv);
+  it->second = new_value;
+  W_ALOGI("option %s overridden to %u", key, new_value);
+}
+
+
+//
+// Parse a key=value pair read from the config file. This will issue
+// warnings or errors to the system logs if the line can't be
+// interpreted properly.
+//
+void ConfigReader::parseLine(const char *key,
+                             const char *value,
+                             unsigned linecount)
+{
+  assert(key);
+  assert(value);
+
+  auto uit = u_entries.find(key);
+  if (uit != u_entries.end()) {
+    unsigned uvalue = 0;
+    if (isdigit(value[0]) == 0 || sscanf(value, "%u", &uvalue) != 1) {
+      W_ALOGW("line %d: malformed unsigned value (ignored)", linecount);
+    } else {
+      values vals;
+      auto iit = u_info.find(key);
+      assert(iit != u_info.end());
+      vals = iit->second;
+      if (uvalue < vals.minv || uvalue > vals.maxv) {
+        W_ALOGW("line %d: specified value %u for '%s' "
+                "outside permitted range [%u %u] (ignored)",
+                linecount, uvalue, key, vals.minv, vals.maxv);
+      } else {
+        if (trace_config_read) {
+          W_ALOGI("option %s set to %u", key, uvalue);
+        }
+        uit->second = uvalue;
+      }
+    }
+    trace_config_read = (getUnsignedValue("trace_config_read") != 0);
+    return;
+  }
+
+  auto sit = s_entries.find(key);
+  if (sit != s_entries.end()) {
+    if (trace_config_read) {
+      W_ALOGI("option %s set to %s", key, value);
+    }
+    sit->second = std::string(value);
+    return;
+  }
+
+  W_ALOGW("line %d: unknown option '%s' ignored", linecount, key);
+}
+
+static bool isblank(const std::string &line)
+{
+  for (std::string::const_iterator it = line.begin(); it != line.end(); ++it)
+  {
+    if (isspace(*it) == 0) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ConfigReader::readFile()
+{
+  std::string contents;
+  if (! android::base::ReadFileToString(config_file_path, &contents)) {
+    return false;
+  }
+
+  std::stringstream ss(contents);
+  std::string line;
+  for (unsigned linecount = 1;
+       std::getline(ss,line,'\n');
+       linecount += 1)
+  {
+
+    // comment line?
+    if (line[0] == '#') {
+      continue;
+    }
+
+    // blank line?
+    if (isblank(line.c_str())) {
+      continue;
+    }
+
+    // look for X=Y assignment
+    auto efound = line.find('=');
+    if (efound == std::string::npos) {
+      W_ALOGW("line %d: line malformed (no '=' found)", linecount);
+      continue;
+    }
+
+    std::string key(line.substr(0, efound));
+    std::string value(line.substr(efound+1, std::string::npos));
+
+    parseLine(key.c_str(), value.c_str(), linecount);
+  }
+
+  return true;
+}
diff --git a/perfprofd/configreader.h b/perfprofd/configreader.h
new file mode 100644 (file)
index 0000000..2e29601
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+**
+** 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.
+*/
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_CONFIGREADER_H_
+#define SYSTEM_EXTRAS_PERFPROFD_CONFIGREADER_H_
+
+#include <string>
+#include <map>
+
+//
+// This table describes the perfprofd config file syntax in terms of
+// key/value pairs.  Values come in two flavors: strings, or unsigned
+// integers. In the latter case the reader sets allowable
+// minimum/maximum for the setting.
+//
+class ConfigReader {
+
+ public:
+  ConfigReader();
+  ~ConfigReader();
+
+  // Ask for the current setting of a config item
+  unsigned getUnsignedValue(const char *key) const;
+  std::string getStringValue(const char *key) const;
+
+  // read the specified config file, applying any settings it contains
+  // returns true for successful read, false if conf file cannot be opened.
+  bool readFile();
+
+  // set/get path to config file
+  static void setConfigFilePath(const char *path);
+  static const char *getConfigFilePath();
+
+  // override a config item (for unit testing purposes)
+  void overrideUnsignedEntry(const char *key, unsigned new_value);
+
+ private:
+  void addUnsignedEntry(const char *key,
+                        unsigned default_value,
+                        unsigned min_value,
+                        unsigned max_value);
+  void addStringEntry(const char *key, const char *default_value);
+  void addDefaultEntries();
+  void parseLine(const char *key, const char *value, unsigned linecount);
+
+  typedef struct { unsigned minv, maxv; } values;
+  std::map<std::string, values> u_info;
+  std::map<std::string, unsigned> u_entries;
+  std::map<std::string, std::string> s_entries;
+  bool trace_config_read;
+};
+
+#endif
index 3932a16..2f60e12 100644 (file)
@@ -99,4 +99,19 @@ message AndroidPerfProfile {
   // to first value from /proc/loadavg multiplied by 100 then
   // converted to int32
   optional int32 sys_load_average = 6;
+
+  // At the point when the profile was collected, was a camera active?
+  optional bool camera_active = 7;
+
+  // At the point when the profile was collected, was the device still booting?
+  optional bool booting = 8;
+
+  // At the point when the profile was collected, was the device plugged into
+  // a charger?
+  optional bool on_charger = 9;
+
+  // CPU utilization measured prior to profile collection (expressed as
+  // 100 minus the idle percentage).
+  optional int32 cpu_utilization = 10;
+
 }
diff --git a/perfprofd/perfprofd.rc b/perfprofd/perfprofd.rc
new file mode 100644 (file)
index 0000000..40aab6b
--- /dev/null
@@ -0,0 +1,6 @@
+service perfprofd /system/xbin/perfprofd
+    class late_start
+    user root
+    group root wakelock
+    oneshot
+    writepid /dev/cpuset/system-background/tasks
index 8f5b013..134c05c 100644 (file)
 #include <set>
 #include <cctype>
 
-#include <base/file.h>
-#include <base/stringprintf.h>
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <cutils/properties.h>
 
 #include "perfprofdcore.h"
 #include "perfprofdutils.h"
 #include "perf_data_converter.h"
 #include "cpuconfig.h"
+#include "configreader.h"
 
 //
 // Perf profiling daemon -- collects system-wide profiles using
@@ -101,274 +102,12 @@ static int is_debug_build = -1;
 static unsigned short random_seed[3];
 
 //
-// Config file path. May be overridden with -c command line option
-//
-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.
-// Values come in two flavors: strings, or unsigned integers. In the latter
-// case the reader sets allowable minimum/maximum for the setting.
-//
-class ConfigReader {
-
- public:
-  ConfigReader();
-  ~ConfigReader();
-
-  // Ask for the current setting of a config item
-  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(bool initial);
-
- private:
-  void addUnsignedEntry(const char *key,
-                        unsigned default_value,
-                        unsigned min_value,
-                        unsigned max_value);
-  void addStringEntry(const char *key, const char *default_value);
-  void addDefaultEntries();
-  void parseLine(const char *key, const char *value, unsigned linecount);
-
-  typedef struct { unsigned minv, maxv; } values;
-  std::map<std::string, values> u_info;
-  std::map<std::string, unsigned> u_entries;
-  std::map<std::string, std::string> s_entries;
-  bool trace_config_read;
-};
-
-ConfigReader::ConfigReader()
-    : trace_config_read(false)
-{
-  addDefaultEntries();
-}
-
-ConfigReader::~ConfigReader()
-{
-}
-
-//
-// Populate the reader with the set of allowable entries
-//
-void ConfigReader::addDefaultEntries()
-{
-  // Average number of seconds between perf profile collections (if
-  // 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", 14400, 100, UINT32_MAX);
-
-  // Use the specified fixed seed for random number generation (unit
-  // testing)
-  addUnsignedEntry("use_fixed_seed", 0, 0, UINT32_MAX);
-
-  // For testing purposes, number of times to iterate through main
-  // loop.  Value of zero indicates that we should loop forever.
-  addUnsignedEntry("main_loop_iterations", 0, 0, UINT32_MAX);
-
-  // Destination directory (where to write profiles). This location
-  // chosen since it is accessible to the uploader service.
-  addStringEntry("destination_directory", "/data/misc/perfprofd");
-
-  // 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
-  // min/max.
-  addUnsignedEntry("sampling_period", 500000, 5000, UINT32_MAX);
-
-  // Length of time to collect samples (number of seconds for 'perf
-  // record -a' run).
-  addUnsignedEntry("sample_duration", 3, 2, 600);
-
-  // If this parameter is non-zero it will cause perfprofd to
-  // exit immediately if the build type is not userdebug or eng.
-  // Currently defaults to 1 (true).
-  addUnsignedEntry("only_debug_build", 1, 0, 1);
-
-  // 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);
-
-  // For unit testing only: if set to 1, emit info messages on config
-  // file parsing.
-  addUnsignedEntry("trace_config_read", 0, 0, 1);
-}
-
-void ConfigReader::addUnsignedEntry(const char *key,
-                                    unsigned default_value,
-                                    unsigned min_value,
-                                    unsigned max_value)
-{
-  std::string ks(key);
-  if (u_entries.find(ks) != u_entries.end() ||
-      s_entries.find(ks) != s_entries.end()) {
-    W_ALOGE("internal error -- duplicate entry for key %s", key);
-    exit(9);
-  }
-  values vals;
-  vals.minv = min_value;
-  vals.maxv = max_value;
-  u_info[ks] = vals;
-  u_entries[ks] = default_value;
-}
-
-void ConfigReader::addStringEntry(const char *key, const char *default_value)
-{
-  std::string ks(key);
-  if (u_entries.find(ks) != u_entries.end() ||
-      s_entries.find(ks) != s_entries.end()) {
-    W_ALOGE("internal error -- duplicate entry for key %s", key);
-    exit(9);
-  }
-  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) const
-{
-  std::string ks(key);
-  auto it = u_entries.find(ks);
-  assert(it != u_entries.end());
-  return it->second;
-}
-
-std::string ConfigReader::getStringValue(const char *key) const
-{
-  std::string ks(key);
-  auto it = s_entries.find(ks);
-  assert(it != s_entries.end());
-  return it->second;
-}
-
-//
-// Parse a key=value pair read from the config file. This will issue
-// warnings or errors to the system logs if the line can't be
-// interpreted properly.
+// 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)
 //
-void ConfigReader::parseLine(const char *key,
-                             const char *value,
-                             unsigned linecount)
-{
-  assert(key);
-  assert(value);
-
-  auto uit = u_entries.find(key);
-  if (uit != u_entries.end()) {
-    unsigned uvalue = 0;
-    if (isdigit(value[0]) == 0 || sscanf(value, "%u", &uvalue) != 1) {
-      W_ALOGW("line %d: malformed unsigned value (ignored)", linecount);
-    } else {
-      values vals;
-      auto iit = u_info.find(key);
-      assert(iit != u_info.end());
-      vals = iit->second;
-      if (uvalue < vals.minv || uvalue > vals.maxv) {
-        W_ALOGW("line %d: specified value %u for '%s' "
-                "outside permitted range [%u %u] (ignored)",
-                linecount, uvalue, key, vals.minv, vals.maxv);
-      } else {
-        if (trace_config_read) {
-          W_ALOGI("option %s set to %u", key, uvalue);
-        }
-        uit->second = uvalue;
-      }
-    }
-    trace_config_read = (getUnsignedValue("trace_config_read") != 0);
-    return;
-  }
-
-  auto sit = s_entries.find(key);
-  if (sit != s_entries.end()) {
-    if (trace_config_read) {
-      W_ALOGI("option %s set to %s", key, value);
-    }
-    sit->second = std::string(value);
-    return;
-  }
-
-  W_ALOGW("line %d: unknown option '%s' ignored", linecount, key);
-}
-
-static bool isblank(const std::string &line)
-{
-  for (std::string::const_iterator it = line.begin(); it != line.end(); ++it)
-  {
-    if (isspace(*it) == 0) {
-      return false;
-    }
-  }
-  return true;
-}
-
-void ConfigReader::readFile(bool initial)
+static void sig_hup(int /* signum */)
 {
-  FILE *fp = fopen(config_file_path, "r");
-  if (!fp) {
-    if (initial) {
-      W_ALOGE("unable to open configuration file %s", config_file_path);
-    }
-    return;
-  }
-
-  char *linebuf = NULL;
-  size_t line_length = 0;
-  for (unsigned linecount = 1;
-       getline(&linebuf, &line_length, fp) != -1;
-       ++linecount) {
-    char *eq = 0;
-    char *key, *value;
-
-    // comment line?
-    if (linebuf[0] == '#') {
-      continue;
-    }
-
-    // blank line?
-    if (isblank(linebuf)) {
-      continue;
-    }
-
-    // look for X=Y assignment
-    eq = strchr(linebuf, '=');
-    if (!eq) {
-      W_ALOGW("line %d: line malformed (no '=' found)", linecount);
-      continue;
-    }
-
-    *eq = '\0';
-    key = linebuf;
-    value = eq+1;
-    char *ln = strrchr(value, '\n');
-    if (ln) { *ln = '\0'; }
-
-    parseLine(key, value, linecount);
-  }
-  free(linebuf);
-  fclose(fp);
+  W_ALOGW("SIGHUP received");
 }
 
 //
@@ -385,8 +124,7 @@ static void parse_args(int argc, char** argv)
         W_ALOGE("malformed command line: -c option requires argument)");
         continue;
       }
-      config_file_path = strdup(argv[ac+1]);
-      W_ALOGI("config file path set to %s", config_file_path);
+      ConfigReader::setConfigFilePath(argv[ac+1]);
       ++ac;
     } else {
       W_ALOGE("malformed command line: unknown option or arg %s)", argv[ac]);
@@ -482,9 +220,208 @@ static CKPROFILE_RESULT check_profiling_enabled(ConfigReader &config)
   return DO_COLLECT_PROFILE;
 }
 
-static void annotate_encoded_perf_profile(wireless_android_play_playlog::AndroidPerfProfile *profile)
+bool get_booting()
+{
+  char propBuf[PROPERTY_VALUE_MAX];
+  propBuf[0] = '\0';
+  property_get("sys.boot_completed", propBuf, "");
+  return (propBuf[0] != '1');
+}
+
+//
+// Constructor takes a timeout (in seconds) and a child pid; If an
+// alarm set for the specified number of seconds triggers, then a
+// SIGKILL is sent to the child. Destructor resets alarm. Example:
+//
+//       pid_t child_pid = ...;
+//       { AlarmHelper h(10, child_pid);
+//         ... = read_from_child(child_pid, ...);
+//       }
+//
+// NB: this helper is not re-entrant-- avoid nested use or
+// use by multiple threads
+//
+class AlarmHelper {
+ public:
+  AlarmHelper(unsigned num_seconds, pid_t child)
+  {
+    struct sigaction sigact;
+    assert(child);
+    assert(child_ == 0);
+    memset(&sigact, 0, sizeof(sigact));
+    sigact.sa_sigaction = handler;
+    sigaction(SIGALRM, &sigact, &oldsigact_);
+    child_ = child;
+    alarm(num_seconds);
+  }
+  ~AlarmHelper()
+  {
+    alarm(0);
+    child_ = 0;
+    sigaction(SIGALRM, &oldsigact_, NULL);
+  }
+  static void handler(int, siginfo_t *, void *);
+
+ private:
+  struct sigaction oldsigact_;
+  static pid_t child_;
+};
+
+pid_t AlarmHelper::child_;
+
+void AlarmHelper::handler(int, siginfo_t *, void *)
+{
+  W_ALOGW("SIGALRM timeout");
+  kill(child_, SIGKILL);
+}
+
+//
+// This implementation invokes "dumpsys media.camera" and inspects the
+// output to determine if any camera clients are active. NB: this is
+// currently disable (via config option) until the selinux issues can
+// be sorted out. Another possible implementation (not yet attempted)
+// would be to use the binder to call into the native camera service
+// via "ICameraService".
+//
+bool get_camera_active()
+{
+  int pipefds[2];
+  if (pipe2(pipefds, O_CLOEXEC) != 0) {
+    W_ALOGE("pipe2() failed (%s)", strerror(errno));
+    return false;
+  }
+  pid_t pid = fork();
+  if (pid == -1) {
+    W_ALOGE("fork() failed (%s)", strerror(errno));
+    close(pipefds[0]);
+    close(pipefds[1]);
+    return false;
+  } else if (pid == 0) {
+    // child
+    close(pipefds[0]);
+    dup2(pipefds[1], fileno(stderr));
+    dup2(pipefds[1], fileno(stdout));
+    const char *argv[10];
+    unsigned slot = 0;
+    argv[slot++] = "/system/bin/dumpsys";
+    argv[slot++] = "media.camera";
+    argv[slot++] = nullptr;
+    execvp(argv[0], (char * const *)argv);
+    W_ALOGE("execvp() failed (%s)", strerror(errno));
+    return false;
+  }
+  // parent
+  AlarmHelper helper(10, pid);
+  close(pipefds[1]);
+
+  // read output
+  bool have_cam = false;
+  bool have_clients = true;
+  std::string dump_output;
+  bool result = android::base::ReadFdToString(pipefds[0], &dump_output);
+  close(pipefds[0]);
+  if (result) {
+    std::stringstream ss(dump_output);
+    std::string line;
+    while (std::getline(ss,line,'\n')) {
+      if (line.find("Camera module API version:") !=
+          std::string::npos) {
+        have_cam = true;
+      }
+      if (line.find("No camera module available") !=
+          std::string::npos ||
+          line.find("No active camera clients yet") !=
+          std::string::npos) {
+        have_clients = false;
+      }
+    }
+  }
+
+  // reap child (no zombies please)
+  int st = 0;
+  TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
+  return have_cam && have_clients;
+}
+
+bool get_charging()
+{
+  std::string psdir("/sys/class/power_supply");
+  DIR* dir = opendir(psdir.c_str());
+  if (dir == NULL) {
+    W_ALOGE("Failed to open dir %s (%s)", psdir.c_str(), strerror(errno));
+    return false;
+  }
+  struct dirent* e;
+  bool result = false;
+  while ((e = readdir(dir)) != 0) {
+    if (e->d_name[0] != '.') {
+      std::string online_path = psdir + "/" + e->d_name + "/online";
+      std::string contents;
+      int value = 0;
+      if (android::base::ReadFileToString(online_path.c_str(), &contents) &&
+          sscanf(contents.c_str(), "%d", &value) == 1) {
+        if (value) {
+          result = true;
+          break;
+        }
+      }
+    }
+  }
+  closedir(dir);
+  return result;
+}
+
+bool postprocess_proc_stat_contents(const std::string &pscontents,
+                                    long unsigned *idleticks,
+                                    long unsigned *remainingticks)
+{
+  long unsigned usertime, nicetime, systime, idletime, iowaittime;
+  long unsigned irqtime, softirqtime;
+
+  int rc = sscanf(pscontents.c_str(), "cpu  %lu %lu %lu %lu %lu %lu %lu",
+                  &usertime, &nicetime, &systime, &idletime,
+                  &iowaittime, &irqtime, &softirqtime);
+  if (rc != 7) {
+    return false;
+  }
+  *idleticks = idletime;
+  *remainingticks = usertime + nicetime + systime + iowaittime + irqtime + softirqtime;
+  return true;
+}
+
+unsigned collect_cpu_utilization()
+{
+  std::string contents;
+  long unsigned idle[2];
+  long unsigned busy[2];
+  for (unsigned iter = 0; iter < 2; ++iter) {
+    if (!android::base::ReadFileToString("/proc/stat", &contents)) {
+      return 0;
+    }
+    if (!postprocess_proc_stat_contents(contents, &idle[iter], &busy[iter])) {
+      return 0;
+    }
+    if (iter == 0) {
+      sleep(1);
+    }
+  }
+  long unsigned total_delta = (idle[1] + busy[1]) - (idle[0] + busy[0]);
+  long unsigned busy_delta = busy[1] - busy[0];
+  return busy_delta * 100 / total_delta;
+}
+
+static void annotate_encoded_perf_profile(wireless_android_play_playlog::AndroidPerfProfile *profile,
+                                          const ConfigReader &config,
+                                          unsigned cpu_utilization)
 {
   //
+  // Incorporate cpu utilization (collected prior to perf run)
+  //
+  if (config.getUnsignedValue("collect_cpu_utilization")) {
+    profile->set_cpu_utilization(cpu_utilization);
+  }
+
+  //
   // Load average as reported by the kernel
   //
   std::string load;
@@ -498,6 +435,20 @@ static void annotate_encoded_perf_profile(wireless_android_play_playlog::Android
   }
 
   //
+  // Device still booting? Camera in use? Plugged into charger?
+  //
+  bool is_booting = get_booting();
+  if (config.getUnsignedValue("collect_booting")) {
+    profile->set_booting(is_booting);
+  }
+  if (config.getUnsignedValue("collect_camera_active")) {
+    profile->set_camera_active(is_booting ? false : get_camera_active());
+  }
+  if (config.getUnsignedValue("collect_charging_state")) {
+    profile->set_on_charger(get_charging());
+  }
+
+  //
   // 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?
@@ -516,7 +467,9 @@ inline char* string_as_array(std::string* str) {
 }
 
 PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
-                               const char *encoded_file_path)
+                               const char *encoded_file_path,
+                               const ConfigReader &config,
+                               unsigned cpu_utilization)
 {
   //
   // Open and read perf.data file
@@ -532,11 +485,11 @@ PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
   }
 
   // All of the info in 'encodedProfile' is derived from the perf.data file;
-  // here we tack display status and system load.
+  // here we tack display status, cpu utilization, system load, etc.
   wireless_android_play_playlog::AndroidPerfProfile &prof =
       const_cast<wireless_android_play_playlog::AndroidPerfProfile&>
       (encodedProfile);
-  annotate_encoded_perf_profile(&prof);
+  annotate_encoded_perf_profile(&prof, config, cpu_utilization);
 
   //
   // Serialize protobuf to array
@@ -751,6 +704,14 @@ static bool post_process(const ConfigReader &config, int current_seq)
 static PROFILE_RESULT collect_profile(const ConfigReader &config, int seq)
 {
   //
+  // Collect cpu utilization if enabled
+  //
+  unsigned cpu_utilization = 0;
+  if (config.getUnsignedValue("collect_cpu_utilization")) {
+    cpu_utilization = collect_cpu_utilization();
+  }
+
+  //
   // Form perf.data file name, perf error output file name
   //
   std::string destdir = config.getStringValue("destination_directory");
@@ -813,15 +774,7 @@ static PROFILE_RESULT collect_profile(const ConfigReader &config, int seq)
   //
   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. 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 */)
-{
+  return encode_to_proto(data_file_path, path.c_str(), config, cpu_utilization);
 }
 
 //
@@ -870,7 +823,20 @@ static void set_seed(ConfigReader &config)
 //
 static void init(ConfigReader &config)
 {
-  config.readFile(true);
+  if (!config.readFile()) {
+    W_ALOGE("unable to open configuration file %s",
+            config.getConfigFilePath());
+  }
+
+  // Children of init inherit an artificially low OOM score -- this is not
+  // desirable for perfprofd (its OOM score should be on par with
+  // other user processes).
+  std::stringstream oomscore_path;
+  oomscore_path << "/proc/" << getpid() << "/oom_score_adj";
+  if (!android::base::WriteStringToFile("0", oomscore_path.str())) {
+    W_ALOGE("unable to write to %s", oomscore_path.str().c_str());
+  }
+
   set_seed(config);
   cleanup_destination_dir(config);
 
@@ -924,7 +890,7 @@ int perfprofd_main(int argc, char** argv)
 
     // Reread config file -- the uploader may have rewritten it as a result
     // of a gservices change
-    config.readFile(false);
+    config.readFile();
 
     // Check for profiling enabled...
     CKPROFILE_RESULT ckresult = check_profiling_enabled(config);
index 53695e2..2607c48 100644 (file)
 ** limitations under the License.
 */
 
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFDCORE_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFDCORE_H_
+
+class ConfigReader;
+
 // Semaphore file that indicates that the user is opting in
 #define SEMAPHORE_FILENAME "perf_profile_collection_enabled.txt"
 
@@ -63,4 +68,16 @@ typedef enum {
 // was successful (either OK_PROFILE_COLLECTION or an error of some sort).
 //
 PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
-                               const char *encoded_file_path);
+                               const char *encoded_file_path,
+                               const ConfigReader &config,
+                               unsigned cpu_utilization);
+
+//
+// Exposed for unit testing
+//
+extern unsigned collect_cpu_utilization();
+extern bool get_booting();
+extern bool get_charging();
+extern bool get_camera_active();
+
+#endif
index d8ea10a..7d372c4 100644 (file)
@@ -34,7 +34,7 @@ include $(CLEAR_VARS)
 LOCAL_CLANG := true
 LOCAL_CPP_EXTENSION := cc
 LOCAL_CXX_STL := libc++
-LOCAL_STATIC_LIBRARIES := libperfprofdcore libperfprofdmockutils libbase
+LOCAL_STATIC_LIBRARIES := libperfprofdcore libperfprofdmockutils libgtest libbase
 LOCAL_SHARED_LIBRARIES := libprotobuf-cpp-lite
 LOCAL_C_INCLUDES += system/extras/perfprofd external/protobuf/src
 LOCAL_SRC_FILES := perfprofd_test.cc
index d13e21e..3a32204 100644 (file)
 #include <sys/stat.h>
 #include <fcntl.h>
 
-#include <base/stringprintf.h>
+#include <android-base/stringprintf.h>
 
 #include "perfprofdcore.h"
+#include "configreader.h"
 #include "perfprofdutils.h"
 #include "perfprofdmockutils.h"
 
@@ -480,6 +481,20 @@ TEST_F(PerfProfdTest, ConfigFileParsing)
                      expected, "ConfigFileParsing");
 }
 
+TEST_F(PerfProfdTest, ProfileCollectionAnnotations)
+{
+  unsigned util1 = collect_cpu_utilization();
+  EXPECT_LE(util1, 100);
+  EXPECT_GE(util1, 0);
+
+  // NB: expectation is that when we run this test, the device will be
+  // completed booted, will be on charger, and will not have the camera
+  // active.
+  EXPECT_FALSE(get_booting());
+  EXPECT_TRUE(get_charging());
+  EXPECT_FALSE(get_camera_active());
+}
+
 TEST_F(PerfProfdTest, BasicRunWithCannedPerf)
 {
   //
@@ -491,9 +506,15 @@ TEST_F(PerfProfdTest, BasicRunWithCannedPerf)
   std::string input_perf_data(test_dir);
   input_perf_data += "/canned.perf.data";
 
+  // Set up config to avoid these annotations (they are tested elsewhere)
+  ConfigReader config;
+  config.overrideUnsignedEntry("collect_cpu_utilization", 0);
+  config.overrideUnsignedEntry("collect_charging_state", 0);
+  config.overrideUnsignedEntry("collect_camera_active", 0);
+
   // Kick off encoder and check return code
   PROFILE_RESULT result =
-      encode_to_proto(input_perf_data, encoded_file_path(0).c_str());
+      encode_to_proto(input_perf_data, encoded_file_path(0).c_str(), config, 0);
   EXPECT_EQ(OK_PROFILE_COLLECTION, result);
 
   // Read and decode the resulting perf.data.encoded file
diff --git a/postinst/Android.mk b/postinst/Android.mk
new file mode 100644 (file)
index 0000000..b0dec05
--- /dev/null
@@ -0,0 +1,30 @@
+#
+# 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)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= postinst_example
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_SRC_FILES := postinst.sh
+
+# Create a symlink from /postinst to our default post-install script in the
+# same filesystem as /postinst.
+# TODO(deymo): Remove this symlink and add the path to the product config.
+LOCAL_POST_INSTALL_CMD := \
+    $(hide) ln -sf bin/postinst_example $(TARGET_OUT)/postinst
+include $(BUILD_PREBUILT)
diff --git a/postinst/MODULE_LICENSE_APACHE2 b/postinst/MODULE_LICENSE_APACHE2
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/postinst/NOTICE b/postinst/NOTICE
new file mode 100644 (file)
index 0000000..34bdaf1
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2015, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/postinst/postinst.sh b/postinst/postinst.sh
new file mode 100644 (file)
index 0000000..eb98e79
--- /dev/null
@@ -0,0 +1,36 @@
+#!/system/bin/sh
+
+#
+# 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.
+#
+
+# This is an example post-install script. This script will be executed by the
+# update_engine right after finishing writing all the partitions, but before
+# marking the new slot as active.
+#
+# This script receives no arguments. argv[0] will include the absolute path to
+# the script, including the temporary directory where the new partition was
+# mounted.
+#
+# This script will run in the context of the old kernel and old system. Note
+# that the absolute path used in first line of this script (/system/bin/sh) is
+# indeed the old system's sh binary. If you use a compiled program, you might
+# want to link it statically or use a wrapper script to use the new ldso to run
+# your program (see the --generate-wrappers option in lddtree.py for example).
+#
+# If the exit code of this program is an error code (different from 0), the
+# update will fail and the new slot will not be marked as active.
+
+exit 0
index ffd5660..b7fba56 100644 (file)
 # limitations under the License.
 
 LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
 
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := procmem.c
-
-LOCAL_C_INCLUDES := $(call include-path-for, libpagemap)
-
 LOCAL_SHARED_LIBRARIES := libpagemap
-
 LOCAL_MODULE := procmem
-
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-
 LOCAL_MODULE_TAGS := debug
-
 include $(BUILD_EXECUTABLE)
index be7c599..f1eb3b6 100644 (file)
 # limitations under the License.
 
 LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
 
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := procrank.c
-
-LOCAL_C_INCLUDES := $(call include-path-for, libpagemap)
-
 LOCAL_CFLAGS := -Wall -Wextra -Wformat=2 -Werror
-
 LOCAL_SHARED_LIBRARIES := libpagemap
-
 LOCAL_MODULE := procrank
-
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-
 LOCAL_MODULE_TAGS := debug
-
 include $(BUILD_EXECUTABLE)
index 881f110..a6f8342 100644 (file)
@@ -144,6 +144,35 @@ void get_mem_info(uint64_t mem[]) {
     }
 }
 
+static uint64_t get_zram_mem_used() {
+#define ZRAM_SYSFS "/sys/block/zram0/"
+    FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
+    if (f) {
+        uint64_t mem_used_total = 0;
+
+        int matched = fscanf(f, "%*d %*d %" SCNu64 " %*d %*d %*d %*d", &mem_used_total);
+        if (matched != 1)
+            fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mm_stat\n");
+
+        fclose(f);
+        return mem_used_total;
+    }
+
+    f = fopen(ZRAM_SYSFS "mem_used_total", "r");
+    if (f) {
+        uint64_t mem_used_total = 0;
+
+        int matched = fscanf(f, "%" SCNu64, &mem_used_total);
+        if (matched != 1)
+            fprintf(stderr, "warning: failed to parse " ZRAM_SYSFS "mem_used_total\n");
+
+        fclose(f);
+        return mem_used_total;
+    }
+
+    return 0;
+}
+
 int main(int argc, char *argv[]) {
     pm_kernel_t *ker;
     pm_process_t *proc;
@@ -172,8 +201,6 @@ int main(int argc, char *argv[]) {
 
     uint64_t mem[MEMINFO_COUNT] = { };
     pm_proportional_swap_t *p_swap;
-    int fd, len;
-    char buffer[1024];
     float zram_cr = 0.0;
 
     signal(SIGPIPE, SIG_IGN);
@@ -277,17 +304,12 @@ int main(int argc, char *argv[]) {
     qsort(procs, num_procs, sizeof(procs[0]), compfn);
 
     if (has_swap) {
-        fd = open("/sys/block/zram0/mem_used_total", O_RDONLY);
-        if (fd >= 0) {
-            len = read(fd, buffer, sizeof(buffer)-1);
-            close(fd);
-            if (len > 0) {
-                buffer[len] = 0;
-                mem[MEMINFO_ZRAM_TOTAL] = atoll(buffer)/1024;
-                zram_cr = (float) mem[MEMINFO_ZRAM_TOTAL] /
-                        (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]);
-                has_zram = true;
-            }
+        uint64_t zram_mem_used = get_zram_mem_used();
+        if (zram_mem_used) {
+            mem[MEMINFO_ZRAM_TOTAL] = zram_mem_used/1024;
+            zram_cr = (float) mem[MEMINFO_ZRAM_TOTAL] /
+                    (mem[MEMINFO_SWAP_TOTAL] - mem[MEMINFO_SWAP_FREE]);
+            has_zram = true;
         }
     }
 
diff --git a/puncture_fs/NOTICE b/puncture_fs/NOTICE
new file mode 100644 (file)
index 0000000..316b4eb
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2014, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index 3f7e4a7..e9d08dc 100644 (file)
@@ -187,7 +187,7 @@ static bool puncture_fs (const char * const path, const u64 total_size,
     free(base_file_data);
     rm_bin_argv[2] = delete_dir;
     if (android_fork_execvp_ext(ARRAY_SIZE(rm_bin_argv), rm_bin_argv,
-                                NULL, 1, LOG_KLOG, 0, NULL) < 0) {
+                                NULL, 1, LOG_KLOG, 0, NULL, NULL, 0) < 0) {
         fprintf(stderr, "\nFailed to delete %s\n", rm_bin_argv[2]);
         return false;
     }
index 5843c43..c984b9f 100644 (file)
@@ -7,5 +7,6 @@ LOCAL_SRC_FILES := sane_schedstat.c
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := debug
 LOCAL_MODULE := sane_schedstat
+LOCAL_CFLAGS := -Wno-unused-parameter
 
 include $(BUILD_EXECUTABLE)
diff --git a/sane_schedstat/NOTICE b/sane_schedstat/NOTICE
new file mode 100644 (file)
index 0000000..5d14293
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index 5c90f12..44ca818 100644 (file)
@@ -3,8 +3,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:= showmap.c
-LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_SRC_FILES:= showmap.cpp
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := debug
 LOCAL_MODULE:= showmap
similarity index 79%
rename from showmap/showmap.c
rename to showmap/showmap.cpp
index 2d60216..6efe260 100644 (file)
@@ -1,15 +1,12 @@
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <math.h>
 #include <string.h>
-#include <errno.h>
 #include <unistd.h>
-#include <fcntl.h>
-
-#include <ctype.h>
-#include <stddef.h>
-
-typedef struct mapinfo mapinfo;
 
 struct mapinfo {
     mapinfo *next;
@@ -28,6 +25,11 @@ struct mapinfo {
     char name[1];
 };
 
+static bool verbose = false;
+static bool terse = false;
+static bool addresses = false;
+static bool quiet = false;
+
 static int is_library(const char *name) {
     int len = strlen(name);
     return len >= 4 && name[0] == '/'
@@ -69,7 +71,7 @@ static int parse_header(const char* line, const mapinfo* prev, mapinfo** mi) {
     }
 
     const int name_size = strlen(name) + 1;
-    struct mapinfo* info = calloc(1, sizeof(mapinfo) + name_size);
+    struct mapinfo* info = reinterpret_cast<mapinfo*>(calloc(1, sizeof(mapinfo) + name_size));
     if (info == NULL) {
         fprintf(stderr, "out of memory\n");
         exit(1);
@@ -176,7 +178,7 @@ static mapinfo *load_maps(int pid, int sort_by_address, int coalesce_by_name)
     snprintf(fn, sizeof(fn), "/proc/%d/smaps", pid);
     fp = fopen(fn, "r");
     if (fp == 0) {
-        fprintf(stderr, "cannot open /proc/%d/smaps: %s\n", pid, strerror(errno));
+        if (!quiet) fprintf(stderr, "cannot open /proc/%d/smaps: %s\n", pid, strerror(errno));
         return NULL;
     }
 
@@ -205,28 +207,20 @@ static mapinfo *load_maps(int pid, int sort_by_address, int coalesce_by_name)
     fclose(fp);
 
     if (!head) {
-        fprintf(stderr, "could not read /proc/%d/smaps\n", pid);
+        if (!quiet) fprintf(stderr, "could not read /proc/%d/smaps\n", pid);
         return NULL;
     }
 
     return head;
 }
 
-static int verbose = 0;
-static int terse = 0;
-static int addresses = 0;
-
 static void print_header()
 {
-    if (addresses) {
-        printf("   start      end ");
-    }
-    printf(" virtual                     shared   shared  private  private\n");
+    const char *addr1 = addresses ? "   start      end " : "";
+    const char *addr2 = addresses ? "    addr     addr " : "";
 
-    if (addresses) {
-        printf("    addr     addr ");
-    }
-    printf("    size      RSS      PSS    clean    dirty    clean    dirty    swap ");
+    printf("%s virtual                     shared   shared  private  private\n", addr1);
+    printf("%s    size      RSS      PSS    clean    dirty    clean    dirty     swap ", addr2);
     if (!verbose && !addresses) {
         printf("   # ");
     }
@@ -245,56 +239,56 @@ static void print_divider()
     printf("------------------------------\n");
 }
 
+static void print_mi(mapinfo *mi, bool total)
+{
+    if (addresses) {
+        if (total) {
+            printf("                  ");
+        } else {
+            printf("%08x %08x ", mi->start, mi->end);
+        }
+    }
+    printf("%8d %8d %8d %8d %8d %8d %8d %8d ", mi->size,
+           mi->rss,
+           mi->pss,
+           mi->shared_clean, mi->shared_dirty,
+           mi->private_clean, mi->private_dirty, mi->swap);
+    if (!verbose && !addresses) {
+        printf("%4d ", mi->count);
+    }
+}
+
 static int show_map(int pid)
 {
-    mapinfo *milist;
-    mapinfo *mi;
-    unsigned shared_dirty = 0;
-    unsigned shared_clean = 0;
-    unsigned private_dirty = 0;
-    unsigned private_clean = 0;
-    unsigned swap = 0;
-    unsigned rss = 0;
-    unsigned pss = 0;
-    unsigned size = 0;
-    unsigned count = 0;
-
-    milist = load_maps(pid, addresses, !verbose && !addresses);
+    mapinfo total;
+    memset(&total, 0, sizeof(total));
+
+    mapinfo *milist = load_maps(pid, addresses, !verbose && !addresses);
     if (milist == NULL) {
-        return 1;
+        return quiet ? 0 : 1;
     }
 
     print_header();
     print_divider();
 
-    for (mi = milist; mi;) {
+    for (mapinfo *mi = milist; mi;) {
         mapinfo* last = mi;
 
-        shared_clean += mi->shared_clean;
-        shared_dirty += mi->shared_dirty;
-        private_clean += mi->private_clean;
-        private_dirty += mi->private_dirty;
-        swap += mi->swap;
-        rss += mi->rss;
-        pss += mi->pss;
-        size += mi->size;
-        count += mi->count;
-        
+        total.shared_clean += mi->shared_clean;
+        total.shared_dirty += mi->shared_dirty;
+        total.private_clean += mi->private_clean;
+        total.private_dirty += mi->private_dirty;
+        total.swap += mi->swap;
+        total.rss += mi->rss;
+        total.pss += mi->pss;
+        total.size += mi->size;
+        total.count += mi->count;
+
         if (terse && !mi->private_dirty) {
             goto out;
         }
 
-        if (addresses) {
-            printf("%08x %08x ", mi->start, mi->end);
-        }
-        printf("%8d %8d %8d %8d %8d %8d %8d %8d", mi->size,
-               mi->rss,
-               mi->pss,
-               mi->shared_clean, mi->shared_dirty,
-               mi->private_clean, mi->private_dirty, mi->swap);
-        if (!verbose && !addresses) {
-            printf("%4d ", mi->count);
-        }
+        print_mi(mi, false);
         printf("%s%s\n", mi->name, mi->is_bss ? " [bss]" : "");
 
 out:
@@ -306,16 +300,7 @@ out:
     print_header();
     print_divider();
 
-    if (addresses) {
-        printf("                  ");
-    }
-    printf("%8d %8d %8d %8d %8d %8d %8d %8d", size,
-            rss, pss,
-            shared_clean, shared_dirty,
-            private_clean, private_dirty, swap);
-    if (!verbose && !addresses) {
-        printf("%4d ", count);
-    }
+    print_mi(&total, true);
     printf("TOTAL\n");
 
     return 0;
@@ -333,15 +318,19 @@ int main(int argc, char *argv[])
     for (argc--, argv++; argc > 0; argc--, argv++) {
         arg = argv[0];
         if (!strcmp(arg,"-v")) {
-            verbose = 1;
+            verbose = true;
             continue;
         }
         if (!strcmp(arg,"-t")) {
-            terse = 1;
+            terse = true;
             continue;
         }
         if (!strcmp(arg,"-a")) {
-            addresses = 1;
+            addresses = true;
+            continue;
+        }
+        if (!strcmp(arg,"-q")) {
+            quiet = true;
             continue;
         }
         if (argc != 1) {
@@ -362,10 +351,11 @@ int main(int argc, char *argv[])
 
     if (usage) {
         fprintf(stderr,
-                "showmap [-t] [-v] [-c] <pid>\n"
+                "showmap [-t] [-v] [-c] [-q] <pid>\n"
                 "        -t = terse (show only items with private pages)\n"
                 "        -v = verbose (don't coalesce maps with the same name)\n"
                 "        -a = addresses (show virtual memory map)\n"
+                "        -q = quiet (don't show error if map could not be read)\n"
                 );
         result = 1;
     }
index 9c9e60e..08acbf8 100644 (file)
@@ -243,7 +243,7 @@ BUILD_SORT_FUNC(nr_pages)
  * set_sort_func - return the slab_sort_func that matches the given key.
  * On unrecognizable key, the call returns NULL.
  */
-static void * set_sort_func(char key)
+static sort_t set_sort_func(char key)
 {
        switch (tolower(key)) {
        case 'a':
index f37c4b0..9eb4be8 100644 (file)
 
 LOCAL_PATH := $(call my-dir)
 
-simpleperf_common_cppflags := -std=c++11 -Wall -Wextra -Werror -Wunused
+simpleperf_common_cppflags := -Wextra -Wunused -Wno-unknown-pragmas
 
-simpleperf_common_shared_libraries := \
+simpleperf_cppflags_target := $(simpleperf_common_cppflags)
+
+simpleperf_cppflags_host := $(simpleperf_common_cppflags) \
+                            -DUSE_BIONIC_UAPI_HEADERS -I bionic/libc/kernel \
+
+simpleperf_cppflags_host_darwin := -I $(LOCAL_PATH)/nonlinux_support/include
+simpleperf_cppflags_host_windows := -I $(LOCAL_PATH)/nonlinux_support/include
+
+
+LLVM_ROOT_PATH := external/llvm
+include $(LLVM_ROOT_PATH)/llvm.mk
+
+simpleperf_shared_libraries_target := \
+  libbacktrace \
+  libunwind \
   libbase \
+  liblog \
+  libutils \
   libLLVM \
 
-LLVM_ROOT_PATH := external/llvm
+simpleperf_static_libraries_target := \
+  libbacktrace_offline \
+  liblzma \
+  libziparchive \
+  libz \
+
+simpleperf_static_libraries_host := \
+  libziparchive-host \
+  libbase \
+  liblog \
+  libz \
+  libutils \
+  libLLVMObject \
+  libLLVMBitReader \
+  libLLVMMC \
+  libLLVMMCParser \
+  libLLVMCore \
+  libLLVMSupport \
 
+simpleperf_static_libraries_host_linux := \
+  libbacktrace_offline \
+  libbacktrace \
+  libunwind \
+  libcutils \
+  liblzma \
+
+simpleperf_ldlibs_host_linux := -lrt
+
+# libsimpleperf
+# =========================================================
 libsimpleperf_src_files := \
+  callchain.cpp \
   cmd_dumprecord.cpp \
   cmd_help.cpp \
-  cmd_list.cpp \
-  cmd_record.cpp \
-  cmd_stat.cpp \
+  cmd_report.cpp \
   command.cpp \
-  environment.cpp \
+  dso.cpp \
   event_attr.cpp \
-  event_fd.cpp \
-  event_selection_set.cpp \
   event_type.cpp \
+  perf_regs.cpp \
+  read_apk.cpp \
   read_elf.cpp \
   record.cpp \
-  record_file.cpp \
+  record_file_reader.cpp \
+  sample_tree.cpp \
+  thread_tree.cpp \
   utils.cpp \
+
+libsimpleperf_src_files_linux := \
+  cmd_list.cpp \
+  cmd_record.cpp \
+  cmd_stat.cpp \
+  dwarf_unwind.cpp \
+  environment.cpp \
+  event_fd.cpp \
+  event_selection_set.cpp \
+  record_file_writer.cpp \
   workload.cpp \
 
+libsimpleperf_src_files_darwin := \
+  nonlinux_support/nonlinux_support.cpp \
+
+libsimpleperf_src_files_windows := \
+  nonlinux_support/nonlinux_support.cpp \
+
+# libsimpleperf target
 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
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
+LOCAL_SRC_FILES := \
+  $(libsimpleperf_src_files) \
+  $(libsimpleperf_src_files_linux) \
+
+LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_target)
+LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
+LOCAL_MULTILIB := first
 include $(LLVM_DEVICE_BUILD_MK)
 include $(BUILD_STATIC_LIBRARY)
 
-ifeq ($(HOST_OS),linux)
+# libsimpleperf host
 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_LDLIBS := -lrt
+#LOCAL_CLANG := true  # Comment it to build on windows.
 LOCAL_MODULE := libsimpleperf
-LOCAL_MODULE_TAGS := optional
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-include $(LLVM_ROOT_PATH)/llvm.mk
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
+LOCAL_CPPFLAGS_darwin := $(simpleperf_cppflags_host_darwin)
+LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
+LOCAL_CPPFLAGS_windows := $(simpleperf_cppflags_host_windows)
+LOCAL_SRC_FILES := $(libsimpleperf_src_files)
+LOCAL_SRC_FILES_darwin := $(libsimpleperf_src_files_darwin)
+LOCAL_SRC_FILES_linux := $(libsimpleperf_src_files_linux)
+LOCAL_SRC_FILES_windows := $(libsimpleperf_src_files_windows)
+LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_host)
+LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
+LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
+LOCAL_MULTILIB := first
 include $(LLVM_HOST_BUILD_MK)
 include $(BUILD_HOST_STATIC_LIBRARY)
-endif
 
+
+# simpleperf
+# =========================================================
+
+# simpleperf target
 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
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
+LOCAL_SRC_FILES := main.cpp
+LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_target)
+LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
+LOCAL_MULTILIB := first
 include $(BUILD_EXECUTABLE)
 
-ifeq ($(HOST_OS),linux)
+# simpleperf host
 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_LDLIBS := -lrt
 LOCAL_MODULE := simpleperf
-LOCAL_MODULE_TAGS := optional
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
+LOCAL_CPPFLAGS_darwin := $(simpleperf_cppflags_host_darwin)
+LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
+LOCAL_CPPFLAGS_windows := $(simpleperf_cppflags_host_windows)
+LOCAL_SRC_FILES := main.cpp
+LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_host)
+LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
+LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
+LOCAL_MULTILIB := first
+include $(LLVM_HOST_BUILD_MK)
 include $(BUILD_HOST_EXECUTABLE)
-endif
 
+
+# simpleperf_unit_test
+# =========================================================
 simpleperf_unit_test_src_files := \
+  cmd_report_test.cpp \
+  command_test.cpp \
+  gtest_main.cpp \
+  read_apk_test.cpp \
+  read_elf_test.cpp \
+  record_test.cpp \
+  sample_tree_test.cpp \
+
+simpleperf_unit_test_src_files_linux := \
   cmd_dumprecord_test.cpp \
   cmd_list_test.cpp \
   cmd_record_test.cpp \
   cmd_stat_test.cpp \
-  command_test.cpp \
   environment_test.cpp \
-  gtest_main.cpp \
   record_file_test.cpp \
-  record_test.cpp \
   workload_test.cpp \
 
+# simpleperf_unit_test target
 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
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
+LOCAL_SRC_FILES := \
+  $(simpleperf_unit_test_src_files) \
+  $(simpleperf_unit_test_src_files_linux) \
+
+LOCAL_STATIC_LIBRARIES += libsimpleperf $(simpleperf_static_libraries_target)
+LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
+LOCAL_MULTILIB := first
 include $(BUILD_NATIVE_TEST)
 
-ifeq ($(HOST_OS),linux)
+# simpleperf_unit_test host
 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
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
+LOCAL_CPPFLAGS_darwin := $(simpleperf_cppflags_host_darwin)
+LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
+LOCAL_CPPFLAGS_windows := $(simpleperf_cppflags_host_windows)
+LOCAL_SRC_FILES := $(simpleperf_unit_test_src_files)
+LOCAL_SRC_FILES_linux := $(simpleperf_unit_test_src_files_linux)
+LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_host)
+LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
+LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
+LOCAL_MULTILIB := first
+include $(LLVM_HOST_BUILD_MK)
 include $(BUILD_HOST_NATIVE_TEST)
-endif
+
+
+# simpleperf_cpu_hotplug_test
+# =========================================================
+simpleperf_cpu_hotplug_test_src_files := \
+  cpu_hotplug_test.cpp \
+
+# simpleperf_cpu_hotplug_test target
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_MODULE := simpleperf_cpu_hotplug_test
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
+LOCAL_SRC_FILES := $(simpleperf_cpu_hotplug_test_src_files)
+LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_target)
+LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
+LOCAL_MULTILIB := first
+include $(BUILD_NATIVE_TEST)
+
+# simpleperf_cpu_hotplug_test linux host
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_MODULE := simpleperf_cpu_hotplug_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
+LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
+LOCAL_SRC_FILES := $(simpleperf_cpu_hotplug_test_src_files)
+LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_host)
+LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
+LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
+LOCAL_MULTILIB := first
+include $(LLVM_HOST_BUILD_MK)
+include $(BUILD_HOST_NATIVE_TEST)
+
+
+# libsimpleperf_cts_test
+# =========================================================
+libsimpleperf_cts_test_src_files := \
+  $(libsimpleperf_src_files) \
+  $(libsimpleperf_src_files_linux) \
+  $(simpleperf_unit_test_src_files) \
+  $(simpleperf_unit_test_src_files_linux) \
+
+# libsimpleperf_cts_test target
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_MODULE := libsimpleperf_cts_test
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_target) -DIN_CTS_TEST
+LOCAL_SRC_FILES := $(libsimpleperf_cts_test_src_files)
+LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_target)
+LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
+LOCAL_MULTILIB := both
+include $(LLVM_DEVICE_BUILD_MK)
+include $(BUILD_STATIC_TEST_LIBRARY)
+
+# libsimpleperf_cts_test linux host
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_MODULE := libsimpleperf_cts_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host) -DIN_CTS_TEST
+LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
+LOCAL_SRC_FILES := $(libsimpleperf_cts_test_src_files)
+LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_host)
+LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
+LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
+LOCAL_MULTILIB := both
+include $(LLVM_HOST_BUILD_MK)
+include $(BUILD_HOST_STATIC_TEST_LIBRARY)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/simpleperf/NOTICE b/simpleperf/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index 5a4b12c..bbd13c4 100644 (file)
 #ifndef SIMPLE_PERF_BUILD_ID_H_
 #define SIMPLE_PERF_BUILD_ID_H_
 
-#include <array>
+#include <string.h>
+#include <algorithm>
+#include <android-base/stringprintf.h>
 
-static constexpr int BUILD_ID_SIZE = 20;
+constexpr size_t BUILD_ID_SIZE = 20;
 
-typedef std::array<unsigned char, BUILD_ID_SIZE> BuildId;
+class BuildId {
+ public:
+  static size_t Size() {
+    return BUILD_ID_SIZE;
+  }
+
+  BuildId() {
+    memset(data_, '\0', BUILD_ID_SIZE);
+  }
+
+  // Copy build id from a byte array, like {0x76, 0x00, 0x32,...}.
+  BuildId(const void* data, size_t len) : BuildId() {
+    memcpy(data_, data, std::min(len, BUILD_ID_SIZE));
+  }
+
+  // Read build id from a hex string, like "7600329e31058e12b145d153ef27cd40e1a5f7b9".
+  explicit BuildId(const std::string& s) : BuildId() {
+    for (size_t i = 0; i < s.size() && i < BUILD_ID_SIZE * 2; i += 2) {
+      unsigned char ch = 0;
+      for (size_t j = i; j < i + 2; ++j) {
+        ch <<= 4;
+        if (s[j] >= '0' && s[j] <= '9') {
+          ch |= s[j] - '0';
+        } else if (s[j] >= 'a' && s[j] <= 'f') {
+          ch |= s[j] - 'a' + 10;
+        } else if (s[j] >= 'A' && s[j] <= 'F') {
+          ch |= s[j] - 'A' + 10;
+        }
+      }
+      data_[i / 2] = ch;
+    }
+  }
+
+  const unsigned char* Data() const {
+    return data_;
+  }
+
+  std::string ToString() const {
+    std::string s = "0x";
+    for (size_t i = 0; i < BUILD_ID_SIZE; ++i) {
+      s += android::base::StringPrintf("%02x", data_[i]);
+    }
+    return s;
+  }
+
+  bool operator==(const BuildId& build_id) const {
+    return memcmp(data_, build_id.data_, BUILD_ID_SIZE) == 0;
+  }
+
+  bool operator!=(const BuildId& build_id) const {
+    return !(*this == build_id);
+  }
+
+  bool IsEmpty() const {
+    static BuildId empty_build_id;
+    return *this == empty_build_id;
+  }
+
+ private:
+  unsigned char data_[BUILD_ID_SIZE];
+};
 
 #endif  // SIMPLE_PERF_BUILD_ID_H_
diff --git a/simpleperf/callchain.cpp b/simpleperf/callchain.cpp
new file mode 100644 (file)
index 0000000..8fdafc4
--- /dev/null
@@ -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 "callchain.h"
+
+#include <string.h>
+
+#include <queue>
+
+#include <android-base/logging.h>
+#include "sample_tree.h"
+
+static bool MatchSampleByName(const SampleEntry* sample1, const SampleEntry* sample2) {
+  return strcmp(sample1->symbol->Name(), sample2->symbol->Name()) == 0;
+}
+
+static size_t GetMatchingLengthInNode(const CallChainNode* node,
+                                      const std::vector<SampleEntry*>& chain, size_t chain_start) {
+  size_t i, j;
+  for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size(); ++i, ++j) {
+    if (!MatchSampleByName(node->chain[i], chain[j])) {
+      break;
+    }
+  }
+  return i;
+}
+
+static CallChainNode* FindMatchingNode(const std::vector<std::unique_ptr<CallChainNode>>& nodes,
+                                       const SampleEntry* sample) {
+  for (auto& node : nodes) {
+    if (MatchSampleByName(node->chain.front(), sample)) {
+      return node.get();
+    }
+  }
+  return nullptr;
+}
+
+static std::unique_ptr<CallChainNode> AllocateNode(const std::vector<SampleEntry*>& chain,
+                                                   size_t chain_start, uint64_t period,
+                                                   uint64_t children_period) {
+  std::unique_ptr<CallChainNode> node(new CallChainNode);
+  for (size_t i = chain_start; i < chain.size(); ++i) {
+    node->chain.push_back(chain[i]);
+  }
+  node->period = period;
+  node->children_period = children_period;
+  return node;
+}
+
+static void SplitNode(CallChainNode* parent, size_t parent_length) {
+  std::unique_ptr<CallChainNode> child =
+      AllocateNode(parent->chain, parent_length, parent->period, parent->children_period);
+  child->children = std::move(parent->children);
+  parent->period = 0;
+  parent->children_period = child->period + child->children_period;
+  parent->chain.resize(parent_length);
+  parent->children.clear();
+  parent->children.push_back(std::move(child));
+}
+
+void CallChainRoot::AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period) {
+  children_period += period;
+  CallChainNode* p = FindMatchingNode(children, callchain[0]);
+  if (p == nullptr) {
+    std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, 0, period, 0);
+    children.push_back(std::move(new_node));
+    return;
+  }
+  size_t callchain_pos = 0;
+  while (true) {
+    size_t match_length = GetMatchingLengthInNode(p, callchain, callchain_pos);
+    CHECK_GT(match_length, 0u);
+    callchain_pos += match_length;
+    bool find_child = true;
+    if (match_length < p->chain.size()) {
+      SplitNode(p, match_length);
+      find_child = false;  // No need to find matching node in p->children.
+    }
+    if (callchain_pos == callchain.size()) {
+      p->period += period;
+      return;
+    }
+    p->children_period += period;
+    if (find_child) {
+      CallChainNode* np = FindMatchingNode(p->children, callchain[callchain_pos]);
+      if (np != nullptr) {
+        p = np;
+        continue;
+      }
+    }
+    std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, callchain_pos, period, 0);
+    p->children.push_back(std::move(new_node));
+    break;
+  }
+}
+
+static bool CompareNodeByPeriod(const std::unique_ptr<CallChainNode>& n1,
+                                const std::unique_ptr<CallChainNode>& n2) {
+  uint64_t period1 = n1->period + n1->children_period;
+  uint64_t period2 = n2->period + n2->children_period;
+  return period1 > period2;
+}
+
+void CallChainRoot::SortByPeriod() {
+  std::queue<std::vector<std::unique_ptr<CallChainNode>>*> queue;
+  queue.push(&children);
+  while (!queue.empty()) {
+    std::vector<std::unique_ptr<CallChainNode>>* v = queue.front();
+    queue.pop();
+    std::sort(v->begin(), v->end(), CompareNodeByPeriod);
+    for (auto& node : *v) {
+      queue.push(&node->children);
+    }
+  }
+}
diff --git a/simpleperf/callchain.h b/simpleperf/callchain.h
new file mode 100644 (file)
index 0000000..4b8a9d4
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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_CALLCHAIN_H_
+#define SIMPLE_PERF_CALLCHAIN_H_
+
+#include <memory>
+#include <vector>
+
+struct SampleEntry;
+
+struct CallChainNode {
+  uint64_t period;
+  uint64_t children_period;
+  std::vector<SampleEntry*> chain;
+  std::vector<std::unique_ptr<CallChainNode>> children;
+};
+
+struct CallChainRoot {
+  uint64_t children_period;
+  std::vector<std::unique_ptr<CallChainNode>> children;
+
+  CallChainRoot() : children_period(0) {
+  }
+
+  void AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period);
+  void SortByPeriod();
+};
+
+#endif  // SIMPLE_PERF_CALLCHAIN_H_
index 57eec1f..f19cfd3 100644 (file)
 #include <string>
 #include <vector>
 
-#include <base/logging.h>
-#include <base/stringprintf.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 
 #include "command.h"
 #include "event_attr.h"
+#include "perf_regs.h"
 #include "record.h"
 #include "record_file.h"
+#include "utils.h"
 
 using namespace PerfFileFormat;
 
-class DumpRecordCommandImpl {
+class DumpRecordCommand : public Command {
  public:
-  DumpRecordCommandImpl() : record_filename_("perf.data") {
+  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"), record_file_arch_(GetBuildArch()) {
   }
 
   bool Run(const std::vector<std::string>& args);
@@ -46,11 +53,10 @@ class DumpRecordCommandImpl {
 
   std::string record_filename_;
   std::unique_ptr<RecordFileReader> record_file_reader_;
-
-  std::vector<int> features_;
+  ArchType record_file_arch_;
 };
 
-bool DumpRecordCommandImpl::Run(const std::vector<std::string>& args) {
+bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
   if (!ParseOptions(args)) {
     return false;
   }
@@ -58,6 +64,14 @@ bool DumpRecordCommandImpl::Run(const std::vector<std::string>& args) {
   if (record_file_reader_ == nullptr) {
     return false;
   }
+  std::string arch = record_file_reader_->ReadFeatureString(FEAT_ARCH);
+  if (!arch.empty()) {
+    record_file_arch_ = GetArchType(arch);
+    if (record_file_arch_ == ARCH_UNSUPPORTED) {
+      return false;
+    }
+  }
+  ScopedCurrentArch scoped_arch(record_file_arch_);
   DumpFileHeader();
   DumpAttrSection();
   DumpDataSection();
@@ -66,47 +80,51 @@ bool DumpRecordCommandImpl::Run(const std::vector<std::string>& args) {
   return true;
 }
 
-bool DumpRecordCommandImpl::ParseOptions(const std::vector<std::string>& args) {
-  if (args.size() == 2) {
-    record_filename_ = args[1];
+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 DumpRecordCommandImpl::DumpFileHeader() {
-  const FileHeader* header = record_file_reader_->FileHeader();
+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("%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("header_size: %" PRId64 "\n", header.header_size);
+  if (header.header_size != sizeof(header)) {
+    PLOG(WARNING) << "record file header size " << header.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("attr_size: %" PRId64 "\n", header.attr_size);
+  if (header.attr_size != sizeof(FileAttr)) {
+    PLOG(WARNING) << "record file attr size " << header.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("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);
+         header.event_types.offset, header.event_types.size);
 
-  features_.clear();
+  std::vector<int> features;
   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);
+    if ((header.features[j] & (1 << k)) != 0) {
+      features.push_back(i);
     }
   }
-  for (auto& feature : features_) {
+  for (auto& feature : features) {
     printf("feature: %s\n", GetFeatureName(feature).c_str());
   }
 }
@@ -127,7 +145,7 @@ static const std::string GetFeatureName(int feature) {
       {FEAT_EVENT_DESC, "event_desc"},
       {FEAT_CPU_TOPOLOGY, "cpu_topology"},
       {FEAT_NUMA_TOPOLOGY, "numa_topology"},
-      {FEAT_BRANCH_STACK, "branck_stack"},
+      {FEAT_BRANCH_STACK, "branch_stack"},
       {FEAT_PMU_MAPPINGS, "pmu_mappings"},
       {FEAT_GROUP_DESC, "group_desc"},
   };
@@ -138,18 +156,21 @@ static const std::string GetFeatureName(int feature) {
   return android::base::StringPrintf("unknown_feature(%d)", feature);
 }
 
-void DumpRecordCommandImpl::DumpAttrSection() {
-  std::vector<const FileAttr*> attrs = record_file_reader_->AttrSection();
+void DumpRecordCommand::DumpAttrSection() {
+  const std::vector<FileAttr>& attrs = record_file_reader_->AttrSection();
   for (size_t i = 0; i < attrs.size(); ++i) {
-    auto& attr = attrs[i];
+    const 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) {
+    DumpPerfEventAttr(attr.attr, 1);
+    printf("  ids[file_section]: offset %" PRId64 ", size %" PRId64 "\n", attr.ids.offset,
+           attr.ids.size);
+    std::vector<uint64_t> ids;
+    if (!record_file_reader_->ReadIdsForAttr(attr, &ids)) {
+      return;
+    }
+    if (!ids.empty()) {
       printf("  ids:");
-      for (auto& id : ids) {
+      for (const auto& id : ids) {
         printf(" %" PRId64, id);
       }
       printf("\n");
@@ -157,48 +178,38 @@ void DumpRecordCommandImpl::DumpAttrSection() {
   }
 }
 
-void DumpRecordCommandImpl::DumpDataSection() {
-  std::vector<std::unique_ptr<const Record>> records = record_file_reader_->DataSection();
-  for (auto& record : records) {
+void DumpRecordCommand::DumpDataSection() {
+  record_file_reader_->ReadDataSection([](std::unique_ptr<Record> record) {
     record->Dump();
-  }
+    return true;
+  }, false);
 }
 
-void DumpRecordCommandImpl::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];
+void DumpRecordCommand::DumpFeatureSection() {
+  std::map<int, SectionDesc> section_map = record_file_reader_->FeatureSectionDescriptors();
+  for (const auto& pair : section_map) {
+    int feature = pair.first;
+    const auto& section = pair.second;
     printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n",
            GetFeatureName(feature).c_str(), section.offset, section.size);
     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);
-        CHECK_EQ(PERF_RECORD_BUILD_ID, header->type);
-        BuildIdRecord record(header);
-        record.Dump(1);
-        p += header->size;
+      std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature();
+      for (auto& r : records) {
+        r.Dump(1);
       }
+    } else if (feature == FEAT_OSRELEASE) {
+      std::string s = record_file_reader_->ReadFeatureString(feature);
+      PrintIndented(1, "osrelease: %s\n", s.c_str());
+    } else if (feature == FEAT_ARCH) {
+      std::string s = record_file_reader_->ReadFeatureString(feature);
+      PrintIndented(1, "arch: %s\n", s.c_str());
+    } else if (feature == FEAT_CMDLINE) {
+      std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
+      PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str());
     }
   }
 }
 
-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") {
-  }
-
-  bool Run(const std::vector<std::string>& args) override {
-    DumpRecordCommandImpl impl;
-    return impl.Run(args);
-  }
-};
-
-DumpRecordCommand dumprecord_cmd;
+void RegisterDumpRecordCommand() {
+  RegisterCommand("dump", [] { return std::unique_ptr<Command>(new DumpRecordCommand); });
+}
index c470833..441851f 100644 (file)
 #include <gtest/gtest.h>
 
 #include "command.h"
+#include "get_test_data.h"
 
-class DumpRecordCommandTest : public ::testing::Test {
- protected:
-  virtual void SetUp() {
-    record_cmd = Command::FindCommandByName("record");
-    ASSERT_TRUE(record_cmd != nullptr);
-    dumprecord_cmd = Command::FindCommandByName("dump");
-    ASSERT_TRUE(dumprecord_cmd != nullptr);
-  }
-
-  Command* record_cmd;
-  Command* dumprecord_cmd;
-};
-
-TEST_F(DumpRecordCommandTest, no_options) {
-  ASSERT_TRUE(record_cmd->Run({"record", "-a", "sleep", "1"}));
-  ASSERT_TRUE(dumprecord_cmd->Run({"dump"}));
+static std::unique_ptr<Command> DumpCmd() {
+  return CreateCommandInstance("dump");
 }
 
-TEST_F(DumpRecordCommandTest, record_file_option) {
-  ASSERT_TRUE(record_cmd->Run({"record", "-a", "-o", "perf2.data", "sleep", "1"}));
-  ASSERT_TRUE(dumprecord_cmd->Run({"dump", "perf2.data"}));
+TEST(cmd_dump, record_file_option) {
+  ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf.data")}));
 }
index 0f3839b..0bb6231 100644 (file)
@@ -18,7 +18,7 @@
 #include <string>
 #include <vector>
 
-#include <base/logging.h>
+#include <android-base/logging.h>
 
 #include "command.h"
 
@@ -39,10 +39,10 @@ class HelpCommand : public Command {
 };
 
 bool HelpCommand::Run(const std::vector<std::string>& args) {
-  if (args.size() == 1) {
+  if (args.empty()) {
     PrintShortHelp();
   } else {
-    Command* cmd = Command::FindCommandByName(args[1]);
+    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\"";
@@ -55,9 +55,17 @@ bool HelpCommand::Run(const std::vector<std::string>& args) {
 }
 
 void HelpCommand::PrintShortHelp() {
-  printf("Usage: simpleperf [--help] subcommand [args_for_subcommand]\n\n");
-  for (auto& command : Command::GetAllCommands()) {
-    printf("%-20s%s\n", command->Name().c_str(), command->ShortHelpString().c_str());
+  printf(
+      "Usage: simpleperf [common options] subcommand [args_for_subcommand]\n"
+      "common options:\n"
+      "    -h/--help     Print this help information.\n"
+      "    --log <severity> Set the minimum severity of logging. Possible severities\n"
+      "                     include verbose, debug, warning, error, fatal. Default is\n"
+      "                     warning.\n"
+      "subcommands:\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());
   }
 }
 
@@ -65,4 +73,6 @@ void HelpCommand::PrintLongHelpForOneCommand(const Command& command) {
   printf("%s\n", command.LongHelpString().c_str());
 }
 
-HelpCommand help_command;
+void RegisterHelpCommand() {
+  RegisterCommand("help", [] { return std::unique_ptr<Command>(new HelpCommand); });
+}
index 923a884..273a803 100644 (file)
  */
 
 #include <stdio.h>
+#include <map>
 #include <string>
 #include <vector>
 
-#include <base/logging.h>
+#include <android-base/logging.h>
 
 #include "command.h"
+#include "environment.h"
+#include "event_attr.h"
+#include "event_fd.h"
 #include "event_type.h"
-#include "perf_event.h"
 
-static void PrintEventTypesOfType(uint32_t type, const char* type_name,
-                                  const std::vector<const EventType>& event_types) {
-  printf("List of %s:\n", type_name);
+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);
+    if (event_type.type == type) {
+      perf_event_attr attr = CreateDefaultPerfEventAttr(event_type);
+      // Exclude kernel to list supported events even when
+      // /proc/sys/kernel/perf_event_paranoid is 2.
+      attr.exclude_kernel = 1;
+      if (IsEventAttrSupportedByKernel(attr)) {
+        printf("  %s\n", event_type.name.c_str());
+      }
     }
   }
   printf("\n");
@@ -38,8 +47,8 @@ static void PrintEventTypesOfType(uint32_t type, const char* type_name,
 class ListCommand : public Command {
  public:
   ListCommand()
-      : Command("list", "list all available perf events",
-                "Usage: simpleperf list\n"
+      : Command("list", "list available event types",
+                "Usage: simpleperf list [hw|sw|cache|tracepoint]\n"
                 "    List all available perf events on this machine.\n") {
   }
 
@@ -47,17 +56,42 @@ class ListCommand : public Command {
 };
 
 bool ListCommand::Run(const std::vector<std::string>& args) {
-  if (args.size() != 1) {
-    LOG(ERROR) << "malformed command line: list subcommand needs no argument";
-    LOG(ERROR) << "try using \"help list\"";
+  if (!CheckPerfEventLimit()) {
     return false;
   }
-  auto& event_types = EventTypeFactory::GetAllEventTypes();
 
-  PrintEventTypesOfType(PERF_TYPE_HARDWARE, "hardware events", event_types);
-  PrintEventTypesOfType(PERF_TYPE_SOFTWARE, "software events", event_types);
-  PrintEventTypesOfType(PERF_TYPE_HW_CACHE, "hw-cache events", event_types);
+  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;
 }
 
-ListCommand list_command;
+void RegisterListCommand() {
+  RegisterCommand("list", [] { return std::unique_ptr<Command>(new ListCommand); });
+}
index 4b873a1..2bc6421 100644 (file)
 
 #include "command.h"
 
-TEST(cmd_list, smoke) {
-  Command* list_cmd = Command::FindCommandByName("list");
-  ASSERT_TRUE(list_cmd != nullptr);
-  ASSERT_TRUE(list_cmd->Run({"list"}));
+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"}));
 }
index 98a0cd5..ae025b0 100644 (file)
 #include <libgen.h>
 #include <poll.h>
 #include <signal.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <set>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
-#include <base/logging.h>
-#include <base/strings.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 
 #include "command.h"
+#include "dwarf_unwind.h"
 #include "environment.h"
 #include "event_selection_set.h"
 #include "event_type.h"
+#include "read_apk.h"
 #include "read_elf.h"
 #include "record.h"
 #include "record_file.h"
+#include "scoped_signal_handler.h"
+#include "thread_tree.h"
 #include "utils.h"
 #include "workload.h"
 
 static std::string default_measured_event_type = "cpu-cycles";
 
-class RecordCommandImpl {
+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;
+}
+
+// Used in cpu-hotplug test.
+bool system_wide_perf_event_open_failed = false;
+
+class RecordCommand : public Command {
  public:
-  RecordCommandImpl()
-      : use_sample_freq_(true),
-        sample_freq_(1000),
+  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"
+            "    --call-graph fp | dwarf[,<dump_stack_size>]\n"
+            "                 Enable call graph recording. Use frame pointer or dwarf as the\n"
+            "                 method to parse call graph in stack. Default is dwarf,8192.\n"
+            "    --cpu cpu_item1,cpu_item2,...\n"
+            "                 Collect samples only on the selected cpus. cpu_item can be cpu\n"
+            "                 number like 1, or cpu range like 0-3.\n"
+            "    -e event1[:modifier1],event2[:modifier2],...\n"
+            "                 Select the event list 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           Same as '--call-graph dwarf'.\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"
+            "    -m mmap_pages\n"
+            "                 Set the size of the buffer used to receiving sample data from\n"
+            "                 the kernel. It should be a power of 2. The default value is 16.\n"
+            "    --no-inherit\n"
+            "                 Don't record created child threads/processes.\n"
+            "    --no-unwind  If `--call-graph dwarf` option is used, then the user's stack will\n"
+            "                 be unwound by default. Use this option to disable the unwinding of\n"
+            "                 the user's stack.\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"
+            "    --post-unwind\n"
+            "                 If `--call-graph dwarf` option is used, then the user's stack will\n"
+            "                 be unwound while recording by default. But it may lose records as\n"
+            "                 stacking unwinding can be time consuming. Use this option to unwind\n"
+            "                 the user's stack after recording.\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),
-        measured_event_type_(nullptr),
-        perf_mmap_pages_(256),
-        record_filename_("perf.data") {
-    // We need signal SIGCHLD to break poll().
-    saved_sigchild_handler_ = signal(SIGCHLD, [](int) {});
-  }
-
-  ~RecordCommandImpl() {
-    signal(SIGCHLD, saved_sigchild_handler_);
+        branch_sampling_(0),
+        fp_callchain_sampling_(false),
+        dwarf_callchain_sampling_(false),
+        dump_stack_size_in_dwarf_sampling_(8192),
+        unwind_dwarf_callchain_(true),
+        post_unwind_(false),
+        child_inherit_(true),
+        perf_mmap_pages_(16),
+        record_filename_("perf.data"),
+        sample_record_count_(0) {
+    signaled = false;
+    scoped_signal_handler_.reset(
+        new ScopedSignalHandler({SIGCHLD, SIGINT, SIGTERM}, signal_handler));
   }
 
   bool Run(const std::vector<std::string>& args);
@@ -58,63 +139,100 @@ class RecordCommandImpl {
 
  private:
   bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
-  bool SetMeasuredEventType(const std::string& event_type_name);
-  void SetEventSelection();
-  bool WriteData(const char* data, size_t size);
+  bool AddMeasuredEventType(const std::string& event_type_name);
+  bool SetEventSelection();
+  bool CreateAndInitRecordFile();
+  std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename);
   bool DumpKernelAndModuleMmaps();
-  bool DumpThreadCommAndMmaps();
-  bool DumpAdditionalFeatures();
+  bool DumpThreadCommAndMmaps(bool all_threads, const std::vector<pid_t>& selected_threads);
+  bool CollectRecordsFromKernel(const char* data, size_t size);
+  bool ProcessRecord(Record* record);
+  void UpdateRecordForEmbeddedElfPath(Record* record);
+  void UnwindRecord(Record* record);
+  bool PostUnwind(const std::vector<std::string>& args);
+  bool DumpAdditionalFeatures(const std::vector<std::string>& args);
   bool DumpBuildIdFeature();
+  void CollectHitFileInfo(Record* record);
+  std::pair<std::string, uint64_t> TestForEmbeddedElf(Dso *dso, uint64_t pgoff);
 
   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_;
-  const EventType* measured_event_type_;
+  uint64_t branch_sampling_;
+  bool fp_callchain_sampling_;
+  bool dwarf_callchain_sampling_;
+  uint32_t dump_stack_size_in_dwarf_sampling_;
+  bool unwind_dwarf_callchain_;
+  bool post_unwind_;
+  bool child_inherit_;
+  std::vector<pid_t> monitored_threads_;
+  std::vector<int> cpus_;
+  std::vector<EventTypeAndModifier> measured_event_types_;
   EventSelectionSet event_selection_set_;
 
-  // mmap pages used by each perf event file, should be power of 2.
-  const size_t perf_mmap_pages_;
+  // mmap pages used by each perf event file, should be power of 2.
+  size_t perf_mmap_pages_;
 
+  std::unique_ptr<RecordCache> record_cache_;
+  ThreadTree thread_tree_;
   std::string record_filename_;
   std::unique_ptr<RecordFileWriter> record_file_writer_;
 
-  sighandler_t saved_sigchild_handler_;
+  std::set<std::string> hit_kernel_modules_;
+  std::set<std::string> hit_user_files_;
+
+  std::unique_ptr<ScopedSignalHandler> scoped_signal_handler_;
+  uint64_t sample_record_count_;
 };
 
-bool RecordCommandImpl::Run(const std::vector<std::string>& args) {
+bool RecordCommand::Run(const std::vector<std::string>& args) {
+  if (!CheckPerfEventLimit()) {
+    return false;
+  }
+
   // 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_ == nullptr) {
-    if (!SetMeasuredEventType(default_measured_event_type)) {
+  if (measured_event_types_.empty()) {
+    if (!AddMeasuredEventType(default_measured_event_type)) {
       return false;
     }
   }
-  SetEventSelection();
+  if (!SetEventSelection()) {
+    return false;
+  }
 
   // 2. Create workload.
-  if (workload_args.empty()) {
-    // TODO: change default workload to sleep 99999, and run record until Ctrl-C.
-    workload_args = std::vector<std::string>({"sleep", "1"});
+  std::unique_ptr<Workload> workload;
+  if (!workload_args.empty()) {
+    workload = Workload::CreateWorkload(workload_args);
+    if (workload == nullptr) {
+      return false;
+    }
   }
-  std::unique_ptr<Workload> 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()) {
+    if (!event_selection_set_.OpenEventFilesForCpus(cpus_)) {
+      system_wide_perf_event_open_failed = true;
       return false;
     }
   } else {
-    event_selection_set_.EnableOnExec();
-    if (!event_selection_set_.OpenEventFilesForProcess(workload->GetPid())) {
+    if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) {
       return false;
     }
   }
@@ -124,60 +242,62 @@ bool RecordCommandImpl::Run(const std::vector<std::string>& args) {
   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_),
-      event_selection_set_.FindEventFdsByType(*measured_event_type_));
-  if (record_file_writer_ == nullptr) {
-    return false;
-  }
-  if (!DumpKernelAndModuleMmaps()) {
-    return false;
-  }
-  if (system_wide_collection_ && !DumpThreadCommAndMmaps()) {
+  // 4. Create perf.data.
+  if (!CreateAndInitRecordFile()) {
     return false;
   }
 
   // 5. Write records in mmap buffers of perf_event_files to output file while workload is running.
-
-  // If monitoring only one process, we use the enable_on_exec flag, and don't need to start
-  // recording manually.
-  if (system_wide_collection_) {
-    if (!event_selection_set_.EnableEvents()) {
-      return false;
-    }
-  }
-  if (!workload->Start()) {
+  if (workload != nullptr && !workload->Start()) {
     return false;
   }
-  auto callback =
-      std::bind(&RecordCommandImpl::WriteData, this, std::placeholders::_1, std::placeholders::_2);
+  record_cache_.reset(
+      new RecordCache(*event_selection_set_.FindEventAttrByType(measured_event_types_[0])));
+  auto callback = std::bind(&RecordCommand::CollectRecordsFromKernel, this, std::placeholders::_1,
+                            std::placeholders::_2);
   while (true) {
     if (!event_selection_set_.ReadMmapEventData(callback)) {
       return false;
     }
-    if (workload->IsFinished()) {
+    if (signaled) {
       break;
     }
     poll(&pollfds[0], pollfds.size(), -1);
   }
+  std::vector<std::unique_ptr<Record>> records = record_cache_->PopAll();
+  for (auto& r : records) {
+    if (!ProcessRecord(r.get())) {
+      return false;
+    }
+  }
 
   // 6. Dump additional features, and close record file.
-  if (!DumpAdditionalFeatures()) {
+  if (!DumpAdditionalFeatures(args)) {
     return false;
   }
   if (!record_file_writer_->Close()) {
     return false;
   }
+
+  // 7. Unwind dwarf callchain.
+  if (post_unwind_) {
+    if (!PostUnwind(args)) {
+      return false;
+    }
+  }
+  LOG(VERBOSE) << "Record " << sample_record_count_ << " samples.";
   return true;
 }
 
-bool RecordCommandImpl::ParseOptions(const std::vector<std::string>& args,
-                                     std::vector<std::string>* non_option_args) {
+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 = 1; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++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;
@@ -189,13 +309,49 @@ bool RecordCommandImpl::ParseOptions(const std::vector<std::string>& args,
         return false;
       }
       use_sample_freq_ = false;
-    } else if (args[i] == "-e") {
+    } else if (args[i] == "--call-graph") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      if (!SetMeasuredEventType(args[i])) {
+      std::vector<std::string> strs = android::base::Split(args[i], ",");
+      if (strs[0] == "fp") {
+        fp_callchain_sampling_ = true;
+        dwarf_callchain_sampling_ = false;
+      } else if (strs[0] == "dwarf") {
+        fp_callchain_sampling_ = false;
+        dwarf_callchain_sampling_ = true;
+        if (strs.size() > 1) {
+          char* endptr;
+          uint64_t size = strtoull(strs[1].c_str(), &endptr, 0);
+          if (*endptr != '\0' || size > UINT_MAX) {
+            LOG(ERROR) << "invalid dump stack size in --call-graph option: " << strs[1];
+            return false;
+          }
+          if ((size & 7) != 0) {
+            LOG(ERROR) << "dump stack size " << size << " is not 8-byte aligned.";
+            return false;
+          }
+          dump_stack_size_in_dwarf_sampling_ = static_cast<uint32_t>(size);
+        }
+      } else {
+        LOG(ERROR) << "unexpected argument for --call-graph option: " << args[i];
+        return false;
+      }
+    } else if (args[i] == "--cpu") {
+      if (!NextArgumentOrError(args, &i)) {
         return false;
       }
+      cpus_ = GetCpusFromString(args[i]);
+    } 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] == "-f" || args[i] == "-F") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -207,18 +363,89 @@ bool RecordCommandImpl::ParseOptions(const std::vector<std::string>& args,
         return false;
       }
       use_sample_freq_ = true;
+    } else if (args[i] == "-g") {
+      fp_callchain_sampling_ = false;
+      dwarf_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] == "-m") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      char* endptr;
+      uint64_t pages = strtoull(args[i].c_str(), &endptr, 0);
+      if (*endptr != '\0' || !IsPowerOfTwo(pages)) {
+        LOG(ERROR) << "Invalid mmap_pages: '" << args[i] << "'";
+        return false;
+      }
+      perf_mmap_pages_ = pages;
+    } else if (args[i] == "--no-inherit") {
+      child_inherit_ = false;
+    } else if (args[i] == "--no-unwind") {
+      unwind_dwarf_callchain_ = 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] == "--post-unwind") {
+      post_unwind_ = true;
+    } else if (args[i] == "-t") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!GetValidThreadsFromThreadString(args[i], &tid_set)) {
+        return false;
+      }
     } else {
-      LOG(ERROR) << "Unknown option for record command: '" << args[i] << "'\n";
-      LOG(ERROR) << "Try `simpleperf help record`";
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+
+  if (!dwarf_callchain_sampling_) {
+    if (!unwind_dwarf_callchain_) {
+      LOG(ERROR) << "--no-unwind is only used with `--call-graph dwarf` option.";
+      return false;
+    }
+    unwind_dwarf_callchain_ = false;
+  }
+  if (post_unwind_) {
+    if (!dwarf_callchain_sampling_) {
+      LOG(ERROR) << "--post-unwind is only used with `--call-graph dwarf` option.";
+      return false;
+    }
+    if (!unwind_dwarf_callchain_) {
+      LOG(ERROR) << "--post-unwind can't be used with `--no-unwind` option.";
       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) {
@@ -228,106 +455,336 @@ bool RecordCommandImpl::ParseOptions(const std::vector<std::string>& args,
   return true;
 }
 
-bool RecordCommandImpl::SetMeasuredEventType(const std::string& event_type_name) {
-  const EventType* event_type = EventTypeFactory::FindEventTypeByName(event_type_name);
-  if (event_type == nullptr) {
+bool RecordCommand::AddMeasuredEventType(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_ = event_type;
+  measured_event_types_.push_back(*event_type_modifier);
   return true;
 }
 
-void RecordCommandImpl::SetEventSelection() {
-  event_selection_set_.AddEventType(*measured_event_type_);
+bool RecordCommand::SetEventSelection() {
+  for (auto& event_type : measured_event_types_) {
+    if (!event_selection_set_.AddEventType(event_type)) {
+      return false;
+    }
+  }
   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 (fp_callchain_sampling_) {
+    event_selection_set_.EnableFpCallChainSampling();
+  } else if (dwarf_callchain_sampling_) {
+    if (!event_selection_set_.EnableDwarfCallChainSampling(dump_stack_size_in_dwarf_sampling_)) {
+      return false;
+    }
+  }
+  event_selection_set_.SetInherit(child_inherit_);
+  return true;
 }
 
-bool RecordCommandImpl::WriteData(const char* data, size_t size) {
-  return record_file_writer_->WriteData(data, size);
+bool RecordCommand::CreateAndInitRecordFile() {
+  record_file_writer_ = CreateRecordFile(record_filename_);
+  if (record_file_writer_ == nullptr) {
+    return false;
+  }
+  if (!DumpKernelAndModuleMmaps()) {
+    return false;
+  }
+  if (!DumpThreadCommAndMmaps(system_wide_collection_, monitored_threads_)) {
+    return false;
+  }
+  return true;
 }
 
-bool RecordCommandImpl::DumpKernelAndModuleMmaps() {
-  KernelMmap kernel_mmap;
-  std::vector<ModuleMmap> module_mmaps;
-  if (!GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps)) {
-    return false;
+std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename) {
+  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
+  if (writer == nullptr) {
+    return nullptr;
+  }
+
+  std::vector<AttrWithId> attr_ids;
+  for (auto& event_type : measured_event_types_) {
+    AttrWithId attr_id;
+    attr_id.attr = event_selection_set_.FindEventAttrByType(event_type);
+    CHECK(attr_id.attr != nullptr);
+    const std::vector<std::unique_ptr<EventFd>>* fds =
+        event_selection_set_.FindEventFdsByType(event_type);
+    CHECK(fds != nullptr);
+    for (auto& fd : *fds) {
+      attr_id.ids.push_back(fd->Id());
+    }
+    attr_ids.push_back(attr_id);
+  }
+  if (!writer->WriteAttrSection(attr_ids)) {
+    return nullptr;
   }
-  const perf_event_attr& attr = event_selection_set_.FindEventAttrByType(*measured_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 writer;
+}
+
+bool RecordCommand::DumpKernelAndModuleMmaps() {
+  KernelMmap kernel_mmap;
+  std::vector<KernelMmap> module_mmaps;
+  GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps);
+
+  const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]);
+  CHECK(attr != nullptr);
+  MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
+                                            kernel_mmap.len, 0, kernel_mmap.filepath);
+  if (!ProcessRecord(&mmap_record)) {
     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())) {
+    MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, module_mmap.start_addr,
+                                              module_mmap.len, 0, module_mmap.filepath);
+    if (!ProcessRecord(&mmap_record)) {
       return false;
     }
   }
   return true;
 }
 
-bool RecordCommandImpl::DumpThreadCommAndMmaps() {
+bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads,
+                                           const std::vector<pid_t>& selected_threads) {
   std::vector<ThreadComm> thread_comms;
   if (!GetThreadComms(&thread_comms)) {
     return false;
   }
-  const perf_event_attr& attr = event_selection_set_.FindEventAttrByType(*measured_event_type_);
+  // Decide which processes and threads to dump.
+  std::set<pid_t> dump_processes;
+  std::set<pid_t> dump_threads;
+  for (auto& tid : selected_threads) {
+    dump_threads.insert(tid);
+  }
   for (auto& thread : thread_comms) {
-    CommRecord record = CreateCommRecord(attr, thread.tgid, thread.tid, thread.comm);
-    if (!record_file_writer_->WriteData(record.BinaryFormat())) {
+    if (dump_threads.find(thread.tid) != dump_threads.end()) {
+      dump_processes.insert(thread.pid);
+    }
+  }
+
+  const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]);
+  CHECK(attr != nullptr);
+
+  // Dump processes.
+  for (auto& thread : thread_comms) {
+    if (thread.pid != thread.tid) {
+      continue;
+    }
+    if (!all_threads && dump_processes.find(thread.pid) == dump_processes.end()) {
+      continue;
+    }
+    CommRecord record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
+    if (!ProcessRecord(&record)) {
       return false;
     }
-    if (thread.is_process) {
-      std::vector<ThreadMmap> thread_mmaps;
-      if (!GetThreadMmapsInProcess(thread.tgid, &thread_mmaps)) {
-        // The thread may exit before we get its info.
-        continue;
+    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.
       }
-      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.tgid, thread.tid, thread_mmap.start_addr,
-                             thread_mmap.len, thread_mmap.pgoff, thread_mmap.name);
-        if (!record_file_writer_->WriteData(record.BinaryFormat())) {
-          return false;
-        }
+      MmapRecord record =
+          CreateMmapRecord(*attr, false, thread.pid, thread.tid, thread_mmap.start_addr,
+                           thread_mmap.len, thread_mmap.pgoff, thread_mmap.name);
+      if (!ProcessRecord(&record)) {
+        return false;
       }
     }
   }
+
+  // Dump threads.
+  for (auto& thread : thread_comms) {
+    if (thread.pid == thread.tid) {
+      continue;
+    }
+    if (!all_threads && dump_threads.find(thread.tid) == dump_threads.end()) {
+      continue;
+    }
+    ForkRecord fork_record = CreateForkRecord(*attr, thread.pid, thread.tid, thread.pid, thread.pid);
+    if (!ProcessRecord(&fork_record)) {
+      return false;
+    }
+    CommRecord comm_record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
+    if (!ProcessRecord(&comm_record)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RecordCommand::CollectRecordsFromKernel(const char* data, size_t size) {
+  record_cache_->Push(data, size);
+  while (true) {
+    std::unique_ptr<Record> r = record_cache_->Pop();
+    if (r == nullptr) {
+      break;
+    }
+    if (!ProcessRecord(r.get())) {
+      return false;
+    }
+  }
   return true;
 }
 
-bool RecordCommandImpl::DumpAdditionalFeatures() {
-  if (!record_file_writer_->WriteFeatureHeader(1)) {
+bool RecordCommand::ProcessRecord(Record* record) {
+  UpdateRecordForEmbeddedElfPath(record);
+  BuildThreadTree(*record, &thread_tree_);
+  CollectHitFileInfo(record);
+  if (unwind_dwarf_callchain_ && !post_unwind_) {
+    UnwindRecord(record);
+  }
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    sample_record_count_++;
+  }
+  bool result = record_file_writer_->WriteData(record->BinaryFormat());
+  return result;
+}
+
+template<class RecordType>
+void UpdateMmapRecordForEmbeddedElfPath(RecordType* record) {
+  RecordType& r = *record;
+  bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL);
+  if (!in_kernel && r.data.pgoff != 0) {
+    // For the case of a shared library "foobar.so" embedded
+    // inside an APK, we rewrite the original MMAP from
+    // ["path.apk" offset=X] to ["path.apk!/foobar.so" offset=W]
+    // so as to make the library name explicit. This update is
+    // done here (as part of the record operation) as opposed to
+    // on the host during the report, since we want to report
+    // the correct library name even if the the APK in question
+    // is not present on the host. The new offset W is
+    // calculated to be with respect to the start of foobar.so,
+    // not to the start of path.apk.
+    EmbeddedElf* ee = ApkInspector::FindElfInApkByOffset(r.filename, r.data.pgoff);
+    if (ee != nullptr) {
+      // Compute new offset relative to start of elf in APK.
+      r.data.pgoff -= ee->entry_offset();
+      r.filename = GetUrlInApk(r.filename, ee->entry_name());
+      r.AdjustSizeBasedOnData();
+    }
+  }
+}
+
+void RecordCommand::UpdateRecordForEmbeddedElfPath(Record* record) {
+  if (record->type() == PERF_RECORD_MMAP) {
+    UpdateMmapRecordForEmbeddedElfPath(static_cast<MmapRecord*>(record));
+  } else if (record->type() == PERF_RECORD_MMAP2) {
+    UpdateMmapRecordForEmbeddedElfPath(static_cast<Mmap2Record*>(record));
+  }
+}
+
+void RecordCommand::UnwindRecord(Record* record) {
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    SampleRecord& r = *static_cast<SampleRecord*>(record);
+    if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
+        (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) &&
+        (!r.stack_user_data.data.empty())) {
+      ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+      RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs);
+      std::vector<char>& stack = r.stack_user_data.data;
+      std::vector<uint64_t> unwind_ips = UnwindCallChain(GetBuildArch(), *thread, regs, stack);
+      r.callchain_data.ips.push_back(PERF_CONTEXT_USER);
+      r.callchain_data.ips.insert(r.callchain_data.ips.end(), unwind_ips.begin(), unwind_ips.end());
+      r.regs_user_data.abi = 0;
+      r.regs_user_data.reg_mask = 0;
+      r.regs_user_data.regs.clear();
+      r.stack_user_data.data.clear();
+      r.stack_user_data.dyn_size = 0;
+      r.AdjustSizeBasedOnData();
+    }
+  }
+}
+
+bool RecordCommand::PostUnwind(const std::vector<std::string>& args) {
+  thread_tree_.Clear();
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_filename_);
+  if (reader == nullptr) {
+    return false;
+  }
+  std::string tmp_filename = record_filename_ + ".tmp";
+  record_file_writer_ = CreateRecordFile(tmp_filename);
+  if (record_file_writer_ == nullptr) {
+    return false;
+  }
+  bool result = reader->ReadDataSection(
+      [this](std::unique_ptr<Record> record) {
+        BuildThreadTree(*record, &thread_tree_);
+        UnwindRecord(record.get());
+        return record_file_writer_->WriteData(record->BinaryFormat());
+      },
+      false);
+  if (!result) {
+    return false;
+  }
+  if (!DumpAdditionalFeatures(args)) {
+    return false;
+  }
+  if (!record_file_writer_->Close()) {
+    return false;
+  }
+
+  if (unlink(record_filename_.c_str()) != 0) {
+    PLOG(ERROR) << "failed to remove " << record_filename_;
     return false;
   }
-  return DumpBuildIdFeature();
+  if (rename(tmp_filename.c_str(), record_filename_.c_str()) != 0) {
+    PLOG(ERROR) << "failed to rename " << tmp_filename << " to " << record_filename_;
+    return false;
+  }
+  return true;
 }
 
-bool RecordCommandImpl::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)) {
+bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args) {
+  size_t feature_count = (branch_sampling_ != 0 ? 5 : 4);
+  if (!record_file_writer_->WriteFeatureHeader(feature_count)) {
+    return false;
+  }
+  if (!DumpBuildIdFeature()) {
+    return false;
+  }
+  utsname uname_buf;
+  if (TEMP_FAILURE_RETRY(uname(&uname_buf)) != 0) {
+    PLOG(ERROR) << "uname() failed";
+    return false;
+  }
+  if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE, uname_buf.release)) {
+    return false;
+  }
+  if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH, uname_buf.machine)) {
+    return false;
+  }
+
+  std::string exec_path = "simpleperf";
+  GetExecPath(&exec_path);
+  std::vector<std::string> cmdline;
+  cmdline.push_back(exec_path);
+  cmdline.push_back("record");
+  cmdline.insert(cmdline.end(), args.begin(), args.end());
+  if (!record_file_writer_->WriteCmdlineFeature(cmdline)) {
     return false;
   }
+  if (branch_sampling_ != 0 && !record_file_writer_->WriteBranchStackFeature()) {
+    return false;
+  }
+  return true;
+}
+
+bool RecordCommand::DumpBuildIdFeature() {
   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) {
+  for (const auto& filename : hit_kernel_modules_) {
+    if (filename == DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID) {
       if (!GetKernelBuildId(&build_id)) {
         LOG(DEBUG) << "can't read build_id for kernel";
         continue;
@@ -335,7 +792,8 @@ bool RecordCommandImpl::DumpBuildIdFeature() {
       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]);
+      std::string path = filename;
+      std::string module_name = basename(&path[0]);
       if (android::base::EndsWith(module_name, ".ko")) {
         module_name = module_name.substr(0, module_name.size() - 3);
       }
@@ -347,13 +805,21 @@ bool RecordCommandImpl::DumpBuildIdFeature() {
     }
   }
   // Add build_ids for user elf files.
-  for (auto& filename : hit_user_files) {
+  for (const 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;
+    auto tuple = SplitUrlInApk(filename);
+    if (std::get<0>(tuple)) {
+      if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple), &build_id)) {
+        LOG(DEBUG) << "can't read build_id from file " << filename;
+        continue;
+      }
+    } else {
+      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));
   }
@@ -363,26 +829,20 @@ bool RecordCommandImpl::DumpBuildIdFeature() {
   return 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]. If [command]\n"
-                "    is not specified, sleep 1 is used instead.\n"
-                "    -a           System-wide collection.\n"
-                "    -c count     Set event sample period.\n"
-                "    -e event     Select the event to sample (Use `simpleperf list`)\n"
-                "                 to find all possible event names.\n"
-                "    -f freq      Set event sample frequency.\n"
-                "    -F freq      Same as '-f freq'.\n"
-                "    -o record_file_name    Set record file name, default is perf.data.\n") {
-  }
-
-  bool Run(const std::vector<std::string>& args) override {
-    RecordCommandImpl impl;
-    return impl.Run(args);
+void RecordCommand::CollectHitFileInfo(Record* record) {
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    auto r = *static_cast<SampleRecord*>(record);
+    bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL);
+    const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+    const MapEntry* map = thread_tree_.FindMap(thread, r.ip_data.ip, in_kernel);
+    if (in_kernel) {
+      hit_kernel_modules_.insert(map->dso->Path());
+    } else {
+      hit_user_files_.insert(map->dso->Path());
+    }
   }
-};
+}
 
-RecordCommand record_command;
+void RegisterRecordCommand() {
+  RegisterCommand("record", [] { return std::unique_ptr<Command>(new RecordCommand()); });
+}
index f0a8878..1a468e2 100644 (file)
 
 #include <gtest/gtest.h>
 
+#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
+
+#include <memory>
+
 #include "command.h"
 #include "environment.h"
+#include "event_selection_set.h"
+#include "get_test_data.h"
 #include "record.h"
 #include "record_file.h"
+#include "test_util.h"
 
 using namespace PerfFileFormat;
 
-class RecordCommandTest : public ::testing::Test {
- protected:
-  virtual void SetUp() {
-    record_cmd = Command::FindCommandByName("record");
-    ASSERT_TRUE(record_cmd != nullptr);
-  }
+static std::unique_ptr<Command> RecordCmd() {
+  return CreateCommandInstance("record");
+}
 
-  Command* record_cmd;
-};
+static bool RunRecordCmd(std::vector<std::string> v, const char* output_file = nullptr) {
+  std::unique_ptr<TemporaryFile> tmpfile;
+  std::string out_file;
+  if (output_file != nullptr) {
+    out_file = output_file;
+  } else {
+    tmpfile.reset(new TemporaryFile);
+    out_file = tmpfile->path;
+  }
+  v.insert(v.end(), {"-o", out_file, "sleep", SLEEP_SEC});
+  return RecordCmd()->Run(v);
+}
 
-TEST_F(RecordCommandTest, no_options) {
-  ASSERT_TRUE(record_cmd->Run({"record", "sleep", "1"}));
+TEST(record_cmd, no_options) {
+  ASSERT_TRUE(RunRecordCmd({}));
 }
 
-TEST_F(RecordCommandTest, system_wide_option) {
-  ASSERT_TRUE(record_cmd->Run({"record", "-a", "sleep", "1"}));
+TEST(record_cmd, system_wide_option) {
+  if (IsRoot()) {
+    ASSERT_TRUE(RunRecordCmd({"-a"}));
+  }
 }
 
-TEST_F(RecordCommandTest, sample_period_option) {
-  ASSERT_TRUE(record_cmd->Run({"record", "-c", "100000", "sleep", "1"}));
+TEST(record_cmd, sample_period_option) {
+  ASSERT_TRUE(RunRecordCmd({"-c", "100000"}));
 }
 
-TEST_F(RecordCommandTest, event_option) {
-  ASSERT_TRUE(record_cmd->Run({"record", "-e", "cpu-clock", "sleep", "1"}));
+TEST(record_cmd, event_option) {
+  ASSERT_TRUE(RunRecordCmd({"-e", "cpu-clock"}));
 }
 
-TEST_F(RecordCommandTest, freq_option) {
-  ASSERT_TRUE(record_cmd->Run({"record", "-f", "99", "sleep", "1"}));
-  ASSERT_TRUE(record_cmd->Run({"record", "-F", "99", "sleep", "1"}));
+TEST(record_cmd, freq_option) {
+  ASSERT_TRUE(RunRecordCmd({"-f", "99"}));
+  ASSERT_TRUE(RunRecordCmd({"-F", "99"}));
 }
 
-TEST_F(RecordCommandTest, output_file_option) {
-  ASSERT_TRUE(record_cmd->Run({"record", "-o", "perf2.data", "sleep", "1"}));
+TEST(record_cmd, output_file_option) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "sleep", SLEEP_SEC}));
 }
 
-TEST_F(RecordCommandTest, dump_kernel_mmap) {
-  ASSERT_TRUE(record_cmd->Run({"record", "sleep", "1"}));
-  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance("perf.data");
+TEST(record_cmd, dump_kernel_mmap) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
   ASSERT_TRUE(reader != nullptr);
-  std::vector<std::unique_ptr<const Record>> records = reader->DataSection();
+  std::vector<std::unique_ptr<Record>> records = reader->DataSection();
   ASSERT_GT(records.size(), 0U);
   bool have_kernel_mmap = false;
   for (auto& record : records) {
@@ -77,12 +96,112 @@ TEST_F(RecordCommandTest, dump_kernel_mmap) {
   ASSERT_TRUE(have_kernel_mmap);
 }
 
-TEST_F(RecordCommandTest, dump_build_id_feature) {
-  ASSERT_TRUE(record_cmd->Run({"record", "sleep", "1"}));
-  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance("perf.data");
+TEST(record_cmd, dump_build_id_feature) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
   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)));
+  const FileHeader& file_header = reader->FileHeader();
+  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) {
+  if (IsRoot()) {
+    ASSERT_TRUE(RunRecordCmd({"-a", "-e", "sched:sched_switch"}));
+  }
+}
+
+TEST(record_cmd, branch_sampling) {
+  if (IsBranchSamplingSupported()) {
+    ASSERT_TRUE(RunRecordCmd({"-b"}));
+    ASSERT_TRUE(RunRecordCmd({"-j", "any,any_call,any_ret,ind_call"}));
+    ASSERT_TRUE(RunRecordCmd({"-j", "any,k"}));
+    ASSERT_TRUE(RunRecordCmd({"-j", "any,u"}));
+    ASSERT_FALSE(RunRecordCmd({"-j", "u"}));
+  } 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(RunRecordCmd({"-e", "cpu-cycles:u"}));
+}
+
+TEST(record_cmd, fp_callchain_sampling) {
+  ASSERT_TRUE(RunRecordCmd({"--call-graph", "fp"}));
+}
+
+TEST(record_cmd, dwarf_callchain_sampling) {
+  if (IsDwarfCallChainSamplingSupported()) {
+    ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf"}));
+    ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf,16384"}));
+    ASSERT_TRUE(RunRecordCmd({"-g"}));
+  } else {
+    GTEST_LOG_(INFO)
+        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+  }
+}
+
+TEST(record_cmd, no_unwind_option) {
+  if (IsDwarfCallChainSamplingSupported()) {
+    ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--no-unwind"}));
+  } else {
+    GTEST_LOG_(INFO)
+        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+  }
+  ASSERT_FALSE(RunRecordCmd({"--no-unwind"}));
+}
+
+TEST(record_cmd, post_unwind_option) {
+  if (IsDwarfCallChainSamplingSupported()) {
+    ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--post-unwind"}));
+  } else {
+    GTEST_LOG_(INFO)
+        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+  }
+  ASSERT_FALSE(RunRecordCmd({"--post-unwind"}));
+  ASSERT_FALSE(
+      RunRecordCmd({"--call-graph", "dwarf", "--no-unwind", "--post-unwind"}));
+}
+
+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(RunRecordCmd({"-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());
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
+}
+
+TEST(record_cmd, no_monitored_threads) {
+  ASSERT_FALSE(RecordCmd()->Run({""}));
+}
+
+TEST(record_cmd, more_than_one_event_types) {
+  ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles,cpu-clock"}));
+  ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles", "-e", "cpu-clock"}));
+}
+
+TEST(record_cmd, cpu_option) {
+  ASSERT_TRUE(RunRecordCmd({"--cpu", "0"}));
+  if (IsRoot()) {
+    ASSERT_TRUE(RunRecordCmd({"--cpu", "0", "-a"}));
+  }
+}
+
+TEST(record_cmd, mmap_page_option) {
+  ASSERT_TRUE(RunRecordCmd({"-m", "1"}));
+  ASSERT_FALSE(RunRecordCmd({"-m", "0"}));
+  ASSERT_FALSE(RunRecordCmd({"-m", "7"}));
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
new file mode 100644 (file)
index 0000000..3d778ab
--- /dev/null
@@ -0,0 +1,783 @@
+/*
+ * 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 <algorithm>
+#include <functional>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "command.h"
+#include "dwarf_unwind.h"
+#include "environment.h"
+#include "event_attr.h"
+#include "event_type.h"
+#include "perf_regs.h"
+#include "record.h"
+#include "record_file.h"
+#include "sample_tree.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+class Displayable {
+ public:
+  Displayable(const std::string& name) : name_(name), width_(name.size()) {
+  }
+
+  virtual ~Displayable() {
+  }
+
+  const std::string& Name() const {
+    return name_;
+  }
+  size_t Width() const {
+    return width_;
+  }
+
+  virtual std::string Show(const SampleEntry& sample) const = 0;
+  void AdjustWidth(const SampleEntry& sample) {
+    size_t size = Show(sample).size();
+    width_ = std::max(width_, size);
+  }
+
+ private:
+  const std::string name_;
+  size_t width_;
+};
+
+class AccumulatedOverheadItem : public Displayable {
+ public:
+  AccumulatedOverheadItem(const SampleTree& sample_tree)
+      : Displayable("Children"), sample_tree_(sample_tree) {
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    uint64_t period = sample.period + sample.accumulated_period;
+    uint64_t total_period = sample_tree_.TotalPeriod();
+    double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
+    return android::base::StringPrintf("%.2lf%%", percentage);
+  }
+
+ private:
+  const SampleTree& sample_tree_;
+};
+
+class SelfOverheadItem : public Displayable {
+ public:
+  SelfOverheadItem(const SampleTree& sample_tree, const std::string& name = "Self")
+      : Displayable(name), sample_tree_(sample_tree) {
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    uint64_t period = sample.period;
+    uint64_t total_period = sample_tree_.TotalPeriod();
+    double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
+    return android::base::StringPrintf("%.2lf%%", percentage);
+  }
+
+ private:
+  const SampleTree& sample_tree_;
+};
+
+class SampleCountItem : public Displayable {
+ public:
+  SampleCountItem() : Displayable("Sample") {
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return android::base::StringPrintf("%" PRId64, sample.sample_count);
+  }
+};
+
+class Comparable {
+ public:
+  virtual ~Comparable() {
+  }
+
+  virtual int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const = 0;
+};
+
+class PidItem : public Displayable, public Comparable {
+ public:
+  PidItem() : Displayable("Pid") {
+  }
+
+  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
+    return sample1.thread->pid - sample2.thread->pid;
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return android::base::StringPrintf("%d", sample.thread->pid);
+  }
+};
+
+class TidItem : public Displayable, public Comparable {
+ public:
+  TidItem() : Displayable("Tid") {
+  }
+
+  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
+    return sample1.thread->tid - sample2.thread->tid;
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return android::base::StringPrintf("%d", sample.thread->tid);
+  }
+};
+
+class CommItem : public Displayable, public Comparable {
+ public:
+  CommItem() : Displayable("Command") {
+  }
+
+  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
+    return strcmp(sample1.thread_comm, sample2.thread_comm);
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return sample.thread_comm;
+  }
+};
+
+class DsoItem : public Displayable, public Comparable {
+ public:
+  DsoItem(const std::string& name = "Shared Object") : Displayable(name) {
+  }
+
+  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
+    return strcmp(sample1.map->dso->Path().c_str(), sample2.map->dso->Path().c_str());
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return sample.map->dso->Path();
+  }
+};
+
+class SymbolItem : public Displayable, public Comparable {
+ public:
+  SymbolItem(const std::string& name = "Symbol") : Displayable(name) {
+  }
+
+  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
+    return strcmp(sample1.symbol->DemangledName(), sample2.symbol->DemangledName());
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return sample.symbol->DemangledName();
+  }
+};
+
+class DsoFromItem : public Displayable, public Comparable {
+ public:
+  DsoFromItem() : Displayable("Source Shared Object") {
+  }
+
+  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
+    return strcmp(sample1.branch_from.map->dso->Path().c_str(),
+                  sample2.branch_from.map->dso->Path().c_str());
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return sample.branch_from.map->dso->Path();
+  }
+};
+
+class DsoToItem : public DsoItem {
+ public:
+  DsoToItem() : DsoItem("Target Shared Object") {
+  }
+};
+
+class SymbolFromItem : public Displayable, public Comparable {
+ public:
+  SymbolFromItem() : Displayable("Source Symbol") {
+  }
+
+  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
+    return strcmp(sample1.branch_from.symbol->DemangledName(),
+                  sample2.branch_from.symbol->DemangledName());
+  }
+
+  std::string Show(const SampleEntry& sample) const override {
+    return sample.branch_from.symbol->DemangledName();
+  }
+};
+
+class SymbolToItem : public SymbolItem {
+ public:
+  SymbolToItem() : SymbolItem("Target Symbol") {
+  }
+};
+
+static std::set<std::string> branch_sort_keys = {
+    "dso_from", "dso_to", "symbol_from", "symbol_to",
+};
+
+class ReportCommand : public Command {
+ public:
+  ReportCommand()
+      : Command(
+            "report", "report sampling information in perf.data",
+            "Usage: simpleperf report [options]\n"
+            "    -b            Use the branch-to addresses in sampled take branches instead of\n"
+            "                  the instruction addresses. Only valid for perf.data recorded with\n"
+            "                  -b/-j option.\n"
+            "    --children    Print the overhead accumulated by appearing in the callchain.\n"
+            "    --comms comm1,comm2,...\n"
+            "                  Report only for selected comms.\n"
+            "    --dsos dso1,dso2,...\n"
+            "                  Report only for selected dsos.\n"
+            "    -g [callee|caller]\n"
+            "                  Print call graph. If callee mode is used, the graph shows how\n"
+            "                  functions are called from others. Otherwise, the graph shows how\n"
+            "                  functions call others. Default is callee mode.\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"
+            "    -o report_file_name  Set report file name, default is stdout.\n"
+            "    --pid pid1,pid2,...\n"
+            "                  Report only for selected pids.\n"
+            "    --sort key1,key2,...\n"
+            "                  Select the keys to sort and print the report. Possible keys\n"
+            "                  include pid, tid, comm, dso, symbol, dso_from, dso_to, symbol_from\n"
+            "                  symbol_to. dso_from, dso_to, symbol_from, symbol_to can only be\n"
+            "                  used with -b option. Default keys are \"comm,pid,tid,dso,symbol\"\n"
+            "    --symfs <dir> Look for files with symbols relative to this directory.\n"
+            "    --tids tid1,tid2,...\n"
+            "                  Report only for selected tids.\n"
+            "    --vmlinux <file>\n"
+            "                  Parse kernel symbols from <file>.\n"),
+        record_filename_("perf.data"),
+        record_file_arch_(GetBuildArch()),
+        use_branch_address_(false),
+        accumulate_callchain_(false),
+        print_callgraph_(false),
+        callgraph_show_callee_(true),
+        report_fp_(nullptr) {
+    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(&thread_tree_, compare_sample_callback));
+  }
+
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args);
+  bool ReadEventAttrFromRecordFile();
+  void ReadSampleTreeFromRecordFile();
+  void ProcessRecord(std::unique_ptr<Record> record);
+  void ProcessSampleRecord(const SampleRecord& r);
+  bool ReadFeaturesFromRecordFile();
+  int CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2);
+  bool PrintReport();
+  void PrintReportContext();
+  void CollectReportWidth();
+  void CollectReportEntryWidth(const SampleEntry& sample);
+  void PrintReportHeader();
+  void PrintReportEntry(const SampleEntry& sample);
+  void PrintCallGraph(const SampleEntry& sample);
+  void PrintCallGraphEntry(size_t depth, std::string prefix, const std::unique_ptr<CallChainNode>& node,
+                           uint64_t parent_period, bool last);
+
+  std::string record_filename_;
+  ArchType record_file_arch_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+  perf_event_attr event_attr_;
+  std::vector<std::unique_ptr<Displayable>> displayable_items_;
+  std::vector<Comparable*> comparable_items_;
+  ThreadTree thread_tree_;
+  std::unique_ptr<SampleTree> sample_tree_;
+  bool use_branch_address_;
+  std::string record_cmdline_;
+  bool accumulate_callchain_;
+  bool print_callgraph_;
+  bool callgraph_show_callee_;
+
+  std::string report_filename_;
+  FILE* report_fp_;
+};
+
+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;
+  }
+  // Read features first to prepare build ids used when building SampleTree.
+  if (!ReadFeaturesFromRecordFile()) {
+    return false;
+  }
+  ScopedCurrentArch scoped_arch(record_file_arch_);
+  ReadSampleTreeFromRecordFile();
+
+  // 3. Show collected information.
+  if (!PrintReport()) {
+    return false;
+  }
+
+  return true;
+}
+
+bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
+  bool demangle = true;
+  std::string symfs_dir;
+  std::string vmlinux;
+  bool print_sample_count = false;
+  std::vector<std::string> sort_keys = {"comm", "pid", "tid", "dso", "symbol"};
+  std::unordered_set<std::string> comm_filter;
+  std::unordered_set<std::string> dso_filter;
+  std::unordered_set<int> pid_filter;
+  std::unordered_set<int> tid_filter;
+
+  for (size_t i = 0; i < args.size(); ++i) {
+    if (args[i] == "-b") {
+      use_branch_address_ = true;
+    } else if (args[i] == "--children") {
+      accumulate_callchain_ = true;
+    } else if (args[i] == "--comms" || args[i] == "--dsos") {
+      std::unordered_set<std::string>& filter = (args[i] == "--comms" ? comm_filter : dso_filter);
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> strs = android::base::Split(args[i], ",");
+      filter.insert(strs.begin(), strs.end());
+
+    } else if (args[i] == "-g") {
+      print_callgraph_ = true;
+      accumulate_callchain_ = true;
+      if (i + 1 < args.size() && args[i + 1][0] != '-') {
+        ++i;
+        if (args[i] == "callee") {
+          callgraph_show_callee_ = true;
+        } else if (args[i] == "caller") {
+          callgraph_show_callee_ = false;
+        } else {
+          LOG(ERROR) << "Unknown argument with -g option: " << args[i];
+          return false;
+        }
+      }
+    } else 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") {
+      demangle = false;
+    } else if (args[i] == "-o") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      report_filename_ = args[i];
+
+    } else if (args[i] == "--pids" || args[i] == "--tids") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> strs = android::base::Split(args[i], ",");
+      std::vector<int> ids;
+      for (const auto& s : strs) {
+        int id;
+        if (!android::base::ParseInt(s.c_str(), &id, 0)) {
+          LOG(ERROR) << "invalid id in " << args[i] << " option: " << s;
+          return false;
+        }
+        ids.push_back(id);
+      }
+      std::unordered_set<int>& filter = (args[i] == "--pids" ? pid_filter : tid_filter);
+      filter.insert(ids.begin(), ids.end());
+
+    } else if (args[i] == "--sort") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      sort_keys = android::base::Split(args[i], ",");
+    } else if (args[i] == "--symfs") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      symfs_dir = args[i];
+
+    } else if (args[i] == "--vmlinux") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      vmlinux = args[i];
+    } else {
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+
+  Dso::SetDemangle(demangle);
+  if (!Dso::SetSymFsDir(symfs_dir)) {
+    return false;
+  }
+  if (!vmlinux.empty()) {
+    Dso::SetVmlinux(vmlinux);
+  }
+
+  if (!accumulate_callchain_) {
+    displayable_items_.push_back(
+        std::unique_ptr<Displayable>(new SelfOverheadItem(*sample_tree_, "Overhead")));
+  } else {
+    displayable_items_.push_back(
+        std::unique_ptr<Displayable>(new AccumulatedOverheadItem(*sample_tree_)));
+    displayable_items_.push_back(std::unique_ptr<Displayable>(new SelfOverheadItem(*sample_tree_)));
+  }
+  if (print_sample_count) {
+    displayable_items_.push_back(std::unique_ptr<Displayable>(new SampleCountItem));
+  }
+  for (auto& key : sort_keys) {
+    if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) {
+      LOG(ERROR) << "sort key '" << key << "' can only be used with -b option.";
+      return false;
+    }
+    if (key == "pid") {
+      PidItem* item = new PidItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "tid") {
+      TidItem* item = new TidItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "comm") {
+      CommItem* item = new CommItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "dso") {
+      DsoItem* item = new DsoItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "symbol") {
+      SymbolItem* item = new SymbolItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "dso_from") {
+      DsoFromItem* item = new DsoFromItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "dso_to") {
+      DsoToItem* item = new DsoToItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "symbol_from") {
+      SymbolFromItem* item = new SymbolFromItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else if (key == "symbol_to") {
+      SymbolToItem* item = new SymbolToItem;
+      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
+      comparable_items_.push_back(item);
+    } else {
+      LOG(ERROR) << "Unknown sort key: " << key;
+      return false;
+    }
+  }
+  sample_tree_->SetFilters(pid_filter, tid_filter, comm_filter, dso_filter);
+  return true;
+}
+
+bool ReportCommand::ReadEventAttrFromRecordFile() {
+  const std::vector<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;
+  if (use_branch_address_ && (event_attr_.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
+    LOG(ERROR) << record_filename_ << " is not recorded with branch stack sampling option.";
+    return false;
+  }
+  return true;
+}
+
+void ReportCommand::ReadSampleTreeFromRecordFile() {
+  thread_tree_.AddThread(0, 0, "swapper");
+  record_file_reader_->ReadDataSection([this](std::unique_ptr<Record> record) {
+    ProcessRecord(std::move(record));
+    return true;
+  });
+}
+
+void ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
+  BuildThreadTree(*record, &thread_tree_);
+  if (record->header.type == PERF_RECORD_SAMPLE) {
+    ProcessSampleRecord(*static_cast<const SampleRecord*>(record.get()));
+  }
+}
+
+void ReportCommand::ProcessSampleRecord(const SampleRecord& r) {
+  if (use_branch_address_ && (r.sample_type & PERF_SAMPLE_BRANCH_STACK)) {
+    for (auto& item : r.branch_stack_data.stack) {
+      if (item.from != 0 && item.to != 0) {
+        sample_tree_->AddBranchSample(r.tid_data.pid, r.tid_data.tid, item.from, item.to,
+                                      item.flags, r.time_data.time, r.period_data.period);
+      }
+    }
+  } else {
+    bool in_kernel = (r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL;
+    SampleEntry* sample = 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);
+    if (sample == nullptr) {
+      return;
+    }
+    if (accumulate_callchain_) {
+      std::vector<uint64_t> ips;
+      if (r.sample_type & PERF_SAMPLE_CALLCHAIN) {
+        ips.insert(ips.end(), r.callchain_data.ips.begin(), r.callchain_data.ips.end());
+      }
+      // Use stack_user_data.data.size() instead of stack_user_data.dyn_size, to make up for
+      // the missing kernel patch in N9. See b/22612370.
+      if ((r.sample_type & PERF_SAMPLE_REGS_USER) && (r.regs_user_data.reg_mask != 0) &&
+          (r.sample_type & PERF_SAMPLE_STACK_USER) && (!r.stack_user_data.data.empty())) {
+        RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs);
+        std::vector<char> stack(r.stack_user_data.data.begin(),
+                                r.stack_user_data.data.begin() + r.stack_user_data.data.size());
+        std::vector<uint64_t> unwind_ips =
+            UnwindCallChain(ScopedCurrentArch::GetCurrentArch(), *sample->thread, regs, stack);
+        if (!unwind_ips.empty()) {
+          ips.push_back(PERF_CONTEXT_USER);
+          ips.insert(ips.end(), unwind_ips.begin(), unwind_ips.end());
+        }
+      }
+
+      std::vector<SampleEntry*> callchain;
+      callchain.push_back(sample);
+
+      bool first_ip = true;
+      for (auto& ip : ips) {
+        if (ip >= PERF_CONTEXT_MAX) {
+          switch (ip) {
+            case PERF_CONTEXT_KERNEL:
+              in_kernel = true;
+              break;
+            case PERF_CONTEXT_USER:
+              in_kernel = false;
+              break;
+            default:
+              LOG(ERROR) << "Unexpected perf_context in callchain: " << ip;
+          }
+        } else {
+          if (first_ip) {
+            first_ip = false;
+            // Remove duplication with sampled ip.
+            if (ip == r.ip_data.ip) {
+              continue;
+            }
+          }
+          SampleEntry* sample =
+              sample_tree_->AddCallChainSample(r.tid_data.pid, r.tid_data.tid, ip, r.time_data.time,
+                                               r.period_data.period, in_kernel, callchain);
+          callchain.push_back(sample);
+        }
+      }
+
+      if (print_callgraph_) {
+        std::set<SampleEntry*> added_set;
+        if (!callgraph_show_callee_) {
+          std::reverse(callchain.begin(), callchain.end());
+        }
+        while (callchain.size() >= 2) {
+          SampleEntry* sample = callchain[0];
+          callchain.erase(callchain.begin());
+          // Add only once for recursive calls on callchain.
+          if (added_set.find(sample) != added_set.end()) {
+            continue;
+          }
+          added_set.insert(sample);
+          sample_tree_->InsertCallChainForSample(sample, callchain, r.period_data.period);
+        }
+      }
+    }
+  }
+}
+
+bool ReportCommand::ReadFeaturesFromRecordFile() {
+  std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature();
+  std::vector<std::pair<std::string, BuildId>> build_ids;
+  for (auto& r : records) {
+    build_ids.push_back(std::make_pair(r.filename, r.build_id));
+  }
+  Dso::SetBuildIds(build_ids);
+
+  std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
+  if (!arch.empty()) {
+    record_file_arch_ = GetArchType(arch);
+    if (record_file_arch_ == ARCH_UNSUPPORTED) {
+      return false;
+    }
+  }
+
+  std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
+  if (!cmdline.empty()) {
+    record_cmdline_ = android::base::Join(cmdline, ' ');
+  }
+  return true;
+}
+
+int ReportCommand::CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2) {
+  for (auto& item : comparable_items_) {
+    int result = item->Compare(sample1, sample2);
+    if (result != 0) {
+      return result;
+    }
+  }
+  return 0;
+}
+
+bool ReportCommand::PrintReport() {
+  std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose);
+  if (report_filename_.empty()) {
+    report_fp_ = stdout;
+  } else {
+    report_fp_ = fopen(report_filename_.c_str(), "w");
+    if (report_fp_ == nullptr) {
+      PLOG(ERROR) << "failed to open file " << report_filename_;
+      return false;
+    }
+    file_handler.reset(report_fp_);
+  }
+  PrintReportContext();
+  CollectReportWidth();
+  PrintReportHeader();
+  sample_tree_->VisitAllSamples(
+      std::bind(&ReportCommand::PrintReportEntry, this, std::placeholders::_1));
+  fflush(report_fp_);
+  if (ferror(report_fp_) != 0) {
+    PLOG(ERROR) << "print report failed";
+    return false;
+  }
+  return true;
+}
+
+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);
+  }
+  if (!record_cmdline_.empty()) {
+    fprintf(report_fp_, "Cmdline: %s\n", record_cmdline_.c_str());
+  }
+  fprintf(report_fp_, "Samples: %" PRIu64 " of event '%s'\n", sample_tree_->TotalSamples(),
+          event_type_name.c_str());
+  fprintf(report_fp_, "Event count: %" PRIu64 "\n\n", sample_tree_->TotalPeriod());
+}
+
+void ReportCommand::CollectReportWidth() {
+  sample_tree_->VisitAllSamples(
+      std::bind(&ReportCommand::CollectReportEntryWidth, this, std::placeholders::_1));
+}
+
+void ReportCommand::CollectReportEntryWidth(const SampleEntry& sample) {
+  for (auto& item : displayable_items_) {
+    item->AdjustWidth(sample);
+  }
+}
+
+void ReportCommand::PrintReportHeader() {
+  for (size_t i = 0; i < displayable_items_.size(); ++i) {
+    auto& item = displayable_items_[i];
+    if (i != displayable_items_.size() - 1) {
+      fprintf(report_fp_, "%-*s  ", static_cast<int>(item->Width()), item->Name().c_str());
+    } else {
+      fprintf(report_fp_, "%s\n", item->Name().c_str());
+    }
+  }
+}
+
+void ReportCommand::PrintReportEntry(const SampleEntry& sample) {
+  for (size_t i = 0; i < displayable_items_.size(); ++i) {
+    auto& item = displayable_items_[i];
+    if (i != displayable_items_.size() - 1) {
+      fprintf(report_fp_, "%-*s  ", static_cast<int>(item->Width()), item->Show(sample).c_str());
+    } else {
+      fprintf(report_fp_, "%s\n", item->Show(sample).c_str());
+    }
+  }
+  if (print_callgraph_) {
+    PrintCallGraph(sample);
+  }
+}
+
+void ReportCommand::PrintCallGraph(const SampleEntry& sample) {
+  std::string prefix = "       ";
+  fprintf(report_fp_, "%s|\n", prefix.c_str());
+  fprintf(report_fp_, "%s-- %s\n", prefix.c_str(), sample.symbol->DemangledName());
+  prefix.append(3, ' ');
+  for (size_t i = 0; i < sample.callchain.children.size(); ++i) {
+    PrintCallGraphEntry(1, prefix, sample.callchain.children[i], sample.callchain.children_period,
+                        (i + 1 == sample.callchain.children.size()));
+  }
+}
+
+void ReportCommand::PrintCallGraphEntry(size_t depth, std::string prefix,
+                                        const std::unique_ptr<CallChainNode>& node,
+                                        uint64_t parent_period, bool last) {
+  if (depth > 20) {
+    LOG(WARNING) << "truncated callgraph at depth " << depth;
+    return;
+  }
+  prefix += "|";
+  fprintf(report_fp_, "%s\n", prefix.c_str());
+  if (last) {
+    prefix.back() = ' ';
+  }
+  std::string percentage_s = "-- ";
+  if (node->period + node->children_period != parent_period) {
+    double percentage = 100.0 * (node->period + node->children_period) / parent_period;
+    percentage_s = android::base::StringPrintf("--%.2lf%%-- ", percentage);
+  }
+  fprintf(report_fp_, "%s%s%s\n", prefix.c_str(), percentage_s.c_str(), node->chain[0]->symbol->DemangledName());
+  prefix.append(percentage_s.size(), ' ');
+  for (size_t i = 1; i < node->chain.size(); ++i) {
+    fprintf(report_fp_, "%s%s\n", prefix.c_str(), node->chain[i]->symbol->DemangledName());
+  }
+
+  for (size_t i = 0; i < node->children.size(); ++i) {
+    PrintCallGraphEntry(depth + 1, prefix, node->children[i], node->children_period,
+                        (i + 1 == node->children.size()));
+  }
+}
+
+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 (file)
index 0000000..bcf8b6e
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ * 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 <set>
+#include <unordered_map>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+
+#include "command.h"
+#include "event_selection_set.h"
+#include "get_test_data.h"
+#include "perf_regs.h"
+#include "read_apk.h"
+#include "test_util.h"
+
+static std::unique_ptr<Command> ReportCmd() {
+  return CreateCommandInstance("report");
+}
+
+class ReportCommandTest : public ::testing::Test {
+ protected:
+  void Report(const std::string perf_data,
+              const std::vector<std::string>& add_args = std::vector<std::string>()) {
+    ReportRaw(GetTestData(perf_data), add_args);
+  }
+
+  void ReportRaw(const std::string perf_data,
+                 const std::vector<std::string>& add_args = std::vector<std::string>()) {
+    success = false;
+    std::vector<std::string> args = {"-i", perf_data,
+        "--symfs", GetTestDataDir(), "-o", tmp_file.path};
+    args.insert(args.end(), add_args.begin(), add_args.end());
+    ASSERT_TRUE(ReportCmd()->Run(args));
+    ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &content));
+    ASSERT_TRUE(!content.empty());
+    std::vector<std::string> raw_lines = android::base::Split(content, "\n");
+    lines.clear();
+    for (const auto& line : raw_lines) {
+      std::string s = android::base::Trim(line);
+      if (!s.empty()) {
+        lines.push_back(s);
+      }
+    }
+    ASSERT_GE(lines.size(), 2u);
+    success = true;
+  }
+
+  TemporaryFile tmp_file;
+  std::string content;
+  std::vector<std::string> lines;
+  bool success;
+};
+
+TEST_F(ReportCommandTest, no_option) {
+  Report(PERF_DATA);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, sort_option_pid) {
+  Report(PERF_DATA, {"--sort", "pid"});
+  ASSERT_TRUE(success);
+  size_t line_index = 0;
+  while (line_index < lines.size() && lines[line_index].find("Pid") == std::string::npos) {
+    line_index++;
+  }
+  ASSERT_LT(line_index + 2, lines.size());
+}
+
+TEST_F(ReportCommandTest, sort_option_more_than_one) {
+  Report(PERF_DATA, {"--sort", "comm,pid,dso,symbol"});
+  ASSERT_TRUE(success);
+  size_t line_index = 0;
+  while (line_index < lines.size() && lines[line_index].find("Overhead") == std::string::npos) {
+    line_index++;
+  }
+  ASSERT_LT(line_index + 1, lines.size());
+  ASSERT_NE(lines[line_index].find("Command"), std::string::npos);
+  ASSERT_NE(lines[line_index].find("Pid"), std::string::npos);
+  ASSERT_NE(lines[line_index].find("Shared Object"), std::string::npos);
+  ASSERT_NE(lines[line_index].find("Symbol"), std::string::npos);
+  ASSERT_EQ(lines[line_index].find("Tid"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, children_option) {
+  Report(CALLGRAPH_FP_PERF_DATA, {"--children", "--sort", "symbol"});
+  ASSERT_TRUE(success);
+  std::unordered_map<std::string, std::pair<double, double>> map;
+  for (size_t i = 0; i < lines.size(); ++i) {
+    char name[1024];
+    std::pair<double, double> pair;
+    if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second, name) == 3) {
+      map.insert(std::make_pair(name, pair));
+    }
+  }
+  ASSERT_NE(map.find("GlobalFunc"), map.end());
+  ASSERT_NE(map.find("main"), map.end());
+  auto func_pair = map["GlobalFunc"];
+  auto main_pair = map["main"];
+  ASSERT_GE(main_pair.first, func_pair.first);
+  ASSERT_GE(func_pair.first, func_pair.second);
+  ASSERT_GE(func_pair.second, main_pair.second);
+}
+
+static bool CheckCalleeMode(std::vector<std::string>& lines) {
+  bool found = false;
+  for (size_t i = 0; i + 2 < lines.size(); ++i) {
+    if (lines[i].find("GlobalFunc") != std::string::npos &&
+        lines[i + 1].find("|") != std::string::npos &&
+        lines[i + 2].find("main") != std::string::npos) {
+      found = true;
+      break;
+    }
+  }
+  return found;
+}
+
+static bool CheckCallerMode(std::vector<std::string>& lines) {
+  bool found = false;
+  for (size_t i = 0; i + 2 < lines.size(); ++i) {
+    if (lines[i].find("main") != std::string::npos &&
+        lines[i + 1].find("|") != std::string::npos &&
+        lines[i + 2].find("GlobalFunc") != std::string::npos) {
+      found = true;
+      break;
+    }
+  }
+  return found;
+}
+
+TEST_F(ReportCommandTest, callgraph_option) {
+  Report(CALLGRAPH_FP_PERF_DATA, {"-g"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(CheckCalleeMode(lines));
+  Report(CALLGRAPH_FP_PERF_DATA, {"-g", "callee"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(CheckCalleeMode(lines));
+  Report(CALLGRAPH_FP_PERF_DATA, {"-g", "caller"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(CheckCallerMode(lines));
+}
+
+static bool AllItemsWithString(std::vector<std::string>& lines, const std::vector<std::string>& strs) {
+  size_t line_index = 0;
+  while (line_index < lines.size() && lines[line_index].find("Overhead") == std::string::npos) {
+    line_index++;
+  }
+  if (line_index == lines.size() || line_index + 1 == lines.size()) {
+    return false;
+  }
+  line_index++;
+  for (; line_index < lines.size(); ++line_index) {
+    bool exist = false;
+    for (auto& s : strs) {
+      if (lines[line_index].find(s) != std::string::npos) {
+        exist = true;
+        break;
+      }
+    }
+    if (!exist) {
+      return false;
+    }
+  }
+  return true;
+}
+
+TEST_F(ReportCommandTest, pid_filter_option) {
+  Report(PERF_DATA);
+  ASSERT_TRUE("success");
+  ASSERT_FALSE(AllItemsWithString(lines, {"26083"}));
+  ASSERT_FALSE(AllItemsWithString(lines, {"26083", "26090"}));
+  Report(PERF_DATA, {"--pids", "26083"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"26083"}));
+  Report(PERF_DATA, {"--pids", "26083,26090"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"26083", "26090"}));
+}
+
+TEST_F(ReportCommandTest, tid_filter_option) {
+  Report(PERF_DATA);
+  ASSERT_TRUE("success");
+  ASSERT_FALSE(AllItemsWithString(lines, {"26083"}));
+  ASSERT_FALSE(AllItemsWithString(lines, {"26083", "26090"}));
+  Report(PERF_DATA, {"--tids", "26083"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"26083"}));
+  Report(PERF_DATA, {"--tids", "26083,26090"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"26083", "26090"}));
+}
+
+TEST_F(ReportCommandTest, comm_filter_option) {
+  Report(PERF_DATA, {"--sort", "comm"});
+  ASSERT_TRUE(success);
+  ASSERT_FALSE(AllItemsWithString(lines, {"t1"}));
+  ASSERT_FALSE(AllItemsWithString(lines, {"t1", "t2"}));
+  Report(PERF_DATA, {"--sort", "comm", "--comms", "t1"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"t1"}));
+  Report(PERF_DATA, {"--sort", "comm", "--comms", "t1,t2"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"t1", "t2"}));
+}
+
+TEST_F(ReportCommandTest, dso_filter_option) {
+  Report(PERF_DATA, {"--sort", "dso"});
+  ASSERT_TRUE(success);
+  ASSERT_FALSE(AllItemsWithString(lines, {"/t1"}));
+  ASSERT_FALSE(AllItemsWithString(lines, {"/t1", "/t2"}));
+  Report(PERF_DATA, {"--sort", "dso", "--dsos", "/t1"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"/t1"}));
+  Report(PERF_DATA, {"--sort", "dso", "--dsos", "/t1,/t2"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"/t1", "/t2"}));
+}
+
+TEST_F(ReportCommandTest, use_branch_address) {
+  Report(BRANCH_PERF_DATA, {"-b", "--sort", "symbol_from,symbol_to"});
+  std::set<std::pair<std::string, std::string>> hit_set;
+  bool after_overhead = false;
+  for (const auto& line : lines) {
+    if (!after_overhead && line.find("Overhead") != std::string::npos) {
+      after_overhead = true;
+    } else if (after_overhead) {
+      char from[80];
+      char to[80];
+      if (sscanf(line.c_str(), "%*f%%%s%s", from, to) == 2) {
+        hit_set.insert(std::make_pair<std::string, std::string>(from, to));
+      }
+    }
+  }
+  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>("GlobalFunc", "CalledFunc")),
+            hit_set.end());
+  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>("CalledFunc", "GlobalFunc")),
+            hit_set.end());
+}
+
+TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
+  Report(NATIVELIB_IN_APK_PERF_DATA);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
+  ASSERT_NE(content.find("Func2"), std::string::npos);
+}
+
+#if defined(__linux__)
+
+static std::unique_ptr<Command> RecordCmd() {
+  return CreateCommandInstance("record");
+}
+
+TEST_F(ReportCommandTest, dwarf_callgraph) {
+  if (IsDwarfCallChainSamplingSupported()) {
+    TemporaryFile tmp_file;
+    ASSERT_TRUE(RecordCmd()->Run({"-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
+    ReportRaw(tmp_file.path, {"-g"});
+    ASSERT_TRUE(success);
+  } else {
+    GTEST_LOG_(INFO)
+        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+  }
+}
+
+TEST_F(ReportCommandTest, report_dwarf_callgraph_of_nativelib_in_apk) {
+  // NATIVELIB_IN_APK_PERF_DATA is recorded on arm64, so can only report callgraph on arm64.
+  if (GetBuildArch() == ARCH_ARM64) {
+    Report(NATIVELIB_IN_APK_PERF_DATA, {"-g"});
+    ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
+    ASSERT_NE(content.find("Func2"), std::string::npos);
+    ASSERT_NE(content.find("Func1"), std::string::npos);
+    ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
+  } else {
+    GTEST_LOG_(INFO) << "This test does nothing as it is only run on arm64 devices";
+  }
+}
+
+#endif
+
index c8e59d9..488e731 100644 (file)
  */
 
 #include <inttypes.h>
+#include <signal.h>
 #include <stdio.h>
+#include <string.h>
+
+#include <algorithm>
 #include <chrono>
+#include <set>
 #include <string>
 #include <vector>
 
-#include <base/logging.h>
-#include <base/strings.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 
 #include "command.h"
 #include "environment.h"
+#include "event_attr.h"
+#include "event_fd.h"
 #include "event_selection_set.h"
 #include "event_type.h"
-#include "perf_event.h"
+#include "scoped_signal_handler.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",
+    "cpu-cycles",   "stalled-cycles-frontend", "stalled-cycles-backend",
+    "instructions", "branch-instructions",     "branch-misses",
+    "task-clock",   "context-switches",        "page-faults",
 };
 
-class StatCommandImpl {
+static volatile bool signaled;
+static void signal_handler(int) {
+  signaled = true;
+}
+
+class StatCommand : public Command {
  public:
-  StatCommandImpl() : verbose_mode_(false), system_wide_collection_(false) {
+  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"
+                "    --cpu cpu_item1,cpu_item2,...\n"
+                "                 Collect information only on the selected cpus. cpu_item can\n"
+                "                 be a cpu number like 1, or a cpu range like 0-3.\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;
+    scoped_signal_handler_.reset(
+        new ScopedSignalHandler({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 AddMeasuredEventType(const std::string& event_type_name);
   bool AddDefaultMeasuredEventTypes();
-  bool ShowCounters(const std::map<const EventType*, std::vector<PerfCounter>>& counters_map,
-                    std::chrono::steady_clock::duration counting_duration);
+  bool SetEventSelection();
+  bool ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec);
 
-  EventSelectionSet event_selection_set_;
   bool verbose_mode_;
   bool system_wide_collection_;
+  bool child_inherit_;
+  std::vector<pid_t> monitored_threads_;
+  std::vector<int> cpus_;
+  std::vector<EventTypeAndModifier> measured_event_types_;
+  EventSelectionSet event_selection_set_;
+
+  std::unique_ptr<ScopedSignalHandler> scoped_signal_handler_;
 };
 
-bool StatCommandImpl::Run(const std::vector<std::string>& args) {
-  // 1. Parse options.
+bool StatCommand::Run(const std::vector<std::string>& args) {
+  if (!CheckPerfEventLimit()) {
+    return false;
+  }
+
+  // 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;
   }
-
-  // 2. Add default measured event types.
-  if (event_selection_set_.Empty()) {
+  if (measured_event_types_.empty()) {
     if (!AddDefaultMeasuredEventTypes()) {
       return false;
     }
   }
-
-  // 3. Create workload.
-  if (workload_args.empty()) {
-    // TODO: change default workload to sleep 99999, and run stat until Ctrl-C.
-    workload_args = std::vector<std::string>({"sleep", "1"});
-  }
-  std::unique_ptr<Workload> workload = Workload::CreateWorkload(workload_args);
-  if (workload == nullptr) {
+  if (!SetEventSelection()) {
     return false;
   }
 
-  // 4. Open perf_event_files.
-  if (system_wide_collection_) {
-    if (!event_selection_set_.OpenEventFilesForAllCpus()) {
+  // 2. Create workload.
+  std::unique_ptr<Workload> workload;
+  if (!workload_args.empty()) {
+    workload = Workload::CreateWorkload(workload_args);
+    if (workload == nullptr) {
       return false;
     }
-  } else {
-    event_selection_set_.EnableOnExec();
-    if (!event_selection_set_.OpenEventFilesForProcess(workload->GetPid())) {
+  }
+  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;
     }
   }
 
-  // 5. Count events while workload running.
-  auto start_time = std::chrono::steady_clock::now();
-  // If monitoring only one process, we use the enable_on_exec flag, and don't need to start
-  // counting manually.
+  // 3. Open perf_event_files.
   if (system_wide_collection_) {
-    if (!event_selection_set_.EnableEvents()) {
+    if (!event_selection_set_.OpenEventFilesForCpus(cpus_)) {
+      return false;
+    }
+  } else {
+    if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) {
       return false;
     }
   }
-  if (!workload->Start()) {
+
+  // 4. Count events while workload running.
+  auto start_time = std::chrono::steady_clock::now();
+  if (workload != nullptr && !workload->Start()) {
     return false;
   }
-  workload->WaitFinish();
+  while (!signaled) {
+    sleep(1);
+  }
   auto end_time = std::chrono::steady_clock::now();
 
-  // 6. Read and print counters.
-  std::map<const EventType*, std::vector<PerfCounter>> counters_map;
-  if (!event_selection_set_.ReadCounters(&counters_map)) {
+  // 5. Read and print counters.
+  std::vector<CountersInfo> counters;
+  if (!event_selection_set_.ReadCounters(&counters)) {
     return false;
   }
-  if (!ShowCounters(counters_map, end_time - start_time)) {
+  double duration_in_sec =
+      std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
+  if (!ShowCounters(counters, duration_in_sec)) {
     return false;
   }
   return true;
 }
 
-bool StatCommandImpl::ParseOptions(const std::vector<std::string>& args,
-                                   std::vector<std::string>* non_option_args) {
+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 = 1; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++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] == "--cpu") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      cpus_ = GetCpusFromString(args[i]);
     } else if (args[i] == "-e") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -133,15 +193,36 @@ bool StatCommandImpl::ParseOptions(const std::vector<std::string>& args,
           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 {
-      LOG(ERROR) << "Unknown option for stat command: " << args[i];
-      LOG(ERROR) << "Try `simpleperf help stat`";
+      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) {
@@ -151,92 +232,189 @@ bool StatCommandImpl::ParseOptions(const std::vector<std::string>& args,
   return true;
 }
 
-bool StatCommandImpl::AddMeasuredEventType(const std::string& event_type_name,
-                                           bool report_unsupported_type) {
-  const EventType* event_type =
-      EventTypeFactory::FindEventTypeByName(event_type_name, report_unsupported_type);
-  if (event_type == nullptr) {
+bool StatCommand::AddMeasuredEventType(const std::string& event_type_name) {
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_name);
+  if (event_type_modifier == nullptr) {
     return false;
   }
-  event_selection_set_.AddEventType(*event_type);
+  measured_event_types_.push_back(*event_type_modifier);
   return true;
 }
 
-bool StatCommandImpl::AddDefaultMeasuredEventTypes() {
+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);
+    const EventType* type = FindEventTypeByName(name);
+    if (type != nullptr && IsEventAttrSupportedByKernel(CreateDefaultPerfEventAttr(*type))) {
+      AddMeasuredEventType(name);
+    }
   }
-  if (event_selection_set_.Empty()) {
+  if (measured_event_types_.empty()) {
     LOG(ERROR) << "Failed to add any supported default measured types";
     return false;
   }
   return true;
 }
 
-bool StatCommandImpl::ShowCounters(
-    const std::map<const EventType*, std::vector<PerfCounter>>& counters_map,
-    std::chrono::steady_clock::duration counting_duration) {
+bool StatCommand::SetEventSelection() {
+  for (auto& event_type : measured_event_types_) {
+    if (!event_selection_set_.AddEventType(event_type)) {
+      return false;
+    }
+  }
+  event_selection_set_.SetInherit(child_inherit_);
+  return true;
+}
+
+static std::string ReadableCountValue(uint64_t count,
+                                      const EventTypeAndModifier& event_type_modifier) {
+  if (event_type_modifier.event_type.name == "cpu-clock" ||
+      event_type_modifier.event_type.name == "task-clock") {
+    double value = count / 1e6;
+    return android::base::StringPrintf("%lf(ms)", value);
+  } else {
+    std::string s = android::base::StringPrintf("%" PRIu64, count);
+    for (size_t i = s.size() - 1, j = 1; i > 0; --i, ++j) {
+      if (j == 3) {
+        s.insert(s.begin() + i, ',');
+        j = 0;
+      }
+    }
+    return s;
+  }
+}
+
+struct CounterSummary {
+  const EventTypeAndModifier* event_type;
+  uint64_t count;
+  double scale;
+  std::string readable_count_str;
+  std::string comment;
+};
+
+static std::string GetCommentForSummary(const CounterSummary& summary,
+                                        const std::vector<CounterSummary>& summaries,
+                                        double duration_in_sec) {
+  const std::string& type_name = summary.event_type->event_type.name;
+  const std::string& modifier = summary.event_type->modifier;
+  if (type_name == "task-clock") {
+    double run_sec = summary.count / 1e9;
+    double cpu_usage = run_sec / duration_in_sec;
+    return android::base::StringPrintf("%lf%% cpu usage", cpu_usage * 100);
+  }
+  if (type_name == "cpu-clock") {
+    return "";
+  }
+  if (type_name == "cpu-cycles") {
+    double hz = summary.count / duration_in_sec;
+    return android::base::StringPrintf("%lf GHz", hz / 1e9);
+  }
+  if (type_name == "instructions" && summary.count != 0) {
+    for (auto& t : summaries) {
+      if (t.event_type->event_type.name == "cpu-cycles" && t.event_type->modifier == modifier) {
+        double cycles_per_instruction = t.count * 1.0 / summary.count;
+        return android::base::StringPrintf("%lf cycles per instruction", cycles_per_instruction);
+      }
+    }
+  }
+  if (android::base::EndsWith(type_name, "-misses")) {
+    std::string s;
+    if (type_name == "cache-misses") {
+      s = "cache-references";
+    } else if (type_name == "branch-misses") {
+      s = "branch-instructions";
+    } else {
+      s = type_name.substr(0, type_name.size() - strlen("-misses")) + "s";
+    }
+    for (auto& t : summaries) {
+      if (t.event_type->event_type.name == s && t.event_type->modifier == modifier && t.count != 0) {
+        double miss_rate = summary.count * 1.0 / t.count;
+        return android::base::StringPrintf("%lf%% miss rate", miss_rate * 100);
+      }
+    }
+  }
+  double rate = summary.count / duration_in_sec;
+  if (rate > 1e9) {
+    return android::base::StringPrintf("%.3lf G/sec", rate / 1e9);
+  }
+  if (rate > 1e6) {
+    return android::base::StringPrintf("%.3lf M/sec", rate / 1e6);
+  }
+  if (rate > 1e3) {
+    return android::base::StringPrintf("%.3lf K/sec", rate / 1e3);
+  }
+  return android::base::StringPrintf("%.3lf /sec", rate);
+}
+
+bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec) {
   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);
+  if (verbose_mode_) {
+    for (auto& counters_info : counters) {
+      const EventTypeAndModifier* event_type = counters_info.event_type;
+      for (auto& counter_info : counters_info.counters) {
+        printf("%s(tid %d, cpu %d): count %s, time_enabled %" PRIu64 ", time running %" PRIu64
+               ", id %" PRIu64 "\n",
+               event_type->name.c_str(), counter_info.tid, counter_info.cpu,
+               ReadableCountValue(counter_info.counter.value, *event_type).c_str(),
+               counter_info.counter.time_enabled, counter_info.counter.time_running,
+               counter_info.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;
+  std::vector<CounterSummary> summaries;
+  for (auto& counters_info : counters) {
+    uint64_t value_sum = 0;
+    uint64_t time_enabled_sum = 0;
+    uint64_t time_running_sum = 0;
+    for (auto& counter_info : counters_info.counters) {
+      value_sum += counter_info.counter.value;
+      time_enabled_sum += counter_info.counter.time_enabled;
+      time_running_sum += counter_info.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) {
+    double scale = 1.0;
+    uint64_t scaled_count = value_sum;
+    if (time_running_sum < time_enabled_sum) {
+      if (time_running_sum == 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);
+        scale = static_cast<double>(time_enabled_sum) / time_running_sum;
+        scaled_count = static_cast<uint64_t>(scale * value_sum);
       }
     }
-    printf("%'30" PRId64 "%s  %s\n", scaled_count, scaled ? "(scaled)" : "       ",
-           event_type->name);
+    CounterSummary summary;
+    summary.event_type = counters_info.event_type;
+    summary.count = scaled_count;
+    summary.scale = scale;
+    summary.readable_count_str = ReadableCountValue(summary.count, *summary.event_type);
+    summaries.push_back(summary);
   }
-  printf("\nTotal test time: %lf seconds.\n",
-         std::chrono::duration_cast<std::chrono::duration<double>>(counting_duration).count());
-  return 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]. If [command]\n"
-                "    is not specified, sleep 1 is used instead.\n\n"
-                "    -a           Collect system-wide information.\n"
-                "    -e event1,event2,... Select the event list to count. Use `simpleperf list`\n"
-                "                         to find all possible event names.\n"
-                "    --verbose    Show result in verbose mode.\n") {
+  for (auto& summary : summaries) {
+    summary.comment = GetCommentForSummary(summary, summaries, duration_in_sec);
   }
 
-  bool Run(const std::vector<std::string>& args) override {
-    // Keep the implementation in StatCommandImpl, so the resources used are cleaned up when the
-    // command finishes. This is useful when we need to call some commands multiple times, like
-    // in unit tests.
-    StatCommandImpl impl;
-    return impl.Run(args);
+  size_t count_column_width = 0;
+  size_t name_column_width = 0;
+  size_t comment_column_width = 0;
+  for (auto& summary : summaries) {
+    count_column_width = std::max(count_column_width, summary.readable_count_str.size());
+    name_column_width = std::max(name_column_width, summary.event_type->name.size());
+    comment_column_width = std::max(comment_column_width, summary.comment.size());
   }
-};
 
-StatCommand stat_command;
+  for (auto& summary : summaries) {
+    printf("  %*s  %-*s   # %-*s   (%.0lf%%)\n", static_cast<int>(count_column_width),
+           summary.readable_count_str.c_str(), static_cast<int>(name_column_width),
+           summary.event_type->name.c_str(), static_cast<int>(comment_column_width),
+           summary.comment.c_str(), 1.0 / summary.scale * 100);
+  }
+
+  printf("\nTotal test time: %lf seconds.\n", duration_in_sec);
+  return true;
+}
+
+void RegisterStatCommand() {
+  RegisterCommand("stat", [] { return std::unique_ptr<Command>(new StatCommand); });
+}
index 6a7a1cd..45ed3dc 100644 (file)
 
 #include <gtest/gtest.h>
 
+#include <android-base/stringprintf.h>
+
 #include "command.h"
+#include "get_test_data.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) {
+  if (IsRoot()) {
+    ASSERT_TRUE(StatCmd()->Run({"-a", "sleep", "1"}));
+  }
+}
+
+TEST(stat_cmd, verbose_option) {
+  ASSERT_TRUE(StatCmd()->Run({"--verbose", "sleep", "1"}));
+}
 
-class StatCommandTest : public ::testing::Test {
- protected:
-  virtual void SetUp() {
-    stat_cmd = Command::FindCommandByName("stat");
-    ASSERT_TRUE(stat_cmd != nullptr);
+TEST(stat_cmd, tracepoint_event) {
+  if (IsRoot()) {
+    ASSERT_TRUE(StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"}));
   }
+}
 
- protected:
-  Command* stat_cmd;
-};
+TEST(stat_cmd, event_modifier) {
+  ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
+}
 
-TEST_F(StatCommandTest, no_options) {
-  ASSERT_TRUE(stat_cmd->Run({"stat", "sleep", "1"}));
+void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads) {
+  workloads->clear();
+  for (size_t i = 0; i < count; ++i) {
+    // Create a workload runs longer than profiling time.
+    auto workload = Workload::CreateWorkload({"sleep", "1000"});
+    ASSERT_TRUE(workload != nullptr);
+    ASSERT_TRUE(workload->Start());
+    workloads->push_back(std::move(workload));
+  }
+}
+
+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, "sleep", "1"}));
 }
 
-TEST_F(StatCommandTest, event_option) {
-  ASSERT_TRUE(stat_cmd->Run({"stat", "-e", "cpu-clock,task-clock", "sleep", "1"}));
+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, "sleep", "1"}));
 }
 
-TEST_F(StatCommandTest, system_wide_option) {
-  ASSERT_TRUE(stat_cmd->Run({"stat", "-a", "sleep", "1"}));
+TEST(stat_cmd, no_monitored_threads) {
+  ASSERT_FALSE(StatCmd()->Run({""}));
 }
 
-TEST_F(StatCommandTest, verbose_option) {
-  ASSERT_TRUE(stat_cmd->Run({"stat", "--verbose", "sleep", "1"}));
+TEST(stat_cmd, cpu_option) {
+  ASSERT_TRUE(StatCmd()->Run({"--cpu", "0", "sleep", "1"}));
+  if (IsRoot()) {
+    ASSERT_TRUE(StatCmd()->Run({"--cpu", "0", "-a", "sleep", "1"}));
+  }
 }
index 79cbc44..3416653 100644 (file)
 #include "command.h"
 
 #include <algorithm>
+#include <map>
 #include <string>
 #include <vector>
 
-static std::vector<Command*>& Commands() {
+#include <android-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::vector<Command*> commands;
-  return commands;
+  static std::map<std::string, callback_t> command_map;
+  return command_map;
 }
 
-Command* Command::FindCommandByName(const std::string& cmd_name) {
-  for (auto& command : Commands()) {
-    if (command->Name() == cmd_name) {
-      return command;
-    }
-  }
-  return nullptr;
+void RegisterCommand(const std::string& cmd_name,
+                     std::function<std::unique_ptr<Command>(void)> callback) {
+  CommandMap().insert(std::make_pair(cmd_name, callback));
 }
 
-static bool CompareCommandByName(Command* cmd1, Command* cmd2) {
-  return cmd1->Name() < cmd2->Name();
+void UnRegisterCommand(const std::string& cmd_name) {
+  CommandMap().erase(cmd_name);
 }
 
-const std::vector<Command*>& Command::GetAllCommands() {
-  std::sort(Commands().begin(), Commands().end(), CompareCommandByName);
-  return Commands();
+std::unique_ptr<Command> CreateCommandInstance(const std::string& cmd_name) {
+  auto it = CommandMap().find(cmd_name);
+  return (it == CommandMap().end()) ? nullptr : (it->second)();
 }
 
-void Command::RegisterCommand(Command* cmd) {
-  Commands().push_back(cmd);
+const std::vector<std::string> GetAllCommandNames() {
+  std::vector<std::string> names;
+  for (auto pair : CommandMap()) {
+    names.push_back(pair.first);
+  }
+  return names;
 }
 
-void Command::UnRegisterCommand(Command* cmd) {
-  for (auto it = Commands().begin(); it != Commands().end(); ++it) {
-    if (*it == cmd) {
-      Commands().erase(it);
-      break;
-    }
+extern void RegisterDumpRecordCommand();
+extern void RegisterHelpCommand();
+extern void RegisterListCommand();
+extern void RegisterRecordCommand();
+extern void RegisterReportCommand();
+extern void RegisterStatCommand();
+
+class CommandRegister {
+ public:
+  CommandRegister() {
+    RegisterDumpRecordCommand();
+    RegisterHelpCommand();
+    RegisterReportCommand();
+#if defined(__linux__)
+    RegisterListCommand();
+    RegisterRecordCommand();
+    RegisterStatCommand();
+#endif
   }
-}
+};
+
+CommandRegister command_register;
index 46b49cb..e8fef45 100644 (file)
 #ifndef SIMPLE_PERF_COMMAND_H_
 #define SIMPLE_PERF_COMMAND_H_
 
+#include <functional>
+#include <memory>
 #include <string>
 #include <vector>
 
-#include <base/macros.h>
+#include <android-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) {
-    RegisterCommand(this);
   }
 
   virtual ~Command() {
-    UnRegisterCommand(this);
   }
 
   const std::string& Name() const {
@@ -48,18 +48,22 @@ class Command {
 
   virtual bool Run(const std::vector<std::string>& args) = 0;
 
-  static Command* FindCommandByName(const std::string& cmd_name);
-  static const std::vector<Command*>& GetAllCommands();
+ 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_;
 
-  static void RegisterCommand(Command* cmd);
-  static void UnRegisterCommand(Command* cmd);
-
   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_
index 4a0baa6..18cb569 100644 (file)
@@ -20,7 +20,7 @@
 
 class MockCommand : public Command {
  public:
-  MockCommand(const std::string& name) : Command(name, name + "_short_help", name + "_long_help") {
+  MockCommand() : Command("mock", "mock_short_help", "mock_long_help") {
   }
 
   bool Run(const std::vector<std::string>&) override {
@@ -28,20 +28,18 @@ class MockCommand : public Command {
   }
 };
 
-TEST(command, FindCommandByName) {
-  ASSERT_EQ(Command::FindCommandByName("mock1"), nullptr);
-  {
-    MockCommand mock1("mock1");
-    ASSERT_EQ(Command::FindCommandByName("mock1"), &mock1);
-  }
-  ASSERT_EQ(Command::FindCommandByName("mock1"), nullptr);
+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 = Command::GetAllCommands().size();
-  {
-    MockCommand mock1("mock1");
-    ASSERT_EQ(command_count + 1, Command::GetAllCommands().size());
-  }
-  ASSERT_EQ(command_count, Command::GetAllCommands().size());
+  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_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
new file mode 100644 (file)
index 0000000..61cbc80
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * 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 <unistd.h>
+#if defined(__BIONIC__)
+#include <sys/system_properties.h>
+#endif
+
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <thread>
+#include <unordered_map>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "command.h"
+#include "event_attr.h"
+#include "event_fd.h"
+#include "event_type.h"
+
+static std::unique_ptr<Command> RecordCmd() {
+  return CreateCommandInstance("record");
+}
+
+#if defined(__BIONIC__)
+class ScopedMpdecisionKiller {
+ public:
+  ScopedMpdecisionKiller() {
+    have_mpdecision_ = IsMpdecisionRunning();
+    if (have_mpdecision_) {
+      DisableMpdecision();
+    }
+  }
+
+  ~ScopedMpdecisionKiller() {
+    if (have_mpdecision_) {
+      EnableMpdecision();
+    }
+  }
+
+ private:
+  bool IsMpdecisionRunning() {
+    char value[PROP_VALUE_MAX];
+    int len = __system_property_get("init.svc.mpdecision", value);
+    if (len == 0 || (len > 0 && strstr(value, "stopped") != nullptr)) {
+      return false;
+    }
+    return true;
+  }
+
+  void DisableMpdecision() {
+    int ret = __system_property_set("ctl.stop", "mpdecision");
+    CHECK_EQ(0, ret);
+    // Need to wait until mpdecision is actually stopped.
+    usleep(500000);
+    CHECK(!IsMpdecisionRunning());
+  }
+
+  void EnableMpdecision() {
+    int ret = __system_property_set("ctl.start", "mpdecision");
+    CHECK_EQ(0, ret);
+    usleep(500000);
+    CHECK(IsMpdecisionRunning());
+  }
+
+  bool have_mpdecision_;
+};
+#else
+class ScopedMpdecisionKiller {
+ public:
+  ScopedMpdecisionKiller() {
+  }
+};
+#endif
+
+static bool IsCpuOnline(int cpu) {
+  std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu);
+  std::string content;
+  CHECK(android::base::ReadFileToString(filename, &content)) << "failed to read file " << filename;
+  return (content.find('1') != std::string::npos);
+}
+
+static void SetCpuOnline(int cpu, bool online) {
+  if (IsCpuOnline(cpu) == online) {
+    return;
+  }
+  std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu);
+  std::string content = online ? "1" : "0";
+  CHECK(android::base::WriteStringToFile(content, filename)) << "Write " << content << " to "
+                                                             << filename << " failed";
+  CHECK_EQ(online, IsCpuOnline(cpu)) << "set cpu " << cpu << (online ? " online" : " offline")
+                                     << " failed";
+}
+
+static int GetCpuCount() {
+  return static_cast<int>(sysconf(_SC_NPROCESSORS_CONF));
+}
+
+class CpuOnlineRestorer {
+ public:
+  CpuOnlineRestorer() {
+    for (int cpu = 1; cpu < GetCpuCount(); ++cpu) {
+      online_map_[cpu] = IsCpuOnline(cpu);
+    }
+  }
+
+  ~CpuOnlineRestorer() {
+    for (const auto& pair : online_map_) {
+      SetCpuOnline(pair.first, pair.second);
+    }
+  }
+
+ private:
+  std::unordered_map<int, bool> online_map_;
+};
+
+struct CpuToggleThreadArg {
+  int toggle_cpu;
+  std::atomic<bool> end_flag;
+};
+
+static void CpuToggleThread(CpuToggleThreadArg* arg) {
+  while (!arg->end_flag) {
+    SetCpuOnline(arg->toggle_cpu, true);
+    sleep(1);
+    SetCpuOnline(arg->toggle_cpu, false);
+    sleep(1);
+  }
+}
+
+static bool RecordInChildProcess(int record_cpu, int record_duration_in_second) {
+  pid_t pid = fork();
+  CHECK(pid != -1);
+  if (pid == 0) {
+    std::string cpu_str = android::base::StringPrintf("%d", record_cpu);
+    std::string record_duration_str = android::base::StringPrintf("%d", record_duration_in_second);
+    bool ret = RecordCmd()->Run({"-a", "--cpu", cpu_str, "sleep", record_duration_str});
+    extern bool system_wide_perf_event_open_failed;
+    // It is not an error if perf_event_open failed because of cpu-hotplug.
+    if (!ret && !system_wide_perf_event_open_failed) {
+      exit(1);
+    }
+    exit(0);
+  }
+  int timeout = record_duration_in_second + 10;
+  auto end_time = std::chrono::steady_clock::now() + std::chrono::seconds(timeout);
+  bool child_success = false;
+  while (std::chrono::steady_clock::now() < end_time) {
+    int exit_state;
+    pid_t ret = waitpid(pid, &exit_state, WNOHANG);
+    if (ret == pid) {
+      if (WIFSIGNALED(exit_state) || (WIFEXITED(exit_state) && WEXITSTATUS(exit_state) != 0)) {
+        child_success = false;
+      } else {
+        child_success = true;
+      }
+      break;
+    } else if (ret == -1) {
+      child_success = false;
+      break;
+    }
+    sleep(1);
+  }
+  return child_success;
+}
+
+// http://b/25193162.
+TEST(cpu_offline, offline_while_recording) {
+  ScopedMpdecisionKiller scoped_mpdecision_killer;
+  CpuOnlineRestorer cpuonline_restorer;
+
+  if (GetCpuCount() == 1) {
+    GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system.";
+    return;
+  }
+  for (int i = 1; i < GetCpuCount(); ++i) {
+    if (!IsCpuOnline(i)) {
+      SetCpuOnline(i, true);
+    }
+  }
+  // Start cpu hotplugger.
+  int test_cpu = GetCpuCount() - 1;
+  CpuToggleThreadArg cpu_toggle_arg;
+  cpu_toggle_arg.toggle_cpu = test_cpu;
+  cpu_toggle_arg.end_flag = false;
+  std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
+
+  const std::chrono::hours test_duration(10);  // Test for 10 hours.
+  const double RECORD_DURATION_IN_SEC = 2.9;
+  const double SLEEP_DURATION_IN_SEC = 1.3;
+
+  auto end_time = std::chrono::steady_clock::now() + test_duration;
+  size_t iterations = 0;
+  while (std::chrono::steady_clock::now() < end_time) {
+    iterations++;
+    GTEST_LOG_(INFO) << "Test for " << iterations << " times.";
+    ASSERT_TRUE(RecordInChildProcess(test_cpu, RECORD_DURATION_IN_SEC));
+    usleep(static_cast<useconds_t>(SLEEP_DURATION_IN_SEC * 1e6));
+  }
+  cpu_toggle_arg.end_flag = true;
+  cpu_toggle_thread.join();
+}
+
+static std::unique_ptr<EventFd> OpenHardwareEventOnCpu(int cpu) {
+  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(), cpu);
+}
+
+// http://b/19863147.
+TEST(cpu_offline, offline_while_recording_on_another_cpu) {
+  ScopedMpdecisionKiller scoped_mpdecision_killer;
+  CpuOnlineRestorer cpuonline_restorer;
+
+  if (GetCpuCount() == 1) {
+    GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system.";
+    return;
+  }
+
+  const size_t TEST_ITERATION_COUNT = 10u;
+  for (size_t i = 0; i < TEST_ITERATION_COUNT; ++i) {
+    int record_cpu = 0;
+    int toggle_cpu = GetCpuCount() - 1;
+    SetCpuOnline(toggle_cpu, true);
+    std::unique_ptr<EventFd> event_fd = OpenHardwareEventOnCpu(record_cpu);
+    ASSERT_TRUE(event_fd != nullptr);
+    SetCpuOnline(toggle_cpu, false);
+    event_fd = nullptr;
+    event_fd = OpenHardwareEventOnCpu(record_cpu);
+    ASSERT_TRUE(event_fd != nullptr);
+  }
+}
+
+int main(int argc, char** argv) {
+  InitLogging(argv, android::base::StderrLogger);
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
new file mode 100644 (file)
index 0000000..9c33667
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * 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 <string.h>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "environment.h"
+#include "read_apk.h"
+#include "read_elf.h"
+#include "utils.h"
+
+static OneTimeFreeAllocator symbol_name_allocator;
+
+Symbol::Symbol(const std::string& name, uint64_t addr, uint64_t len)
+    : addr(addr),
+      len(len),
+      name_(symbol_name_allocator.AllocateString(name)),
+      demangled_name_(nullptr) {
+}
+
+const char* Symbol::DemangledName() const {
+  if (demangled_name_ == nullptr) {
+    const std::string s = Dso::Demangle(name_);
+    if (s == name_) {
+      demangled_name_ = name_;
+    } else {
+      demangled_name_ = symbol_name_allocator.AllocateString(s);
+    }
+  }
+  return demangled_name_;
+}
+
+bool Dso::demangle_ = true;
+std::string Dso::symfs_dir_;
+std::string Dso::vmlinux_;
+std::unordered_map<std::string, BuildId> Dso::build_id_map_;
+size_t Dso::dso_count_;
+
+void Dso::SetDemangle(bool demangle) {
+  demangle_ = demangle;
+}
+
+extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status);
+
+std::string Dso::Demangle(const std::string& name) {
+  if (!demangle_) {
+    return 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();
+  }
+  std::string result = name;
+  char* demangled_name = __cxa_demangle(mangled_str, nullptr, nullptr, &status);
+  if (status == 0) {
+    if (is_linker_symbol) {
+      result = std::string("[linker]") + demangled_name;
+    } else {
+      result = demangled_name;
+    }
+    free(demangled_name);
+  } else if (is_linker_symbol) {
+    result = std::string("[linker]") + mangled_str;
+  }
+  return result;
+}
+
+bool Dso::SetSymFsDir(const std::string& symfs_dir) {
+  std::string dirname = symfs_dir;
+  if (!dirname.empty()) {
+    if (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;
+    }
+  }
+  symfs_dir_ = dirname;
+  return true;
+}
+
+void Dso::SetVmlinux(const std::string& vmlinux) {
+  vmlinux_ = vmlinux;
+}
+
+void Dso::SetBuildIds(const std::vector<std::pair<std::string, BuildId>>& build_ids) {
+  std::unordered_map<std::string, BuildId> map;
+  for (auto& pair : build_ids) {
+    LOG(DEBUG) << "build_id_map: " << pair.first << ", " << pair.second.ToString();
+    map.insert(pair);
+  }
+  build_id_map_ = std::move(map);
+}
+
+BuildId Dso::GetExpectedBuildId(const std::string& filename) {
+  auto it = build_id_map_.find(filename);
+  if (it != build_id_map_.end()) {
+    return it->second;
+  }
+  return BuildId();
+}
+
+std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_path) {
+  std::string path = dso_path;
+  if (dso_type == DSO_KERNEL) {
+    path = "[kernel.kallsyms]";
+  }
+  return std::unique_ptr<Dso>(new Dso(dso_type, path));
+}
+
+Dso::Dso(DsoType type, const std::string& path)
+    : type_(type), path_(path), min_vaddr_(std::numeric_limits<uint64_t>::max()), is_loaded_(false) {
+  dso_count_++;
+}
+
+Dso::~Dso() {
+  if (--dso_count_ == 0) {
+    symbol_name_allocator.Clear();
+  }
+}
+
+struct SymbolComparator {
+  bool operator()(const Symbol& symbol1, const Symbol& symbol2) {
+    return symbol1.addr < symbol2.addr;
+  }
+};
+
+std::string Dso::GetAccessiblePath() const {
+  return symfs_dir_ + path_;
+}
+
+const Symbol* Dso::FindSymbol(uint64_t vaddr_in_dso) {
+  if (!is_loaded_) {
+    is_loaded_ = true;
+    if (!Load()) {
+      LOG(DEBUG) << "failed to load dso: " << path_;
+      return nullptr;
+    }
+  }
+
+  auto it = std::upper_bound(symbols_.begin(), symbols_.end(), Symbol("", vaddr_in_dso, 0),
+                             SymbolComparator());
+  if (it != symbols_.begin()) {
+    --it;
+    if (it->addr <= vaddr_in_dso && it->addr + it->len > vaddr_in_dso) {
+      return &*it;
+    }
+  }
+  return nullptr;
+}
+
+uint64_t Dso::MinVirtualAddress() {
+  if (min_vaddr_ == std::numeric_limits<uint64_t>::max()) {
+    min_vaddr_ = 0;
+    if (type_ == DSO_ELF_FILE) {
+      BuildId build_id = GetExpectedBuildId(GetAccessiblePath());
+
+      uint64_t addr;
+      if (ReadMinExecutableVirtualAddressFromElfFile(GetAccessiblePath(), build_id, &addr)) {
+        min_vaddr_ = addr;
+      }
+    }
+  }
+  return min_vaddr_;
+}
+
+bool Dso::Load() {
+  bool result = false;
+  switch (type_) {
+    case DSO_KERNEL:
+      result = LoadKernel();
+      break;
+    case DSO_KERNEL_MODULE:
+      result = LoadKernelModule();
+      break;
+    case DSO_ELF_FILE: {
+      if (std::get<0>(SplitUrlInApk(path_))) {
+        result = LoadEmbeddedElfFile();
+      } else {
+        result = LoadElfFile();
+      }
+      break;
+    }
+  }
+  if (result) {
+    std::sort(symbols_.begin(), symbols_.end(), SymbolComparator());
+    FixupSymbolLength();
+  }
+  return result;
+}
+
+static bool IsKernelFunctionSymbol(const KernelSymbol& symbol) {
+  return (symbol.type == 'T' || symbol.type == 't' || symbol.type == 'W' || symbol.type == 'w');
+}
+
+bool Dso::KernelSymbolCallback(const KernelSymbol& kernel_symbol, Dso* dso) {
+  if (IsKernelFunctionSymbol(kernel_symbol)) {
+    dso->InsertSymbol(Symbol(kernel_symbol.name, kernel_symbol.addr, 0));
+  }
+  return false;
+}
+
+void Dso::VmlinuxSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso) {
+  if (elf_symbol.is_func) {
+    dso->InsertSymbol(Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len));
+  }
+}
+
+bool Dso::LoadKernel() {
+  BuildId build_id = GetExpectedBuildId(DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID);
+  if (!vmlinux_.empty()) {
+    ParseSymbolsFromElfFile(vmlinux_, build_id,
+                            std::bind(VmlinuxSymbolCallback, std::placeholders::_1, this));
+  } else {
+    if (!build_id.IsEmpty()) {
+      BuildId real_build_id;
+      GetKernelBuildId(&real_build_id);
+      bool match = (build_id == real_build_id);
+      LOG(DEBUG) << "check kernel build id (" << (match ? "match" : "mismatch") << "): expected "
+                 << build_id.ToString() << ", real " << real_build_id.ToString();
+      if (!match) {
+        return false;
+      }
+    }
+
+    ProcessKernelSymbols("/proc/kallsyms",
+                         std::bind(&KernelSymbolCallback, std::placeholders::_1, this));
+    bool allZero = true;
+    for (auto& symbol : symbols_) {
+      if (symbol.addr != 0) {
+        allZero = false;
+        break;
+      }
+    }
+    if (allZero) {
+      LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. Check "
+                      "/proc/sys/kernel/kptr_restrict if possible.";
+      symbols_.clear();
+      return false;
+    }
+  }
+  return true;
+}
+
+void Dso::ElfFileSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso,
+                                bool (*filter)(const ElfFileSymbol&)) {
+  if (filter(elf_symbol)) {
+    dso->InsertSymbol(Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len));
+  }
+}
+
+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);
+}
+
+bool Dso::LoadKernelModule() {
+  BuildId build_id = GetExpectedBuildId(path_);
+  ParseSymbolsFromElfFile(
+      symfs_dir_ + path_, build_id,
+      std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForKernelModule));
+  return true;
+}
+
+static bool SymbolFilterForDso(const ElfFileSymbol& elf_symbol) {
+  return elf_symbol.is_func || (elf_symbol.is_label && elf_symbol.is_in_text_section);
+}
+
+bool Dso::LoadElfFile() {
+  bool loaded = false;
+  BuildId build_id = GetExpectedBuildId(GetAccessiblePath());
+
+  if (symfs_dir_.empty()) {
+    // Linux host can store debug shared libraries in /usr/lib/debug.
+    loaded = ParseSymbolsFromElfFile(
+        "/usr/lib/debug" + path_, build_id,
+        std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForDso));
+  }
+  if (!loaded) {
+    loaded = ParseSymbolsFromElfFile(
+        GetAccessiblePath(), build_id,
+        std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForDso));
+  }
+  return loaded;
+}
+
+bool Dso::LoadEmbeddedElfFile() {
+  std::string path = GetAccessiblePath();
+  BuildId build_id = GetExpectedBuildId(path);
+  auto tuple = SplitUrlInApk(path);
+  CHECK(std::get<0>(tuple));
+  return ParseSymbolsFromApkFile(std::get<1>(tuple), std::get<2>(tuple), build_id,
+                                 std::bind(ElfFileSymbolCallback, std::placeholders::_1,
+                                           this, SymbolFilterForDso));
+}
+
+void Dso::InsertSymbol(const Symbol& symbol) {
+  symbols_.push_back(symbol);
+}
+
+void Dso::FixupSymbolLength() {
+  Symbol* prev_symbol = nullptr;
+  for (auto& symbol : symbols_) {
+    if (prev_symbol != nullptr && prev_symbol->len == 0) {
+      prev_symbol->len = symbol.addr - prev_symbol->addr;
+    }
+    prev_symbol = &symbol;
+  }
+  if (prev_symbol != nullptr && prev_symbol->len == 0) {
+    prev_symbol->len = std::numeric_limits<unsigned long long>::max() - prev_symbol->addr;
+  }
+}
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
new file mode 100644 (file)
index 0000000..9697319
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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 <string>
+#include <unordered_map>
+#include <vector>
+
+#include "build_id.h"
+
+struct Symbol {
+  uint64_t addr;
+  uint64_t len;
+
+  Symbol(const std::string& name, uint64_t addr, uint64_t len);
+  const char* Name() const {
+    return name_;
+  }
+
+  const char* DemangledName() const;
+
+ private:
+  const char* name_;
+  mutable const char* demangled_name_;
+};
+
+enum DsoType {
+  DSO_KERNEL,
+  DSO_KERNEL_MODULE,
+  DSO_ELF_FILE,
+};
+
+struct KernelSymbol;
+struct ElfFileSymbol;
+
+struct Dso {
+ public:
+  static void SetDemangle(bool demangle);
+  static std::string Demangle(const std::string& name);
+  static bool SetSymFsDir(const std::string& symfs_dir);
+  static void SetVmlinux(const std::string& vmlinux);
+  static void SetBuildIds(const std::vector<std::pair<std::string, BuildId>>& build_ids);
+
+  static std::unique_ptr<Dso> CreateDso(DsoType dso_type, const std::string& dso_path = "");
+
+  ~Dso();
+
+  // Return the path recorded in perf.data.
+  const std::string& Path() const {
+    return path_;
+  }
+
+  // Return the accessible path. It may be the same as Path(), or
+  // return the path with prefix set by SetSymFsDir().
+  std::string GetAccessiblePath() const;
+
+  // Return the minimum virtual address in program header.
+  uint64_t MinVirtualAddress();
+
+  const Symbol* FindSymbol(uint64_t vaddr_in_dso);
+
+ private:
+  static BuildId GetExpectedBuildId(const std::string& filename);
+  static bool KernelSymbolCallback(const KernelSymbol& kernel_symbol, Dso* dso);
+  static void VmlinuxSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso);
+  static void ElfFileSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso,
+                                    bool (*filter)(const ElfFileSymbol&));
+
+  static bool demangle_;
+  static std::string symfs_dir_;
+  static std::string vmlinux_;
+  static std::unordered_map<std::string, BuildId> build_id_map_;
+  static size_t dso_count_;
+
+  Dso(DsoType type, const std::string& path);
+  bool Load();
+  bool LoadKernel();
+  bool LoadKernelModule();
+  bool LoadElfFile();
+  bool LoadEmbeddedElfFile();
+  void InsertSymbol(const Symbol& symbol);
+  void FixupSymbolLength();
+
+  const DsoType type_;
+  const std::string path_;
+  uint64_t min_vaddr_;
+  std::vector<Symbol> symbols_;
+  bool is_loaded_;
+};
+
+#endif  // SIMPLE_PERF_DSO_H_
diff --git a/simpleperf/dwarf_unwind.cpp b/simpleperf/dwarf_unwind.cpp
new file mode 100644 (file)
index 0000000..ae2e1a1
--- /dev/null
@@ -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 "dwarf_unwind.h"
+
+#include <ucontext.h>
+
+#include <backtrace/Backtrace.h>
+#include <android-base/logging.h>
+
+#include "thread_tree.h"
+
+#define SetUContextReg(dst, perf_regno)          \
+  do {                                           \
+    uint64_t value;                              \
+    if (GetRegValue(regs, perf_regno, &value)) { \
+      dst = value;                               \
+    }                                            \
+  } while (0)
+
+static ucontext_t BuildUContextFromRegs(const RegSet& regs __attribute__((unused))) {
+  ucontext_t ucontext;
+  memset(&ucontext, 0, sizeof(ucontext));
+#if defined(__i386__)
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_GS], PERF_REG_X86_GS);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_FS], PERF_REG_X86_FS);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_ES], PERF_REG_X86_ES);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_DS], PERF_REG_X86_DS);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_EAX], PERF_REG_X86_AX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_EBX], PERF_REG_X86_BX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_ECX], PERF_REG_X86_CX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_EDX], PERF_REG_X86_DX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_ESI], PERF_REG_X86_SI);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_EDI], PERF_REG_X86_DI);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_EBP], PERF_REG_X86_BP);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_EIP], PERF_REG_X86_IP);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_ESP], PERF_REG_X86_SP);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_CS], PERF_REG_X86_CS);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_EFL], PERF_REG_X86_FLAGS);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_SS], PERF_REG_X86_SS);
+#elif defined(__x86_64__)
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R8], PERF_REG_X86_R8);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R9], PERF_REG_X86_R9);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R10], PERF_REG_X86_R10);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R11], PERF_REG_X86_R11);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R12], PERF_REG_X86_R12);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R13], PERF_REG_X86_R13);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R14], PERF_REG_X86_R14);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_R15], PERF_REG_X86_R15);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RDI], PERF_REG_X86_DI);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RSI], PERF_REG_X86_SI);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RBP], PERF_REG_X86_BP);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RBX], PERF_REG_X86_BX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RDX], PERF_REG_X86_DX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RAX], PERF_REG_X86_AX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RCX], PERF_REG_X86_CX);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RSP], PERF_REG_X86_SP);
+  SetUContextReg(ucontext.uc_mcontext.gregs[REG_RIP], PERF_REG_X86_IP);
+#elif defined(__aarch64__)
+  for (size_t i = PERF_REG_ARM64_X0; i < PERF_REG_ARM64_MAX; ++i) {
+    SetUContextReg(ucontext.uc_mcontext.regs[i], i);
+  }
+#elif defined(__arm__)
+  SetUContextReg(ucontext.uc_mcontext.arm_r0, PERF_REG_ARM_R0);
+  SetUContextReg(ucontext.uc_mcontext.arm_r1, PERF_REG_ARM_R1);
+  SetUContextReg(ucontext.uc_mcontext.arm_r2, PERF_REG_ARM_R2);
+  SetUContextReg(ucontext.uc_mcontext.arm_r3, PERF_REG_ARM_R3);
+  SetUContextReg(ucontext.uc_mcontext.arm_r4, PERF_REG_ARM_R4);
+  SetUContextReg(ucontext.uc_mcontext.arm_r5, PERF_REG_ARM_R5);
+  SetUContextReg(ucontext.uc_mcontext.arm_r6, PERF_REG_ARM_R6);
+  SetUContextReg(ucontext.uc_mcontext.arm_r7, PERF_REG_ARM_R7);
+  SetUContextReg(ucontext.uc_mcontext.arm_r8, PERF_REG_ARM_R8);
+  SetUContextReg(ucontext.uc_mcontext.arm_r9, PERF_REG_ARM_R9);
+  SetUContextReg(ucontext.uc_mcontext.arm_r10, PERF_REG_ARM_R10);
+  SetUContextReg(ucontext.uc_mcontext.arm_fp, PERF_REG_ARM_FP);
+  SetUContextReg(ucontext.uc_mcontext.arm_ip, PERF_REG_ARM_IP);
+  SetUContextReg(ucontext.uc_mcontext.arm_sp, PERF_REG_ARM_SP);
+  SetUContextReg(ucontext.uc_mcontext.arm_lr, PERF_REG_ARM_LR);
+  SetUContextReg(ucontext.uc_mcontext.arm_pc, PERF_REG_ARM_PC);
+#endif
+  return ucontext;
+}
+
+std::vector<uint64_t> UnwindCallChain(ArchType arch, const ThreadEntry& thread,
+                                      const RegSet& regs, const std::vector<char>& stack) {
+  std::vector<uint64_t> result;
+  if (arch != GetBuildArch()) {
+    LOG(ERROR) << "can't unwind data recorded on a different architecture";
+    return result;
+  }
+  uint64_t sp_reg_value;
+  if (!GetSpRegValue(regs, arch, &sp_reg_value)) {
+    LOG(ERROR) << "can't get sp reg value";
+    return result;
+  }
+  uint64_t stack_addr = sp_reg_value;
+
+  std::vector<backtrace_map_t> bt_maps(thread.maps.size());
+  size_t map_index = 0;
+  for (auto& map : thread.maps) {
+    backtrace_map_t& bt_map = bt_maps[map_index++];
+    bt_map.start = map->start_addr;
+    bt_map.end = map->start_addr + map->len;
+    bt_map.offset = map->pgoff;
+    bt_map.name = map->dso->GetAccessiblePath();
+  }
+  std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(thread.pid, bt_maps));
+
+  backtrace_stackinfo_t stack_info;
+  stack_info.start = stack_addr;
+  stack_info.end = stack_addr + stack.size();
+  stack_info.data = reinterpret_cast<const uint8_t*>(stack.data());
+
+  std::unique_ptr<Backtrace> backtrace(
+      Backtrace::CreateOffline(thread.pid, thread.tid, backtrace_map.get(), stack_info, true));
+  ucontext_t ucontext = BuildUContextFromRegs(regs);
+  if (backtrace->Unwind(0, &ucontext)) {
+    for (auto it = backtrace->begin(); it != backtrace->end(); ++it) {
+      result.push_back(it->pc);
+    }
+  }
+  return result;
+}
diff --git a/simpleperf/dwarf_unwind.h b/simpleperf/dwarf_unwind.h
new file mode 100644 (file)
index 0000000..6982b05
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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_DWARF_UNWIND_H_
+#define SIMPLE_PERF_DWARF_UNWIND_H_
+
+#include <vector>
+
+#include "perf_regs.h"
+
+namespace simpleperf {
+struct ThreadEntry;
+}
+
+using ThreadEntry = simpleperf::ThreadEntry;
+
+std::vector<uint64_t> UnwindCallChain(ArchType arch, const ThreadEntry& thread, const RegSet& regs,
+                                      const std::vector<char>& stack);
+
+#endif  // SIMPLE_PERF_DWARF_UNWIND_H_
index 0270b24..5a8552a 100644 (file)
 #include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
+
+#include <limits>
+#include <set>
 #include <unordered_map>
 #include <vector>
 
-#include <base/file.h>
-#include <base/logging.h>
-#include <base/strings.h>
-#include <base/stringprintf.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/stringprintf.h>
+
+#if defined(__ANDROID__)
+#include <sys/system_properties.h>
+#endif
 
 #include "read_elf.h"
 #include "utils.h"
 
+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_;
+};
+
 std::vector<int> GetOnlineCpus() {
   std::vector<int> result;
   FILE* fp = fopen("/sys/devices/system/cpu/online", "re");
@@ -41,27 +76,29 @@ std::vector<int> GetOnlineCpus() {
   LineReader reader(fp);
   char* line;
   if ((line = reader.ReadLine()) != nullptr) {
-    result = GetOnlineCpusFromString(line);
+    result = GetCpusFromString(line);
   }
   CHECK(!result.empty()) << "can't get online cpu information";
   return result;
 }
 
-std::vector<int> GetOnlineCpusFromString(const std::string& s) {
-  std::vector<int> result;
+std::vector<int> GetCpusFromString(const std::string& s) {
+  std::set<int> cpu_set;
   bool have_dash = false;
   const char* p = s.c_str();
   char* endp;
+  int last_cpu;
   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);
+    if (have_dash && !cpu_set.empty()) {
+      for (int t = last_cpu + 1; t < cpu; ++t) {
+        cpu_set.insert(t);
       }
     }
     have_dash = false;
-    result.push_back(cpu);
+    cpu_set.insert(cpu);
+    last_cpu = cpu;
     p = endp;
     while (!isdigit(*p) && *p != '\0') {
       if (*p == '-') {
@@ -70,7 +107,7 @@ std::vector<int> GetOnlineCpusFromString(const std::string& s) {
       ++p;
     }
   }
-  return result;
+  return std::vector<int>(cpu_set.begin(), cpu_set.end());
 }
 
 bool ProcessKernelSymbols(const std::string& symbol_file,
@@ -108,37 +145,8 @@ bool ProcessKernelSymbols(const std::string& symbol_file,
   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;
+static std::vector<KernelMmap> GetLoadedModules() {
+  std::vector<KernelMmap> 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.
@@ -152,7 +160,7 @@ std::vector<ModuleMmap> GetLoadedModules() {
     char name[reader.MaxLineSize()];
     uint64_t addr;
     if (sscanf(line, "%s%*lu%*u%*s%*s 0x%" PRIx64, name, &addr) == 2) {
-      ModuleMmap map;
+      KernelMmap map;
       map.name = name;
       map.start_addr = addr;
       result.push_back(map);
@@ -182,7 +190,7 @@ static void GetAllModuleFiles(const std::string& path,
     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));
+      module_file_map->insert(std::make_pair(module_name, path + "/" + name));
     }
   }
   for (auto& name : subdirs) {
@@ -190,9 +198,9 @@ static void GetAllModuleFiles(const std::string& path,
   }
 }
 
-static std::vector<ModuleMmap> GetModulesInUse() {
+static std::vector<KernelMmap> GetModulesInUse() {
   // TODO: There is no /proc/modules or /lib/modules on Android, find methods work on it.
-  std::vector<ModuleMmap> module_mmaps = GetLoadedModules();
+  std::vector<KernelMmap> 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;
@@ -206,24 +214,23 @@ static std::vector<ModuleMmap> GetModulesInUse() {
   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;
-  }
+void GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<KernelMmap>* module_mmaps) {
   kernel_mmap->name = DEFAULT_KERNEL_MMAP_NAME;
+  kernel_mmap->start_addr = 0;
+  kernel_mmap->filepath = kernel_mmap->name;
   *module_mmaps = GetModulesInUse();
+  for (auto& map : *module_mmaps) {
+    if (map.filepath.empty()) {
+      map.filepath = "[" + map.name + "]";
+    }
+  }
+
   if (module_mmaps->size() == 0) {
-    kernel_mmap->len = ULLONG_MAX - kernel_mmap->start_addr;
+    kernel_mmap->len = std::numeric_limits<unsigned long long>::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);
+        [](const KernelMmap& m1, const KernelMmap& m2) { return m1.start_addr < m2.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;
@@ -238,15 +245,9 @@ bool GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<ModuleMmap>* m
             (*module_mmaps)[i + 1].start_addr - (*module_mmaps)[i].start_addr - 1;
       }
     }
-    module_mmaps->back().len = ULLONG_MAX - module_mmaps->back().start_addr;
+    module_mmaps->back().len =
+        std::numeric_limits<unsigned long long>::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) {
@@ -273,26 +274,36 @@ static bool ReadThreadNameAndTgid(const std::string& status_file, std::string* c
   return false;
 }
 
-static bool GetThreadComm(pid_t pid, std::vector<ThreadComm>* thread_comms) {
+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)) {
+  for (const auto& name : subdirs) {
+    int tid;
+    if (!android::base::ParseInt(name.c_str(), &tid, 0)) {
       continue;
     }
-    std::string status_file = task_dirname + "/" + name + "/status";
+    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)) {
-      return false;
+      continue;
     }
+    CHECK_EQ(pid, tgid);
     ThreadComm thread;
     thread.tid = tid;
-    thread.tgid = tgid;
+    thread.pid = pid;
     thread.comm = comm;
-    thread.is_process = (tid == pid);
     thread_comms->push_back(thread);
   }
   return true;
@@ -303,8 +314,8 @@ bool GetThreadComms(std::vector<ThreadComm>* thread_comms) {
   std::vector<std::string> subdirs;
   GetEntriesInDir("/proc", nullptr, &subdirs);
   for (auto& name : subdirs) {
-    pid_t pid;
-    if (!StringToPid(name, &pid)) {
+    int pid;
+    if (!android::base::ParseInt(name.c_str(), &pid, 0)) {
       continue;
     }
     if (!GetThreadComm(pid, thread_comms)) {
@@ -356,3 +367,113 @@ 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 (const auto& s : strs) {
+    int pid;
+    if (!android::base::ParseInt(s.c_str(), &pid, 0)) {
+      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 (const auto& s : strs) {
+    int tid;
+    if (!android::base::ParseInt(s.c_str(), &tid, 0)) {
+      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;
+}
+
+bool GetExecPath(std::string* exec_path) {
+  char path[PATH_MAX];
+  ssize_t path_len = readlink("/proc/self/exe", path, sizeof(path));
+  if (path_len <= 0 || path_len >= static_cast<ssize_t>(sizeof(path))) {
+    PLOG(ERROR) << "readlink failed";
+    return false;
+  }
+  path[path_len] = '\0';
+  *exec_path = path;
+  return true;
+}
+
+/*
+ * perf event paranoia level:
+ *  -1 - not paranoid at all
+ *   0 - disallow raw tracepoint access for unpriv
+ *   1 - disallow cpu events for unpriv
+ *   2 - disallow kernel profiling for unpriv
+ *   3 - disallow user profiling for unpriv
+ */
+static bool ReadPerfEventParanoid(int* value) {
+  std::string s;
+  if (!android::base::ReadFileToString("/proc/sys/kernel/perf_event_paranoid", &s)) {
+    PLOG(ERROR) << "failed to read /proc/sys/kernel/perf_event_paranoid";
+    return false;
+  }
+  s = android::base::Trim(s);
+  if (!android::base::ParseInt(s.c_str(), value)) {
+    PLOG(ERROR) << "failed to parse /proc/sys/kernel/perf_event_paranoid: " << s;
+    return false;
+  }
+  return true;
+}
+
+static const char* GetLimitLevelDescription(int limit_level) {
+  switch (limit_level) {
+    case -1: return "unlimited";
+    case 0: return "disallowing raw tracepoint access for unpriv";
+    case 1: return "disallowing cpu events for unpriv";
+    case 2: return "disallowing kernel profiling for unpriv";
+    case 3: return "disallowing user profiling for unpriv";
+    default: return "unknown level";
+  }
+}
+
+bool CheckPerfEventLimit() {
+  // root is not limited by /proc/sys/kernel/perf_event_paranoid.
+  if (IsRoot()) {
+    return true;
+  }
+  int limit_level;
+  if (!ReadPerfEventParanoid(&limit_level)) {
+    return false;
+  }
+  if (limit_level <= 1) {
+    return true;
+  }
+#if defined(__ANDROID__)
+  // Try to enable perf_event_paranoid by setprop security.perf_harden=0.
+  if (__system_property_set("security.perf_harden", "0") == 0) {
+    sleep(1);
+    if (ReadPerfEventParanoid(&limit_level) && limit_level <= 1) {
+      return true;
+    }
+  }
+  LOG(WARNING) << "/proc/sys/kernel/perf_event_paranoid is " << limit_level
+      << ", " << GetLimitLevelDescription(limit_level) << ".";
+  LOG(WARNING) << "Try using `adb shell setprop security.perf_harden 0` to allow profiling.";
+#else
+  LOG(WARNING) << "/proc/sys/kernel/perf_event_paranoid is " << limit_level
+      << ", " << GetLimitLevelDescription(limit_level) << ".";
+#endif
+  return true;
+}
index f81005c..6da632b 100644 (file)
 #ifndef SIMPLE_PERF_ENVIRONMENT_H_
 #define SIMPLE_PERF_ENVIRONMENT_H_
 
+#include <sys/types.h>
+
 #include <functional>
+#include <set>
 #include <string>
 #include <vector>
+
 #include "build_id.h"
 
 std::vector<int> GetOnlineCpus();
+std::vector<int> GetCpusFromString(const std::string& s);
 
-static const char* DEFAULT_KERNEL_MMAP_NAME = "[kernel.kallsyms]_text";
+constexpr 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);
+void GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<KernelMmap>* module_mmaps);
 
 struct ThreadComm {
-  pid_t tgid, tid;
+  pid_t pid, tid;
   std::string comm;
-  bool is_process;
 };
 
 bool GetThreadComms(std::vector<ThreadComm>* thread_comms);
 
-static const char* DEFAULT_EXECNAME_FOR_THREAD_MMAP = "//anon";
+constexpr char DEFAULT_EXECNAME_FOR_THREAD_MMAP[] = "//anon";
 
 struct ThreadMmap {
   uint64_t start_addr;
@@ -62,14 +59,17 @@ struct ThreadMmap {
 
 bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps);
 
-static const char* DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID = "[kernel.kallsyms]";
+constexpr char DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID[] = "[kernel.kallsyms]";
 
 bool GetKernelBuildId(BuildId* build_id);
 bool GetModuleBuildId(const std::string& module_name, BuildId* build_id);
 
-// Expose the following functions for unit tests.
-std::vector<int> GetOnlineCpusFromString(const std::string& s);
+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);
 
+bool GetExecPath(std::string* exec_path);
+
+// Expose the following functions for unit tests.
 struct KernelSymbol {
   uint64_t addr;
   char type;
@@ -79,5 +79,6 @@ struct KernelSymbol {
 
 bool ProcessKernelSymbols(const std::string& symbol_file,
                           std::function<bool(const KernelSymbol&)> callback);
+bool CheckPerfEventLimit();
 
 #endif  // SIMPLE_PERF_ENVIRONMENT_H_
diff --git a/simpleperf/environment_fake.cpp b/simpleperf/environment_fake.cpp
new file mode 100644 (file)
index 0000000..fdcf814
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+// Add fake functions to build successfully on non-linux environments.
+#include "environment.h"
+
+bool ProcessKernelSymbols(const std::string&, std::function<bool(const KernelSymbol&)>) {
+  return false;
+}
+
+bool GetKernelBuildId(BuildId*) {
+  return false;
+}
index 3cf81fa..6bca7b8 100644 (file)
 #include <gtest/gtest.h>
 
 #include <functional>
-#include <base/file.h>
+#include <android-base/file.h>
+#include <android-base/test_utils.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}));
+TEST(environment, GetCpusFromString) {
+  ASSERT_EQ(GetCpusFromString(""), std::vector<int>());
+  ASSERT_EQ(GetCpusFromString("0-2"), std::vector<int>({0, 1, 2}));
+  ASSERT_EQ(GetCpusFromString("0,2-3"), std::vector<int>({0, 2, 3}));
+  ASSERT_EQ(GetCpusFromString("1,0-3,3,4"), std::vector<int>({0, 1, 2, 3, 4}));
 }
 
-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));
+static bool ModulesMatch(const char* p, const char* q) {
+  if (p == nullptr && q == nullptr) {
+    return true;
+  }
+  if (p != nullptr && q != nullptr) {
+    return strcmp(p, q) == 0;
+  }
+  return false;
+}
+
+static bool KernelSymbolsMatch(const KernelSymbol& sym1, const KernelSymbol& sym2) {
+  return sym1.addr == sym2.addr &&
+         sym1.type == sym2.type &&
+         strcmp(sym1.name, sym2.name) == 0 &&
+         ModulesMatch(sym1.module, sym2.module);
 }
 
 TEST(environment, ProcessKernelSymbols) {
@@ -38,25 +51,24 @@ TEST(environment, ProcessKernelSymbols) {
       "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));
+  TemporaryFile tempfile;
+  ASSERT_TRUE(android::base::WriteStringToFile(data, tempfile.path));
   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)));
+      tempfile.path, std::bind(&KernelSymbolsMatch, 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)));
+      tempfile.path, std::bind(&KernelSymbolsMatch, 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));
+      tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
 }
index 2b05931..c9449b1 100644 (file)
@@ -21,7 +21,7 @@
 #include <string>
 #include <unordered_map>
 
-#include <base/logging.h>
+#include <android-base/logging.h>
 
 #include "event_type.h"
 #include "utils.h"
@@ -46,17 +46,20 @@ static std::string BitsToString(const std::string& name, uint64_t bits,
 
 static std::string SampleTypeToString(uint64_t sample_type) {
   static std::vector<std::pair<int, std::string>> sample_type_names = {
-      {PERF_SAMPLE_IP, "ip"},
-      {PERF_SAMPLE_TID, "tid"},
-      {PERF_SAMPLE_TIME, "time"},
       {PERF_SAMPLE_ADDR, "addr"},
-      {PERF_SAMPLE_READ, "read"},
+      {PERF_SAMPLE_BRANCH_STACK, "branch_stack"},
       {PERF_SAMPLE_CALLCHAIN, "callchain"},
-      {PERF_SAMPLE_ID, "id"},
       {PERF_SAMPLE_CPU, "cpu"},
+      {PERF_SAMPLE_ID, "id"},
+      {PERF_SAMPLE_IP, "ip"},
       {PERF_SAMPLE_PERIOD, "period"},
-      {PERF_SAMPLE_STREAM_ID, "stream_id"},
       {PERF_SAMPLE_RAW, "raw"},
+      {PERF_SAMPLE_READ, "read"},
+      {PERF_SAMPLE_REGS_USER, "regs_user"},
+      {PERF_SAMPLE_STACK_USER, "stack_user"},
+      {PERF_SAMPLE_STREAM_ID, "stream_id"},
+      {PERF_SAMPLE_TID, "tid"},
+      {PERF_SAMPLE_TIME, "time"},
   };
   return BitsToString("sample_type", sample_type, sample_type_names);
 }
@@ -79,23 +82,31 @@ perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type) {
   attr.config = event_type.config;
   attr.mmap = 1;
   attr.comm = 1;
-  attr.disabled = 1;
+  attr.disabled = 0;
   // 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;
+  attr.sample_type |=
+      PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_PERIOD | PERF_SAMPLE_CPU;
+
+  if (attr.type == PERF_TYPE_TRACEPOINT) {
+    attr.sample_freq = 0;
+    attr.sample_period = 1;
+    // Tracepoint information are stored in raw data in sample records.
+    attr.sample_type |= PERF_SAMPLE_RAW;
+  }
   return attr;
 }
 
 void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
   std::string event_name = "unknown";
-  const EventType* event_type = EventTypeFactory::FindEventTypeByConfig(attr.type, attr.config);
+  const EventType* event_type = FindEventTypeByConfig(attr.type, attr.config);
   if (event_type != nullptr) {
     event_name = event_type->name;
   }
 
-  PrintIndented(indent, "event_attr: for event %s\n", event_name.c_str());
+  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);
 
@@ -111,21 +122,26 @@ void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
   PrintIndented(indent + 1, "read_format (0x%llx) %s\n", attr.read_format,
                 ReadFormatToString(attr.read_format).c_str());
 
-  PrintIndented(indent + 1, "disabled %llu, inherit %llu, pinned %llu, exclusive %llu\n",
-                attr.disabled, attr.inherit, attr.pinned, attr.exclusive);
+  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 %llu, exclude_kernel %llu, exclude_hv %llu\n",
+  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 %llu, mmap %llu, comm %llu, freq %llu\n",
-                attr.exclude_idle, attr.mmap, attr.comm, attr.freq);
+  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 %llu, enable_on_exec %llu, task %llu\n",
-                attr.inherit_stat, attr.enable_on_exec, attr.task);
+  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 %llu, precise_ip %llu, mmap_data %llu\n", attr.watermark,
+  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 %llu, exclude_host %llu, exclude_guest %llu\n",
+  PrintIndented(indent + 1, "sample_id_all %u, exclude_host %u, exclude_guest %u\n",
                 attr.sample_id_all, attr.exclude_host, attr.exclude_guest);
+  PrintIndented(indent + 1, "branch_sample_type 0x%" PRIx64 "\n", attr.branch_sample_type);
+  PrintIndented(indent + 1, "exclude_callchain_kernel %u, exclude_callchain_user %u\n",
+                attr.exclude_callchain_kernel, attr.exclude_callchain_user);
+  PrintIndented(indent + 1, "sample_regs_user 0x%" PRIx64 "\n", attr.sample_regs_user);
+  PrintIndented(indent + 1, "sample_stack_user 0x%" PRIx64 "\n", attr.sample_stack_user);
 }
index 52f4aca..79d3df4 100644 (file)
@@ -17,8 +17,7 @@
 #ifndef SIMPLE_PERF_EVENT_ATTR_H_
 #define SIMPLE_PERF_EVENT_ATTR_H_
 
-#include <stdint.h>
-#include <string>
+#include <stddef.h>
 
 #include "perf_event.h"
 
index 386685c..808639b 100644 (file)
 #include "event_fd.h"
 
 #include <fcntl.h>
+#include <poll.h>
 #include <stdio.h>
+#include <string.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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 #include "event_type.h"
 #include "perf_event.h"
 #include "utils.h"
 
+std::vector<char> EventFd::data_process_buffer_;
+
 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::OpenEventFileForProcess(const perf_event_attr& attr, pid_t pid) {
-  return OpenEventFile(attr, pid, -1);
-}
-
-std::unique_ptr<EventFd> EventFd::OpenEventFileForCpu(const perf_event_attr& attr, int cpu) {
-  return OpenEventFile(attr, -1, cpu);
-}
-
-std::unique_ptr<EventFd> EventFd::OpenEventFile(const perf_event_attr& attr, pid_t pid, int cpu) {
+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 =
-      EventTypeFactory::FindEventTypeByConfig(perf_attr.type, perf_attr.config);
+  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, pid, cpu, -1, 0);
+  int perf_event_fd = perf_event_open(&perf_attr, tid, cpu, -1, 0);
   if (perf_event_fd == -1) {
-    // It depends whether the perf_event_file configuration is supported by the kernel and the
-    // machine. So fail to open the file is not an error.
-    PLOG(DEBUG) << "open perf_event_file (event " << event_name << ", pid " << pid << ", cpu "
-                << cpu << ") failed";
+    if (report_error) {
+      PLOG(ERROR) << "open perf_event_file (event " << event_name << ", tid " << tid << ", cpu "
+                  << cpu << ") failed";
+    } else {
+      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) {
-    PLOG(ERROR) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", pid " << pid
-                << ", cpu " << cpu << ") failed";
+    if (report_error) {
+      PLOG(ERROR) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", tid "
+                  << tid << ", cpu " << cpu << ") failed";
+    } else {
+      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, pid, cpu));
+  return std::unique_ptr<EventFd>(new EventFd(perf_event_fd, event_name, tid, cpu));
 }
 
 EventFd::~EventFd() {
@@ -77,8 +82,8 @@ EventFd::~EventFd() {
 }
 
 std::string EventFd::Name() const {
-  return android::base::StringPrintf("perf_event_file(event %s, pid %d, cpu %d)",
-                                     event_name_.c_str(), pid_, cpu_);
+  return android::base::StringPrintf("perf_event_file(event %s, tid %d, cpu %d)",
+                                     event_name_.c_str(), tid_, cpu_);
 }
 
 uint64_t EventFd::Id() const {
@@ -91,24 +96,6 @@ uint64_t EventFd::Id() const {
   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))) {
@@ -132,6 +119,9 @@ bool EventFd::MmapContent(size_t mmap_pages) {
   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;
+  if (data_process_buffer_.size() < mmap_data_buffer_size_) {
+    data_process_buffer_.resize(mmap_data_buffer_size_);
+  }
   return true;
 }
 
@@ -147,9 +137,9 @@ size_t EventFd::GetAvailableMmapData(char** pdata) {
   // 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;
+  size_t buf_mask = mmap_data_buffer_size_ - 1;
+  size_t write_head = static_cast<size_t>(mmap_metadata_page_->data_head & buf_mask);
+  size_t read_head = static_cast<size_t>(mmap_metadata_page_->data_tail & buf_mask);
 
   if (read_head == write_head) {
     // No available data.
@@ -159,12 +149,28 @@ size_t EventFd::GetAvailableMmapData(char** pdata) {
   // Make sure we can see the data after the fence.
   std::atomic_thread_fence(std::memory_order_acquire);
 
-  *pdata = mmap_data_buffer_ + read_head;
+  // Copy records from mapped buffer to data_process_buffer. Note that records can be wrapped
+  // at the end of the mapped buffer.
+  char* to = data_process_buffer_.data();
   if (read_head < write_head) {
-    return write_head - read_head;
+    char* from = mmap_data_buffer_ + read_head;
+    size_t n = write_head - read_head;
+    memcpy(to, from, n);
+    to += n;
   } else {
-    return mmap_data_buffer_size_ - read_head;
+    char* from = mmap_data_buffer_ + read_head;
+    size_t n = mmap_data_buffer_size_ - read_head;
+    memcpy(to, from, n);
+    to += n;
+    from = mmap_data_buffer_;
+    n = write_head;
+    memcpy(to, from, n);
+    to += n;
   }
+  size_t read_bytes = to - data_process_buffer_.data();
+  *pdata = data_process_buffer_.data();
+  DiscardMmapData(read_bytes);
+  return read_bytes;
 }
 
 void EventFd::DiscardMmapData(size_t discard_size) {
@@ -176,3 +182,8 @@ void EventFd::PreparePollForMmapData(pollfd* poll_fd) {
   poll_fd->fd = perf_event_fd_;
   poll_fd->events = POLLIN;
 }
+
+bool IsEventAttrSupportedByKernel(perf_event_attr attr) {
+  auto event_fd = EventFd::OpenEventFile(attr, getpid(), -1, false);
+  return event_fd != nullptr;
+}
index 36ea0cb..c1a7d75 100644 (file)
 #ifndef SIMPLE_PERF_EVENT_FD_H_
 #define SIMPLE_PERF_EVENT_FD_H_
 
-#include <poll.h>
 #include <sys/types.h>
 
 #include <memory>
 #include <string>
+#include <vector>
 
-#include <base/macros.h>
+#include <android-base/macros.h>
 
 #include "perf_event.h"
 
@@ -34,25 +34,25 @@ struct PerfCounter {
   uint64_t id;            // The id of the perf_event_file.
 };
 
+struct pollfd;
+
 // EventFd represents an opened perf_event_file.
 class EventFd {
  public:
-  static std::unique_ptr<EventFd> OpenEventFileForProcess(const perf_event_attr& attr, pid_t pid);
-  static std::unique_ptr<EventFd> OpenEventFileForCpu(const perf_event_attr& attr, int cpu);
-  static std::unique_ptr<EventFd> OpenEventFile(const perf_event_attr& attr, pid_t pid, int cpu);
+  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, pid, 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();
+  pid_t ThreadId() const {
+    return tid_;
+  }
 
-  // It tells the kernel to stop counting and recording events specified by this file.
-  bool DisableEvent();
+  int Cpu() const {
+    return cpu_;
+  }
 
   bool ReadCounter(PerfCounter* counter) const;
 
@@ -64,28 +64,31 @@ class EventFd {
   // 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 pid, int cpu)
+  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),
-        pid_(pid),
+        tid_(tid),
         cpu_(cpu),
         mmap_addr_(nullptr),
         mmap_len_(0) {
   }
 
+  // Give information about this perf_event_file, like (event_name, tid, cpu).
+  std::string Name() const;
+
+  // 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);
+
   int perf_event_fd_;
   mutable uint64_t id_;
   const std::string event_name_;
-  pid_t pid_;
+  pid_t tid_;
   int cpu_;
 
   void* mmap_addr_;
@@ -95,7 +98,14 @@ class EventFd {
                             // by then kernel.
   size_t mmap_data_buffer_size_;
 
+  // As mmap_data_buffer is a ring buffer, it is possible that one record is wrapped at the
+  // end of the buffer. So we need to copy records from mmap_data_buffer to data_process_buffer
+  // before processing them.
+  static std::vector<char> data_process_buffer_;
+
   DISALLOW_COPY_AND_ASSIGN(EventFd);
 };
 
+bool IsEventAttrSupportedByKernel(perf_event_attr attr);
+
 #endif  // SIMPLE_PERF_EVENT_FD_H_
index 61f1705..fad8b1e 100644 (file)
 
 #include "event_selection_set.h"
 
-#include <base/logging.h>
+#include <poll.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 #include "environment.h"
 #include "event_attr.h"
 #include "event_type.h"
+#include "perf_regs.h"
+
+bool IsBranchSamplingSupported() {
+  const EventType* type = FindEventTypeByName("cpu-cycles");
+  if (type == nullptr) {
+    return false;
+  }
+  perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
+  attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
+  attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY;
+  return IsEventAttrSupportedByKernel(attr);
+}
+
+bool IsDwarfCallChainSamplingSupported() {
+  const EventType* type = FindEventTypeByName("cpu-cycles");
+  if (type == nullptr) {
+    return false;
+  }
+  perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
+  attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+  attr.exclude_callchain_user = 1;
+  attr.sample_regs_user = GetSupportedRegMask(GetBuildArch());
+  attr.sample_stack_user = 8192;
+  return IsEventAttrSupportedByKernel(attr);
+}
 
-void EventSelectionSet::AddEventType(const EventType& event_type) {
+bool EventSelectionSet::AddEventType(const EventTypeAndModifier& event_type_modifier) {
   EventSelection selection;
-  selection.event_type = &event_type;
-  selection.event_attr = CreateDefaultPerfEventAttr(event_type);
+  selection.event_type_modifier = event_type_modifier;
+  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;
+  if (!IsEventAttrSupportedByKernel(selection.event_attr)) {
+    LOG(ERROR) << "Event type '" << event_type_modifier.name << "' is not supported by the kernel";
+    return false;
+  }
   selections_.push_back(std::move(selection));
+  UnionSampleType();
+  return true;
+}
+
+// Union the sample type of different event attrs can make reading sample records in perf.data
+// easier.
+void EventSelectionSet::UnionSampleType() {
+  uint64_t sample_type = 0;
+  for (auto& selection : selections_) {
+    sample_type |= selection.event_attr.sample_type;
+  }
+  for (auto& selection : selections_) {
+    selection.event_attr.sample_type = sample_type;
+  }
+}
+
+void EventSelectionSet::SetEnableOnExec(bool enable) {
+  for (auto& selection : selections_) {
+    // If sampling is enabled on exec, then it is disabled at startup, otherwise
+    // it should be enabled at startup. Don't use ioctl(PERF_EVENT_IOC_ENABLE)
+    // to enable it after perf_event_open(). Because some android kernels can't
+    // handle ioctl() well when cpu-hotplug happens. See http://b/25193162.
+    if (enable) {
+      selection.event_attr.enable_on_exec = 1;
+      selection.event_attr.disabled = 1;
+    } else {
+      selection.event_attr.enable_on_exec = 0;
+      selection.event_attr.disabled = 0;
+    }
+  }
 }
 
-void EventSelectionSet::EnableOnExec() {
+bool EventSelectionSet::GetEnableOnExec() {
   for (auto& selection : selections_) {
-    selection.event_attr.enable_on_exec = 1;
+    if (selection.event_attr.enable_on_exec == 0) {
+      return false;
+    }
   }
+  return true;
 }
 
 void EventSelectionSet::SampleIdAll() {
@@ -57,46 +128,102 @@ void EventSelectionSet::SetSamplePeriod(uint64_t sample_period) {
   }
 }
 
-bool EventSelectionSet::OpenEventFilesForAllCpus() {
-  std::vector<int> cpus = GetOnlineCpus();
-  if (cpus.empty()) {
+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_) {
-    for (auto& cpu : cpus) {
-      auto event_fd = EventFd::OpenEventFileForCpu(selection.event_attr, cpu);
-      if (event_fd != nullptr) {
-        selection.event_fds.push_back(std::move(event_fd));
-      }
-    }
-    // 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 (selection.event_fds.empty()) {
-      LOG(ERROR) << "failed to open perf event file for event_type " << selection.event_type->name
-                 << " on all cpus";
-      return false;
+    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;
 }
 
-bool EventSelectionSet::OpenEventFilesForProcess(pid_t pid) {
+void EventSelectionSet::EnableFpCallChainSampling() {
   for (auto& selection : selections_) {
-    auto event_fd = EventFd::OpenEventFileForProcess(selection.event_attr, pid);
-    if (event_fd == nullptr) {
-      PLOG(ERROR) << "failed to open perf event file for event type " << selection.event_type->name
-                  << " on pid " << pid;
+    selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+  }
+}
+
+bool EventSelectionSet::EnableDwarfCallChainSampling(uint32_t dump_stack_size) {
+  if (!IsDwarfCallChainSamplingSupported()) {
+    LOG(ERROR) << "dwarf callchain sampling is not supported on this device.";
+    return false;
+  }
+  for (auto& selection : selections_) {
+    selection.event_attr.sample_type |=
+        PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+    selection.event_attr.exclude_callchain_user = 1;
+    selection.event_attr.sample_regs_user = GetSupportedRegMask(GetBuildArch());
+    selection.event_attr.sample_stack_user = dump_stack_size;
+  }
+  return true;
+}
+
+void EventSelectionSet::SetInherit(bool enable) {
+  for (auto& selection : selections_) {
+    selection.event_attr.inherit = (enable ? 1 : 0);
+  }
+}
+
+static bool CheckIfCpusOnline(const std::vector<int>& cpus) {
+  std::vector<int> online_cpus = GetOnlineCpus();
+  for (const auto& cpu : cpus) {
+    if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
+      LOG(ERROR) << "cpu " << cpu << " is not online.";
       return false;
     }
-    selection.event_fds.push_back(std::move(event_fd));
   }
   return true;
 }
 
-bool EventSelectionSet::EnableEvents() {
+bool EventSelectionSet::OpenEventFilesForCpus(const std::vector<int>& cpus) {
+  return OpenEventFilesForThreadsOnCpus({-1}, cpus);
+}
+
+bool EventSelectionSet::OpenEventFilesForThreadsOnCpus(const std::vector<pid_t>& threads,
+                                                       std::vector<int> cpus) {
+  if (!cpus.empty()) {
+    if (!CheckIfCpusOnline(cpus)) {
+      return false;
+    }
+  } else {
+    cpus = GetOnlineCpus();
+  }
+  return OpenEventFiles(threads, cpus);
+}
+
+bool EventSelectionSet::OpenEventFiles(const std::vector<pid_t>& threads,
+                                       const std::vector<int>& cpus) {
   for (auto& selection : selections_) {
-    for (auto& event_fd : selection.event_fds) {
-      if (!event_fd->EnableEvent()) {
+    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) {
+          LOG(VERBOSE) << "OpenEventFile for tid " << tid << ", cpu " << cpu;
+          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_modifier.name << " for "
+                    << (tid == -1 ? "all threads" : android::base::StringPrintf(" thread %d", tid));
         return false;
       }
     }
@@ -104,18 +231,21 @@ bool EventSelectionSet::EnableEvents() {
   return true;
 }
 
-bool EventSelectionSet::ReadCounters(
-    std::map<const EventType*, std::vector<PerfCounter>>* counters_map) {
+bool EventSelectionSet::ReadCounters(std::vector<CountersInfo>* counters) {
+  counters->clear();
   for (auto& selection : selections_) {
-    std::vector<PerfCounter> counters;
+    CountersInfo counters_info;
+    counters_info.event_type = &selection.event_type_modifier;
     for (auto& event_fd : selection.event_fds) {
-      PerfCounter counter;
-      if (!event_fd->ReadCounter(&counter)) {
+      CountersInfo::CounterInfo counter_info;
+      if (!event_fd->ReadCounter(&counter_info.counter)) {
         return false;
       }
-      counters.push_back(counter);
+      counter_info.tid = event_fd->ThreadId();
+      counter_info.cpu = event_fd->Cpu();
+      counters_info.counters.push_back(counter_info);
     }
-    counters_map->insert(std::make_pair(selection.event_type, counters));
+    counters->push_back(counters_info);
   }
   return true;
 }
@@ -155,7 +285,6 @@ static bool ReadMmapEventDataForFd(std::unique_ptr<EventFd>& event_fd,
       return false;
     }
     *have_data = true;
-    event_fd->DiscardMmapData(size);
   }
   return true;
 }
@@ -177,32 +306,24 @@ bool EventSelectionSet::ReadMmapEventData(std::function<bool(const char*, size_t
   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) {
+    const EventTypeAndModifier& event_type_modifier) {
   for (auto& selection : selections_) {
-    if (strcmp(selection.event_type->name, event_type.name) == 0) {
+    if (selection.event_type_modifier.name == event_type_modifier.name) {
       return &selection;
     }
   }
   return nullptr;
 }
 
-const perf_event_attr& EventSelectionSet::FindEventAttrByType(const EventType& event_type) {
-  return FindSelectionByType(event_type)->event_attr;
+const perf_event_attr* EventSelectionSet::FindEventAttrByType(
+    const EventTypeAndModifier& event_type_modifier) {
+  EventSelection* selection = FindSelectionByType(event_type_modifier);
+  return (selection != nullptr) ? &selection->event_attr : nullptr;
 }
 
-const std::vector<std::unique_ptr<EventFd>>& EventSelectionSet::FindEventFdsByType(
-    const EventType& event_type) {
-  return FindSelectionByType(event_type)->event_fds;
+const std::vector<std::unique_ptr<EventFd>>* EventSelectionSet::FindEventFdsByType(
+    const EventTypeAndModifier& event_type_modifier) {
+  EventSelection* selection = FindSelectionByType(event_type_modifier);
+  return (selection != nullptr) ? &selection->event_fds : nullptr;
 }
index 78be069..746abfa 100644 (file)
 #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 <android-base/macros.h>
 
 #include "event_fd.h"
+#include "event_type.h"
 #include "perf_event.h"
 
-struct EventType;
+struct CountersInfo {
+  const EventTypeAndModifier* event_type;
+  struct CounterInfo {
+    pid_t tid;
+    int cpu;
+    PerfCounter counter;
+  };
+  std::vector<CounterInfo> counters;
+};
+
+struct pollfd;
 
 // EventSelectionSet helps to monitor events.
 // Firstly, the user creates an EventSelectionSet, and adds the specific event types to monitor.
@@ -47,36 +57,46 @@ class EventSelectionSet {
     return selections_.empty();
   }
 
-  void AddEventType(const EventType& event_type);
+  bool AddEventType(const EventTypeAndModifier& event_type_modifier);
 
-  void EnableOnExec();
+  void SetEnableOnExec(bool enable);
+  bool GetEnableOnExec();
   void SampleIdAll();
   void SetSampleFreq(uint64_t sample_freq);
   void SetSamplePeriod(uint64_t sample_period);
-
-  bool OpenEventFilesForAllCpus();
-  bool OpenEventFilesForProcess(pid_t pid);
-  bool EnableEvents();
-  bool ReadCounters(std::map<const EventType*, std::vector<PerfCounter>>* counters_map);
+  bool SetBranchSampling(uint64_t branch_sample_type);
+  void EnableFpCallChainSampling();
+  bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);
+  void SetInherit(bool enable);
+
+  bool OpenEventFilesForCpus(const std::vector<int>& cpus);
+  bool OpenEventFilesForThreadsOnCpus(const std::vector<pid_t>& threads, std::vector<int> cpus);
+  bool ReadCounters(std::vector<CountersInfo>* counters);
   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);
+  const perf_event_attr* FindEventAttrByType(const EventTypeAndModifier& event_type_modifier);
+  const std::vector<std::unique_ptr<EventFd>>* FindEventFdsByType(
+      const EventTypeAndModifier& event_type_modifier);
 
  private:
+  void UnionSampleType();
+  bool OpenEventFiles(const std::vector<pid_t>& threads, const std::vector<int>& cpus);
+
   struct EventSelection {
-    const EventType* event_type;
+    EventTypeAndModifier event_type_modifier;
     perf_event_attr event_attr;
     std::vector<std::unique_ptr<EventFd>> event_fds;
   };
-  EventSelection* FindSelectionByType(const EventType& event_type);
+  EventSelection* FindSelectionByType(const EventTypeAndModifier& event_type_modifier);
 
   std::vector<EventSelection> selections_;
 
   DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
 };
 
+bool IsBranchSamplingSupported();
+bool IsDwarfCallChainSamplingSupported();
+
 #endif  // SIMPLE_PERF_EVENT_SELECTION_SET_H_
index ee0e161..2eaafa2 100644 (file)
 #include "event_type.h"
 
 #include <unistd.h>
+#include <algorithm>
 #include <string>
 #include <vector>
 
-#include <base/logging.h>
+#include <android-base/file.h>
+#include <android-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 }                           \
-  ,
+#define EVENT_TYPE_TABLE_ENTRY(name, type, config) {name, type, config},
 
-static std::vector<const EventType> event_type_array = {
+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::OpenEventFileForProcess(CreateDefaultPerfEventAttr(event_type), getpid());
-  return event_fd != nullptr;
+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;
 }
 
-bool EventType::IsSupportedByKernel() const {
-  return IsEventTypeSupportedByKernel(*this);
+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 std::vector<const EventType>& EventTypeFactory::GetAllEventTypes() {
-  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;
 }
 
-const EventType* EventTypeFactory::FindEventTypeByName(const std::string& name,
-                                                       bool report_unsupported_type) {
+const EventType* FindEventTypeByName(const std::string& name) {
   const EventType* result = nullptr;
-  for (auto& event_type : event_type_array) {
+  for (auto& event_type : GetAllEventTypes()) {
     if (event_type.name == name) {
       result = &event_type;
       break;
@@ -60,19 +96,79 @@ const EventType* EventTypeFactory::FindEventTypeByName(const std::string& 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;
 }
 
-const EventType* EventTypeFactory::FindEventTypeByConfig(uint32_t type, uint64_t config) {
-  for (auto& event_type : event_type_array) {
-    if (event_type.type == type && event_type.config == config) {
-      return &event_type;
+std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_type_str) {
+  static std::string modifier_characters = "ukhGHp";
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier(new EventTypeAndModifier);
+  event_type_modifier->name = event_type_str;
+  std::string event_type_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) {
+      event_type_name = event_type_str.substr(0, comm_pos);
+      modifier = event_type_str.substr(comm_pos + 1);
     }
   }
-  return nullptr;
+  const EventType* event_type = FindEventTypeByName(event_type_name);
+  if (event_type == nullptr) {
+    // Try if the modifier belongs to the event type name, like some tracepoint events.
+    if (!modifier.empty()) {
+      event_type_name = event_type_str;
+      modifier.clear();
+      event_type = FindEventTypeByName(event_type_name);
+    }
+    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 << "'";
+    }
+  }
+  event_type_modifier->modifier = modifier;
+  return event_type_modifier;
 }
index b486a29..4100125 100644 (file)
@@ -18,6 +18,7 @@
 #define SIMPLE_PERF_EVENT_H_
 
 #include <stdint.h>
+#include <memory>
 #include <string>
 #include <vector>
 
 // the event type is supported by the kernel.
 
 struct EventType {
-  bool IsSupportedByKernel() const;
+  EventType(const std::string& name, uint32_t type, uint64_t config)
+      : name(name), type(type), config(config) {
+  }
 
-  const char* name;
+  EventType() : type(0), config(0) {
+  }
+
+  std::string name;
   uint32_t type;
   uint64_t config;
 };
 
-class EventTypeFactory {
- public:
-  static const std::vector<const EventType>& GetAllEventTypes();
-  static const EventType* FindEventTypeByName(const std::string& name,
-                                              bool report_unsupported_type = true);
-  static const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config);
+const std::vector<EventType>& GetAllEventTypes();
+const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config);
+const EventType* FindEventTypeByName(const std::string& name);
+
+struct EventTypeAndModifier {
+  std::string name;
+  EventType event_type;
+  std::string modifier;
+  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);
+
 #endif  // SIMPLE_PERF_EVENT_H_
index 895cc85..a77be0a 100644 (file)
 {"alignment-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS},
 {"emulation-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS},
 
-{"L1-dcache-loades", 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-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-loades", 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-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-loades", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 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-loades", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 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-loades", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 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-loades", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 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-loades", PERF_TYPE_HW_CACHE, ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_READ << 8) | (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 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))},
index b3fb897..ff60c23 100755 (executable)
 
 
 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)
+  """
+  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()
+  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)
+    generated_str += gen_event_type_entry_str(
+        event_type_name, "PERF_TYPE_HARDWARE", event_config)
 
-    return generated_str
+  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()
+  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 isinstance(config, 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)
+    generated_str += gen_event_type_entry_str(
+        event_type_name, "PERF_TYPE_SOFTWARE", event_config)
 
-    return generated_str
+  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 = [["loades", "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_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_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
+  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 = "// 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')
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
new file mode 100644 (file)
index 0000000..4aba379
--- /dev/null
@@ -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_GET_TEST_DATA_H_
+#define SIMPLE_PERF_GET_TEST_DATA_H_
+
+#include <string>
+
+#include "build_id.h"
+
+std::string GetTestData(const std::string& filename);
+const std::string& GetTestDataDir();
+
+bool IsRoot();
+
+// The source code of elf is testdata/elf_file_source.cpp.
+static const std::string ELF_FILE = "elf";
+// perf.data is generated by sampling on three processes running different
+// executables: elf, t1, t2 (all generated by elf_file_source.cpp, but with different
+// executable name).
+static const std::string PERF_DATA = "perf.data";
+// perf_g_fp.data is generated by sampling on one process running elf using --call-graph fp option.
+static const std::string CALLGRAPH_FP_PERF_DATA = "perf_g_fp.data";
+// perf_b.data is generated by sampling on one process running elf using -b option.
+static const std::string BRANCH_PERF_DATA = "perf_b.data";
+
+static BuildId elf_file_build_id("0b12a384a9f4a3f3659b7171ca615dbec3a81f71");
+
+
+// To generate apk supporting execution on shared libraries in apk:
+// 1. Add android:extractNativeLibs=false in AndroidManifest.xml.
+// 2. Use `zip -0` to store native libraries in apk without compression.
+// 3. Use `zipalign -p 4096` to make native libraries in apk start at page boundaries.
+//
+// The logical in libhello-jni.so is as below:
+//  volatile int GlobalVar;
+//
+//  while (true) {
+//    GlobalFunc() -> Func1() -> Func2()
+//  }
+// And most time is spent in Func2().
+static const std::string APK_FILE = "data/app/com.example.hellojni-1/base.apk";
+static const std::string NATIVELIB_IN_APK = "lib/arm64-v8a/libhello-jni.so";
+// has_embedded_native_libs_apk_perf.data is generated by sampling on one process running
+// APK_FILE using -g --no-unwind option.
+static const std::string NATIVELIB_IN_APK_PERF_DATA = "has_embedded_native_libs_apk_perf.data";
+// The offset and size info are extracted from the generated apk file to run read_apk tests.
+constexpr size_t NATIVELIB_OFFSET_IN_APK = 0x639000;
+constexpr size_t NATIVELIB_SIZE_IN_APK = 0x1678;
+
+static BuildId native_lib_build_id("8ed5755a7fdc07586ca228b8ee21621bce2c7a97");
+
+#endif  // SIMPLE_PERF_GET_TEST_DATA_H_
index 33ec32f..444a1c2 100644 (file)
 
 #include <gtest/gtest.h>
 
-#include <base/logging.h>
+#include <memory>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/test_utils.h>
+#include <ziparchive/zip_archive.h>
+
+#if defined(__ANDROID__)
+#include <sys/system_properties.h>
+#endif
+
+#include "get_test_data.h"
+#include "read_elf.h"
+#include "utils.h"
+
+static std::string testdata_dir;
+
+#if defined(IN_CTS_TEST)
+static const std::string testdata_section = ".testzipdata";
+
+static bool ExtractTestDataFromElfSection() {
+  if (!MkdirWithParents(testdata_dir)) {
+    PLOG(ERROR) << "failed to create testdata_dir " << testdata_dir;
+    return false;
+  }
+  std::string content;
+  if (!ReadSectionFromElfFile("/proc/self/exe", testdata_section, &content)) {
+    LOG(ERROR) << "failed to read section " << testdata_section;
+    return false;
+  }
+  TemporaryFile tmp_file;
+  if (!android::base::WriteStringToFile(content, tmp_file.path)) {
+    PLOG(ERROR) << "failed to write file " << tmp_file.path;
+    return false;
+  }
+  ArchiveHelper ahelper(tmp_file.fd, tmp_file.path);
+  if (!ahelper) {
+    LOG(ERROR) << "failed to open archive " << tmp_file.path;
+    return false;
+  }
+  ZipArchiveHandle& handle = ahelper.archive_handle();
+  void* cookie;
+  int ret = StartIteration(handle, &cookie, nullptr, nullptr);
+  if (ret != 0) {
+    LOG(ERROR) << "failed to start iterating zip entries";
+    return false;
+  }
+  std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+  ZipEntry entry;
+  ZipString name;
+  while (Next(cookie, &entry, &name) == 0) {
+    std::string entry_name(name.name, name.name + name.name_length);
+    std::string path = testdata_dir + entry_name;
+    // Skip dir.
+    if (path.back() == '/') {
+      continue;
+    }
+    if (!MkdirWithParents(path)) {
+      LOG(ERROR) << "failed to create dir for " << path;
+      return false;
+    }
+    FileHelper fhelper = FileHelper::OpenWriteOnly(path);
+    if (!fhelper) {
+      PLOG(ERROR) << "failed to create file " << path;
+      return false;
+    }
+    std::vector<uint8_t> data(entry.uncompressed_length);
+    if (ExtractToMemory(handle, &entry, data.data(), data.size()) != 0) {
+      LOG(ERROR) << "failed to extract entry " << entry_name;
+      return false;
+    }
+    if (!android::base::WriteFully(fhelper.fd(), data.data(), data.size())) {
+      LOG(ERROR) << "failed to write file " << path;
+      return false;
+    }
+  }
+  return true;
+}
+
+#if defined(__ANDROID__)
+class SavedPerfHardenProperty {
+ public:
+  SavedPerfHardenProperty() {
+    __system_property_get("security.perf_harden", prop_value_);
+    if (!android::base::ReadFileToString("/proc/sys/kernel/perf_event_paranoid",
+                                    &paranoid_value_)) {
+      PLOG(ERROR) << "failed to read /proc/sys/kernel/perf_event_paranoid";
+    }
+  }
+
+  ~SavedPerfHardenProperty() {
+    if (strlen(prop_value_) != 0) {
+      if (__system_property_set("security.perf_harden", prop_value_) != 0) {
+        PLOG(ERROR) << "failed to set security.perf_harden";
+        return;
+      }
+      // Sleep one second to wait for security.perf_harden changing
+      // /proc/sys/kernel/perf_event_paranoid.
+      sleep(1);
+      std::string paranoid_value;
+      if (!android::base::ReadFileToString("/proc/sys/kernel/perf_event_paranoid",
+                                           &paranoid_value)) {
+        PLOG(ERROR) << "failed to read /proc/sys/kernel/perf_event_paranoid";
+        return;
+      }
+      if (paranoid_value_ != paranoid_value) {
+        LOG(ERROR) << "failed to restore /proc/sys/kernel/perf_event_paranoid";
+      }
+    }
+  }
+
+ private:
+  char prop_value_[PROP_VALUE_MAX];
+  std::string paranoid_value_;
+};
+#endif  // defined(__ANDROID__)
+#endif  // defined(IN_CTS_TEST)
 
 int main(int argc, char** argv) {
   InitLogging(argv, android::base::StderrLogger);
   testing::InitGoogleTest(&argc, argv);
+  android::base::LogSeverity log_severity = android::base::WARNING;
+
+  for (int i = 1; i < argc; ++i) {
+    if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) {
+      testdata_dir = argv[i + 1];
+      i++;
+    } else if (strcmp(argv[i], "--log") == 0) {
+      if (i + 1 < argc) {
+        ++i;
+        if (!GetLogSeverity(argv[i], &log_severity)) {
+          LOG(ERROR) << "Unknown log severity: " << argv[i];
+          return 1;
+        }
+      } else {
+        LOG(ERROR) << "Missing argument for --log option.\n";
+        return 1;
+      }
+    }
+  }
+  android::base::ScopedLogSeverity severity(log_severity);
+
+#if defined(IN_CTS_TEST)
+  std::unique_ptr<TemporaryDir> tmp_dir;
+  if (!::testing::GTEST_FLAG(list_tests) && testdata_dir.empty()) {
+    tmp_dir.reset(new TemporaryDir);
+    testdata_dir = std::string(tmp_dir->path) + "/";
+    if (!ExtractTestDataFromElfSection()) {
+      LOG(ERROR) << "failed to extract test data from elf section";
+      return 1;
+    }
+  }
+
+#if defined(__ANDROID__)
+  // A cts test PerfEventParanoidTest.java is testing if
+  // /proc/sys/kernel/perf_event_paranoid is 3, so restore perf_harden
+  // value after current test to not break that test.
+  SavedPerfHardenProperty saved_perf_harden;
+#endif
+#endif
+  if (!::testing::GTEST_FLAG(list_tests) && testdata_dir.empty()) {
+    printf("Usage: %s -t <testdata_dir>\n", argv[0]);
+    return 1;
+  }
+  if (testdata_dir.back() != '/') {
+    testdata_dir.push_back('/');
+  }
+  LOG(INFO) << "testdata is in " << testdata_dir;
   return RUN_ALL_TESTS();
 }
+
+std::string GetTestData(const std::string& filename) {
+  return testdata_dir + filename;
+}
+
+const std::string& GetTestDataDir() {
+  return testdata_dir;
+}
index 173026e..0a73c2d 100644 (file)
  */
 
 #include <string.h>
+
 #include <string>
 #include <vector>
 
-#include <base/logging.h>
+#include <android-base/logging.h>
 
 #include "command.h"
+#include "utils.h"
 
 int main(int argc, char** argv) {
   InitLogging(argv, android::base::StderrLogger);
   std::vector<std::string> args;
+  android::base::LogSeverity log_severity = android::base::WARNING;
 
-  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");
+  for (int i = 1; i < argc; ++i) {
+    if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
+      args.insert(args.begin(), "help");
+    } else if (strcmp(argv[i], "--log") == 0) {
+      if (i + 1 < argc) {
+        ++i;
+        if (!GetLogSeverity(argv[i], &log_severity)) {
+          LOG(ERROR) << "Unknown log severity: " << argv[i];
+          return 1;
+        }
       } else {
-        args.push_back(argv[i]);
+        LOG(ERROR) << "Missing argument for --log option.\n";
+        return 1;
       }
+    } else {
+      args.push_back(argv[i]);
     }
   }
+  android::base::ScopedLogSeverity severity(log_severity);
 
-  Command* command = Command::FindCommandByName(args[0]);
+  if (args.empty()) {
+    args.push_back("help");
+  }
+  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);
diff --git a/simpleperf/nonlinux_support/include/asm/byteorder.h b/simpleperf/nonlinux_support/include/asm/byteorder.h
new file mode 100644 (file)
index 0000000..d118abc
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ */
diff --git a/simpleperf/nonlinux_support/include/linux/ioctl.h b/simpleperf/nonlinux_support/include/linux/ioctl.h
new file mode 100644 (file)
index 0000000..f580736
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define __IO(type, nr)
+#define __IOR(type, nr, size)
+#define __IOW(type, nr, size)
diff --git a/simpleperf/nonlinux_support/include/linux/types.h b/simpleperf/nonlinux_support/include/linux/types.h
new file mode 100644 (file)
index 0000000..6af5b8c
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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 <stdint.h>
+
+typedef uint8_t __u8;
+typedef uint16_t __u16;
+typedef uint32_t __u32;
+typedef int32_t __s32;
+typedef uint64_t __u64;
+typedef int64_t __s64;
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
new file mode 100644 (file)
index 0000000..7551d36
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+// Add fake functions to build successfully on darwin.
+#include <android-base/logging.h>
+
+#include "dwarf_unwind.h"
+#include "environment.h"
+
+std::vector<uint64_t> UnwindCallChain(ArchType, const ThreadEntry&, const RegSet&,
+                                      const std::vector<char>&) {
+  return std::vector<uint64_t>();
+}
+
+bool ProcessKernelSymbols(const std::string&, std::function<bool(const KernelSymbol&)>) {
+  return false;
+}
+
+bool GetKernelBuildId(BuildId*) {
+  return false;
+}
index a91eb6b..7e7e48d 100644 (file)
 #ifndef SIMPLE_PERF_PERF_EVENT_H_
 #define SIMPLE_PERF_PERF_EVENT_H_
 
+#if defined(USE_BIONIC_UAPI_HEADERS)
+#include <uapi/linux/perf_event.h>
+#else
 #include <linux/perf_event.h>
+#endif
 
 #endif  // SIMPLE_PERF_PERF_EVENT_H_
diff --git a/simpleperf/perf_regs.cpp b/simpleperf/perf_regs.cpp
new file mode 100644 (file)
index 0000000..29d144e
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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 "perf_regs.h"
+
+#include <unordered_map>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+ArchType ScopedCurrentArch::current_arch = GetBuildArch();
+
+ArchType GetArchType(const std::string& arch) {
+  if (arch == "x86" || arch == "i686") {
+    return ARCH_X86_32;
+  } else if (arch == "x86_64") {
+    return ARCH_X86_64;
+  } else if (arch == "aarch64") {
+    return ARCH_ARM64;
+  } else if (android::base::StartsWith(arch, "arm")) {
+    return ARCH_ARM;
+  }
+  LOG(ERROR) << "unsupported arch: " << arch;
+  return ARCH_UNSUPPORTED;
+}
+
+uint64_t GetSupportedRegMask(ArchType arch) {
+  switch (arch) {
+    case ARCH_X86_32:
+      return ((1ULL << PERF_REG_X86_32_MAX) - 1);
+    case ARCH_X86_64:
+      return (((1ULL << PERF_REG_X86_64_MAX) - 1) & ~(1ULL << PERF_REG_X86_DS) &
+              ~(1ULL << PERF_REG_X86_ES) & ~(1ULL << PERF_REG_X86_FS) & ~(1ULL << PERF_REG_X86_GS));
+    case ARCH_ARM:
+      return ((1ULL << PERF_REG_ARM_MAX) - 1);
+    case ARCH_ARM64:
+      return ((1ULL << PERF_REG_ARM64_MAX) - 1);
+    default:
+      return 0;
+  }
+  return 0;
+}
+
+static std::unordered_map<size_t, std::string> x86_reg_map = {
+    {PERF_REG_X86_AX, "ax"},       {PERF_REG_X86_BX, "bx"}, {PERF_REG_X86_CX, "cx"},
+    {PERF_REG_X86_DX, "dx"},       {PERF_REG_X86_SI, "si"}, {PERF_REG_X86_DI, "di"},
+    {PERF_REG_X86_BP, "bp"},       {PERF_REG_X86_SP, "sp"}, {PERF_REG_X86_IP, "ip"},
+    {PERF_REG_X86_FLAGS, "flags"}, {PERF_REG_X86_CS, "cs"}, {PERF_REG_X86_SS, "ss"},
+    {PERF_REG_X86_DS, "ds"},       {PERF_REG_X86_ES, "es"}, {PERF_REG_X86_FS, "fs"},
+    {PERF_REG_X86_GS, "gs"},
+};
+
+static std::unordered_map<size_t, std::string> arm_reg_map = {
+    {PERF_REG_ARM_FP, "fp"}, {PERF_REG_ARM_IP, "ip"}, {PERF_REG_ARM_SP, "sp"},
+    {PERF_REG_ARM_LR, "lr"}, {PERF_REG_ARM_PC, "pc"},
+};
+
+static std::unordered_map<size_t, std::string> arm64_reg_map = {
+    {PERF_REG_ARM64_LR, "lr"}, {PERF_REG_ARM64_SP, "sp"}, {PERF_REG_ARM64_PC, "pc"},
+};
+
+std::string GetRegName(size_t regno, ArchType arch) {
+  // Cast regno to int type to avoid -Werror=type-limits.
+  int reg = static_cast<int>(regno);
+  switch (arch) {
+    case ARCH_X86_64: {
+      if (reg >= PERF_REG_X86_R8 && reg <= PERF_REG_X86_R15) {
+        return android::base::StringPrintf("r%d", reg - PERF_REG_X86_R8 + 8);
+      }
+    }  // go through
+    case ARCH_X86_32: {
+      auto it = x86_reg_map.find(reg);
+      CHECK(it != x86_reg_map.end()) << "unknown reg " << reg;
+      return it->second;
+    }
+    case ARCH_ARM: {
+      if (reg >= PERF_REG_ARM_R0 && reg <= PERF_REG_ARM_R10) {
+        return android::base::StringPrintf("r%d", reg - PERF_REG_ARM_R0);
+      }
+      auto it = arm_reg_map.find(reg);
+      CHECK(it != arm_reg_map.end()) << "unknown reg " << reg;
+      return it->second;
+    }
+    case ARCH_ARM64: {
+      if (reg >= PERF_REG_ARM64_X0 && reg <= PERF_REG_ARM64_X29) {
+        return android::base::StringPrintf("r%d", reg - PERF_REG_ARM64_X0);
+      }
+      auto it = arm64_reg_map.find(reg);
+      CHECK(it != arm64_reg_map.end()) << "unknown reg " << reg;
+      return it->second;
+    }
+    default:
+      return "unknown";
+  }
+}
+
+RegSet CreateRegSet(uint64_t valid_mask, const std::vector<uint64_t>& valid_regs) {
+  RegSet regs;
+  regs.valid_mask = valid_mask;
+  for (int i = 0, j = 0; i < 64; ++i) {
+    if ((valid_mask >> i) & 1) {
+      regs.data[i] = valid_regs[j++];
+    }
+  }
+  return regs;
+}
+
+bool GetRegValue(const RegSet& regs, size_t regno, uint64_t* value) {
+  CHECK_LT(regno, 64U);
+  if ((regs.valid_mask >> regno) & 1) {
+    *value = regs.data[regno];
+    return true;
+  }
+  return false;
+}
+
+bool GetSpRegValue(const RegSet& regs, ArchType arch, uint64_t* value) {
+  size_t regno;
+  switch (arch) {
+    case ARCH_X86_32:
+      regno = PERF_REG_X86_SP;
+      break;
+    case ARCH_X86_64:
+      regno = PERF_REG_X86_SP;
+      break;
+    case ARCH_ARM:
+      regno = PERF_REG_ARM_SP;
+      break;
+    case ARCH_ARM64:
+      regno = PERF_REG_ARM64_SP;
+      break;
+    default:
+      return false;
+  }
+  return GetRegValue(regs, regno, value);
+}
diff --git a/simpleperf/perf_regs.h b/simpleperf/perf_regs.h
new file mode 100644 (file)
index 0000000..9fc610f
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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_PERF_REGS_H_
+#define SIMPLE_PERF_PERF_REGS_H_
+
+#if defined(USE_BIONIC_UAPI_HEADERS)
+#include <uapi/asm-x86/asm/perf_regs.h>
+#include <uapi/asm-arm/asm/perf_regs.h>
+#define perf_event_arm_regs perf_event_arm64_regs
+#include <uapi/asm-arm64/asm/perf_regs.h>
+#else
+#include <asm-x86/asm/perf_regs.h>
+#include <asm-arm/asm/perf_regs.h>
+#define perf_event_arm_regs perf_event_arm64_regs
+#include <asm-arm64/asm/perf_regs.h>
+#endif
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+enum ArchType {
+  ARCH_X86_32,
+  ARCH_X86_64,
+  ARCH_ARM,
+  ARCH_ARM64,
+  ARCH_UNSUPPORTED,
+};
+
+constexpr ArchType GetBuildArch() {
+#if defined(__i386__)
+  return ARCH_X86_32;
+#elif defined(__x86_64__)
+  return ARCH_X86_64;
+#elif defined(__aarch64__)
+  return ARCH_ARM64;
+#elif defined(__arm__)
+  return ARCH_ARM;
+#else
+  return ARCH_UNSUPPORTED;
+#endif
+}
+
+ArchType GetArchType(const std::string& arch);
+uint64_t GetSupportedRegMask(ArchType arch);
+std::string GetRegName(size_t regno, ArchType arch);
+
+class ScopedCurrentArch {
+ public:
+  ScopedCurrentArch(ArchType arch) : saved_arch(current_arch) {
+    current_arch = arch;
+  }
+  ~ScopedCurrentArch() {
+    current_arch = saved_arch;
+  }
+  static ArchType GetCurrentArch() {
+    return current_arch;
+  }
+
+ private:
+  ArchType saved_arch;
+  static ArchType current_arch;
+};
+
+struct RegSet {
+  uint64_t valid_mask;
+  uint64_t data[64];
+};
+
+RegSet CreateRegSet(uint64_t valid_mask, const std::vector<uint64_t>& valid_regs);
+
+bool GetRegValue(const RegSet& regs, size_t regno, uint64_t* value);
+bool GetSpRegValue(const RegSet& regs, ArchType arch, uint64_t* value);
+
+#endif  // SIMPLE_PERF_PERF_REGS_H_
diff --git a/simpleperf/read_apk.cpp b/simpleperf/read_apk.cpp
new file mode 100644 (file)
index 0000000..af72cdf
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "read_apk.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <ziparchive/zip_archive.h>
+#include "read_elf.h"
+#include "utils.h"
+
+std::map<ApkInspector::ApkOffset, std::unique_ptr<EmbeddedElf>> ApkInspector::embedded_elf_cache_;
+
+EmbeddedElf* ApkInspector::FindElfInApkByOffset(const std::string& apk_path, uint64_t file_offset) {
+  // Already in cache?
+  ApkOffset ami(apk_path, file_offset);
+  auto it = embedded_elf_cache_.find(ami);
+  if (it != embedded_elf_cache_.end()) {
+    return it->second.get();
+  }
+  std::unique_ptr<EmbeddedElf> elf = FindElfInApkByOffsetWithoutCache(apk_path, file_offset);
+  EmbeddedElf* result = elf.get();
+  embedded_elf_cache_[ami] = std::move(elf);
+  return result;
+}
+
+std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByOffsetWithoutCache(const std::string& apk_path,
+                                                                            uint64_t file_offset) {
+  // Crack open the apk(zip) file and take a look.
+  if (!IsValidApkPath(apk_path)) {
+    return nullptr;
+  }
+
+  FileHelper fhelper = FileHelper::OpenReadOnly(apk_path);
+  if (!fhelper) {
+    return nullptr;
+  }
+
+  ArchiveHelper ahelper(fhelper.fd(), apk_path);
+  if (!ahelper) {
+    return nullptr;
+  }
+  ZipArchiveHandle &handle = ahelper.archive_handle();
+
+  // Iterate through the zip file. Look for a zip entry corresponding
+  // to an uncompressed blob whose range intersects with the mmap
+  // offset we're interested in.
+  void* iteration_cookie;
+  if (StartIteration(handle, &iteration_cookie, nullptr, nullptr) < 0) {
+    return nullptr;
+  }
+  ZipEntry zentry;
+  ZipString zname;
+  bool found = false;
+  int zrc;
+  while ((zrc = Next(iteration_cookie, &zentry, &zname)) == 0) {
+    if (zentry.method == kCompressStored &&
+        file_offset >= static_cast<uint64_t>(zentry.offset) &&
+        file_offset < static_cast<uint64_t>(zentry.offset + zentry.uncompressed_length)) {
+      // Found.
+      found = true;
+      break;
+    }
+  }
+  EndIteration(iteration_cookie);
+  if (!found) {
+    return nullptr;
+  }
+
+  // We found something in the zip file at the right spot. Is it an ELF?
+  if (lseek(fhelper.fd(), zentry.offset, SEEK_SET) != zentry.offset) {
+    PLOG(ERROR) << "lseek() failed in " << apk_path << " offset " << zentry.offset;
+    return nullptr;
+  }
+  std::string entry_name;
+  entry_name.resize(zname.name_length,'\0');
+  memcpy(&entry_name[0], zname.name, zname.name_length);
+  if (!IsValidElfFile(fhelper.fd())) {
+    LOG(ERROR) << "problems reading ELF from in " << apk_path << " entry '"
+               << entry_name << "'";
+    return nullptr;
+  }
+
+  // Elf found: add EmbeddedElf to vector, update cache.
+  return std::unique_ptr<EmbeddedElf>(new EmbeddedElf(apk_path, entry_name, zentry.offset,
+                                                      zentry.uncompressed_length));
+}
+
+std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByName(const std::string& apk_path,
+                                                              const std::string& elf_filename) {
+  if (!IsValidApkPath(apk_path)) {
+    return nullptr;
+  }
+  FileHelper fhelper = FileHelper::OpenReadOnly(apk_path);
+  if (!fhelper) {
+    return nullptr;
+  }
+  ArchiveHelper ahelper(fhelper.fd(), apk_path);
+  if (!ahelper) {
+    return nullptr;
+  }
+  ZipArchiveHandle& handle = ahelper.archive_handle();
+  ZipEntry zentry;
+  int32_t rc = FindEntry(handle, ZipString(elf_filename.c_str()), &zentry);
+  if (rc != 0) {
+    LOG(ERROR) << "failed to find " << elf_filename << " in " << apk_path
+        << ": " << ErrorCodeString(rc);
+    return nullptr;
+  }
+  if (zentry.method != kCompressStored || zentry.compressed_length != zentry.uncompressed_length) {
+    LOG(ERROR) << "shared library " << elf_filename << " in " << apk_path << " is compressed";
+    return nullptr;
+  }
+  return std::unique_ptr<EmbeddedElf>(new EmbeddedElf(apk_path, elf_filename, zentry.offset,
+                                                  zentry.uncompressed_length));
+}
+
+bool IsValidApkPath(const std::string& apk_path) {
+  static const char zip_preamble[] = {0x50, 0x4b, 0x03, 0x04 };
+  if (!IsRegularFile(apk_path)) {
+    return false;
+  }
+  std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE;
+  FILE* fp = fopen(apk_path.c_str(), mode.c_str());
+  if (fp == nullptr) {
+    return false;
+  }
+  char buf[4];
+  if (fread(buf, 4, 1, fp) != 1) {
+    fclose(fp);
+    return false;
+  }
+  fclose(fp);
+  return memcmp(buf, zip_preamble, 4) == 0;
+}
+
+// Refer file in apk in compliance with http://developer.android.com/reference/java/net/JarURLConnection.html.
+std::string GetUrlInApk(const std::string& apk_path, const std::string& elf_filename) {
+  return apk_path + "!/" + elf_filename;
+}
+
+std::tuple<bool, std::string, std::string> SplitUrlInApk(const std::string& path) {
+  size_t pos = path.find("!/");
+  if (pos == std::string::npos) {
+    return std::make_tuple(false, "", "");
+  }
+  return std::make_tuple(true, path.substr(0, pos), path.substr(pos + 2));
+}
+
+bool GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+                           BuildId* build_id) {
+  std::unique_ptr<EmbeddedElf> ee = ApkInspector::FindElfInApkByName(apk_path, elf_filename);
+  if (ee == nullptr) {
+    return false;
+  }
+  return GetBuildIdFromEmbeddedElfFile(apk_path, ee->entry_offset(), ee->entry_size(), build_id);
+}
+
+bool ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+                             const BuildId& expected_build_id,
+                             std::function<void(const ElfFileSymbol&)> callback) {
+  std::unique_ptr<EmbeddedElf> ee = ApkInspector::FindElfInApkByName(apk_path, elf_filename);
+  if (ee == nullptr) {
+    return false;
+  }
+  return ParseSymbolsFromEmbeddedElfFile(apk_path, ee->entry_offset(), ee->entry_size(),
+                                         expected_build_id, callback);
+}
diff --git a/simpleperf/read_apk.h b/simpleperf/read_apk.h
new file mode 100644 (file)
index 0000000..82531f4
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_READ_APK_H_
+#define SIMPLE_PERF_READ_APK_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+
+#include "read_elf.h"
+
+// Container for info an on ELF file embedded into an APK file
+class EmbeddedElf {
+ public:
+  EmbeddedElf()
+      : entry_offset_(0)
+      , entry_size_(0)
+  {
+  }
+
+  EmbeddedElf(std::string filepath,
+              std::string entry_name,
+              size_t entry_offset,
+              size_t entry_size)
+      : filepath_(filepath)
+      , entry_name_(entry_name)
+      , entry_offset_(entry_offset)
+      , entry_size_(entry_size)
+  {
+  }
+
+  // Path to APK file
+  const std::string &filepath() const { return filepath_; }
+
+  // Entry name within zip archive
+  const std::string &entry_name() const { return entry_name_; }
+
+  // Offset of zip entry from start of containing APK file
+  uint64_t entry_offset() const { return entry_offset_; }
+
+  // Size of zip entry (length of embedded ELF)
+  uint32_t entry_size() const { return entry_size_; }
+
+ private:
+  std::string filepath_; // containing APK path
+  std::string entry_name_; // name of entry in zip index of embedded elf file
+  uint64_t entry_offset_; // offset of ELF from start of containing APK file
+  uint32_t entry_size_;  // size of ELF file in zip
+};
+
+// APK inspector helper class
+class ApkInspector {
+ public:
+  // Given an APK/ZIP/JAR file and an offset into that file, if the
+  // corresponding region of the APK corresponds to an uncompressed
+  // ELF file, then return pertinent info on the ELF.
+  static EmbeddedElf* FindElfInApkByOffset(const std::string& apk_path, uint64_t file_offset);
+  static std::unique_ptr<EmbeddedElf> FindElfInApkByName(const std::string& apk_path,
+                                                         const std::string& elf_filename);
+
+ private:
+  static std::unique_ptr<EmbeddedElf> FindElfInApkByOffsetWithoutCache(const std::string& apk_path,
+                                                                       uint64_t file_offset);
+
+  // First component of pair is APK file path, second is offset into APK.
+  typedef std::pair<std::string, uint64_t> ApkOffset;
+
+  static std::map<ApkOffset, std::unique_ptr<EmbeddedElf>> embedded_elf_cache_;
+};
+
+// Export for test only.
+bool IsValidApkPath(const std::string& apk_path);
+
+std::string GetUrlInApk(const std::string& apk_path, const std::string& elf_filename);
+std::tuple<bool, std::string, std::string> SplitUrlInApk(const std::string& path);
+
+bool GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+                           BuildId* build_id);
+
+bool ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+                             const BuildId& expected_build_id,
+                             std::function<void(const ElfFileSymbol&)> callback);
+
+
+#endif  // SIMPLE_PERF_READ_APK_H_
diff --git a/simpleperf/read_apk_test.cpp b/simpleperf/read_apk_test.cpp
new file mode 100644 (file)
index 0000000..d7b30c5
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "read_apk.h"
+
+#include <gtest/gtest.h>
+#include "get_test_data.h"
+#include "test_util.h"
+
+
+TEST(read_apk, IsValidApkPath) {
+  ASSERT_FALSE(IsValidApkPath("/dev/zero"));
+  ASSERT_FALSE(IsValidApkPath(GetTestData(ELF_FILE)));
+  ASSERT_TRUE(IsValidApkPath(GetTestData(APK_FILE)));
+}
+
+TEST(read_apk, FindElfInApkByOffset) {
+  ApkInspector inspector;
+  ASSERT_TRUE(inspector.FindElfInApkByOffset("/dev/null", 0) == nullptr);
+  ASSERT_TRUE(inspector.FindElfInApkByOffset(GetTestData(APK_FILE), 0) == nullptr);
+  // Test if we can read the EmbeddedElf using an offset inside its [offset, offset+size] range
+  // in the apk file.
+  EmbeddedElf* ee = inspector.FindElfInApkByOffset(GetTestData(APK_FILE),
+                                                   NATIVELIB_OFFSET_IN_APK + NATIVELIB_SIZE_IN_APK / 2);
+  ASSERT_TRUE(ee != nullptr);
+  ASSERT_EQ(NATIVELIB_IN_APK, ee->entry_name());
+  ASSERT_EQ(NATIVELIB_OFFSET_IN_APK, ee->entry_offset());
+  ASSERT_EQ(NATIVELIB_SIZE_IN_APK, ee->entry_size());
+}
+
+TEST(read_apk, FindElfInApkByName) {
+  ASSERT_TRUE(ApkInspector::FindElfInApkByName("/dev/null", "") == nullptr);
+  ASSERT_TRUE(ApkInspector::FindElfInApkByName(GetTestData(APK_FILE), "") == nullptr);
+  auto ee = ApkInspector::FindElfInApkByName(GetTestData(APK_FILE), NATIVELIB_IN_APK);
+  ASSERT_TRUE(ee != nullptr);
+  ASSERT_EQ(NATIVELIB_OFFSET_IN_APK, ee->entry_offset());
+  ASSERT_EQ(NATIVELIB_SIZE_IN_APK, ee->entry_size());
+}
+
+TEST(read_apk, GetBuildIdFromApkFile) {
+  BuildId build_id;
+  ASSERT_TRUE(GetBuildIdFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, &build_id));
+  ASSERT_EQ(build_id, native_lib_build_id);
+}
+
+TEST(read_apk, ParseSymbolsFromApkFile) {
+  std::map<std::string, ElfFileSymbol> symbols;
+  ASSERT_TRUE(ParseSymbolsFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, native_lib_build_id,
+                                      std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+  CheckElfFileSymbols(symbols);
+}
index 1873b30..05b06aa 100644 (file)
  */
 
 #include "read_elf.h"
+#include "read_apk.h"
 
 #include <stdio.h>
 #include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
 #include <algorithm>
-#include <base/file.h>
-#include <base/logging.h>
+#include <limits>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
 
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wunused-parameter"
 
 #pragma clang diagnostic pop
 
-#include <elf.h>
-
 #include "utils.h"
 
+#define ELF_NOTE_GNU "GNU"
+#define NT_GNU_BUILD_ID 3
+
+
+bool IsValidElfFile(int fd) {
+  static const char elf_magic[] = {0x7f, 'E', 'L', 'F'};
+  char buf[4];
+  return android::base::ReadFully(fd, buf, 4) && memcmp(buf, elf_magic, 4) == 0;
+}
+
+bool IsValidElfPath(const std::string& filename) {
+  if (!IsRegularFile(filename)) {
+    return false;
+  }
+  std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE;
+  FILE* fp = fopen(filename.c_str(), mode.c_str());
+  if (fp == nullptr) {
+    return false;
+  }
+  bool result = IsValidElfFile(fileno(fp));
+  fclose(fp);
+  return result;
+}
+
 static bool GetBuildIdFromNoteSection(const char* section, size_t section_size, BuildId* build_id) {
   const char* p = section;
   const char* end = p + section_size;
@@ -51,8 +79,7 @@ static bool GetBuildIdFromNoteSection(const char* section, size_t section_size,
     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));
+      *build_id = BuildId(p + namesz, descsz);
       return true;
     }
     p += namesz + descsz;
@@ -75,9 +102,9 @@ bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id) {
 
 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();
+  for (auto section_iterator = elf->section_begin(); section_iterator != elf->section_end();
        ++section_iterator) {
-    if (section_iterator->sh_type == SHT_NOTE) {
+    if (section_iterator->sh_type == llvm::ELF::SHT_NOTE) {
       auto contents = elf->getSectionContents(&*section_iterator);
       if (contents.getError()) {
         LOG(DEBUG) << "read note section error";
@@ -92,25 +119,277 @@ bool GetBuildIdFromELFFile(const llvm::object::ELFFile<ELFT>* elf, BuildId* buil
   return false;
 }
 
+static bool GetBuildIdFromObjectFile(llvm::object::ObjectFile* obj, BuildId* build_id) {
+  bool result = false;
+  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 {
+    LOG(ERROR) << "unknown elf format in file " << obj->getFileName().data();
+    return false;
+  }
+  if (!result) {
+    LOG(DEBUG) << "no build id present in file " << obj->getFileName().data();
+  }
+  return result;
+}
+
+struct BinaryRet {
+  llvm::object::OwningBinary<llvm::object::Binary> binary;
+  llvm::object::ObjectFile* obj;
+
+  BinaryRet() : obj(nullptr) {
+  }
+};
+
+static BinaryRet OpenObjectFile(const std::string& filename, uint64_t file_offset = 0,
+                                uint64_t file_size = 0) {
+  BinaryRet ret;
+  FileHelper fhelper = FileHelper::OpenReadOnly(filename);
+  if (!fhelper) {
+    PLOG(DEBUG) << "failed to open " << filename;
+    return ret;
+  }
+  if (file_size == 0) {
+    file_size = GetFileSize(filename);
+    if (file_size == 0) {
+      PLOG(ERROR) << "failed to get size of file " << filename;
+      return ret;
+    }
+  }
+  auto buffer_or_err = llvm::MemoryBuffer::getOpenFileSlice(fhelper.fd(), filename, file_size, file_offset);
+  if (!buffer_or_err) {
+    LOG(ERROR) << "failed to read " << filename << " [" << file_offset << "-" << (file_offset + file_size)
+        << "]: " << buffer_or_err.getError().message();
+    return ret;
+  }
+  auto binary_or_err = llvm::object::createBinary(buffer_or_err.get()->getMemBufferRef());
+  if (!binary_or_err) {
+    LOG(ERROR) << filename << " [" << file_offset << "-" << (file_offset + file_size)
+        << "] is not a binary file: " << binary_or_err.getError().message();
+    return ret;
+  }
+  ret.binary = llvm::object::OwningBinary<llvm::object::Binary>(std::move(binary_or_err.get()),
+                                                                std::move(buffer_or_err.get()));
+  ret.obj = llvm::dyn_cast<llvm::object::ObjectFile>(ret.binary.getBinary());
+  if (ret.obj == nullptr) {
+    LOG(ERROR) << filename << " [" << file_offset << "-" << (file_offset + file_size)
+        << "] is not an object file";
+  }
+  return ret;
+}
+
 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;
+  if (!IsValidElfPath(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;
+  bool result = GetBuildIdFromEmbeddedElfFile(filename, 0, 0, build_id);
+  LOG(VERBOSE) << "GetBuildIdFromElfFile(" << filename << ") => " << build_id->ToString();
+  return result;
+}
+
+bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+                                   uint32_t file_size, BuildId* build_id) {
+  BinaryRet ret = OpenObjectFile(filename, file_offset, file_size);
+  if (ret.obj == nullptr) {
+    return false;
+  }
+  return GetBuildIdFromObjectFile(ret.obj, build_id);
+}
+
+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>
+void ParseSymbolsFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf_obj,
+                             std::function<void(const ElfFileSymbol&)> callback) {
+  auto elf = elf_obj->getELFFile();
+  bool is_arm = (elf->getHeader()->e_machine == llvm::ELF::EM_ARM ||
+                 elf->getHeader()->e_machine == llvm::ELF::EM_AARCH64);
+  auto begin = elf_obj->symbol_begin();
+  auto end = elf_obj->symbol_end();
+  if (begin == end) {
+    begin = elf_obj->dynamic_symbol_begin();
+    end = elf_obj->dynamic_symbol_end();
+  }
+  for (; begin != end; ++begin) {
+    ElfFileSymbol symbol;
+    auto elf_symbol = static_cast<const llvm::object::ELFSymbolRef*>(&*begin);
+    auto section_it = elf_symbol->getSection();
+    if (!section_it) {
+      continue;
+    }
+    llvm::StringRef section_name;
+    if (section_it.get()->getName(section_name) || section_name.empty()) {
+      continue;
+    }
+    if (section_name.str() == ".text") {
+      symbol.is_in_text_section = true;
+    }
+
+    auto symbol_name = elf_symbol->getName();
+    if (!symbol_name || symbol_name.get().empty()) {
+      continue;
+    }
+    symbol.name = symbol_name.get();
+    symbol.vaddr = elf_symbol->getValue();
+    if ((symbol.vaddr & 1) != 0 && is_arm) {
+      // Arm sets bit 0 to mark it as thumb code, remove the flag.
+      symbol.vaddr &= ~1;
+    }
+    symbol.len = elf_symbol->getSize();
+    int type = elf_symbol->getELFType();
+    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);
+  }
+}
+
+bool MatchBuildId(llvm::object::ObjectFile* obj, const BuildId& expected_build_id,
+                  const std::string& debug_filename) {
+  if (expected_build_id.IsEmpty()) {
+    return true;
+  }
+  BuildId real_build_id;
+  if (!GetBuildIdFromObjectFile(obj, &real_build_id)) {
+    return false;
+  }
+  if (expected_build_id != real_build_id) {
+    LOG(DEBUG) << "build id for " << debug_filename << " mismatch: "
+               << "expected " << expected_build_id.ToString()
+               << ", real " << real_build_id.ToString();
+    return false;
+  }
+  return true;
+}
+
+bool ParseSymbolsFromElfFile(const std::string& filename, const BuildId& expected_build_id,
+                             std::function<void(const ElfFileSymbol&)> callback) {
+  if (!IsValidElfPath(filename)) {
+    return false;
+  }
+  return ParseSymbolsFromEmbeddedElfFile(filename, 0, 0, expected_build_id, callback);
+}
+
+bool ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+                                     uint32_t file_size, const BuildId& expected_build_id,
+                                     std::function<void(const ElfFileSymbol&)> callback) {
+  BinaryRet ret = OpenObjectFile(filename, file_offset, file_size);
+  if (ret.obj == nullptr || !MatchBuildId(ret.obj, expected_build_id, filename)) {
+    return false;
+  }
+  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) {
+    ParseSymbolsFromELFFile(elf, callback);
+  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) {
+    ParseSymbolsFromELFFile(elf, callback);
+  } else {
+    LOG(ERROR) << "unknown elf format in file " << filename;
+    return false;
+  }
+  return true;
+}
+
+template <class ELFT>
+bool ReadMinExecutableVirtualAddress(const llvm::object::ELFFile<ELFT>* elf, uint64_t* p_vaddr) {
+  bool has_vaddr = false;
+  uint64_t min_addr = std::numeric_limits<uint64_t>::max();
+  for (auto it = elf->program_header_begin(); it != elf->program_header_end(); ++it) {
+    if ((it->p_type == llvm::ELF::PT_LOAD) && (it->p_flags & llvm::ELF::PF_X)) {
+      if (it->p_vaddr < min_addr) {
+        min_addr = it->p_vaddr;
+        has_vaddr = true;
+      }
     }
   }
+  if (has_vaddr) {
+    *p_vaddr = min_addr;
+  }
+  return has_vaddr;
+}
+
+bool ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
+                                                const BuildId& expected_build_id,
+                                                uint64_t* min_vaddr) {
+  if (!IsValidElfPath(filename)) {
+    return false;
+  }
+  BinaryRet ret = OpenObjectFile(filename);
+  if (ret.obj == nullptr || !MatchBuildId(ret.obj, expected_build_id, filename)) {
+    return false;
+  }
+
+  bool result = false;
+  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) {
+    result = ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr);
+  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) {
+    result = ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr);
+  } else {
+    LOG(ERROR) << "unknown elf format in file" << filename;
+    return false;
+  }
+
   if (!result) {
-    PLOG(DEBUG) << "can't read build_id from file " << filename;
+    LOG(ERROR) << "no program header in file " << filename;
+  }
+  return result;
+}
+
+template <class ELFT>
+bool ReadSectionFromELFFile(const llvm::object::ELFFile<ELFT>* elf, const std::string& section_name,
+                            std::string* content) {
+  for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
+    auto name_or_err = elf->getSectionName(&*it);
+    if (name_or_err && *name_or_err == section_name) {
+      auto data_or_err = elf->getSectionContents(&*it);
+      if (!data_or_err) {
+        LOG(ERROR) << "failed to read section " << section_name;
+        return false;
+      }
+      content->append(data_or_err->begin(), data_or_err->end());
+      return true;
+    }
+  }
+  LOG(ERROR) << "can't find section " << section_name;
+  return false;
+}
+
+bool ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
+                            std::string* content) {
+  if (!IsValidElfPath(filename)) {
+    return false;
+  }
+  BinaryRet ret = OpenObjectFile(filename);
+  if (ret.obj == nullptr) {
+    return false;
+  }
+  bool result = false;
+  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) {
+    result = ReadSectionFromELFFile(elf->getELFFile(), section_name, content);
+  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) {
+    result = ReadSectionFromELFFile(elf->getELFFile(), section_name, content);
+  } else {
+    LOG(ERROR) << "unknown elf format in file" << filename;
+    return false;
   }
   return result;
 }
index bc65fea..a6c73c3 100644 (file)
 #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);
+bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+                                   uint32_t file_size, 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 vaddr;
+  uint64_t len;
+  bool is_func;
+  bool is_label;
+  bool is_in_text_section;
+  std::string name;
+
+  ElfFileSymbol() : vaddr(0), len(0), is_func(false), is_label(false), is_in_text_section(false) {
+  }
+};
+
+bool ParseSymbolsFromElfFile(const std::string& filename, const BuildId& expected_build_id,
+                             std::function<void(const ElfFileSymbol&)> callback);
+bool ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+                                     uint32_t file_size, const BuildId& expected_build_id,
+                                     std::function<void(const ElfFileSymbol&)> callback);
+
+bool ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
+                                                const BuildId& expected_build_id,
+                                                uint64_t* min_addr);
+
+bool ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
+                            std::string* content);
+
+// Expose the following functions for unit tests.
+bool IsArmMappingSymbol(const char* name);
+bool IsValidElfFile(int fd);
+bool IsValidElfPath(const std::string& filename);
 
 #endif  // SIMPLE_PERF_READ_ELF_H_
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
new file mode 100644 (file)
index 0000000..929540f
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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>
+
+#include <map>
+#include "get_test_data.h"
+
+TEST(read_elf, GetBuildIdFromElfFile) {
+  BuildId build_id;
+  ASSERT_TRUE(GetBuildIdFromElfFile(GetTestData(ELF_FILE), &build_id));
+  ASSERT_EQ(build_id, BuildId(elf_file_build_id));
+}
+
+TEST(read_elf, GetBuildIdFromEmbeddedElfFile) {
+  BuildId build_id;
+  ASSERT_TRUE(GetBuildIdFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
+                                            NATIVELIB_SIZE_IN_APK, &build_id));
+  ASSERT_EQ(build_id, native_lib_build_id);
+}
+
+void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols) {
+  (*symbols)[symbol.name] = symbol;
+}
+
+void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
+  auto pos = symbols.find("GlobalVar");
+  ASSERT_NE(pos, symbols.end());
+  ASSERT_FALSE(pos->second.is_func);
+  pos = symbols.find("GlobalFunc");
+  ASSERT_NE(pos, symbols.end());
+  ASSERT_TRUE(pos->second.is_func);
+  ASSERT_TRUE(pos->second.is_in_text_section);
+}
+
+TEST(read_elf, parse_symbols_from_elf_file_with_correct_build_id) {
+  std::map<std::string, ElfFileSymbol> symbols;
+  ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData(ELF_FILE), elf_file_build_id,
+                                      std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+  CheckElfFileSymbols(symbols);
+}
+
+TEST(read_elf, parse_symbols_from_elf_file_without_build_id) {
+  std::map<std::string, ElfFileSymbol> symbols;
+  ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData(ELF_FILE), BuildId(),
+                                      std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+  CheckElfFileSymbols(symbols);
+}
+
+TEST(read_elf, parse_symbols_from_elf_file_with_wrong_build_id) {
+  BuildId build_id("01010101010101010101");
+  std::map<std::string, ElfFileSymbol> symbols;
+  ASSERT_FALSE(ParseSymbolsFromElfFile(GetTestData(ELF_FILE), build_id,
+                                       std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+}
+
+TEST(read_elf, ParseSymbolsFromEmbeddedElfFile) {
+  std::map<std::string, ElfFileSymbol> symbols;
+  ASSERT_TRUE(ParseSymbolsFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
+                                              NATIVELIB_SIZE_IN_APK, native_lib_build_id,
+                                              std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+  CheckElfFileSymbols(symbols);
+}
+
+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"));
+}
+
+TEST(read_elf, IsValidElfPath) {
+  ASSERT_FALSE(IsValidElfPath("/dev/zero"));
+  ASSERT_FALSE(IsValidElfPath("/sys/devices/system/cpu/online"));
+  ASSERT_TRUE(IsValidElfPath(GetTestData(ELF_FILE)));
+}
index 46910b9..fca2403 100644 (file)
 #include "record.h"
 
 #include <inttypes.h>
+#include <algorithm>
 #include <unordered_map>
 
-#include <base/logging.h>
-#include <base/stringprintf.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 #include "environment.h"
+#include "perf_regs.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_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);
@@ -47,9 +45,10 @@ static std::string RecordTypeToString(int record_type) {
 }
 
 template <class T>
-void MoveFromBinaryFormat(T& data, const char*& p) {
-  data = *reinterpret_cast<const T*>(p);
-  p += sizeof(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>
@@ -58,6 +57,13 @@ void MoveToBinaryFormat(const T& data, char*& p) {
   p += sizeof(T);
 }
 
+template <class T>
+void MoveToBinaryFormat(const T* data_p, size_t n, char*& p) {
+  size_t size = n * sizeof(T);
+  memcpy(p, data_p, size);
+  p += size;
+}
+
 SampleId::SampleId() {
   memset(this, 0, sizeof(SampleId));
 }
@@ -67,25 +73,7 @@ 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;
+  return Size();
 }
 
 void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end) {
@@ -155,6 +143,28 @@ void SampleId::Dump(size_t indent) const {
   }
 }
 
+size_t SampleId::Size() const {
+  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;
+}
+
 Record::Record() {
   memset(&header, 0, sizeof(header));
 }
@@ -170,6 +180,10 @@ void Record::Dump(size_t indent) const {
   sample_id.Dump(indent + 1);
 }
 
+uint64_t Record::Timestamp() const {
+  return sample_id.time_data.time;
+}
+
 MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader)
     : Record(pheader) {
   const char* p = reinterpret_cast<const char*>(pheader + 1);
@@ -181,13 +195,39 @@ MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* phe
   sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
+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;
+}
+
+void MmapRecord::AdjustSizeBasedOnData() {
+  header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size();
+}
+
 void MmapRecord::DumpData(size_t indent) const {
-  PrintIndented(indent, "pid %u, tid %u, addr %p, len 0x%" PRIx64 "\n", data.pid, data.tid,
-                reinterpret_cast<void*>(data.addr), data.len);
+  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 {
+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);
+}
+
+std::vector<char> Mmap2Record::BinaryFormat() const {
   std::vector<char> buf(header.size);
   char* p = buf.data();
   MoveToBinaryFormat(header, p);
@@ -198,6 +238,20 @@ std::vector<char> MmapRecord::BinaryFormat() const {
   return buf;
 }
 
+void Mmap2Record::AdjustSizeBasedOnData() {
+  header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size();
+}
+
+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);
@@ -209,10 +263,6 @@ CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* phe
   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();
@@ -224,7 +274,11 @@ std::vector<char> CommRecord::BinaryFormat() const {
   return buf;
 }
 
-ExitRecord::ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader)
+void CommRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "pid %u, tid %u, comm %s\n", data.pid, data.tid, comm.c_str());
+}
+
+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;
@@ -233,7 +287,16 @@ ExitRecord::ExitRecord(const perf_event_attr& attr, const perf_event_header* phe
   sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
-void ExitRecord::DumpData(size_t indent) const {
+std::vector<char> ExitOrForkRecord::BinaryFormat() const {
+  std::vector<char> buf(header.size);
+  char* p = buf.data();
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(data, p);
+  sample_id.WriteToBinaryFormat(p);
+  return buf;
+}
+
+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);
 }
@@ -268,6 +331,51 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header*
   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_RAW) {
+    uint32_t size;
+    MoveFromBinaryFormat(size, p);
+    raw_data.data.resize(size);
+    MoveFromBinaryFormat(raw_data.data.data(), size, 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);
+  }
+  if (sample_type & PERF_SAMPLE_REGS_USER) {
+    MoveFromBinaryFormat(regs_user_data.abi, p);
+    if (regs_user_data.abi == 0) {
+      regs_user_data.reg_mask = 0;
+    } else {
+      regs_user_data.reg_mask = attr.sample_regs_user;
+      size_t bit_nr = 0;
+      for (size_t i = 0; i < 64; ++i) {
+        if ((regs_user_data.reg_mask >> i) & 1) {
+          bit_nr++;
+        }
+      }
+      regs_user_data.regs.resize(bit_nr);
+      MoveFromBinaryFormat(regs_user_data.regs.data(), bit_nr, p);
+    }
+  }
+  if (sample_type & PERF_SAMPLE_STACK_USER) {
+    uint64_t size;
+    MoveFromBinaryFormat(size, p);
+    if (size == 0) {
+      stack_user_data.dyn_size = 0;
+    } else {
+      stack_user_data.data.resize(size);
+      MoveFromBinaryFormat(stack_user_data.data.data(), size, p);
+      MoveFromBinaryFormat(stack_user_data.dyn_size, p);
+    }
+  }
   // TODO: Add parsing of other PERF_SAMPLE_*.
   CHECK_LE(p, end);
   if (p < end) {
@@ -275,6 +383,77 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header*
   }
 }
 
+std::vector<char> SampleRecord::BinaryFormat() const {
+  std::vector<char> buf(header.size);
+  char* p = buf.data();
+  MoveToBinaryFormat(header, p);
+  if (sample_type & PERF_SAMPLE_IP) {
+    MoveToBinaryFormat(ip_data, p);
+  }
+  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_ADDR) {
+    MoveToBinaryFormat(addr_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);
+  }
+  if (sample_type & PERF_SAMPLE_PERIOD) {
+    MoveToBinaryFormat(period_data, p);
+  }
+  if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+    uint64_t nr = callchain_data.ips.size();
+    MoveToBinaryFormat(nr, p);
+    MoveToBinaryFormat(callchain_data.ips.data(), nr, p);
+  }
+  if (sample_type & PERF_SAMPLE_RAW) {
+    uint32_t size = raw_data.data.size();
+    MoveToBinaryFormat(size, p);
+    MoveToBinaryFormat(raw_data.data.data(), size, p);
+  }
+  if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+    uint64_t nr = branch_stack_data.stack.size();
+    MoveToBinaryFormat(nr, p);
+    MoveToBinaryFormat(branch_stack_data.stack.data(), nr, p);
+  }
+  if (sample_type & PERF_SAMPLE_REGS_USER) {
+    MoveToBinaryFormat(regs_user_data.abi, p);
+    if (regs_user_data.abi != 0) {
+      MoveToBinaryFormat(regs_user_data.regs.data(), regs_user_data.regs.size(), p);
+    }
+  }
+  if (sample_type & PERF_SAMPLE_STACK_USER) {
+    uint64_t size = stack_user_data.data.size();
+    MoveToBinaryFormat(size, p);
+    if (size != 0) {
+      MoveToBinaryFormat(stack_user_data.data.data(), size, p);
+      MoveToBinaryFormat(stack_user_data.dyn_size, p);
+    }
+  }
+
+  // If record command does stack unwinding, sample records' size may be decreased.
+  // So we can't trust header.size here, and should adjust buffer size based on real need.
+  buf.resize(p - buf.data());
+  return buf;
+}
+
+void SampleRecord::AdjustSizeBasedOnData() {
+  size_t size = BinaryFormat().size();
+  LOG(DEBUG) << "Record (type " << RecordTypeToString(header.type) << ") size is changed from "
+      << header.size << " to " << size;
+  header.size = size;
+}
+
 void SampleRecord::DumpData(size_t indent) const {
   PrintIndented(indent, "sample_type: 0x%" PRIx64 "\n", sample_type);
   if (sample_type & PERF_SAMPLE_IP) {
@@ -301,55 +480,152 @@ void SampleRecord::DumpData(size_t indent) const {
   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_RAW) {
+    PrintIndented(indent, "raw size=%zu\n", raw_data.data.size());
+    const uint32_t* data = reinterpret_cast<const uint32_t*>(raw_data.data.data());
+    size_t size = raw_data.data.size() / sizeof(uint32_t);
+    for (size_t i = 0; i < size; ++i) {
+      PrintIndented(indent + 1, "0x%08x (%zu)\n", data[i], data[i]);
+    }
+  }
+  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);
+    }
+  }
+  if (sample_type & PERF_SAMPLE_REGS_USER) {
+    PrintIndented(indent, "user regs: abi=%" PRId64 "\n", regs_user_data.abi);
+    for (size_t i = 0, pos = 0; i < 64; ++i) {
+      if ((regs_user_data.reg_mask >> i) & 1) {
+        PrintIndented(indent + 1, "reg (%s) 0x%016" PRIx64 "\n",
+                      GetRegName(i, ScopedCurrentArch::GetCurrentArch()).c_str(),
+                      regs_user_data.regs[pos++]);
+      }
+    }
+  }
+  if (sample_type & PERF_SAMPLE_STACK_USER) {
+    PrintIndented(indent, "user stack: size %zu dyn_size %" PRIu64 "\n",
+                  stack_user_data.data.size(), stack_user_data.dyn_size);
+    const uint64_t* p = reinterpret_cast<const uint64_t*>(stack_user_data.data.data());
+    const uint64_t* end = p + (stack_user_data.data.size() / sizeof(uint64_t));
+    while (p < end) {
+      PrintIndented(indent + 1, "");
+      for (size_t i = 0; i < 4 && p < end; ++i, ++p) {
+        printf(" %016" PRIx64, *p);
+      }
+      printf("\n");
+    }
+    printf("\n");
+  }
+}
+
+uint64_t SampleRecord::Timestamp() const {
+  return time_data.time;
 }
 
 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);
+  build_id = BuildId(p, BUILD_ID_SIZE);
+  p += ALIGN(build_id.Size(), 8);
   filename = p;
   p += ALIGN(filename.size() + 1, 64);
   CHECK_EQ(p, end);
 }
 
+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;
+}
+
 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, "build_id %s\n", build_id.ToString().c_str());
   PrintIndented(indent, "filename %s\n", filename.c_str());
 }
 
-std::vector<char> BuildIdRecord::BinaryFormat() const {
+UnknownRecord::UnknownRecord(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;
+  data.insert(data.end(), p, end);
+}
+
+std::vector<char> UnknownRecord::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);
+  MoveToBinaryFormat(data.data(), data.size(), p);
   return buf;
 }
 
-std::unique_ptr<const Record> ReadRecordFromBuffer(const perf_event_attr& attr,
-                                                   const perf_event_header* pheader) {
+void UnknownRecord::DumpData(size_t) const {
+}
+
+static std::unique_ptr<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));
+      return std::unique_ptr<Record>(new MmapRecord(attr, pheader));
+    case PERF_RECORD_MMAP2:
+      return std::unique_ptr<Record>(new Mmap2Record(attr, pheader));
     case PERF_RECORD_COMM:
-      return std::unique_ptr<const Record>(new CommRecord(attr, pheader));
+      return std::unique_ptr<Record>(new CommRecord(attr, pheader));
     case PERF_RECORD_EXIT:
-      return std::unique_ptr<const Record>(new ExitRecord(attr, pheader));
+      return std::unique_ptr<Record>(new ExitRecord(attr, pheader));
+    case PERF_RECORD_FORK:
+      return std::unique_ptr<Record>(new ForkRecord(attr, pheader));
     case PERF_RECORD_SAMPLE:
-      return std::unique_ptr<const Record>(new SampleRecord(attr, pheader));
+      return std::unique_ptr<Record>(new SampleRecord(attr, pheader));
     default:
-      return std::unique_ptr<const Record>(new Record(pheader));
+      return std::unique_ptr<Record>(new UnknownRecord(pheader));
+  }
+}
+
+std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr& attr,
+                                                           const char* buf, size_t buf_size) {
+  std::vector<std::unique_ptr<Record>> result;
+  const char* p = buf;
+  const char* end = buf + buf_size;
+  while (p < end) {
+    const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p);
+    CHECK_LE(p + header->size, end);
+    CHECK_NE(0u, header->size);
+    result.push_back(ReadRecordFromBuffer(attr, header));
+    p += header->size;
   }
+  return result;
+}
+
+std::unique_ptr<Record> ReadRecordFromFile(const perf_event_attr& attr, FILE* fp) {
+  std::vector<char> buf(sizeof(perf_event_header));
+  perf_event_header* header = reinterpret_cast<perf_event_header*>(&buf[0]);
+  if (fread(header, sizeof(perf_event_header), 1, fp) != 1) {
+    PLOG(ERROR) << "Failed to read record file";
+    return nullptr;
+  }
+  buf.resize(header->size);
+  header = reinterpret_cast<perf_event_header*>(&buf[0]);
+  if (fread(&buf[sizeof(perf_event_header)], buf.size() - sizeof(perf_event_header), 1, fp) != 1) {
+    PLOG(ERROR) << "Failed to read record file";
+    return nullptr;
+  }
+  return ReadRecordFromBuffer(attr, header);
 }
 
 MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
@@ -384,6 +660,21 @@ CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t
   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;
@@ -393,6 +684,90 @@ BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& buil
   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);
+                       ALIGN(record.build_id.Size(), 8) + ALIGN(filename.size() + 1, 64);
   return record;
 }
+
+bool RecordCache::RecordWithSeq::IsHappensBefore(const RecordWithSeq& other) const {
+  bool is_sample = (record->header.type == PERF_RECORD_SAMPLE);
+  bool is_other_sample = (other.record->header.type == PERF_RECORD_SAMPLE);
+  uint64_t time = record->Timestamp();
+  uint64_t other_time = other.record->Timestamp();
+  // The record with smaller time happens first.
+  if (time != other_time) {
+    return time < other_time;
+  }
+  // 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_sample != is_other_sample) {
+    return is_sample ? false : true;
+  }
+  // Otherwise, use the same order as they enter the cache.
+  return seq < other.seq;
+}
+
+bool RecordCache::RecordComparator::operator()(const RecordWithSeq& r1,
+                                               const RecordWithSeq& r2) {
+  return r2.IsHappensBefore(r1);
+}
+
+RecordCache::RecordCache(const perf_event_attr& attr, size_t min_cache_size,
+                         uint64_t min_time_diff_in_ns)
+    : attr_(attr),
+      has_timestamp_(attr.sample_id_all && (attr.sample_type & PERF_SAMPLE_TIME)),
+      min_cache_size_(min_cache_size),
+      min_time_diff_in_ns_(min_time_diff_in_ns),
+      last_time_(0),
+      cur_seq_(0),
+      queue_(RecordComparator()) {
+}
+
+RecordCache::~RecordCache() {
+  PopAll();
+}
+
+void RecordCache::Push(const char* data, size_t size) {
+  std::vector<std::unique_ptr<Record>> records = ReadRecordsFromBuffer(attr_, data, size);
+  if (has_timestamp_) {
+    for (const auto& r : records) {
+      last_time_ = std::max(last_time_, r->Timestamp());
+    }
+  }
+  for (auto& r : records) {
+    queue_.push(CreateRecordWithSeq(r.release()));
+  }
+}
+
+void RecordCache::Push(std::unique_ptr<Record> record) {
+  queue_.push(CreateRecordWithSeq(record.release()));
+}
+
+std::unique_ptr<Record> RecordCache::Pop() {
+  if (queue_.size() < min_cache_size_) {
+    return nullptr;
+  }
+  Record* r = queue_.top().record;
+  if (has_timestamp_) {
+    if (r->Timestamp() + min_time_diff_in_ns_ > last_time_) {
+      return nullptr;
+    }
+  }
+  queue_.pop();
+  return std::unique_ptr<Record>(r);
+}
+
+std::vector<std::unique_ptr<Record>> RecordCache::PopAll() {
+  std::vector<std::unique_ptr<Record>> result;
+  while (!queue_.empty()) {
+    result.emplace_back(queue_.top().record);
+    queue_.pop();
+  }
+  return result;
+}
+
+RecordCache::RecordWithSeq RecordCache::CreateRecordWithSeq(Record *r) {
+  RecordWithSeq result;
+  result.seq = cur_seq_++;
+  result.record = r;
+  return result;
+}
index 83f60db..a94a917 100644 (file)
 #ifndef SIMPLE_PERF_RECORD_H_
 #define SIMPLE_PERF_RECORD_H_
 
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <queue>
 #include <string>
 #include <vector>
 
@@ -68,6 +73,34 @@ struct PerfSamplePeriodType {
   uint64_t period;
 };
 
+struct PerfSampleCallChainType {
+  std::vector<uint64_t> ips;
+};
+
+struct PerfSampleRawType {
+  std::vector<char> data;
+};
+
+struct PerfSampleBranchStackType {
+  struct BranchStackItemType {
+    uint64_t from;
+    uint64_t to;
+    uint64_t flags;
+  };
+  std::vector<BranchStackItemType> stack;
+};
+
+struct PerfSampleRegsUserType {
+  uint64_t abi;
+  uint64_t reg_mask;
+  std::vector<uint64_t> regs;
+};
+
+struct PerfSampleStackUserType {
+  std::vector<char> data;
+  uint64_t dyn_size;
+};
+
 // 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.
@@ -92,6 +125,7 @@ struct SampleId {
   // Write the binary format of sample_id to the buffer pointed by p.
   void WriteToBinaryFormat(char*& p) const;
   void Dump(size_t indent) const;
+  size_t Size() const;
 };
 
 // Usually one record contains the following three parts in order in binary format:
@@ -110,11 +144,20 @@ struct Record {
   virtual ~Record() {
   }
 
+  size_t size() const {
+    return header.size;
+  }
+
+  uint32_t type() const {
+    return header.type;
+  }
+
   void Dump(size_t indent = 0) const;
+  virtual std::vector<char> BinaryFormat() const = 0;
+  virtual uint64_t Timestamp() const;
 
  protected:
-  virtual void DumpData(size_t) const {
-  }
+  virtual void DumpData(size_t) const = 0;
 };
 
 struct MmapRecord : public Record {
@@ -126,11 +169,37 @@ struct MmapRecord : public Record {
   } data;
   std::string filename;
 
-  MmapRecord() {  // For storage in std::vector.
+  MmapRecord() {  // For CreateMmapRecord.
   }
 
   MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const;
+  std::vector<char> BinaryFormat() const override;
+  void AdjustSizeBasedOnData();
+
+ 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);
+  std::vector<char> BinaryFormat() const override;
+  void AdjustSizeBasedOnData();
 
  protected:
   void DumpData(size_t indent) const override;
@@ -146,25 +215,42 @@ struct CommRecord : public Record {
   }
 
   CommRecord(const perf_event_attr& attr, const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const;
+  std::vector<char> BinaryFormat() const override;
 
  protected:
   void DumpData(size_t indent) const override;
 };
 
-struct ExitRecord : public Record {
-  struct ExitRecordDataType {
+struct ExitOrForkRecord : public Record {
+  struct ExitOrForkRecordDataType {
     uint32_t pid, ppid;
     uint32_t tid, ptid;
     uint64_t time;
   } data;
 
-  ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+  ExitOrForkRecord() {
+  }
+  ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+  std::vector<char> BinaryFormat() const override;
 
  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) {
+  }
+};
+
 struct SampleRecord : public Record {
   uint64_t sample_type;  // sample_type is a bit mask determining which fields below are valid.
 
@@ -177,7 +263,16 @@ struct SampleRecord : public Record {
   PerfSampleCpuType cpu_data;             // Valid if PERF_SAMPLE_CPU.
   PerfSamplePeriodType period_data;       // Valid if PERF_SAMPLE_PERIOD.
 
+  PerfSampleCallChainType callchain_data;       // Valid if PERF_SAMPLE_CALLCHAIN.
+  PerfSampleRawType raw_data;                   // Valid if PERF_SAMPLE_RAW.
+  PerfSampleBranchStackType branch_stack_data;  // Valid if PERF_SAMPLE_BRANCH_STACK.
+  PerfSampleRegsUserType regs_user_data;        // Valid if PERF_SAMPLE_REGS_USER.
+  PerfSampleStackUserType stack_user_data;      // Valid if PERF_SAMPLE_STACK_USER.
+
   SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+  std::vector<char> BinaryFormat() const override;
+  void AdjustSizeBasedOnData();
+  uint64_t Timestamp() const override;
 
  protected:
   void DumpData(size_t indent) const override;
@@ -193,19 +288,83 @@ struct BuildIdRecord : public Record {
   }
 
   BuildIdRecord(const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const;
+  std::vector<char> BinaryFormat() const override;
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+// UnknownRecord is used for unknown record types, it makes sure all unknown records
+// are not changed when modifying perf.data.
+struct UnknownRecord : public Record {
+  std::vector<char> data;
+
+  UnknownRecord(const perf_event_header* pheader);
+  std::vector<char> BinaryFormat() const override;
 
  protected:
   void DumpData(size_t indent) const override;
 };
 
-std::unique_ptr<const Record> ReadRecordFromBuffer(const perf_event_attr& attr,
-                                                   const perf_event_header* pheader);
+// RecordCache is a cache used when receiving records from the kernel.
+// It sorts received records based on type and timestamp, and pops records
+// in sorted order. Records from the kernel need to be sorted because
+// records may come from different cpus at the same time, and it is affected
+// by the order in which we collect records from different cpus.
+// RecordCache pushes records and pops sorted record online. It uses two checks to help
+// ensure that records are popped in order. Each time we pop a record A, it is the earliest record
+// among all records in the cache. In addition, we have checks for min_cache_size and
+// min_time_diff. For min_cache_size check, we check if the cache size >= min_cache_size,
+// which is based on the assumption that if we have received (min_cache_size - 1) records
+// after record A, we are not likely to receive a record earlier than A. For min_time_diff
+// check, we check if record A is generated min_time_diff ns earlier than the latest
+// record, which is based on the assumption that if we have received a record for time t,
+// we are not likely to receive a record for time (t - min_time_diff) or earlier.
+class RecordCache {
+ public:
+  RecordCache(const perf_event_attr& attr, size_t min_cache_size = 1000u,
+              uint64_t min_time_diff_in_ns = 1000000u);
+  ~RecordCache();
+  void Push(const char* data, size_t size);
+  void Push(std::unique_ptr<Record> record);
+  std::unique_ptr<Record> Pop();
+  std::vector<std::unique_ptr<Record>> PopAll();
+
+ private:
+  struct RecordWithSeq {
+    uint32_t seq;
+    Record *record;
+
+    bool IsHappensBefore(const RecordWithSeq& other) const;
+  };
+
+  struct RecordComparator {
+    bool operator()(const RecordWithSeq& r1, const RecordWithSeq& r2);
+  };
+
+  RecordWithSeq CreateRecordWithSeq(Record *r);
+
+  const perf_event_attr attr_;
+  bool has_timestamp_;
+  size_t min_cache_size_;
+  uint64_t min_time_diff_in_ns_;
+  uint64_t last_time_;
+  uint32_t cur_seq_;
+  std::priority_queue<RecordWithSeq, std::vector<RecordWithSeq>,
+      RecordComparator> queue_;
+};
+
+std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr& attr,
+                                                           const char* buf, size_t buf_size);
+std::unique_ptr<Record> ReadRecordFromFile(const perf_event_attr& attr, FILE* fp);
 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_file.cpp b/simpleperf/record_file.cpp
deleted file mode 100644 (file)
index 54a4dda..0000000
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * 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) {
-  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;
-  }
-  if (fseek(record_fp_, 0, SEEK_END) == -1) {
-    PLOG(ERROR) << "fseek() failed";
-    return false;
-  }
-  long file_size = ftell(record_fp_);
-  if (file_size == -1) {
-    PLOG(ERROR) << "ftell() failed";
-    return false;
-  }
-  size_t mmap_len = 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::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) {
-  if (current_feature_index_ >= feature_count_) {
-    return false;
-  }
-  // Always write features at the end of the file.
-  if (fseek(record_fp_, 0, SEEK_END) == -1) {
-    PLOG(ERROR) << "fseek() failed";
-    return false;
-  }
-  long section_start = ftell(record_fp_);
-  if (section_start == -1) {
-    PLOG(ERROR) << "ftell() failed";
-    return false;
-  }
-  for (auto& record : build_id_records) {
-    std::vector<char> data = record.BinaryFormat();
-    if (!Write(data.data(), data.size())) {
-      return false;
-    }
-  }
-  long section_end = ftell(record_fp_);
-  if (section_end == -1) {
-    return false;
-  }
-
-  // Write feature section descriptor for build_id feature.
-  SectionDesc desc;
-  desc.offset = section_start;
-  desc.size = section_end - section_start;
-  uint64_t feature_offset = data_section_offset_ + data_section_size_;
-  if (fseek(record_fp_, feature_offset + current_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;
-  }
-  ++current_feature_index_;
-  features_.push_back(FEAT_BUILD_ID);
-  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;
-}
-
-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;
-  }
-  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;
-}
index 694486c..c0f53b1 100644 (file)
 #define SIMPLE_PERF_RECORD_FILE_H_
 
 #include <stdio.h>
+
+#include <functional>
+#include <map>
 #include <memory>
 #include <string>
 #include <vector>
 
-#include <base/macros.h>
+#include <android-base/macros.h>
 
 #include "perf_event.h"
 #include "record.h"
 #include "record_file_format.h"
 
-class EventFd;
+struct AttrWithId {
+  const perf_event_attr* attr;
+  std::vector<uint64_t> ids;
+};
 
 // 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);
+  static std::unique_ptr<RecordFileWriter> CreateInstance(const std::string& filename);
 
   ~RecordFileWriter();
 
+  bool WriteAttrSection(const std::vector<AttrWithId>& attr_ids);
   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 WriteFeatureString(int feature, const std::string& s);
+  bool WriteCmdlineFeature(const std::vector<std::string>& cmdline);
+  bool WriteBranchStackFeature();
 
   // Normally, Close() should be called after writing. But if something
   // wrong happens and we need to finish in advance, the destructor
@@ -60,13 +63,14 @@ class RecordFileWriter {
 
  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 WriteFeatureBegin(uint64_t* start_offset);
+  bool WriteFeatureEnd(int feature, uint64_t start_offset);
 
   const std::string filename_;
   FILE* record_fp_;
@@ -91,25 +95,42 @@ class RecordFileReader {
 
   ~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;
+  const PerfFileFormat::FileHeader& FileHeader() const {
+    return header_;
+  }
+
+  const std::vector<PerfFileFormat::FileAttr>& AttrSection() const {
+    return file_attrs_;
   }
+
+  const std::map<int, PerfFileFormat::SectionDesc>& FeatureSectionDescriptors() const {
+    return feature_section_descriptors_;
+  }
+
+  bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
+  // If sorted is true, sort records before passing them to callback function.
+  bool ReadDataSection(std::function<bool(std::unique_ptr<Record>)> callback, bool sorted = true);
+  std::vector<std::string> ReadCmdlineFeature();
+  std::vector<BuildIdRecord> ReadBuildIdFeature();
+  std::string ReadFeatureString(int feature);
   bool Close();
 
+  // For testing only.
+  std::vector<std::unique_ptr<Record>> DataSection();
+
  private:
-  RecordFileReader(const std::string& filename, int fd);
-  bool MmapFile();
+  RecordFileReader(const std::string& filename, FILE* fp);
+  bool ReadHeader();
+  bool ReadAttrSection();
+  bool ReadFeatureSectionDescriptors();
+  bool ReadFeatureSection(int feature, std::vector<char>* data);
 
   const std::string filename_;
-  int record_fd_;
+  FILE* record_fp_;
 
-  const char* mmap_addr_;
-  size_t mmap_len_;
+  PerfFileFormat::FileHeader header_;
+  std::vector<PerfFileFormat::FileAttr> file_attrs_;
+  std::map<int, PerfFileFormat::SectionDesc> feature_section_descriptors_;
 
   DISALLOW_COPY_AND_ASSIGN(RecordFileReader);
 };
index 9758f11..da6434b 100644 (file)
@@ -63,7 +63,7 @@ struct SectionDesc {
   uint64_t size;
 };
 
-static const char* PERF_MAGIC = "PERFILE2";
+constexpr char PERF_MAGIC[] = "PERFILE2";
 
 struct FileHeader {
   char magic[8];
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
new file mode 100644 (file)
index 0000000..f126a6b
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * 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 <set>
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "perf_event.h"
+#include "record.h"
+#include "utils.h"
+
+using namespace PerfFileFormat;
+
+std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::string& filename) {
+  std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE;
+  FILE* fp = fopen(filename.c_str(), mode.c_str());
+  if (fp == nullptr) {
+    PLOG(ERROR) << "failed to open record file '" << filename << "'";
+    return nullptr;
+  }
+  auto reader = std::unique_ptr<RecordFileReader>(new RecordFileReader(filename, fp));
+  if (!reader->ReadHeader() || !reader->ReadAttrSection() ||
+      !reader->ReadFeatureSectionDescriptors()) {
+    return nullptr;
+  }
+  return reader;
+}
+
+RecordFileReader::RecordFileReader(const std::string& filename, FILE* fp)
+    : filename_(filename), record_fp_(fp) {
+}
+
+RecordFileReader::~RecordFileReader() {
+  if (record_fp_ != nullptr) {
+    Close();
+  }
+}
+
+bool RecordFileReader::Close() {
+  bool result = true;
+  if (fclose(record_fp_) != 0) {
+    PLOG(ERROR) << "failed to close record file '" << filename_ << "'";
+    result = false;
+  }
+  record_fp_ = nullptr;
+  return result;
+}
+
+bool RecordFileReader::ReadHeader() {
+  if (fread(&header_, sizeof(header_), 1, record_fp_) != 1) {
+    PLOG(ERROR) << "failed to read file " << filename_;
+    return false;
+  }
+  return true;
+}
+
+bool RecordFileReader::ReadAttrSection() {
+  size_t attr_count = header_.attrs.size / header_.attr_size;
+  if (header_.attr_size != sizeof(FileAttr)) {
+    LOG(DEBUG) << "attr size (" << header_.attr_size << ") in " << filename_
+                 << " doesn't match expected size (" << sizeof(FileAttr) << ")";
+  }
+  if (attr_count == 0) {
+    LOG(ERROR) << "no attr in file " << filename_;
+    return false;
+  }
+  if (fseek(record_fp_, header_.attrs.offset, SEEK_SET) != 0) {
+    PLOG(ERROR) << "failed to fseek()";
+    return false;
+  }
+  for (size_t i = 0; i < attr_count; ++i) {
+    std::vector<char> buf(header_.attr_size);
+    if (fread(&buf[0], buf.size(), 1, record_fp_) != 1) {
+      PLOG(ERROR) << "failed to read " << filename_;
+      return false;
+    }
+    // The size of perf_event_attr is changing between different linux kernel versions.
+    // Make sure we copy correct data to memory.
+    FileAttr attr;
+    memset(&attr, 0, sizeof(attr));
+    size_t section_desc_size = sizeof(attr.ids);
+    size_t perf_event_attr_size = header_.attr_size - section_desc_size;
+    memcpy(&attr.attr, &buf[0], std::min(sizeof(attr.attr), perf_event_attr_size));
+    memcpy(&attr.ids, &buf[perf_event_attr_size], section_desc_size);
+    file_attrs_.push_back(attr);
+  }
+  return true;
+}
+
+bool RecordFileReader::ReadFeatureSectionDescriptors() {
+  std::vector<int> features;
+  for (size_t i = 0; i < sizeof(header_.features); ++i) {
+    for (size_t j = 0; j < 8; ++j) {
+      if (header_.features[i] & (1 << j)) {
+        features.push_back(i * 8 + j);
+      }
+    }
+  }
+  uint64_t feature_section_offset = header_.data.offset + header_.data.size;
+  if (fseek(record_fp_, feature_section_offset, SEEK_SET) != 0) {
+    PLOG(ERROR) << "failed to fseek()";
+    return false;
+  }
+  for (const auto& id : features) {
+    SectionDesc desc;
+    if (fread(&desc, sizeof(desc), 1, record_fp_) != 1) {
+      PLOG(ERROR) << "failed to read " << filename_;
+      return false;
+    }
+    feature_section_descriptors_.emplace(id, desc);
+  }
+  return true;
+}
+
+bool RecordFileReader::ReadIdsForAttr(const FileAttr& attr, std::vector<uint64_t>* ids) {
+  size_t id_count = attr.ids.size / sizeof(uint64_t);
+  if (fseek(record_fp_, attr.ids.offset, SEEK_SET) != 0) {
+    PLOG(ERROR) << "failed to fseek()";
+    return false;
+  }
+  ids->resize(id_count);
+  if (fread(ids->data(), attr.ids.size, 1, record_fp_) != 1) {
+    PLOG(ERROR) << "failed to read file " << filename_;
+    return false;
+  }
+  return true;
+}
+
+bool RecordFileReader::ReadDataSection(std::function<bool(std::unique_ptr<Record>)> callback,
+                                       bool sorted) {
+  if (fseek(record_fp_, header_.data.offset, SEEK_SET) != 0) {
+    PLOG(ERROR) << "failed to fseek()";
+    return false;
+  }
+  RecordCache cache(file_attrs_[0].attr);
+  for (size_t nbytes_read = 0; nbytes_read < header_.data.size;) {
+    std::unique_ptr<Record> record = ReadRecordFromFile(file_attrs_[0].attr, record_fp_);
+    if (record == nullptr) {
+      return false;
+    }
+    nbytes_read += record->size();
+    if (sorted) {
+      cache.Push(std::move(record));
+      record = cache.Pop();
+      if (record != nullptr) {
+        if (!callback(std::move(record))) {
+          return false;
+        }
+      }
+    } else {
+      if (!callback(std::move(record))) {
+        return false;
+      }
+    }
+  }
+  std::vector<std::unique_ptr<Record>> records = cache.PopAll();
+  for (auto& record : records) {
+    if (!callback(std::move(record))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RecordFileReader::ReadFeatureSection(int feature, std::vector<char>* data) {
+  const std::map<int, SectionDesc>& section_map = FeatureSectionDescriptors();
+  auto it = section_map.find(feature);
+  if (it == section_map.end()) {
+    return false;
+  }
+  SectionDesc section = it->second;
+  data->resize(section.size);
+  if (fseek(record_fp_, section.offset, SEEK_SET) != 0) {
+    PLOG(ERROR) << "failed to fseek()";
+    return false;
+  }
+  if (fread(data->data(), data->size(), 1, record_fp_) != 1) {
+    PLOG(ERROR) << "failed to read " << filename_;
+    return false;
+  }
+  return true;
+}
+
+std::vector<std::string> RecordFileReader::ReadCmdlineFeature() {
+  std::vector<char> buf;
+  if (!ReadFeatureSection(FEAT_CMDLINE, &buf)) {
+    return std::vector<std::string>();
+  }
+  const char* p = buf.data();
+  const char* end = buf.data() + buf.size();
+  std::vector<std::string> cmdline;
+  uint32_t arg_count;
+  MoveFromBinaryFormat(arg_count, p);
+  CHECK_LE(p, end);
+  for (size_t i = 0; i < arg_count; ++i) {
+    uint32_t len;
+    MoveFromBinaryFormat(len, p);
+    CHECK_LE(p + len, end);
+    cmdline.push_back(p);
+    p += len;
+  }
+  return cmdline;
+}
+
+std::vector<BuildIdRecord> RecordFileReader::ReadBuildIdFeature() {
+  std::vector<char> buf;
+  if (!ReadFeatureSection(FEAT_BUILD_ID, &buf)) {
+    return std::vector<BuildIdRecord>();
+  }
+  const char* p = buf.data();
+  const char* end = buf.data() + buf.size();
+  std::vector<BuildIdRecord> result;
+  while (p < end) {
+    const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p);
+    CHECK_LE(p + header->size, end);
+    BuildIdRecord record(header);
+    // Set type explicitly as the perf.data produced by perf doesn't set it.
+    record.header.type = PERF_RECORD_BUILD_ID;
+    result.push_back(record);
+    p += header->size;
+  }
+  return result;
+}
+
+std::string RecordFileReader::ReadFeatureString(int feature) {
+  std::vector<char> buf;
+  if (!ReadFeatureSection(feature, &buf)) {
+    return std::string();
+  }
+  const char* p = buf.data();
+  const char* end = buf.data() + buf.size();
+  uint32_t len;
+  MoveFromBinaryFormat(len, p);
+  CHECK_LE(p + len, end);
+  return p;
+}
+
+std::vector<std::unique_ptr<Record>> RecordFileReader::DataSection() {
+  std::vector<std::unique_ptr<Record>> records;
+  ReadDataSection([&](std::unique_ptr<Record> record) {
+    records.push_back(std::move(record));
+    return true;
+  });
+  return records;
+}
index fffaa2a..4648a64 100644 (file)
 #include <gtest/gtest.h>
 
 #include <string.h>
+
+#include <memory>
+
+#include <android-base/test_utils.h>
+
 #include "environment.h"
 #include "event_attr.h"
-#include "event_fd.h"
 #include "event_type.h"
 #include "record.h"
 #include "record_file.h"
@@ -30,68 +34,128 @@ using namespace PerfFileFormat;
 
 class RecordFileTest : public ::testing::Test {
  protected:
-  virtual void SetUp() {
-    filename = "temporary.record_file";
-    const EventType* event_type = EventTypeFactory::FindEventTypeByName("cpu-cycles");
-    ASSERT_TRUE(event_type != nullptr);
-    event_attr = CreateDefaultPerfEventAttr(*event_type);
-    std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFileForProcess(event_attr, getpid());
-    ASSERT_TRUE(event_fd != nullptr);
-    event_fds.push_back(std::move(event_fd));
+  void AddEventType(const std::string& event_type_str) {
+    std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
+    ASSERT_TRUE(event_type_modifier != nullptr);
+    perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+    attrs_.push_back(std::unique_ptr<perf_event_attr>(new perf_event_attr(attr)));
+    AttrWithId attr_id;
+    attr_id.attr = attrs_.back().get();
+    attr_id.ids.push_back(attrs_.size());  // Fake id.
+    attr_ids_.push_back(attr_id);
   }
 
-  std::string filename;
-  perf_event_attr event_attr;
-  std::vector<std::unique_ptr<EventFd>> event_fds;
+  TemporaryFile tmpfile_;
+  std::vector<std::unique_ptr<perf_event_attr>> attrs_;
+  std::vector<AttrWithId> attr_ids_;
 };
 
 TEST_F(RecordFileTest, smoke) {
   // Write to a record file.
-  std::unique_ptr<RecordFileWriter> writer =
-      RecordFileWriter::CreateInstance(filename, event_attr, event_fds);
+  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(writer != nullptr);
 
+  // Write attr section.
+  AddEventType("cpu-cycles");
+  ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
+
   // Write data section.
-  MmapRecord mmap_record =
-      CreateMmapRecord(event_attr, true, 1, 1, 0x1000, 0x2000, 0x3000, "mmap_record_example");
+  MmapRecord mmap_record = CreateMmapRecord(*(attr_ids_[0].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;
+  char p[BuildId::Size()];
+  for (size_t i = 0; i < BuildId::Size(); ++i) {
+    p[i] = i;
   }
+  BuildId build_id(p);
   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);
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   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());
+  const std::vector<FileAttr>& file_attrs = reader->AttrSection();
+  ASSERT_EQ(1u, file_attrs.size());
+  ASSERT_EQ(0, memcmp(&file_attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+  std::vector<uint64_t> ids;
+  ASSERT_TRUE(reader->ReadIdsForAttr(file_attrs[0], &ids));
+  ASSERT_EQ(ids, attr_ids_[0].ids);
 
   // Read and check data section.
-  std::vector<std::unique_ptr<const Record>> records = reader->DataSection();
+  std::vector<std::unique_ptr<Record>> records = reader->DataSection();
   ASSERT_EQ(1u, records.size());
-  ASSERT_EQ(mmap_record.header.type, records[0]->header.type);
   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));
+  std::vector<BuildIdRecord> build_id_records = reader->ReadBuildIdFeature();
+  ASSERT_EQ(1u, build_id_records.size());
+  CheckRecordEqual(build_id_record, build_id_records[0]);
+
+  ASSERT_TRUE(reader->Close());
+}
+
+TEST_F(RecordFileTest, records_sorted_by_time) {
+  // Write to a record file.
+  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
+  ASSERT_TRUE(writer != nullptr);
+
+  // Write attr section.
+  AddEventType("cpu-cycles");
+  attrs_[0]->sample_id_all = 1;
+  attrs_[0]->sample_type |= PERF_SAMPLE_TIME;
+  ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
+
+  // Write data section.
+  MmapRecord r1 =
+      CreateMmapRecord(*(attr_ids_[0].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(tmpfile_.path);
+  ASSERT_TRUE(reader != nullptr);
+  std::vector<std::unique_ptr<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());
 }
+
+TEST_F(RecordFileTest, record_more_than_one_attr) {
+  // Write to a record file.
+  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
+  ASSERT_TRUE(writer != nullptr);
+
+  // Write attr section.
+  AddEventType("cpu-cycles");
+  AddEventType("cpu-clock");
+  AddEventType("task-clock");
+  ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
+
+  ASSERT_TRUE(writer->Close());
+
+  // Read from a record file.
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
+  ASSERT_TRUE(reader != nullptr);
+  const std::vector<FileAttr>& file_attrs = reader->AttrSection();
+  ASSERT_EQ(3u, file_attrs.size());
+  for (size_t i = 0; i < file_attrs.size(); ++i) {
+    ASSERT_EQ(0, memcmp(&file_attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+    std::vector<uint64_t> ids;
+    ASSERT_TRUE(reader->ReadIdsForAttr(file_attrs[i], &ids));
+    ASSERT_EQ(ids, attr_ids_[i].ids);
+  }
+}
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
new file mode 100644 (file)
index 0000000..dddd0b0
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+#include "perf_event.h"
+#include "record.h"
+#include "utils.h"
+
+using namespace PerfFileFormat;
+
+std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(const std::string& filename) {
+  // Remove old perf.data to avoid file ownership problems.
+  std::string err;
+  if (!android::base::RemoveFileIfExists(filename, &err)) {
+    LOG(ERROR) << "failed to remove file " << filename << ": " << err;
+    return nullptr;
+  }
+  FILE* fp = fopen(filename.c_str(), "web+");
+  if (fp == nullptr) {
+    PLOG(ERROR) << "failed to open record file '" << filename << "'";
+    return nullptr;
+  }
+
+  return std::unique_ptr<RecordFileWriter>(new RecordFileWriter(filename, fp));
+}
+
+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 std::vector<AttrWithId>& attr_ids) {
+  if (attr_ids.empty()) {
+    return false;
+  }
+
+  // Skip file header part.
+  if (fseek(record_fp_, sizeof(FileHeader), SEEK_SET) == -1) {
+    return false;
+  }
+
+  // Write id section.
+  long id_section_offset = ftell(record_fp_);
+  if (id_section_offset == -1) {
+    return false;
+  }
+  for (auto& attr_id : attr_ids) {
+    if (!Write(attr_id.ids.data(), attr_id.ids.size() * sizeof(uint64_t))) {
+      return false;
+    }
+  }
+
+  // Write attr section.
+  long attr_section_offset = ftell(record_fp_);
+  if (attr_section_offset == -1) {
+    return false;
+  }
+  for (auto& attr_id : attr_ids) {
+    FileAttr file_attr;
+    file_attr.attr = *attr_id.attr;
+    file_attr.ids.offset = id_section_offset;
+    file_attr.ids.size = attr_id.ids.size() * sizeof(uint64_t);
+    id_section_offset += file_attr.ids.size;
+    if (!Write(&file_attr, sizeof(file_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_ = data_section_offset - attr_section_offset;
+  data_section_offset_ = data_section_offset;
+
+  // Save event_attr for use when reading records.
+  event_attr_ = *attr_ids[0].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;
+}
+
+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) {
+  uint64_t start_offset;
+  if (!WriteFeatureBegin(&start_offset)) {
+    return false;
+  }
+  for (auto& record : build_id_records) {
+    std::vector<char> data = record.BinaryFormat();
+    if (!Write(data.data(), data.size())) {
+      return false;
+    }
+  }
+  return WriteFeatureEnd(FEAT_BUILD_ID, start_offset);
+}
+
+bool RecordFileWriter::WriteFeatureString(int feature, const std::string& s) {
+  uint64_t start_offset;
+  if (!WriteFeatureBegin(&start_offset)) {
+    return false;
+  }
+  uint32_t len = static_cast<uint32_t>(ALIGN(s.size() + 1, 64));
+  if (!Write(&len, sizeof(len))) {
+    return false;
+  }
+  std::vector<char> v(len, '\0');
+  std::copy(s.begin(), s.end(), v.begin());
+  if (!Write(v.data(), v.size())) {
+    return false;
+  }
+  return WriteFeatureEnd(feature, start_offset);
+}
+
+bool RecordFileWriter::WriteCmdlineFeature(const std::vector<std::string>& cmdline) {
+  uint64_t start_offset;
+  if (!WriteFeatureBegin(&start_offset)) {
+    return false;
+  }
+  uint32_t arg_count = cmdline.size();
+  if (!Write(&arg_count, sizeof(arg_count))) {
+    return false;
+  }
+  for (auto& arg : cmdline) {
+    uint32_t len = static_cast<uint32_t>(ALIGN(arg.size() + 1, 64));
+    if (!Write(&len, sizeof(len))) {
+      return false;
+    }
+    std::vector<char> array(len, '\0');
+    std::copy(arg.begin(), arg.end(), array.begin());
+    if (!Write(array.data(), array.size())) {
+      return false;
+    }
+  }
+  return WriteFeatureEnd(FEAT_CMDLINE, start_offset);
+}
+
+bool RecordFileWriter::WriteBranchStackFeature() {
+  uint64_t start_offset;
+  if (!WriteFeatureBegin(&start_offset)) {
+    return false;
+  }
+  return WriteFeatureEnd(FEAT_BRANCH_STACK, start_offset);
+}
+
+bool RecordFileWriter::WriteFeatureBegin(uint64_t* start_offset) {
+  CHECK_LT(current_feature_index_, feature_count_);
+  if (!SeekFileEnd(start_offset)) {
+    return false;
+  }
+  return true;
+}
+
+bool RecordFileWriter::WriteFeatureEnd(int feature, uint64_t start_offset) {
+  uint64_t end_offset;
+  if (!SeekFileEnd(&end_offset)) {
+    return false;
+  }
+  SectionDesc desc;
+  desc.offset = start_offset;
+  desc.size = end_offset - start_offset;
+  uint64_t feature_offset = data_section_offset_ + data_section_size_;
+  if (fseek(record_fp_, feature_offset + current_feature_index_ * sizeof(SectionDesc), SEEK_SET) ==
+      -1) {
+    PLOG(ERROR) << "fseek() failed";
+    return false;
+  }
+  if (!Write(&desc, sizeof(SectionDesc))) {
+    return false;
+  }
+  ++current_feature_index_;
+  features_.push_back(feature);
+  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;
+}
index d9e9a4b..76eebe9 100644 (file)
@@ -24,9 +24,9 @@
 class RecordTest : public ::testing::Test {
  protected:
   virtual void SetUp() {
-    const EventType* event_type = EventTypeFactory::FindEventTypeByName("cpu-cycles");
-    ASSERT_TRUE(event_type != nullptr);
-    event_attr = CreateDefaultPerfEventAttr(*event_type);
+    const EventType* type = FindEventTypeByName("cpu-cycles");
+    ASSERT_TRUE(type != nullptr);
+    event_attr = CreateDefaultPerfEventAttr(*type);
   }
 
   template <class RecordType>
@@ -38,10 +38,10 @@ class RecordTest : public ::testing::Test {
 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);
+  std::vector<std::unique_ptr<Record>> records =
+      ReadRecordsFromBuffer(event_attr, binary.data(), binary.size());
+  ASSERT_EQ(1u, records.size());
+  CheckRecordEqual(record, *records[0]);
 }
 
 TEST_F(RecordTest, MmapRecordMatchBinary) {
@@ -54,3 +54,67 @@ TEST_F(RecordTest, CommRecordMatchBinary) {
   CommRecord record = CreateCommRecord(event_attr, 1, 2, "CommRecord");
   CheckRecordMatchBinary(record);
 }
+
+TEST_F(RecordTest, RecordCache_smoke) {
+  event_attr.sample_id_all = 1;
+  event_attr.sample_type |= PERF_SAMPLE_TIME;
+  RecordCache cache(event_attr, 2, 2);
+  MmapRecord r1 = CreateMmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300, "mmap_record1");
+  MmapRecord r2 = r1;
+  MmapRecord r3 = r1;
+  MmapRecord r4 = r1;
+  r1.sample_id.time_data.time = 3;
+  r2.sample_id.time_data.time = 1;
+  r3.sample_id.time_data.time = 4;
+  r4.sample_id.time_data.time = 6;
+  std::vector<char> buf1 = r1.BinaryFormat();
+  std::vector<char> buf2 = r2.BinaryFormat();
+  std::vector<char> buf3 = r3.BinaryFormat();
+  std::vector<char> buf4 = r4.BinaryFormat();
+  // Push r1.
+  cache.Push(buf1.data(), buf1.size());
+  ASSERT_EQ(nullptr, cache.Pop());
+  // Push r2.
+  cache.Push(buf2.data(), buf2.size());
+  // Pop r2.
+  std::unique_ptr<Record> popped_r = cache.Pop();
+  ASSERT_TRUE(popped_r != nullptr);
+  CheckRecordEqual(r2, *popped_r);
+  ASSERT_EQ(nullptr, cache.Pop());
+  // Push r3.
+  cache.Push(buf3.data(), buf3.size());
+  ASSERT_EQ(nullptr, cache.Pop());
+  // Push r4.
+  cache.Push(buf4.data(), buf4.size());
+  // Pop r1.
+  popped_r = cache.Pop();
+  ASSERT_TRUE(popped_r != nullptr);
+  CheckRecordEqual(r1, *popped_r);
+  // Pop r3.
+  popped_r = cache.Pop();
+  ASSERT_TRUE(popped_r != nullptr);
+  CheckRecordEqual(r3, *popped_r);
+  ASSERT_EQ(nullptr, cache.Pop());
+  // Pop r4.
+  std::vector<std::unique_ptr<Record>> last_records = cache.PopAll();
+  ASSERT_EQ(1u, last_records.size());
+  CheckRecordEqual(r4, *last_records[0]);
+}
+
+TEST_F(RecordTest, RecordCache_FIFO) {
+  event_attr.sample_id_all = 1;
+  event_attr.sample_type |= PERF_SAMPLE_TIME;
+  RecordCache cache(event_attr, 2, 2);
+  std::vector<MmapRecord> records;
+  for (size_t i = 0; i < 10; ++i) {
+    MmapRecord r = CreateMmapRecord(event_attr, true, 1, i, 0x100, 0x200, 0x300, "mmap_record1");
+    records.push_back(r);
+    std::vector<char> buf = r.BinaryFormat();
+    cache.Push(buf.data(), buf.size());
+  }
+  std::vector<std::unique_ptr<Record>> out_records = cache.PopAll();
+  ASSERT_EQ(records.size(), out_records.size());
+  for (size_t i = 0; i < records.size(); ++i) {
+    CheckRecordEqual(records[i], *out_records[i]);
+  }
+}
diff --git a/simpleperf/report.py b/simpleperf/report.py
new file mode 100644 (file)
index 0000000..225998a
--- /dev/null
@@ -0,0 +1,260 @@
+#!/usr/bin/env 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.
+#
+
+"""Simpleperf gui reporter: provide gui interface for simpleperf report command.
+
+There are two ways to use gui reporter. One way is to pass it a report file
+generated by simpleperf report command, and reporter will display it. The
+other ways is to pass it any arguments you want to use when calling
+simpleperf report command. The reporter will call `simpleperf report` to
+generate report file, and display it.
+"""
+
+import os.path
+import re
+import subprocess
+import sys
+from tkFont import *
+from Tkinter import *
+from ttk import *
+
+PAD_X = 3
+PAD_Y = 3
+
+
+class CallTreeNode(object):
+
+  """Representing a node in call-graph."""
+
+  def __init__(self, percentage, function_name):
+    self.percentage = percentage
+    self.call_stack = [function_name]
+    self.children = []
+
+  def add_call(self, function_name):
+    self.call_stack.append(function_name)
+
+  def add_child(self, node):
+    self.children.append(node)
+
+  def __str__(self):
+    strs = self.dump()
+    return '\n'.join(strs)
+
+  def dump(self):
+    strs = []
+    strs.append('CallTreeNode percentage = %.2f' % self.percentage)
+    for function_name in self.call_stack:
+      strs.append(' %s' % function_name)
+    for child in self.children:
+      child_strs = child.dump()
+      strs.extend(['  ' + x for x in child_strs])
+    return strs
+
+
+class ReportItem(object):
+
+  """Representing one item in report, may contain a CallTree."""
+
+  def __init__(self, raw_line):
+    self.raw_line = raw_line
+    self.call_tree = None
+
+  def __str__(self):
+    strs = []
+    strs.append('ReportItem (raw_line %s)' % self.raw_line)
+    if self.call_tree is not None:
+      strs.append('%s' % self.call_tree)
+    return '\n'.join(strs)
+
+
+def parse_report_items(lines):
+  report_items = []
+  cur_report_item = None
+  call_tree_stack = {}
+  vertical_columns = []
+  last_node = None
+
+  for line in lines:
+    if not line:
+      continue
+    if not line[0].isspace():
+      cur_report_item = ReportItem(line)
+      report_items.append(cur_report_item)
+      # Each report item can have different column depths.
+      vertical_columns = []
+    else:
+      for i in range(len(line)):
+        if line[i] == '|':
+          if not vertical_columns or vertical_columns[-1] < i:
+            vertical_columns.append(i)
+
+      if not line.strip('| \t'):
+        continue
+      if line.find('-') == -1:
+        line = line.strip('| \t')
+        function_name = line
+        last_node.add_call(function_name)
+      else:
+        pos = line.find('-')
+        depth = -1
+        for i in range(len(vertical_columns)):
+          if pos >= vertical_columns[i]:
+            depth = i
+        assert depth != -1
+
+        line = line.strip('|- \t')
+        m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line)
+        if m:
+          percentage = float(m.group(1))
+          function_name = m.group(2)
+        else:
+          percentage = 100.0
+          function_name = line
+
+        node = CallTreeNode(percentage, function_name)
+        if depth == 0:
+          cur_report_item.call_tree = node
+        else:
+          call_tree_stack[depth - 1].add_child(node)
+        call_tree_stack[depth] = node
+        last_node = node
+
+  return report_items
+
+
+class ReportWindow(object):
+
+  """A window used to display report file."""
+
+  def __init__(self, master, report_context, title_line, report_items):
+    frame = Frame(master)
+    frame.pack(fill=BOTH, expand=1)
+
+    font = Font(family='courier', size=10)
+
+    # Report Context
+    for line in report_context:
+      label = Label(frame, text=line, font=font)
+      label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+    # Space
+    label = Label(frame, text='', font=font)
+    label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+    # Title
+    label = Label(frame, text='  ' + title_line, font=font)
+    label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+    # Report Items
+    report_frame = Frame(frame)
+    report_frame.pack(fill=BOTH, expand=1)
+
+    yscrollbar = Scrollbar(report_frame)
+    yscrollbar.pack(side=RIGHT, fill=Y)
+    xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL)
+    xscrollbar.pack(side=BOTTOM, fill=X)
+
+    tree = Treeview(report_frame, columns=[title_line], show='')
+    tree.pack(side=LEFT, fill=BOTH, expand=1)
+    tree.tag_configure('set_font', font=font)
+
+    tree.config(yscrollcommand=yscrollbar.set)
+    yscrollbar.config(command=tree.yview)
+    tree.config(xscrollcommand=xscrollbar.set)
+    xscrollbar.config(command=tree.xview)
+
+    self.display_report_items(tree, report_items)
+
+  def display_report_items(self, tree, report_items):
+    for report_item in report_items:
+      prefix_str = '+ ' if report_item.call_tree is not None else '  '
+      id = tree.insert(
+          '',
+          'end',
+          None,
+          values=[
+              prefix_str +
+              report_item.raw_line],
+          tag='set_font')
+      if report_item.call_tree is not None:
+        self.display_call_tree(tree, id, report_item.call_tree, 1)
+
+  def display_call_tree(self, tree, parent_id, node, indent):
+    id = parent_id
+    indent_str = '  ' * indent
+
+    if node.percentage != 100.0:
+      percentage_str = '%.2f%%' % node.percentage
+    else:
+      percentage_str = ''
+    first_open = True if node.percentage == 100.0 else False
+
+    for i in range(len(node.call_stack)):
+      s = indent_str
+      s += '+ ' if node.children else '  '
+      s += percentage_str if i == 0 else ' ' * len(percentage_str)
+      s += node.call_stack[i]
+      child_open = first_open if i == 0 else True
+      id = tree.insert(id, 'end', None, values=[s], open=child_open,
+                       tag='set_font')
+
+    for child in node.children:
+      self.display_call_tree(tree, id, child, indent + 1)
+
+
+def display_report_file(report_file):
+  fh = open(report_file, 'r')
+  lines = fh.readlines()
+  fh.close()
+
+  lines = [x.rstrip() for x in lines]
+
+  blank_line_index = -1
+  for i in range(len(lines)):
+    if not lines[i]:
+      blank_line_index = i
+      break
+  assert blank_line_index != -1
+  assert blank_line_index + 1 < len(lines)
+
+  report_context = lines[:blank_line_index]
+  title_line = lines[blank_line_index + 1]
+  report_items = parse_report_items(lines[blank_line_index + 2:])
+
+  root = Tk()
+  ReportWindow(root, report_context, title_line, report_items)
+  root.mainloop()
+
+
+def call_simpleperf_report(args, report_file):
+  output_fh = open(report_file, 'w')
+  args = ['simpleperf', 'report'] + args
+  subprocess.check_call(args, stdout=output_fh)
+  output_fh.close()
+
+
+def main():
+  if len(sys.argv) == 2 and os.path.isfile(sys.argv[1]):
+    display_report_file(sys.argv[1])
+  else:
+    call_simpleperf_report(sys.argv[1:], 'perf.report')
+    display_report_file('perf.report')
+
+
+if __name__ == '__main__':
+  main()
diff --git a/simpleperf/runtest/Android.build.mk b/simpleperf/runtest/Android.build.mk
new file mode 100644 (file)
index 0000000..8520765
--- /dev/null
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+# -O0 is need to prevent optimizations like function inlining in runtest executables.
+simpleperf_runtest_cppflags := -Wall -Wextra -Werror -Wunused \
+                               -O0 \
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_runtest_cppflags)
+LOCAL_SRC_FILES := $(module_src_files)
+LOCAL_MODULE := $(module)
+LOCAL_STRIP_MODULE := false
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.build.mk
+include $(BUILD_EXECUTABLE)
+
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_runtest_cppflags)
+LOCAL_SRC_FILES := $(module_src_files)
+LOCAL_MODULE := $(module)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.build.mk
+include $(BUILD_HOST_EXECUTABLE)
+endif
\ No newline at end of file
diff --git a/simpleperf/runtest/Android.mk b/simpleperf/runtest/Android.mk
new file mode 100644 (file)
index 0000000..55bf3b7
--- /dev/null
@@ -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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+module := simpleperf_runtest_one_function
+module_src_files := one_function.cpp
+include $(LOCAL_PATH)/Android.build.mk
+
+module := simpleperf_runtest_two_functions
+module_src_files := two_functions.cpp
+include $(LOCAL_PATH)/Android.build.mk
+
+module := simpleperf_runtest_function_fork
+module_src_files := function_fork.cpp
+include $(LOCAL_PATH)/Android.build.mk
+
+module := simpleperf_runtest_function_pthread
+module_src_files := function_pthread.cpp
+include $(LOCAL_PATH)/Android.build.mk
+
+module := simpleperf_runtest_comm_change
+module_src_files := comm_change.cpp
+include $(LOCAL_PATH)/Android.build.mk
+
+module := simpleperf_runtest_function_recursive
+module_src_files := function_recursive.cpp
+include $(LOCAL_PATH)/Android.build.mk
+
+module := simpleperf_runtest_function_indirect_recursive
+module_src_files := function_indirect_recursive.cpp
+include $(LOCAL_PATH)/Android.build.mk
\ No newline at end of file
diff --git a/simpleperf/runtest/comm_change.cpp b/simpleperf/runtest/comm_change.cpp
new file mode 100644 (file)
index 0000000..f8b2ae6
--- /dev/null
@@ -0,0 +1,16 @@
+#include <sys/prctl.h>
+
+constexpr int LOOP_COUNT = 100000000;
+
+void Function1() {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+int main() {
+  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM1"), 0, 0, 0);
+  Function1();
+  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM2"), 0, 0, 0);
+  Function1();
+  return 0;
+}
diff --git a/simpleperf/runtest/function_fork.cpp b/simpleperf/runtest/function_fork.cpp
new file mode 100644 (file)
index 0000000..b813702
--- /dev/null
@@ -0,0 +1,24 @@
+#include <unistd.h>
+
+constexpr int LOOP_COUNT = 100000000;
+
+void ParentFunction() {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+void ChildFunction() {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+int main() {
+  pid_t pid = fork();
+  if (pid == 0) {
+    ChildFunction();
+    return 0;
+  } else {
+    ParentFunction();
+  }
+  return 0;
+}
diff --git a/simpleperf/runtest/function_indirect_recursive.cpp b/simpleperf/runtest/function_indirect_recursive.cpp
new file mode 100644 (file)
index 0000000..5e70fd3
--- /dev/null
@@ -0,0 +1,24 @@
+constexpr int LOOP_COUNT = 5000000;
+
+void FunctionRecursiveTwo(int loop);
+
+void FunctionRecursiveOne(int loop) {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+  if (loop >= 0) {
+    FunctionRecursiveTwo(loop);
+  }
+}
+
+void FunctionRecursiveTwo(int loop) {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+  if (loop > 0) {
+    FunctionRecursiveOne(loop - 1);
+  }
+}
+
+int main() {
+  FunctionRecursiveOne(10);
+  return 0;
+}
diff --git a/simpleperf/runtest/function_pthread.cpp b/simpleperf/runtest/function_pthread.cpp
new file mode 100644 (file)
index 0000000..02fc0a5
--- /dev/null
@@ -0,0 +1,33 @@
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+constexpr int LOOP_COUNT = 100000000;
+
+void* ChildThreadFunction(void*) {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+  return nullptr;
+}
+
+void MainThreadFunction() {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+int main() {
+  pthread_t thread;
+  int ret = pthread_create(&thread, nullptr, ChildThreadFunction, nullptr);
+  if (ret != 0) {
+    fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
+    exit(1);
+  }
+  MainThreadFunction();
+  ret = pthread_join(thread, nullptr);
+  if (ret != 0) {
+    fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
+    exit(1);
+  }
+  return 0;
+}
diff --git a/simpleperf/runtest/function_recursive.cpp b/simpleperf/runtest/function_recursive.cpp
new file mode 100644 (file)
index 0000000..d8d28bc
--- /dev/null
@@ -0,0 +1,16 @@
+constexpr int LOOP_COUNT = 5000000;
+
+void FunctionRecursive(int loop) {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+  if (loop > 0) {
+    FunctionRecursive(loop - 1);
+  }
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+int main() {
+  FunctionRecursive(10);
+  return 0;
+}
diff --git a/simpleperf/runtest/one_function.cpp b/simpleperf/runtest/one_function.cpp
new file mode 100644 (file)
index 0000000..49090ac
--- /dev/null
@@ -0,0 +1,11 @@
+constexpr int LOOP_COUNT = 100000000;
+
+void Function1() {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+int main() {
+  Function1();
+  return 0;
+}
diff --git a/simpleperf/runtest/runtest.conf b/simpleperf/runtest/runtest.conf
new file mode 100644 (file)
index 0000000..863ea99
--- /dev/null
@@ -0,0 +1,205 @@
+<runtests>
+<test name="one_function">
+  <executable name="simpleperf_runtest_one_function"/>
+
+  <symbol_overhead>
+    <symbol name="Function1()" min="90" max="100"/>
+  </symbol_overhead>
+
+  <symbol_children_overhead>
+    <symbol name="main" min="90" max="100"/>
+  </symbol_children_overhead>
+
+  <symbol_callgraph_relation>
+    <symbol name="Function1()">
+      <symbol name="main"/>
+    </symbol>
+  </symbol_callgraph_relation>
+</test>
+
+<test name="two_functions">
+  <executable name="simpleperf_runtest_two_functions"/>
+
+  <symbol_overhead>
+    <symbol name="Function1()" min="30" max="70"/>
+    <symbol name="Function2()" min="30" max="70"/>
+  </symbol_overhead>
+
+  <symbol_children_overhead>
+    <symbol name="main" min="90" max="100"/>
+  </symbol_children_overhead>
+
+  <symbol_callgraph_relation>
+    <symbol name="Function1()">
+      <symbol name="main"/>
+    </symbol>
+    <symbol name="Function2()">
+      <symbol name="main"/>
+    </symbol>
+  </symbol_callgraph_relation>
+</test>
+
+<test name="function_fork">
+  <executable name="simpleperf_runtest_function_fork"/>
+
+  <symbol_overhead>
+    <symbol name="ParentFunction()" min="10" max="90"/>
+    <symbol name="ChildFunction()" min="10" max="90"/>
+  </symbol_overhead>
+
+  <symbol_children_overhead>
+    <symbol name="main" min="10" max="90"/>
+  </symbol_children_overhead>
+
+  <symbol_callgraph_relation>
+    <symbol name="ParentFunction()">
+      <symbol name="main"/>
+    </symbol>
+    <symbol name="ChildFunction()">
+      <symbol name="main"/>
+    </symbol>
+  </symbol_callgraph_relation>
+</test>
+
+<test name="function_pthread">
+  <executable name="simpleperf_runtest_function_pthread"/>
+
+  <symbol_overhead>
+    <symbol name="MainThreadFunction()" min="20" max="80"/>
+    <symbol name="ChildThreadFunction(void*)" min="20" max="80"/>
+  </symbol_overhead>
+
+  <symbol_children_overhead>
+    <symbol name="main" min="20" max="80"/>
+  </symbol_children_overhead>
+
+  <symbol_callgraph_relation>
+    <symbol name="MainThreadFunction()">
+      <symbol name="main"/>
+    </symbol>
+  </symbol_callgraph_relation>
+</test>
+
+<test name="comm_change">
+  <executable name="simpleperf_runtest_comm_change"/>
+
+  <symbol_overhead>
+    <symbol name="Function1()" comm="RUN_COMM1" min="30" max="70"/>
+    <symbol name="Function1()" comm="RUN_COMM2" min="30" max="70"/>
+  </symbol_overhead>
+
+  <symbol_children_overhead>
+    <symbol name="main" comm="RUN_COMM1" min="30" max="70"/>
+    <symbol name="main" comm="RUN_COMM2" min="30" max="70"/>
+  </symbol_children_overhead>
+
+  <symbol_callgraph_relation>
+    <symbol name="Function1()" comm="RUN_COMM1">
+      <symbol name="main"/>
+    </symbol>
+    <symbol name="Function1()" comm="RUN_COMM2">
+      <symbol name="main"/>
+    </symbol>
+  </symbol_callgraph_relation>
+
+</test>
+
+<test name="function_recursive">
+  <executable name="simpleperf_runtest_function_recursive"/>
+
+  <symbol_overhead>
+    <symbol name="FunctionRecursive(int)" min="90"/>
+  </symbol_overhead>
+
+  <symbol_children_overhead>
+    <symbol name="main" min="90"/>
+  </symbol_children_overhead>
+
+  <symbol_callgraph_relation>
+    <symbol name="FunctionRecursive(int)">
+      <symbol name="FunctionRecursive(int)">
+        <symbol name="FunctionRecursive(int)">
+          <symbol name="FunctionRecursive(int)">
+            <symbol name="FunctionRecursive(int)">
+              <symbol name="FunctionRecursive(int)">
+                <symbol name="FunctionRecursive(int)">
+                  <symbol name="FunctionRecursive(int)">
+                    <symbol name="FunctionRecursive(int)">
+                      <symbol name="FunctionRecursive(int)">
+                        <symbol name="FunctionRecursive(int)">
+                          <symbol name="main"/>
+                        </symbol>
+                        <symbol name="main"/>
+                      </symbol>
+                      <symbol name="main"/>
+                    </symbol>
+                    <symbol name="main"/>
+                  </symbol>
+                  <symbol name="main"/>
+                </symbol>
+                <symbol name="main"/>
+              </symbol>
+              <symbol name="main"/>
+            </symbol>
+            <symbol name="main"/>
+          </symbol>
+          <symbol name="main"/>
+        </symbol>
+        <symbol name="main"/>
+      </symbol>
+      <symbol name="main"/>
+    </symbol>
+  </symbol_callgraph_relation>
+</test>
+
+<test name="function_indirect_recursive">
+  <executable name="simpleperf_runtest_function_indirect_recursive"/>
+
+  <symbol_overhead>
+    <symbol name="FunctionRecursiveOne(int)" min="30" max="70"/>
+    <symbol name="FunctionRecursiveTwo(int)" min="30" max="70"/>
+  </symbol_overhead>
+
+  <symbol_children_overhead>
+    <symbol name="FunctionRecursiveOne(int)" min="90"/>
+    <symbol name="FunctionRecursiveTwo(int)" min="80"/>
+  </symbol_children_overhead>
+
+  <symbol_callgraph_relation>
+    <symbol name="FunctionRecursiveOne(int)">
+      <symbol name="FunctionRecursiveTwo(int)">
+        <symbol name="FunctionRecursiveOne(int)">
+          <symbol name="FunctionRecursiveTwo(int)">
+            <symbol name="FunctionRecursiveOne(int)"/>
+          </symbol>
+          <symbol name="main"/>
+        </symbol>
+      </symbol>
+      <symbol name="main"/>
+    </symbol>
+
+    <symbol name="FunctionRecursiveTwo(int)">
+      <symbol name="FunctionRecursiveOne(int)">
+        <symbol name="FunctionRecursiveTwo(int)">
+          <symbol name="FunctionRecursiveOne(int)">
+            <symbol name="FunctionRecursiveTwo(int)">
+            </symbol>
+            <symbol name="main"/>
+          </symbol>
+        </symbol>
+        <symbol name="main"/>
+      </symbol>
+    </symbol>
+  </symbol_callgraph_relation>
+</test>
+
+<test name="selected_comm">
+  <executable name="simpleperf_runtest_comm_change"/>
+  <report option="--comms RUN_COMM1"/>
+
+  <symbol_overhead>
+    <symbol comm="RUN_COMM1" min="100" max="100"/>
+  </symbol_overhead>
+</test>
+
+</runtests>
diff --git a/simpleperf/runtest/runtest.py b/simpleperf/runtest/runtest.py
new file mode 100644 (file)
index 0000000..e4cc0d2
--- /dev/null
@@ -0,0 +1,590 @@
+#!/usr/bin/env 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.
+#
+"""Simpleperf runtest runner: run simpleperf runtests on host or on device.
+
+For a simpleperf runtest like one_function test, it contains following steps:
+1. Run simpleperf record command to record simpleperf_runtest_one_function's
+   running samples, which is generated in perf.data.
+2. Run simpleperf report command to parse perf.data, generate perf.report.
+4. Parse perf.report and see if it matches expectation.
+
+The information of all runtests is stored in runtest.conf.
+"""
+
+import re
+import subprocess
+import sys
+import xml.etree.ElementTree as ET
+
+
+class CallTreeNode(object):
+
+  def __init__(self, name):
+    self.name = name
+    self.children = []
+
+  def add_child(self, child):
+    self.children.append(child)
+
+  def __str__(self):
+    return 'CallTreeNode:\n' + '\n'.join(self._dump(1))
+
+  def _dump(self, indent):
+    indent_str = '  ' * indent
+    strs = [indent_str + self.name]
+    for child in self.children:
+      strs.extend(child._dump(indent + 1))
+    return strs
+
+
+class Symbol(object):
+
+  def __init__(self, name, comm, overhead, children_overhead):
+    self.name = name
+    self.comm = comm
+    self.overhead = overhead
+    # children_overhead is the overhead sum of this symbol and functions
+    # called by this symbol.
+    self.children_overhead = children_overhead
+    self.call_tree = None
+
+  def set_call_tree(self, call_tree):
+    self.call_tree = call_tree
+
+  def __str__(self):
+    strs = []
+    strs.append('Symbol name=%s comm=%s overhead=%f children_overhead=%f' % (
+        self.name, self.comm, self.overhead, self.children_overhead))
+    if self.call_tree:
+      strs.append('\t%s' % self.call_tree)
+    return '\n'.join(strs)
+
+
+class SymbolOverheadRequirement(object):
+
+  def __init__(self, symbol_name=None, comm=None, min_overhead=None,
+               max_overhead=None):
+    self.symbol_name = symbol_name
+    self.comm = comm
+    self.min_overhead = min_overhead
+    self.max_overhead = max_overhead
+
+  def __str__(self):
+    strs = []
+    strs.append('SymbolOverheadRequirement')
+    if self.symbol_name is not None:
+      strs.append('symbol_name=%s' % self.symbol_name)
+    if self.comm is not None:
+      strs.append('comm=%s' % self.comm)
+    if self.min_overhead is not None:
+      strs.append('min_overhead=%f' % self.min_overhead)
+    if self.max_overhead is not None:
+      strs.append('max_overhead=%f' % self.max_overhead)
+    return ' '.join(strs)
+
+  def is_match(self, symbol):
+    if self.symbol_name is not None:
+      if self.symbol_name != symbol.name:
+        return False
+    if self.comm is not None:
+      if self.comm != symbol.comm:
+        return False
+    return True
+
+  def check_overhead(self, overhead):
+    if self.min_overhead is not None:
+      if self.min_overhead > overhead:
+        return False
+    if self.max_overhead is not None:
+      if self.max_overhead < overhead:
+        return False
+    return True
+
+
+class SymbolRelationRequirement(object):
+
+  def __init__(self, symbol_name, comm=None):
+    self.symbol_name = symbol_name
+    self.comm = comm
+    self.children = []
+
+  def add_child(self, child):
+    self.children.append(child)
+
+  def __str__(self):
+    return 'SymbolRelationRequirement:\n' + '\n'.join(self._dump(1))
+
+  def _dump(self, indent):
+    indent_str = '  ' * indent
+    strs = [indent_str + self.symbol_name +
+            (' ' + self.comm if self.comm else '')]
+    for child in self.children:
+      strs.extend(child._dump(indent + 1))
+    return strs
+
+  def is_match(self, symbol):
+    if symbol.name != self.symbol_name:
+      return False
+    if self.comm is not None:
+      if symbol.comm != self.comm:
+        return False
+    return True
+
+  def check_relation(self, call_tree):
+    if not call_tree:
+      return False
+    if self.symbol_name != call_tree.name:
+      return False
+    for child in self.children:
+      child_matched = False
+      for node in call_tree.children:
+        if child.check_relation(node):
+          child_matched = True
+          break
+      if not child_matched:
+        return False
+    return True
+
+
+class Test(object):
+
+  def __init__(
+          self,
+          test_name,
+          executable_name,
+          report_options,
+          symbol_overhead_requirements,
+          symbol_children_overhead_requirements,
+          symbol_relation_requirements):
+    self.test_name = test_name
+    self.executable_name = executable_name
+    self.report_options = report_options
+    self.symbol_overhead_requirements = symbol_overhead_requirements
+    self.symbol_children_overhead_requirements = (
+        symbol_children_overhead_requirements)
+    self.symbol_relation_requirements = symbol_relation_requirements
+
+  def __str__(self):
+    strs = []
+    strs.append('Test test_name=%s' % self.test_name)
+    strs.append('\texecutable_name=%s' % self.executable_name)
+    strs.append('\treport_options=%s' % (' '.join(self.report_options)))
+    strs.append('\tsymbol_overhead_requirements:')
+    for req in self.symbol_overhead_requirements:
+      strs.append('\t\t%s' % req)
+    strs.append('\tsymbol_children_overhead_requirements:')
+    for req in self.symbol_children_overhead_requirements:
+      strs.append('\t\t%s' % req)
+    strs.append('\tsymbol_relation_requirements:')
+    for req in self.symbol_relation_requirements:
+      strs.append('\t\t%s' % req)
+    return '\n'.join(strs)
+
+
+def load_config_file(config_file):
+  tests = []
+  tree = ET.parse(config_file)
+  root = tree.getroot()
+  assert root.tag == 'runtests'
+  for test in root:
+    assert test.tag == 'test'
+    test_name = test.attrib['name']
+    executable_name = None
+    report_options = []
+    symbol_overhead_requirements = []
+    symbol_children_overhead_requirements = []
+    symbol_relation_requirements = []
+    for test_item in test:
+      if test_item.tag == 'executable':
+        executable_name = test_item.attrib['name']
+      elif test_item.tag == 'report':
+        report_options = test_item.attrib['option'].split()
+      elif (test_item.tag == 'symbol_overhead' or
+              test_item.tag == 'symbol_children_overhead'):
+        for symbol_item in test_item:
+          assert symbol_item.tag == 'symbol'
+          symbol_name = None
+          if 'name' in symbol_item.attrib:
+            symbol_name = symbol_item.attrib['name']
+          comm = None
+          if 'comm' in symbol_item.attrib:
+            comm = symbol_item.attrib['comm']
+          overhead_min = None
+          if 'min' in symbol_item.attrib:
+            overhead_min = float(symbol_item.attrib['min'])
+          overhead_max = None
+          if 'max' in symbol_item.attrib:
+            overhead_max = float(symbol_item.attrib['max'])
+
+          if test_item.tag == 'symbol_overhead':
+            symbol_overhead_requirements.append(
+                SymbolOverheadRequirement(
+                    symbol_name,
+                    comm,
+                    overhead_min,
+                    overhead_max)
+            )
+          else:
+            symbol_children_overhead_requirements.append(
+                SymbolOverheadRequirement(
+                    symbol_name,
+                    comm,
+                    overhead_min,
+                    overhead_max))
+      elif test_item.tag == 'symbol_callgraph_relation':
+        for symbol_item in test_item:
+          req = load_symbol_relation_requirement(symbol_item)
+          symbol_relation_requirements.append(req)
+
+    tests.append(
+        Test(
+            test_name,
+            executable_name,
+            report_options,
+            symbol_overhead_requirements,
+            symbol_children_overhead_requirements,
+            symbol_relation_requirements))
+  return tests
+
+
+def load_symbol_relation_requirement(symbol_item):
+  symbol_name = symbol_item.attrib['name']
+  comm = None
+  if 'comm' in symbol_item.attrib:
+    comm = symbol_item.attrib['comm']
+  req = SymbolRelationRequirement(symbol_name, comm)
+  for item in symbol_item:
+    child_req = load_symbol_relation_requirement(item)
+    req.add_child(child_req)
+  return req
+
+
+class Runner(object):
+
+  def __init__(self, perf_path):
+    self.perf_path = perf_path
+
+  def record(self, test_executable_name, record_file, additional_options=[]):
+    call_args = [self.perf_path,
+                 'record'] + additional_options + ['-e',
+                                                   'cpu-cycles:u',
+                                                   '-o',
+                                                   record_file,
+                                                   test_executable_name]
+    self._call(call_args)
+
+  def report(self, record_file, report_file, additional_options=[]):
+    call_args = [self.perf_path,
+                 'report'] + additional_options + ['-i',
+                                                   record_file]
+    self._call(call_args, report_file)
+
+  def _call(self, args, output_file=None):
+    pass
+
+
+class HostRunner(Runner):
+
+  """Run perf test on host."""
+
+  def _call(self, args, output_file=None):
+    output_fh = None
+    if output_file is not None:
+      output_fh = open(output_file, 'w')
+    subprocess.check_call(args, stdout=output_fh)
+    if output_fh is not None:
+      output_fh.close()
+
+
+class DeviceRunner(Runner):
+
+  """Run perf test on device."""
+
+  def _call(self, args, output_file=None):
+    output_fh = None
+    if output_file is not None:
+      output_fh = open(output_file, 'w')
+    args_with_adb = ['adb', 'shell']
+    args_with_adb.extend(args)
+    subprocess.check_call(args_with_adb, stdout=output_fh)
+    if output_fh is not None:
+      output_fh.close()
+
+
+class ReportAnalyzer(object):
+
+  """Check if perf.report matches expectation in Configuration."""
+
+  def _read_report_file(self, report_file, has_callgraph):
+    fh = open(report_file, 'r')
+    lines = fh.readlines()
+    fh.close()
+
+    lines = [x.rstrip() for x in lines]
+    blank_line_index = -1
+    for i in range(len(lines)):
+      if not lines[i]:
+        blank_line_index = i
+    assert blank_line_index != -1
+    assert blank_line_index + 1 < len(lines)
+    title_line = lines[blank_line_index + 1]
+    report_item_lines = lines[blank_line_index + 2:]
+
+    if has_callgraph:
+      assert re.search(r'^Children\s+Self\s+Command.+Symbol$', title_line)
+    else:
+      assert re.search(r'^Overhead\s+Command.+Symbol$', title_line)
+
+    return self._parse_report_items(report_item_lines, has_callgraph)
+
+  def _parse_report_items(self, lines, has_callgraph):
+    symbols = []
+    cur_symbol = None
+    call_tree_stack = {}
+    vertical_columns = []
+    last_node = None
+    last_depth = -1
+
+    for line in lines:
+      if not line:
+        continue
+      if not line[0].isspace():
+        if has_callgraph:
+          m = re.search(r'^([\d\.]+)%\s+([\d\.]+)%\s+(\S+).*\s+(\S+)$', line)
+          children_overhead = float(m.group(1))
+          overhead = float(m.group(2))
+          comm = m.group(3)
+          symbol_name = m.group(4)
+          cur_symbol = Symbol(symbol_name, comm, overhead, children_overhead)
+          symbols.append(cur_symbol)
+        else:
+          m = re.search(r'^([\d\.]+)%\s+(\S+).*\s+(\S+)$', line)
+          overhead = float(m.group(1))
+          comm = m.group(2)
+          symbol_name = m.group(3)
+          cur_symbol = Symbol(symbol_name, comm, overhead, 0)
+          symbols.append(cur_symbol)
+        # Each report item can have different column depths.
+        vertical_columns = []
+      else:
+        for i in range(len(line)):
+          if line[i] == '|':
+            if not vertical_columns or vertical_columns[-1] < i:
+              vertical_columns.append(i)
+
+        if not line.strip('| \t'):
+          continue
+        if line.find('-') == -1:
+          function_name = line.strip('| \t')
+          node = CallTreeNode(function_name)
+          last_node.add_child(node)
+          last_node = node
+          call_tree_stack[last_depth] = node
+        else:
+          pos = line.find('-')
+          depth = -1
+          for i in range(len(vertical_columns)):
+            if pos >= vertical_columns[i]:
+              depth = i
+          assert depth != -1
+
+          line = line.strip('|- \t')
+          m = re.search(r'^[\d\.]+%[-\s]+(.+)$', line)
+          if m:
+            function_name = m.group(1)
+          else:
+            function_name = line
+
+          node = CallTreeNode(function_name)
+          if depth == 0:
+            cur_symbol.set_call_tree(node)
+
+          else:
+            call_tree_stack[depth - 1].add_child(node)
+          call_tree_stack[depth] = node
+          last_node = node
+          last_depth = depth
+
+    return symbols
+
+  def check_report_file(self, test, report_file, has_callgraph):
+    symbols = self._read_report_file(report_file, has_callgraph)
+    if not self._check_symbol_overhead_requirements(test, symbols):
+      return False
+    if has_callgraph:
+      if not self._check_symbol_children_overhead_requirements(test, symbols):
+        return False
+      if not self._check_symbol_relation_requirements(test, symbols):
+        return False
+    return True
+
+  def _check_symbol_overhead_requirements(self, test, symbols):
+    result = True
+    matched = [False] * len(test.symbol_overhead_requirements)
+    matched_overhead = [0] * len(test.symbol_overhead_requirements)
+    for symbol in symbols:
+      for i in range(len(test.symbol_overhead_requirements)):
+        req = test.symbol_overhead_requirements[i]
+        if req.is_match(symbol):
+          matched[i] = True
+          matched_overhead[i] += symbol.overhead
+    for i in range(len(matched)):
+      if not matched[i]:
+        print 'requirement (%s) has no matched symbol in test %s' % (
+            test.symbol_overhead_requirements[i], test)
+        result = False
+      else:
+        fulfilled = req.check_overhead(matched_overhead[i])
+        if not fulfilled:
+          print "Symbol (%s) doesn't match requirement (%s) in test %s" % (
+              symbol, req, test)
+          result = False
+    return result
+
+  def _check_symbol_children_overhead_requirements(self, test, symbols):
+    result = True
+    matched = [False] * len(test.symbol_children_overhead_requirements)
+    for symbol in symbols:
+      for i in range(len(test.symbol_children_overhead_requirements)):
+        req = test.symbol_children_overhead_requirements[i]
+        if req.is_match(symbol):
+          matched[i] = True
+          fulfilled = req.check_overhead(symbol.children_overhead)
+          if not fulfilled:
+            print "Symbol (%s) doesn't match requirement (%s) in test %s" % (
+                symbol, req, test)
+            result = False
+    for i in range(len(matched)):
+      if not matched[i]:
+        print 'requirement (%s) has no matched symbol in test %s' % (
+            test.symbol_children_overhead_requirements[i], test)
+        result = False
+    return result
+
+  def _check_symbol_relation_requirements(self, test, symbols):
+    result = True
+    matched = [False] * len(test.symbol_relation_requirements)
+    for symbol in symbols:
+      for i in range(len(test.symbol_relation_requirements)):
+        req = test.symbol_relation_requirements[i]
+        if req.is_match(symbol):
+          matched[i] = True
+          fulfilled = req.check_relation(symbol.call_tree)
+          if not fulfilled:
+            print "Symbol (%s) doesn't match requirement (%s) in test %s" % (
+                symbol, req, test)
+            result = False
+    for i in range(len(matched)):
+      if not matched[i]:
+        print 'requirement (%s) has no matched symbol in test %s' % (
+            test.symbol_relation_requirements[i], test)
+        result = False
+    return result
+
+
+def runtest(host, device, normal, callgraph, selected_tests):
+  tests = load_config_file('runtest.conf')
+  host_runner = HostRunner('simpleperf')
+  device_runner = DeviceRunner('simpleperf')
+  report_analyzer = ReportAnalyzer()
+  for test in tests:
+    if selected_tests is not None:
+      if test.test_name not in selected_tests:
+        continue
+    if host and normal:
+      host_runner.record(test.executable_name, 'perf.data')
+      host_runner.report('perf.data', 'perf.report',
+                         additional_options = test.report_options)
+      result = report_analyzer.check_report_file(
+          test, 'perf.report', False)
+      print 'test %s on host %s' % (
+          test.test_name, 'Succeeded' if result else 'Failed')
+      if not result:
+        exit(1)
+
+    if device and normal:
+      device_runner.record(test.executable_name, '/data/perf.data')
+      device_runner.report('/data/perf.data', 'perf.report',
+                           additional_options = test.report_options)
+      result = report_analyzer.check_report_file(test, 'perf.report', False)
+      print 'test %s on device %s' % (
+          test.test_name, 'Succeeded' if result else 'Failed')
+      if not result:
+        exit(1)
+
+    if host and callgraph:
+      host_runner.record(
+          test.executable_name,
+          'perf_g.data',
+          additional_options=['-g'])
+      host_runner.report(
+          'perf_g.data',
+          'perf_g.report',
+          additional_options=['-g'] + test.report_options)
+      result = report_analyzer.check_report_file(test, 'perf_g.report', True)
+      print 'call-graph test %s on host %s' % (
+          test.test_name, 'Succeeded' if result else 'Failed')
+      if not result:
+        exit(1)
+
+    if device and callgraph:
+      device_runner.record(
+          test.executable_name,
+          '/data/perf_g.data',
+          additional_options=['-g'])
+      device_runner.report(
+          '/data/perf_g.data',
+          'perf_g.report',
+          additional_options=['-g'] + test.report_options)
+      result = report_analyzer.check_report_file(test, 'perf_g.report', True)
+      print 'call-graph test %s on device %s' % (
+          test.test_name, 'Succeeded' if result else 'Failed')
+      if not result:
+        exit(1)
+
+def main():
+  host = True
+  device = True
+  normal = True
+  callgraph = True
+  selected_tests = None
+  i = 1
+  while i < len(sys.argv):
+    if sys.argv[i] == '--host':
+      host = True
+      device = False
+    elif sys.argv[i] == '--device':
+      host = False
+      device = True
+    elif sys.argv[i] == '--normal':
+      normal = True
+      callgraph = False
+    elif sys.argv[i] == '--callgraph':
+      normal = False
+      callgraph = True
+    elif sys.argv[i] == '--test':
+      if i < len(sys.argv):
+        i += 1
+        for test in sys.argv[i].split(','):
+          if selected_tests is None:
+            selected_tests = {}
+          selected_tests[test] = True
+    i += 1
+  runtest(host, device, normal, callgraph, selected_tests)
+
+if __name__ == '__main__':
+  main()
diff --git a/simpleperf/runtest/two_functions.cpp b/simpleperf/runtest/two_functions.cpp
new file mode 100644 (file)
index 0000000..1511102
--- /dev/null
@@ -0,0 +1,17 @@
+constexpr int LOOP_COUNT = 100000000;
+
+void Function1() {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+void Function2() {
+  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  }
+}
+
+int main() {
+  Function1();
+  Function2();
+  return 0;
+}
diff --git a/simpleperf/sample_tree.cpp b/simpleperf/sample_tree.cpp
new file mode 100644 (file)
index 0000000..a34107b
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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 <android-base/logging.h>
+
+#include "environment.h"
+
+void SampleTree::SetFilters(const std::unordered_set<int>& pid_filter,
+                            const std::unordered_set<int>& tid_filter,
+                            const std::unordered_set<std::string>& comm_filter,
+                            const std::unordered_set<std::string>& dso_filter) {
+  pid_filter_ = pid_filter;
+  tid_filter_ = tid_filter;
+  comm_filter_ = comm_filter;
+  dso_filter_ = dso_filter;
+}
+
+SampleEntry* SampleTree::AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
+                                   bool in_kernel) {
+  const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
+  const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
+  const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
+
+  SampleEntry value(ip, time, period, 0, 1, thread, map, symbol);
+
+  if (IsFilteredOut(value)) {
+    return nullptr;
+  }
+  return InsertSample(value);
+}
+
+void SampleTree::AddBranchSample(int pid, int tid, uint64_t from_ip, uint64_t to_ip,
+                                 uint64_t branch_flags, uint64_t time, uint64_t period) {
+  const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
+  const MapEntry* from_map = thread_tree_->FindMap(thread, from_ip, false);
+  if (from_map == thread_tree_->UnknownMap()) {
+    from_map = thread_tree_->FindMap(thread, from_ip, true);
+  }
+  const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, from_ip);
+  const MapEntry* to_map = thread_tree_->FindMap(thread, to_ip, false);
+  if (to_map == thread_tree_->UnknownMap()) {
+    to_map = thread_tree_->FindMap(thread, to_ip, true);
+  }
+  const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, to_ip);
+
+  SampleEntry value(to_ip, time, period, 0, 1, thread, to_map, to_symbol);
+  value.branch_from.ip = from_ip;
+  value.branch_from.map = from_map;
+  value.branch_from.symbol = from_symbol;
+  value.branch_from.flags = branch_flags;
+
+  if (IsFilteredOut(value)) {
+    return;
+  }
+  InsertSample(value);
+}
+
+SampleEntry* SampleTree::AddCallChainSample(int pid, int tid, uint64_t ip, uint64_t time,
+                                            uint64_t period, bool in_kernel,
+                                            const std::vector<SampleEntry*>& callchain) {
+  const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
+  const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
+  const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
+
+  SampleEntry value(ip, time, 0, period, 0, thread, map, symbol);
+
+  if (IsFilteredOut(value)) {
+    // Store in callchain_sample_tree_ for use in other SampleEntry's callchain.
+    auto it = callchain_sample_tree_.find(&value);
+    if (it != callchain_sample_tree_.end()) {
+      return *it;
+    }
+    SampleEntry* sample = AllocateSample(value);
+    callchain_sample_tree_.insert(sample);
+    return sample;
+  }
+
+  auto it = sample_tree_.find(&value);
+  if (it != sample_tree_.end()) {
+    SampleEntry* sample = *it;
+    // Process only once for recursive function call.
+    if (std::find(callchain.begin(), callchain.end(), sample) != callchain.end()) {
+      return sample;
+    }
+  }
+  return InsertSample(value);
+}
+
+bool SampleTree::IsFilteredOut(const SampleEntry& value) {
+  if (!pid_filter_.empty() && pid_filter_.find(value.thread->pid) == pid_filter_.end()) {
+    return true;
+  }
+  if (!tid_filter_.empty() && tid_filter_.find(value.thread->tid) == tid_filter_.end()) {
+    return true;
+  }
+  if (!comm_filter_.empty() && comm_filter_.find(value.thread_comm) == comm_filter_.end()) {
+    return true;
+  }
+  if (!dso_filter_.empty() && dso_filter_.find(value.map->dso->Path()) == dso_filter_.end()) {
+    return true;
+  }
+  return false;
+}
+
+SampleEntry* SampleTree::InsertSample(SampleEntry& value) {
+  SampleEntry* result;
+  auto it = sample_tree_.find(&value);
+  if (it == sample_tree_.end()) {
+    result = AllocateSample(value);
+    auto pair = sample_tree_.insert(result);
+    CHECK(pair.second);
+  } else {
+    result = *it;
+    result->period += value.period;
+    result->accumulated_period += value.accumulated_period;
+    result->sample_count += value.sample_count;
+  }
+  total_samples_ += value.sample_count;
+  total_period_ += value.period;
+  return result;
+}
+
+SampleEntry* SampleTree::AllocateSample(SampleEntry& value) {
+  SampleEntry* sample = new SampleEntry(std::move(value));
+  sample_storage_.push_back(std::unique_ptr<SampleEntry>(sample));
+  return sample;
+}
+
+void SampleTree::InsertCallChainForSample(SampleEntry* sample,
+                                          const std::vector<SampleEntry*>& callchain,
+                                          uint64_t period) {
+  sample->callchain.AddCallChain(callchain, period);
+}
+
+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_) {
+      sample->callchain.SortByPeriod();
+      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 (file)
index 0000000..6eb7372
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * 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 <unordered_set>
+#include <vector>
+
+#include "callchain.h"
+#include "thread_tree.h"
+
+struct BranchFromEntry {
+  uint64_t ip;
+  const MapEntry* map;
+  const Symbol* symbol;
+  uint64_t flags;
+
+  BranchFromEntry() : ip(0), map(nullptr), symbol(nullptr), flags(0) {
+  }
+};
+
+struct SampleEntry {
+  uint64_t ip;
+  uint64_t time;
+  uint64_t period;
+  uint64_t accumulated_period;  // Accumulated when appearing in other samples' callchain.
+  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 Symbol* symbol;
+  BranchFromEntry branch_from;
+  CallChainRoot callchain;  // A callchain tree representing all callchains in the sample records.
+
+  SampleEntry(uint64_t ip, uint64_t time, uint64_t period, uint64_t accumulated_period,
+              uint64_t sample_count, const ThreadEntry* thread, const MapEntry* map,
+              const Symbol* symbol)
+      : ip(ip),
+        time(time),
+        period(period),
+        accumulated_period(accumulated_period),
+        sample_count(sample_count),
+        thread(thread),
+        thread_comm(thread->comm),
+        map(map),
+        symbol(symbol) {
+  }
+
+  // The data member 'callchain' can only move, not copy.
+  SampleEntry(SampleEntry&&) = default;
+  SampleEntry(SampleEntry&) = delete;
+};
+
+typedef std::function<int(const SampleEntry&, const SampleEntry&)> compare_sample_func_t;
+
+class SampleTree {
+ public:
+  SampleTree(ThreadTree* thread_tree, compare_sample_func_t sample_compare_function)
+      : thread_tree_(thread_tree),
+        sample_comparator_(sample_compare_function),
+        sample_tree_(sample_comparator_),
+        callchain_sample_tree_(sample_comparator_),
+        sorted_sample_comparator_(sample_compare_function),
+        sorted_sample_tree_(sorted_sample_comparator_),
+        total_samples_(0),
+        total_period_(0) {
+  }
+
+  void SetFilters(const std::unordered_set<int>& pid_filter,
+                  const std::unordered_set<int>& tid_filter,
+                  const std::unordered_set<std::string>& comm_filter,
+                  const std::unordered_set<std::string>& dso_filter);
+
+  SampleEntry* AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
+                         bool in_kernel);
+  void AddBranchSample(int pid, int tid, uint64_t from_ip, uint64_t to_ip, uint64_t branch_flags,
+                       uint64_t time, uint64_t period);
+  SampleEntry* AddCallChainSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
+                                  bool in_kernel, const std::vector<SampleEntry*>& callchain);
+  void InsertCallChainForSample(SampleEntry* sample, const std::vector<SampleEntry*>& callchain,
+                                uint64_t period);
+  void VisitAllSamples(std::function<void(const SampleEntry&)> callback);
+
+  uint64_t TotalSamples() const {
+    return total_samples_;
+  }
+
+  uint64_t TotalPeriod() const {
+    return total_period_;
+  }
+
+ private:
+  bool IsFilteredOut(const SampleEntry& value);
+  SampleEntry* InsertSample(SampleEntry& value);
+  SampleEntry* AllocateSample(SampleEntry& value);
+
+  struct SampleComparator {
+    bool operator()(SampleEntry* sample1, 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()(SampleEntry* sample1, SampleEntry* sample2) const {
+      uint64_t period1 = sample1->period + sample1->accumulated_period;
+      uint64_t period2 = sample2->period + sample2->accumulated_period;
+      if (period1 != period2) {
+        return period1 > period2;
+      }
+      return compare_function(*sample1, *sample2) < 0;
+    }
+    SortedSampleComparator(compare_sample_func_t compare_function)
+        : compare_function(compare_function) {
+    }
+
+    compare_sample_func_t compare_function;
+  };
+
+  ThreadTree* thread_tree_;
+  SampleComparator sample_comparator_;
+  std::set<SampleEntry*, SampleComparator> sample_tree_;
+  // If a CallChainSample is filtered out, it is stored in callchain_sample_tree_ and only used
+  // in other SampleEntry's callchain.
+  std::set<SampleEntry*, SampleComparator> callchain_sample_tree_;
+
+  SortedSampleComparator sorted_sample_comparator_;
+  std::set<SampleEntry*, SortedSampleComparator> sorted_sample_tree_;
+  std::vector<std::unique_ptr<SampleEntry>> sample_storage_;
+
+  std::unordered_set<int> pid_filter_;
+  std::unordered_set<int> tid_filter_;
+  std::unordered_set<std::string> comm_filter_;
+  std::unordered_set<std::string> dso_filter_;
+
+  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 (file)
index 0000000..434ee71
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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() {
+    thread_tree.AddThread(1, 1, "p1t1");
+    thread_tree.AddThread(1, 11, "p1t11");
+    thread_tree.AddThread(2, 2, "p2t2");
+    thread_tree.AddThreadMap(1, 1, 1, 5, 0, 0, "process1_thread1");
+    thread_tree.AddThreadMap(1, 1, 6, 5, 0, 0, "process1_thread1_map2");
+    thread_tree.AddThreadMap(1, 11, 1, 10, 0, 0, "process1_thread11");
+    thread_tree.AddThreadMap(2, 2, 1, 20, 0, 0, "process2_thread2");
+    thread_tree.AddKernelMap(10, 20, 0, 0, "kernel");
+    sample_tree = std::unique_ptr<SampleTree>(new SampleTree(&thread_tree, CompareSampleFunction));
+  }
+
+  void VisitSampleTree(const std::vector<ExpectedSampleInMap>& expected_samples) {
+    ::VisitSampleTree(sample_tree.get(), expected_samples);
+  }
+
+  ThreadTree thread_tree;
+  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);
+  thread_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) {
+  ThreadTree thread_tree;
+  SampleTree sample_tree(&thread_tree, CompareSampleFunction);
+  thread_tree.AddThread(1, 1, "thread1");
+  thread_tree.AddThreadMap(1, 1, 1, 10, 0, 0, "map1");  // Add map 1.
+  sample_tree.AddSample(1, 1, 5, 0, 0, false);          // Hit map 1.
+  thread_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 map 1.
+  thread_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 map 2.
+
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, "thread1", "map1", 1, 2},
+      {1, 1, "thread1", "map2", 5, 1},
+      {1, 1, "thread1", "map2", 9, 1},
+      {1, 1, "thread1", "map3", 2, 1},
+  };
+  VisitSampleTree(&sample_tree, expected_samples);
+}
diff --git a/simpleperf/scoped_signal_handler.h b/simpleperf/scoped_signal_handler.h
new file mode 100644 (file)
index 0000000..15beee7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_SCOPED_SIGNAL_HANDLER_H_
+#define SIMPLE_PERF_SCOPED_SIGNAL_HANDLER_H_
+
+#include <signal.h>
+
+#include <vector>
+
+class ScopedSignalHandler {
+ public:
+  ScopedSignalHandler(const std::vector<int>& signums, void (*handler)(int)) {
+    for (auto& sig : signums) {
+      sig_t old_handler = signal(sig, handler);
+      saved_signal_handlers_.push_back(std::make_pair(sig, old_handler));
+    }
+  }
+
+  ~ScopedSignalHandler() {
+    for (auto& pair : saved_signal_handlers_) {
+      signal(pair.first, pair.second);
+    }
+  }
+
+ private:
+  std::vector<std::pair<int, sig_t>> saved_signal_handlers_;
+};
+
+#endif  // SIMPLE_PERF_SCOPED_SIGNAL_HANDLER_H_
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
new file mode 100644 (file)
index 0000000..cfbe493
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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 <map>
+#include <string>
+
+#include "read_elf.h"
+#include "workload.h"
+
+static const std::string SLEEP_SEC = "0.001";
+
+void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads);
+
+void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols);
+void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols);
diff --git a/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk b/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk
new file mode 100644 (file)
index 0000000..95ea93a
Binary files /dev/null and b/simpleperf/testdata/data/app/com.example.hellojni-1/base.apk differ
diff --git a/simpleperf/testdata/elf b/simpleperf/testdata/elf
new file mode 100644 (file)
index 0000000..f63c25c
Binary files /dev/null and b/simpleperf/testdata/elf differ
diff --git a/simpleperf/testdata/elf_file_source.cpp b/simpleperf/testdata/elf_file_source.cpp
new file mode 100644 (file)
index 0000000..3cfd00b
--- /dev/null
@@ -0,0 +1,20 @@
+#include <pthread.h>
+
+volatile int GlobalVar;
+
+extern "C" void CalledFunc() {
+  GlobalVar++;
+}
+
+extern "C" void GlobalFunc() {
+  for (int i = 0; i < 1000000; ++i) {
+    CalledFunc();
+  }
+}
+
+int main() {
+  while (true) {
+    GlobalFunc();
+  }
+  return 0;
+}
diff --git a/simpleperf/testdata/has_embedded_native_libs_apk_perf.data b/simpleperf/testdata/has_embedded_native_libs_apk_perf.data
new file mode 100644 (file)
index 0000000..fafbbbc
Binary files /dev/null and b/simpleperf/testdata/has_embedded_native_libs_apk_perf.data differ
diff --git a/simpleperf/testdata/perf.data b/simpleperf/testdata/perf.data
new file mode 100644 (file)
index 0000000..64a59da
Binary files /dev/null and b/simpleperf/testdata/perf.data differ
diff --git a/simpleperf/testdata/perf_b.data b/simpleperf/testdata/perf_b.data
new file mode 100644 (file)
index 0000000..e514944
Binary files /dev/null and b/simpleperf/testdata/perf_b.data differ
diff --git a/simpleperf/testdata/perf_g_fp.data b/simpleperf/testdata/perf_g_fp.data
new file mode 100644 (file)
index 0000000..de9cf53
Binary files /dev/null and b/simpleperf/testdata/perf_g_fp.data differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
new file mode 100644 (file)
index 0000000..daf3ff5
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * 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 "thread_tree.h"
+
+#include <limits>
+
+#include <android-base/logging.h>
+
+#include "environment.h"
+#include "perf_event.h"
+#include "record.h"
+
+namespace simpleperf {
+
+bool MapComparator::operator()(const MapEntry* map1, const MapEntry* map2) const {
+  if (map1->start_addr != map2->start_addr) {
+    return map1->start_addr < map2->start_addr;
+  }
+  // Compare map->len instead of map->get_end_addr() here. Because we set map's len
+  // to std::numeric_limits<uint64_t>::max() in FindMapByAddr(), which makes
+  // map->get_end_addr() overflow.
+  if (map1->len != map2->len) {
+    return map1->len < map2->len;
+  }
+  if (map1->time != map2->time) {
+    return map1->time < map2->time;
+  }
+  return false;
+}
+
+void ThreadTree::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, tid,
+        "unknown",                             // comm
+        std::set<MapEntry*, MapComparator>(),  // maps
+    };
+    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 ThreadTree::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;
+}
+
+ThreadEntry* ThreadTree::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 {
+    if (pid != it->second.get()->pid) {
+      // TODO: b/22185053.
+      LOG(DEBUG) << "unexpected (pid, tid) pair: expected (" << it->second.get()->pid << ", " << tid
+                 << "), actual (" << pid << ", " << tid << ")";
+    }
+  }
+  return it->second.get();
+}
+
+void ThreadTree::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;
+  }
+  Dso* dso = FindKernelDsoOrNew(filename);
+  MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso));
+  FixOverlappedMap(&kernel_map_tree_, map);
+  auto pair = kernel_map_tree_.insert(map);
+  CHECK(pair.second);
+}
+
+Dso* ThreadTree::FindKernelDsoOrNew(const std::string& filename) {
+  if (filename == DEFAULT_KERNEL_MMAP_NAME) {
+    if (kernel_dso_ == nullptr) {
+      kernel_dso_ = Dso::CreateDso(DSO_KERNEL);
+    }
+    return kernel_dso_.get();
+  }
+  auto it = module_dso_tree_.find(filename);
+  if (it == module_dso_tree_.end()) {
+    module_dso_tree_[filename] = Dso::CreateDso(DSO_KERNEL_MODULE, filename);
+    it = module_dso_tree_.find(filename);
+  }
+  return it->second.get();
+}
+
+void ThreadTree::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);
+  Dso* dso = FindUserDsoOrNew(filename);
+  MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso));
+  FixOverlappedMap(&thread->maps, map);
+  auto pair = thread->maps.insert(map);
+  CHECK(pair.second);
+}
+
+Dso* ThreadTree::FindUserDsoOrNew(const std::string& filename) {
+  auto it = user_dso_tree_.find(filename);
+  if (it == user_dso_tree_.end()) {
+    user_dso_tree_[filename] = Dso::CreateDso(DSO_ELF_FILE, filename);
+    it = user_dso_tree_.find(filename);
+  }
+  return it->second.get();
+}
+
+MapEntry* ThreadTree::AllocateMap(const MapEntry& value) {
+  MapEntry* map = new MapEntry(value);
+  map_storage_.push_back(std::unique_ptr<MapEntry>(map));
+  return map;
+}
+
+void ThreadTree::FixOverlappedMap(std::set<MapEntry*, MapComparator>* map_set, const MapEntry* map) {
+  for (auto it = map_set->begin(); it != map_set->end();) {
+    if ((*it)->start_addr >= map->get_end_addr()) {
+      // No more overlapped maps.
+      break;
+    }
+    if ((*it)->get_end_addr() <= map->start_addr) {
+      ++it;
+    } else {
+      MapEntry* old = *it;
+      if (old->start_addr < map->start_addr) {
+        MapEntry* before = AllocateMap(MapEntry(old->start_addr, map->start_addr - old->start_addr,
+                                                old->pgoff, old->time, old->dso));
+        map_set->insert(before);
+      }
+      if (old->get_end_addr() > map->get_end_addr()) {
+        MapEntry* after = AllocateMap(
+            MapEntry(map->get_end_addr(), old->get_end_addr() - map->get_end_addr(),
+                     map->get_end_addr() - old->start_addr + old->pgoff, old->time, old->dso));
+        map_set->insert(after);
+      }
+
+      it = map_set->erase(it);
+    }
+  }
+}
+
+static bool IsAddrInMap(uint64_t addr, const MapEntry* map) {
+  return (addr >= map->start_addr && addr < map->get_end_addr());
+}
+
+static MapEntry* FindMapByAddr(const std::set<MapEntry*, MapComparator>& maps, uint64_t addr) {
+  // Construct a map_entry which is strictly after the searched map_entry, based on MapComparator.
+  MapEntry find_map(addr, std::numeric_limits<uint64_t>::max(), 0,
+                    std::numeric_limits<uint64_t>::max(), nullptr);
+  auto it = maps.upper_bound(&find_map);
+  if (it != maps.begin() && IsAddrInMap(addr, *--it)) {
+    return *it;
+  }
+  return nullptr;
+}
+
+const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel) {
+  MapEntry* result = nullptr;
+  if (!in_kernel) {
+    result = FindMapByAddr(thread->maps, ip);
+  } else {
+    result = FindMapByAddr(kernel_map_tree_, ip);
+  }
+  return result != nullptr ? result : &unknown_map_;
+}
+
+const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip) {
+  uint64_t vaddr_in_file;
+  if (map->dso == kernel_dso_.get()) {
+    vaddr_in_file = ip;
+  } else {
+    vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
+  }
+  const Symbol* symbol = map->dso->FindSymbol(vaddr_in_file);
+  if (symbol == nullptr) {
+    symbol = &unknown_symbol_;
+  }
+  return symbol;
+}
+
+void ThreadTree::Clear() {
+  thread_tree_.clear();
+  thread_comm_storage_.clear();
+  kernel_map_tree_.clear();
+  map_storage_.clear();
+  kernel_dso_.reset();
+  module_dso_tree_.clear();
+  user_dso_tree_.clear();
+}
+
+}  // namespace simpleperf
+
+void BuildThreadTree(const Record& record, ThreadTree* thread_tree) {
+  if (record.header.type == PERF_RECORD_MMAP) {
+    const MmapRecord& r = *static_cast<const MmapRecord*>(&record);
+    if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) {
+      thread_tree->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time,
+                                r.filename);
+    } else {
+      thread_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);
+    if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) {
+      thread_tree->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time,
+                                r.filename);
+    } else {
+      std::string filename =
+          (r.filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) ? "[unknown]" : r.filename;
+      thread_tree->AddThreadMap(r.data.pid, r.data.tid, r.data.addr, r.data.len, r.data.pgoff,
+                                r.sample_id.time_data.time, filename);
+    }
+  } else if (record.header.type == PERF_RECORD_COMM) {
+    const CommRecord& r = *static_cast<const CommRecord*>(&record);
+    thread_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);
+    thread_tree->ForkThread(r.data.pid, r.data.tid, r.data.ppid, r.data.ptid);
+  }
+}
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
new file mode 100644 (file)
index 0000000..de10138
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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_THREAD_TREE_H_
+#define SIMPLE_PERF_THREAD_TREE_H_
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <set>
+
+#include "dso.h"
+
+namespace simpleperf {
+
+struct MapEntry {
+  uint64_t start_addr;
+  uint64_t len;
+  uint64_t pgoff;
+  uint64_t time;  // Map creation time.
+  Dso* dso;
+
+  MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time, Dso* dso)
+      : start_addr(start_addr), len(len), pgoff(pgoff), time(time), dso(dso) {
+  }
+  MapEntry() {
+  }
+
+  uint64_t get_end_addr() const {
+    return start_addr + len;
+  }
+};
+
+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;
+};
+
+class ThreadTree {
+ public:
+  ThreadTree() : unknown_symbol_("unknown", 0, std::numeric_limits<unsigned long long>::max()) {
+    unknown_dso_ = Dso::CreateDso(DSO_ELF_FILE, "unknown");
+    unknown_map_ =
+        MapEntry(0, std::numeric_limits<unsigned long long>::max(), 0, 0, unknown_dso_.get());
+  }
+
+  void AddThread(int pid, int tid, const std::string& comm);
+  void ForkThread(int pid, int tid, int ppid, int ptid);
+  ThreadEntry* FindThreadOrNew(int pid, int tid);
+  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);
+  const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel);
+  const Symbol* FindSymbol(const MapEntry* map, uint64_t ip);
+  const MapEntry* UnknownMap() const {
+    return &unknown_map_;
+  }
+
+  void Clear();
+
+ private:
+  Dso* FindKernelDsoOrNew(const std::string& filename);
+  Dso* FindUserDsoOrNew(const std::string& filename);
+  MapEntry* AllocateMap(const MapEntry& value);
+  void FixOverlappedMap(std::set<MapEntry*, MapComparator>* map_set, const MapEntry* map);
+
+  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<Dso> kernel_dso_;
+  std::unordered_map<std::string, std::unique_ptr<Dso>> module_dso_tree_;
+  std::unordered_map<std::string, std::unique_ptr<Dso>> user_dso_tree_;
+  std::unique_ptr<Dso> unknown_dso_;
+  Symbol unknown_symbol_;
+};
+
+}  // namespace simpleperf
+
+using MapEntry = simpleperf::MapEntry;
+using ThreadEntry = simpleperf::ThreadEntry;
+using ThreadTree = simpleperf::ThreadTree;
+
+struct Record;
+
+void BuildThreadTree(const Record& record, ThreadTree* thread_tree);
+
+#endif  // SIMPLE_PERF_THREAD_TREE_H_
index 349cf5d..d14d20c 100644 (file)
 
 #include <dirent.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <sys/stat.h>
 #include <unistd.h>
 
-#include <base/logging.h>
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+void OneTimeFreeAllocator::Clear() {
+  for (auto& p : v_) {
+    delete[] p;
+  }
+  v_.clear();
+  cur_ = nullptr;
+  end_ = nullptr;
+}
+
+const char* OneTimeFreeAllocator::AllocateString(const std::string& s) {
+  size_t size = s.size() + 1;
+  if (cur_ + size > end_) {
+    size_t alloc_size = std::max(size, unit_size_);
+    char* p = new char[alloc_size];
+    v_.push_back(p);
+    cur_ = p;
+    end_ = p + alloc_size;
+  }
+  strcpy(cur_, s.c_str());
+  const char* result = cur_;
+  cur_ += size;
+  return result;
+}
+
+
+FileHelper FileHelper::OpenReadOnly(const std::string& filename) {
+    int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY | O_BINARY));
+    return FileHelper(fd);
+}
+
+FileHelper FileHelper::OpenWriteOnly(const std::string& filename) {
+    int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_WRONLY | O_BINARY | O_CREAT, 0644));
+    return FileHelper(fd);
+}
+
+FileHelper::~FileHelper() {
+  if (fd_ != -1) {
+    close(fd_);
+  }
+}
+
+ArchiveHelper::ArchiveHelper(int fd, const std::string& debug_filename) : valid_(false) {
+  int rc = OpenArchiveFd(fd, "", &handle_, false);
+  if (rc == 0) {
+    valid_ = true;
+  } else {
+    LOG(ERROR) << "Failed to open archive " << debug_filename << ": " << ErrorCodeString(rc);
+  }
+}
+
+ArchiveHelper::~ArchiveHelper() {
+  if (valid_) {
+    CloseArchive(handle_);
+  }
+}
 
 void PrintIndented(size_t indent, const char* fmt, ...) {
   va_list ap;
@@ -36,16 +99,6 @@ bool IsPowerOfTwo(uint64_t value) {
   return (value != 0 && ((value & (value - 1)) == 0));
 }
 
-bool 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 "
-               << args[0] << "`";
-    return false;
-  }
-  ++*pi;
-  return true;
-}
-
 void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
                      std::vector<std::string>* subdirs) {
   if (files != nullptr) {
@@ -64,7 +117,7 @@ void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files
     if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
       continue;
     }
-    if (entry->d_type == DT_DIR) {
+    if (IsDir(dirpath + std::string("/") + entry->d_name)) {
       if (subdirs != nullptr) {
         subdirs->push_back(entry->d_name);
       }
@@ -76,3 +129,83 @@ void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files
   }
   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 IsRegularFile(const std::string& filename) {
+  struct stat st;
+  if (stat(filename.c_str(), &st) == 0) {
+    if (S_ISREG(st.st_mode)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+uint64_t GetFileSize(const std::string& filename) {
+  struct stat st;
+  if (stat(filename.c_str(), &st) == 0) {
+    return static_cast<uint64_t>(st.st_size);
+  }
+  return 0;
+}
+
+bool MkdirWithParents(const std::string& path) {
+  size_t prev_end = 0;
+  while (prev_end < path.size()) {
+    size_t next_end = path.find('/', prev_end + 1);
+    if (next_end == std::string::npos) {
+      break;
+    }
+    std::string dir_path = path.substr(0, next_end);
+    if (!IsDir(dir_path)) {
+#if defined(_WIN32)
+      int ret = mkdir(dir_path.c_str());
+#else
+      int ret = mkdir(dir_path.c_str(), 0755);
+#endif
+      if (ret != 0) {
+        PLOG(ERROR) << "failed to create dir " << dir_path;
+        return false;
+      }
+    }
+    prev_end = next_end;
+  }
+  return true;
+}
+
+bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity) {
+  static std::map<std::string, android::base::LogSeverity> log_severity_map = {
+      {"verbose", android::base::VERBOSE},
+      {"debug", android::base::DEBUG},
+      {"warning", android::base::WARNING},
+      {"error", android::base::ERROR},
+      {"fatal", android::base::FATAL},
+  };
+  auto it = log_severity_map.find(name);
+  if (it != log_severity_map.end()) {
+    *severity = it->second;
+    return true;
+  }
+  return false;
+}
+
+bool IsRoot() {
+  static int is_root = -1;
+  if (is_root == -1) {
+#if defined(__linux__)
+    is_root = (getuid() == 0) ? 1 : 0;
+#else
+    is_root = 0;
+#endif
+  }
+  return is_root == 1;
+}
index fba3558..420fcc9 100644 (file)
 #define SIMPLE_PERF_UTILS_H_
 
 #include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
+
 #include <string>
 #include <vector>
 
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <ziparchive/zip_archive.h>
+
 #define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1))
 
-class LineReader {
+#ifdef _WIN32
+#define CLOSE_ON_EXEC_MODE ""
+#else
+#define CLOSE_ON_EXEC_MODE "e"
+#endif
+
+// OneTimeAllocator is used to allocate memory many times and free only once at the end.
+// It reduces the cost to free each allocated memory.
+class OneTimeFreeAllocator {
  public:
-  LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
+  OneTimeFreeAllocator(size_t unit_size = 8192u)
+      : unit_size_(unit_size), cur_(nullptr), end_(nullptr) {
   }
 
-  ~LineReader() {
-    free(buf_);
-    fclose(fp_);
+  ~OneTimeFreeAllocator() {
+    Clear();
   }
 
-  char* ReadLine() {
-    if (getline(&buf_, &bufsize_, fp_) != -1) {
-      return buf_;
-    }
-    return nullptr;
+  void Clear();
+  const char* AllocateString(const std::string& s);
+
+ private:
+  const size_t unit_size_;
+  std::vector<char*> v_;
+  char* cur_;
+  char* end_;
+};
+
+class FileHelper {
+ public:
+  static FileHelper OpenReadOnly(const std::string& filename);
+  static FileHelper OpenWriteOnly(const std::string& filename);
+
+  FileHelper(FileHelper&& other) {
+    fd_ = other.fd_;
+    other.fd_ = -1;
+  }
+
+  ~FileHelper();
+
+  explicit operator bool() const {
+    return fd_ != -1;
   }
 
-  size_t MaxLineSize() {
-    return bufsize_;
+  int fd() const {
+    return fd_;
   }
 
  private:
-  FILE* fp_;
-  char* buf_;
-  size_t bufsize_;
+  FileHelper(int fd) : fd_(fd) {}
+  int fd_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileHelper);
 };
 
+class ArchiveHelper {
+ public:
+  ArchiveHelper(int fd, const std::string& debug_filename);
+  ~ArchiveHelper();
+
+  explicit operator bool() const {
+    return valid_;
+  }
+  ZipArchiveHandle &archive_handle() {
+    return handle_;
+  }
+
+ private:
+  ZipArchiveHandle handle_;
+  bool valid_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArchiveHelper);
+};
+
+template <class T>
+void MoveFromBinaryFormat(T& data, const char*& p) {
+  data = *reinterpret_cast<const T*>(p);
+  p += sizeof(T);
+}
+
 void PrintIndented(size_t indent, const char* fmt, ...);
 
 bool IsPowerOfTwo(uint64_t value);
 
-bool NextArgumentOrError(const std::vector<std::string>& args, size_t* pi);
-
 void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
                      std::vector<std::string>* subdirs);
+bool IsDir(const std::string& dirpath);
+bool IsRegularFile(const std::string& filename);
+uint64_t GetFileSize(const std::string& filename);
+bool MkdirWithParents(const std::string& path);
+
+bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity);
 
+bool IsRoot();
 #endif  // SIMPLE_PERF_UTILS_H_
index 999e315..35617fd 100644 (file)
@@ -21,7 +21,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-#include <base/logging.h>
+#include <android-base/logging.h>
 
 std::unique_ptr<Workload> Workload::CreateWorkload(const std::vector<std::string>& args) {
   std::unique_ptr<Workload> workload(new Workload(args));
@@ -31,6 +31,21 @@ std::unique_ptr<Workload> Workload::CreateWorkload(const std::vector<std::string
   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() {
@@ -63,6 +78,7 @@ bool Workload::CreateNewProcess() {
     close(start_signal_pipe[1]);
     close(exec_child_pipe[0]);
     ChildProcessFn(args_, start_signal_pipe[0], exec_child_pipe[1]);
+    _exit(0);
   }
   // In parent process.
   close(start_signal_pipe[0]);
@@ -93,11 +109,10 @@ static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd,
     TEMP_FAILURE_RETRY(write(exec_child_fd, &exec_child_failed, 1));
     close(exec_child_fd);
     errno = saved_errno;
-    PLOG(ERROR) << "execvp(" << argv[0] << ") failed";
+    PLOG(ERROR) << "child process failed to execvp(" << argv[0] << ")";
   } else {
-    PLOG(DEBUG) << "child process failed to receive start_signal, nread = " << nread;
+    PLOG(ERROR) << "child process failed to receive start_signal, nread = " << nread;
   }
-  exit(1);
 }
 
 bool Workload::Start() {
@@ -111,38 +126,30 @@ bool Workload::Start() {
   char exec_child_failed;
   ssize_t nread = TEMP_FAILURE_RETRY(read(exec_child_fd_, &exec_child_failed, 1));
   if (nread != 0) {
-    LOG(ERROR) << "exec child failed";
+    if (nread == -1) {
+      PLOG(ERROR) << "failed to receive error message from child process";
+    } else {
+      LOG(ERROR) << "received error message from child process";
+    }
     return false;
   }
   work_state_ = Started;
   return true;
 }
 
-bool Workload::IsFinished() {
-  if (work_state_ == Started) {
-    WaitChildProcess(true);
-  }
-  return work_state_ == Finished;
-}
-
-void Workload::WaitFinish() {
-  CHECK(work_state_ == Started || work_state_ == Finished);
-  if (work_state_ == Started) {
-    WaitChildProcess(false);
-  }
-}
-
-void Workload::WaitChildProcess(bool no_hang) {
+bool Workload::WaitChildProcess(bool wait_forever) {
+  bool finished = false;
   int status;
-  pid_t result = TEMP_FAILURE_RETRY(waitpid(work_pid_, &status, (no_hang ? WNOHANG : 0)));
+  pid_t result = TEMP_FAILURE_RETRY(waitpid(work_pid_, &status, (wait_forever ? 0 : WNOHANG)));
   if (result == work_pid_) {
-    work_state_ = Finished;
+    finished = true;
     if (WIFSIGNALED(status)) {
-      LOG(ERROR) << "work process was terminated by signal " << strsignal(WTERMSIG(status));
+      LOG(WARNING) << "child process was terminated by signal " << strsignal(WTERMSIG(status));
     } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
-      LOG(ERROR) << "work process exited with exit code " << WEXITSTATUS(status);
+      LOG(WARNING) << "child process exited with exit code " << WEXITSTATUS(status);
     }
   } else if (result == -1) {
-    PLOG(FATAL) << "waitpid() failed";
+    PLOG(ERROR) << "waitpid() failed";
   }
+  return finished;
 }
index 57622c8..60b9ee8 100644 (file)
@@ -22,7 +22,7 @@
 #include <string>
 #include <vector>
 
-#include <base/macros.h>
+#include <android-base/macros.h>
 
 class Workload {
  private:
@@ -30,24 +30,14 @@ class Workload {
     NotYetCreateNewProcess,
     NotYetStartNewProcess,
     Started,
-    Finished,
   };
 
  public:
   static std::unique_ptr<Workload> CreateWorkload(const std::vector<std::string>& args);
 
-  ~Workload() {
-    if (start_signal_fd_ != -1) {
-      close(start_signal_fd_);
-    }
-    if (exec_child_fd_ != -1) {
-      close(exec_child_fd_);
-    }
-  }
+  ~Workload();
 
   bool Start();
-  bool IsFinished();
-  void WaitFinish();
   pid_t GetPid() {
     return work_pid_;
   }
@@ -62,7 +52,7 @@ class Workload {
   }
 
   bool CreateNewProcess();
-  void WaitChildProcess(bool no_hang);
+  bool WaitChildProcess(bool wait_forever);
 
   WorkState work_state_;
   std::vector<std::string> args_;
index 0cc67b8..eb1e345 100644 (file)
 
 #include <gtest/gtest.h>
 
-#include <workload.h>
+#include <signal.h>
 
-#include <chrono>
+#include "scoped_signal_handler.h"
+#include "utils.h"
+#include "workload.h"
 
-using namespace std::chrono;
+static volatile bool signaled;
+static void signal_handler(int) {
+  signaled = true;
+}
 
-TEST(workload, smoke) {
+TEST(workload, success) {
+  signaled = false;
+  ScopedSignalHandler scoped_signal_handler({SIGCHLD}, signal_handler);
   auto workload = Workload::CreateWorkload({"sleep", "1"});
   ASSERT_TRUE(workload != nullptr);
-  ASSERT_FALSE(workload->IsFinished());
   ASSERT_TRUE(workload->GetPid() != 0);
-  auto start_time = steady_clock::now();
   ASSERT_TRUE(workload->Start());
-  ASSERT_FALSE(workload->IsFinished());
-  workload->WaitFinish();
-  ASSERT_TRUE(workload->IsFinished());
-  auto end_time = steady_clock::now();
-  ASSERT_TRUE(end_time >= start_time + seconds(1));
+  while (!signaled) {
+  }
 }
 
 TEST(workload, execvp_failure) {
@@ -41,3 +43,42 @@ TEST(workload, execvp_failure) {
   ASSERT_TRUE(workload != nullptr);
   ASSERT_FALSE(workload->Start());
 }
+
+static void run_signaled_workload() {
+  {
+    signaled = false;
+    ScopedSignalHandler scoped_signal_handler({SIGCHLD}, signal_handler);
+    auto workload = Workload::CreateWorkload({"sleep", "10"});
+    ASSERT_TRUE(workload != nullptr);
+    ASSERT_TRUE(workload->Start());
+    ASSERT_EQ(0, kill(workload->GetPid(), SIGKILL));
+    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;
+    ScopedSignalHandler scoped_signal_handler({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/NOTICE b/slideshow/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index 318d805..15824f8 100644 (file)
 #include <time.h>
 #include <linux/input.h>
 #include <cutils/klog.h>
+#include <utils/SystemClock.h>
 #include "minui/minui.h"
 
 #define NEXT_TIMEOUT_MS 5000
-#define LAST_TIMEOUT_S  30
+#define LAST_TIMEOUT_MS 30000
 
 #define LOGE(x...) do { KLOG_ERROR("slideshow", x); } while (0)
 
@@ -38,7 +39,7 @@ static int input_cb(int fd, unsigned int epevents, void *data)
         return -1;
     }
 
-    if (ev.type == EV_KEY) {
+    if (ev.type == EV_KEY && ev.value == 1) {
         *key_code = ev.code;
     }
 
@@ -85,9 +86,9 @@ int main(int argc, char **argv)
     int input = false;
     int opt;
     long int timeout = NEXT_TIMEOUT_MS;
-    time_t start;
+    int64_t start;
 
-    while ((opt = getopt(argc, argv, "t")) != -1) {
+    while ((opt = getopt(argc, argv, "t:")) != -1) {
         switch (opt) {
         case 't':
             timeout = strtol(optarg, NULL, 0);
@@ -118,21 +119,28 @@ int main(int argc, char **argv)
     while (optind < argc - 1) {
         draw(argv[optind++]);
 
-        if (ev_wait(timeout) == 0) {
-            ev_dispatch();
+        start = android::uptimeMillis();
+        long int timeout_remaining = timeout;
+        do {
+            if (ev_wait(timeout_remaining) == 0) {
+                ev_dispatch();
 
-            if (key_code != -1) {
-                input = true;
+                if (key_code != -1) {
+                    input = true;
+                    break;
+                }
             }
-        }
+            timeout_remaining -= android::uptimeMillis() - start;
+        } while (timeout_remaining > 0);
     };
 
     /* if there was user input while showing the images, display the last
-     * image and wait until the power button is pressed or LAST_TIMEOUT_S
+     * image and wait until the power button is pressed or LAST_TIMEOUT_MS
      * has elapsed */
 
     if (input) {
-        start = time(NULL);
+        start = android::uptimeMillis();
+
         draw(argv[optind]);
 
         do {
@@ -140,7 +148,7 @@ int main(int argc, char **argv)
                 ev_dispatch();
             }
 
-            if (time(NULL) - start >= LAST_TIMEOUT_S) {
+            if (android::uptimeMillis() - start >= LAST_TIMEOUT_MS) {
                 break;
             }
         } while (key_code != KEY_POWER);
index 62174cc..a08c437 100644 (file)
@@ -3,6 +3,6 @@ LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 LOCAL_MODULE := sound
 LOCAL_SRC_FILES := playwav.c
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wno-unused-parameter
 include $(BUILD_EXECUTABLE)
 
diff --git a/sound/NOTICE b/sound/NOTICE
new file mode 100644 (file)
index 0000000..7340b9e
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index c3d2f2d..d6f8edd 100644 (file)
@@ -9,7 +9,13 @@ LOCAL_C_INCLUDES := external/squashfs-tools/squashfs-tools
 LOCAL_MODULE := libsquashfs_utils
 include $(BUILD_STATIC_LIBRARY)
 
-ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := squashfs_utils.c
+LOCAL_STATIC_LIBRARIES := libcutils
+LOCAL_C_INCLUDES := external/squashfs-tools/squashfs-tools
+LOCAL_CFLAGS := -Wall -Werror -D_GNU_SOURCE -DSQUASHFS_NO_KLOG
+LOCAL_MODULE := libsquashfs_utils_host
+include $(BUILD_HOST_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := mksquashfsimage.sh
@@ -19,6 +25,5 @@ LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_SUFFIX :=
 LOCAL_BUILT_MODULE_STEM := $(notdir $(LOCAL_SRC_FILES))
 LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_HOST_OS := linux darwin
 include $(BUILD_PREBUILT)
-
-endif
diff --git a/squashfs_utils/NOTICE b/squashfs_utils/NOTICE
new file mode 100644 (file)
index 0000000..8530865
--- /dev/null
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index 1bc2b83..5f45a64 100755 (executable)
@@ -5,7 +5,7 @@
 function usage() {
 cat<<EOT
 Usage:
-${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-d PRODUCT_OUT] [-c FILE_CONTEXTS] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT]
+${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-d PRODUCT_OUT] [-C FS_CONFIG ] [-c FILE_CONTEXTS] [-B BLOCK_MAP_FILE] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT] [-a ]
 EOT
 }
 
@@ -42,12 +42,24 @@ if [[ "$1" == "-d" ]]; then
     shift; shift
 fi
 
+FS_CONFIG=
+if [[ "$1" == "-C" ]]; then
+    FS_CONFIG=$2
+    shift; shift
+fi
+
 FILE_CONTEXTS=
 if [[ "$1" == "-c" ]]; then
     FILE_CONTEXTS=$2
     shift; shift
 fi
 
+BLOCK_MAP_FILE=
+if [[ "$1" == "-B" ]]; then
+    BLOCK_MAP_FILE=$2
+    shift; shift
+fi
+
 BLOCK_SIZE=131072
 if [[ "$1" == "-b" ]]; then
     BLOCK_SIZE=$2
@@ -67,6 +79,12 @@ if [[ "$1" == "-zo" ]]; then
     shift; shift
 fi
 
+DISABLE_4K_ALIGN=false
+if [[ "$1" == "-a" ]]; then
+    DISABLE_4K_ALIGN=true
+    shift;
+fi
+
 OPT=""
 if [ -n "$MOUNT_POINT" ]; then
   OPT="$OPT -mount-point $MOUNT_POINT"
@@ -74,14 +92,23 @@ fi
 if [ -n "$PRODUCT_OUT" ]; then
   OPT="$OPT -product-out $PRODUCT_OUT"
 fi
+if [ -n "$FS_CONFIG" ]; then
+  OPT="$OPT -fs-config-file $FS_CONFIG"
+fi
 if [ -n "$FILE_CONTEXTS" ]; then
   OPT="$OPT -context-file $FILE_CONTEXTS"
 fi
+if [ -n "$BLOCK_MAP_FILE" ]; then
+  OPT="$OPT -block-map $BLOCK_MAP_FILE"
+fi
 if [ -n "$BLOCK_SIZE" ]; then
   OPT="$OPT -b $BLOCK_SIZE"
 fi
+if [ "$DISABLE_4K_ALIGN" = true ]; then
+  OPT="$OPT -disable-4k-align"
+fi
 
-MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp $COMPRESSOR $COMPRESSOR_OPT -no-exports -noappend -no-recovery -android-fs-config $OPT"
+MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp $COMPRESSOR $COMPRESSOR_OPT -no-exports -noappend -no-recovery -no-fragments -no-duplicates -android-fs-config $OPT"
 echo $MAKE_SQUASHFS_CMD
 $MAKE_SQUASHFS_CMD
 
index 6189189..1db424b 100644 (file)
@@ -16,6 +16,7 @@
 
 #include "squashfs_utils.h"
 
+#include <cutils/fs.h>
 #include <cutils/klog.h>
 #include <errno.h>
 #include <fcntl.h>
 
 #include "squashfs_fs.h"
 
+#ifdef SQUASHFS_NO_KLOG
+#include <stdio.h>
+#define ERROR(x...)   fprintf(stderr, x)
+#else
 #define ERROR(x...)   KLOG_ERROR("squashfs_utils", x)
+#endif
 
-int squashfs_parse_sb(char *blk_device, struct squashfs_info *info) {
+size_t squashfs_get_sb_size()
+{
+    return sizeof(struct squashfs_super_block);
+}
+
+int squashfs_parse_sb_buffer(const void *buf, struct squashfs_info *info)
+{
+    const struct squashfs_super_block *sb =
+        (const struct squashfs_super_block *)buf;
+
+    if (sb->s_magic != SQUASHFS_MAGIC) {
+        return -1;
+    }
+
+    info->block_size = sb->block_size;
+    info->inodes = sb->inodes;
+    info->bytes_used = sb->bytes_used;
+    // by default mksquashfs pads the filesystem to 4K blocks
+    info->bytes_used_4K_padded =
+        sb->bytes_used + (4096 - (sb->bytes_used & (4096 - 1)));
+
+    return 0;
+}
+
+int squashfs_parse_sb(const char *blk_device, struct squashfs_info *info)
+{
     int ret = 0;
     struct squashfs_super_block sb;
     int data_device;
@@ -44,19 +75,13 @@ int squashfs_parse_sb(char *blk_device, struct squashfs_info *info) {
         ret = -1;
         goto cleanup;
     }
-    if (sb.s_magic != SQUASHFS_MAGIC) {
+
+    if (squashfs_parse_sb_buffer(&sb, info) == -1) {
         ERROR("Not a valid squashfs filesystem\n");
         ret = -1;
         goto cleanup;
     }
 
-    info->block_size = sb.block_size;
-    info->inodes = sb.inodes;
-    info->bytes_used = sb.bytes_used;
-    // by default mksquashfs pads the filesystem to 4K blocks
-    info->bytes_used_4K_padded =
-        sb.bytes_used + (4096 - (sb.bytes_used & (4096 - 1)));
-
 cleanup:
     close(data_device);
     return ret;
index ccad32d..465429f 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef _SQUASHFS_UTILS_H_
 #define _SQUASHFS_UTILS_H_
 
+#include <stddef.h>
 #include <stdint.h>
 
 #ifdef __cplusplus
@@ -30,7 +31,9 @@ struct squashfs_info {
     uint64_t bytes_used_4K_padded;
 };
 
-int squashfs_parse_sb(char *blk_device, struct squashfs_info *info);
+size_t squashfs_get_sb_size();
+int squashfs_parse_sb_buffer(const void *data, struct squashfs_info *info);
+int squashfs_parse_sb(const char *blk_device, struct squashfs_info *info);
 
 #ifdef __cplusplus
 }
diff --git a/su/su.c b/su/su.c
index d932c1b..ea61f93 100644 (file)
--- a/su/su.c
+++ b/su/su.c
@@ -122,8 +122,13 @@ int main(int argc, char** argv) {
     setenv("PATH", _PATH_DEFPATH, 1);
     unsetenv("IFS");
     struct passwd* pw = getpwuid(uid);
-    setenv("LOGNAME", pw->pw_name, 1);
-    setenv("USER", pw->pw_name, 1);
+    if (pw) {
+        setenv("LOGNAME", pw->pw_name, 1);
+        setenv("USER", pw->pw_name, 1);
+    } else {
+        unsetenv("LOGNAME");
+        unsetenv("USER");
+    }
 
     // Set up the arguments for exec.
     char* exec_args[argc + 1];  // Having too much space is fine.
diff --git a/systrace_analysis/analysis.html b/systrace_analysis/analysis.html
new file mode 100644 (file)
index 0000000..df8e6e6
--- /dev/null
@@ -0,0 +1,212 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/extras/importer/android/event_log_importer.html">
+<link rel="import" href="/tracing/extras/android/android_auditor.html">
+<link rel="import" href="/tracing/importer/import.html">
+<link rel="import" href="/tracing/model/model.html">
+<script>
+'use strict';
+
+(function() {
+  var FRAME_PERF_CLASS = tr.model.FRAME_PERF_CLASS;
+
+  if (!tr.isHeadless) {
+    throw new Error('Can only run in headless mode.');
+  }
+
+  function printFrameStats(process, range) {
+    var goodFrames = 0;
+    var badFrames = 0;
+    var neutralFrames = 0;
+    var terribleFrames = 0;
+
+    process.frames.forEach(function(frame) {
+      // Check if frame belongs to any activity
+      if (frame.start >= range.min && (frame.end <= range.max)) {
+        if (frame.perfClass == FRAME_PERF_CLASS.NEUTRAL) neutralFrames++;
+        if (frame.perfClass == FRAME_PERF_CLASS.GOOD) goodFrames++;
+        if (frame.perfClass == FRAME_PERF_CLASS.BAD) badFrames++;
+        if (frame.perfClass == FRAME_PERF_CLASS.TERRIBLE) terribleFrames++;
+      }
+    });
+
+    var totalFrames = goodFrames + badFrames + neutralFrames;
+    if (totalFrames > 0) {
+      console.log("  Frame stats:");
+      console.log("    # Total frames: " + totalFrames);
+      console.log("    # Terrible frames: " + terribleFrames);
+      console.log("    # Bad frames: " + badFrames);
+      console.log("    # Neutral frames: " + neutralFrames);
+      console.log("    # Good frames: " + goodFrames);
+    }
+  };
+
+  function printMemoryStats(process, range) {
+    var numDirectReclaim = 0;
+    for (var tid in process.threads) {
+      if (!process.threads[tid].timeSlices) continue;
+      process.threads[tid].sliceGroup.slices.forEach(function(slice) {
+        if (slice.title.startsWith('direct reclaim')) {
+          if (slice.start >= range.min &&
+              slice.start + slice.duration <= range.max) {
+            numDirectReclaim++;
+          }
+        }
+      });
+    }
+    console.log("  Memory stats:");
+    console.log("    # of direct reclaims: " + numDirectReclaim);
+  };
+
+  function printCpuStatsForThread(thread, range) {
+    var stats = thread.getCpuStatsForRange(range);
+    // Sum total CPU duration
+    console.log('    ' + thread.name + ' CPU allocation: ');
+    for (var cpu in stats) {
+      var percentage = (stats[cpu] / stats.total * 100).toFixed(2);
+
+      console.log("      CPU " + cpu + ": " + percentage + "% (" +
+          stats[cpu].toFixed(2) + " ms.)");
+    }
+  };
+
+  function printCpuStatsForProcess(process, range) {
+    var total_runtime = 0;
+    for (var tid in process.threads) {
+      var stats = process.threads[tid].getCpuStatsForRange(range);
+      total_runtime += stats.total;
+    }
+    console.log("  CPU stats:");
+    console.log("    Total CPU runtime: " + total_runtime.toFixed(2) + " ms.");
+    var uiThread = process.getThread(process.pid);
+    var renderThreads = process.findAllThreadsNamed('RenderThread');
+    var renderThread = renderThreads.length == 1 ? renderThreads[0] : undefined;
+    if (uiThread)
+      printCpuStatsForThread(uiThread, range);
+    if (renderThread)
+      printCpuStatsForThread(renderThread, range);
+    printCpuFreqStats(range);
+  };
+
+  function printCpuFreqStats(range) {
+    for (var i = 0; i < 8; i++) {
+      var cpu = model.kernel.getOrCreateCpu(i);
+      if (cpu !== undefined) {
+        var stats = cpu.getFreqStatsForRange(range);
+
+        console.log('    CPU ' + i + ' frequency distribution:');
+        for (var freq in stats) {
+          var percentage = (stats[freq] / range.duration * 100).toFixed(2);
+          console.log('      ' + freq + ' ' + percentage + "% (" +
+              stats[freq].toFixed(2)  + ' ms.)');
+        }
+      }
+    }
+  };
+
+  function printBinderStats(process, range) {
+    var outgoing_transactions = 0;
+    var incoming_transactions = 0;
+    for (var tid in process.threads) {
+      var outgoing_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder transaction');
+      var outgoing_async_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder transaction async');
+      var incoming_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder reply');
+      var incoming_async_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder Async recv');
+      outgoing_transactions += outgoing_slices.length + outgoing_async_slices.length;
+      incoming_transactions += incoming_slices.length + incoming_async_slices.length;
+    }
+    console.log('  Binder transaction stats:');
+    console.log('    # Outgoing binder transactions: ' + outgoing_transactions);
+    console.log('    # Incoming binder transactions: ' + incoming_transactions);
+  };
+
+  function pagesInMBString(pages) {
+    if (isNaN(pages))
+      return '0 (0.00 MB)';
+    return pages + ' (' + (pages * 4096 / 1024 / 1024).toFixed(2) + ' MB)';
+  };
+
+  function printPageCacheStats(process) {
+    console.log('  Page cache stats:');
+    var totalAccess = 0;
+    var totalMiss = 0;
+    var totalAdd = 0;
+    for (var file in process.pageCacheAccesses) {
+      totalAccess += process.pageCacheAccesses[file];
+      totalMiss += process.pageCacheMisses[file];
+      totalAdd += process.pageCacheAdd[file];
+      console.log('    File: ' + file);
+      console.log('        # of pages accessed: ' + pagesInMBString(process.pageCacheAccesses[file]));
+      console.log('        # of pages missed: ' + pagesInMBString(process.pageCacheMisses[file]));
+      console.log('        # of pages added to cache: ' + pagesInMBString(process.pageCacheAdd[file]));
+    }
+    console.log('    TOTALS:');
+    console.log('        # of pages accessed: ' + pagesInMBString(totalAccess));
+    console.log('        # of pages missed: ' + pagesInMBString(totalMiss));
+    console.log('        # of pages added to cache: ' + pagesInMBString(totalAdd));
+  };
+
+  function printProcessStats(process, opt_range) {
+    var range = opt_range;
+    if (range === undefined) {
+      // Use the process range
+      range = process.bounds;
+    }
+    printCpuStatsForProcess(process, range);
+    printPageCacheStats(process);
+    printMemoryStats(process, range);
+    printBinderStats(process, range);
+    printFrameStats(process, range);
+  };
+
+  if (sys.argv.length < 2)
+    console.log('First argument needs to be a systrace file.');
+
+  // Import model
+  var systrace = read(sys.argv[1]);
+  var traces = [systrace];
+  if (sys.argv.length >= 3) {
+    // Add event log file if we got it
+    var events = read(sys.argv[2]);
+    traces.push(events);
+  }
+  var model = new tr.Model();
+  var i = new tr.importer.Import(model);
+  console.log("Starting import...");
+  i.importTraces(traces);
+  console.log("Done.");
+  model.getAllProcesses().forEach(function(process) {
+    if (process.name === undefined) return;
+    console.log('Stats for process ' + process.name + ' (' + process.pid + ')');
+    // Check if process has activity starts
+    if (process.activities && process.activities.length > 0) {
+      process.activities.forEach(function(activity) {
+        console.log('Activity ' + activity.name + ' foreground from ' +
+            activity.start + ' until ' + activity.end);
+        var activityRange = tr.b.Range.fromExplicitRange(activity.start,
+            activity.start + activity.duration);
+        printProcessStats(process, activityRange);
+      }, this);
+    } else {
+      printProcessStats(process);
+    }
+    console.log('');
+  });
+})();
+</script>
diff --git a/systrace_analysis/analyze_trace.py b/systrace_analysis/analyze_trace.py
new file mode 100755 (executable)
index 0000000..ddc9927
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/env 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.
+#
+
+import argparse
+import os
+import subprocess
+import sys
+
+def main():
+    # Create argument parser
+    parser = argparse.ArgumentParser()
+    parser.add_argument('systrace_file', metavar='SYSTRACE_FILE', help='systrace file to analyze')
+    parser.add_argument('-e', metavar='EVENT_LOG', help='android event log file')
+    args = parser.parse_args()
+
+    this_script_path = os.path.dirname(os.path.realpath(__file__))
+
+    # Find chromium-trace directory and vinn binary as offset from this script
+    chromium_trace_path = os.path.normpath(this_script_path + '/../../../external/chromium-trace')
+    if not os.path.exists(chromium_trace_path):
+        sys.exit('Can\'t find chromium-trace in your source tree')
+
+    vinn_path = chromium_trace_path + '/catapult/third_party/vinn/'
+    if not os.path.exists(vinn_path):
+        sys.exit('Can\'t find vinn in your source tree')
+
+    sys.path.append(vinn_path)
+    import vinn
+
+    # Find source paths and construct vinn launch arguments
+    tracing_path = chromium_trace_path + '/catapult/tracing/'
+    gldist_path = chromium_trace_path + '/catapult/tracing/third_party/gl-matrix/dist/'
+    source_paths_arg = [tracing_path, gldist_path]
+    js_args_arg = [args.systrace_file]
+    if args.e is not None:
+        js_args_arg += [args.e]
+    res = vinn.RunFile(this_script_path + '/analysis.html', source_paths=source_paths_arg,
+            js_args=js_args_arg, stdout=sys.stdout, stdin=sys.stdin);
+    return res.returncode
+
+if __name__ == '__main__':
+    main()
index 57a7ebc..361e99f 100644 (file)
@@ -25,6 +25,7 @@
 #include <getopt.h>
 #include <netlink/attr.h>
 #include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
 #include <netlink/handlers.h>
 #include <netlink/msg.h>
 #include <stdio.h>
@@ -41,71 +42,15 @@ struct TaskStatistics {
     struct taskstats stats;
 };
 
-int send_command(struct nl_sock* netlink_socket, uint16_t nlmsg_type,
-                 uint32_t nlmsg_pid, uint8_t genl_cmd, uint16_t nla_type,
-                 void* nla_data, int nla_len) {
-    struct nl_msg* message = nlmsg_alloc();
-    int seq = 0;
-    int version = 1;
-    int header_length = 0;
-    int flags = NLM_F_REQUEST;
-    genlmsg_put(message, nlmsg_pid, seq, nlmsg_type, header_length, flags,
-                genl_cmd, version);
-    nla_put(message, nla_type, nla_len, nla_data);
-
-    /* Override the header flags since we don't want NLM_F_ACK. */
-    struct nlmsghdr* header = nlmsg_hdr(message);
-    header->nlmsg_flags = flags;
-
-    int result = nl_send(netlink_socket, message);
-    nlmsg_free(message);
-    return result;
-}
-
 int print_receive_error(struct sockaddr_nl* address __unused,
                         struct nlmsgerr* error, void* arg __unused) {
     fprintf(stderr, "Netlink receive error: %s\n", strerror(-error->error));
     return NL_STOP;
 }
 
-int parse_family_id(struct nl_msg* msg, void* arg) {
-    struct genlmsghdr* gnlh = (struct genlmsghdr*)nlmsg_data(nlmsg_hdr(msg));
-    struct nlattr* attr = genlmsg_attrdata(gnlh, 0);
-    int remaining = genlmsg_attrlen(gnlh, 0);
-
-    do {
-        if (attr->nla_type == CTRL_ATTR_FAMILY_ID) {
-            *((int*)arg) = nla_get_u16(attr);
-            return NL_STOP;
-        }
-    } while ((attr = nla_next(attr, &remaining)));
-    return NL_OK;
-}
-
-int get_family_id(struct nl_sock* netlink_socket, const char* name) {
-    if (send_command(netlink_socket, GENL_ID_CTRL, getpid(),
-                     CTRL_CMD_GETFAMILY,
-                     CTRL_ATTR_FAMILY_NAME,
-                     (void*)name, strlen(name) + 1) < 0) {
-        return 0;
-    }
-
-    int family_id = 0;
-    struct nl_cb* callbacks = nl_cb_get(nl_cb_alloc(NL_CB_VALID));
-    nl_cb_set(callbacks, NL_CB_VALID, NL_CB_DEFAULT, &parse_family_id,
-              &family_id);
-    nl_cb_err(callbacks, NL_CB_DEFAULT, &print_receive_error, NULL);
-
-    if (nl_recvmsgs(netlink_socket, callbacks) < 0) {
-        return 0;
-    }
-    nl_cb_put(callbacks);
-    return family_id;
-}
-
 void parse_aggregate_task_stats(struct nlattr* attr, int attr_size,
                                 struct TaskStatistics* stats) {
-    do {
+    nla_for_each_attr(attr, attr, attr_size, attr_size) {
         switch (attr->nla_type) {
             case TASKSTATS_TYPE_PID:
                 stats->pid = nla_get_u32(attr);
@@ -119,7 +64,7 @@ void parse_aggregate_task_stats(struct nlattr* attr, int attr_size,
             default:
                 break;
         }
-    } while ((attr = nla_next(attr, &attr_size)));
+    }
 }
 
 int parse_task_stats(struct nl_msg* msg, void* arg) {
@@ -128,7 +73,7 @@ int parse_task_stats(struct nl_msg* msg, void* arg) {
     struct nlattr* attr = genlmsg_attrdata(gnlh, 0);
     int remaining = genlmsg_attrlen(gnlh, 0);
 
-    do {
+    nla_for_each_attr(attr, attr, remaining, remaining) {
         switch (attr->nla_type) {
             case TASKSTATS_TYPE_AGGR_PID:
             case TASKSTATS_TYPE_AGGR_TGID:
@@ -138,7 +83,7 @@ int parse_task_stats(struct nl_msg* msg, void* arg) {
             default:
                 break;
         }
-    } while ((attr = nla_next(attr, &remaining)));
+    }
     return NL_STOP;
 }
 
@@ -146,22 +91,27 @@ int query_task_stats(struct nl_sock* netlink_socket, int family_id,
                      int command_type, int parameter,
                      struct TaskStatistics* stats) {
     memset(stats, 0, sizeof(*stats));
-    int result = send_command(netlink_socket, family_id, getpid(),
-                              TASKSTATS_CMD_GET, command_type, &parameter,
-                              sizeof(parameter));
+
+    struct nl_msg* message = nlmsg_alloc();
+    genlmsg_put(message, NL_AUTO_PID, NL_AUTO_SEQ, family_id, 0, 0,
+               TASKSTATS_CMD_GET, TASKSTATS_VERSION);
+    nla_put_u32(message, command_type, parameter);
+
+    int result = nl_send_auto_complete(netlink_socket, message);
+    nlmsg_free(message);
     if (result < 0) {
         return result;
     }
 
-    struct nl_cb* callbacks = nl_cb_get(nl_cb_alloc(NL_CB_VALID));
-    nl_cb_set(callbacks, NL_CB_VALID, NL_CB_DEFAULT, &parse_task_stats, stats);
-    nl_cb_err(callbacks, NL_CB_DEFAULT, &print_receive_error, &family_id);
+    struct nl_cb* callbacks = nl_cb_get(nl_cb_alloc(NL_CB_CUSTOM));
+    nl_cb_set(callbacks, NL_CB_VALID, NL_CB_CUSTOM, &parse_task_stats, stats);
+    nl_cb_err(callbacks, NL_CB_CUSTOM, &print_receive_error, &family_id);
 
     result = nl_recvmsgs(netlink_socket, callbacks);
+    nl_cb_put(callbacks);
     if (result < 0) {
         return result;
     }
-    nl_cb_put(callbacks);
     return stats->pid || stats->tgid;
 }
 
@@ -349,21 +299,27 @@ int main(int argc, char** argv) {
     }
 
     struct nl_sock* netlink_socket = nl_socket_alloc();
-    if (!netlink_socket || genl_connect(netlink_socket) < 0) {
-        perror("Unable to open netlink socket (are you root?)");
+    if (!netlink_socket) {
+        fprintf(stderr, "Unable to allocate netlink socket\n");
+        goto error;
+    }
+
+    int ret = genl_connect(netlink_socket);
+    if (ret < 0) {
+        nl_perror(ret, "Unable to open netlink socket (are you root?)");
         goto error;
     }
 
-    int family_id = get_family_id(netlink_socket, TASKSTATS_GENL_NAME);
-    if (!family_id) {
-        perror("Unable to determine taskstats family id "
+    int family_id = genl_ctrl_resolve(netlink_socket, TASKSTATS_GENL_NAME);
+    if (family_id < 0) {
+        nl_perror(family_id, "Unable to determine taskstats family id "
                "(does your kernel support taskstats?)");
         goto error;
     }
     struct TaskStatistics stats;
-    if (query_task_stats(netlink_socket, family_id, command_type, pid,
-                         &stats) < 0) {
-        perror("Failed to query taskstats");
+    ret = query_task_stats(netlink_socket, family_id, command_type, pid, &stats);
+    if (ret < 0) {
+        nl_perror(ret, "Failed to query taskstats");
         goto error;
     }
     print_task_stats(&stats, human_readable);
similarity index 100%
rename from timeinfo/NOTICE
rename to tests/NOTICE
index b8bc5f2..7233e10 100644 (file)
@@ -53,7 +53,7 @@ static unsigned int cards;
 static unsigned int mixers;
 static unsigned int timers;
 
-int getPcmNodes(void)
+unsigned int getPcmNodes(void)
 {
     DIR *d;
     struct dirent *de;
@@ -165,7 +165,7 @@ int getPcmParams(unsigned int i)
 
 TEST(pcmtest, CheckAudioDir) {
     pcms = getPcmNodes();
-    ASSERT_GT(pcms, 0);
+    ASSERT_GT(pcms, 0U);
 }
 
 TEST(pcmtest, GetSoundDevs) {
@@ -176,24 +176,24 @@ TEST(pcmtest, GetSoundDevs) {
 }
 
 TEST(pcmtest, CheckPcmSanity0) {
-    ASSERT_NE(0, pcms);
+    ASSERT_NE(0U, pcms);
 }
 
 TEST(pcmtest, CheckPcmSanity1) {
-    EXPECT_NE(1, pcms % 2);
+    EXPECT_NE(1U, pcms % 2);
 }
 
 TEST(pcmtests, CheckMixerSanity) {
-    ASSERT_NE(0, mixers);
+    ASSERT_NE(0U, mixers);
     ASSERT_EQ(mixers, cards);
 }
 
 TEST(pcmtest, CheckTimesSanity0) {
-    ASSERT_NE(0, timers);
+    ASSERT_NE(0U, timers);
 }
 
 TEST(pcmtest, CheckTimesSanity1) {
-    EXPECT_EQ(1, timers);
+    EXPECT_EQ(1U, timers);
 }
 
 TEST(pcmtest, CheckPcmDevices) {
index eb2ead2..fa7629c 100644 (file)
@@ -20,7 +20,6 @@ include $(CLEAR_VARS)
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
 LOCAL_MODULE_TAGS := eng tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativebenchmark
 
 LOCAL_STATIC_LIBRARIES += \
     libtestUtil
@@ -37,4 +36,4 @@ LOCAL_C_INCLUDES += \
 LOCAL_MODULE := binderAddInts
 LOCAL_SRC_FILES := binderAddInts.cpp
 
-include $(BUILD_EXECUTABLE)
+include $(BUILD_NATIVE_BENCHMARK)
index e4ddcfa..d0b2910 100644 (file)
  */
 
 /*
- * Binder add integers benchmark
+ * Binder add integers benchmark (Using google-benchmark library)
  *
- * Measures the rate at which a short binder IPC operation can be
- * performed.  The operation consists of the client sending a parcel
- * that contains two integers.  For each parcel that the server
- * receives, it adds the two integers and sends the sum back to
- * the client.
- *
- * This benchmark supports the following command-line options:
- *
- *   -c cpu - bind client to specified cpu (default: unbound)
- *   -s cpu - bind server to specified cpu (default: unbound)
- *   -n num - perform IPC operation num times (default: 1000)
- *   -d time - delay specified amount of seconds after each
- *             IPC operation. (default 1e-3)
  */
 
 #include <cerrno>
 #include <grp.h>
 #include <iostream>
+#include <iomanip>
 #include <libgen.h>
 #include <time.h>
 #include <unistd.h>
@@ -48,6 +36,9 @@
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
+
+#include <benchmark/benchmark.h>
+
 #include <utils/Log.h>
 #include <testUtil.h>
 
@@ -61,13 +52,11 @@ String16 serviceName("test.binderAddInts");
 struct options {
     int serverCPU;
     int clientCPU;
-    unsigned int iterations;
     float        iterDelay; // End of iteration delay in seconds
 } options = { // Set defaults
     unbound, // Server CPU
     unbound, // Client CPU
-    1000,    // Iterations
-    1e-3,    // End of iteration delay
+    0.0,    // End of iteration delay
 };
 
 class AddIntsService : public BBinder
@@ -89,137 +78,13 @@ class AddIntsService : public BBinder
 };
 
 // File scope function prototypes
-static void server(void);
-static void client(void);
+static bool server(void);
+static void BM_client(benchmark::State& state);
 static void bindCPU(unsigned int cpu);
 static ostream &operator<<(ostream &stream, const String16& str);
 static ostream &operator<<(ostream &stream, const cpu_set_t& set);
 
-int main(int argc, char *argv[])
-{
-    int rv;
-
-    // Determine CPUs available for use.
-    // This testcase limits its self to using CPUs that were
-    // available at the start of the benchmark.
-    cpu_set_t availCPUs;
-    if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
-        cerr << "sched_getaffinity failure, rv: " << rv
-            << " errno: " << errno << endl;
-        exit(1);
-    }
-
-    // Parse command line arguments
-    int opt;
-    while ((opt = getopt(argc, argv, "s:c:n:d:?")) != -1) {
-        char *chptr; // character pointer for command-line parsing
-
-        switch (opt) {
-        case 'c': // client CPU
-        case 's': { // server CPU
-            // Parse the CPU number
-            int cpu = strtoul(optarg, &chptr, 10);
-            if (*chptr != '\0') {
-                cerr << "Invalid cpu specified for -" << (char) opt
-                    << " option of: " << optarg << endl;
-                exit(2);
-            }
-
-            // Is the CPU available?
-            if (!CPU_ISSET(cpu, &availCPUs)) {
-                cerr << "CPU " << optarg << " not currently available" << endl;
-                cerr << "  Available CPUs: " << availCPUs << endl;
-                exit(3);
-            }
-
-            // Record the choice
-            *((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
-            break;
-        }
-
-        case 'n': // iterations
-            options.iterations = strtoul(optarg, &chptr, 10);
-            if (*chptr != '\0') {
-                cerr << "Invalid iterations specified of: " << optarg << endl;
-                exit(4);
-            }
-            if (options.iterations < 1) {
-                cerr << "Less than 1 iteration specified by: "
-                    << optarg << endl;
-                exit(5);
-            }
-            break;
-
-        case 'd': // Delay between each iteration
-            options.iterDelay = strtod(optarg, &chptr);
-            if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
-                cerr << "Invalid delay specified of: " << optarg << endl;
-                exit(6);
-            }
-            break;
-
-        case '?':
-        default:
-            cerr << basename(argv[0]) << " [options]" << endl;
-            cerr << "  options:" << endl;
-            cerr << "    -s cpu - server CPU number" << endl;
-            cerr << "    -c cpu - client CPU number" << endl;
-            cerr << "    -n num - iterations" << endl;
-            cerr << "    -d time - delay after operation in seconds" << endl;
-            exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
-        }
-    }
-
-    // Display selected options
-    cout << "serverCPU: ";
-    if (options.serverCPU == unbound) {
-        cout << " unbound";
-    } else {
-        cout << options.serverCPU;
-    }
-    cout << endl;
-    cout << "clientCPU: ";
-    if (options.clientCPU == unbound) {
-        cout << " unbound";
-    } else {
-        cout << options.clientCPU;
-    }
-    cout << endl;
-    cout << "iterations: " << options.iterations << endl;
-    cout << "iterDelay: " << options.iterDelay << endl;
-
-    // Fork client, use this process as server
-    fflush(stdout);
-    switch (pid_t pid = fork()) {
-    case 0: // Child
-        client();
-        return 0;
-
-    default: // Parent
-        server();
-
-        // Wait for all children to end
-        do {
-            int stat;
-            rv = wait(&stat);
-            if ((rv == -1) && (errno == ECHILD)) { break; }
-            if (rv == -1) {
-                cerr << "wait failed, rv: " << rv << " errno: "
-                    << errno << endl;
-                perror(NULL);
-                exit(8);
-            }
-        } while (1);
-        return 0;
-
-    case -1: // Error
-        exit(9);
-    }
-
-    return 0;
-}
-
-static void server(void)
+static bool server(void)
 {
     int rv;
 
@@ -230,63 +95,62 @@ static void server(void)
         new AddIntsService(options.serverCPU))) != 0) {
         cerr << "addService " << serviceName << " failed, rv: " << rv
             << " errno: " << errno << endl;
+        return false;
     }
 
     // Start threads to handle server work
     proc->startThreadPool();
+    return true;
 }
 
-static void client(void)
+static void BM_client(benchmark::State& state)
 {
+    server();
     int rv;
     sp<IServiceManager> sm = defaultServiceManager();
-    double min = FLT_MAX, max = 0.0, total = 0.0; // Time in seconds for all
-                                                  // the IPC calls.
 
     // If needed bind to client CPU
     if (options.clientCPU != unbound) { bindCPU(options.clientCPU); }
 
     // Attach to service
     sp<IBinder> binder;
-    do {
+    for (int i = 0; i < 3; i++) {
         binder = sm->getService(serviceName);
         if (binder != 0) break;
         cout << serviceName << " not published, waiting..." << endl;
         usleep(500000); // 0.5 s
-    } while(true);
+    }
 
-    // Perform the IPC operations
-    for (unsigned int iter = 0; iter < options.iterations; iter++) {
+    if (binder == 0) {
+        cout << serviceName << " failed to publish, aborting" << endl;
+        return;
+    }
+
+    unsigned int iter = 0;
+    // Perform the IPC operations in the benchmark
+    while (state.KeepRunning()) {
         Parcel send, reply;
 
         // Create parcel to be sent.  Will use the iteration cound
         // and the iteration count + 3 as the two integer values
         // to be sent.
+        state.PauseTiming();
         int val1 = iter;
         int val2 = iter + 3;
         int expected = val1 + val2;  // Expect to get the sum back
         send.writeInt32(val1);
         send.writeInt32(val2);
-
+        state.ResumeTiming();
         // Send the parcel, while timing how long it takes for
         // the answer to return.
-        struct timespec start;
-        clock_gettime(CLOCK_MONOTONIC, &start);
         if ((rv = binder->transact(AddIntsService::ADD_INTS,
             send, &reply)) != 0) {
             cerr << "binder->transact failed, rv: " << rv
                 << " errno: " << errno << endl;
             exit(10);
         }
-        struct timespec current;
-        clock_gettime(CLOCK_MONOTONIC, &current);
-
-        // Calculate how long this operation took and update the stats
-        struct timespec deltaTimespec = tsDelta(&start, &current);
-        double delta = ts2double(&deltaTimespec);
-        min = (delta < min) ? delta : min;
-        max = (delta > max) ? delta : max;
-        total += delta;
+
+        state.PauseTiming();
         int result = reply.readInt32();
         if (result != (int) (iter + iter + 3)) {
             cerr << "Unexpected result for iteration " << iter << endl;
@@ -295,14 +159,11 @@ static void client(void)
         }
 
         if (options.iterDelay > 0.0) { testDelaySpin(options.iterDelay); }
+        state.ResumeTiming();
     }
-
-    // Display the results
-    cout << "Time per iteration min: " << min
-        << " avg: " << (total / options.iterations)
-        << " max: " << max
-        << endl;
 }
+BENCHMARK(BM_client);
+
 
 AddIntsService::AddIntsService(int cpu): cpu_(cpu) {
     if (cpu != unbound) { bindCPU(cpu); }
@@ -310,7 +171,7 @@ AddIntsService::AddIntsService(int cpu): cpu_(cpu) {
 
 // Server function that handles parcels received from the client
 status_t AddIntsService::onTransact(uint32_t code, const Parcel &data,
-                                    Parcel* reply, uint32_t flags) {
+                                    Parcel* reply, uint32_t /* flags */) {
     int val1, val2;
     status_t rv(0);
     int cpu;
@@ -382,3 +243,68 @@ static ostream &operator<<(ostream &stream, const cpu_set_t& set)
 
     return stream;
 }
+
+int main(int argc, char *argv[])
+{
+    int rv;
+    ::benchmark::Initialize(&argc, argv);
+    // Determine CPUs available for use.
+    // This testcase limits its self to using CPUs that were
+    // available at the start of the benchmark.
+    cpu_set_t availCPUs;
+    if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
+        cerr << "sched_getaffinity failure, rv: " << rv
+            << " errno: " << errno << endl;
+        exit(1);
+    }
+
+    // Parse command line arguments
+    int opt;
+    while ((opt = getopt(argc, argv, "s:c:d:?")) != -1) {
+        char *chptr; // character pointer for command-line parsing
+
+        switch (opt) {
+        case 'c': // client CPU
+        case 's': { // server CPU
+            // Parse the CPU number
+            int cpu = strtoul(optarg, &chptr, 10);
+            if (*chptr != '\0') {
+                cerr << "Invalid cpu specified for -" << (char) opt
+                    << " option of: " << optarg << endl;
+                exit(2);
+            }
+
+            // Is the CPU available?
+            if (!CPU_ISSET(cpu, &availCPUs)) {
+                cerr << "CPU " << optarg << " not currently available" << endl;
+                cerr << "  Available CPUs: " << availCPUs << endl;
+                exit(3);
+            }
+
+            // Record the choice
+            *((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
+            break;
+        }
+
+        case 'd': // delay between each iteration
+            options.iterDelay = strtod(optarg, &chptr);
+            if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
+                cerr << "Invalid delay specified of: " << optarg << endl;
+                exit(6);
+            }
+            break;
+
+        case '?':
+        default:
+            cerr << basename(argv[0]) << " [options]" << endl;
+            cerr << "  options:" << endl;
+            cerr << "    -s cpu - server CPU number" << endl;
+            cerr << "    -c cpu - client CPU number" << endl;
+            cerr << "    -d time - delay after operation in seconds" << endl;
+            exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
+        }
+    }
+
+    ::benchmark::RunSpecifiedBenchmarks();
+}
+
diff --git a/tests/bionic/libc/Android.mk b/tests/bionic/libc/Android.mk
deleted file mode 100644 (file)
index 155a701..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-# Copyright (C) 2008 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.
-#
-# Build control file for Bionic's test programs
-# define the BIONIC_TESTS environment variable to build the test programs
-#
-ifdef BIONIC_TESTS
-
-LOCAL_PATH:= $(call my-dir)
-
-# used to define a simple test program and build it as a standalone
-# device executable.
-#
-# you can use EXTRA_CFLAGS to indicate additional CFLAGS to use
-# in the build. the variable will be cleaned on exit
-#
-define device-test
-  $(foreach file,$(1), \
-    $(eval include $(CLEAR_VARS)) \
-    $(eval LOCAL_SRC_FILES := $(file)) \
-    $(eval LOCAL_MODULE := $(notdir $(file:%.c=%))) \
-    $(eval LOCAL_MODULE := $(LOCAL_MODULE:%.cpp=%)) \
-    $(eval LOCAL_CFLAGS += $(EXTRA_CFLAGS)) \
-    $(eval LOCAL_LDFLAGS += $(EXTRA_LDLIBS)) \
-    $(eval LOCAL_MODULE_TAGS := tests) \
-    $(eval include $(BUILD_EXECUTABLE)) \
-  ) \
-  $(eval EXTRA_CFLAGS :=) \
-  $(eval EXTRA_LDLIBS :=)
-endef
-
-# same as 'device-test' but builds a host executable instead
-# you can use EXTRA_LDLIBS to indicate additional linker flags
-#
-define host-test
-  $(foreach file,$(1), \
-    $(eval include $(CLEAR_VARS)) \
-    $(eval LOCAL_SRC_FILES := $(file)) \
-    $(eval LOCAL_MODULE := $(notdir $(file:%.c=%))) \
-    $(eval LOCAL_MODULE := $(LOCAL_MODULE:%.cpp=%)) \
-    $(eval LOCAL_CFLAGS += $(EXTRA_CFLAGS)) \
-    $(eval LOCAL_LDLIBS += $(EXTRA_LDLIBS)) \
-    $(eval LOCAL_MODULE_TAGS := tests) \
-    $(eval include $(BUILD_HOST_EXECUTABLE)) \
-  ) \
-  $(eval EXTRA_CFLAGS :=) \
-  $(eval EXTRA_LDLIBS :=)
-endef
-
-# First, the tests in 'common'
-
-sources := \
-    common/test_pthread_mutex.c \
-    common/test_pthread_rwlock.c \
-
-# _XOPEN_SOURCE=600 is needed to get pthread_mutexattr_settype() on GLibc
-#
-EXTRA_LDLIBS := -lpthread -lrt
-EXTRA_CFLAGS := -D_XOPEN_SOURCE=600 -DHOST
-$(call host-test, $(sources))
-$(call device-test, $(sources))
-
-# Second, the Bionic-specific tests
-
-sources :=  \
-    bionic/test_cond.c \
-    bionic/test_pthread_cond.c \
-
-$(call device-test, $(sources))
-
-endif  # BIONIC_TESTS
diff --git a/tests/bionic/libc/README.TXT b/tests/bionic/libc/README.TXT
deleted file mode 100644 (file)
index c43f93b..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-This directory contains a set of tests for Android's Bionic C library.
-
-You must define the BIONIC_TESTS environment variable to build these
-test programs. For example, do:
-
-    cd system/extras/tests/bionic/libc
-    mm BIONIC_TESTS=1
-
-All test programs, except those in the 'other' directory, should exit
-with a status code of 0 in case of success, and 1 in case of failure.
-
-The directory layout is simple:
-
-  common/
-    Contains tests that can be compiled either with Bionic or another
-    C library.
-
-  bionic/
-    Contains tests that can *only* be compiled against Bionic
-
-  other/
-    Other unrelated tests. These are not run by the test runner
-    program but will be installed to your device nevertheless.
-    Put benchmarks and various debug/info stuff there.
diff --git a/tests/bionic/libc/bionic/test_cond.c b/tests/bionic/libc/bionic/test_cond.c
deleted file mode 100644 (file)
index 62d9694..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the 
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-#include <pthread.h>
-#include <semaphore.h>
-#include <errno.h>
-#include <stdio.h>
-#include <time.h>
-#include <string.h>
-#include <unistd.h>
-
-static pthread_mutex_t lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
-static pthread_cond_t  wait = PTHREAD_COND_INITIALIZER;
-
-static void* _thread1(void *__u __attribute__((unused)))
-{
-    printf("1: obtaining mutex\n");
-    pthread_mutex_lock(&lock);
-    printf("1: waiting on condition variable\n");
-    pthread_cond_wait(&wait, &lock);
-    printf("1: releasing mutex\n");
-    pthread_mutex_unlock(&lock);
-    printf("1: exiting\n");    
-    return NULL;
-}
-
-static void* _thread2(void *__u __attribute__((unused)))
-{
-    int cnt = 2;
-    while(cnt--) {
-        printf("2: obtaining mutex\n");
-        pthread_mutex_lock(&lock);
-        printf("2: signaling\n");
-        pthread_cond_signal(&wait);
-        printf("2: releasing mutex\n");
-        pthread_mutex_unlock(&lock);
-    }
-
-    printf("2: exiting\n");
-    return NULL;
-}
-
-typedef void* (*thread_func)(void*);
-static const  thread_func thread_routines[] =
-{
-    &_thread1,
-    &_thread2,
-};
-
-int main(void)
-{
-    pthread_t t[2];
-    int nn;
-    int count = (int)(sizeof t/sizeof t[0]);
-
-    for (nn = 0; nn < count; nn++) {
-        printf("main: creating thread %d\n", nn+1);
-        if (pthread_create( &t[nn], NULL, thread_routines[nn], NULL) < 0) {
-            printf("main: could not create thread %d: %s\n", nn+1, strerror(errno));
-            return -2;
-        }
-    }
-
-    for (nn = 0; nn < count; nn++) {
-        printf("main: joining thread %d\n", nn+1);
-        if (pthread_join(t[nn], NULL)) {
-            printf("main: could not join thread %d: %s\n", nn+1, strerror(errno));
-            return -2;
-        }
-    }
-
-    return 0;
-}
diff --git a/tests/bionic/libc/bionic/test_pthread_cond.c b/tests/bionic/libc/bionic/test_pthread_cond.c
deleted file mode 100644 (file)
index 6b13d77..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-#include <pthread.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-
-
-static pthread_cond_t cond1;
-static pthread_cond_t cond2;
-static pthread_mutex_t test_lock = PTHREAD_MUTEX_INITIALIZER;
-
-static void *
-thread1_func(void* arg)
-{
-    printf("Thread 1 (arg=%p tid=%d) entered.\n", arg, gettid());
-    printf("1 waiting for cond1\n");
-    pthread_mutex_lock(&test_lock);
-    pthread_cond_wait(&cond1, &test_lock );
-    pthread_mutex_unlock(&test_lock);
-    printf("Thread 1 done.\n");
-    return 0;
-}
-
-static void *
-thread2_func(void* arg)
-{
-    printf("Thread 2 (arg=%p tid=%d) entered.\n", arg, gettid());
-    printf("2 waiting for cond2\n");
-    pthread_mutex_lock(&test_lock);
-    pthread_cond_wait(&cond2, &test_lock );
-    pthread_mutex_unlock(&test_lock);
-
-    printf("Thread 2 done.\n");
-    return 0;
-}
-
-static void *
-thread3_func(void* arg)
-{
-    printf("Thread 3 (arg=%p tid=%d) entered.\n", arg, gettid());
-    printf("3 waiting for cond1\n");
-    pthread_mutex_lock(&test_lock);
-    pthread_cond_wait(&cond1, &test_lock );
-    pthread_mutex_unlock(&test_lock);
-    printf("3 Sleeping\n");
-    sleep(2);
-    printf("3 signal cond2\n");
-    pthread_cond_signal(&cond2);
-
-    printf("Thread 3 done.\n");
-    return 0;
-}
-
-static void *
-thread4_func(void* arg)
-{
-    printf("Thread 4 (arg=%p tid=%d) entered.\n", arg, gettid());
-    printf("4 Sleeping\n");
-    sleep(5);
-
-    printf("4 broadcast cond1\n");
-    pthread_cond_broadcast(&cond1);
-    printf("Thread 4 done.\n");
-    return 0;
-}
-
-int main(int argc __unused, const char *argv[] __unused)
-{
-    pthread_t t[4];
-
-    pthread_cond_init(&cond1, NULL);
-    pthread_cond_init(&cond2, NULL);
-    pthread_create( &t[0], NULL, thread1_func, (void *)1 );
-    pthread_create( &t[1], NULL, thread2_func, (void *)2 );
-    pthread_create( &t[2], NULL, thread3_func, (void *)3 );
-    pthread_create( &t[3], NULL, thread4_func, (void *)4 );
-
-    pthread_join(t[0], NULL);
-    pthread_join(t[1], NULL);
-    pthread_join(t[2], NULL);
-    pthread_join(t[3], NULL);
-    return 0;
-}
diff --git a/tests/bionic/libc/common/test_pthread_mutex.c b/tests/bionic/libc/common/test_pthread_mutex.c
deleted file mode 100644 (file)
index a84d5be..0000000
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the 
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <pthread.h>
-#include <errno.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-/* Posix states that EDEADLK should be returned in case a deadlock condition
- * is detected with a PTHREAD_MUTEX_ERRORCHECK lock() or trylock(), but
- * GLibc returns EBUSY instead.
- */
-#ifdef HOST
-#  define ERRNO_PTHREAD_EDEADLK   EBUSY
-#else
-#  define ERRNO_PTHREAD_EDEADLK   EDEADLK
-#endif
-
-static void __attribute__((noreturn))
-panic(const char* func, const char* format, ...)
-{
-    va_list  args;
-    fprintf(stderr, "%s: ", func);
-    va_start(args, format);
-    vfprintf(stderr, format, args);
-    va_end(args);
-    fprintf(stderr, "\n");
-    exit(1);
-}
-
-#define  PANIC(...)   panic(__FUNCTION__,__VA_ARGS__)
-
-static void __attribute__((noreturn))
-error(int  errcode, const char* func, const char* format, ...)
-{
-    va_list  args;
-    fprintf(stderr, "%s: ", func);
-    va_start(args, format);
-    vfprintf(stderr, format, args);
-    va_end(args);
-    fprintf(stderr, " error=%d: %s\n", errcode, strerror(errcode));
-    exit(1);
-}
-
-/* return current time in seconds as floating point value */
-static double
-time_now(void)
-{
-    struct timespec ts[1];
-
-    clock_gettime(CLOCK_MONOTONIC, ts);
-    return (double)ts->tv_sec + ts->tv_nsec/1e9;
-}
-
-static void
-time_sleep(double  delay)
-{
-    struct timespec ts;
-    int             ret;
-
-    ts.tv_sec  = (time_t)delay;
-    ts.tv_nsec = (long)((delay - ts.tv_sec)*1e9);
-
-    do {
-        ret = nanosleep(&ts, &ts);
-    } while (ret < 0 && errno == EINTR);
-}
-
-#define  ERROR(errcode,...)   error((errcode),__FUNCTION__,__VA_ARGS__)
-
-#define  TZERO(cond)   \
-    { int _ret = (cond); if (_ret != 0) ERROR(_ret,"%d:%s", __LINE__, #cond); }
-
-#define  TTRUE(cond)   \
-    { if (!(cond)) PANIC("%d:%s", __LINE__, #cond); }
-
-#define  TFALSE(cond)   \
-    { if (!!(cond)) PANIC("%d:%s", __LINE__, #cond); }
-
-#define  TEXPECT_INT(cond,val) \
-    { int _ret = (cond); if (_ret != (val)) PANIC("%d:%s returned %d (%d expected)", __LINE__, #cond, _ret, (val)); }
-
-/* perform a simple init/lock/unlock/destroy test on a mutex of given attributes */
-static void do_test_mutex_1(pthread_mutexattr_t *attr)
-{
-    int              ret;
-    pthread_mutex_t  lock[1];
-
-    TZERO(pthread_mutex_init(lock, attr));
-    TZERO(pthread_mutex_lock(lock));
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_destroy(lock));
-}
-
-static void set_mutexattr_type(pthread_mutexattr_t *attr, int type)
-{
-    int  newtype;
-    TZERO(pthread_mutexattr_settype(attr, type));
-    newtype = ~type;
-    TZERO(pthread_mutexattr_gettype(attr, &newtype));
-    TEXPECT_INT(newtype,type);
-}
-
-/* simple init/lock/unlock/destroy on all mutex types */
-static void do_test_1(void)
-{
-    int                  ret, type;
-    pthread_mutexattr_t  attr[1];
-
-    do_test_mutex_1(NULL);
-
-    /* non-shared version */
-
-    TZERO(pthread_mutexattr_init(attr));
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_NORMAL);
-    do_test_mutex_1(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_RECURSIVE);
-    do_test_mutex_1(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_ERRORCHECK);
-    do_test_mutex_1(attr);
-
-    TZERO(pthread_mutexattr_destroy(attr));
-
-    /* shared version */
-    TZERO(pthread_mutexattr_init(attr));
-    TZERO(pthread_mutexattr_setpshared(attr, PTHREAD_PROCESS_SHARED));
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_NORMAL);
-    do_test_mutex_1(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_RECURSIVE);
-    do_test_mutex_1(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_ERRORCHECK);
-    do_test_mutex_1(attr);
-
-    TZERO(pthread_mutexattr_destroy(attr));
-}
-
-/* perform init/trylock/unlock/destroy then init/lock/trylock/destroy */
-static void do_test_mutex_2(pthread_mutexattr_t *attr)
-{
-    pthread_mutex_t  lock[1];
-
-    TZERO(pthread_mutex_init(lock, attr));
-    TZERO(pthread_mutex_trylock(lock));
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_destroy(lock));
-
-    TZERO(pthread_mutex_init(lock, attr));
-    TZERO(pthread_mutex_trylock(lock));
-    TEXPECT_INT(pthread_mutex_trylock(lock),EBUSY);
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_destroy(lock));
-}
-
-static void do_test_mutex_2_rec(pthread_mutexattr_t *attr)
-{
-    pthread_mutex_t  lock[1];
-
-    TZERO(pthread_mutex_init(lock, attr));
-    TZERO(pthread_mutex_trylock(lock));
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_destroy(lock));
-
-    TZERO(pthread_mutex_init(lock, attr));
-    TZERO(pthread_mutex_trylock(lock));
-    TZERO(pthread_mutex_trylock(lock));
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_destroy(lock));
-}
-
-static void do_test_mutex_2_chk(pthread_mutexattr_t *attr)
-{
-    pthread_mutex_t  lock[1];
-
-    TZERO(pthread_mutex_init(lock, attr));
-    TZERO(pthread_mutex_trylock(lock));
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_destroy(lock));
-
-    TZERO(pthread_mutex_init(lock, attr));
-    TZERO(pthread_mutex_trylock(lock));
-    TEXPECT_INT(pthread_mutex_trylock(lock),ERRNO_PTHREAD_EDEADLK);
-    TZERO(pthread_mutex_unlock(lock));
-    TZERO(pthread_mutex_destroy(lock));
-}
-
-static void do_test_2(void)
-{
-    pthread_mutexattr_t  attr[1];
-
-    do_test_mutex_2(NULL);
-
-    /* non-shared version */
-
-    TZERO(pthread_mutexattr_init(attr));
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_NORMAL);
-    do_test_mutex_2(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_RECURSIVE);
-    do_test_mutex_2_rec(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_ERRORCHECK);
-    do_test_mutex_2_chk(attr);
-
-    TZERO(pthread_mutexattr_destroy(attr));
-
-    /* shared version */
-    TZERO(pthread_mutexattr_init(attr));
-    TZERO(pthread_mutexattr_setpshared(attr, PTHREAD_PROCESS_SHARED));
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_NORMAL);
-    do_test_mutex_2(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_RECURSIVE);
-    do_test_mutex_2_rec(attr);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_ERRORCHECK);
-    do_test_mutex_2_chk(attr);
-
-    TZERO(pthread_mutexattr_destroy(attr));
-}
-
-/* This is more complex example to test contention of mutexes.
- * Essentially, what happens is this:
- *
- * - main thread creates a mutex and locks it
- * - it then creates thread 1 and thread 2
- *
- * - it then record the current time, sleep for a specific 'waitDelay'
- *   then unlock the mutex.
- *
- * - thread 1 locks() the mutex. It shall be stopped for a least 'waitDelay'
- *   seconds. It then unlocks the mutex.
- *
- * - thread 2 trylocks() the mutex. In case of failure (EBUSY), it waits
- *   for a small amount of time (see 'spinDelay') and tries again, until
- *   it succeeds. It then unlocks the mutex.
- *
- * The goal of this test is to verify that thread 1 has been stopped
- * for a sufficiently long time, and that thread 2 has been spinning for
- * the same minimum period. There is no guarantee as to which thread is
- * going to acquire the mutex first.
- */
-typedef struct {
-    pthread_mutex_t  mutex[1];
-    double           t0;
-    double           waitDelay;
-    double           spinDelay;
-} Test3State;
-
-static void* do_mutex_test_3_t1(void* arg)
-{
-    Test3State *s = arg;
-    double      t1;
-
-    TZERO(pthread_mutex_lock(s->mutex));
-    t1 = time_now();
-    //DEBUG ONLY: printf("t1-s->t0=%g waitDelay=%g\n", t1-s->t0, s->waitDelay);
-    TTRUE((t1-s->t0) >= s->waitDelay); 
-    TZERO(pthread_mutex_unlock(s->mutex));
-    return NULL;
-}
-
-static void* do_mutex_test_3_t2(void* arg)
-{
-    Test3State *s = arg;
-    double      t1;
-
-    for (;;) {
-        int ret = pthread_mutex_trylock(s->mutex);
-        if (ret == 0)
-            break;
-        if (ret == EBUSY) {
-            time_sleep(s->spinDelay);
-            continue;
-        }
-    }
-    t1 = time_now();
-    TTRUE((t1-s->t0) >= s->waitDelay);
-    TZERO(pthread_mutex_unlock(s->mutex));
-    return NULL;
-}
-
-
-static void do_test_mutex_3(pthread_mutexattr_t *attr, double delay)
-{
-    Test3State  s[1];
-    pthread_t   th1, th2;
-    void*       dummy;
-
-    TZERO(pthread_mutex_init(s->mutex, attr));
-    s->waitDelay = delay;
-    s->spinDelay = delay/20.;
-
-    TZERO(pthread_mutex_lock(s->mutex));
-
-    pthread_create(&th1, NULL, do_mutex_test_3_t1, s);
-    pthread_create(&th2, NULL, do_mutex_test_3_t2, s);
-
-    s->t0 = time_now();
-    time_sleep(delay);
-
-    TZERO(pthread_mutex_unlock(s->mutex));
-
-    TZERO(pthread_join(th1, &dummy));
-    TZERO(pthread_join(th2, &dummy));
-}
-
-static void do_test_3(double  delay)
-{
-    pthread_mutexattr_t  attr[1];
-
-    do_test_mutex_3(NULL, delay);
-
-    /* non-shared version */
-
-    TZERO(pthread_mutexattr_init(attr));
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_NORMAL);
-    do_test_mutex_3(attr, delay);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_RECURSIVE);
-    do_test_mutex_3(attr, delay);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_ERRORCHECK);
-    do_test_mutex_3(attr, delay);
-
-    TZERO(pthread_mutexattr_destroy(attr));
-
-    /* shared version */
-    TZERO(pthread_mutexattr_init(attr));
-    TZERO(pthread_mutexattr_setpshared(attr, PTHREAD_PROCESS_SHARED));
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_NORMAL);
-    do_test_mutex_3(attr, delay);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_RECURSIVE);
-    do_test_mutex_3(attr, delay);
-
-    set_mutexattr_type(attr, PTHREAD_MUTEX_ERRORCHECK);
-    do_test_mutex_3(attr, delay);
-
-    TZERO(pthread_mutexattr_destroy(attr));
-}
-
-
-int main(void)
-{
-    do_test_1();
-    do_test_2();
-    do_test_3(0.1);
-    return 0;
-}
diff --git a/tests/bionic/libc/common/test_pthread_rwlock.c b/tests/bionic/libc/common/test_pthread_rwlock.c
deleted file mode 100644 (file)
index 4687e01..0000000
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-#include <pthread.h>
-#include <errno.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-/* Posix states that EDEADLK should be returned in case a deadlock condition
- * is detected with a PTHREAD_MUTEX_ERRORCHECK lock() or trylock(), but
- * GLibc returns EBUSY instead.
- */
-#ifdef HOST
-#  define ERRNO_PTHREAD_EDEADLK   EBUSY
-#else
-#  define ERRNO_PTHREAD_EDEADLK   EDEADLK
-#endif
-
-static void __attribute__((noreturn))
-panic(const char* func, const char* format, ...)
-{
-    va_list  args;
-    fprintf(stderr, "%s: ", func);
-    va_start(args, format);
-    vfprintf(stderr, format, args);
-    va_end(args);
-    fprintf(stderr, "\n");
-    exit(1);
-}
-
-#define  PANIC(...)   panic(__FUNCTION__,__VA_ARGS__)
-
-static void __attribute__((noreturn))
-error(int  errcode, const char* func, const char* format, ...)
-{
-    va_list  args;
-    fprintf(stderr, "%s: ", func);
-    va_start(args, format);
-    vfprintf(stderr, format, args);
-    va_end(args);
-    fprintf(stderr, " error=%d: %s\n", errcode, strerror(errcode));
-    exit(1);
-}
-
-/* return current time in seconds as floating point value */
-static double
-time_now(void)
-{
-    struct timespec ts[1];
-
-    clock_gettime(CLOCK_MONOTONIC, ts);
-    return (double)ts->tv_sec + ts->tv_nsec/1e9;
-}
-
-static void
-time_sleep(double  delay)
-{
-    struct timespec ts;
-    int             ret;
-
-    ts.tv_sec  = (time_t)delay;
-    ts.tv_nsec = (long)((delay - ts.tv_sec)*1e9);
-
-    do {
-        ret = nanosleep(&ts, &ts);
-    } while (ret < 0 && errno == EINTR);
-}
-
-#define  ERROR(errcode,...)   error((errcode),__FUNCTION__,__VA_ARGS__)
-
-#define  TZERO(cond)   \
-    { int _ret = (cond); if (_ret != 0) ERROR(_ret,"%d:%s", __LINE__, #cond); }
-
-#define  TTRUE(cond)   \
-    { if (!(cond)) PANIC("%d:%s", __LINE__, #cond); }
-
-#define  TFALSE(cond)   \
-    { if (!!(cond)) PANIC("%d:%s", __LINE__, #cond); }
-
-#define  TEXPECT_INT(cond,val) \
-    { int _ret = (cond); if (_ret != (val)) PANIC("%d:%s returned %d (%d expected)", __LINE__, #cond, _ret, (val)); }
-
-/* perform a simple init/lock/unlock/destroy test on a rwlock of given attributes */
-static void do_test_rwlock_rd1(pthread_rwlockattr_t *attr)
-{
-    int               ret;
-    pthread_rwlock_t  lock[1];
-
-    TZERO(pthread_rwlock_init(lock, attr));
-    TZERO(pthread_rwlock_rdlock(lock));
-    TZERO(pthread_rwlock_unlock(lock));
-    TZERO(pthread_rwlock_destroy(lock));
-}
-
-static void do_test_rwlock_wr1(pthread_rwlockattr_t *attr)
-{
-    int               ret;
-    pthread_rwlock_t  lock[1];
-
-    TZERO(pthread_rwlock_init(lock, attr));
-    TZERO(pthread_rwlock_wrlock(lock));
-    TZERO(pthread_rwlock_unlock(lock));
-    TZERO(pthread_rwlock_destroy(lock));
-}
-
-static void set_rwlockattr_shared(pthread_rwlockattr_t *attr, int shared)
-{
-    int  newshared;
-    TZERO(pthread_rwlockattr_setpshared(attr, shared));
-    newshared = ~shared;
-    TZERO(pthread_rwlockattr_getpshared(attr, &newshared));
-    TEXPECT_INT(newshared,shared);
-}
-
-/* simple init/lock/unlock/destroy on all rwlock types */
-static void do_test_1(void)
-{
-    int                  ret, type;
-    pthread_rwlockattr_t  attr[1];
-
-    do_test_rwlock_rd1(NULL);
-    do_test_rwlock_wr1(NULL);
-
-    /* non-shared version */
-
-    TZERO(pthread_rwlockattr_init(attr));
-
-    set_rwlockattr_shared(attr, PTHREAD_PROCESS_PRIVATE);
-    do_test_rwlock_rd1(attr);
-    do_test_rwlock_wr1(attr);
-
-    set_rwlockattr_shared(attr, PTHREAD_PROCESS_SHARED);
-    do_test_rwlock_rd1(attr);
-    do_test_rwlock_wr1(attr);
-
-    TZERO(pthread_rwlockattr_destroy(attr));
-}
-
-static void do_test_rwlock_rd2_rec(pthread_rwlockattr_t *attr)
-{
-    pthread_rwlock_t  lock[1];
-
-    TZERO(pthread_rwlock_init(lock, attr));
-    TZERO(pthread_rwlock_tryrdlock(lock));
-    TZERO(pthread_rwlock_unlock(lock));
-    TZERO(pthread_rwlock_destroy(lock));
-
-    TZERO(pthread_rwlock_init(lock, attr));
-    TZERO(pthread_rwlock_tryrdlock(lock));
-    TZERO(pthread_rwlock_tryrdlock(lock));
-    TZERO(pthread_rwlock_unlock(lock));
-    TZERO(pthread_rwlock_unlock(lock));
-    TZERO(pthread_rwlock_destroy(lock));
-}
-
-static void do_test_rwlock_wr2_rec(pthread_rwlockattr_t *attr)
-{
-    pthread_rwlock_t  lock[1];
-
-    TZERO(pthread_rwlock_init(lock, attr));
-    TZERO(pthread_rwlock_trywrlock(lock));
-    TZERO(pthread_rwlock_unlock(lock));
-    TZERO(pthread_rwlock_destroy(lock));
-
-    TZERO(pthread_rwlock_init(lock, attr));
-    TZERO(pthread_rwlock_trywrlock(lock));
-#ifdef HOST
-    /* The host implementation (GLibc) does not support recursive
-     * write locks */
-    TEXPECT_INT(pthread_rwlock_trywrlock(lock),EBUSY);
-#else
-    /* Our implementation supports recursive write locks ! */
-    TZERO(pthread_rwlock_trywrlock(lock));
-    TZERO(pthread_rwlock_unlock(lock));
-#endif
-    TZERO(pthread_rwlock_unlock(lock));
-    TZERO(pthread_rwlock_destroy(lock));
-}
-
-static void do_test_2(void)
-{
-    pthread_rwlockattr_t  attr[1];
-
-    do_test_rwlock_rd2_rec(NULL);
-    do_test_rwlock_wr2_rec(NULL);
-
-    /* non-shared version */
-
-    TZERO(pthread_rwlockattr_init(attr));
-
-    set_rwlockattr_shared(attr, PTHREAD_PROCESS_PRIVATE);
-    do_test_rwlock_rd2_rec(attr);
-    do_test_rwlock_wr2_rec(attr);
-
-    set_rwlockattr_shared(attr, PTHREAD_PROCESS_SHARED);
-    do_test_rwlock_rd2_rec(attr);
-    do_test_rwlock_wr2_rec(attr);
-
-    TZERO(pthread_rwlockattr_destroy(attr));
-}
-
-/* This is more complex example to test contention of rwlockes.
- * Essentially, what happens is this:
- *
- * - main thread creates a rwlock and rdlocks it
- * - it then creates thread 1 and thread 2
- *
- * - it then record the current time, sleep for a specific 'waitDelay'
- *   then unlock the rwlock.
- *
- * - thread 1 tryrdlocks() the rwlock. It shall acquire the lock
- *   immediately, then release it, then wrlock().
- *
- * - thread 2 trywrlocks() the rwlock. In case of failure (EBUSY), it waits
- *   for a small amount of time (see 'spinDelay') and tries again, until
- *   it succeeds. It then unlocks the rwlock.
- *
- * The goal of this test is to verify that thread 1 has been stopped
- * for a sufficiently long time (in the wrlock), and that thread 2 has
- * been spinning for the same minimum period. There is no guarantee as
- * to which thread is going to acquire the rwlock first.
- */
-typedef struct {
-    pthread_rwlock_t  rwlock[1];
-    double            t0;
-    double            waitDelay;
-    double            spinDelay;
-} Test3State;
-
-static void* do_rwlock_test_rd3_t1(void* arg)
-{
-    Test3State *s = arg;
-    double      t1;
-
-    /* try-acquire the lock, should succeed immediately */
-    TZERO(pthread_rwlock_tryrdlock(s->rwlock));
-    TZERO(pthread_rwlock_unlock(s->rwlock));
-
-    /* wrlock() the lock, now */
-    TZERO(pthread_rwlock_wrlock(s->rwlock));
-
-    t1 = time_now();
-    //DEBUG ONLY: printf("t1-s->t0=%g waitDelay=%g\n", t1-s->t0, s->waitDelay);
-    TTRUE((t1-s->t0) >= s->waitDelay); 
-    TZERO(pthread_rwlock_unlock(s->rwlock));
-    return NULL;
-}
-
-static void* do_rwlock_test_rd3_t2(void* arg)
-{
-    Test3State *s = arg;
-    double      t1;
-
-    for (;;) {
-        int ret = pthread_rwlock_trywrlock(s->rwlock);
-        if (ret == 0)
-            break;
-        if (ret == EBUSY) {
-            time_sleep(s->spinDelay);
-            continue;
-        }
-    }
-    t1 = time_now();
-    TTRUE((t1-s->t0) >= s->waitDelay);
-    TZERO(pthread_rwlock_unlock(s->rwlock));
-    return NULL;
-}
-
-
-static void do_test_rwlock_rd3(pthread_rwlockattr_t *attr, double delay)
-{
-    Test3State  s[1];
-    pthread_t   th1, th2;
-    void*       dummy;
-
-    TZERO(pthread_rwlock_init(s->rwlock, attr));
-    s->waitDelay = delay;
-    s->spinDelay = delay/20.;
-
-    TZERO(pthread_rwlock_rdlock(s->rwlock));
-
-    pthread_create(&th1, NULL, do_rwlock_test_rd3_t1, s);
-    pthread_create(&th2, NULL, do_rwlock_test_rd3_t2, s);
-
-    s->t0 = time_now();
-    time_sleep(delay);
-
-    TZERO(pthread_rwlock_unlock(s->rwlock));
-
-    TZERO(pthread_join(th1, &dummy));
-    TZERO(pthread_join(th2, &dummy));
-}
-
-static void do_test_3(double  delay)
-{
-    pthread_rwlockattr_t  attr[1];
-
-    do_test_rwlock_rd3(NULL, delay);
-
-    /* non-shared version */
-
-    TZERO(pthread_rwlockattr_init(attr));
-
-    set_rwlockattr_shared(attr, PTHREAD_PROCESS_PRIVATE);
-    do_test_rwlock_rd3(attr, delay);
-
-    set_rwlockattr_shared(attr, PTHREAD_PROCESS_SHARED);
-    do_test_rwlock_rd3(attr, delay);
-
-    TZERO(pthread_rwlockattr_destroy(attr));
-}
-
-
-int main(void)
-{
-    do_test_1();
-    do_test_2();
-    do_test_3(0.1);
-    return 0;
-}
diff --git a/tests/bionic/libc/other/test_jpeg.c b/tests/bionic/libc/other/test_jpeg.c
deleted file mode 100644 (file)
index f481b9a..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the 
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-/* this small program is used to measure the performance of libjpeg decompression
- * algorithm...
- */
-
-#include <time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include "jpeglib.h"
-#include <setjmp.h>
-#ifdef HAVE_ANDROID_OS
-#include <hardware_legacy/qemu_tracing.h>
-#endif
-
-#define  USE_STDIO
-
-#define  CHUNK    32768
-
-typedef struct {
-    struct jpeg_source_mgr  jpeg_mgr;
-    char*                   base;
-    char*                   cursor;
-    char*                   end;
-} SourceMgrRec, *SourceMgr;
-
-static void
-_source_init_source(j_decompress_ptr cinfo)
-{
-    SourceMgr  src = (SourceMgr) cinfo->src;
-
-    src->jpeg_mgr.next_input_byte = (unsigned char*)src->base,
-    src->jpeg_mgr.bytes_in_buffer = src->end - src->base;
-}
-
-static int
-_source_fill_input_buffer(j_decompress_ptr cinfo)
-{
-    SourceMgr  src = (SourceMgr) cinfo->src;
-
-    cinfo->err->error_exit((j_common_ptr)cinfo);
-    return FALSE;
-}
-
-static void
-_source_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
-{
-    SourceMgr  src = (SourceMgr) cinfo->src;
-
-    if (src->jpeg_mgr.next_input_byte + num_bytes > (unsigned char*)src->end ) {
-        cinfo->err->error_exit((j_common_ptr)cinfo);
-    }
-
-    src->jpeg_mgr.next_input_byte += num_bytes;
-    src->jpeg_mgr.bytes_in_buffer -= num_bytes;
-}
-
-static int
-_source_resync_to_restart( j_decompress_ptr cinfo, int desired)
-{
-    SourceMgr  src = (SourceMgr) cinfo->src;
-
-    src->jpeg_mgr.next_input_byte = (unsigned char*)src->base;
-    src->jpeg_mgr.bytes_in_buffer = src->end - src->base;
-    return TRUE;
-}
-
-static void
-_source_term_source(j_decompress_ptr  cinfo)
-{
-    // nothing to do
-}
-
-static void
-_source_init( SourceMgr  src, char*  base, long  size )
-{
-    src->base   = base;
-    src->cursor = base;
-    src->end    = base + size;
-
-    src->jpeg_mgr.init_source       = _source_init_source;
-    src->jpeg_mgr.fill_input_buffer = _source_fill_input_buffer;
-    src->jpeg_mgr.skip_input_data   = _source_skip_input_data;
-    src->jpeg_mgr.resync_to_restart = _source_resync_to_restart;
-    src->jpeg_mgr.term_source       = _source_term_source;
-}
-
-
-typedef struct {
-    struct jpeg_error_mgr   jpeg_mgr;
-    jmp_buf                 jumper;
-    int volatile            error;
-
-} ErrorMgrRec, *ErrorMgr;
-
-static void _error_exit(j_common_ptr cinfo)
-{
-    ErrorMgr error = (ErrorMgr) cinfo->err;
-
-    (*error->jpeg_mgr.output_message) (cinfo);
-
-    /* Let the memory manager delete any temp files before we die */
-    longjmp(error->jumper, -1);
-}
-
-#ifdef USE_STDIO
-int decompress(FILE*  input_file, int  dct_method, int  disable_rgb)
-#else
-int decompress(char*  data, long  fsize)
-#endif
-{
-    ErrorMgrRec             errmgr;
-    SourceMgrRec            sourcemgr;
-    struct jpeg_decompress_struct  cinfo;
-    int volatile            error = 0;
-    jmp_buf                 jumper;
-    int                     isRGB;
-    char*                   pixels;
-    JSAMPLE*                temprow;
-
-    memset( &cinfo, 0, sizeof(cinfo) );
-    memset( &errmgr, 0, sizeof(errmgr) );
-    jpeg_create_decompress(&cinfo);
-    cinfo.err         = jpeg_std_error(&errmgr.jpeg_mgr);
-#if 0
-    errmgr.jpeg_mgr.error_exit = _error_exit;
-    errmgr.error      = 0;
-#endif
-
-    if (setjmp(errmgr.jumper) != 0) {
-        fprintf(stderr, "returning error from jpeglib ---\n" );
-        goto Exit;
-    }
-
-#ifdef USE_STDIO
-    /* Specify data source for decompression */
-    jpeg_stdio_src(&cinfo, input_file);
-#else
-    _source_init( &sourcemgr, data, fsize );
-    cinfo.src = &sourcemgr.jpeg_mgr;
-#endif
-
-    jpeg_read_header(&cinfo, 1);
-
-    if (3 == cinfo.num_components && JCS_RGB == cinfo.out_color_space)
-        isRGB = 1;
-    else if (1 == cinfo.num_components && JCS_GRAYSCALE == cinfo.out_color_space)
-        isRGB = 0;  // could use Index8 config if we want...
-    else {
-        fprintf( stderr, "unsupported jpeg colorspace %d with %d components\n",
-                  cinfo.jpeg_color_space, cinfo.num_components );
-        goto Exit;
-    }
-
-    cinfo.dct_method = dct_method;
-    if (disable_rgb)
-        cinfo.out_color_space = JCS_YCbCr;
-
-    jpeg_start_decompress(&cinfo);
-
-    temprow = calloc( cinfo.num_components * cinfo.output_width, sizeof(JSAMPLE) );
-
-    {
-        unsigned  y;
-        for (y = 0; y < cinfo.output_height; y++) {
-            JSAMPLE*  rowptr = temprow;
-            (void)jpeg_read_scanlines(&cinfo, &rowptr, 1);
-        }
-    }
-    jpeg_finish_decompress(&cinfo);
-
-    free( temprow );
-Exit:
-    jpeg_destroy_decompress(&cinfo);
-    return error;
-}
-
-
-#define  DEFAULT_REPEAT  10
-
-static void usage(void)
-{
-    fprintf(stderr, "usage: test_jpeg [options] filename.jpg [filename2.jpg ...]\n" );
-    fprintf(stderr, "options:  -r NN   repeat count  (default %d)\n", DEFAULT_REPEAT );
-    fprintf(stderr, "          -d N    idct method   (0=default, 1=fastest, 2=slow, 3=float)\n" );
-    fprintf(stderr, "          -C      no RGB color conversion (YCbCr instead)\n" );
-    exit(1);
-}
-
-static double
-get_time_usec( void )
-{
-#ifdef HAVE_ANDROID_OS
-    struct timespec  ts;
-
-    if ( clock_gettime( CLOCK_MONOTONIC, &ts ) < 0 )
-        fprintf(stderr, "clock_gettime: %s\n", strerror(errno) );
-
-    return ts.tv_sec*1e6 + ts.tv_nsec*1e-3;
-#else
-    struct timeval  tv;
-    if (gettimeofday( &tv, NULL ) < 0)
-        fprintf(stderr, "gettimeofday: %s\n", strerror(errno) );
-
-    return tv.tv_sec*1000000. + tv.tv_usec*1.0;
-#endif
-}
-
-
-int  main( int  argc, char**  argv )
-{
-    FILE*  f;
-    int    repeat_count      = DEFAULT_REPEAT;
-    int    dct_method        = JDCT_DEFAULT;
-    int    disable_rgb       = 0;
-    double  usec0, usec1;
-
-    if (argc < 2)
-        usage();
-
-    for ( ; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
-        const char*  arg = &argv[1][1];
-        switch (arg[0]) {
-            case 'r':
-                if (arg[1] == 0) {
-                    if (argc < 3)
-                        usage();
-                    arg = argv[2];
-                    argc--;
-                    argv++;
-                } else
-                    arg += 1;
-
-                repeat_count = strtol(arg, NULL, 10);
-
-                if (repeat_count <= 0)
-                    repeat_count = 1;
-                break;
-
-            case 'C':
-                disable_rgb = 1;
-                break;
-
-            case 'd':
-                if (arg[1] == 0) {
-                    if (argc < 3)
-                        usage();
-                    arg = argv[2];
-                    argc--;
-                    argv++;
-                } else
-                    arg += 1;
-
-                dct_method = strtol(arg, NULL, 10);
-                switch (dct_method) {
-                    case 0:
-                        dct_method = JDCT_DEFAULT;
-                        break;
-                    case 1:
-                        dct_method = JDCT_IFAST;
-                        break;
-                    case 2:
-                        dct_method = JDCT_ISLOW;
-                        break;
-                    case 3:
-                        dct_method = JDCT_FLOAT;
-                        break;
-                    default:
-                        usage();
-                }
-                break;
-
-            default:
-                usage();
-        }
-    }
-
-    for ( ; argc > 1; argc--, argv++ )
-    {
-        long   fsize;
-        char*   data;
-        FILE*  f = fopen( argv[1], "rb" );
-        int    rr;
-
-        if (f == NULL) {
-            fprintf(stderr, "could not open '%s': %s\n", argv[1], strerror(errno) );
-            continue;
-        }
-
-        fseek( f, 0, SEEK_END );
-        fsize = ftell(f);
-        fseek( f, 0, SEEK_SET );
-
-        usec0 = get_time_usec();
-#ifdef HAVE_ANDROID_OS
-        qemu_start_tracing();
-#endif
-#ifdef USE_STDIO
-        for ( rr = repeat_count; rr > 0; rr-- ) {
-            fseek( f, 0, SEEK_SET );
-            decompress(f, dct_method, disable_rgb);
-        }
-        fclose( f );
-#else
-
-        data = malloc( fsize );
-        if (data == NULL) {
-            if (fsize > 0)
-                fprintf(stderr, "could not allocate %ld bytes to load '%s'\n", fsize, argv[1] );
-            fclose(f);
-            continue;
-        }
-        fread( data, 1, fsize, f );
-        fclose(f);
-
-        usec1 = get_time_usec() - usec0;
-        printf( "compressed load:     %10.2f ms (%ld bytes)\n", usec1*1e-3, fsize );
-
-        usec0 = get_time_usec();
-        for ( rr = repeat_count; rr > 0; rr -- )
-        {
-            decompress( data, fsize );
-        }
-        free( data );
-#endif
-#ifdef HAVE_ANDROID_OS
-        qemu_stop_tracing();
-#endif
-        usec1 = get_time_usec() - usec0;
-        printf( "decompression took:  %10.3f ms (%.2f KB/s, %d passes)\n", usec1/1e3, fsize*(1e6/1024)*repeat_count/usec1, repeat_count );
-    }
-    return 0;
-}
diff --git a/tests/bionic/libc/other/test_zlib.c b/tests/bionic/libc/other/test_zlib.c
deleted file mode 100644 (file)
index 3eae827..0000000
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the 
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-/* this small program is used to measure the performance of zlib's inflate
- * algorithm...
- */
-
-/* most code lifted from the public-domain http://www.zlib.net/zpipe.c */
-
-#include <zlib.h>
-#include <time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <sys/time.h>
-
-#define  CHUNK    32768
-
-int def(FILE *source, FILE *dest, int level)
-{
-    int ret, flush;
-    unsigned have;
-    z_stream strm;
-    unsigned char in[CHUNK];
-    unsigned char out[CHUNK];
-
-    /* allocate deflate state */
-    strm.zalloc = Z_NULL;
-    strm.zfree = Z_NULL;
-    strm.opaque = Z_NULL;
-    ret = deflateInit(&strm, level);
-    if (ret != Z_OK)
-        return ret;
-
-    /* compress until end of file */
-    do {
-        strm.avail_in = fread(in, 1, CHUNK, source);
-        if (ferror(source)) {
-            (void)deflateEnd(&strm);
-            return Z_ERRNO;
-        }
-        flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
-        strm.next_in = in;
-
-        /* run deflate() on input until output buffer not full, finish
-        compression if all of source has been read in */
-        do {
-            strm.avail_out = CHUNK;
-            strm.next_out = out;
-            ret = deflate(&strm, flush);    /* no bad return value */
-            have = CHUNK - strm.avail_out;
-            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
-                (void)deflateEnd(&strm);
-                return Z_ERRNO;
-            }
-        } while (strm.avail_out == 0);
-
-        /* done when last data in file processed */
-    } while (flush != Z_FINISH);
-
-    /* clean up and return */
-    (void)deflateEnd(&strm);
-    return Z_OK;
-}
-
-
-int inf(FILE *source)
-{
-    int ret;
-    unsigned have;
-    z_stream strm;
-    static unsigned char in[CHUNK];
-    static unsigned char out[CHUNK];
-
-    /* allocate inflate state */
-    strm.zalloc   = Z_NULL;
-    strm.zfree    = Z_NULL;
-    strm.opaque   = Z_NULL;
-    strm.avail_in = 0;
-    strm.next_in  = Z_NULL;
-    ret = inflateInit(&strm);
-    if (ret != Z_OK)
-        return ret;
-
-    /* decompress until deflate stream ends or end of file */
-    do {
-        strm.avail_in = fread(in, 1, CHUNK, source);
-        if (ferror(source)) {
-            (void)inflateEnd(&strm);
-            return Z_ERRNO;
-        }
-        if (strm.avail_in == 0)
-            break;
-        strm.next_in = in;
-
-        /* run inflate() on input until output buffer not full */
-        do {
-            strm.avail_out = CHUNK;
-            strm.next_out  = out;
-            ret = inflate(&strm, Z_NO_FLUSH);
-            switch (ret) {
-                case Z_NEED_DICT:
-                    ret = Z_DATA_ERROR;     /* and fall through */
-                case Z_DATA_ERROR:
-                case Z_MEM_ERROR:
-                    (void)inflateEnd(&strm);
-                    return ret;
-            }
-        } while (strm.avail_out == 0);
-
-        /* done when inflate() says it's done */
-    } while (ret != Z_STREAM_END);
-
-    /* clean up and return */
-    (void)inflateEnd(&strm);
-    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
-}
-
-#define  DEFAULT_REPEAT  10
-#define  DEFAULT_LEVEL   9
-
-static void usage(void)
-{
-    fprintf(stderr, "usage: test_zlib [options] filename [filename2 ...]\n" );
-    fprintf(stderr, "options:  -r NN   repeat count  (default %d)\n", DEFAULT_REPEAT );
-    fprintf(stderr, "          -N      set compression level (default %d)\n", DEFAULT_LEVEL );
-    exit(1);
-}
-
-static double
-get_time_usec( void )
-{
-#ifdef HAVE_ANDROID_OS
-    struct timespec  ts;
-
-    if ( clock_gettime( CLOCK_MONOTONIC, &ts ) < 0 )
-        fprintf(stderr, "clock_gettime: %s\n", strerror(errno) );
-
-    return ts.tv_sec*1e6 + ts.tv_nsec*1e-3;
-#else
-    struct timeval  tv;
-    if (gettimeofday( &tv, NULL ) < 0)
-        fprintf(stderr, "gettimeofday: %s\n", strerror(errno) );
-
-    return tv.tv_sec*1000000. + tv.tv_usec*1.0;
-#endif
-}
-
-int  main( int  argc, char**  argv )
-{
-    FILE*  f;
-    char   tempfile[256];
-    int    repeat_count      = DEFAULT_REPEAT;
-    int    compression_level = DEFAULT_LEVEL;
-    double  usec0, usec1;
-
-    if (argc < 2)
-        usage();
-
-    for ( ; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
-        const char*  arg = &argv[1][1];
-        switch (arg[0]) {
-            case 'r':
-                if (arg[1] == 0) {
-                    if (argc < 3)
-                        usage();
-                    arg = argv[2];
-                    argc--;
-                    argv++;
-                } else
-                    arg += 1;
-
-                repeat_count = strtol(arg, NULL, 10);
-
-                if (repeat_count <= 0)
-                    repeat_count = 1;
-                break;
-
-            case '0': case '1': case '2': case '3': case '4':
-            case '5': case '6': case '7': case '8': case '9':
-                compression_level = arg[0] - '0';
-                break;
-
-            default:
-                usage();
-        }
-    }
-
-    sprintf(tempfile, "/tmp/ztest.%d", getpid() );
-
-    for ( ; argc > 1; argc--, argv++ )
-    {
-        /* first, compress the file into a temporary storage */
-        FILE*  f   = fopen(argv[1], "rb");
-        FILE*  out = NULL;
-        long   fsize;
-        int    ret, rr;
-
-        if (f == NULL) {
-            fprintf(stderr, "could not open '%s': %s\n", argv[1], strerror(errno) );
-            continue;
-        }
-
-        printf( "testing %s\n", argv[1] );
-        fseek( f, 0, SEEK_END );
-        fsize = ftell(f);
-        fseek( f, 0, SEEK_SET );
-
-        out = fopen( tempfile, "wb" );
-        if (out == NULL) {
-            fprintf(stderr, "could not create '%s': %s\n", tempfile, strerror(errno));
-            fclose(f);
-            continue;
-        }
-
-        usec0 = get_time_usec();
-
-        ret = def( f, out, compression_level );
-
-        usec1 = get_time_usec() - usec0;
-        printf( "compression took:   %10.3f ms  (%.2f KB/s)\n", usec1/1e3, fsize*(1e6/1024)/usec1 );
-
-        fclose( out );
-        fclose(f);
-
-        usec0 = get_time_usec();
-        f    = fopen( tempfile, "rb" );
-
-        for ( rr = repeat_count; rr > 0; rr -- )
-        {
-            fseek( f, 0, SEEK_SET );
-            inf(f);
-        }
-        fclose( f );
-        usec1 = get_time_usec() - usec0;
-        printf( "decompression took: %10.3f ms (%.2f KB/s, %d passes)\n", usec1/1e3, fsize*(1e6/1024)*repeat_count/usec1, repeat_count );
-    }
-
-    unlink(tempfile);
-    return 0;
-}
diff --git a/tests/bionic/libc/run-test.sh b/tests/bionic/libc/run-test.sh
deleted file mode 100755 (executable)
index c88c6fe..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/bin/sh
-#
-# 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.
-#
-#  This shell script is used to run one test on a device emulator.
-#
-
-PROGDIR=`dirname $0`
-
-#
-# Parse options
-#
-VERBOSE=no
-VERBOSE2=no
-ADB_CMD=adb
-
-while [ -n "$1" ]; do
-    opt="$1"
-    optarg=`expr "x$opt" : 'x[^=]*=\(.*\)'`
-    case "$opt" in
-        --help|-h|-\?)
-            OPTION_HELP=yes
-            ;;
-        --verbose)
-            if [ "$VERBOSE" = "yes" ] ; then
-                VERBOSE2=yes
-            else
-                VERBOSE=yes
-            fi
-            ;;
-        --adb=*)
-            ADB_CMD="$optarg"
-            ;;
-        -*) # unknown options
-            echo "ERROR: Unknown option '$opt', use --help for list of valid ones."
-            exit 1
-        ;;
-        *)  # Simply record parameter
-            if [ -z "$PARAMETERS" ] ; then
-                PARAMETERS="$opt"
-            else
-                PARAMETERS="$PARAMETERS $opt"
-            fi
-            ;;
-    esac
-    shift
-done
-
-if [ "$OPTION_HELP" = "yes" ] ; then
-    echo "Usage: $PROGNAME [options] <test-name>"
-    echo ""
-    echo "Run one C library test on a device/emulator through ADB."
-    echo ""
-    echo "Valid options:"
-    echo ""
-    echo "    --help|-h|-?      Print this help"
-    echo "    --verbose         Enable verbose mode"
-    echo "    --adb=<file>      Specify adb executable for device tests"
-    echo ""
-    exit 0
-fi
-
-if [ -z "$ANDROID_PRODUCT_OUT" ] ; then
-    echo "ERROR: ANDROID_PRODUCT_OUT not defined. Please run the 'lunch' command"
-    exit 1
-fi
-
-if [ ! -f "$ANDROID_PRODUCT_OUT/system.img" ] ; then
-    echo "ERROR: Missing file: $ANDROID_PRODUCT_OUT/system.img"
-    echo "Are you sure you built the proper system image?"
-    exit 1
-fi
-
-EXEC_ROOT_PATH="$ANDROID_PRODUCT_OUT/obj/EXECUTABLES"
-if [ ! -d "$EXEC_ROOT_PATH" ] ; then
-    echo "ERROR: Missing directory: $EXEC_ROOT_PATH"
-    echo "Are you sure you built the proper system image?"
-    exit 1
-fi
-
-if [ -z "$PARAMETERS" ] ; then
-    echo "ERROR: Please specify test name."
-    echo "Must be one of the following:"
-    for FILE in `cd $EXEC_ROOT_PATH && ls -d test_*`; do
-        TEST=`echo "$FILE" | sed -e "s!test_\(.*\)_intermediates!\\1!g"`
-        echo "  $TEST"
-    done
-    exit 1
-fi
-
-TEST="$PARAMETERS"
-# Normalize test name, i.e. remove test_ prefix
-TEST=`echo "$TEST" | sed -e "s!^test_!!g"`
-
-TESTDIR="$EXEC_ROOT_PATH/test_${TEST}_intermediates"
-if [ ! -d "$TESTDIR" ] ; then
-    echo "ERROR: No test by that name: test_$TEST!"
-    exit 1
-fi
-
-TESTNAME="test_$TEST"
-TESTEXE="$TESTDIR/$TESTNAME"
-if [ ! -f "$TESTEXE" ] ; then
-    echo "ERROR: Missing file: $TESTEXE"
-    echo "Are you sure your last test build was complete?"
-    exit 1
-fi
-
-# Run a command in ADB and return 0 in case of success, or 1 otherwise.
-# This is needed because "adb shell" does not return the proper status
-# of the launched command.
-#
-# NOTE: You must call set_adb_cmd_log before that to set the location
-#        of the temporary log file that will be used.
-#
-adb_cmd ()
-{
-    local RET
-    if [ -z "$ADB_CMD_LOG" ] ; then
-        dump "INTERNAL ERROR: ADB_CMD_LOG not set!"
-        exit 1
-    fi
-    if [ $VERBOSE = "yes" ] ; then
-        echo "$ADB_CMD shell $@"
-        $ADB_CMD shell $@ "&&" echo OK "||" echo KO | tee $ADB_CMD_LOG
-    else
-        $ADB_CMD shell $@ "&&" echo OK "||" echo KO > $ADB_CMD_LOG
-    fi
-    # Get last line in log, should be OK or KO
-    RET=`tail -n1 $ADB_CMD_LOG`
-    # Get rid of \r at the end of lines
-    RET=`echo "$RET" | sed -e 's![[:cntrl:]]!!g'`
-    [ "$RET" = "OK" ]
-}
-
-set_adb_cmd_log ()
-{
-    ADB_CMD_LOG="$1"
-}
-
-# Returns 0 if a variable containing one or more items separated
-# by spaces contains a given value.
-# $1: variable name (e.g. FOO)
-# $2: value to test
-var_list_contains ()
-{
-    echo `var_value $1` | tr ' ' '\n' | fgrep -q -e "$2"
-}
-
-TMPDIR=/tmp/bionic-tests
-mkdir -p $TMPDIR
-set_adb_cmd_log $TMPDIR/adb.log.txt
-
-DEVICE_TEST_DIR=/data/local/bionic-test
-DEVICE_TEST=$DEVICE_TEST_DIR/$TESTNAME
-adb_cmd mkdir $DEVICE_TEST_DIR
-$ADB_CMD push $TESTEXE $DEVICE_TEST_DIR/
-if [ $? != 0 ] ; then
-    echo "ERROR: Can't push test to device!"
-    exit 1
-fi
-
-adb_cmd chmod 0755 $DEVICE_TEST &&
-adb_cmd $DEVICE_TEST
-RET=$?
-adb_cmd rm -r $DEVICE_TEST_DIR
-
-if [ "$RET" != 0 ] ; then
-    echo "FAIL!"
-else
-    echo "OK!"
-fi
-exit 0
index 733c67a..0bb08d9 100644 (file)
@@ -20,6 +20,7 @@ LOCAL_MODULE := cpueater
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := eng
 LOCAL_SRC_FILES := cpueater.c
+LOCAL_CFLAGS := -Wno-unused-parameter
 include $(BUILD_EXECUTABLE)
 
 include $(CLEAR_VARS)
index 737e000..e118e5d 100644 (file)
@@ -7,5 +7,5 @@ include $(CLEAR_VARS)
 LOCAL_SRC_FILES:= get_dm_versions.c
 LOCAL_MODULE:= get_dm_versions
 LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS :=
+LOCAL_CFLAGS := -Wno-unused-parameter
 include $(BUILD_EXECUTABLE)
index 75a9f75..7f4c712 100644 (file)
@@ -9,7 +9,7 @@ LOCAL_SHARED_LIBRARIES := \
 
 LOCAL_MODULE:= test-fb-refresh
 
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wno-unused-parameter
 
 include $(BUILD_EXECUTABLE)
 
@@ -18,7 +18,7 @@ include $(BUILD_EXECUTABLE)
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := fb_test.c
 LOCAL_MODULE = test-fb-simple
-LOCAL_MODULE_TAGS := optional
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_STATIC_LIBRARIES := libc
+LOCAL_CFLAGS := -Wno-unused-parameter
 include $(BUILD_EXECUTABLE)
index 4121a87..b93de83 100644 (file)
@@ -39,8 +39,8 @@
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
 #define FSTAB_PREFIX "/fstab."
 #define SB_OFFSET 1024
-#define UMOUNT_BIN "/system/bin/umount"
-#define VDC_BIN "/system/bin/vdc"
+static char UMOUNT_BIN[] = "/system/bin/umount";
+static char VDC_BIN[] = "/system/bin/vdc";
 
 enum Fs_Type { FS_UNKNOWN, FS_EXT4, FS_F2FS };
 
@@ -216,24 +216,29 @@ class FsRecoveryTest : public ::testing::Test {
   }
 
   bool unmountCache() {
+    char cache_str[] = "/cache";
     char *umount_argv[] = {
       UMOUNT_BIN,
-      "/cache"
+      cache_str,
     };
     int status;
     return android_fork_execvp_ext(ARRAY_SIZE(umount_argv), umount_argv,
-                                   NULL, true, LOG_KLOG, false, NULL) >= 0;
+                                   NULL, true, LOG_KLOG, false, NULL,
+                                   NULL, 0) >= 0;
   }
 
   bool mountAll() {
+    char storage_str[] = "storage";
+    char mountall_str[] = "mountall";
     char *mountall_argv[] = {
       VDC_BIN,
-      "storage",
-      "mountall"
+      storage_str,
+      mountall_str,
     };
     int status;
     return android_fork_execvp_ext(ARRAY_SIZE(mountall_argv), mountall_argv,
-                                   NULL, true, LOG_KLOG, false, NULL) >= 0;
+                                   NULL, true, LOG_KLOG, false, NULL,
+                                   NULL, 0) >= 0;
   }
 
   int getCacheBlkFd() {
index a661678..b92b662 100644 (file)
@@ -23,5 +23,6 @@ LOCAL_SRC_FILES := socketTag.cpp
 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/kernel.config/Android.mk b/tests/kernel.config/Android.mk
new file mode 100644 (file)
index 0000000..fc90f66
--- /dev/null
@@ -0,0 +1,79 @@
+# Copyright 2016 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+
+# -----------------------------------------------------------------------------
+# Unit tests.
+# -----------------------------------------------------------------------------
+
+test_c_flags := \
+    -fstack-protector-all \
+    -g \
+    -Wall -Wextra \
+    -Werror \
+    -fno-builtin \
+    -std=gnu++11
+
+# Required Tests
+cts_src_files := \
+    aslr_test.cpp \
+    multicast_test.cpp \
+    pstore_test.cpp \
+    sysvipc_test.cpp \
+    logger_test.cpp
+
+# Required plus Recommended Tests
+test_src_files := \
+    $(cts_src_files) \
+    aslr_rec_test.cpp \
+    mmc_max_speed_test.cpp \
+
+cts_executable := CtsKernelConfigTestCases
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := kernel-config-unit-tests
+LOCAL_MODULE_TAGS := tests
+LOCAL_CFLAGS := $(test_c_flags)
+LOCAL_CFLAGS := -DHAS_KCMP
+LOCAL_SRC_FILES := $(test_src_files)
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(cts_executable)
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := $(test_c_flags)
+LOCAL_CFLAGS := -DHAS_KCMP
+LOCAL_SRC_FILES := $(cts_src_files)
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+LOCAL_STATIC_LIBRARIES := libgtest libgtest_main
+
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+LOCAL_CTS_TEST_PACKAGE := android.kernel.config
+include $(BUILD_CTS_EXECUTABLE)
+
+ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64))
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(cts_executable)_list
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := $(test_c_flags)
+LOCAL_C_INCLUDES := external/gtest/include
+LOCAL_SRC_FILES := $(cts_src_files)
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+LOCAL_CXX_STL := libc++
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_NATIVE_TEST)
+
+endif  # ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64))
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    scrape_mmap_addr.cpp
+
+LOCAL_MODULE := scrape_mmap_addr
+include $(BUILD_NATIVE_TEST)
diff --git a/tests/kernel.config/AndroidTest.xml b/tests/kernel.config/AndroidTest.xml
new file mode 100644 (file)
index 0000000..4fe3192
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Kernel Config test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsKernelConfigTestCases->/data/local/tmp/CtsKernelConfigTestCases" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="CtsKernelConfigTestCases" />
+    </test>
+</configuration>
diff --git a/tests/kernel.config/aslr_rec_test.cpp b/tests/kernel.config/aslr_rec_test.cpp
new file mode 100644 (file)
index 0000000..e154424
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aslr_test.h"
+
+/* run tests if on supported arch */
+#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) || defined(__arm__)
+
+/* make sure the default entropy values matches what we expect */
+TEST_F(AslrMmapTest, match_default) {
+    if (user32) {
+        // running 32-bit userspace on 64-bit kernel, only compat used.
+        return;
+    } else {
+        EXPECT_EQ(def, get_mmap_rnd_bits(false));
+    }
+}
+
+/* make sure the default compat entropy values matches what we expect */
+TEST_F(AslrMmapTest, match_compat_default) {
+    if (compat || user32)
+        EXPECT_EQ(def_cmpt, get_mmap_rnd_bits(true));
+}
+
+/* make sure we can't set entropy below a minimum threshold */
+TEST_F(AslrMmapTest, match_min) {
+    if (user32) {
+        // running 32-bit userspace on 64-bit kernel, only compat used.
+        return;
+    } else {
+        EXPECT_FALSE(set_mmap_rnd_bits(min - 1, false));
+        EXPECT_TRUE(set_mmap_rnd_bits(min, false));
+        EXPECT_EQ(min, get_mmap_rnd_bits(false));
+    }
+}
+
+/* make sure we can't set compat entropy below a minimum threshold */
+TEST_F(AslrMmapTest, match_compat_min) {
+    if (compat || user32) {
+        EXPECT_FALSE(set_mmap_rnd_bits(min_cmpt - 1, true));
+        EXPECT_TRUE(set_mmap_rnd_bits(min_cmpt, true));
+        EXPECT_EQ(min_cmpt, get_mmap_rnd_bits(true));
+    }
+}
+
+/* make sure we can't set entropy above a maximum threshold */
+TEST_F(AslrMmapTest, match_max) {
+    if (user32) {
+        // running 32-bit userspace on 64-bit kernel, only compat used.
+        return;
+    } else {
+        EXPECT_FALSE(set_mmap_rnd_bits(max + 1, false));
+        EXPECT_TRUE(set_mmap_rnd_bits(max, false));
+        EXPECT_EQ(max, get_mmap_rnd_bits(false));
+    }
+}
+
+/* make sure we can't set compat entropy above a maximum threshold */
+TEST_F(AslrMmapTest, match_compat_max) {
+    if (compat || user32) {
+        EXPECT_FALSE(set_mmap_rnd_bits(max_cmpt + 1, true));
+        EXPECT_TRUE(set_mmap_rnd_bits(max_cmpt, true));
+        EXPECT_EQ(max_cmpt, get_mmap_rnd_bits(true));
+    }
+}
+
+/* make sure observed entropy is what we expect when we set min value */
+TEST_F(AslrMmapTest, entropy_min) {
+    if (user32) {
+        // running 32-bit userspace on 64-bit kernel, only compat used.
+        return;
+    } else {
+        EXPECT_TRUE(set_mmap_rnd_bits(min, false));
+        EXPECT_EQ(min, calc_mmap_entropy(path, lib, 16));
+    }
+}
+
+/* make sure observed compat entropy is what we expect when we set min value */
+TEST_F(AslrMmapTest, entropy_cmpt_min) {
+    if (compat || user32) {
+        EXPECT_TRUE(set_mmap_rnd_bits(min_cmpt, true));
+        EXPECT_EQ(min_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16));
+    }
+}
+
+/* make sure observed entropy is what we expect when we set max value */
+TEST_F(AslrMmapTest, entropy_max) {
+    if (user32) {
+        // running 32-bit userspace on 64-bit kernel, only compat used.
+        return;
+    } else {
+        EXPECT_TRUE(set_mmap_rnd_bits(max, false));
+        EXPECT_EQ(max, calc_mmap_entropy(path, lib, 16));
+    }
+}
+
+/* make sure observed compat entropy is what we expect when we set max value */
+TEST_F(AslrMmapTest, entropy_cmpt_max) {
+    if (compat || user32) {
+        EXPECT_TRUE(set_mmap_rnd_bits(max_cmpt, true));
+        EXPECT_EQ(max_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16));
+    }
+}
+
+/* make sure observed entropy is what we expect for default value */
+TEST_F(AslrMmapTest, entropy_def) {
+    if (user32) {
+        // running 32-bit userspace on 64-bit kernel, only compat used.
+        return;
+    } else {
+        EXPECT_EQ(def, calc_mmap_entropy(path, lib, 16));
+    }
+}
+
+/* make sure observed entropy is what we expect for default compat value */
+TEST_F(AslrMmapTest, entropy_cmpt_def) {
+    if (compat || user32) {
+        EXPECT_EQ(def_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16));
+    }
+}
+
+#endif /* supported arch */
diff --git a/tests/kernel.config/aslr_test.cpp b/tests/kernel.config/aslr_test.cpp
new file mode 100644 (file)
index 0000000..ef47f4f
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aslr_test.h"
+
+unsigned int get_mmap_rnd_bits(bool compat) {
+    std::string path;
+
+    if (compat)
+        path = PROCFS_COMPAT_PATH;
+    else
+        path = PROCFS_PATH;
+
+    std::ifstream bi_file(path);
+    if (!bi_file)
+        return false;
+    std::string str_rec;
+    bi_file >> str_rec;
+
+    return stoi(str_rec);
+}
+
+bool set_mmap_rnd_bits(unsigned int new_val, bool compat) {
+    std::string path;
+
+    if (compat)
+        path = "/proc/sys/vm/mmap_rnd_compat_bits";
+    else
+        path = "/proc/sys/vm/mmap_rnd_bits";
+
+    std::ofstream bo_file(path, std::ios::out);
+    if (!bo_file)
+        return false;
+
+    std::string str_val = std::to_string(new_val);
+    bo_file << str_val << std::flush;
+    bo_file.close();
+
+    // check to make sure it was recorded
+    std::ifstream bi_file(path);
+    if (!bi_file)
+        return false;
+    std::string str_rec;
+    bi_file >> str_rec;
+    bi_file.close();
+    if (str_val.compare(str_rec) != 0)
+        return false;
+    return true;
+}
+
+std::string scrape_addr(const char *exec_name, const char *lib_match) {
+    pid_t pid;
+    int fd[2];
+    char buff[MAX_ADDR_LEN];
+    int len, status;
+    if(pipe(fd)) {
+        std::cerr << "Error creating pipe:" << strerror(errno) << "\n";
+        return std::string();
+    }
+
+    if ((pid = fork()) < 0) {
+        std::cerr << "Error creating new process: " << strerror(errno) << "\n";
+        close(fd[0]);
+        close(fd[1]);
+        return std::string();
+    } else if (pid > 0) {
+        // parent
+        close(fd[1]);
+        wait(&status);
+        if (status == -1) {
+            std::cerr << "Unable to find starting address of mmapp'd libc. Aborting.\n";
+            close(fd[0]);
+            return std::string();
+        }
+        len = read(fd[0], buff, MAX_ADDR_LEN - 1);
+        if (len < 0) {
+            std::cerr << "Error reading pipe from child: " << strerror(errno) << "\n";
+            close(fd[0]);
+            return std::string();
+        }
+        buff[len] = '\0';
+        close(fd[0]);
+    } else {
+        // child, dup 'n' exec
+        close(fd[0]);
+        if(dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
+            std::cerr << "Error dup'n pipe to STDOUT of child: " << strerror(errno) << "\n";
+            close(fd[1]);
+            return std::string();
+        }
+        if(execlp(exec_name, exec_name, lib_match, (char *) NULL)) {
+            std::cerr << "Error exec'ing mmap_scraper: " << strerror(errno) << "\n";
+            close(fd[1]);
+            return std::string();
+        }
+    }
+    return std::string(buff, strlen(buff));
+}
+
+unsigned int calc_mmap_entropy(const char *exec_name, const char *lib_match, size_t samp_sz) {
+    uint64_t min_addr = 0, max_addr = 0;
+
+    std::unordered_set<uint64_t> addrs = { };
+
+    // get our first value
+    uint64_t addr = min_addr = max_addr = std::stoll(scrape_addr(exec_name, lib_match), 0, 16);
+    addrs.insert(addr);
+    for (unsigned int i = 0; i < samp_sz - 1; ++i) {
+        std::string addr_str = scrape_addr(exec_name, lib_match);
+        if (addr_str.empty())
+            return 0;
+        addr = std::stoll(addr_str, 0, 16);
+        if (addr < min_addr)
+            min_addr = addr;
+        if (addr >= max_addr)
+            max_addr = addr;
+        addrs.insert(addr);
+    }
+    if (addrs.size() < (samp_sz >> 1)) {
+        std::cerr << "> 50% collisions in mmap addresses, entropy appears to be rigged!";
+        return 0;
+    }
+    unsigned int e_bits = (int) (std::ceil(std::log2(max_addr - min_addr)) - std::log2(getpagesize()));
+    return e_bits;
+}
+
+const char *AslrMmapTest::path;
+const char *AslrMmapTest::lib;
+unsigned int AslrMmapTest::def, AslrMmapTest::min, AslrMmapTest::max;
+bool AslrMmapTest::compat = false, AslrMmapTest::user32 = false;
+unsigned int AslrMmapTest::def_cmpt, AslrMmapTest::min_cmpt, AslrMmapTest::max_cmpt;
+
+void AslrMmapTest::SetUpTestCase() {
+    /* set up per-arch values */
+#if defined(__x86_64__)
+    def = 32;
+    min = 28;
+    max = 32;
+    path = SCRAPE_PATH_64;
+    lib = SCRAPE_LIB_64;
+
+    compat = true;
+    def_cmpt = 16;
+    min_cmpt = 8;
+    max_cmpt = 16;
+
+#elif defined(__i386__)
+    def = 16;
+    min = 8;
+    max = 16;
+    path = SCRAPE_PATH_32;
+    lib = SCRAPE_LIB_32;
+
+    if (!access(PROCFS_COMPAT_PATH, F_OK)) {
+        // running 32 bit userspace over 64-bit kernel
+        user32 = true;
+        def_cmpt = 16;
+        min_cmpt = 8;
+        max_cmpt = 16;
+    }
+
+#elif defined(__aarch64__)
+    unsigned int pgbits = std::log2(getpagesize());
+    def = 24;
+    min = 18 - (pgbits - 12);
+    max = 24;
+    path = SCRAPE_PATH_64;
+    lib = SCRAPE_LIB_64;
+
+    compat = true;
+    def_cmpt = 16;
+    min_cmpt = 11 - (pgbits - 12);
+    max_cmpt = 16;
+
+#elif defined(__arm__)
+    unsigned int pgbits = std::log2(getpagesize());
+    def = 16;
+    min = 8;
+    max = 16;
+    path = SCRAPE_PATH_32;
+    lib = SCRAPE_LIB_32;
+
+    if (!access(PROCFS_COMPAT_PATH, F_OK)) {
+        // running 32 bit userspace over 64-bit kernel
+        user32 = true;
+        def_cmpt = 16;
+        min_cmpt = 11 - (pgbits - 12);;
+        max_cmpt = 16;
+    }
+#endif
+}
+
+void AslrMmapTest::TearDown() {
+    if (!user32)
+        set_mmap_rnd_bits(def, false);
+    if (user32 || compat)
+        set_mmap_rnd_bits(def_cmpt, true);
+}
+
+/* run tests only if on supported arch */
+#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) || defined(__arm__)
+
+TEST_F(AslrMmapTest, entropy_min_def) {
+    if (user32) {
+        // running 32-bit userspace on 64-bit kernel, only compat used.
+        return;
+    } else {
+        EXPECT_GE(def, calc_mmap_entropy(path, lib, 16));
+    }
+}
+
+TEST_F(AslrMmapTest, entropy_min_cmpt_def) {
+    if (compat || user32) {
+        EXPECT_GE(def_cmpt, calc_mmap_entropy(SCRAPE_PATH_32, SCRAPE_LIB_32, 16));
+    }
+}
+
+#endif /* supported arch */
diff --git a/tests/kernel.config/aslr_test.h b/tests/kernel.config/aslr_test.h
new file mode 100644 (file)
index 0000000..355a62a
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ASLR_TEST_H
+#define ASLR_TEST_H
+
+#include <cmath>
+#include <errno.h>
+#include <fstream>
+#include <iostream>
+#include <stdint.h>
+#include <string>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <unordered_set>
+
+#include <gtest/gtest.h>
+
+#define MAX_ADDR_LEN 256
+
+#define PROCFS_PATH "/proc/sys/vm/mmap_rnd_bits"
+#define PROCFS_COMPAT_PATH "/proc/sys/vm/mmap_rnd_compat_bits"
+
+#define SCRAPE_PATH_64 "/data/nativetest64/scrape_mmap_addr/scrape_mmap_addr"
+#define SCRAPE_PATH_32 "/data/nativetest/scrape_mmap_addr/scrape_mmap_addr"
+#define SCRAPE_LIB_64 "/system/bin/linker64"
+#define SCRAPE_LIB_32 "/system/bin/linker"
+
+class AslrMmapTest : public ::testing::Test {
+  protected:
+    static void SetUpTestCase();
+    static const char *path;
+    static const char *lib;
+    static unsigned int def, min, max;
+    static bool compat, user32;
+    static unsigned int def_cmpt, min_cmpt, max_cmpt;
+
+    void TearDown();
+};
+
+/*
+ * gets the current mmap_rnd_bits value. requires root.
+ */
+unsigned int get_mmap_rnd_bits(bool compat);
+
+/*
+ * sets the corresponding mmap_rnd_bits variable, returns false if couldn't
+ * change. requires root.
+ */
+bool set_mmap_rnd_bits(unsigned int new_val, bool compat);
+
+/*
+ * scrape_addr - get the raw starting address from /proc/child_pid/mmaps
+ */
+std::string scrape_addr(const char *exec_name, const char *lib_match);
+
+/*
+ * forks off sample_size processes and records the starting address of the
+ * indicated library as reported by exec_name.  Reports entropy observed among
+ * recorded samples.
+ */
+unsigned int calc_mmap_entropy(const char *exec_name, const char *lib_match, size_t samp_sz);
+
+#endif //ASLR_TEST_H
diff --git a/tests/kernel.config/logger_test.cpp b/tests/kernel.config/logger_test.cpp
new file mode 100644 (file)
index 0000000..f0874d8
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+TEST(kernel_config, NOT_CONFIG_ANDROID_LOGGER) {
+  EXPECT_NE(0, access("/dev/log/main", F_OK));
+  EXPECT_NE(0, access("/dev/log_main", F_OK));
+  EXPECT_NE(0, access("/dev/log/radio", F_OK));
+  EXPECT_NE(0, access("/dev/log_radio", F_OK));
+  EXPECT_NE(0, access("/dev/log/events", F_OK));
+  EXPECT_NE(0, access("/dev/log_events", F_OK));
+  EXPECT_NE(0, access("/dev/log/system", F_OK));
+  EXPECT_NE(0, access("/dev/log_system", F_OK));
+}
diff --git a/tests/kernel.config/mmc_max_speed_test.cpp b/tests/kernel.config/mmc_max_speed_test.cpp
new file mode 100644 (file)
index 0000000..40cf0d0
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+TEST(kernel_config, CONFIG_MMC_BLOCK_MAX_SPEED) {
+  EXPECT_EQ(0, access("/sys/block/mmcblk0/max_read_speed", F_OK));
+  EXPECT_EQ(0, access("/sys/block/mmcblk0/max_write_speed", F_OK));
+  EXPECT_EQ(0, access("/sys/block/mmcblk0/cache_size", F_OK));
+}
diff --git a/tests/kernel.config/multicast_test.cpp b/tests/kernel.config/multicast_test.cpp
new file mode 100644 (file)
index 0000000..28655e8
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+TEST(kernel_config, CONFIG_IPV6) {
+  EXPECT_EQ(0, access("/proc/net/igmp6", F_OK));
+}
+
+TEST(kernel_config, CONFIG_IP_MULTICAST) {
+  EXPECT_EQ(0, access("/proc/net/igmp", F_OK));
+}
diff --git a/tests/kernel.config/pstore_test.cpp b/tests/kernel.config/pstore_test.cpp
new file mode 100644 (file)
index 0000000..1dd5e72
--- /dev/null
@@ -0,0 +1,32 @@
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+TEST(kernel_config, CONFIG_PSTORE) {
+  EXPECT_EQ(0, access("/sys/fs/pstore", F_OK));
+}
+
+TEST(kernel_config, CONFIG_PSTORE_CONSOLE) {
+  EXPECT_EQ(0, access("/sys/fs/pstore/console-ramoops", F_OK));
+}
+
+TEST(kernel_config, CONFIG_PSTORE_PMSG) {
+  EXPECT_EQ(0, access("/dev/pmsg0", F_OK));
+  EXPECT_EQ(0, access("/sys/fs/pstore/pmsg-ramoops-0", F_OK));
+}
diff --git a/tests/kernel.config/scrape_mmap_addr.cpp b/tests/kernel.config/scrape_mmap_addr.cpp
new file mode 100644 (file)
index 0000000..be5995f
--- /dev/null
@@ -0,0 +1,32 @@
+#include <errno.h>
+#include <fstream>
+#include <iostream>
+#include <regex>
+#include <string>
+#include <string.h>
+
+int main(int argc, char * argv[]) {
+    if (argc != 2) {
+        std::cerr << "usage: " << argv[0] << ": libname\n";
+        return -1;
+    }
+    std::regex reg(std::string("^([a-f0-9]+)\\-[0-9a-f]+\\s+.+\\s+(\\d+)\\s+.+\\s+\\d+\\s+") + std::string(argv[1]) + std::string("\\s*$"));
+
+    /* open /proc/self/maps */
+    std::string ln;
+    std::ifstream m_file("/proc/self/maps");
+    if (!m_file) {
+        std::cerr << "Unable to open /proc/self/maps " << strerror(errno) << "\n";
+        return -1;
+    }
+    while (getline(m_file, ln)) {
+        std::smatch sm;
+        if (std::regex_match (ln,sm, reg)) {
+            if (std::stoi(sm[2]) == 0) {
+                std::cout << sm[1];
+                return 0;
+            }
+        }
+    }
+    return -1;
+}
diff --git a/tests/kernel.config/sysvipc_test.cpp b/tests/kernel.config/sysvipc_test.cpp
new file mode 100644 (file)
index 0000000..49952f0
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <errno.h>
+#ifdef HAS_KCMP
+#include <linux/kcmp.h>
+#include <sys/syscall.h>
+#endif
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#ifdef HAS_KCMP
+int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) {
+  return syscall(SYS_kcmp, pid1, pid2, type, 0, idx1, idx2);
+}
+#endif
+
+TEST(kernel_config, NOT_CONFIG_SYSVIPC) {
+#ifdef HAS_KCMP
+  pid_t pid = getpid();
+  int ret = kcmp(pid, pid, KCMP_SYSVSEM, 0, 0);
+  int error = (ret == -1) ? (errno == ENOSYS) ? EOPNOTSUPP : errno : 0;
+  EXPECT_EQ(-1, kcmp(pid, pid, KCMP_SYSVSEM, 0, 0));
+  EXPECT_EQ(EOPNOTSUPP, error);
+#endif
+  EXPECT_EQ(-1, access("/proc/sysvipc", F_OK));
+  EXPECT_EQ(-1, access("/proc/sysvipc/msg", F_OK));
+  EXPECT_EQ(-1, access("/proc/sysvipc/sem", F_OK));
+  EXPECT_EQ(-1, access("/proc/sysvipc/shm", F_OK));
+}
+
index 771a22e..90d6d83 100644 (file)
@@ -1,18 +1,26 @@
 # Copyright 2006 The Android Open Source Project
-ifeq ($(TARGET_ARCH),arm)
+ifneq ($(filter $(TARGET_ARCH),arm arm64),)
+
 LOCAL_PATH:= $(call my-dir)
+
 include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
-LOCAL_SRC_FILES:= \
-               memtest.cpp.arm \
-               fptest.cpp \
-               thumb.cpp \
-               bandwidth.cpp \
+LOCAL_SRC_FILES := \
+    memtest.cpp \
+    fptest.cpp \
+    thumb.cpp \
+    bandwidth.cpp \
+
+LOCAL_MODULE := memtest
+LOCAL_MODULE_TAGS := debug
+LOCAL_CFLAGS += \
+    -fomit-frame-pointer \
+    -Wall \
+    -Werror \
+
+LOCAL_MULTILIB := 32
 
-LOCAL_MODULE:= memtest
-LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS += -fomit-frame-pointer
+LOCAL_SANITIZE := never
 
 include $(BUILD_EXECUTABLE)
 endif
index 7dab4ba..5ff6756 100644 (file)
@@ -136,7 +136,7 @@ BandwidthBenchmark *createBandwidthBenchmarkObject(arg_t values) {
         return NULL;
     }
 
-    if (!bench->setSize(values["size"].int_value)) {
+    if (!bench->setSize(size)) {
         printf("Failed to allocate buffers for benchmark.\n");
         return NULL;
     }
@@ -343,7 +343,6 @@ int multithread_bandwidth(int argc, char** argv) {
 
     thread_arg_t args[num_threads];
 
-    int i = 0;
     for (int i = 0; i < num_threads; i++) {
         args[i].core = -1;
         args[i].bench = createBandwidthBenchmarkObject(values);
@@ -395,7 +394,7 @@ bool run_bandwidth_benchmark(int argc, char** argv, const char *name,
         if (!(*it)->canRun()) {
             continue;
         }
-        if (!(*it)->setSize(values["num_warm_loops"].int_value)) {
+        if (!(*it)->setSize(values["size"].int_value)) {
             printf("Failed creating buffer for bandwidth test.\n");
             return false;
         }
index 4e8b1c2..b43349c 100644 (file)
@@ -272,8 +272,8 @@ public:
 
 protected:
     // Copy using vld1/vst1 instructions.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3,r4}\n"
 
@@ -300,6 +300,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3,r4}\n"
         :: "r" (_src), "r" (_dst), "r" (_size), "r" (num_loops) : "r0", "r1", "r2", "r3");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -315,8 +317,8 @@ public:
 
 protected:
     // Copy using vldr/vstr instructions.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3,r4}\n"
 
@@ -357,6 +359,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3,r4}\n"
         :: "r" (_src), "r" (_dst), "r" (_size), "r" (num_loops) : "r0", "r1", "r2", "r3");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -372,8 +376,8 @@ public:
 
 protected:
     // Copy using vldmia/vstmia instructions.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3,r4}\n"
 
@@ -398,6 +402,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3,r4}\n"
         :: "r" (_src), "r" (_dst), "r" (_size), "r" (num_loops) : "r0", "r1", "r2", "r3");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -433,7 +439,7 @@ public:
             _buffer = NULL;
         }
 
-        if (_size == 0) {
+        if (size == 0) {
             _size = DEFAULT_SINGLE_BUFFER_SIZE;
         } else {
             _size = size;
@@ -585,8 +591,8 @@ public:
 
 protected:
     // Write a given value using vst.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3,r4}\n"
 
@@ -615,6 +621,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3,r4}\n"
         :: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -630,8 +638,8 @@ public:
 
 protected:
     // Write a given value using vst.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3,r4}\n"
 
@@ -664,6 +672,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3,r4}\n"
         :: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -679,8 +689,8 @@ public:
 
 protected:
     // Write a given value using vstmia.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3,r4}\n"
 
@@ -709,6 +719,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3,r4}\n"
         :: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -811,8 +823,8 @@ public:
 
 protected:
     // Write a given value using vst.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3}\n"
 
@@ -834,6 +846,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3}\n"
         :: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -849,8 +863,8 @@ public:
 
 protected:
     // Write a given value using vst.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3}\n"
 
@@ -876,6 +890,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3}\n"
         :: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+    void bench(size_t) {
 #endif
     }
 };
@@ -892,8 +908,8 @@ public:
 
 protected:
     // Write a given value using vstmia.
-    void bench(size_t num_loops) {
 #if defined(__ARM_NEON__)
+    void bench(size_t num_loops) {
         asm volatile(
             "stmfd sp!, {r0,r1,r2,r3}\n"
 
@@ -915,6 +931,8 @@ protected:
 
             "ldmfd sp!, {r0,r1,r2,r3}\n"
         :: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+    void bench(size_t) {
 #endif
     }
 };
index 564b700..1ae04b2 100644 (file)
@@ -63,7 +63,6 @@ static void test_mad() {
 
     startTime();
 
-    float total = 0;
     // Do ~1 billion ops
     for (int ct=0; ct < (1000 * (1000 / 20)); ct++) {
         for (int i=0; i < 1000; i++) {
@@ -99,7 +98,6 @@ static void test_fma() {
 
     startTime();
 
-    float total = 0;
     // Do ~1 billion ops
     for (int ct=0; ct < (1000 * (1000 / 80)); ct++) {
         for (int i=0; i < 1000; i++) {
@@ -123,7 +121,7 @@ static void test_fma() {
 }
 #endif
 
-int fp_test(int argc, char** argv) {
+int fp_test(int, char**) {
     test_mad();
 
 #ifdef __ARM_NEON__
@@ -132,7 +130,3 @@ int fp_test(int argc, char** argv) {
 
     return 0;
 }
-
-
-
-
index 7b097d3..bad9209 100644 (file)
@@ -28,8 +28,7 @@
 
 #include "memtest.h"
 
-nsecs_t system_time()
-{
+nsecs_t system_time() {
     struct timespec t;
     t.tv_sec = t.tv_nsec = 0;
     clock_gettime(CLOCK_MONOTONIC, &t);
@@ -56,7 +55,6 @@ static void usage(char* p) {
            "  malloc [fill]\n"
            "  madvise\n"
            "  resampler\n"
-           "  crash\n"
            "  stack (stack smasher)\n"
            "  crawl\n"
            , p);
@@ -69,7 +67,6 @@ int per_core_bandwidth(int argc, char** argv);
 int multithread_bandwidth(int argc, char** argv);
 int malloc_test(int argc, char** argv);
 int madvise_test(int argc, char** argv);
-int crash_test(int argc, char** argv);
 int stack_smasher_test(int argc, char** argv);
 int crawl_test(int argc, char** argv);
 int fp_test(int argc, char** argv);
@@ -82,7 +79,6 @@ typedef struct {
 function_t function_table[] = {
     { "malloc", malloc_test },
     { "madvise", madvise_test },
-    { "crash", crash_test },
     { "stack", stack_smasher_test },
     { "crawl", crawl_test },
     { "fp", fp_test },
@@ -93,8 +89,7 @@ function_t function_table[] = {
     { "multithread_bandwidth", multithread_bandwidth },
 };
 
-int main(int argc, char** argv)
-{
+int main(int argc, char** argv) {
     if (argc == 1) {
         usage(argv[0]);
         return 0;
@@ -112,8 +107,7 @@ int main(int argc, char** argv)
     return err;
 }
 
-int malloc_test(int argc, char** argv)
-{
+int malloc_test(int argc, char** argv) {
     bool fill = (argc>=2 && !strcmp(argv[1], "fill"));
     size_t total = 0;
     size_t size = 0x40000000;
@@ -138,8 +132,7 @@ int malloc_test(int argc, char** argv)
     return 0;
 }
 
-int madvise_test(int argc, char** argv)
-{
+int madvise_test(int, char**) {
     for (int i=0 ; i<2 ; i++) {
         size_t size = i==0 ? 4096 : 48*1024*1024; // 48 MB
         printf("Allocating %zd MB... ", size/(1024*1024)); fflush(stdout);
@@ -182,22 +175,7 @@ int madvise_test(int argc, char** argv)
     return 0;
 }
 
-int crash_test(int argc, char** argv)
-{
-    printf("about to crash...\n");
-    asm volatile(
-        "mov r0,  #0 \n"
-        "mov r1,  #1 \n"
-        "mov r2,  #2 \n"
-        "mov r3,  #3 \n"
-        "ldr r12, [r0] \n"
-    );
-
-    return 0;
-}
-
-int stack_smasher_test(int argc, char** argv)
-{
+int stack_smasher_test(int, char**) {
     int dummy = 0;
     printf("corrupting our stack...\n");
     *(volatile long long*)&dummy = 0;
@@ -212,25 +190,23 @@ extern "C" void arm_function_3(int*p);
 extern "C" void arm_function_2(int*p);
 extern "C" void arm_function_1(int*p);
 
-void arm_function_3(int*p) {
+void arm_function_3(int*) {
     int a = 0;
     thumb_function_2(&a);
 }
 
-void arm_function_2(int*p) {
+void arm_function_2(int*) {
     int a = 0;
     thumb_function_1(&a);
 }
 
-void arm_function_1(int*p) {
+void arm_function_1(int*) {
     int a = 0;
     arm_function_2(&a);
 }
 
-int crawl_test(int argc, char** argv)
-{
+int crawl_test(int, char**) {
     int a = 0;
     arm_function_1(&a);
     return 0;
 }
-
index a70f4c3..9293966 100644 (file)
@@ -1,27 +1,22 @@
 #include <stdio.h>
 #include <unwind.h>
 
-extern "C" void arm_function_3(int*p);
-extern "C" void thumb_function_1(int*p);
-extern "C" void thumb_function_2(int*p);
+extern "C" void arm_function_3(int* p);
+extern "C" void thumb_function_1(int* p);
+extern "C" void thumb_function_2(int* p);
 
-extern "C" _Unwind_Reason_Code trace_function(_Unwind_Context *context, void *arg)
-{
-    int i = 0;
+extern "C" _Unwind_Reason_Code trace_function(_Unwind_Context* context, void *) {
     printf("0x%x\n", _Unwind_GetIP(context));
     fflush(stdout);
     return _URC_NO_REASON;
 }
 
-void thumb_function_1(int*p)
-{
+void thumb_function_1(int*) {
     int a = 0;
     arm_function_3(&a);
 }
 
-void thumb_function_2(int*p)
-{
-    int a = 0;
+void thumb_function_2(int*) {
     printf("unwinding...\n");
-    _Unwind_Backtrace(trace_function, (void*)"backtrace!");
+    _Unwind_Backtrace(trace_function, (void*) "backtrace!");
 }
diff --git a/tests/net_test/anycast_test.py b/tests/net_test/anycast_test.py
new file mode 100755 (executable)
index 0000000..82130db
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/python
+#
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+from socket import *  # pylint: disable=wildcard-import
+import threading
+import time
+import unittest
+
+import cstruct
+import multinetwork_base
+import net_test
+
+IPV6_JOIN_ANYCAST = 27
+IPV6_LEAVE_ANYCAST = 28
+
+# pylint: disable=invalid-name
+IPv6Mreq = cstruct.Struct("IPv6Mreq", "=16si", "multiaddr ifindex")
+
+
+_CLOSE_HUNG = False
+
+
+def CauseOops():
+  open("/proc/sysrq-trigger", "w").write("c")
+
+
+class CloseFileDescriptorThread(threading.Thread):
+
+  def __init__(self, fd):
+    super(CloseFileDescriptorThread, self).__init__()
+    self.daemon = True
+    self._fd = fd
+    self.finished = False
+
+  def run(self):
+    global _CLOSE_HUNG
+    _CLOSE_HUNG = True
+    self._fd.close()
+    _CLOSE_HUNG = False
+    self.finished = True
+
+
+class AnycastTest(multinetwork_base.MultiNetworkBaseTest):
+  """Tests for IPv6 anycast addresses.
+
+  Relevant kernel commits:
+    upstream net-next:
+      381f4dc ipv6: clean up anycast when an interface is destroyed
+
+    android-3.10:
+      86a47ad ipv6: clean up anycast when an interface is destroyed
+  """
+  _TEST_NETID = 123
+
+  def AnycastSetsockopt(self, s, is_add, netid, addr):
+    ifindex = self.ifindices[netid]
+    self.assertTrue(ifindex)
+    ipv6mreq = IPv6Mreq((addr, ifindex))
+    option = IPV6_JOIN_ANYCAST if is_add else IPV6_LEAVE_ANYCAST
+    s.setsockopt(IPPROTO_IPV6, option, ipv6mreq.Pack())
+
+  def testAnycastNetdeviceUnregister(self):
+    netid = self._TEST_NETID
+    self.assertNotIn(netid, self.tuns)
+    self.tuns[netid] = self.CreateTunInterface(netid)
+    self.SendRA(netid)
+    iface = self.GetInterfaceName(netid)
+    self.ifindices[netid] = net_test.GetInterfaceIndex(iface)
+
+    s = socket(AF_INET6, SOCK_DGRAM, 0)
+    addr = self.MyAddress(6, netid)
+    self.assertIsNotNone(addr)
+
+    addr = inet_pton(AF_INET6, addr)
+    addr = addr[:8] + os.urandom(8)
+    self.AnycastSetsockopt(s, True, netid, addr)
+
+    # Close the tun fd in the background.
+    # This will hang if the kernel has the bug.
+    thread = CloseFileDescriptorThread(self.tuns[netid])
+    thread.start()
+    time.sleep(0.1)
+
+    # Make teardown work.
+    del self.tuns[netid]
+    # Check that the interface is gone.
+    try:
+      self.assertIsNone(self.MyAddress(6, netid))
+    finally:
+      # This doesn't seem to help, but still.
+      self.AnycastSetsockopt(s, False, netid, addr)
+    self.assertTrue(thread.finished)
+
+
+if __name__ == "__main__":
+  unittest.main(exit=False)
+  if _CLOSE_HUNG:
+    time.sleep(3)
+    CauseOops()
index 4b268e9..5dc495c 100644 (file)
@@ -24,6 +24,7 @@ import cstruct
 
 
 # Data structures.
+# These aren't constants, they're classes. So, pylint: disable=invalid-name
 CMsgHdr = cstruct.Struct("cmsghdr", "@Lii", "len level type")
 Iovec = cstruct.Struct("iovec", "@LL", "base len")
 MsgHdr = cstruct.Struct("msghdr", "@LLLLLLi",
@@ -112,6 +113,7 @@ def Bind(s, to):
   MaybeRaiseSocketError(ret)
   return ret
 
+
 def Connect(s, to):
   """Python wrapper for connect."""
   ret = libc.connect(s.fileno(), to.CPointer(), len(to))
index 266a5a0..91cd72e 100644 (file)
@@ -47,10 +47,17 @@ NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510)
 """
 
 import ctypes
+import string
 import struct
 
 
-def Struct(name, fmt, fields):
+def CalcNumElements(fmt):
+  size = struct.calcsize(fmt)
+  elements = struct.unpack(fmt, "\x00" * size)
+  return len(elements)
+
+
+def Struct(name, fmt, fieldnames, substructs={}):
   """Function that returns struct classes."""
 
   class Meta(type):
@@ -67,13 +74,32 @@ def Struct(name, fmt, fields):
 
     __metaclass__ = Meta
 
+    # Name of the struct.
     _name = name
-    _format = fmt
-    _fields = fields
+    # List of field names.
+    _fieldnames = fieldnames
+    # Dict mapping field indices to nested struct classes.
+    _nested = {}
+
+    if isinstance(_fieldnames, str):
+      _fieldnames = _fieldnames.split(" ")
+
+    # Parse fmt into _format, converting any S format characters to "XXs",
+    # where XX is the length of the struct type's packed representation.
+    _format = ""
+    laststructindex = 0
+    for i in xrange(len(fmt)):
+      if fmt[i] == "S":
+        # Nested struct. Record the index in our struct it should go into.
+        index = CalcNumElements(fmt[:i])
+        _nested[index] = substructs[laststructindex]
+        laststructindex += 1
+        _format += "%ds" % len(_nested[index])
+      else:
+         # Standard struct format character.
+        _format += fmt[i]
 
     _length = struct.calcsize(_format)
-    if isinstance(_fields, str):
-      _fields = _fields.split(" ")
 
     def _SetValues(self, values):
       super(CStruct, self).__setattr__("_values", list(values))
@@ -81,6 +107,9 @@ def Struct(name, fmt, fields):
     def _Parse(self, data):
       data = data[:self._length]
       values = list(struct.unpack(self._format, data))
+      for index, value in enumerate(values):
+        if isinstance(value, str) and index in self._nested:
+          values[index] = self._nested[index](value)
       self._SetValues(values)
 
     def __init__(self, values):
@@ -92,14 +121,14 @@ def Struct(name, fmt, fields):
         self._Parse(values)
       else:
         # Initializing from a tuple.
-        if len(values) != len(self._fields):
-          raise TypeError("%s has exactly %d fields (%d given)" %
-                          (self._name, len(self._fields), len(values)))
+        if len(values) != len(self._fieldnames):
+          raise TypeError("%s has exactly %d fieldnames (%d given)" %
+                          (self._name, len(self._fieldnames), len(values)))
         self._SetValues(values)
 
     def _FieldIndex(self, attr):
       try:
-        return self._fields.index(attr)
+        return self._fieldnames.index(attr)
       except ValueError:
         raise AttributeError("'%s' has no attribute '%s'" %
                              (self._name, attr))
@@ -114,12 +143,38 @@ def Struct(name, fmt, fields):
     def __len__(cls):
       return cls._length
 
+    def __ne__(self, other):
+      return not self.__eq__(other)
+
+    def __eq__(self, other):
+      return (isinstance(other, self.__class__) and
+              self._name == other._name and
+              self._fieldnames == other._fieldnames and
+              self._values == other._values)
+
+    @staticmethod
+    def _MaybePackStruct(value):
+      if hasattr(value, "__metaclass__"):# and value.__metaclass__ == Meta:
+        return value.Pack()
+      else:
+        return value
+
     def Pack(self):
-      return struct.pack(self._format, *self._values)
+      values = [self._MaybePackStruct(v) for v in self._values]
+      return struct.pack(self._format, *values)
 
     def __str__(self):
-      return "%s(%s)" % (self._name, ", ".join(
-          "%s=%s" % (i, v) for i, v in zip(self._fields, self._values)))
+      def FieldDesc(index, name, value):
+        if isinstance(value, str) and any(
+            c not in string.printable for c in value):
+          value = value.encode("hex")
+        return "%s=%s" % (name, value)
+
+      descriptions = [
+          FieldDesc(i, n, v) for i, (n, v) in
+          enumerate(zip(self._fieldnames, self._values))]
+
+      return "%s(%s)" % (self._name, ", ".join(descriptions))
 
     def __repr__(self):
       return str(self)
diff --git a/tests/net_test/cstruct_test.py b/tests/net_test/cstruct_test.py
new file mode 100755 (executable)
index 0000000..2d5a408
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import cstruct
+
+
+# These aren't constants, they're classes. So, pylint: disable=invalid-name
+TestStructA = cstruct.Struct("TestStructA", "=BI", "byte1 int2")
+TestStructB = cstruct.Struct("TestStructB", "=BI", "byte1 int2")
+
+
+class CstructTest(unittest.TestCase):
+
+  def CheckEquals(self, a, b):
+    self.assertEquals(a, b)
+    self.assertEquals(b, a)
+    assert a == b
+    assert b == a
+    assert not (a != b)  # pylint: disable=g-comparison-negation,superfluous-parens
+    assert not (b != a)  # pylint: disable=g-comparison-negation,superfluous-parens
+
+  def CheckNotEquals(self, a, b):
+    self.assertNotEquals(a, b)
+    self.assertNotEquals(b, a)
+    assert a != b
+    assert b != a
+    assert not (a == b)  # pylint: disable=g-comparison-negation,superfluous-parens
+    assert not (b == a)  # pylint: disable=g-comparison-negation,superfluous-parens
+
+  def testEqAndNe(self):
+    a1 = TestStructA((1, 2))
+    a2 = TestStructA((2, 3))
+    a3 = TestStructA((1, 2))
+    b = TestStructB((1, 2))
+    self.CheckNotEquals(a1, b)
+    self.CheckNotEquals(a2, b)
+    self.CheckNotEquals(a1, a2)
+    self.CheckNotEquals(a2, a3)
+    for i in [a1, a2, a3, b]:
+      self.CheckEquals(i, i)
+    self.CheckEquals(a1, a3)
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tests/net_test/forwarding_test.py b/tests/net_test/forwarding_test.py
new file mode 100755 (executable)
index 0000000..185e477
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+#
+# 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.
+
+import itertools
+import random
+import unittest
+
+from socket import *
+
+import iproute
+import multinetwork_base
+import net_test
+import packets
+
+
+class ForwardingTest(multinetwork_base.MultiNetworkBaseTest):
+
+  TCP_TIME_WAIT = 6
+
+  def ForwardBetweenInterfaces(self, enabled, iface1, iface2):
+    for iif, oif in itertools.permutations([iface1, iface2]):
+      self.iproute.IifRule(6, enabled, self.GetInterfaceName(iif),
+                           self._TableForNetid(oif), self.PRIORITY_IIF)
+
+  def setUp(self):
+    self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
+
+  def tearDown(self):
+    self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 0)
+
+  def CheckForwardingCrash(self, netid, iface1, iface2):
+    listenport = packets.RandomPort()
+    listensocket = net_test.IPv6TCPSocket()
+    listensocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+    listensocket.bind(("::", listenport))
+    listensocket.listen(100)
+    self.SetSocketMark(listensocket, netid)
+
+    version = 6
+    remoteaddr = self.GetRemoteAddress(version)
+    myaddr = self.MyAddress(version, netid)
+
+    desc, syn = packets.SYN(listenport, version, remoteaddr, myaddr)
+    synack_desc, synack = packets.SYNACK(version, myaddr, remoteaddr, syn)
+    msg = "Sent %s, expected %s" % (desc, synack_desc)
+    reply = self._ReceiveAndExpectResponse(netid, syn, synack, msg)
+
+    establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
+    self.ReceivePacketOn(netid, establishing_ack)
+    accepted, peer = listensocket.accept()
+    remoteport = accepted.getpeername()[1]
+
+    accepted.close()
+    desc, fin = packets.FIN(version, myaddr, remoteaddr, establishing_ack)
+    self.ExpectPacketOn(netid, msg + ": expecting %s after close" % desc, fin)
+
+    desc, finack = packets.FIN(version, remoteaddr, myaddr, fin)
+    self.ReceivePacketOn(netid, finack)
+
+    # Check our socket is now in TIME_WAIT.
+    sockets = self.ReadProcNetSocket("tcp6")
+    mysrc = "%s:%04X" % (net_test.FormatSockStatAddress(myaddr), listenport)
+    mydst = "%s:%04X" % (net_test.FormatSockStatAddress(remoteaddr), remoteport)
+    state = None
+    sockets = [s for s in sockets if s[0] == mysrc and s[1] == mydst]
+    self.assertEquals(1, len(sockets))
+    self.assertEquals("%02X" % self.TCP_TIME_WAIT, sockets[0][2])
+
+    # Remove our IP address.
+    try:
+      self.iproute.DelAddress(myaddr, 64, self.ifindices[netid])
+
+      self.ReceivePacketOn(iface1, finack)
+      self.ReceivePacketOn(iface1, establishing_ack)
+      self.ReceivePacketOn(iface1, establishing_ack)
+      # No crashes? Good.
+
+    finally:
+      # Put back our IP address.
+      self.SendRA(netid)
+      listensocket.close()
+
+  def testCrash(self):
+    # Run the test a few times as it doesn't crash/hang the first time.
+    for netids in itertools.permutations(self.tuns):
+      # Pick an interface to send traffic on and two to forward traffic between.
+      netid, iface1, iface2 = random.sample(netids, 3)
+      self.ForwardBetweenInterfaces(True, iface1, iface2)
+      try:
+        self.CheckForwardingCrash(netid, iface1, iface2)
+      finally:
+        self.ForwardBetweenInterfaces(False, iface1, iface2)
+
+
+if __name__ == "__main__":
+  unittest.main()
index cde1803..2c63993 100644 (file)
@@ -25,6 +25,7 @@ import struct
 import sys
 
 import cstruct
+import netlink
 
 
 ### Base netlink constants. See include/uapi/linux/netlink.h.
@@ -33,6 +34,7 @@ NETLINK_ROUTE = 0
 # Request constants.
 NLM_F_REQUEST = 1
 NLM_F_ACK = 4
+NLM_F_REPLACE = 0x100
 NLM_F_EXCL = 0x200
 NLM_F_CREATE = 0x400
 NLM_F_DUMP = 0x300
@@ -64,6 +66,7 @@ RTM_DELROUTE = 25
 RTM_GETROUTE = 26
 RTM_NEWNEIGH = 28
 RTM_DELNEIGH = 29
+RTM_GETNEIGH = 30
 RTM_NEWRULE = 32
 RTM_DELRULE = 33
 RTM_GETRULE = 34
@@ -99,6 +102,7 @@ RTA_UID = 18
 
 # Route metric attributes.
 RTAX_MTU = 2
+RTAX_HOPLIMIT = 10
 
 # Data structure formats.
 IfinfoMsg = cstruct.Struct(
@@ -133,12 +137,16 @@ IfAddrMsg = cstruct.Struct(
     "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 +163,7 @@ FRA_PRIORITY = 6
 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 +189,7 @@ IFLA_NUM_TX_QUEUES = 31
 IFLA_NUM_RX_QUEUES = 32
 IFLA_CARRIER = 33
 
+
 def CommandVerb(command):
   return ["NEW", "DEL", "GET", "SET"][command % 4]
 
@@ -191,37 +201,14 @@ def CommandSubject(command):
 def CommandName(command):
   try:
     return "RTM_%s%s" % (CommandVerb(command), CommandSubject(command))
-  except KeyError:
+  except IndexError:
     return "RTM_%d" % command
 
 
-def PaddedLength(length):
-  # TODO: This padding is probably overly simplistic.
-  return NLA_ALIGNTO * ((length / NLA_ALIGNTO) + (length % NLA_ALIGNTO != 0))
-
-
-class IPRoute(object):
-
+class IPRoute(netlink.NetlinkSocket):
   """Provides a tiny subset of iproute functionality."""
 
-  BUFSIZE = 65536
-  DEBUG = False
-  # List of netlink messages to print, e.g., [], ["NEIGH", "ROUTE"], or ["ALL"]
-  NL_DEBUG = []
-
-  def _Debug(self, s):
-    if self.DEBUG:
-      print s
-
-  def _NlAttr(self, nla_type, data):
-    datalen = len(data)
-    # Pad the data if it's not a multiple of NLA_ALIGNTO bytes long.
-    padding = "\x00" * (PaddedLength(datalen) - datalen)
-    nla_len = datalen + len(NLAttr)
-    return NLAttr((nla_len, nla_type)).Pack() + data + padding
-
-  def _NlAttrU32(self, nla_type, value):
-    return self._NlAttr(nla_type, struct.pack("=I", value))
+  FAMILY = NETLINK_ROUTE
 
   def _NlAttrIPAddress(self, nla_type, family, address):
     return self._NlAttr(nla_type, socket.inet_pton(family, address))
@@ -230,16 +217,9 @@ class IPRoute(object):
     return self._NlAttr(nla_type, interface + "\x00")
 
   def _GetConstantName(self, value, prefix):
-    thismodule = sys.modules[__name__]
-    for name in dir(thismodule):
-      if (name.startswith(prefix) and
-          not name.startswith(prefix + "F_") and
-          name.isupper() and
-          getattr(thismodule, name) == value):
-        return name
-    return value
-
-  def _Decode(self, command, family, nla_type, nla_data):
+    return super(IPRoute, self)._GetConstantName(__name__, value, prefix)
+
+  def _Decode(self, command, msg, nla_type, nla_data):
     """Decodes netlink attributes to Python types.
 
     Values for which the code knows the type (e.g., the fwmark ID in a
@@ -269,10 +249,6 @@ class IPRoute(object):
          will be the raw byte string.
     """
     if command == -RTA_METRICS:
-      if nla_type == RTAX_MTU:
-        return ("RTAX_MTU", struct.unpack("=I", nla_data)[0])
-
-    if command == -RTA_METRICS:
       name = self._GetConstantName(nla_type, "RTAX_")
     elif CommandSubject(command) == "ADDR":
       name = self._GetConstantName(nla_type, "IFA_")
@@ -288,12 +264,13 @@ class IPRoute(object):
       # 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", "RTAX_MTU",
+                "RTAX_HOPLIMIT"]:
       data = struct.unpack("=I", nla_data)[0]
     elif name == "FRA_SUPPRESS_PREFIXLEN":
       data = struct.unpack("=i", nla_data)[0]
@@ -302,15 +279,17 @@ class IPRoute(object):
     elif name in ["IFA_ADDRESS", "IFA_LOCAL", "RTA_DST", "RTA_SRC",
                   "RTA_GATEWAY", "RTA_PREFSRC", "RTA_UID",
                   "NDA_DST"]:
-      data = socket.inet_ntop(family, nla_data)
+      data = socket.inet_ntop(msg.family, nla_data)
     elif name in ["FRA_IIFNAME", "FRA_OIFNAME", "IFLA_IFNAME", "IFLA_QDISC"]:
       data = nla_data.strip("\x00")
     elif name == "RTA_METRICS":
-      data = self._ParseAttributes(-RTA_METRICS, family, nla_data)
+      data = self._ParseAttributes(-RTA_METRICS, msg.family, None, nla_data)
     elif name == "RTA_CACHEINFO":
       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:
@@ -318,104 +297,23 @@ class IPRoute(object):
 
     return name, data
 
-  def _ParseAttributes(self, command, family, data):
-    """Parses and decodes netlink attributes.
-
-    Takes a block of NLAttr data structures, decodes them using Decode, and
-    returns the result in a dict keyed by attribute number.
-
-    Args:
-      command: An integer, the rtnetlink command being carried out.
-      family: The address family.
-      data: A byte string containing a sequence of NLAttr data structures.
-
-    Returns:
-      A dictionary mapping attribute types (integers) to decoded values.
-
-    Raises:
-      ValueError: There was a duplicate attribute type.
-    """
-    attributes = {}
-    while data:
-      # Read the nlattr header.
-      nla, data = cstruct.Read(data, NLAttr)
-
-      # Read the data.
-      datalen = nla.nla_len - len(nla)
-      padded_len = PaddedLength(nla.nla_len) - len(nla)
-      nla_data, data = data[:datalen], data[padded_len:]
-
-      # If it's an attribute we know about, try to decode it.
-      nla_name, nla_data = self._Decode(command, family, nla.nla_type, nla_data)
-
-      # We only support unique attributes for now.
-      if nla_name in attributes:
-        raise ValueError("Duplicate attribute %d" % nla_name)
-
-      attributes[nla_name] = nla_data
-      self._Debug("      %s" % str((nla_name, nla_data)))
-
-    return attributes
-
   def __init__(self):
-    # Global sequence number.
-    self.seq = 0
-    self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW,
-                              socket.NETLINK_ROUTE)
-    self.sock.connect((0, 0))  # The kernel.
-    self.pid = self.sock.getsockname()[1]
-
-  def _Send(self, msg):
-    # self._Debug(msg.encode("hex"))
-    self.seq += 1
-    self.sock.send(msg)
-
-  def _Recv(self):
-    data = self.sock.recv(self.BUFSIZE)
-    # self._Debug(data.encode("hex"))
-    return data
-
-  def _ExpectDone(self):
-    response = self._Recv()
-    hdr = NLMsgHdr(response)
-    if hdr.type != NLMSG_DONE:
-      raise ValueError("Expected DONE, got type %d" % hdr.type)
-
-  def _ParseAck(self, response):
-    # Find the error code.
-    hdr, data = cstruct.Read(response, NLMsgHdr)
-    if hdr.type == NLMSG_ERROR:
-      error = NLMsgErr(data).error
-      if error:
-        raise IOError(error, os.strerror(-error))
-    else:
-      raise ValueError("Expected ACK, got type %d" % hdr.type)
-
-  def _ExpectAck(self):
-    response = self._Recv()
-    self._ParseAck(response)
+    super(IPRoute, self).__init__()
 
   def _AddressFamily(self, version):
     return {4: socket.AF_INET, 6: socket.AF_INET6}[version]
 
-  def _SendNlRequest(self, command, data):
+  def _SendNlRequest(self, command, data, flags=0):
     """Sends a netlink request and expects an ack."""
-    flags = NLM_F_REQUEST
+
+    flags |= NLM_F_REQUEST
     if CommandVerb(command) != "GET":
       flags |= NLM_F_ACK
     if CommandVerb(command) == "NEW":
-      flags |= (NLM_F_EXCL | NLM_F_CREATE)
+      if not flags & NLM_F_REPLACE:
+        flags |= (NLM_F_EXCL | NLM_F_CREATE)
 
-    length = len(NLMsgHdr) + len(data)
-    nlmsg = NLMsgHdr((length, command, flags, self.seq, self.pid)).Pack()
-
-    self.MaybeDebugCommand(command, nlmsg + data)
-
-    # Send the message.
-    self._Send(nlmsg + data)
-
-    if flags & NLM_F_ACK:
-      self._ExpectAck()
+    super(IPRoute, self)._SendNlRequest(command, data, flags)
 
   def _Rule(self, version, is_add, rule_type, table, match_nlattr, priority):
     """Python equivalent of "ip rule <add|del> <match_cond> lookup <table>".
@@ -465,6 +363,10 @@ class IPRoute(object):
     nlattr = self._NlAttrU32(FRA_FWMARK, fwmark)
     return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
 
+  def IifRule(self, version, is_add, iif, table, priority):
+    nlattr = self._NlAttrInterfaceName(FRA_IIFNAME, iif)
+    return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
+
   def OifRule(self, version, is_add, oif, table, priority):
     nlattr = self._NlAttrInterfaceName(FRA_OIFNAME, oif)
     return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
@@ -481,42 +383,10 @@ class IPRoute(object):
   def DefaultRule(self, version, is_add, table, priority):
     return self.FwmarkRule(version, is_add, 0, table, priority)
 
-  def _ParseNLMsg(self, data, msgtype):
-    """Parses a Netlink message into a header and a dictionary of attributes."""
-    nlmsghdr, data = cstruct.Read(data, NLMsgHdr)
-    self._Debug("  %s" % nlmsghdr)
-
-    if nlmsghdr.type == NLMSG_ERROR or nlmsghdr.type == NLMSG_DONE:
-      print "done"
-      return None, data
-
-    nlmsg, data = cstruct.Read(data, msgtype)
-    self._Debug("    %s" % nlmsg)
-
-    # Parse the attributes in the nlmsg.
-    attrlen = nlmsghdr.length - len(nlmsghdr) - len(nlmsg)
-    attributes = self._ParseAttributes(nlmsghdr.type, nlmsg.family,
-                                       data[:attrlen])
-    data = data[attrlen:]
-    return (nlmsg, attributes), data
-
-  def _GetMsgList(self, msgtype, data, expect_done):
-    out = []
-    while data:
-      msg, data = self._ParseNLMsg(data, msgtype)
-      if msg is None:
-        break
-      out.append(msg)
-    if expect_done:
-      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,45 +395,34 @@ class IPRoute(object):
           "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 _Dump(self, command, msg, msgtype):
-    """Sends a dump request and returns a list of decoded messages."""
-    # Create a netlink dump request containing the msg.
-    flags = NLM_F_DUMP | NLM_F_REQUEST
-    length = len(NLMsgHdr) + len(msg)
-    nlmsghdr = NLMsgHdr((length, command, flags, self.seq, self.pid))
-
-    # Send the request.
-    self._Send(nlmsghdr.Pack() + msg.Pack())
-
-    # Keep reading netlink messages until we get a NLMSG_DONE.
-    out = []
-    while True:
-      data = self._Recv()
-      response_type = NLMsgHdr(data).type
-      if response_type == NLMSG_DONE:
-        break
-      out.extend(self._GetMsgList(msgtype, data, False))
-
-    return out
+  def PrintMessage(self, message):
+    hdr = NLMsgHdr(message)
+    print self.CommandToString(hdr.type, message)
 
   def DumpRules(self, version):
     """Returns the IP rules for the specified IP version."""
     # Create a struct rtmsg specifying the table and the given match attributes.
     family = self._AddressFamily(version)
     rtmsg = RTMsg((family, 0, 0, 0, 0, 0, 0, 0, 0))
-    return self._Dump(RTM_GETRULE, rtmsg, RTMsg)
+    return self._Dump(RTM_GETRULE, rtmsg, RTMsg, "")
 
   def DumpLinks(self):
     ifinfomsg = IfinfoMsg((0, 0, 0, 0, 0, 0))
-    return self._Dump(RTM_GETLINK, ifinfomsg, IfinfoMsg)
+    return self._Dump(RTM_GETLINK, ifinfomsg, IfinfoMsg, "")
 
   def _Address(self, version, command, addr, prefixlen, flags, scope, ifindex):
     """Adds or deletes an IP address."""
@@ -591,10 +450,7 @@ class IPRoute(object):
       # implement parsing dump results.
       raise NotImplementedError("IPv4 RTM_GETADDR not implemented.")
     self._Address(6, RTM_GETADDR, address, 0, 0, RT_SCOPE_UNIVERSE, ifindex)
-    data = self._Recv()
-    if NLMsgHdr(data).type == NLMSG_ERROR:
-      self._ParseAck(data)
-    return self._ParseNLMsg(data, IfAddrMsg)[0]
+    return self._GetMsg(IfAddrMsg)
 
   def _Route(self, version, command, table, dest, prefixlen, nexthop, dev,
              mark, uid):
@@ -640,12 +496,12 @@ class IPRoute(object):
     routes = self._GetMsgList(RTMsg, data, False)
     return routes
 
-  def _Neighbour(self, version, is_add, addr, lladdr, dev, state):
+  def _Neighbour(self, version, is_add, addr, lladdr, dev, state, flags=0):
     """Adds or deletes a neighbour cache entry."""
     family = self._AddressFamily(version)
 
     # Convert the link-layer address to a raw byte string.
-    if is_add:
+    if is_add and lladdr:
       lladdr = lladdr.split(":")
       if len(lladdr) != 6:
         raise ValueError("Invalid lladdr %s" % ":".join(lladdr))
@@ -653,10 +509,10 @@ class IPRoute(object):
 
     ndmsg = NdMsg((family, dev, state, 0, RTN_UNICAST)).Pack()
     ndmsg += self._NlAttrIPAddress(NDA_DST, family, addr)
-    if is_add:
+    if is_add and lladdr:
       ndmsg += self._NlAttr(NDA_LLADDR, lladdr)
     command = RTM_NEWNEIGH if is_add else RTM_DELNEIGH
-    self._SendNlRequest(command, ndmsg)
+    self._SendNlRequest(command, ndmsg, flags)
 
   def AddNeighbour(self, version, addr, lladdr, dev):
     self._Neighbour(version, True, addr, lladdr, dev, NUD_PERMANENT)
@@ -664,6 +520,18 @@ class IPRoute(object):
   def DelNeighbour(self, version, addr, lladdr, dev):
     self._Neighbour(version, False, addr, lladdr, dev, 0)
 
+  def UpdateNeighbour(self, version, addr, lladdr, dev, state):
+    self._Neighbour(version, True, addr, lladdr, dev, state,
+                    flags=NLM_F_REPLACE)
+
+  def DumpNeighbours(self, version):
+    ndmsg = NdMsg((self._AddressFamily(version), 0, 0, 0, 0))
+    return self._Dump(RTM_GETNEIGH, ndmsg, NdMsg, "")
+
+  def ParseNeighbourMessage(self, msg):
+    msg, _ = self._ParseNLMsg(msg, NdMsg)
+    return msg
+
 
 if __name__ == "__main__":
   iproute = IPRoute()
index 8940258..31fcc4c 100644 (file)
@@ -100,7 +100,6 @@ def MakePktInfo(version, addr, ifindex):
 
 
 class MultiNetworkBaseTest(net_test.NetworkTest):
-
   """Base class for all multinetwork tests.
 
   This class does not contain any test code, but contains code to set up and
@@ -129,6 +128,7 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
   PRIORITY_UID = 100
   PRIORITY_OIF = 200
   PRIORITY_FWMARK = 300
+  PRIORITY_IIF = 400
   PRIORITY_DEFAULT = 999
   PRIORITY_UNREACHABLE = 1000
 
@@ -188,8 +188,13 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
   @classmethod
   def MyAddress(cls, version, netid):
     return {4: cls._MyIPv4Address(netid),
+            5: "::ffff:" + cls._MyIPv4Address(netid),
             6: cls._MyIPv6Address(netid)}[version]
 
+  @classmethod
+  def MyLinkLocalAddress(cls, netid):
+    return net_test.GetLinkAddress(cls.GetInterfaceName(netid), True)
+
   @staticmethod
   def IPv6Prefix(netid):
     return "2001:db8:%02x::" % netid
@@ -216,12 +221,14 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
     net_test.SetInterfaceHWAddr(iface, cls.MyMacAddress(netid))
     # Disable DAD so we don't have to wait for it.
     cls.SetSysctl("/proc/sys/net/ipv6/conf/%s/accept_dad" % iface, 0)
+    # Set accept_ra to 2, because that's what we use.
+    cls.SetSysctl("/proc/sys/net/ipv6/conf/%s/accept_ra" % iface, 2)
     net_test.SetInterfaceUp(iface)
     net_test.SetNonBlocking(f)
     return f
 
   @classmethod
-  def SendRA(cls, netid, retranstimer=None):
+  def SendRA(cls, netid, retranstimer=None, reachabletime=0):
     validity = 300                 # seconds
     macaddr = cls.RouterMacAddress(netid)
     lladdr = cls._RouterAddress(netid, 6)
@@ -238,7 +245,8 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
 
     ra = (scapy.Ether(src=macaddr, dst="33:33:00:00:00:01") /
           scapy.IPv6(src=lladdr, hlim=255) /
-          scapy.ICMPv6ND_RA(retranstimer=retranstimer,
+          scapy.ICMPv6ND_RA(reachabletime=reachabletime,
+                            retranstimer=retranstimer,
                             routerlifetime=routerlifetime) /
           scapy.ICMPv6NDOptSrcLLAddr(lladdr=macaddr) /
           scapy.ICMPv6NDOptPrefixInfo(prefix=cls.IPv6Prefix(netid),
@@ -321,6 +329,13 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
     open(sysctl, "w").write(str(value) + "\n")
 
   @classmethod
+  def SetIPv6SysctlOnAllIfaces(cls, sysctl, value):
+    for netid in cls.tuns:
+      iface = cls.GetInterfaceName(netid)
+      name = "/proc/sys/net/ipv6/conf/%s/%s" % (iface, sysctl)
+      cls.SetSysctl(name, value)
+
+  @classmethod
   def _RestoreSysctls(cls):
     for sysctl, value in cls.saved_sysctls.iteritems():
       try:
@@ -414,7 +429,9 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
       s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_IF, ifindex)
 
   def GetRemoteAddress(self, version):
-    return {4: self.IPV4_ADDR, 6: self.IPV6_ADDR}[version]
+    return {4: self.IPV4_ADDR,
+            5: "::ffff:" + self.IPV4_ADDR,
+            6: self.IPV6_ADDR}[version]
 
   def SelectInterface(self, s, netid, mode):
     if mode == "uid":
@@ -430,13 +447,12 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
       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
 
@@ -504,6 +520,12 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
       actualip.flags &= 5
       actualip.chksum = None  # Change the header, recalculate the checksum.
 
+    # Blank out the flow label, since new kernels randomize it by default.
+    actualipv6 = actual.getlayer("IPv6")
+    expectedipv6 = expected.getlayer("IPv6")
+    if actualipv6 and expectedipv6:
+      actualipv6.fl = expectedipv6.fl
+
     # Blank out UDP fields that we can't predict (e.g., the source port for
     # kernel-originated packets).
     actualudp = actual.getlayer("UDP")
@@ -516,7 +538,6 @@ class MultiNetworkBaseTest(net_test.NetworkTest):
     # Since the TCP code below messes with options, recalculate the length.
     if actualip:
       actualip.len = None
-    actualipv6 = actual.getlayer("IPv6")
     if actualipv6:
       actualipv6.plen = None
 
index b66d765..660fdf6 100755 (executable)
@@ -27,29 +27,18 @@ from scapy import all as scapy
 import iproute
 import multinetwork_base
 import net_test
+import packets
 
-PING_IDENT = 0xff19
-PING_PAYLOAD = "foobarbaz"
-PING_SEQ = 3
-PING_TOS = 0x83
+# For brevity.
+UDP_PAYLOAD = net_test.UDP_PAYLOAD
 
 IPV6_FLOWINFO = 11
 
-
-UDP_PAYLOAD = str(scapy.DNS(rd=1,
-                            id=random.randint(0, 65535),
-                            qd=scapy.DNSQR(qname="wWW.GoOGle.CoM",
-                                           qtype="AAAA")))
-
-
 IPV4_MARK_REFLECT_SYSCTL = "/proc/sys/net/ipv4/fwmark_reflect"
 IPV6_MARK_REFLECT_SYSCTL = "/proc/sys/net/ipv6/fwmark_reflect"
 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)
 
@@ -58,189 +47,6 @@ class ConfigurationError(AssertionError):
   pass
 
 
-class Packets(object):
-
-  TCP_FIN = 1
-  TCP_SYN = 2
-  TCP_RST = 4
-  TCP_PSH = 8
-  TCP_ACK = 16
-
-  TCP_SEQ = 1692871236
-  TCP_WINDOW = 14400
-
-  @staticmethod
-  def RandomPort():
-    return random.randint(1025, 65535)
-
-  @staticmethod
-  def _GetIpLayer(version):
-    return {4: scapy.IP, 6: scapy.IPv6}[version]
-
-  @staticmethod
-  def _SetPacketTos(packet, tos):
-    if isinstance(packet, scapy.IPv6):
-      packet.tc = tos
-    elif isinstance(packet, scapy.IP):
-      packet.tos = tos
-    else:
-      raise ValueError("Can't find ToS Field")
-
-  @classmethod
-  def UDP(cls, version, srcaddr, dstaddr, sport=0):
-    ip = cls._GetIpLayer(version)
-    # Can't just use "if sport" because None has meaning (it means unspecified).
-    if sport == 0:
-      sport = cls.RandomPort()
-    return ("UDPv%d packet" % version,
-            ip(src=srcaddr, dst=dstaddr) /
-            scapy.UDP(sport=sport, dport=53) / UDP_PAYLOAD)
-
-  @classmethod
-  def UDPWithOptions(cls, version, srcaddr, dstaddr, sport=0):
-    if version == 4:
-      packet = (scapy.IP(src=srcaddr, dst=dstaddr, ttl=39, tos=0x83) /
-                scapy.UDP(sport=sport, dport=53) /
-                UDP_PAYLOAD)
-    else:
-      packet = (scapy.IPv6(src=srcaddr, dst=dstaddr,
-                           fl=0xbeef, hlim=39, tc=0x83) /
-                scapy.UDP(sport=sport, dport=53) /
-                UDP_PAYLOAD)
-    return ("UDPv%d packet with options" % version, packet)
-
-  @classmethod
-  def SYN(cls, dport, version, srcaddr, dstaddr, sport=0, seq=TCP_SEQ):
-    ip = cls._GetIpLayer(version)
-    if sport == 0:
-      sport = cls.RandomPort()
-    return ("TCP SYN",
-            ip(src=srcaddr, dst=dstaddr) /
-            scapy.TCP(sport=sport, dport=dport,
-                      seq=seq, ack=0,
-                      flags=cls.TCP_SYN, window=cls.TCP_WINDOW))
-
-  @classmethod
-  def RST(cls, version, srcaddr, dstaddr, packet):
-    ip = cls._GetIpLayer(version)
-    original = packet.getlayer("TCP")
-    return ("TCP RST",
-            ip(src=srcaddr, dst=dstaddr) /
-            scapy.TCP(sport=original.dport, dport=original.sport,
-                      ack=original.seq + 1, seq=None,
-                      flags=cls.TCP_RST | cls.TCP_ACK, window=cls.TCP_WINDOW))
-
-  @classmethod
-  def SYNACK(cls, version, srcaddr, dstaddr, packet):
-    ip = cls._GetIpLayer(version)
-    original = packet.getlayer("TCP")
-    return ("TCP SYN+ACK",
-            ip(src=srcaddr, dst=dstaddr) /
-            scapy.TCP(sport=original.dport, dport=original.sport,
-                      ack=original.seq + 1, seq=None,
-                      flags=cls.TCP_SYN | cls.TCP_ACK, window=None))
-
-  @classmethod
-  def ACK(cls, version, srcaddr, dstaddr, packet, payload=""):
-    ip = cls._GetIpLayer(version)
-    original = packet.getlayer("TCP")
-    was_syn_or_fin = (original.flags & (cls.TCP_SYN | cls.TCP_FIN)) != 0
-    ack_delta = was_syn_or_fin + len(original.payload)
-    desc = "TCP data" if payload else "TCP ACK"
-    flags = cls.TCP_ACK | cls.TCP_PSH if payload else cls.TCP_ACK
-    return (desc,
-            ip(src=srcaddr, dst=dstaddr) /
-            scapy.TCP(sport=original.dport, dport=original.sport,
-                      ack=original.seq + ack_delta, seq=original.ack,
-                      flags=flags, window=cls.TCP_WINDOW) /
-            payload)
-
-  @classmethod
-  def FIN(cls, version, srcaddr, dstaddr, packet):
-    ip = cls._GetIpLayer(version)
-    original = packet.getlayer("TCP")
-    was_fin = (original.flags & cls.TCP_FIN) != 0
-    return ("TCP FIN",
-            ip(src=srcaddr, dst=dstaddr) /
-            scapy.TCP(sport=original.dport, dport=original.sport,
-                      ack=original.seq + was_fin, seq=original.ack,
-                      flags=cls.TCP_ACK | cls.TCP_FIN, window=cls.TCP_WINDOW))
-
-  @classmethod
-  def GRE(cls, version, srcaddr, dstaddr, proto, packet):
-    if version == 4:
-      ip = scapy.IP(src=srcaddr, dst=dstaddr, proto=net_test.IPPROTO_GRE)
-    else:
-      ip = scapy.IPv6(src=srcaddr, dst=dstaddr, nh=net_test.IPPROTO_GRE)
-    packet = ip / scapy.GRE(proto=proto) / packet
-    return ("GRE packet", packet)
-
-  @classmethod
-  def ICMPPortUnreachable(cls, version, srcaddr, dstaddr, packet):
-    if version == 4:
-      # Linux hardcodes the ToS on ICMP errors to 0xc0 or greater because of
-      # RFC 1812 4.3.2.5 (!).
-      return ("ICMPv4 port unreachable",
-              scapy.IP(src=srcaddr, dst=dstaddr, proto=1, tos=0xc0) /
-              scapy.ICMPerror(type=3, code=3) / packet)
-    else:
-      return ("ICMPv6 port unreachable",
-              scapy.IPv6(src=srcaddr, dst=dstaddr) /
-              scapy.ICMPv6DestUnreach(code=4) / packet)
-
-  @classmethod
-  def ICMPPacketTooBig(cls, version, srcaddr, dstaddr, packet):
-    if version == 4:
-      return ("ICMPv4 fragmentation needed",
-              scapy.IP(src=srcaddr, dst=dstaddr, proto=1) /
-              scapy.ICMPerror(type=3, code=4, unused=1280) / str(packet)[:64])
-    else:
-      udp = packet.getlayer("UDP")
-      udp.payload = str(udp.payload)[:1280-40-8]
-      return ("ICMPv6 Packet Too Big",
-              scapy.IPv6(src=srcaddr, dst=dstaddr) /
-              scapy.ICMPv6PacketTooBig() / str(packet)[:1232])
-
-  @classmethod
-  def ICMPEcho(cls, version, srcaddr, dstaddr):
-    ip = cls._GetIpLayer(version)
-    icmp = {4: scapy.ICMP, 6: scapy.ICMPv6EchoRequest}[version]
-    packet = (ip(src=srcaddr, dst=dstaddr) /
-              icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
-    cls._SetPacketTos(packet, PING_TOS)
-    return ("ICMPv%d echo" % version, packet)
-
-  @classmethod
-  def ICMPReply(cls, version, srcaddr, dstaddr, packet):
-    ip = cls._GetIpLayer(version)
-    # Scapy doesn't provide an ICMP echo reply constructor.
-    icmpv4_reply = lambda **kwargs: scapy.ICMP(type=0, **kwargs)
-    icmp = {4: icmpv4_reply, 6: scapy.ICMPv6EchoReply}[version]
-    packet = (ip(src=srcaddr, dst=dstaddr) /
-              icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
-    # IPv6 only started copying the tclass to echo replies in 3.14.
-    if version == 4 or net_test.LINUX_VERSION >= (3, 14):
-      cls._SetPacketTos(packet, PING_TOS)
-    return ("ICMPv%d echo reply" % version, packet)
-
-  @classmethod
-  def NS(cls, srcaddr, tgtaddr, srcmac):
-    solicited = inet_pton(AF_INET6, tgtaddr)
-    last3bytes = tuple([ord(b) for b in solicited[-3:]])
-    solicited = "ff02::1:ff%02x:%02x%02x" % last3bytes
-    packet = (scapy.IPv6(src=srcaddr, dst=solicited) /
-              scapy.ICMPv6ND_NS(tgt=tgtaddr) /
-              scapy.ICMPv6NDOptSrcLLAddr(lladdr=srcmac))
-    return ("ICMPv6 NS", packet)
-
-  @classmethod
-  def NA(cls, srcaddr, dstaddr, srcmac):
-    packet = (scapy.IPv6(src=srcaddr, dst=dstaddr) /
-              scapy.ICMPv6ND_NA(tgt=srcaddr, R=0, S=1, O=1) /
-              scapy.ICMPv6NDOptDstLLAddr(lladdr=srcmac))
-    return ("ICMPv6 NA", packet)
-
-
 class InboundMarkingTest(multinetwork_base.MultiNetworkBaseTest):
 
   @classmethod
@@ -290,14 +96,14 @@ class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
 
     myaddr = self.MyAddress(version, netid)
     s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
-    s.bind((myaddr, PING_IDENT))
-    net_test.SetSocketTos(s, PING_TOS)
+    s.bind((myaddr, packets.PING_IDENT))
+    net_test.SetSocketTos(s, packets.PING_TOS)
 
-    desc, expected = Packets.ICMPEcho(version, myaddr, dstaddr)
+    desc, expected = packets.ICMPEcho(version, myaddr, dstaddr)
     msg = "IPv%d ping: expected %s on %s" % (
         version, desc, self.GetInterfaceName(netid))
 
-    s.sendto(packet + PING_PAYLOAD, (dstaddr, 19321))
+    s.sendto(packet + packets.PING_PAYLOAD, (dstaddr, 19321))
 
     self.ExpectPacketOn(netid, msg, expected)
 
@@ -307,7 +113,7 @@ class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
     if version == 6 and dstaddr.startswith("::ffff"):
       version = 4
     myaddr = self.MyAddress(version, netid)
-    desc, expected = Packets.SYN(53, version, myaddr, dstaddr,
+    desc, expected = packets.SYN(53, version, myaddr, dstaddr,
                                  sport=None, seq=None)
 
     # Non-blocking TCP connects always return EINPROGRESS.
@@ -323,7 +129,7 @@ class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
     if version == 6 and dstaddr.startswith("::ffff"):
       version = 4
     myaddr = self.MyAddress(version, netid)
-    desc, expected = Packets.UDP(version, myaddr, dstaddr, sport=None)
+    desc, expected = packets.UDP(version, myaddr, dstaddr, sport=None)
     msg = "IPv%s UDP %%s: expected %s on %s" % (
         version, desc, self.GetInterfaceName(netid))
 
@@ -343,7 +149,7 @@ class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
     inner_version = {4: 6, 6: 4}[version]
     inner_src = self.MyAddress(inner_version, netid)
     inner_dst = self.GetRemoteAddress(inner_version)
-    inner = str(Packets.UDP(inner_version, inner_src, inner_dst, sport=None)[1])
+    inner = str(packets.UDP(inner_version, inner_src, inner_dst, sport=None)[1])
 
     ethertype = {4: net_test.ETH_P_IP, 6: net_test.ETH_P_IPV6}[inner_version]
     # A GRE header can be as simple as two zero bytes and the ethertype.
@@ -351,7 +157,7 @@ class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
     myaddr = self.MyAddress(version, netid)
 
     s.sendto(packet, (dstaddr, IPPROTO_GRE))
-    desc, expected = Packets.GRE(version, myaddr, dstaddr, ethertype, inner)
+    desc, expected = packets.GRE(version, myaddr, dstaddr, ethertype, inner)
     msg = "Raw IPv%d GRE with inner IPv%d UDP: expected %s on %s" % (
         version, inner_version, desc, self.GetInterfaceName(netid))
     self.ExpectPacketOn(netid, msg, expected)
@@ -418,10 +224,10 @@ class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
 
       # Figure out what packets to expect.
       unspec = {4: "0.0.0.0", 6: "::"}[version]
-      sport = Packets.RandomPort()
+      sport = packets.RandomPort()
       s.bind((unspec, sport))
       dstaddr = {4: self.IPV4_ADDR, 6: self.IPV6_ADDR}[version]
-      desc, expected = Packets.UDP(version, unspec, dstaddr, sport)
+      desc, expected = packets.UDP(version, unspec, dstaddr, sport)
 
       # If we're testing connected sockets, connect the socket on the first
       # netid now.
@@ -524,7 +330,7 @@ class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
         sport = s.getsockname()[1]
         srcaddr = self.MyAddress(version, netid)
 
-        desc, expected = Packets.UDPWithOptions(version, srcaddr, dstaddr,
+        desc, expected = packets.UDPWithOptions(version, srcaddr, dstaddr,
                                                 sport=sport)
 
         msg = "IPv%d UDP using pktinfo routing: expected %s on %s" % (
@@ -579,31 +385,25 @@ class MarkTest(InboundMarkingTest):
         self._ReceiveAndExpectResponse(netid, packet, reply, msg)
 
   def SYNToClosedPort(self, *args):
-    return Packets.SYN(999, *args)
+    return packets.SYN(999, *args)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv4ICMPErrorsReflectMark(self):
-    self.CheckReflection(4, Packets.UDP, Packets.ICMPPortUnreachable)
+    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)
+    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)
+    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)
+    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)
+    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)
+    self.CheckReflection(6, self.SYNToClosedPort, packets.RST)
 
 
 class TCPAcceptTest(InboundMarkingTest):
@@ -646,7 +446,7 @@ class TCPAcceptTest(InboundMarkingTest):
 
   def CheckTCPConnection(self, mode, listensocket, netid, version,
                          myaddr, remoteaddr, packet, reply, msg):
-    establishing_ack = Packets.ACK(version, remoteaddr, myaddr, reply)[1]
+    establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
 
     # Attempt to confuse the kernel.
     self.BounceSocket(listensocket)
@@ -662,14 +462,14 @@ class TCPAcceptTest(InboundMarkingTest):
 
     try:
       # Check that data sent on the connection goes out on the right interface.
-      desc, data = Packets.ACK(version, myaddr, remoteaddr, establishing_ack,
+      desc, data = packets.ACK(version, myaddr, remoteaddr, establishing_ack,
                                payload=UDP_PAYLOAD)
       s.send(UDP_PAYLOAD)
       self.ExpectPacketOn(netid, msg + ": expecting %s" % desc, data)
       self.BounceSocket(s)
 
       # Keep up our end of the conversation.
-      ack = Packets.ACK(version, remoteaddr, myaddr, data)[1]
+      ack = packets.ACK(version, remoteaddr, myaddr, data)[1]
       self.BounceSocket(listensocket)
       self.ReceivePacketOn(netid, ack)
 
@@ -690,16 +490,16 @@ class TCPAcceptTest(InboundMarkingTest):
     # likely working, but a) extra tests are always good and b) extra packets
     # like the FIN (and retransmitted FINs) could cause later tests that expect
     # no packets to fail.
-    desc, fin = Packets.FIN(version, myaddr, remoteaddr, ack)
+    desc, fin = packets.FIN(version, myaddr, remoteaddr, ack)
     self.ExpectPacketOn(netid, msg + ": expecting %s after close" % desc, fin)
 
-    desc, finack = Packets.FIN(version, remoteaddr, myaddr, fin)
+    desc, finack = packets.FIN(version, remoteaddr, myaddr, fin)
     self.ReceivePacketOn(netid, finack)
 
     # Since we called close() earlier, the userspace socket object is gone, so
     # the socket has no UID. If we're doing UID routing, the ack might be routed
     # incorrectly. Not much we can do here.
-    desc, finackack = Packets.ACK(version, myaddr, remoteaddr, finack)
+    desc, finackack = packets.ACK(version, myaddr, remoteaddr, finack)
     if mode != self.MODE_UID:
       self.ExpectPacketOn(netid, msg + ": expecting final ack", finackack)
     else:
@@ -723,9 +523,8 @@ class TCPAcceptTest(InboundMarkingTest):
 
           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)
@@ -737,10 +536,10 @@ class TCPAcceptTest(InboundMarkingTest):
           # subsequent TCP connections use different source ports and
           # retransmissions from old connections don't confuse subsequent
           # tests.
-          desc, packet = Packets.SYN(listenport, version, remoteaddr, myaddr)
+          desc, packet = packets.SYN(listenport, version, remoteaddr, myaddr)
 
           if mode:
-            reply_desc, reply = Packets.SYNACK(version, myaddr, remoteaddr,
+            reply_desc, reply = packets.SYNACK(version, myaddr, remoteaddr,
                                                packet)
           else:
             reply_desc, reply = None, None
@@ -756,11 +555,9 @@ class TCPAcceptTest(InboundMarkingTest):
     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])
 
@@ -798,6 +595,7 @@ class RATest(multinetwork_base.MultiNetworkBaseTest):
 
     try:
       CheckIPv6Connectivity(True)
+      self.SetIPv6SysctlOnAllIfaces("accept_ra", 1)
       self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
       CheckIPv6Connectivity(False)
     finally:
@@ -822,14 +620,14 @@ class RATest(multinetwork_base.MultiNetworkBaseTest):
       # Expect an NS for that destination on the interface.
       myaddr = self.MyAddress(6, netid)
       mymac = self.MyMacAddress(netid)
-      desc, expected = Packets.NS(myaddr, dstaddr, mymac)
+      desc, expected = packets.NS(myaddr, dstaddr, mymac)
       msg = "Sending UDP packet to on-link destination: expecting %s" % desc
       time.sleep(0.0001)  # Required to make the test work on kernel 3.1(!)
       self.ExpectPacketOn(netid, msg, expected)
 
       # Send an NA.
       tgtmac = "02:00:00:00:%02x:99" % netid
-      _, reply = Packets.NA(dstaddr, myaddr, tgtmac)
+      _, reply = packets.NA(dstaddr, myaddr, tgtmac)
       # Don't use ReceivePacketOn, since that uses the router's MAC address as
       # the source. Instead, construct our own Ethernet header with source
       # MAC of tgtmac.
@@ -839,7 +637,7 @@ class RATest(multinetwork_base.MultiNetworkBaseTest):
       # Expect the kernel to send the original UDP packet now that the ND cache
       # entry has been populated.
       sport = s.getsockname()[1]
-      desc, expected = Packets.UDP(6, myaddr, dstaddr, sport=sport)
+      desc, expected = packets.UDP(6, myaddr, dstaddr, sport=sport)
       msg = "After NA response, expecting %s" % desc
       self.ExpectPacketOn(netid, msg, expected)
 
@@ -917,10 +715,10 @@ class PMTUTest(InboundMarkingTest):
 
         # Send a packet and receive a packet too big.
         SendBigPacket(version, s, dstaddr, netid, payload)
-        packets = self.ReadAllPacketsOn(netid)
-        self.assertEquals(1, len(packets))
-        _, toobig = Packets.ICMPPacketTooBig(version, intermediate, srcaddr,
-                                             packets[0])
+        received = self.ReadAllPacketsOn(netid)
+        self.assertEquals(1, len(received))
+        _, toobig = packets.ICMPPacketTooBig(version, intermediate, srcaddr,
+                                             received[0])
         self.ReceivePacketOn(netid, toobig)
 
         # Check that another send on the same socket returns EMSGSIZE.
@@ -953,6 +751,16 @@ class PMTUTest(InboundMarkingTest):
         self.assertEquals(metrics["RTAX_MTU"], 1280)
 
   def testIPv4BasicPMTU(self):
+    """Tests IPv4 path MTU discovery.
+
+    Relevant kernel commits:
+      upstream net-next:
+        6a66271 ipv4, fib: pass LOOPBACK_IFINDEX instead of 0 to flowi4_iif
+
+      android-3.10:
+        4bc64dd ipv4, fib: pass LOOPBACK_IFINDEX instead of 0 to flowi4_iif
+    """
+
     self.CheckPMTU(4, True, ["mark", "oif"])
     self.CheckPMTU(4, False, ["mark", "oif"])
 
@@ -975,7 +783,6 @@ class PMTUTest(InboundMarkingTest):
   # 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 +790,6 @@ class PMTUTest(InboundMarkingTest):
     finally:
       self.SetMarkReflectSysctls(0)
 
-  @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
   def testIPv6UnmarkedSocketPMTU(self):
     self.SetMarkReflectSysctls(1)
     try:
@@ -994,6 +800,17 @@ class PMTUTest(InboundMarkingTest):
 
 @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
 class UidRoutingTest(multinetwork_base.MultiNetworkBaseTest):
+  """Tests that per-UID routing works properly.
+
+  Relevant kernel commits:
+    android-3.4:
+      0b42874 net: core: Support UID-based routing.
+      0836a0c Handle 'sk' being NULL in UID-based routing.
+
+    android-3.10:
+      99a6ea4 net: core: Support UID-based routing.
+      455b09d Handle 'sk' being NULL in UID-based routing.
+  """
 
   def GetRulesAtPriority(self, version, priority):
     rules = self.iproute.DumpRules(version)
diff --git a/tests/net_test/neighbour_test.py b/tests/net_test/neighbour_test.py
new file mode 100755 (executable)
index 0000000..1e7739e
--- /dev/null
@@ -0,0 +1,297 @@
+#!/usr/bin/python
+#
+# 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.
+
+import errno
+import random
+from socket import *  # pylint: disable=wildcard-import
+import time
+import unittest
+
+from scapy import all as scapy
+
+import multinetwork_base
+import net_test
+
+
+RTMGRP_NEIGH = 4
+
+NUD_INCOMPLETE = 0x01
+NUD_REACHABLE = 0x02
+NUD_STALE = 0x04
+NUD_DELAY = 0x08
+NUD_PROBE = 0x10
+NUD_FAILED = 0x20
+NUD_PERMANENT = 0x80
+
+
+# TODO: Support IPv4.
+class NeighbourTest(multinetwork_base.MultiNetworkBaseTest):
+
+  # Set a 100-ms retrans timer so we can test for ND retransmits without
+  # waiting too long. Apparently this cannot go below 500ms.
+  RETRANS_TIME_MS = 500
+
+  # This can only be in seconds, so 1000 is the minimum.
+  DELAY_TIME_MS = 1000
+
+  # Unfortunately, this must be above the delay timer or the kernel ND code will
+  # not behave correctly (e.g., go straight from REACHABLE into DELAY). This is
+  # is fuzzed by the kernel from 0.5x to 1.5x of its value, so we need a value
+  # that's 2x the delay timer.
+  REACHABLE_TIME_MS = 2 * DELAY_TIME_MS
+
+  @classmethod
+  def setUpClass(cls):
+    super(NeighbourTest, cls).setUpClass()
+    for netid in cls.tuns:
+      iface = cls.GetInterfaceName(netid)
+      # This can't be set in an RA.
+      cls.SetSysctl(
+          "/proc/sys/net/ipv6/neigh/%s/delay_first_probe_time" % iface,
+          cls.DELAY_TIME_MS / 1000)
+
+  def setUp(self):
+    super(NeighbourTest, self).setUp()
+
+    for netid in self.tuns:
+      # Clear the ND cache entries for all routers, so each test starts with
+      # the IPv6 default router in state STALE.
+      addr = self._RouterAddress(netid, 6)
+      ifindex = self.ifindices[netid]
+      self.iproute.UpdateNeighbour(6, addr, None, ifindex, NUD_FAILED)
+
+      # Configure IPv6 by sending an RA.
+      self.SendRA(netid,
+                  retranstimer=self.RETRANS_TIME_MS,
+                  reachabletime=self.REACHABLE_TIME_MS)
+
+    self.sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)
+    self.sock.bind((0, RTMGRP_NEIGH))
+    net_test.SetNonBlocking(self.sock)
+
+    self.netid = random.choice(self.tuns.keys())
+    self.ifindex = self.ifindices[self.netid]
+
+  def GetNeighbour(self, addr):
+    version = 6 if ":" in addr else 4
+    for msg, args in self.iproute.DumpNeighbours(version):
+      if args["NDA_DST"] == addr:
+        return msg, args
+
+  def GetNdEntry(self, addr):
+    return self.GetNeighbour(addr)
+
+  def CheckNoNdEvents(self):
+    self.assertRaisesErrno(errno.EAGAIN, self.sock.recvfrom, 4096, MSG_PEEK)
+
+  def assertNeighbourState(self, state, addr):
+    self.assertEquals(state, self.GetNdEntry(addr)[0].state)
+
+  def assertNeighbourAttr(self, addr, name, value):
+    self.assertEquals(value, self.GetNdEntry(addr)[1][name])
+
+  def ExpectNeighbourNotification(self, addr, state, attrs=None):
+    msg = self.sock.recv(4096)
+    msg, actual_attrs = self.iproute.ParseNeighbourMessage(msg)
+    self.assertEquals(addr, actual_attrs["NDA_DST"])
+    self.assertEquals(state, msg.state)
+    if attrs:
+      for name in attrs:
+        self.assertEquals(attrs[name], actual_attrs[name])
+
+  def ExpectProbe(self, is_unicast, addr):
+    version = 6 if ":" in addr else 4
+    if version == 6:
+      llsrc = self.MyMacAddress(self.netid)
+      if is_unicast:
+        src = self.MyLinkLocalAddress(self.netid)
+        dst = addr
+      else:
+        solicited = inet_pton(AF_INET6, addr)
+        last3bytes = tuple([ord(b) for b in solicited[-3:]])
+        dst = "ff02::1:ff%02x:%02x%02x" % last3bytes
+        src = self.MyAddress(6, self.netid)
+      expected = (
+          scapy.IPv6(src=src, dst=dst) /
+          scapy.ICMPv6ND_NS(tgt=addr) /
+          scapy.ICMPv6NDOptSrcLLAddr(lladdr=llsrc)
+      )
+      msg = "%s probe" % ("Unicast" if is_unicast else "Multicast")
+      self.ExpectPacketOn(self.netid, msg, expected)
+    else:
+      raise NotImplementedError
+
+  def ExpectUnicastProbe(self, addr):
+    self.ExpectProbe(True, addr)
+
+  def ExpectMulticastNS(self, addr):
+    self.ExpectProbe(False, addr)
+
+  def ReceiveUnicastAdvertisement(self, addr, mac, srcaddr=None, dstaddr=None,
+                                  S=1, O=0, R=1):
+    version = 6 if ":" in addr else 4
+    if srcaddr is None:
+      srcaddr = addr
+    if dstaddr is None:
+      dstaddr = self.MyLinkLocalAddress(self.netid)
+    if version == 6:
+      packet = (
+          scapy.Ether(src=mac, dst=self.MyMacAddress(self.netid)) /
+          scapy.IPv6(src=srcaddr, dst=dstaddr) /
+          scapy.ICMPv6ND_NA(tgt=addr, S=S, O=O, R=R) /
+          scapy.ICMPv6NDOptDstLLAddr(lladdr=mac)
+      )
+      self.ReceiveEtherPacketOn(self.netid, packet)
+    else:
+      raise NotImplementedError
+
+  def MonitorSleepMs(self, interval, addr):
+    slept = 0
+    while slept < interval:
+      sleep_ms = min(100, interval - slept)
+      time.sleep(sleep_ms / 1000.0)
+      slept += sleep_ms
+      print self.GetNdEntry(addr)
+
+  def MonitorSleep(self, intervalseconds, addr):
+    self.MonitorSleepMs(intervalseconds * 1000, addr)
+
+  def SleepMs(self, ms):
+    time.sleep(ms / 1000.0)
+
+  def testNotifications(self):
+    """Tests neighbour notifications.
+
+    Relevant kernel commits:
+      upstream net-next:
+        765c9c6 neigh: Better handling of transition to NUD_PROBE state
+        53385d2 neigh: Netlink notification for administrative NUD state change
+          (only checked on kernel v3.13+, not on v3.10)
+
+      android-3.10:
+        e4a6d6b neigh: Better handling of transition to NUD_PROBE state
+
+      android-3.18:
+        2011e72 neigh: Better handling of transition to NUD_PROBE state
+    """
+
+    router4 = self._RouterAddress(self.netid, 4)
+    router6 = self._RouterAddress(self.netid, 6)
+    self.assertNeighbourState(NUD_PERMANENT, router4)
+    self.assertNeighbourState(NUD_STALE, router6)
+
+    # Send a packet and check that we go into DELAY.
+    routing_mode = random.choice(["mark", "oif", "uid"])
+    s = self.BuildSocket(6, net_test.UDPSocket, self.netid, routing_mode)
+    s.connect((net_test.IPV6_ADDR, 53))
+    s.send(net_test.UDP_PAYLOAD)
+    self.assertNeighbourState(NUD_DELAY, router6)
+
+    # Wait for the probe interval, then check that we're in PROBE, and that the
+    # kernel has notified us.
+    self.SleepMs(self.DELAY_TIME_MS)
+    self.ExpectNeighbourNotification(router6, NUD_PROBE)
+    self.assertNeighbourState(NUD_PROBE, router6)
+    self.ExpectUnicastProbe(router6)
+
+    # Respond to the NS and verify we're in REACHABLE again.
+    self.ReceiveUnicastAdvertisement(router6, self.RouterMacAddress(self.netid))
+    self.assertNeighbourState(NUD_REACHABLE, router6)
+    if net_test.LINUX_VERSION >= (3, 13, 0):
+      # commit 53385d2 (v3.13) "neigh: Netlink notification for administrative
+      # NUD state change" produces notifications for NUD_REACHABLE, but these
+      # are not generated on earlier kernels.
+      self.ExpectNeighbourNotification(router6, NUD_REACHABLE)
+
+    # Wait until the reachable time has passed, and verify we're in STALE.
+    self.SleepMs(self.REACHABLE_TIME_MS * 1.5)
+    self.assertNeighbourState(NUD_STALE, router6)
+    self.ExpectNeighbourNotification(router6, NUD_STALE)
+
+    # Send a packet, and verify we go into DELAY and then to PROBE.
+    s.send(net_test.UDP_PAYLOAD)
+    self.assertNeighbourState(NUD_DELAY, router6)
+    self.SleepMs(self.DELAY_TIME_MS)
+    self.assertNeighbourState(NUD_PROBE, router6)
+    self.ExpectNeighbourNotification(router6, NUD_PROBE)
+
+    # Wait for the probes to time out, and expect a FAILED notification.
+    self.assertNeighbourAttr(router6, "NDA_PROBES", 1)
+    self.ExpectUnicastProbe(router6)
+
+    self.SleepMs(self.RETRANS_TIME_MS)
+    self.ExpectUnicastProbe(router6)
+    self.assertNeighbourAttr(router6, "NDA_PROBES", 2)
+
+    self.SleepMs(self.RETRANS_TIME_MS)
+    self.ExpectUnicastProbe(router6)
+    self.assertNeighbourAttr(router6, "NDA_PROBES", 3)
+
+    self.SleepMs(self.RETRANS_TIME_MS)
+    self.assertNeighbourState(NUD_FAILED, router6)
+    self.ExpectNeighbourNotification(router6, NUD_FAILED, {"NDA_PROBES": 3})
+
+  def testRepeatedProbes(self):
+    router4 = self._RouterAddress(self.netid, 4)
+    router6 = self._RouterAddress(self.netid, 6)
+    routermac = self.RouterMacAddress(self.netid)
+    self.assertNeighbourState(NUD_PERMANENT, router4)
+    self.assertNeighbourState(NUD_STALE, router6)
+
+    def ForceProbe(addr, mac):
+      self.iproute.UpdateNeighbour(6, addr, None, self.ifindex, NUD_PROBE)
+      self.assertNeighbourState(NUD_PROBE, addr)
+      self.SleepMs(1)  # TODO: Why is this necessary?
+      self.assertNeighbourState(NUD_PROBE, addr)
+      self.ExpectUnicastProbe(addr)
+      self.ReceiveUnicastAdvertisement(addr, mac)
+      self.assertNeighbourState(NUD_REACHABLE, addr)
+
+    for _ in xrange(5):
+      ForceProbe(router6, routermac)
+
+  def testIsRouterFlag(self):
+    router6 = self._RouterAddress(self.netid, 6)
+    self.assertNeighbourState(NUD_STALE, router6)
+
+    # Get into FAILED.
+    ifindex = self.ifindices[self.netid]
+    self.iproute.UpdateNeighbour(6, router6, None, ifindex, NUD_FAILED)
+    self.ExpectNeighbourNotification(router6, NUD_FAILED)
+    self.assertNeighbourState(NUD_FAILED, router6)
+
+    time.sleep(1)
+
+    # Send another packet and expect a multicast NS.
+    routing_mode = random.choice(["mark", "oif", "uid"])
+    s = self.BuildSocket(6, net_test.UDPSocket, self.netid, routing_mode)
+    s.connect((net_test.IPV6_ADDR, 53))
+    s.send(net_test.UDP_PAYLOAD)
+    self.ExpectMulticastNS(router6)
+
+    # Receive a unicast NA with the R flag set to 0.
+    self.ReceiveUnicastAdvertisement(router6, self.RouterMacAddress(self.netid),
+                                     srcaddr=self._RouterAddress(self.netid, 6),
+                                     dstaddr=self.MyAddress(6, self.netid),
+                                     S=1, O=0, R=0)
+
+    # Expect that this takes us to REACHABLE.
+    self.ExpectNeighbourNotification(router6, NUD_REACHABLE)
+    self.assertNeighbourState(NUD_REACHABLE, router6)
+
+
+if __name__ == "__main__":
+  unittest.main()
index a87b71b..d7ea013 100755 (executable)
@@ -16,6 +16,8 @@
 
 import fcntl
 import os
+import random
+import re
 from socket import *  # pylint: disable=wildcard-import
 import struct
 import unittest
@@ -28,11 +30,14 @@ IPV6_RECVERR = 25
 IP_TRANSPARENT = 19
 IPV6_TRANSPARENT = 75
 IPV6_TCLASS = 67
-SO_BINDTODEVICE = 25
-SO_MARK = 36
 IPV6_FLOWLABEL_MGR = 32
 IPV6_FLOWINFO_SEND = 33
 
+SO_BINDTODEVICE = 25
+SO_MARK = 36
+SO_PROTOCOL = 38
+SO_DOMAIN = 39
+
 ETH_P_IP = 0x0800
 ETH_P_IPV6 = 0x86dd
 
@@ -51,6 +56,8 @@ IPV6_FL_S_NONE = 0
 IPV6_FL_S_EXCL = 1
 IPV6_FL_S_ANY = 255
 
+IFNAMSIZ = 16
+
 IPV4_PING = "\x08\x00\x00\x00\x0a\xce\x00\x03"
 IPV6_PING = "\x80\x00\x00\x00\x0a\xce\x00\x03"
 
@@ -63,6 +70,12 @@ IPV6_SEQ_DGRAM_HEADER = ("  sl  "
                          "st tx_queue rx_queue tr tm->when retrnsmt"
                          "   uid  timeout inode ref pointer drops\n")
 
+# Arbitrary packet payload.
+UDP_PAYLOAD = str(scapy.DNS(rd=1,
+                            id=random.randint(0, 65535),
+                            qd=scapy.DNSQR(qname="wWW.GoOGle.CoM",
+                                           qtype="AAAA")))
+
 # Unix group to use if we want to open sockets as non-root.
 AID_INET = 3003
 
@@ -140,11 +153,29 @@ def RawGRESocket(family):
   return s
 
 
+def DisableLinger(sock):
+  sock.setsockopt(SOL_SOCKET, SO_LINGER, struct.pack("ii", 1, 0))
+
+
+def CreateSocketPair(family, socktype, addr):
+  clientsock = socket(family, socktype, 0)
+  listensock = socket(family, socktype, 0)
+  listensock.bind((addr, 0))
+  addr = listensock.getsockname()
+  listensock.listen(1)
+  clientsock.connect(addr)
+  acceptedsock, _ = listensock.accept()
+  DisableLinger(clientsock)
+  DisableLinger(acceptedsock)
+  listensock.close()
+  return clientsock, acceptedsock
+
+
 def GetInterfaceIndex(ifname):
   s = IPv4PingSocket()
-  ifr = struct.pack("16si", ifname, 0)
+  ifr = struct.pack("%dsi" % IFNAMSIZ, ifname, 0)
   ifr = fcntl.ioctl(s, scapy.SIOCGIFINDEX, ifr)
-  return struct.unpack("16si", ifr)[1]
+  return struct.unpack("%dsi" % IFNAMSIZ, ifr)[1]
 
 
 def SetInterfaceHWAddr(ifname, hwaddr):
@@ -153,20 +184,20 @@ def SetInterfaceHWAddr(ifname, hwaddr):
   hwaddr = hwaddr.decode("hex")
   if len(hwaddr) != 6:
     raise ValueError("Unknown hardware address length %d" % len(hwaddr))
-  ifr = struct.pack("16sH6s", ifname, scapy.ARPHDR_ETHER, hwaddr)
+  ifr = struct.pack("%dsH6s" % IFNAMSIZ, ifname, scapy.ARPHDR_ETHER, hwaddr)
   fcntl.ioctl(s, SIOCSIFHWADDR, ifr)
 
 
 def SetInterfaceState(ifname, up):
   s = IPv4PingSocket()
-  ifr = struct.pack("16sH", ifname, 0)
+  ifr = struct.pack("%dsH" % IFNAMSIZ, ifname, 0)
   ifr = fcntl.ioctl(s, scapy.SIOCGIFFLAGS, ifr)
-  _, flags = struct.unpack("16sH", ifr)
+  _, flags = struct.unpack("%dsH" % IFNAMSIZ, ifr)
   if up:
     flags |= scapy.IFF_UP
   else:
     flags &= ~scapy.IFF_UP
-  ifr = struct.pack("16sH", ifname, flags)
+  ifr = struct.pack("%dsH" % IFNAMSIZ, ifname, flags)
   ifr = fcntl.ioctl(s, scapy.SIOCSIFFLAGS, ifr)
 
 
@@ -285,7 +316,6 @@ except ValueError:
 
 
 class RunAsUid(object):
-
   """Context guard to run a code block as a given UID."""
 
   def __init__(self, uid):
@@ -311,6 +341,54 @@ class NetworkTest(unittest.TestCase):
     msg = os.strerror(err_num)
     self.assertRaisesRegexp(EnvironmentError, msg, f, *args)
 
+  def ReadProcNetSocket(self, protocol):
+    # Read file.
+    filename = "/proc/net/%s" % protocol
+    lines = open(filename).readlines()
+
+    # Possibly check, and strip, header.
+    if protocol in ["icmp6", "raw6", "udp6"]:
+      self.assertEqual(IPV6_SEQ_DGRAM_HEADER, lines[0])
+    lines = lines[1:]
+
+    # Check contents.
+    if protocol.endswith("6"):
+      addrlen = 32
+    else:
+      addrlen = 8
+
+    if protocol.startswith("tcp"):
+      # Real sockets have 5 extra numbers, timewait sockets have none.
+      end_regexp = "(| +[0-9]+ [0-9]+ [0-9]+ [0-9]+ -?[0-9]+|)$"
+    elif re.match("icmp|udp|raw", protocol):
+      # Drops.
+      end_regexp = " +([0-9]+) *$"
+    else:
+      raise ValueError("Don't know how to parse %s" % filename)
+
+    regexp = re.compile(r" *(\d+): "                    # bucket
+                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # srcaddr, port
+                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # dstaddr, port
+                        "([0-9A-F][0-9A-F]) "           # state
+                        "([0-9A-F]{8}:[0-9A-F]{8}) "    # mem
+                        "([0-9A-F]{2}:[0-9A-F]{8}) "    # ?
+                        "([0-9A-F]{8}) +"               # ?
+                        "([0-9]+) +"                    # uid
+                        "([0-9]+) +"                    # timeout
+                        "([0-9]+) +"                    # inode
+                        "([0-9]+) +"                    # refcnt
+                        "([0-9a-f]+)"                   # sp
+                        "%s"                            # icmp has spaces
+                        % (addrlen, addrlen, end_regexp))
+    # Return a list of lists with only source / dest addresses for now.
+    # TODO: consider returning a dict or namedtuple instead.
+    out = []
+    for line in lines:
+      (_, src, dst, state, mem,
+       _, _, uid, _, _, refcnt, _, extra) = regexp.match(line).groups()
+      out.append([src, dst, state, mem, uid, refcnt, extra])
+    return out
+
 
 if __name__ == "__main__":
   unittest.main()
diff --git a/tests/net_test/netlink.py b/tests/net_test/netlink.py
new file mode 100644 (file)
index 0000000..2b8f744
--- /dev/null
@@ -0,0 +1,255 @@
+#!/usr/bin/python
+#
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Partial Python implementation of iproute functionality."""
+
+# pylint: disable=g-bad-todo
+
+import errno
+import os
+import socket
+import struct
+import sys
+
+import cstruct
+
+
+# Request constants.
+NLM_F_REQUEST = 1
+NLM_F_ACK = 4
+NLM_F_REPLACE = 0x100
+NLM_F_EXCL = 0x200
+NLM_F_CREATE = 0x400
+NLM_F_DUMP = 0x300
+
+# Message types.
+NLMSG_ERROR = 2
+NLMSG_DONE = 3
+
+# Data structure formats.
+# These aren't constants, they're classes. So, pylint: disable=invalid-name
+NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
+NLMsgErr = cstruct.Struct("NLMsgErr", "=i", "error")
+NLAttr = cstruct.Struct("NLAttr", "=HH", "nla_len nla_type")
+
+# Alignment / padding.
+NLA_ALIGNTO = 4
+
+
+def PaddedLength(length):
+  # TODO: This padding is probably overly simplistic.
+  return NLA_ALIGNTO * ((length / NLA_ALIGNTO) + (length % NLA_ALIGNTO != 0))
+
+
+class NetlinkSocket(object):
+  """A basic netlink socket object."""
+
+  BUFSIZE = 65536
+  DEBUG = False
+  # List of netlink messages to print, e.g., [], ["NEIGH", "ROUTE"], or ["ALL"]
+  NL_DEBUG = []
+
+  def _Debug(self, s):
+    if self.DEBUG:
+      print s
+
+  def _NlAttr(self, nla_type, data):
+    datalen = len(data)
+    # Pad the data if it's not a multiple of NLA_ALIGNTO bytes long.
+    padding = "\x00" * (PaddedLength(datalen) - datalen)
+    nla_len = datalen + len(NLAttr)
+    return NLAttr((nla_len, nla_type)).Pack() + data + padding
+
+  def _NlAttrU32(self, nla_type, value):
+    return self._NlAttr(nla_type, struct.pack("=I", value))
+
+  def _GetConstantName(self, module, value, prefix):
+    thismodule = sys.modules[module]
+    for name in dir(thismodule):
+      if name.startswith("INET_DIAG_BC"):
+        break
+      if (name.startswith(prefix) and
+          not name.startswith(prefix + "F_") and
+          name.isupper() and getattr(thismodule, name) == value):
+          return name
+    return value
+
+  def _Decode(self, command, msg, nla_type, nla_data):
+    """No-op, nonspecific version of decode."""
+    return nla_type, nla_data
+
+  def _ParseAttributes(self, command, family, msg, data):
+    """Parses and decodes netlink attributes.
+
+    Takes a block of NLAttr data structures, decodes them using Decode, and
+    returns the result in a dict keyed by attribute number.
+
+    Args:
+      command: An integer, the rtnetlink command being carried out.
+      family: The address family.
+      msg: A Struct, the type of the data after the netlink header.
+      data: A byte string containing a sequence of NLAttr data structures.
+
+    Returns:
+      A dictionary mapping attribute types (integers) to decoded values.
+
+    Raises:
+      ValueError: There was a duplicate attribute type.
+    """
+    attributes = {}
+    while data:
+      # Read the nlattr header.
+      nla, data = cstruct.Read(data, NLAttr)
+
+      # Read the data.
+      datalen = nla.nla_len - len(nla)
+      padded_len = PaddedLength(nla.nla_len) - len(nla)
+      nla_data, data = data[:datalen], data[padded_len:]
+
+      # If it's an attribute we know about, try to decode it.
+      nla_name, nla_data = self._Decode(command, msg, nla.nla_type, nla_data)
+
+      # We only support unique attributes for now, except for INET_DIAG_NONE,
+      # which can appear more than once but doesn't seem to contain any data.
+      if nla_name in attributes and nla_name != "INET_DIAG_NONE":
+        raise ValueError("Duplicate attribute %s" % nla_name)
+
+      attributes[nla_name] = nla_data
+      self._Debug("      %s" % str((nla_name, nla_data)))
+
+    return attributes
+
+  def __init__(self):
+    # Global sequence number.
+    self.seq = 0
+    self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.FAMILY)
+    self.sock.connect((0, 0))  # The kernel.
+    self.pid = self.sock.getsockname()[1]
+
+  def _Send(self, msg):
+    # self._Debug(msg.encode("hex"))
+    self.seq += 1
+    self.sock.send(msg)
+
+  def _Recv(self):
+    data = self.sock.recv(self.BUFSIZE)
+    # self._Debug(data.encode("hex"))
+    return data
+
+  def _ExpectDone(self):
+    response = self._Recv()
+    hdr = NLMsgHdr(response)
+    if hdr.type != NLMSG_DONE:
+      raise ValueError("Expected DONE, got type %d" % hdr.type)
+
+  def _ParseAck(self, response):
+    # Find the error code.
+    hdr, data = cstruct.Read(response, NLMsgHdr)
+    if hdr.type == NLMSG_ERROR:
+      error = NLMsgErr(data).error
+      if error:
+        raise IOError(error, os.strerror(-error))
+    else:
+      raise ValueError("Expected ACK, got type %d" % hdr.type)
+
+  def _ExpectAck(self):
+    response = self._Recv()
+    self._ParseAck(response)
+
+  def _SendNlRequest(self, command, data, flags):
+    """Sends a netlink request and expects an ack."""
+    length = len(NLMsgHdr) + len(data)
+    nlmsg = NLMsgHdr((length, command, flags, self.seq, self.pid)).Pack()
+
+    self.MaybeDebugCommand(command, nlmsg + data)
+
+    # Send the message.
+    self._Send(nlmsg + data)
+
+    if flags & NLM_F_ACK:
+      self._ExpectAck()
+
+  def _ParseNLMsg(self, data, msgtype):
+    """Parses a Netlink message into a header and a dictionary of attributes."""
+    nlmsghdr, data = cstruct.Read(data, NLMsgHdr)
+    self._Debug("  %s" % nlmsghdr)
+
+    if nlmsghdr.type == NLMSG_ERROR or nlmsghdr.type == NLMSG_DONE:
+      print "done"
+      return (None, None), data
+
+    nlmsg, data = cstruct.Read(data, msgtype)
+    self._Debug("    %s" % nlmsg)
+
+    # Parse the attributes in the nlmsg.
+    attrlen = nlmsghdr.length - len(nlmsghdr) - len(nlmsg)
+    attributes = self._ParseAttributes(nlmsghdr.type, nlmsg.family,
+                                       nlmsg, data[:attrlen])
+    data = data[attrlen:]
+    return (nlmsg, attributes), data
+
+  def _GetMsg(self, msgtype):
+    data = self._Recv()
+    if NLMsgHdr(data).type == NLMSG_ERROR:
+      self._ParseAck(data)
+    return self._ParseNLMsg(data, msgtype)[0]
+
+  def _GetMsgList(self, msgtype, data, expect_done):
+    out = []
+    while data:
+      msg, data = self._ParseNLMsg(data, msgtype)
+      if msg is None:
+        break
+      out.append(msg)
+    if expect_done:
+      self._ExpectDone()
+    return out
+
+  def _Dump(self, command, msg, msgtype, attrs):
+    """Sends a dump request and returns a list of decoded messages.
+
+    Args:
+      command: An integer, the command to run (e.g., RTM_NEWADDR).
+      msg: A string, the raw bytes of the request (e.g., a packed RTMsg).
+      msgtype: A cstruct.Struct, the data type to parse the dump results as.
+      attrs: A string, the raw bytes of any request attributes to include.
+
+    Returns:
+      A list of (msg, attrs) tuples where msg is of type msgtype and attrs is
+      a dict of attributes.
+    """
+    # Create a netlink dump request containing the msg.
+    flags = NLM_F_DUMP | NLM_F_REQUEST
+    length = len(NLMsgHdr) + len(msg) + len(attrs)
+    nlmsghdr = NLMsgHdr((length, command, flags, self.seq, self.pid))
+
+    # Send the request.
+    self._Send(nlmsghdr.Pack() + msg.Pack() + attrs)
+
+    # Keep reading netlink messages until we get a NLMSG_DONE.
+    out = []
+    while True:
+      data = self._Recv()
+      response_type = NLMsgHdr(data).type
+      if response_type == NLMSG_DONE:
+        break
+      elif response_type == NLMSG_ERROR:
+        # Likely means that the kernel didn't like our dump request.
+        # Parse the error and throw an exception.
+        self._ParseAck(data)
+      out.extend(self._GetMsgList(msgtype, data, False))
+
+    return out
diff --git a/tests/net_test/packets.py b/tests/net_test/packets.py
new file mode 100644 (file)
index 0000000..c02adc0
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/python
+#
+# 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.
+
+import random
+
+from scapy import all as scapy
+from socket import *
+
+import net_test
+
+TCP_FIN = 1
+TCP_SYN = 2
+TCP_RST = 4
+TCP_PSH = 8
+TCP_ACK = 16
+
+TCP_SEQ = 1692871236
+TCP_WINDOW = 14400
+
+PING_IDENT = 0xff19
+PING_PAYLOAD = "foobarbaz"
+PING_SEQ = 3
+PING_TOS = 0x83
+
+# For brevity.
+UDP_PAYLOAD = net_test.UDP_PAYLOAD
+
+
+def RandomPort():
+  return random.randint(1025, 65535)
+
+def _GetIpLayer(version):
+  return {4: scapy.IP, 6: scapy.IPv6}[version]
+
+def _SetPacketTos(packet, tos):
+  if isinstance(packet, scapy.IPv6):
+    packet.tc = tos
+  elif isinstance(packet, scapy.IP):
+    packet.tos = tos
+  else:
+    raise ValueError("Can't find ToS Field")
+
+def UDP(version, srcaddr, dstaddr, sport=0):
+  ip = _GetIpLayer(version)
+  # Can't just use "if sport" because None has meaning (it means unspecified).
+  if sport == 0:
+    sport = RandomPort()
+  return ("UDPv%d packet" % version,
+          ip(src=srcaddr, dst=dstaddr) /
+          scapy.UDP(sport=sport, dport=53) / UDP_PAYLOAD)
+
+def UDPWithOptions(version, srcaddr, dstaddr, sport=0):
+  if version == 4:
+    packet = (scapy.IP(src=srcaddr, dst=dstaddr, ttl=39, tos=0x83) /
+              scapy.UDP(sport=sport, dport=53) /
+              UDP_PAYLOAD)
+  else:
+    packet = (scapy.IPv6(src=srcaddr, dst=dstaddr,
+                         fl=0xbeef, hlim=39, tc=0x83) /
+              scapy.UDP(sport=sport, dport=53) /
+              UDP_PAYLOAD)
+  return ("UDPv%d packet with options" % version, packet)
+
+def SYN(dport, version, srcaddr, dstaddr, sport=0, seq=TCP_SEQ):
+  ip = _GetIpLayer(version)
+  if sport == 0:
+    sport = RandomPort()
+  return ("TCP SYN",
+          ip(src=srcaddr, dst=dstaddr) /
+          scapy.TCP(sport=sport, dport=dport,
+                    seq=seq, ack=0,
+                    flags=TCP_SYN, window=TCP_WINDOW))
+
+def RST(version, srcaddr, dstaddr, packet):
+  ip = _GetIpLayer(version)
+  original = packet.getlayer("TCP")
+  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
+  return ("TCP RST",
+          ip(src=srcaddr, dst=dstaddr) /
+          scapy.TCP(sport=original.dport, dport=original.sport,
+                    ack=original.seq + was_syn_or_fin, seq=None,
+                    flags=TCP_RST | TCP_ACK, window=TCP_WINDOW))
+
+def SYNACK(version, srcaddr, dstaddr, packet):
+  ip = _GetIpLayer(version)
+  original = packet.getlayer("TCP")
+  return ("TCP SYN+ACK",
+          ip(src=srcaddr, dst=dstaddr) /
+          scapy.TCP(sport=original.dport, dport=original.sport,
+                    ack=original.seq + 1, seq=None,
+                    flags=TCP_SYN | TCP_ACK, window=None))
+
+def ACK(version, srcaddr, dstaddr, packet, payload=""):
+  ip = _GetIpLayer(version)
+  original = packet.getlayer("TCP")
+  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
+  ack_delta = was_syn_or_fin + len(original.payload)
+  desc = "TCP data" if payload else "TCP ACK"
+  flags = TCP_ACK | TCP_PSH if payload else TCP_ACK
+  return (desc,
+          ip(src=srcaddr, dst=dstaddr) /
+          scapy.TCP(sport=original.dport, dport=original.sport,
+                    ack=original.seq + ack_delta, seq=original.ack,
+                    flags=flags, window=TCP_WINDOW) /
+          payload)
+
+def FIN(version, srcaddr, dstaddr, packet):
+  ip = _GetIpLayer(version)
+  original = packet.getlayer("TCP")
+  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
+  ack_delta = was_syn_or_fin + len(original.payload)
+  return ("TCP FIN",
+          ip(src=srcaddr, dst=dstaddr) /
+          scapy.TCP(sport=original.dport, dport=original.sport,
+                    ack=original.seq + ack_delta, seq=original.ack,
+                    flags=TCP_ACK | TCP_FIN, window=TCP_WINDOW))
+
+def GRE(version, srcaddr, dstaddr, proto, packet):
+  if version == 4:
+    ip = scapy.IP(src=srcaddr, dst=dstaddr, proto=net_test.IPPROTO_GRE)
+  else:
+    ip = scapy.IPv6(src=srcaddr, dst=dstaddr, nh=net_test.IPPROTO_GRE)
+  packet = ip / scapy.GRE(proto=proto) / packet
+  return ("GRE packet", packet)
+
+def ICMPPortUnreachable(version, srcaddr, dstaddr, packet):
+  if version == 4:
+    # Linux hardcodes the ToS on ICMP errors to 0xc0 or greater because of
+    # RFC 1812 4.3.2.5 (!).
+    return ("ICMPv4 port unreachable",
+            scapy.IP(src=srcaddr, dst=dstaddr, proto=1, tos=0xc0) /
+            scapy.ICMPerror(type=3, code=3) / packet)
+  else:
+    return ("ICMPv6 port unreachable",
+            scapy.IPv6(src=srcaddr, dst=dstaddr) /
+            scapy.ICMPv6DestUnreach(code=4) / packet)
+
+def ICMPPacketTooBig(version, srcaddr, dstaddr, packet):
+  if version == 4:
+    return ("ICMPv4 fragmentation needed",
+            scapy.IP(src=srcaddr, dst=dstaddr, proto=1) /
+            scapy.ICMPerror(type=3, code=4, unused=1280) / str(packet)[:64])
+  else:
+    udp = packet.getlayer("UDP")
+    udp.payload = str(udp.payload)[:1280-40-8]
+    return ("ICMPv6 Packet Too Big",
+            scapy.IPv6(src=srcaddr, dst=dstaddr) /
+            scapy.ICMPv6PacketTooBig() / str(packet)[:1232])
+
+def ICMPEcho(version, srcaddr, dstaddr):
+  ip = _GetIpLayer(version)
+  icmp = {4: scapy.ICMP, 6: scapy.ICMPv6EchoRequest}[version]
+  packet = (ip(src=srcaddr, dst=dstaddr) /
+            icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
+  _SetPacketTos(packet, PING_TOS)
+  return ("ICMPv%d echo" % version, packet)
+
+def ICMPReply(version, srcaddr, dstaddr, packet):
+  ip = _GetIpLayer(version)
+  # Scapy doesn't provide an ICMP echo reply constructor.
+  icmpv4_reply = lambda **kwargs: scapy.ICMP(type=0, **kwargs)
+  icmp = {4: icmpv4_reply, 6: scapy.ICMPv6EchoReply}[version]
+  packet = (ip(src=srcaddr, dst=dstaddr) /
+            icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
+  # IPv6 only started copying the tclass to echo replies in 3.14.
+  if version == 4 or net_test.LINUX_VERSION >= (3, 14):
+    _SetPacketTos(packet, PING_TOS)
+  return ("ICMPv%d echo reply" % version, packet)
+
+def NS(srcaddr, tgtaddr, srcmac):
+  solicited = inet_pton(AF_INET6, tgtaddr)
+  last3bytes = tuple([ord(b) for b in solicited[-3:]])
+  solicited = "ff02::1:ff%02x:%02x%02x" % last3bytes
+  packet = (scapy.IPv6(src=srcaddr, dst=solicited) /
+            scapy.ICMPv6ND_NS(tgt=tgtaddr) /
+            scapy.ICMPv6NDOptSrcLLAddr(lladdr=srcmac))
+  return ("ICMPv6 NS", packet)
+
+def NA(srcaddr, dstaddr, srcmac):
+  packet = (scapy.IPv6(src=srcaddr, dst=dstaddr) /
+            scapy.ICMPv6ND_NA(tgt=srcaddr, R=0, S=1, O=1) /
+            scapy.ICMPv6NDOptDstLLAddr(lladdr=srcmac))
+  return ("ICMPv6 NA", packet)
+
index 8fc82d1..bf51cfa 100755 (executable)
@@ -20,7 +20,6 @@ import errno
 import os
 import posix
 import random
-import re
 from socket import *  # pylint: disable=wildcard-import
 import threading
 import time
@@ -254,42 +253,6 @@ class Ping6Test(multinetwork_base.MultiNetworkBaseTest):
     self.assertEqual(len(data), len(rcvd))
     self.assertEqual(data[6:].encode("hex"), rcvd[6:].encode("hex"))
 
-  def ReadProcNetSocket(self, protocol):
-    # Read file.
-    lines = open("/proc/net/%s" % protocol).readlines()
-
-    # Possibly check, and strip, header.
-    if protocol in ["icmp6", "raw6", "udp6"]:
-      self.assertEqual(net_test.IPV6_SEQ_DGRAM_HEADER, lines[0])
-    lines = lines[1:]
-
-    # Check contents.
-    if protocol.endswith("6"):
-      addrlen = 32
-    else:
-      addrlen = 8
-    regexp = re.compile(r" *(\d+): "                    # bucket
-                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # srcaddr, port
-                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # dstaddr, port
-                        "([0-9A-F][0-9A-F]) "           # state
-                        "([0-9A-F]{8}:[0-9A-F]{8}) "    # mem
-                        "([0-9A-F]{2}:[0-9A-F]{8}) "    # ?
-                        "([0-9A-F]{8}) +"               # ?
-                        "([0-9]+) +"                    # uid
-                        "([0-9]+) +"                    # ?
-                        "([0-9]+) +"                    # inode
-                        "([0-9]+) +"                    # refcnt
-                        "([0-9a-f]+) +"                 # sp
-                        "([0-9]+) *$"                   # drops, icmp has spaces
-                        % (addrlen, addrlen))
-    # Return a list of lists with only source / dest addresses for now.
-    out = []
-    for line in lines:
-      (_, src, dst, state, mem,
-       _, _, uid, _, _, refcnt, _, drops) = regexp.match(line).groups()
-      out.append([src, dst, state, mem, uid, refcnt, drops])
-    return out
-
   def CheckSockStatFile(self, name, srcaddr, srcport, dstaddr, dstport, state,
                         txmem=0, rxmem=0):
     expected = ["%s:%04X" % (net_test.FormatSockStatAddress(srcaddr), srcport),
@@ -380,7 +343,17 @@ class Ping6Test(multinetwork_base.MultiNetworkBaseTest):
     # No crash? Good.
 
   def testCrossProtocolCalls(self):
-    """Tests that passing in the wrong family returns EAFNOSUPPORT."""
+    """Tests that passing in the wrong family returns EAFNOSUPPORT.
+
+    Relevant kernel commits:
+      upstream net:
+        91a0b60 net/ping: handle protocol mismatching scenario
+        9145736d net: ping: Return EAFNOSUPPORT when appropriate.
+
+      android-3.10:
+        78a6809 net/ping: handle protocol mismatching scenario
+        428e6d6 net: ping: Return EAFNOSUPPORT when appropriate.
+    """
 
     def CheckEAFNoSupport(function, *args):
       self.assertRaisesErrno(errno.EAFNOSUPPORT, function, *args)
index fae1145..080aac7 100755 (executable)
@@ -1,18 +1,36 @@
 #!/bin/bash
 
-# Kernel configration options.
-OPTIONS=" IPV6 IPV6_ROUTER_PREF IPV6_MULTIPLE_TABLES IPV6_ROUTE_INFO"
+# Kernel configuration options.
+OPTIONS=" DEBUG_SPINLOCK DEBUG_ATOMIC_SLEEP DEBUG_MUTEXES DEBUG_RT_MUTEXES"
+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"
 OPTIONS="$OPTIONS NETFILTER_XT_MARK NETFILTER_XT_TARGET_MARK"
 OPTIONS="$OPTIONS IP_NF_IPTABLES IP_NF_MANGLE"
 OPTIONS="$OPTIONS IP6_NF_IPTABLES IP6_NF_MANGLE INET6_IPCOMP"
 OPTIONS="$OPTIONS IPV6_PRIVACY IPV6_OPTIMISTIC_DAD"
+OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_TARGET_NFLOG"
+OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA CONFIG_NETFILTER_XT_MATCH_QUOTA2"
+OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG"
+OPTIONS="$OPTIONS CONFIG_INET_UDP_DIAG CONFIG_INET_DIAG_DESTROY"
+
 # For 3.1 kernels, where devtmpfs is not on by default.
 OPTIONS="$OPTIONS DEVTMPFS DEVTMPFS_MOUNT"
 
-# How many tap interfaces to create.
-NUMTAPINTERFACES=2
+# 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 to provide the VM with real network access
+# via the host. This requires privileges (e.g., root access) on the host.
+#
+# This is not needed to run the tests, but can be used, for example, to allow
+# the VM to update system packages, or to write tests that need access to a
+# real network. The VM does not set up networking by default, but it contains a
+# DHCP client and has the ability to use IPv6 autoconfiguration. This script
+# does not perform any host-level setup beyond configuring tap interfaces;
+# configuring IPv4 NAT and/or IPv6 router advertisements or ND proxying must
+# be done separately.
+NUMTAPINTERFACES=0
 
 # The root filesystem disk image we'll use.
 ROOTFS=net_test.rootfs.20150203
@@ -42,40 +60,47 @@ fi
 echo "Using $ROOTFS"
 cd -
 
-# Create NUMTAPINTERFACES tap interfaces on the host, and prepare UML command
-# line params to use them. The interfaces are called <user>TAP0, <user>TAP1,
-# ..., on the host, and eth0, eth1, ..., in the VM.
-user=${USER:0:10}
-tapinterfaces=
-netconfig=
-for id in $(seq 0 $(( NUMTAPINTERFACES - 1 )) ); do
-  tap=${user}TAP$id
-  tapinterfaces="$tapinterfaces $tap"
-  mac=$(printf fe:fd:00:00:00:%02x $id)
-  netconfig="$netconfig eth$id=tuntap,$tap,$mac"
-done
-
-for tap in $tapinterfaces; do
-  if ! ip link list $tap > /dev/null; then
-    echo "Creating tap interface $tap" >&2
-    sudo tunctl -u $USER -t $tap
-    sudo ip link set $tap up
-  fi
-done
+# If network access was requested, create NUMTAPINTERFACES tap interfaces on
+# the host, and prepare UML command line params to use them. The interfaces are
+# called <user>TAP0, <user>TAP1, on the host, and eth0, eth1, ..., in the VM.
+if (( $NUMTAPINTERFACES > 0 )); then
+  user=${USER:0:10}
+  tapinterfaces=
+  netconfig=
+  for id in $(seq 0 $(( NUMTAPINTERFACES - 1 )) ); do
+    tap=${user}TAP$id
+    tapinterfaces="$tapinterfaces $tap"
+    mac=$(printf fe:fd:00:00:00:%02x $id)
+    netconfig="$netconfig eth$id=tuntap,$tap,$mac"
+  done
+
+  for tap in $tapinterfaces; do
+    if ! ip link list $tap > /dev/null; then
+      echo "Creating tap interface $tap" >&2
+      sudo tunctl -u $USER -t $tap
+      sudo ip link set $tap up
+    fi
+  done
+fi
 
-# Exporting ARCH=um SUBARCH=x86_64 doesn't seem to work, as it "sometimes" (?)
-# results in a 32-bit kernel.
+if [ -z "$KERNEL_BINARY" ]; then
+  # Exporting ARCH=um SUBARCH=x86_64 doesn't seem to work, as it "sometimes"
+  # (?) results in a 32-bit kernel.
 
-# If there's no kernel config at all, create one or UML won't work.
-[ -f .config ] || make defconfig ARCH=um SUBARCH=x86_64
+  # If there's no kernel config at all, create one or UML won't work.
+  [ -f .config ] || make defconfig ARCH=um SUBARCH=x86_64
 
-# Enable the kernel config options listed in $OPTIONS.
-cmdline=${OPTIONS// / -e }
-./scripts/config $cmdline
+  # Enable the kernel config options listed in $OPTIONS.
+  cmdline=${OPTIONS// / -e }
+  ./scripts/config $cmdline
 
-# olddefconfig doesn't work on old kernels.
-if ! make olddefconfig ARCH=um SUBARCH=x86_64 CROSS_COMPILE= ; then
-  cat >&2 << EOF
+  # 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
 
 Warning: "make olddefconfig" failed.
 Perhaps this kernel is too old to support it.
@@ -83,15 +108,18 @@ You may get asked lots of questions.
 Keep enter pressed to accept the defaults.
 
 EOF
+  fi
+
+  # Compile the kernel.
+  make -j32 linux ARCH=um SUBARCH=x86_64 CROSS_COMPILE=
+  KERNEL_BINARY=./linux
 fi
 
-# Compile the kernel.
-make -j12 linux ARCH=um SUBARCH=x86_64 CROSS_COMPILE=
 
 # Get the absolute path to the test file that's being run.
 dir=/host$(dirname $(readlink -f $0))
 
 # Start the VM.
-exec ./linux umid=net_test ubda=$(dirname $0)/$ROOTFS \
+exec $KERNEL_BINARY umid=net_test ubda=$(dirname $0)/$ROOTFS \
     mem=512M init=/sbin/net_test.sh net_test=$dir/$test \
     $netconfig
diff --git a/tests/net_test/sock_diag.py b/tests/net_test/sock_diag.py
new file mode 100755 (executable)
index 0000000..b4d9cf6
--- /dev/null
@@ -0,0 +1,342 @@
+#!/usr/bin/python
+#
+# 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.
+
+"""Partial Python implementation of sock_diag functionality."""
+
+# pylint: disable=g-bad-todo
+
+import errno
+from socket import *  # pylint: disable=wildcard-import
+import struct
+
+import cstruct
+import net_test
+import netlink
+
+### Base netlink constants. See include/uapi/linux/netlink.h.
+NETLINK_SOCK_DIAG = 4
+
+### sock_diag constants. See include/uapi/linux/sock_diag.h.
+# Message types.
+SOCK_DIAG_BY_FAMILY = 20
+SOCK_DESTROY = 21
+
+### inet_diag_constants. See include/uapi/linux/inet_diag.h
+# Message types.
+TCPDIAG_GETSOCK = 18
+
+# Request attributes.
+INET_DIAG_REQ_BYTECODE = 1
+
+# Extensions.
+INET_DIAG_NONE = 0
+INET_DIAG_MEMINFO = 1
+INET_DIAG_INFO = 2
+INET_DIAG_VEGASINFO = 3
+INET_DIAG_CONG = 4
+INET_DIAG_TOS = 5
+INET_DIAG_TCLASS = 6
+INET_DIAG_SKMEMINFO = 7
+INET_DIAG_SHUTDOWN = 8
+INET_DIAG_DCTCPINFO = 9
+
+# Bytecode operations.
+INET_DIAG_BC_NOP = 0
+INET_DIAG_BC_JMP = 1
+INET_DIAG_BC_S_GE = 2
+INET_DIAG_BC_S_LE = 3
+INET_DIAG_BC_D_GE = 4
+INET_DIAG_BC_D_LE = 5
+INET_DIAG_BC_AUTO = 6
+INET_DIAG_BC_S_COND = 7
+INET_DIAG_BC_D_COND = 8
+
+# Data structure formats.
+# These aren't constants, they're classes. So, pylint: disable=invalid-name
+InetDiagSockId = cstruct.Struct(
+    "InetDiagSockId", "!HH16s16sI8s", "sport dport src dst iface cookie")
+InetDiagReqV2 = cstruct.Struct(
+    "InetDiagReqV2", "=BBBxIS", "family protocol ext states id",
+    [InetDiagSockId])
+InetDiagMsg = cstruct.Struct(
+    "InetDiagMsg", "=BBBBSLLLLL",
+    "family state timer retrans id expires rqueue wqueue uid inode",
+    [InetDiagSockId])
+InetDiagMeminfo = cstruct.Struct(
+    "InetDiagMeminfo", "=IIII", "rmem wmem fmem tmem")
+InetDiagBcOp = cstruct.Struct("InetDiagBcOp", "BBH", "code yes no")
+InetDiagHostcond = cstruct.Struct("InetDiagHostcond", "=BBxxi",
+                                  "family prefix_len port")
+
+SkMeminfo = cstruct.Struct(
+    "SkMeminfo", "=IIIIIIII",
+    "rmem_alloc rcvbuf wmem_alloc sndbuf fwd_alloc wmem_queued optmem backlog")
+TcpInfo = cstruct.Struct(
+    "TcpInfo", "=BBBBBBBxIIIIIIIIIIIIIIIIIIIIIIII",
+    "state ca_state retransmits probes backoff options wscale "
+    "rto ato snd_mss rcv_mss "
+    "unacked sacked lost retrans fackets "
+    "last_data_sent last_ack_sent last_data_recv last_ack_recv "
+    "pmtu rcv_ssthresh rtt rttvar snd_ssthresh snd_cwnd advmss reordering "
+    "rcv_rtt rcv_space "
+    "total_retrans")  # As of linux 3.13, at least.
+
+TCP_TIME_WAIT = 6
+ALL_NON_TIME_WAIT = 0xffffffff & ~(1 << TCP_TIME_WAIT)
+
+
+class SockDiag(netlink.NetlinkSocket):
+
+  FAMILY = NETLINK_SOCK_DIAG
+  NL_DEBUG = []
+
+  def _Decode(self, command, msg, nla_type, nla_data):
+    """Decodes netlink attributes to Python types."""
+    if msg.family == AF_INET or msg.family == AF_INET6:
+      name = self._GetConstantName(__name__, nla_type, "INET_DIAG")
+    else:
+      # Don't know what this is. Leave it as an integer.
+      name = nla_type
+
+    if name in ["INET_DIAG_SHUTDOWN", "INET_DIAG_TOS", "INET_DIAG_TCLASS"]:
+      data = ord(nla_data)
+    elif name == "INET_DIAG_CONG":
+      data = nla_data.strip("\x00")
+    elif name == "INET_DIAG_MEMINFO":
+      data = InetDiagMeminfo(nla_data)
+    elif name == "INET_DIAG_INFO":
+      # TODO: Catch the exception and try something else if it's not TCP.
+      data = TcpInfo(nla_data)
+    elif name == "INET_DIAG_SKMEMINFO":
+      data = SkMeminfo(nla_data)
+    else:
+      data = nla_data
+
+    return name, data
+
+  def MaybeDebugCommand(self, command, data):
+    name = self._GetConstantName(__name__, command, "SOCK_")
+    if "ALL" not in self.NL_DEBUG and "SOCK" not in self.NL_DEBUG:
+      return
+    parsed = self._ParseNLMsg(data, InetDiagReqV2)
+    print "%s %s" % (name, str(parsed))
+
+  @staticmethod
+  def _EmptyInetDiagSockId():
+    return InetDiagSockId(("\x00" * len(InetDiagSockId)))
+
+  def PackBytecode(self, instructions):
+    """Compiles instructions to inet_diag bytecode.
+
+    The input is a list of (INET_DIAG_BC_xxx, yes, no, arg) tuples, where yes
+    and no are relative jump offsets measured in instructions. The yes branch
+    is taken if the instruction matches.
+
+    To accept, jump 1 past the last instruction. To reject, jump 2 past the
+    last instruction.
+
+    The target of a no jump is only valid if it is reachable by following
+    only yes jumps from the first instruction - see inet_diag_bc_audit and
+    valid_cc. This means that if cond1 and cond2 are two mutually exclusive
+    filter terms, it is not possible to implement cond1 OR cond2 using:
+
+      ...
+      cond1 2 1 arg
+      cond2 1 2 arg
+      accept
+      reject
+
+    but only using:
+
+      ...
+      cond1 1 2 arg
+      jmp   1 2
+      cond2 1 2 arg
+      accept
+      reject
+
+    The jmp instruction ignores yes and always jumps to no, but yes must be 1
+    or the bytecode won't validate. It doesn't have to be jmp - any instruction
+    that is guaranteed not to match on real data will do.
+
+    Args:
+      instructions: list of instruction tuples
+
+    Returns:
+      A string, the raw bytecode.
+    """
+    args = []
+    positions = [0]
+
+    for op, yes, no, arg in instructions:
+
+      if yes <= 0 or no <= 0:
+        raise ValueError("Jumps must be > 0")
+
+      if op in [INET_DIAG_BC_NOP, INET_DIAG_BC_JMP, INET_DIAG_BC_AUTO]:
+        arg = ""
+      elif op in [INET_DIAG_BC_S_GE, INET_DIAG_BC_S_LE,
+                  INET_DIAG_BC_D_GE, INET_DIAG_BC_D_LE]:
+        arg = "\x00\x00" + struct.pack("=H", arg)
+      elif op in [INET_DIAG_BC_S_COND, INET_DIAG_BC_D_COND]:
+        addr, prefixlen, port = arg
+        family = AF_INET6 if ":" in addr else AF_INET
+        addr = inet_pton(family, addr)
+        arg = InetDiagHostcond((family, prefixlen, port)).Pack() + addr
+      else:
+        raise ValueError("Unsupported opcode %d" % op)
+
+      args.append(arg)
+      length = len(InetDiagBcOp) + len(arg)
+      positions.append(positions[-1] + length)
+
+    # Reject label.
+    positions.append(positions[-1] + 4)  # Why 4? Because the kernel uses 4.
+    assert len(args) == len(instructions) == len(positions) - 2
+
+    # print positions
+
+    packed = ""
+    for i, (op, yes, no, arg) in enumerate(instructions):
+      yes = positions[i + yes] - positions[i]
+      no = positions[i + no] - positions[i]
+      instruction = InetDiagBcOp((op, yes, no)).Pack() + args[i]
+      #print "%3d: %d %3d %3d %s %s" % (positions[i], op, yes, no,
+      #                                 arg, instruction.encode("hex"))
+      packed += instruction
+    #print
+
+    return packed
+
+  def Dump(self, diag_req, bytecode=""):
+    out = self._Dump(SOCK_DIAG_BY_FAMILY, diag_req, InetDiagMsg, bytecode)
+    return out
+
+  def DumpAllInetSockets(self, protocol, bytecode, sock_id=None, ext=0,
+                         states=ALL_NON_TIME_WAIT):
+    """Dumps IPv4 or IPv6 sockets matching the specified parameters."""
+    # DumpSockets(AF_UNSPEC) does not result in dumping all inet sockets, it
+    # results in ENOENT.
+    if sock_id is None:
+      sock_id = self._EmptyInetDiagSockId()
+
+    if bytecode:
+      bytecode = self._NlAttr(INET_DIAG_REQ_BYTECODE, bytecode)
+
+    sockets = []
+    for family in [AF_INET, AF_INET6]:
+      diag_req = InetDiagReqV2((family, protocol, ext, states, sock_id))
+      sockets += self.Dump(diag_req, bytecode)
+
+    return sockets
+
+  @staticmethod
+  def GetRawAddress(family, addr):
+    """Fetches the source address from an InetDiagMsg."""
+    addrlen = {AF_INET:4, AF_INET6: 16}[family]
+    return inet_ntop(family, addr[:addrlen])
+
+  @staticmethod
+  def GetSourceAddress(diag_msg):
+    """Fetches the source address from an InetDiagMsg."""
+    return SockDiag.GetRawAddress(diag_msg.family, diag_msg.id.src)
+
+  @staticmethod
+  def GetDestinationAddress(diag_msg):
+    """Fetches the source address from an InetDiagMsg."""
+    return SockDiag.GetRawAddress(diag_msg.family, diag_msg.id.dst)
+
+  @staticmethod
+  def RawAddress(addr):
+    """Converts an IP address string to binary format."""
+    family = AF_INET6 if ":" in addr else AF_INET
+    return inet_pton(family, addr)
+
+  @staticmethod
+  def PaddedAddress(addr):
+    """Converts an IP address string to binary format for InetDiagSockId."""
+    padded = SockDiag.RawAddress(addr)
+    if len(padded) < 16:
+      padded += "\x00" * (16 - len(padded))
+    return padded
+
+  @staticmethod
+  def DiagReqFromSocket(s):
+    """Creates an InetDiagReqV2 that matches the specified socket."""
+    family = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_DOMAIN)
+    protocol = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_PROTOCOL)
+    if net_test.LINUX_VERSION >= (3, 8):
+      iface = s.getsockopt(SOL_SOCKET, net_test.SO_BINDTODEVICE,
+                           net_test.IFNAMSIZ)
+      iface = GetInterfaceIndex(iface) if iface else 0
+    else:
+      iface = 0
+    src, sport = s.getsockname()[:2]
+    try:
+      dst, dport = s.getpeername()[:2]
+    except error, e:
+      if e.errno == errno.ENOTCONN:
+        dport = 0
+        dst = "::" if family == AF_INET6 else "0.0.0.0"
+      else:
+        raise e
+    src = SockDiag.PaddedAddress(src)
+    dst = SockDiag.PaddedAddress(dst)
+    sock_id = InetDiagSockId((sport, dport, src, dst, iface, "\x00" * 8))
+    return InetDiagReqV2((family, protocol, 0, 0xffffffff, sock_id))
+
+  def FindSockDiagFromReq(self, req):
+    for diag_msg, attrs in self.Dump(req):
+      return diag_msg
+    raise ValueError("Dump of %s returned no sockets" % req)
+
+  def FindSockDiagFromFd(self, s):
+    """Gets an InetDiagMsg from the kernel for the specified socket."""
+    req = self.DiagReqFromSocket(s)
+    return self.FindSockDiagFromReq(req)
+
+  def GetSockDiag(self, req):
+    """Gets an InetDiagMsg from the kernel for the specified request."""
+    self._SendNlRequest(SOCK_DIAG_BY_FAMILY, req.Pack(), netlink.NLM_F_REQUEST)
+    return self._GetMsg(InetDiagMsg)[0]
+
+  @staticmethod
+  def DiagReqFromDiagMsg(d, protocol):
+    """Constructs a diag_req from a diag_msg the kernel has given us."""
+    return InetDiagReqV2((d.family, protocol, 0, 1 << d.state, d.id))
+
+  def CloseSocket(self, req):
+    self._SendNlRequest(SOCK_DESTROY, req.Pack(),
+                        netlink.NLM_F_REQUEST | netlink.NLM_F_ACK)
+
+  def CloseSocketFromFd(self, s):
+    diag_msg = self.FindSockDiagFromFd(s)
+    protocol = s.getsockopt(SOL_SOCKET, net_test.SO_PROTOCOL)
+    req = self.DiagReqFromDiagMsg(diag_msg, protocol)
+    return self.CloseSocket(req)
+
+
+if __name__ == "__main__":
+  n = SockDiag()
+  n.DEBUG = True
+  bytecode = ""
+  sock_id = n._EmptyInetDiagSockId()
+  sock_id.dport = 443
+  ext = 1 << (INET_DIAG_TOS - 1) | 1 << (INET_DIAG_TCLASS - 1)
+  states = 0xffffffff
+  diag_msgs = n.DumpAllInetSockets(IPPROTO_TCP, "",
+                                   sock_id=sock_id, ext=ext, states=states)
+  print diag_msgs
diff --git a/tests/net_test/sock_diag_test.py b/tests/net_test/sock_diag_test.py
new file mode 100755 (executable)
index 0000000..3c5d0a9
--- /dev/null
@@ -0,0 +1,548 @@
+#!/usr/bin/python
+#
+# 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.
+
+# pylint: disable=g-bad-todo,g-bad-file-header,wildcard-import
+from errno import *  # pylint: disable=wildcard-import
+import os
+import random
+import re
+from socket import *  # pylint: disable=wildcard-import
+import threading
+import time
+import unittest
+
+import multinetwork_base
+import net_test
+import packets
+import sock_diag
+import tcp_test
+
+
+NUM_SOCKETS = 30
+NO_BYTECODE = ""
+
+
+class SockDiagBaseTest(multinetwork_base.MultiNetworkBaseTest):
+
+  @staticmethod
+  def _CreateLotsOfSockets():
+    # Dict mapping (addr, sport, dport) tuples to socketpairs.
+    socketpairs = {}
+    for _ in xrange(NUM_SOCKETS):
+      family, addr = random.choice([
+          (AF_INET, "127.0.0.1"),
+          (AF_INET6, "::1"),
+          (AF_INET6, "::ffff:127.0.0.1")])
+      socketpair = net_test.CreateSocketPair(family, SOCK_STREAM, addr)
+      sport, dport = (socketpair[0].getsockname()[1],
+                      socketpair[1].getsockname()[1])
+      socketpairs[(addr, sport, dport)] = socketpair
+    return socketpairs
+
+  def assertSocketClosed(self, sock):
+    self.assertRaisesErrno(ENOTCONN, sock.getpeername)
+
+  def assertSocketConnected(self, sock):
+    sock.getpeername()  # No errors? Socket is alive and connected.
+
+  def assertSocketsClosed(self, socketpair):
+    for sock in socketpair:
+      self.assertSocketClosed(sock)
+
+  def setUp(self):
+    super(SockDiagBaseTest, self).setUp()
+    self.sock_diag = sock_diag.SockDiag()
+    self.socketpairs = {}
+
+  def tearDown(self):
+    for socketpair in self.socketpairs.values():
+      for s in socketpair:
+        s.close()
+    super(SockDiagBaseTest, self).tearDown()
+
+
+class SockDiagTest(SockDiagBaseTest):
+
+  def assertSockDiagMatchesSocket(self, s, diag_msg):
+    family = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_DOMAIN)
+    self.assertEqual(diag_msg.family, family)
+
+    src, sport = s.getsockname()[0:2]
+    self.assertEqual(diag_msg.id.src, self.sock_diag.PaddedAddress(src))
+    self.assertEqual(diag_msg.id.sport, sport)
+
+    if self.sock_diag.GetDestinationAddress(diag_msg) not in ["0.0.0.0", "::"]:
+      dst, dport = s.getpeername()[0:2]
+      self.assertEqual(diag_msg.id.dst, self.sock_diag.PaddedAddress(dst))
+      self.assertEqual(diag_msg.id.dport, dport)
+    else:
+      self.assertRaisesErrno(ENOTCONN, s.getpeername)
+
+  def testFindsMappedSockets(self):
+    """Tests that inet_diag_find_one_icsk can find mapped sockets.
+
+    Relevant kernel commits:
+      android-3.10:
+        f77e059 net: diag: support v4mapped sockets in inet_diag_find_one_icsk()
+    """
+    socketpair = net_test.CreateSocketPair(AF_INET6, SOCK_STREAM,
+                                           "::ffff:127.0.0.1")
+    for sock in socketpair:
+      diag_msg = self.sock_diag.FindSockDiagFromFd(sock)
+      diag_req = self.sock_diag.DiagReqFromDiagMsg(diag_msg, IPPROTO_TCP)
+      self.sock_diag.GetSockDiag(diag_req)
+      # No errors? Good.
+
+  def testFindsAllMySockets(self):
+    """Tests that basic socket dumping works.
+
+    Relevant commits:
+      android-3.4:
+        ab4a727 net: inet_diag: zero out uninitialized idiag_{src,dst} fields
+      android-3.10
+        3eb409b net: inet_diag: zero out uninitialized idiag_{src,dst} fields
+    """
+    self.socketpairs = self._CreateLotsOfSockets()
+    sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, NO_BYTECODE)
+    self.assertGreaterEqual(len(sockets), NUM_SOCKETS)
+
+    # Find the cookies for all of our sockets.
+    cookies = {}
+    for diag_msg, unused_attrs in sockets:
+      addr = self.sock_diag.GetSourceAddress(diag_msg)
+      sport = diag_msg.id.sport
+      dport = diag_msg.id.dport
+      if (addr, sport, dport) in self.socketpairs:
+        cookies[(addr, sport, dport)] = diag_msg.id.cookie
+      elif (addr, dport, sport) in self.socketpairs:
+        cookies[(addr, sport, dport)] = diag_msg.id.cookie
+
+    # Did we find all the cookies?
+    self.assertEquals(2 * NUM_SOCKETS, len(cookies))
+
+    socketpairs = self.socketpairs.values()
+    random.shuffle(socketpairs)
+    for socketpair in socketpairs:
+      for sock in socketpair:
+        # Check that we can find a diag_msg by scanning a dump.
+        self.assertSockDiagMatchesSocket(
+            sock,
+            self.sock_diag.FindSockDiagFromFd(sock))
+        cookie = self.sock_diag.FindSockDiagFromFd(sock).id.cookie
+
+        # Check that we can find a diag_msg once we know the cookie.
+        req = self.sock_diag.DiagReqFromSocket(sock)
+        req.id.cookie = cookie
+        diag_msg = self.sock_diag.GetSockDiag(req)
+        req.states = 1 << diag_msg.state
+        self.assertSockDiagMatchesSocket(sock, diag_msg)
+
+  def testBytecodeCompilation(self):
+    # pylint: disable=bad-whitespace
+    instructions = [
+        (sock_diag.INET_DIAG_BC_S_GE,   1, 8, 0),                      # 0
+        (sock_diag.INET_DIAG_BC_D_LE,   1, 7, 0xffff),                 # 8
+        (sock_diag.INET_DIAG_BC_S_COND, 1, 2, ("::1", 128, -1)),       # 16
+        (sock_diag.INET_DIAG_BC_JMP,    1, 3, None),                   # 44
+        (sock_diag.INET_DIAG_BC_S_COND, 2, 4, ("127.0.0.1", 32, -1)),  # 48
+        (sock_diag.INET_DIAG_BC_D_LE,   1, 3, 0x6665),  # not used     # 64
+        (sock_diag.INET_DIAG_BC_NOP,    1, 1, None),                   # 72
+                                                                       # 76 acc
+                                                                       # 80 rej
+    ]
+    # pylint: enable=bad-whitespace
+    bytecode = self.sock_diag.PackBytecode(instructions)
+    expected = (
+        "0208500000000000"
+        "050848000000ffff"
+        "071c20000a800000ffffffff00000000000000000000000000000001"
+        "01041c00"
+        "0718200002200000ffffffff7f000001"
+        "0508100000006566"
+        "00040400"
+    )
+    self.assertMultiLineEqual(expected, bytecode.encode("hex"))
+    self.assertEquals(76, len(bytecode))
+    self.socketpairs = self._CreateLotsOfSockets()
+    filteredsockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode)
+    allsockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, NO_BYTECODE)
+    self.assertItemsEqual(allsockets, filteredsockets)
+
+    # Pick a few sockets in hash table order, and check that the bytecode we
+    # compiled selects them properly.
+    for socketpair in self.socketpairs.values()[:20]:
+      for s in socketpair:
+        diag_msg = self.sock_diag.FindSockDiagFromFd(s)
+        instructions = [
+            (sock_diag.INET_DIAG_BC_S_GE, 1, 5, diag_msg.id.sport),
+            (sock_diag.INET_DIAG_BC_S_LE, 1, 4, diag_msg.id.sport),
+            (sock_diag.INET_DIAG_BC_D_GE, 1, 3, diag_msg.id.dport),
+            (sock_diag.INET_DIAG_BC_D_LE, 1, 2, diag_msg.id.dport),
+        ]
+        bytecode = self.sock_diag.PackBytecode(instructions)
+        self.assertEquals(32, len(bytecode))
+        sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode)
+        self.assertEquals(1, len(sockets))
+
+        # TODO: why doesn't comparing the cstructs work?
+        self.assertEquals(diag_msg.Pack(), sockets[0][0].Pack())
+
+  def testCrossFamilyBytecode(self):
+    """Checks for a cross-family bug in inet_diag_hostcond matching.
+
+    Relevant kernel commits:
+      android-3.4:
+        f67caec inet_diag: avoid unsafe and nonsensical prefix matches in inet_diag_bc_run()
+    """
+    # TODO: this is only here because the test fails if there are any open
+    # sockets other than the ones it creates itself. Make the bytecode more
+    # specific and remove it.
+    self.assertFalse(self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, ""))
+
+    unused_pair4 = net_test.CreateSocketPair(AF_INET, SOCK_STREAM, "127.0.0.1")
+    unused_pair6 = net_test.CreateSocketPair(AF_INET6, SOCK_STREAM, "::1")
+
+    bytecode4 = self.sock_diag.PackBytecode([
+        (sock_diag.INET_DIAG_BC_S_COND, 1, 2, ("0.0.0.0", 0, -1))])
+    bytecode6 = self.sock_diag.PackBytecode([
+        (sock_diag.INET_DIAG_BC_S_COND, 1, 2, ("::", 0, -1))])
+
+    # IPv4/v6 filters must never match IPv6/IPv4 sockets...
+    v4sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode4)
+    self.assertTrue(v4sockets)
+    self.assertTrue(all(d.family == AF_INET for d, _ in v4sockets))
+
+    v6sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode6)
+    self.assertTrue(v6sockets)
+    self.assertTrue(all(d.family == AF_INET6 for d, _ in v6sockets))
+
+    # Except for mapped addresses, which match both IPv4 and IPv6.
+    pair5 = net_test.CreateSocketPair(AF_INET6, SOCK_STREAM,
+                                      "::ffff:127.0.0.1")
+    diag_msgs = [self.sock_diag.FindSockDiagFromFd(s) for s in pair5]
+    v4sockets = [d for d, _ in self.sock_diag.DumpAllInetSockets(IPPROTO_TCP,
+                                                                 bytecode4)]
+    v6sockets = [d for d, _ in self.sock_diag.DumpAllInetSockets(IPPROTO_TCP,
+                                                                 bytecode6)]
+    self.assertTrue(all(d in v4sockets for d in diag_msgs))
+    self.assertTrue(all(d in v6sockets for d in diag_msgs))
+
+  def testPortComparisonValidation(self):
+    """Checks for a bug in validating port comparison bytecode.
+
+    Relevant kernel commits:
+      android-3.4:
+        5e1f542 inet_diag: validate port comparison byte code to prevent unsafe reads
+    """
+    bytecode = sock_diag.InetDiagBcOp((sock_diag.INET_DIAG_BC_D_GE, 4, 8))
+    self.assertRaisesErrno(
+        EINVAL,
+        self.sock_diag.DumpAllInetSockets, IPPROTO_TCP, bytecode.Pack())
+
+  def testNonSockDiagCommand(self):
+    def DiagDump(code):
+      sock_id = self.sock_diag._EmptyInetDiagSockId()
+      req = sock_diag.InetDiagReqV2((AF_INET6, IPPROTO_TCP, 0, 0xffffffff,
+                                     sock_id))
+      self.sock_diag._Dump(code, req, sock_diag.InetDiagMsg, "")
+
+    op = sock_diag.SOCK_DIAG_BY_FAMILY
+    DiagDump(op)  # No errors? Good.
+    self.assertRaisesErrno(EINVAL, DiagDump, op + 17)
+
+
+class SockDestroyTest(SockDiagBaseTest):
+  """Tests that SOCK_DESTROY works correctly.
+
+  Relevant kernel commits:
+    net-next:
+      b613f56 net: diag: split inet_diag_dump_one_icsk into two
+      64be0ae net: diag: Add the ability to destroy a socket.
+      6eb5d2e net: diag: Support SOCK_DESTROY for inet sockets.
+      c1e64e2 net: diag: Support destroying TCP sockets.
+      2010b93 net: tcp: deal with listen sockets properly in tcp_abort.
+
+    android-3.4:
+      d48ec88 net: diag: split inet_diag_dump_one_icsk into two
+      2438189 net: diag: Add the ability to destroy a socket.
+      7a2ddbc net: diag: Support SOCK_DESTROY for inet sockets.
+      44047b2 net: diag: Support destroying TCP sockets.
+      200dae7 net: tcp: deal with listen sockets properly in tcp_abort.
+
+    android-3.10:
+      9eaff90 net: diag: split inet_diag_dump_one_icsk into two
+      d60326c net: diag: Add the ability to destroy a socket.
+      3d4ce85 net: diag: Support SOCK_DESTROY for inet sockets.
+      529dfc6 net: diag: Support destroying TCP sockets.
+      9c712fe net: tcp: deal with listen sockets properly in tcp_abort.
+
+    android-3.18:
+      100263d net: diag: split inet_diag_dump_one_icsk into two
+      194c5f3 net: diag: Add the ability to destroy a socket.
+      8387ea2 net: diag: Support SOCK_DESTROY for inet sockets.
+      b80585a net: diag: Support destroying TCP sockets.
+      476c6ce net: tcp: deal with listen sockets properly in tcp_abort.
+  """
+
+  def testClosesSockets(self):
+    self.socketpairs = self._CreateLotsOfSockets()
+    for _, socketpair in self.socketpairs.iteritems():
+      # Close one of the sockets.
+      # This will send a RST that will close the other side as well.
+      s = random.choice(socketpair)
+      if random.randrange(0, 2) == 1:
+        self.sock_diag.CloseSocketFromFd(s)
+      else:
+        diag_msg = self.sock_diag.FindSockDiagFromFd(s)
+
+        # Get the cookie wrong and ensure that we get an error and the socket
+        # is not closed.
+        real_cookie = diag_msg.id.cookie
+        diag_msg.id.cookie = os.urandom(len(real_cookie))
+        req = self.sock_diag.DiagReqFromDiagMsg(diag_msg, IPPROTO_TCP)
+        self.assertRaisesErrno(ENOENT, self.sock_diag.CloseSocket, req)
+        self.assertSocketConnected(s)
+
+        # Now close it with the correct cookie.
+        req.id.cookie = real_cookie
+        self.sock_diag.CloseSocket(req)
+
+      # Check that both sockets in the pair are closed.
+      self.assertSocketsClosed(socketpair)
+
+  def testNonTcpSockets(self):
+    s = socket(AF_INET6, SOCK_DGRAM, 0)
+    s.connect(("::1", 53))
+    self.sock_diag.FindSockDiagFromFd(s)  # No exceptions? Good.
+    self.assertRaisesErrno(EOPNOTSUPP, self.sock_diag.CloseSocketFromFd, s)
+
+  # TODO:
+  # Test that killing unix sockets returns EOPNOTSUPP.
+
+
+class SocketExceptionThread(threading.Thread):
+
+  def __init__(self, sock, operation):
+    self.exception = None
+    super(SocketExceptionThread, self).__init__()
+    self.daemon = True
+    self.sock = sock
+    self.operation = operation
+
+  def run(self):
+    try:
+      self.operation(self.sock)
+    except IOError, e:
+      self.exception = e
+
+
+class SockDiagTcpTest(tcp_test.TcpBaseTest, SockDiagBaseTest):
+
+  def testIpv4MappedSynRecvSocket(self):
+    """Tests for the absence of a bug with AF_INET6 TCP SYN-RECV sockets.
+
+    Relevant kernel commits:
+         android-3.4:
+           457a04b inet_diag: fix oops for IPv4 AF_INET6 TCP SYN-RECV state
+    """
+    netid = random.choice(self.tuns.keys())
+    self.IncomingConnection(5, tcp_test.TCP_SYN_RECV, netid)
+    sock_id = self.sock_diag._EmptyInetDiagSockId()
+    sock_id.sport = self.port
+    states = 1 << tcp_test.TCP_SYN_RECV
+    req = sock_diag.InetDiagReqV2((AF_INET6, IPPROTO_TCP, 0, states, sock_id))
+    children = self.sock_diag.Dump(req, NO_BYTECODE)
+
+    self.assertTrue(children)
+    for child, unused_args in children:
+      self.assertEqual(tcp_test.TCP_SYN_RECV, child.state)
+      self.assertEqual(self.sock_diag.PaddedAddress(self.remoteaddr),
+                       child.id.dst)
+      self.assertEqual(self.sock_diag.PaddedAddress(self.myaddr),
+                       child.id.src)
+
+
+class SockDestroyTcpTest(tcp_test.TcpBaseTest, SockDiagBaseTest):
+
+  def setUp(self):
+    super(SockDestroyTcpTest, self).setUp()
+    self.netid = random.choice(self.tuns.keys())
+
+  def CheckRstOnClose(self, sock, req, expect_reset, msg, do_close=True):
+    """Closes the socket and checks whether a RST is sent or not."""
+    if sock is not None:
+      self.assertIsNone(req, "Must specify sock or req, not both")
+      self.sock_diag.CloseSocketFromFd(sock)
+      self.assertRaisesErrno(EINVAL, sock.accept)
+    else:
+      self.assertIsNone(sock, "Must specify sock or req, not both")
+      self.sock_diag.CloseSocket(req)
+
+    if expect_reset:
+      desc, rst = self.RstPacket()
+      msg = "%s: expecting %s: " % (msg, desc)
+      self.ExpectPacketOn(self.netid, msg, rst)
+    else:
+      msg = "%s: " % msg
+      self.ExpectNoPacketsOn(self.netid, msg)
+
+    if sock is not None and do_close:
+      sock.close()
+
+  def CheckTcpReset(self, state, statename):
+    for version in [4, 5, 6]:
+      msg = "Closing incoming IPv%d %s socket" % (version, statename)
+      self.IncomingConnection(version, state, self.netid)
+      self.CheckRstOnClose(self.s, None, False, msg)
+      if state != tcp_test.TCP_LISTEN:
+        msg = "Closing accepted IPv%d %s socket" % (version, statename)
+        self.CheckRstOnClose(self.accepted, None, True, msg)
+
+  def testTcpResets(self):
+    """Checks that closing sockets in appropriate states sends a RST."""
+    self.CheckTcpReset(tcp_test.TCP_LISTEN, "TCP_LISTEN")
+    self.CheckTcpReset(tcp_test.TCP_ESTABLISHED, "TCP_ESTABLISHED")
+    self.CheckTcpReset(tcp_test.TCP_CLOSE_WAIT, "TCP_CLOSE_WAIT")
+
+  def FindChildSockets(self, s):
+    """Finds the SYN_RECV child sockets of a given listening socket."""
+    d = self.sock_diag.FindSockDiagFromFd(self.s)
+    req = self.sock_diag.DiagReqFromDiagMsg(d, IPPROTO_TCP)
+    req.states = 1 << tcp_test.TCP_SYN_RECV | 1 << tcp_test.TCP_ESTABLISHED
+    req.id.cookie = "\x00" * 8
+    children = self.sock_diag.Dump(req, NO_BYTECODE)
+    return [self.sock_diag.DiagReqFromDiagMsg(d, IPPROTO_TCP)
+            for d, _ in children]
+
+  def CheckChildSocket(self, version, statename, parent_first):
+    state = getattr(tcp_test, statename)
+
+    self.IncomingConnection(version, state, self.netid)
+
+    d = self.sock_diag.FindSockDiagFromFd(self.s)
+    parent = self.sock_diag.DiagReqFromDiagMsg(d, IPPROTO_TCP)
+    children = self.FindChildSockets(self.s)
+    self.assertEquals(1, len(children))
+
+    is_established = (state == tcp_test.TCP_NOT_YET_ACCEPTED)
+
+    # The new TCP listener code in 4.4 makes SYN_RECV sockets live in the
+    # regular TCP hash tables, and inet_diag_find_one_icsk can find them.
+    # Before 4.4, we can see those sockets in dumps, but we can't fetch
+    # or close them.
+    can_close_children = is_established or net_test.LINUX_VERSION >= (4, 4)
+
+    for child in children:
+      if can_close_children:
+        self.sock_diag.GetSockDiag(child)  # No errors? Good, child found.
+      else:
+        self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, child)
+
+    def CloseParent(expect_reset):
+      msg = "Closing parent IPv%d %s socket %s child" % (
+          version, statename, "before" if parent_first else "after")
+      self.CheckRstOnClose(self.s, None, expect_reset, msg)
+      self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, parent)
+
+    def CheckChildrenClosed():
+      for child in children:
+        self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, child)
+
+    def CloseChildren():
+      for child in children:
+        msg = "Closing child IPv%d %s socket %s parent" % (
+            version, statename, "after" if parent_first else "before")
+        self.sock_diag.GetSockDiag(child)
+        self.CheckRstOnClose(None, child, is_established, msg)
+        self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, child)
+      CheckChildrenClosed()
+
+    if parent_first:
+      # Closing the parent will close child sockets, which will send a RST,
+      # iff they are already established.
+      CloseParent(is_established)
+      if is_established:
+        CheckChildrenClosed()
+      elif can_close_children:
+        CloseChildren()
+        CheckChildrenClosed()
+      self.s.close()
+    else:
+      if can_close_children:
+        CloseChildren()
+      CloseParent(False)
+      self.s.close()
+
+  def testChildSockets(self):
+    for version in [4, 5, 6]:
+      self.CheckChildSocket(version, "TCP_SYN_RECV", False)
+      self.CheckChildSocket(version, "TCP_SYN_RECV", True)
+      self.CheckChildSocket(version, "TCP_NOT_YET_ACCEPTED", False)
+      self.CheckChildSocket(version, "TCP_NOT_YET_ACCEPTED", True)
+
+  def CloseDuringBlockingCall(self, sock, call, expected_errno):
+    thread = SocketExceptionThread(sock, call)
+    thread.start()
+    time.sleep(0.1)
+    self.sock_diag.CloseSocketFromFd(sock)
+    thread.join(1)
+    self.assertFalse(thread.is_alive())
+    self.assertIsNotNone(thread.exception)
+    self.assertTrue(isinstance(thread.exception, IOError),
+                    "Expected IOError, got %s" % thread.exception)
+    self.assertEqual(expected_errno, thread.exception.errno)
+    self.assertSocketClosed(sock)
+
+  def testAcceptInterrupted(self):
+    """Tests that accept() is interrupted by SOCK_DESTROY."""
+    for version in [4, 5, 6]:
+      self.IncomingConnection(version, tcp_test.TCP_LISTEN, self.netid)
+      self.CloseDuringBlockingCall(self.s, lambda sock: sock.accept(), EINVAL)
+      self.assertRaisesErrno(ECONNABORTED, self.s.send, "foo")
+      self.assertRaisesErrno(EINVAL, self.s.accept)
+
+  def testReadInterrupted(self):
+    """Tests that read() is interrupted by SOCK_DESTROY."""
+    for version in [4, 5, 6]:
+      self.IncomingConnection(version, tcp_test.TCP_ESTABLISHED, self.netid)
+      self.CloseDuringBlockingCall(self.accepted, lambda sock: sock.recv(4096),
+                                   ECONNABORTED)
+      self.assertRaisesErrno(EPIPE, self.accepted.send, "foo")
+
+  def testConnectInterrupted(self):
+    """Tests that connect() is interrupted by SOCK_DESTROY."""
+    for version in [4, 5, 6]:
+      family = {4: AF_INET, 5: AF_INET6, 6: AF_INET6}[version]
+      s = net_test.Socket(family, SOCK_STREAM, IPPROTO_TCP)
+      self.SelectInterface(s, self.netid, "mark")
+      if version == 5:
+        remoteaddr = "::ffff:" + self.GetRemoteAddress(4)
+        version = 4
+      else:
+        remoteaddr = self.GetRemoteAddress(version)
+      s.bind(("", 0))
+      _, sport = s.getsockname()[:2]
+      self.CloseDuringBlockingCall(
+          s, lambda sock: sock.connect((remoteaddr, 53)), ECONNABORTED)
+      desc, syn = packets.SYN(53, version, self.MyAddress(version, self.netid),
+                              remoteaddr, sport=sport, seq=None)
+      self.ExpectPacketOn(self.netid, desc, syn)
+      msg = "SOCK_DESTROY of socket in connect, expected no RST"
+      self.ExpectNoPacketsOn(self.netid, msg)
+
+
+if __name__ == "__main__":
+  unittest.main()
index eb09b7f..d3efdd9 100755 (executable)
@@ -15,7 +15,6 @@
 # limitations under the License.
 
 import errno
-import os
 import random
 from socket import *  # pylint: disable=wildcard-import
 import time
@@ -26,7 +25,7 @@ from scapy import all as scapy
 import csocket
 import iproute
 import multinetwork_base
-import multinetwork_test
+import packets
 import net_test
 
 # Setsockopt values.
@@ -34,12 +33,26 @@ IPV6_ADDR_PREFERENCES = 72
 IPV6_PREFER_SRC_PUBLIC = 0x0002
 
 
-USE_OPTIMISTIC_SYSCTL = "/proc/sys/net/ipv6/conf/default/use_optimistic"
-
-HAVE_USE_OPTIMISTIC = os.path.isfile(USE_OPTIMISTIC_SYSCTL)
-
-
 class IPv6SourceAddressSelectionTest(multinetwork_base.MultiNetworkBaseTest):
+  """Test for IPv6 source address selection.
+
+  Relevant kernel commits:
+    upstream net-next:
+      7fd2561 net: ipv6: Add a sysctl to make optimistic addresses useful candidates
+      c58da4c net: ipv6: allow explicitly choosing optimistic addresses
+      9131f3d ipv6: Do not iterate over all interfaces when finding source address on specific interface.
+      c0b8da1 ipv6: Fix finding best source address in ipv6_dev_get_saddr().
+      c15df30 ipv6: Remove unused arguments for __ipv6_dev_get_saddr().
+      3985e8a ipv6: sysctl to restrict candidate source addresses
+
+    android-3.10:
+      2ce95507 net: ipv6: Add a sysctl to make optimistic addresses useful candidates
+      0065bf4 net: ipv6: allow choosing optimistic addresses with use_optimistic
+      0633924 ipv6: sysctl to restrict candidate source addresses
+  """
+
+  def SetIPv6Sysctl(self, ifname, sysctl, value):
+    self.SetSysctl("/proc/sys/net/ipv6/conf/%s/%s" % (ifname, sysctl), value)
 
   def SetDAD(self, ifname, value):
     self.SetSysctl("/proc/sys/net/ipv6/conf/%s/accept_dad" % ifname, value)
@@ -120,23 +133,27 @@ class MultiInterfaceSourceAddressSelectionTest(IPv6SourceAddressSelectionTest):
     # [0]  Make sure DAD, optimistic DAD, and the use_optimistic option
     # are all consistently disabled at the outset.
     for netid in self.tuns:
-      self.SetDAD(self.GetInterfaceName(netid), 0)
-      self.SetOptimisticDAD(self.GetInterfaceName(netid), 0)
-      self.SetUseTempaddrs(self.GetInterfaceName(netid), 0)
-      if HAVE_USE_OPTIMISTIC:
-        self.SetUseOptimistic(self.GetInterfaceName(netid), 0)
+      ifname = self.GetInterfaceName(netid)
+      self.SetDAD(ifname, 0)
+      self.SetOptimisticDAD(ifname, 0)
+      self.SetUseTempaddrs(ifname, 0)
+      self.SetUseOptimistic(ifname, 0)
+      self.SetIPv6Sysctl(ifname, "use_oif_addrs_only", 0)
 
     # [1]  Pick an interface on which to test.
     self.test_netid = random.choice(self.tuns.keys())
     self.test_ip = self.MyAddress(6, self.test_netid)
     self.test_ifindex = self.ifindices[self.test_netid]
     self.test_ifname = self.GetInterfaceName(self.test_netid)
+    self.test_lladdr = net_test.GetLinkAddress(self.test_ifname, True)
 
     # [2]  Delete the test interface's IPv6 address.
     self.iproute.DelAddress(self.test_ip, 64, self.test_ifindex)
     self.assertAddressNotPresent(self.test_ip)
 
     self.assertAddressNotUsable(self.test_ip, self.test_netid)
+    # Verify that the link-local address is not tentative.
+    self.assertFalse(self.AddressIsTentative(self.test_lladdr))
 
 
 class TentativeAddressTest(MultiInterfaceSourceAddressSelectionTest):
@@ -194,7 +211,6 @@ class OptimisticAddressTest(MultiInterfaceSourceAddressSelectionTest):
 
 class OptimisticAddressOkayTest(MultiInterfaceSourceAddressSelectionTest):
 
-  @unittest.skipUnless(HAVE_USE_OPTIMISTIC, "use_optimistic not supported")
   def testModifiedRfc6724Behaviour(self):
     # [3]  Get an IPv6 address back, in optimistic DAD start-up.
     self.SetDAD(self.test_ifname, 1)  # Enable DAD
@@ -214,7 +230,6 @@ class OptimisticAddressOkayTest(MultiInterfaceSourceAddressSelectionTest):
 
 class ValidBeforeOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
 
-  @unittest.skipUnless(HAVE_USE_OPTIMISTIC, "use_optimistic not supported")
   def testModifiedRfc6724Behaviour(self):
     # [3]  Add a valid IPv6 address to this interface and verify it is
     # selected as the source address.
@@ -243,7 +258,6 @@ class ValidBeforeOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
 
 class DadFailureTest(MultiInterfaceSourceAddressSelectionTest):
 
-  @unittest.skipUnless(HAVE_USE_OPTIMISTIC, "use_optimistic not supported")
   def testDadFailure(self):
     # [3]  Get an IPv6 address back, in optimistic DAD start-up.
     self.SetDAD(self.test_ifname, 1)  # Enable DAD
@@ -275,9 +289,6 @@ class DadFailureTest(MultiInterfaceSourceAddressSelectionTest):
 
 class NoNsFromOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
 
-  @unittest.skipUnless(HAVE_USE_OPTIMISTIC, "use_optimistic not supported")
-  @unittest.skipUnless(net_test.LinuxVersion() >= (3, 18, 0),
-                       "correct optimistic bind() not supported")
   def testSendToOnlinkDestination(self):
     # [3]  Get an IPv6 address back, in optimistic DAD start-up.
     self.SetDAD(self.test_ifname, 1)  # Enable DAD
@@ -297,15 +308,38 @@ class NoNsFromOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
     onlink_dest = self.GetRandomDestination(self.IPv6Prefix(self.test_netid))
     self.SendWithSourceAddress(self.test_ip, self.test_netid, onlink_dest)
 
-    expected_ns = multinetwork_test.Packets.NS(
-        net_test.GetLinkAddress(self.test_ifname, True),
-        onlink_dest,
-        self.MyMacAddress(self.test_netid))[1]
-    self.ExpectPacketOn(self.test_netid, "link-local NS", expected_ns)
+    if net_test.LinuxVersion() >= (3, 18, 0):
+      # Older versions will actually choose the optimistic address to
+      # originate Neighbor Solications (RFC violation).
+      expected_ns = packets.NS(
+          self.test_lladdr,
+          onlink_dest,
+          self.MyMacAddress(self.test_netid))[1]
+      self.ExpectPacketOn(self.test_netid, "link-local NS", expected_ns)
 
 
 # TODO(ek): add tests listening for netlink events.
 
 
+class DefaultCandidateSrcAddrsTest(MultiInterfaceSourceAddressSelectionTest):
+
+  def testChoosesNonInterfaceSourceAddress(self):
+    self.SetIPv6Sysctl(self.test_ifname, "use_oif_addrs_only", 0)
+    src_ip = self.GetSourceIP(self.test_netid)
+    self.assertFalse(src_ip in [self.test_ip, self.test_lladdr])
+    self.assertTrue(src_ip in
+                    [self.MyAddress(6, netid)
+                     for netid in self.tuns if netid != self.test_netid])
+
+
+class RestrictedCandidateSrcAddrsTest(MultiInterfaceSourceAddressSelectionTest):
+
+  def testChoosesOnlyInterfaceSourceAddress(self):
+    self.SetIPv6Sysctl(self.test_ifname, "use_oif_addrs_only", 1)
+    # self.test_ifname does not have a global IPv6 address, so the only
+    # candidate is the existing link-local address.
+    self.assertAddressSelected(self.test_lladdr, self.test_netid)
+
+
 if __name__ == "__main__":
   unittest.main()
diff --git a/tests/net_test/tcp_nuke_addr_test.py b/tests/net_test/tcp_nuke_addr_test.py
new file mode 100755 (executable)
index 0000000..b0ba27d
--- /dev/null
@@ -0,0 +1,250 @@
+#!/usr/bin/python
+#
+# 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.
+
+import contextlib
+import errno
+import fcntl
+import resource
+import os
+from socket import *  # pylint: disable=wildcard-import
+import struct
+import threading
+import time
+import unittest
+
+import csocket
+import cstruct
+import net_test
+
+IPV4_LOOPBACK_ADDR = "127.0.0.1"
+IPV6_LOOPBACK_ADDR = "::1"
+LOOPBACK_DEV = "lo"
+LOOPBACK_IFINDEX = 1
+
+SIOCKILLADDR = 0x8939
+
+DEFAULT_TCP_PORT = 8001
+DEFAULT_BUFFER_SIZE = 20
+DEFAULT_TEST_MESSAGE = "TCP NUKE ADDR TEST"
+DEFAULT_TEST_RUNS = 100
+HASH_TEST_RUNS = 4000
+HASH_TEST_NOFILE = 16384
+
+
+Ifreq = cstruct.Struct("Ifreq", "=16s16s", "name data")
+In6Ifreq = cstruct.Struct("In6Ifreq", "=16sIi", "addr prefixlen ifindex")
+
+@contextlib.contextmanager
+def RunInBackground(thread):
+  """Starts a thread and waits until it joins.
+
+  Args:
+    thread: A not yet started threading.Thread object.
+  """
+  try:
+    thread.start()
+    yield thread
+  finally:
+    thread.join()
+
+
+def TcpAcceptAndReceive(listening_sock, buffer_size=DEFAULT_BUFFER_SIZE):
+  """Accepts a single connection and blocks receiving data from it.
+
+  Args:
+    listening_socket: A socket in LISTEN state.
+    buffer_size: Size of buffer where to read a message.
+  """
+  connection, _ = listening_sock.accept()
+  with contextlib.closing(connection):
+    _ = connection.recv(buffer_size)
+
+
+def ExchangeMessage(addr_family, ip_addr):
+  """Creates a listening socket, accepts a connection and sends data to it.
+
+  Args:
+    addr_family: The address family (e.g. AF_INET6).
+    ip_addr: The IP address (IPv4 or IPv6 depending on the addr_family).
+    tcp_port: The TCP port to listen on.
+  """
+  # Bind to a random port and connect to it.
+  test_addr = (ip_addr, 0)
+  with contextlib.closing(
+      socket(addr_family, SOCK_STREAM)) as listening_socket:
+    listening_socket.bind(test_addr)
+    test_addr = listening_socket.getsockname()
+    listening_socket.listen(1)
+    with RunInBackground(threading.Thread(target=TcpAcceptAndReceive,
+                                          args=(listening_socket,))):
+      with contextlib.closing(
+          socket(addr_family, SOCK_STREAM)) as client_socket:
+        client_socket.connect(test_addr)
+        client_socket.send(DEFAULT_TEST_MESSAGE)
+
+
+def KillAddrIoctl(addr):
+  """Calls the SIOCKILLADDR ioctl on the provided IP address.
+
+  Args:
+    addr The IP address to pass to the ioctl.
+
+  Raises:
+    ValueError: If addr is of an unsupported address family.
+  """
+  family, _, _, _, _ = getaddrinfo(addr, None, AF_UNSPEC, SOCK_DGRAM, 0,
+                                   AI_NUMERICHOST)[0]
+  if family == AF_INET6:
+    addr = inet_pton(AF_INET6, addr)
+    ifreq = In6Ifreq((addr, 128, LOOPBACK_IFINDEX)).Pack()
+  elif family == AF_INET:
+    addr = inet_pton(AF_INET, addr)
+    sockaddr = csocket.SockaddrIn((AF_INET, 0, addr)).Pack()
+    ifreq = Ifreq((LOOPBACK_DEV, sockaddr)).Pack()
+  else:
+    raise ValueError('Address family %r not supported.' % family)
+  datagram_socket = socket(family, SOCK_DGRAM)
+  fcntl.ioctl(datagram_socket.fileno(), SIOCKILLADDR, ifreq)
+  datagram_socket.close()
+
+
+class ExceptionalReadThread(threading.Thread):
+
+  def __init__(self, sock):
+    self.sock = sock
+    self.exception = None
+    super(ExceptionalReadThread, self).__init__()
+    self.daemon = True
+
+  def run(self):
+    try:
+      read = self.sock.recv(4096)
+    except Exception, e:
+      self.exception = e
+
+# For convenience.
+def CreateIPv4SocketPair():
+  return net_test.CreateSocketPair(AF_INET, SOCK_STREAM, IPV4_LOOPBACK_ADDR)
+
+def CreateIPv6SocketPair():
+  return net_test.CreateSocketPair(AF_INET6, SOCK_STREAM, IPV6_LOOPBACK_ADDR)
+
+
+class TcpNukeAddrTest(net_test.NetworkTest):
+
+  def testTimewaitSockets(self):
+    """Tests that SIOCKILLADDR works as expected.
+
+    Relevant kernel commits:
+      https://www.codeaurora.org/cgit/quic/la/kernel/msm-3.18/commit/net/ipv4/tcp.c?h=aosp/android-3.10&id=1dcd3a1fa2fe78251cc91700eb1d384ab02e2dd6
+    """
+    for i in xrange(DEFAULT_TEST_RUNS):
+      ExchangeMessage(AF_INET6, IPV6_LOOPBACK_ADDR)
+      KillAddrIoctl(IPV6_LOOPBACK_ADDR)
+      ExchangeMessage(AF_INET, IPV4_LOOPBACK_ADDR)
+      KillAddrIoctl(IPV4_LOOPBACK_ADDR)
+      # Test passes if kernel does not crash.
+
+  def testClosesIPv6Sockets(self):
+    """Tests that SIOCKILLADDR closes IPv6 sockets and unblocks threads."""
+
+    threadpairs = []
+
+    for i in xrange(DEFAULT_TEST_RUNS):
+      clientsock, acceptedsock = CreateIPv6SocketPair()
+      clientthread = ExceptionalReadThread(clientsock)
+      clientthread.start()
+      serverthread = ExceptionalReadThread(acceptedsock)
+      serverthread.start()
+      threadpairs.append((clientthread, serverthread))
+
+    KillAddrIoctl(IPV6_LOOPBACK_ADDR)
+
+    def CheckThreadException(thread):
+      thread.join(100)
+      self.assertFalse(thread.is_alive())
+      self.assertIsNotNone(thread.exception)
+      self.assertTrue(isinstance(thread.exception, IOError))
+      self.assertEquals(errno.ETIMEDOUT, thread.exception.errno)
+      self.assertRaisesErrno(errno.ENOTCONN, thread.sock.getpeername)
+      self.assertRaisesErrno(errno.EISCONN, thread.sock.connect,
+                             (IPV6_LOOPBACK_ADDR, 53))
+      self.assertRaisesErrno(errno.EPIPE, thread.sock.send, "foo")
+
+    for clientthread, serverthread in threadpairs:
+      CheckThreadException(clientthread)
+      CheckThreadException(serverthread)
+
+  def assertSocketsClosed(self, socketpair):
+    for sock in socketpair:
+      self.assertRaisesErrno(errno.ENOTCONN, sock.getpeername)
+
+  def assertSocketsNotClosed(self, socketpair):
+    for sock in socketpair:
+      self.assertTrue(sock.getpeername())
+
+  def testAddresses(self):
+    socketpair = CreateIPv4SocketPair()
+    KillAddrIoctl("::")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("::1")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("127.0.0.3")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("0.0.0.0")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("127.0.0.1")
+    self.assertSocketsClosed(socketpair)
+
+    socketpair = CreateIPv6SocketPair()
+    KillAddrIoctl("0.0.0.0")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("127.0.0.1")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("::2")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("::")
+    self.assertSocketsNotClosed(socketpair)
+    KillAddrIoctl("::1")
+    self.assertSocketsClosed(socketpair)
+
+
+class TcpNukeAddrHashTest(net_test.NetworkTest):
+
+  def setUp(self):
+    self.nofile = resource.getrlimit(resource.RLIMIT_NOFILE)
+    resource.setrlimit(resource.RLIMIT_NOFILE, (HASH_TEST_NOFILE,
+                                                HASH_TEST_NOFILE))
+
+  def tearDown(self):
+    resource.setrlimit(resource.RLIMIT_NOFILE, self.nofile)
+
+  def testClosesAllSockets(self):
+    socketpairs = []
+    for i in xrange(HASH_TEST_RUNS):
+      socketpairs.append(CreateIPv4SocketPair())
+      socketpairs.append(CreateIPv6SocketPair())
+
+    KillAddrIoctl(IPV4_LOOPBACK_ADDR)
+    KillAddrIoctl(IPV6_LOOPBACK_ADDR)
+
+    for socketpair in socketpairs:
+      for sock in socketpair:
+        self.assertRaisesErrno(errno.ENOTCONN, sock.getpeername)
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tests/net_test/tcp_test.py b/tests/net_test/tcp_test.py
new file mode 100644 (file)
index 0000000..81a6884
--- /dev/null
@@ -0,0 +1,124 @@
+#!/usr/bin/python
+#
+# 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.
+
+import time
+from socket import *  # pylint: disable=wildcard-import
+
+import net_test
+import multinetwork_base
+import packets
+
+# TCP states. See include/net/tcp_states.h.
+TCP_ESTABLISHED = 1
+TCP_SYN_SENT = 2
+TCP_SYN_RECV = 3
+TCP_FIN_WAIT1 = 4
+TCP_FIN_WAIT2 = 5
+TCP_TIME_WAIT = 6
+TCP_CLOSE = 7
+TCP_CLOSE_WAIT = 8
+TCP_LAST_ACK = 9
+TCP_LISTEN = 10
+TCP_CLOSING = 11
+TCP_NEW_SYN_RECV = 12
+
+TCP_NOT_YET_ACCEPTED = -1
+
+
+class TcpBaseTest(multinetwork_base.MultiNetworkBaseTest):
+
+  def tearDown(self):
+    if hasattr(self, "s"):
+      self.s.close()
+    super(TcpBaseTest, self).tearDown()
+
+  def OpenListenSocket(self, version, netid):
+    self.port = packets.RandomPort()
+    family = {4: AF_INET, 5: AF_INET6, 6: AF_INET6}[version]
+    address = {4: "0.0.0.0", 5: "::", 6: "::"}[version]
+    s = net_test.Socket(family, SOCK_STREAM, IPPROTO_TCP)
+    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+    s.bind((address, self.port))
+    # We haven't configured inbound iptables marking, so bind explicitly.
+    self.SelectInterface(s, netid, "mark")
+    s.listen(100)
+    return s
+
+  def _ReceiveAndExpectResponse(self, netid, packet, reply, msg):
+    pkt = super(TcpBaseTest, self)._ReceiveAndExpectResponse(netid, packet,
+                                                             reply, msg)
+    self.last_packet = pkt
+    return pkt
+
+  def ReceivePacketOn(self, netid, packet):
+    super(TcpBaseTest, self).ReceivePacketOn(netid, packet)
+    self.last_packet = packet
+
+  def RstPacket(self):
+    return packets.RST(self.version, self.myaddr, self.remoteaddr,
+                       self.last_packet)
+
+  def IncomingConnection(self, version, end_state, netid):
+    self.s = self.OpenListenSocket(version, netid)
+    self.end_state = end_state
+
+    remoteaddr = self.remoteaddr = self.GetRemoteAddress(version)
+    myaddr = self.myaddr = self.MyAddress(version, netid)
+
+    if version == 5: version = 4
+    self.version = version
+
+    if end_state == TCP_LISTEN:
+      return
+
+    desc, syn = packets.SYN(self.port, version, remoteaddr, myaddr)
+    synack_desc, synack = packets.SYNACK(version, myaddr, remoteaddr, syn)
+    msg = "Received %s, expected to see reply %s" % (desc, synack_desc)
+    reply = self._ReceiveAndExpectResponse(netid, syn, synack, msg)
+    if end_state == TCP_SYN_RECV:
+      return
+
+    establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
+    self.ReceivePacketOn(netid, establishing_ack)
+
+    if end_state == TCP_NOT_YET_ACCEPTED:
+      return
+
+    self.accepted, _ = self.s.accept()
+    net_test.DisableLinger(self.accepted)
+
+    if end_state == TCP_ESTABLISHED:
+      return
+
+    desc, data = packets.ACK(version, myaddr, remoteaddr, establishing_ack,
+                             payload=net_test.UDP_PAYLOAD)
+    self.accepted.send(net_test.UDP_PAYLOAD)
+    self.ExpectPacketOn(netid, msg + ": expecting %s" % desc, data)
+
+    desc, fin = packets.FIN(version, remoteaddr, myaddr, data)
+    fin = packets._GetIpLayer(version)(str(fin))
+    ack_desc, ack = packets.ACK(version, myaddr, remoteaddr, fin)
+    msg = "Received %s, expected to see reply %s" % (desc, ack_desc)
+
+    # TODO: Why can't we use this?
+    #   self._ReceiveAndExpectResponse(netid, fin, ack, msg)
+    self.ReceivePacketOn(netid, fin)
+    time.sleep(0.1)
+    self.ExpectPacketOn(netid, msg + ": expecting %s" % ack_desc, ack)
+    if end_state == TCP_CLOSE_WAIT:
+      return
+
+    raise ValueError("Invalid TCP state %d specified" % end_state)
index 727e3b8..23ffea1 100644 (file)
@@ -8,6 +8,8 @@ LOCAL_SRC_FILES:=    \
     pageinout_test.c \
     thrashing_test.c
 
+LOCAL_CFLAGS := -std=gnu11
+
 LOCAL_MODULE:= pagingtest
 
 LOCAL_MODULE_TAGS := tests
index 165cd99..7ecd3ad 100644 (file)
@@ -14,7 +14,6 @@ int thrashing_test(int test_runs) {
     int fds[4] = {-1, -1, -1, -1};
     char tmpnames[4][17] = { "thrashing1XXXXXX", "thrashing2XXXXXX", "thrashing3XXXXXX", "thrashing4XXXXXX" };
     volatile char *bufs[4] = {0};
-    unsigned i, j;
     long long k;
     int ret = -1;
     struct timeval begin_time, end_time, elapsed_time, total_time;
@@ -33,14 +32,14 @@ int thrashing_test(int test_runs) {
 
     filesize = num_pages * pagesize / (ARRAY_SIZE(fds) - 1);
 
-    for (i = 0; i < ARRAY_SIZE(fds); i++) {
+    for (size_t i = 0; i < ARRAY_SIZE(fds); i++) {
         fds[i] = create_tmp_file(tmpnames[i], filesize);
         if (fds[i] < 0) {
             goto err_fd;
         }
     }
 
-    for (i = 0; i < ARRAY_SIZE(fds); i++) {
+    for (size_t i = 0; i < ARRAY_SIZE(fds); i++) {
         bufs[i] = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fds[i], 0);
         if (bufs[i] == ((void *)-1)) {
             fprintf(stderr, "Failed to mmap file: %s\n", strerror(errno));
@@ -48,8 +47,8 @@ int thrashing_test(int test_runs) {
         }
     }
 
-    for (i = 0; i < test_runs; i++) {
-        for (j = 0; j < ARRAY_SIZE(fds); j++) {
+    for (int i = 0; i < test_runs; i++) {
+        for (size_t j = 0; j < ARRAY_SIZE(fds); j++) {
             gettimeofday(&begin_time, NULL);
             //Unfortunately when under memory pressure, fadvise and madvise stop working...
             //Read backwards to prevent mmap prefetching
@@ -69,11 +68,11 @@ int thrashing_test(int test_runs) {
     ret = 0;
 
 err:
-    for (i = 0; i < ARRAY_SIZE(bufs) && bufs[i] != NULL; i++) {
+    for (size_t i = 0; i < ARRAY_SIZE(bufs) && bufs[i] != NULL; i++) {
         munmap((void *)bufs[i], filesize);
     }
 err_fd:
-    for (i = 0; i < ARRAY_SIZE(fds) && fds[i] >= 0; i++) {
+    for (size_t i = 0; i < ARRAY_SIZE(fds) && fds[i] >= 0; i++) {
         close(fds[i]);
     }
     return ret;
index 58e8d62..036c9fe 100644 (file)
@@ -6,5 +6,6 @@ LOCAL_SRC_FILES := \
        schedtest.c
 
 LOCAL_MODULE := schedtest
+LOCAL_CFLAGS := -Wno-unused-parameter
 
 include $(BUILD_EXECUTABLE)
diff --git a/tests/tcp_nuke_addr/Android.mk b/tests/tcp_nuke_addr/Android.mk
new file mode 100644 (file)
index 0000000..2eef608
--- /dev/null
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := tcp_nuke_addr_test
+
+LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
+LOCAL_CPPFLAGS += -std=c++11 -Wall -Werror
+LOCAL_SHARED_LIBRARIES := libc++
+LOCAL_SRC_FILES := tcp_nuke_addr_test.cpp
+LOCAL_MODULE_TAGS := eng tests
+
+include $(BUILD_NATIVE_TEST)
diff --git a/tests/tcp_nuke_addr/tcp_nuke_addr_test.cpp b/tests/tcp_nuke_addr/tcp_nuke_addr_test.cpp
new file mode 100644 (file)
index 0000000..587f768
--- /dev/null
@@ -0,0 +1,150 @@
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <mutex>
+#include <thread>
+
+#include "utils/RWLock.h"
+
+// Defined only in ifc_utils.c, in the kernel, and in the NDK.
+#ifndef SIOCKILLADDR
+#define SIOCKILLADDR 0x8939
+#endif
+
+#ifndef TCP_LINGER2
+#define TCP_LINGER2 8
+#endif
+
+#define KILL_INTERVAL_MS 10
+#define CONNECT_THREADS 1
+
+#define PERROR_EXIT(msg) { do { perror((msg)); exit(1); } while (0); };
+
+
+// Ensures that sockets don't stay in TIME_WAIT state.
+void setSoLinger(int s) {
+    const struct linger l = {
+        0,  // off
+        0,  // 0 seconds
+    };
+    if (setsockopt(s, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) {
+        PERROR_EXIT("SO_LINGER");
+    }
+    const int nolinger = -1;
+    if (setsockopt(s, SOL_TCP, TCP_LINGER2, &nolinger, sizeof(nolinger)) == -1) {
+        PERROR_EXIT("TCP_LINGER2");
+    }
+}
+
+
+// Binds to a random port on a random loopback address. We don't just use 127.0.0.1 because we don't
+// want this test to kill unrelated connections on loopback.
+int bindRandomAddr() {
+    sockaddr_in sin;
+    sin.sin_family = AF_INET;
+    sin.sin_port = 0;
+    sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+    while (sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
+        arc4random_buf(
+             ((uint8_t *) &sin.sin_addr.s_addr) + 1,
+             sizeof(sin.sin_addr.s_addr) - 1);
+    }
+
+    int listensock;
+    if ((listensock = socket(AF_INET, SOCK_STREAM, 0)) == -1) PERROR_EXIT("listensock");
+    if (bind(listensock, (sockaddr *) &sin, sizeof(sin)) == -1) PERROR_EXIT("bind");
+    if (listen(listensock, 10) == -1) PERROR_EXIT("listen");
+
+    return listensock;
+}
+
+
+// Thread that calls SIOCKILLADDR in a loop.
+void killSockets(sockaddr_in listenaddr, int intervalMs, android::RWLock *lock) {
+    ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    listenaddr.sin_port = 0;
+    strncpy(ifr.ifr_name, "lo", strlen("lo"));
+    memcpy(&ifr.ifr_addr, &listenaddr, sizeof(listenaddr));
+
+    int ioctlsock = socket(AF_INET, SOCK_DGRAM, 0);
+    if (ioctlsock == -1) PERROR_EXIT("ioctlsock");
+    while(true) {
+        lock->writeLock();
+        if (ioctl(ioctlsock, SIOCKILLADDR, &ifr) != 0) {
+            PERROR_EXIT("SIOCKILLADDR failed, did you run 32-bit userspace on a 64-bit kernel?");
+        }
+        lock->unlock();
+        std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
+    }
+}
+
+
+// Thread that calls connect() in a loop.
+void connectLoop(sockaddr_in listenaddr, int listensock,
+        android::RWLock *lock, std::atomic<unsigned int> *attempts) {
+    while(true) {
+        int s = socket(AF_INET, SOCK_STREAM, 0);
+        setSoLinger(s);
+
+        // Don't call SIOCKILLADDR while connect() is running, or we'll end up with lots of
+        // connections in state FIN_WAITx or TIME_WAIT, which will then slow down future
+        // due to SYN retransmits.
+        lock->readLock();
+        if (connect(s, (sockaddr *) &listenaddr, sizeof(listenaddr)) == -1) PERROR_EXIT("connect");
+        lock->unlock();
+
+        send(s, "foo", 3, 0);
+        int acceptedsock = accept(listensock, NULL, 0);
+        if (close(acceptedsock) == -1) PERROR_EXIT("close");
+        if (close(s) == -1) PERROR_EXIT("close");
+
+        attempts->fetch_add(1);
+    }
+}
+
+
+// Thread that prints progress every second.
+void progressThread(std::atomic<unsigned int> *attempts) {
+    uint32_t elapsed = 0;
+    uint32_t total, previous = 0;
+    while (true) {
+        std::this_thread::sleep_for(std::chrono::seconds(1));
+        elapsed++;
+        total = attempts->load();
+        printf("%ds: %u cps, total %u\n", elapsed, total-previous, total);
+        fflush(stdout);
+        previous = total;
+    }
+}
+
+
+int main() {
+    int listensock = bindRandomAddr();
+    struct sockaddr_in sin;
+    socklen_t len = sizeof(sin);
+    if (getsockname(listensock, (sockaddr *) &sin, &len) == -1) PERROR_EXIT("getsockname");
+
+    printf("Using address %s:%d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+
+    android::RWLock lock;
+    std::atomic<unsigned int> attempts;
+    attempts.store(0);
+
+    std::thread t0(killSockets, sin, KILL_INTERVAL_MS, &lock);
+    std::thread *connectThreads[CONNECT_THREADS];
+    for (size_t i = 0; i < CONNECT_THREADS; i++) {
+        connectThreads[i] = new std::thread(connectLoop, sin, listensock, &lock, &attempts);
+    }
+    std::thread t1(progressThread, &attempts);
+    t1.join();
+
+    return 0;
+}
index 05e21fb..b2a1aa5 100644 (file)
@@ -1,6 +1,7 @@
 # Copyright 2006 The Android Open Source Project
 
 LOCAL_PATH:= $(call my-dir)
+
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= timetest.c
@@ -17,3 +18,25 @@ LOCAL_STATIC_LIBRARIES := libc
 
 include $(BUILD_EXECUTABLE)
 
+# -----------------------------------------------------------------------------
+# Unit tests.
+# -----------------------------------------------------------------------------
+
+test_c_flags := \
+    -fstack-protector-all \
+    -g \
+    -Wall -Wextra \
+    -Werror \
+    -fno-builtin \
+    -std=gnu++11
+
+test_src_files := \
+    rtc_test.cpp
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := time-unit-tests
+LOCAL_MODULE_TAGS := tests
+LOCAL_CFLAGS += $(test_c_flags)
+LOCAL_SRC_FILES := $(test_src_files)
+include $(BUILD_NATIVE_TEST)
+
diff --git a/tests/timetest/rtc_test.cpp b/tests/timetest/rtc_test.cpp
new file mode 100644 (file)
index 0000000..26ca13a
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/rtc.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+static int hwtime(int flag, int request, struct rtc_time *tm) {
+  static const char rtc[] = "/dev/rtc0";
+
+  int ret = TEMP_FAILURE_RETRY(access(rtc, flag & O_WRONLY) ? W_OK : R_OK);
+  if (ret < 0) {
+    ret = -errno;
+  }
+  if (ret == -EACCES) {
+    return ret;
+  }
+
+  if (flag & O_WRONLY) {
+    struct stat st;
+    ret = TEMP_FAILURE_RETRY(stat(rtc, &st));
+    if (ret < 0) {
+      ret = -errno;
+    } else if (!(st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))) {
+      ret = -EACCES;
+    }
+  }
+  if (ret == -EACCES) {
+    return ret;
+  }
+
+  do {
+    ret = TEMP_FAILURE_RETRY(open(rtc, flag));
+    if (ret < 0) {
+      ret = -errno;
+    }
+  } while (ret == -EBUSY);
+  if (ret < 0) {
+    return ret;
+  }
+
+  int fd = ret;
+  do {
+    ret = TEMP_FAILURE_RETRY(ioctl(fd, request, tm));
+    if (ret < 0) {
+      ret = -errno;
+    }
+  } while (ret == -EBUSY);
+  close(fd);
+  return ret;
+}
+
+static int rd_hwtime(struct rtc_time *tm) {
+  return hwtime(O_RDONLY, RTC_RD_TIME, tm);
+}
+
+static int set_hwtime(struct rtc_time *tm) {
+  return hwtime(O_WRONLY, RTC_SET_TIME, tm);
+}
+
+static void rtc_rollover(int start, int end) {
+  struct rtc_time roll;
+  memset(&roll, 0, sizeof(roll));
+  ASSERT_LE(0, rd_hwtime(&roll));
+  int mday[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+  mday[1] = (roll.tm_year % 4) ? 28 : 29;
+  ASSERT_LE(0, roll.tm_sec);
+  ASSERT_GT(60, roll.tm_sec);
+  ASSERT_LE(0, roll.tm_min);
+  ASSERT_GT(60, roll.tm_min);
+  ASSERT_LE(0, roll.tm_hour);
+  ASSERT_GT(24, roll.tm_hour);
+  ASSERT_LE(0, roll.tm_mday);
+  ASSERT_GE(mday[roll.tm_mon], roll.tm_mday);
+  ASSERT_LE(0, roll.tm_mon);
+  ASSERT_GT(12, roll.tm_mon);
+  ASSERT_LE(0, roll.tm_year);
+  ASSERT_GT(138, roll.tm_year);
+
+  // Wait for granular clock
+  struct rtc_time save = roll;
+  static const useconds_t timeout_sleep = 10000;
+  static const int timeout_num = 2000000 / timeout_sleep;
+  int timeout;
+  for (timeout = timeout_num; timeout && (roll.tm_year == save.tm_year); --timeout) {
+    ASSERT_LE(0, rd_hwtime(&save));
+    usleep(timeout_sleep);
+  }
+
+  memset(&roll, 0, sizeof(roll));
+  roll.tm_sec = 59;
+  roll.tm_min = 59;
+  roll.tm_hour = 23;
+  roll.tm_mday = 31;
+  roll.tm_mon = 11;
+  roll.tm_year = 70;
+  roll.tm_wday = 0;
+  roll.tm_yday = 0;
+  roll.tm_isdst = 0;
+
+  bool eacces = true;
+  for (roll.tm_year = start; roll.tm_year < end; ++roll.tm_year) {
+    struct rtc_time tm = roll;
+    int __set_hwtime = set_hwtime(&tm);
+    // Allowed to be 100% denied for writing
+    if ((__set_hwtime == -EACCES) && (eacces == true)) {
+      continue;
+    }
+    eacces = false;
+    // below 2016, permitted to error out.
+    if ((__set_hwtime == -EINVAL) && (roll.tm_year < 116)) {
+      continue;
+    }
+    ASSERT_LE(0, __set_hwtime);
+    ASSERT_LE(0, rd_hwtime(&tm));
+    ASSERT_EQ(roll.tm_sec, tm.tm_sec);
+    ASSERT_EQ(roll.tm_min, tm.tm_min);
+    ASSERT_EQ(roll.tm_hour, tm.tm_hour);
+    ASSERT_EQ(roll.tm_mday, tm.tm_mday);
+    ASSERT_EQ(roll.tm_mon, tm.tm_mon);
+    ASSERT_EQ(roll.tm_year, tm.tm_year);
+    for (timeout = timeout_num; timeout && (roll.tm_year == tm.tm_year); --timeout) {
+      ASSERT_LE(0, rd_hwtime(&tm));
+      usleep(timeout_sleep);
+    }
+    ASSERT_EQ(roll.tm_year + 1, tm.tm_year);
+    EXPECT_LT(timeout_num * 5 / 100, timeout);
+    EXPECT_GT(timeout_num * 95 / 100, timeout);
+
+    // correct saved time to compensate for rollover check
+    if (++save.tm_sec >= 60) {
+      save.tm_sec = 0;
+      if (++save.tm_min >= 60) {
+        save.tm_min = 0;
+        if (++save.tm_hour >= 24) {
+          save.tm_hour = 0;
+          mday[1] = (save.tm_year % 4) ? 28 : 29;
+          if (++save.tm_mday >= mday[save.tm_mon]) {
+            save.tm_mday = 1;
+            if (++save.tm_mon >= 12) {
+              save.tm_mon = 0;
+              ++save.tm_year;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if (!eacces) {
+    ASSERT_LE(0, set_hwtime(&save));
+  }
+  ASSERT_LE(0, rd_hwtime(&roll));
+
+  if (!eacces) {
+    ASSERT_EQ(save.tm_sec, roll.tm_sec);
+    ASSERT_EQ(save.tm_min, roll.tm_min);
+    ASSERT_EQ(save.tm_hour, roll.tm_hour);
+    ASSERT_EQ(save.tm_mday, roll.tm_mday);
+    ASSERT_EQ(save.tm_mon, roll.tm_mon);
+    ASSERT_EQ(save.tm_year, roll.tm_year);
+  }
+}
+
+TEST(time, rtc_rollover_1970_1990) {
+  rtc_rollover(70, 90);
+}
+
+TEST(time, rtc_rollover_1990_2010) {
+  rtc_rollover(90, 110);
+}
+
+TEST(time, rtc_rollover_2010_2030) {
+  rtc_rollover(110, 130);
+}
+
+TEST(time, rtc_rollover_2030_2037) {
+  rtc_rollover(130, 137);
+}
index f04b211..fb8a851 100644 (file)
@@ -6,6 +6,6 @@ LOCAL_SRC_FILES := uevents.c
 LOCAL_SHARED_LIBRARIES += libcutils
 LOCAL_MODULE:= uevents
 
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wno-unused-parameter
 
 include $(BUILD_EXECUTABLE)
index 3b2f446..9c3eadc 100755 (executable)
@@ -38,7 +38,7 @@ function convert {
 
 
 case $DEVICE in
-(shamu|hammerhead|bullhead)
+(shamu|hammerhead|bullhead|ariel)
        # no scaling necessary
        xmax=0
        ymax=0;;
diff --git a/tests/workloads/chromefling.sh b/tests/workloads/chromefling.sh
new file mode 100755 (executable)
index 0000000..db6aa2e
--- /dev/null
@@ -0,0 +1,153 @@
+#
+# Script to start 3 chrome tabs, fling each of them, repeat
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+iterations=10
+startapps=1
+capturesystrace=0
+waittime=4
+app=chrome
+
+function processLocalOption {
+       ret=0
+       case "$1" in
+       (-N) startapps=0;;
+       (-A) unset appList;;
+       (-L) appList=$2; shift; ret=1;;
+       (-T) capturesystrace=1;;
+       (-W) waittime=$2; shift; ret=1;;
+       (*)
+               echo "$0: unrecognized option: $1"
+               echo; echo "Usage: $0 [options]"
+               echo "-A : use all known applications"
+               echo "-L applist : list of applications"
+               echo "   default: $appList"
+               echo "-N : no app startups, just fling"
+               echo "-g : generate activity strings"
+               echo "-i iterations"
+               echo "-T : capture systrace on each iteration"
+               echo "-d device : device type (shamu, volantis, bullhead,...)"
+               exit 1;;
+       esac
+       return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(hammerhead)
+       flingtime=300
+       downCount=2
+       upCount=6
+       UP="70 400 70 100 $flingtime"
+       DOWN="70 100 70 400 $flingtime";;
+(shamu)
+       flingtime=100
+       downCount=2 
+       upCount=2
+       UP="700 1847 700 400 $flingtime"
+       DOWN="700 400 700 1847 $flingtime";;
+(angler)
+       flingtime=150
+       downCount=4
+       upCount=3
+       UP="500 1200 500 550 $flingtime"
+       DOWN="500 550 500 1200 $flingtime";;
+(bullhead|volantis)
+       flingtime=200
+       downCount=5
+       upCount=5
+       UP="500 1400 500 400 $flingtime"
+       DOWN="500 400 500 1400 $flingtime";;
+(ariel)
+       flingtime=200
+       downCount=5
+       upCount=5
+       UP="500 1560 500 530 $flingtime"
+       DOWN="500 530 500 1560 $flingtime";;
+(*)
+       echo "Error: No display information available for $DEVICE"
+       exit 1;;
+esac
+
+function swipe {
+       count=0
+       while [ $count -lt $2 ]
+       do
+               doSwipe $1
+               ((count=count+1))
+       done
+       sleep 1
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames $(getPackageName $app)
+
+while [ $cur -le $iterations ]
+do
+       if [ $capturesystrace -gt 0 ]; then
+               ${ADB}atrace --async_start -z -c -b 16000 freq gfx view idle sched
+       fi
+       t=$(startActivity $app)
+       sleep $waittime
+       swipe "$UP" $upCount
+
+       sleep $waittime
+       swipe "$DOWN" $downCount
+
+       doKeyevent BACK
+       sleep 0.5
+
+       if [ $capturesystrace -gt 0 ]; then
+               ${ADB}atrace --async_dump -z -c -b 16000 freq gfx view idle sched > trace.${cur}.out
+       fi
+       doKeyevent HOME
+       sleep 0.5
+
+       set -- $(getJankyFrames $(getPackageName $app))
+       totalDiff=$1
+       jankyDiff=$2
+       latency90=$3
+       latency95=$4
+       latency99=$5
+       if [ ${totalDiff:=0} -eq 0 ]; then
+               echo Error: could not read frame info with \"dumpsys gfxinfo\"
+               exit 1
+       fi
+
+       ((frameSum=frameSum+totalDiff))
+       ((jankSum=jankSum+jankyDiff))
+       ((latency90Sum=latency90Sum+latency90))
+       ((latency95Sum=latency95Sum+latency95))
+       ((latency99Sum=latency99Sum+latency99))
+       if [ "$totalDiff" -eq 0 ]; then
+               echo Error: no frames detected. Is the display off?
+               exit 1
+       fi
+       ((jankPct=jankyDiff*100/totalDiff))
+       resetJankyFrames $(getPackageName $app)
+
+
+       echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+       ((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)
index a2b7138..400722b 100755 (executable)
@@ -10,21 +10,25 @@ generateActivities=0
 
 # default activities. Can dynamically generate with -g.
 gmailActivity='com.google.android.gm/com.google.android.gm.ConversationListActivityGmail'
+clockActivity='com.google.android.deskclock/com.android.deskclock.DeskClock'
 hangoutsActivity='com.google.android.talk/com.google.android.talk.SigningInActivity'
 chromeActivity='com.android.chrome/_not_used'
+contactsActivity='com.google.android.contacts/com.android.contacts.activities.PeopleActivity'
 youtubeActivity='com.google.android.youtube/com.google.android.apps.youtube.app.WatchWhileActivity'
 cameraActivity='com.google.android.GoogleCamera/com.android.camera.CameraActivity'
 playActivity='com.android.vending/com.google.android.finsky.activities.MainActivity'
 feedlyActivity='com.devhd.feedly/com.devhd.feedly.Main'
-photosActivity='com.google.android.apps.plus/com.google.android.apps.photos.phone.PhotosHomeActivity'
+photosActivity='com.google.android.apps.photos/com.google.android.apps.photos.home.HomeActivity'
 mapsActivity='com.google.android.apps.maps/com.google.android.maps.MapsActivity'
 calendarActivity='com.google.android.calendar/com.android.calendar.AllInOneActivity'
 earthActivity='com.google.earth/com.google.earth.EarthActivity'
-calculatorActivity='com.android.calculator2/com.android.calculator2.Calculator'
+calculatorActivity='com.google.android.calculator/com.android.calculator2.Calculator'
+calculatorLActivity='com.android.calculator2/com.android.calculator2.Calculator'
 sheetsActivity='com.google.android.apps.docs.editors.sheets/com.google.android.apps.docs.app.NewMainProxyActivity'
 docsActivity='com.google.android.apps.docs.editors.docs/com.google.android.apps.docs.app.NewMainProxyActivity'
 operaActivity='com.opera.mini.native/com.opera.mini.android.Browser'
 firefoxActivity='org.mozilla.firefox/org.mozilla.firefox.App'
+suntempleActivity='com.BrueComputing.SunTemple/com.epicgames.ue4.GameActivity'
 homeActivity='com.google.android.googlequicksearchbox/com.google.android.launcher.GEL'
 
 function showUsage {
@@ -93,10 +97,24 @@ else
        fi
        deviceName=$1
        ADB="adb -s $deviceName shell "
-       DEVICE=$(echo $4 | sed 's/product://')
+       if [ "$DEVICE" = "" -o "$DEVICE" = unknown ]; then
+               DEVICE=$(echo $4 | sed 's/product://')
+       fi
        isOnDevice=0
 fi
 
+if [ $isOnDevice -gt 0 ]; then
+       case "$DEVICE" in
+       (bullhead|angler)
+               if ! echo $$ > /dev/cpuset/background/tasks; then
+                       echo Could not put PID $$ in background
+               fi
+               ;;
+       (*)
+               ;;
+       esac
+fi
+
 # default values if not set by options or calling script
 appList=${appList:=$dfltAppList}
 savetmpfiles=${savetmpfiles:=0}
@@ -109,7 +127,9 @@ ADB=${ADB:=""}
 output=${output:="./out"}
 
 # clear the output file
-> $output
+if [ -f $output ]; then
+       > $output
+fi
 
 # ADB commands
 AM_FORCE_START="${ADB}am start -W -S"
@@ -162,6 +182,7 @@ function log2msec {
        in=$1
        in=${in:=0.0}
        set -- $(echo $in | tr . " ")
+
        # shell addition via (( )) doesn't like leading zeroes in msecs
        # field so remove leading zeroes
        msecfield=$(expr 0 + $2)
@@ -209,7 +230,7 @@ function getEndTime {
 
 function resetJankyFrames {
        _gfxapp=$1
-       _gfxapp=${app:="com.android.systemui"}
+       _gfxapp=${_gfxapp:="com.android.systemui"}
        ${ADB}dumpsys gfxinfo $_gfxapp reset 2>&1 >/dev/null
 }
 
@@ -256,14 +277,21 @@ function checkForDirectReclaim {
 }
 
 function startInstramentation {
+       _iter=$1
+       _iter=${_iter:=0}
+       enableAtrace=$2
+       enableAtrace=${enableAtrace:=1}
        # Called at beginning of loop. Turn on instramentation like atrace
        vout start instramentation $(date)
        echo =============================== >> $output
-       echo Before iteration >> $output
+       echo Before iteration $_iter >> $output
        echo =============================== >> $output
        ${ADB}cat /proc/meminfo 2>&1 >> $output
        ${ADB}dumpsys meminfo 2>&1 >> $output
-       if [ "$user" = root ]; then
+       if [ "$DEVICE" = volantis ]; then
+               ${ADB}cat /d/nvmap/iovmm/procrank 2>&1 >> $output
+       fi
+       if [ "$user" = root -a $enableAtrace -gt 0 ]; then
                vout ${ADB}atrace -b 32768 --async_start $tracecategories
                ${ADB}atrace -b 32768 --async_start $tracecategories >> $output
                echo >> $output
@@ -271,14 +299,15 @@ function startInstramentation {
 }
 
 function stopInstramentation {
-       if [ "$user" = root ]; then
+       enableAtrace=$1
+       enableAtrace=${enableAtrace:=1}
+       if [ "$user" = root -a $enableAtrace -gt 0 ]; then
                vout ${ADB}atrace --async_stop
                ${ADB}atrace --async_stop > /dev/null
        fi
 }
 
 function stopAndDumpInstramentation {
-       # Called at beginning of loop. Turn on instramentation like atrace
        vout stop instramentation $(date)
        echo =============================== >> $output
        echo After iteration >> $output
@@ -300,9 +329,9 @@ function stopAndDumpInstramentation {
                        python $UNCOMPRESS $tmpTrace >> $traceout
                        rm -f $tmpTrace
                else
-                       ${ADB}atrace $zarg -b 32768 --async_dump >> $traceout
+                       ${ADB}atrace -b 32768 --async_dump > $traceout
                fi
-               vout ${ADB}atrace $zarg --async_dump
+               vout ${ADB}atrace $zarg -b 32768 --async_dump
                vout ${ADB}atrace --async_stop
                ${ADB}atrace --async_stop > /dev/null
        fi
@@ -336,7 +365,7 @@ function startActivity {
                echo 0
                return 0
        elif [ "$1" = chrome ]; then
-               if [ "$DEVICE" = volantis ]; then
+               if [ "$DEVICE" = volantis -o "$DEVICE" = ariel ]; then
                        vout $AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com
                        $AM_START_NOWAIT -p "$(getPackageName $1)" http://www.theverge.com > /dev/null
                        set -- 0 0
@@ -375,7 +404,14 @@ function checkActivity {
 
 function doSwipe {
        vout ${ADB}input swipe $*
-       ${ADB}input swipe $*
+       ${ADB}nice input swipe $*
+}
+
+function doText {
+       echo $* > ./tmpOutput
+       vout ${ADB}input text \"$*\"
+       ${ADB}input text "$(cat ./tmpOutput)"
+       rm -f ./tmpOutput
 }
 
 function doTap {
diff --git a/tests/workloads/powerave.py b/tests/workloads/powerave.py
new file mode 100755 (executable)
index 0000000..4d786b9
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+
+import sys
+import getopt
+
+def usage():
+    print "powersum.py [OPTIONS] HZ VOLTAGE [FILE]"
+    print "OPTIONS: "
+    print "-o OFFSET: subtract OFFSET from all data points"
+    print "\nHZ: samples per second in FILE or stdin"
+    sys.exit(0)
+
+offset = 0.0
+voltage = 4.3
+
+parsedargv,argvrem = getopt.getopt(sys.argv[1:], "vo:w:l:h", ["help"])
+for o,a in parsedargv:
+    if o == '-o': offset = float(a)
+    if o == '-h' or o == '--help': usage()
+
+hz = float(argvrem[0])
+voltage = float(argvrem[1])
+if len(argvrem) > 1:
+    f = open(argvrem[2], "r")
+else:
+    f = sys.stdin
+
+totalpower = 0.0
+samplectr = 0
+
+for line in f:
+    try:
+        val = float(line.split(" ")[1]) # xxx take 2nd arg in line
+        val -= offset
+    except:
+        print "Can't parse data line, did you remember the timestamp?"
+        print "data was: %s" % line
+        sys.exit(1)
+
+    samplectr+=1
+    totalpower += val/hz
+
+avecurrent = totalpower * hz *1000 / samplectr
+avepower = avecurrent * voltage
+
+print "%.3f %.3f" % (avecurrent, avepower)
diff --git a/tests/workloads/pwrsummary.sh b/tests/workloads/pwrsummary.sh
new file mode 100755 (executable)
index 0000000..3d3aeb8
--- /dev/null
@@ -0,0 +1,235 @@
+# print summary of output generated by pwrtest.sh
+#
+# default results directories are <device>-<date>[-experiment]. By default
+# match any device and the year 201*.
+#
+# Examples:
+#
+# - show output for all bullhead tests in july 2015:
+#    ./pwrsummary.sh -r "bh-201507*"
+#
+# - generate CSV file for import into spreadsheet:
+#    ./pwrsummary.sh -o csv
+#
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+cd $CMDDIR
+CMDDIR=$(pwd)
+cd -
+POWERAVE="python $CMDDIR/powerave.py"
+
+defaultPattern="*-201*"
+defaultVoltage=4.3
+defaultFrequency=5
+
+function Usage {
+       echo "$0 [-o format] [-v voltage] [-h freq] [-f resultsDirectories]"
+}
+
+while [ $# -gt 0 ]
+do
+       case "$1" in
+       (-o) format=$2; shift;;
+       (-v) voltage=$2; shift;;
+       (-h) hz=$2; shift;;
+       (-r) testResults="$2"; shift;;
+       (--help) Usage; exit 0;;
+       (--) shift; break;;
+       (*)
+               echo Unknown option: $1
+               Usage
+               exit 1;;
+       esac
+       shift
+done
+
+testResults=${testResults:=$defaultPattern}
+voltage=${voltage:=$defaultVoltage}
+hz=${hz:=$defaultFrequency}
+
+function printHeader {
+       workload=$1
+       units="unknown"
+       case $workload in
+       (suntemple|shadowgrid2)
+               units="FPS";;
+       (recentfling|youtube|chrome)
+               units="FPS from app point of view: 1/(90th percentile render time)";;
+       (sysapps)
+               units="App start/switch per second";;
+       esac
+
+       echo "Performance unit for $workload is: $units"
+       if [ "$format" = csv ]; then
+               printf "%s,%s,%s,%s,%s,%s,%s,%s,%s\n" " " build min ave max net-mA@${voltage}v base-mW net-mW perf/W
+       else
+               printf "%-30s %-8s %12.12s %12.12s %12.12s %12.12s %12.12s %12.12s %12.12s\n" " " build min ave max net-mA@${voltage}v base-mW net-mW perf/W
+       fi
+}
+
+function average {
+       awk 'BEGIN { count=0; sum=0; max=-1000000000; min=1000000000; }
+       {
+               cur = $1;
+               sum = sum + cur; 
+               if (cur > max) max = cur;
+               if (cur < min) min = cur;
+               count++;
+       }
+
+       END {
+               if (count > 0) {
+                       ave = sum / count;
+                       printf "%.2f %.2f %.2f\n", min, ave, max; 
+               }
+       }'
+}
+
+function hwuiOutputParser {
+       # Stats since: 60659316905953ns
+       # Total frames rendered: 150
+       # Janky frames: 89 (59.33%)
+       # 90th percentile: 23ms
+       # 95th percentile: 27ms
+       # 99th percentile: 32ms
+       # Number Missed Vsync: 0
+       # Number High input latency: 0
+       # Number Slow UI thread: 0
+       # Number Slow bitmap uploads: 12
+       # Number Slow draw: 89
+       # use with "stdbuf -o0 " to disable pipe buffering
+       # stdbuf -o0 adb shell /data/hwuitest shadowgrid2 400 | stdbuf -o0 ./hwuitestfilter.sh  | tee t.csv
+       sed -e 's/ns//' -e 's/[\(\)%]/ /g' | awk '
+       BEGIN { startTime=0; lastTime=0; }
+       /^Stats since:/ {
+               curTime = $3;
+               if (startTime == 0) {
+                       startTime = curTime;
+               }
+               if (lastTime) {
+                       interval = curTime - lastTime;
+                       fps = totalFrames*1000000000 / interval;
+                       diffTime = curTime - startTime;
+                       printf "%.2f, %.2f, ",diffTime/1000000, fps;
+               }
+       }
+       /^Total frames/ { totalFrames=$4; }
+       /^Janky frames:/ {
+               if (lastTime) {
+                       printf "%.2f\n",$4; lastTime=curTime;
+               }
+               lastTime = curTime;
+       }'
+}
+
+function sysappOutputParser {
+       awk '
+       BEGIN { fmt=0; count=0; sum=0; }
+       /^App/ {
+               if (count != 0) {
+                       if (fmt > 2) printf "Ave: %0.2fms\n", sum/count;
+                       else printf " %0.2f\n", sum/count;
+                       count = 0;
+                       sum = 0;
+               }
+       }
+       /^[a-z]/ { val=$2; if (val != 0) { count++; sum+=val; } }
+       /^Iteration/ { if (fmt > 2) printf "%s : ", $0; else if (fmt) printf "%d ", $2; }
+       '
+}
+
+function calcPerfData {
+       testdir=$1
+       workload=$2
+       baselineCurrent=$3
+       baselinePower=$4
+
+       file=${workload}.out
+       powerfile=${workload}-power.out
+       build="$(cat build 2>/dev/null)"
+       build=${build:="Unknown"}
+
+       lines=$(wc -l $file 2>/dev/null | cut -f1 -d\ )
+
+       if [ ${lines:=0} -eq -0 ]; then
+               # No performance data captured
+               if [ "$format" = csv ]; then
+                       printf "%s,%s,%s\n" $testdir "$build" "no data"
+               else
+                       printf "%-30s %-8s %12.12s\n" $testdir "$build" "no data"
+               fi
+               return 1
+       fi
+
+       set -- $($POWERAVE $hz $voltage $powerfile)
+       current=$(echo $1 $baselineCurrent | awk '{ printf "%.2f", $1-$2; }')
+       power=$(echo $2 $baselinePower | awk '{ printf "%.2f", $1-$2; }')
+
+       case $workload in
+       (idle)
+               set -- 0 0 0
+               ;;
+       (suntemple)
+               # units are fps
+               set -- $(grep "FPS average" $file  | sed 's/^.*seconds for a //' | awk '{ print $1; }' | average)
+               ;;
+       (recentfling|youtube|chrome)
+               # units are ms, so need to convert to app/ms
+               set -- $(grep ^Frames:  $file | tr "/" " " | awk '{ print $4; }' | average | awk '{ printf "%.3f %.3f %.3f\n", 1000/$3, 1000/$2, 1000/$1;}'  )
+               ;;
+       (sysapps)
+               # units are ms, so need to convert to app/ms
+               set -- $(cat $file | sysappOutputParser | average | awk '{ printf "%.3f %.3f %.3f\n", 1000/$3, 1000/$2, 1000/$1;}'  )
+               ;;
+       (shadowgrid2)
+               # units are fps
+               set -- $(cat $file | hwuiOutputParser | tr ',' ' ' | awk '{print $2;}' | average)
+               ;;
+       esac
+
+       minperf=$1
+       aveperf=$2
+       maxperf=$3
+       perfPerWatt=$(echo $aveperf $power | awk '{ if ($2) { val=$1*1000/$2; printf "%.3f\n", val; } else print "unknown"; }')
+       if [ "$format" = csv ]; then
+               printf "%s,%s,%f,%f,%f,%f,%f,%f," $testdir "$build" $minperf $aveperf $maxperf $current $baselinePower $power
+               printf "%s\n" $perfPerWatt
+       else
+               printf "%-30s %-8s %12.2f %12.2f %12.2f %12.2f %12.2f %12.2f " $testdir "$build" $minperf $aveperf $maxperf $current $baselinePower $power
+               printf "%12s\n" $perfPerWatt
+       fi
+}
+
+function calcBaselinePower {
+       workload=$1
+       defaultPowerFile="idle-display-power.out"
+       powerFile=$defaultPowerFile
+       case $workload in
+       (shadowgrid2|suntemple|recentfling)
+               powerFile="idle-airplane-display-power.out"
+               if [ ! -f  $powerFile ]; then
+                       powerFile=$defaultPowerFile
+               fi;;
+       esac
+       if [ -f  $powerFile ]; then
+               $POWERAVE 5 4.3 $powerFile
+       fi
+}
+
+for t in $(cat tests)
+do
+       echo .======================= $t ================================
+       printHeader $t
+       for i in $testResults
+       do
+               cd $i
+               baseline="$(calcBaselinePower $t)"
+               if [ "$baseline" != "" ]; then
+                       calcPerfData $i $t $baseline
+               else
+                       echo "$i : no baseline current"
+               fi
+               cd - > /dev/null
+       done
+done
diff --git a/tests/workloads/pwrtest.sh b/tests/workloads/pwrtest.sh
new file mode 100755 (executable)
index 0000000..39f7b11
--- /dev/null
@@ -0,0 +1,365 @@
+# Script to gather perf and perf/watt data for several workloads
+#
+# Setup:
+#
+# - device connected to monsoon with USB passthrough enabled
+# - network enabled (baseline will be measured and subtracted
+#   from results) (network needed for chrome, youtube tests)
+# - the device is rebooted after each test (can be inhibited
+#   with "-r 0")
+#
+# Default behavior is to run each of the known workloads for
+# 30 minutes gathering both performance and power data.
+#
+# The default time can be overridden with the -t option. To
+# change individual test times, a config file can be specifed
+# via -f with times for individual tests. Example file contents:
+#
+#      idleTime=60
+#      recentflingTime=60
+#      chromeTime=60
+#      youtubeTime=0
+#      sysappsTime=60
+#      suntempleTime=5
+#
+# Output goes to the current directory.
+#
+# Examples:
+#
+# - Run all tests for 15 minutes (default is 30): ./pwrtest.sh -t 15 -R MDA20
+#
+# - Use a config file for test times: ./pwrtest.sh -f ./myconfig -R MDA20
+#
+# - Use a init file to setup device tuneables after each restart (this is
+#   a bash script which should include adb commands to set up device):
+#     ./pwrtest.sh -F devtunables
+#
+
+defaultTime=30
+garbageminutes=8
+
+function Usage {
+       echo "Usage: $0 [OPTIONS]"
+       echo "-d device : device type (shamu, bullhead, ...)"
+       echo "-f configFile : config file to override individual test times"
+       echo "-g garbageMinutes : time to skip power measurement at beginning of test"
+       echo "                    default=$garbagetime minutes"
+       echo "-r restart : 0=no reboot between tests, 1=reboot (default)"
+       echo "-t defaultTimeMin : default time to run each test"
+       echo "                    default=$defaultTime minutes"
+       echo "-D cmddir : directory to find defs.sh"
+       echo "-F restartHookFile : file of commands to set device tunables after restart (optional)"
+       echo "-R release : release running on device (MDA20, 2054728, etc)"
+}
+
+restart=1
+hz=5
+shadowgrid2TimeMax=25
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+
+MONSOON=monsoon.par
+
+while [ $# -gt 0 ]
+do
+       case "$1" in
+       (-D) CMDDIR=$2; shift;;
+       (-r) restart=$2; shift;;
+       (-t) defaultTime=$2; shift;;
+       (-F) restartfile=$2; shift;;
+       (-g) garbageminutes=$2; shift;;
+       (-f)
+               configFile=$2;
+               echo "Reading configs from $configFile..."
+               . ./$configFile
+               shift;;
+       (-R) echo $2 > ./build; shift;;
+       (--) ;;
+       (--help)
+               Usage
+               exit 0;;
+       (*)
+               echo "Unknown option: $1"
+               Usage
+               exit 1;;
+       esac
+       shift
+done
+
+. $CMDDIR/defs.sh --
+
+devdir="/data/local/tmp"
+suntempledir=${CMDDIR}/suntemple
+
+case $DEVICE in
+(shamu|hammerhead)
+       HWUITEST=hwuitest
+       onSwipe="700 1847 700 400 50"
+       ;;
+(*)
+       HWUITEST=hwuitest64
+       onSwipe="500 1200 500 550 150"
+       ;;
+esac
+
+scripts="defs.sh systemapps.sh recentfling.sh youtube.sh chromefling.sh $HWUITEST"
+
+if ! $MONSOON >/dev/null 2>&1; then
+       echo $MONSOON must be in your PATH >&2
+       exit 1
+fi
+
+function usbpassthru {
+       if [ "$1" = off ]; then
+               state=off
+       else
+               state=on
+       fi
+       echo Setting usb pass-thru to $state
+       monsoon.par --usbpassthrough=$state
+}
+
+function pwrcollect {
+       collectmin=$1
+       collectmin=${collectmin:=60}
+       # samples = hz * 60 * minutes
+       ((samples=5*60*collectmin))
+       monsoon.par --timestamp --samples $samples --hz 5
+}
+
+function copy_files {
+       adb shell mkdir -p $devdir
+       for file in $scripts
+       do
+               adb push $CMDDIR/$file $devdir
+       done
+}
+
+function install_suntemple {
+       echo Checking for suntemple installation...
+       #stdest=/storage/sdcard0/obb/com.BrueComputing.SunTemple
+       stdest=/storage/emulated/0/obb/com.BrueComputing.SunTemple
+       dircontents=$(adb ls $stdest 2>/dev/null)
+       if [ "$dircontents" = "" ]; then
+               echo Installing suntemple...
+               adb install $suntempledir/*.apk
+               adb shell mkdir -p $stdest
+               adb push $suntempledir/main*obb $stdest
+       else
+               echo dircontents=$dircontents
+               echo Suntemple already installed.
+       fi
+}
+
+function run_test {
+       testName=$1
+       collectMinutes=$2
+       collectOutput=${testName}-power-raw.out
+       powerOutput=${testName}-power.out
+       echo -----------------------------------------------------
+       echo TEST: $testName
+       echo enabled Cores $(adb shell "cat /sys/devices/system/cpu/online")
+       date
+       echo -----------------------------------------------------
+       usbpassthru off
+       pwrcollect $collectMinutes > $collectOutput 2>/dev/null
+       # take off the first 2min of samples
+       totalSamples=$(cat $collectOutput | wc -l)
+       # we throw away the first "garbageminutes" of the data
+       # since it is volatile
+       ((garbage=hz*60*garbageminutes))
+       ((remaining=totalSamples-garbage))
+       if [ $remaining -gt 0 ]; then
+               tail -$remaining $collectOutput > $powerOutput
+       else
+               cp $collectOutput $powerOutput
+       fi
+       echo power data for $testName copied to $collectOutput
+       usbpassthru on
+       sleep 10
+       adb devices
+       sleep 10
+}
+
+function start_job {
+       cmdline="$1"
+       echo Running $cmdline
+       (adb shell "cd $devdir && nohup $cmdline > test.out") &
+       sleep 5
+       kill %1 2>/dev/null
+}
+
+function cleanup_job {
+       testName=$1
+       processName=$2
+       processName=${processName:=" sh "}
+       set -- $(adb shell ps | tr "\r" " " | grep "$processName")
+       echo killing PID=$2...
+       adb shell kill $2
+       sleep 1
+       echo copying test output to $testName...
+       adb pull $devdir/test.out
+       mv test.out ${testName}.out
+       if [ $restart -gt 0 ]; then
+               restart_device
+       else
+               doKeyevent HOME
+       fi
+}
+
+function airplane_mode {
+       if [ "$1" = "on" ]; then
+               mode=true
+               setting=1
+       else
+               mode=false
+               setting=0
+       fi
+       adb shell settings put global airplane_mode_on $setting
+       adb shell am broadcast -a android.intent.action.AIRPLANE_MODE --ez state $mode
+       echo Set airplane mode to $mode
+}
+
+function restart_device {
+       adb reboot
+       echo Wait 60s for device to restart...
+       sleep 60
+       while ! adb root
+       do
+               echo Waiting for device to come up...
+               sleep 10
+       done
+       echo Wait 30s to complete boot activities...
+       sleep 30
+       echo Restart complete.
+       doTap 897 1075
+       sleep 2
+       doSwipe $onSwipe
+       restartfile=${restartfile:="./restarthook"}
+       if [ -f $restartfile ]; then
+               # hook to change tunables after a restart
+               . $restartfile
+       fi
+}
+
+usbpassthru on
+adb devices 2>/dev/null
+
+airplane_mode off
+if [ $restart -gt 0 ]; then
+       restart_device
+fi
+
+echo Copying $scripts to device $devdir...
+copy_files
+tests=""
+
+# measure background power
+idleTime=${idleTime:=$defaultTime}
+if [ $idleTime -gt 0 ]; then
+       echo Test 1 : measure idle power for $idleTime minutes
+       run_test idle $idleTime
+       airplane_mode on
+       echo Restarting for power baseline in airplane mode...
+       restart_device
+       run_test idle-airplane $idleTime
+       airplane_mode off
+       # the screen blanks after 30 minutes. The first 2 minutes of the test
+       # have already been filtered off. For our power baseline, keep the first
+       # 20 minutes of the results
+       ((twentyminutes=hz*20*60))
+       powerOutput="idle-power.out"
+       displayPowerOutput="idle-display-power.out"
+       airplanePowerOutput="idle-airplane-power.out"
+       airplaneDisplayPowerOutput="idle-airplane-display-power.out"
+       totalSamples=$(cat $powerOutput | wc -l)
+       if [ $twentyminutes -lt $totalSamples ]; then
+               head -$twentyminutes $powerOutput > $displayPowerOutput
+               head -$twentyminutes $airplanePowerOutput > $airplaneDisplayPowerOutput
+       else
+               cp $powerOutput $displayPowerOutput
+               cp $airplanePowerOutput $airplaneDisplayPowerOutput
+       fi
+       tests="$tests idle"
+fi
+
+recentflingTime=${recentflingTime:=$defaultTime}
+if [ $recentflingTime -gt 0 ]; then
+       echo $(date) Test 2 : recents fling for $recentflingTime minutes
+       airplane_mode on
+       adb shell "cd $devdir && ./systemapps.sh -A -T -i 1"
+       start_job "./recentfling.sh -N -i 1000 -d $DEVICE"
+       run_test recentfling $recentflingTime
+       cleanup_job recentfling
+       airplane_mode off
+       date
+       tests="$tests recentfling"
+fi
+
+suntempleTime=${suntempleTime:=$defaultTime}
+if [ $suntempleTime -gt 0 ]; then
+       echo $(date) Test 2 : run Sun Temple $suntempleTime minutes
+       airplane_mode on
+       install_suntemple
+       adb shell "am start $suntempleActivity"
+       run_test suntemple $suntempleTime
+       adb pull /sdcard/SunTemple/SunTemple/Saved/Logs/SunTemple.log
+       cleanup_job suntemple BrueComp
+       airplane_mode off
+       mv SunTemple.log suntemple.out
+       # grab the suntemple log
+       date
+       tests="$tests suntemple"
+fi
+
+chromeTime=${chromeTime:=$defaultTime}
+if [ $chromeTime -gt 0 ]; then
+       echo $(date) Test 3 : chrome fling for $chromeTime minutes
+       start_job "./chromefling.sh -i 1000 -d $DEVICE"
+       run_test chrome $chromeTime
+       cleanup_job chrome
+       date
+       tests="$tests chrome"
+fi
+
+shadowgrid2Time=${shadowgrid2Time:=$defaultTime}
+if [ $shadowgrid2Time -gt $shadowgrid2TimeMax ]; then
+       # we cap shadowgrid2 time since the display goes
+       # off after 30 minutes
+       $shadowgrid2Time=$shadowgrid2TimeMax
+fi
+if [ $shadowgrid2Time -gt 0 ]; then
+       airplane_mode on
+       echo $(date) Test 4 : shadowgrid2 for $shadowgrid2Time minutes
+       start_job "./$HWUITEST shadowgrid2 100000"
+       run_test shadowgrid2 $shadowgrid2Time
+       cleanup_job shadowgrid2 $HWUITEST
+       airplane_mode off
+       date
+       tests="$tests shadowgrid2"
+fi
+
+youtubeTime=${youtubeTime:=$defaultTime}
+if [ $youtubeTime -gt 0 ]; then
+       echo $(date) Test 5 : youtube for $youtubeTime minutes
+       start_job "./youtube.sh -i 1000 -d $DEVICE"
+       run_test youtube $youtubeTime
+       cleanup_job youtube
+       date
+       tests="$tests youtube"
+fi
+
+sysappsTime=${sysappsTime:=$defaultTime}
+if [ $sysappsTime -gt 0 ]; then
+       echo $(date) Test 6 : app switching for $sysappsTime minutes
+       start_job "./systemapps.sh -T -i 1000 -d $DEVICE"
+       run_test sysapps $sysappsTime
+       cleanup_job sysapps
+       date
+       tests="$tests sysapps"
+fi
+
+echo Ran tests: $tests
+echo $tests > tests
+
index 092c8d9..79713f3 100755 (executable)
@@ -18,10 +18,12 @@ function processLocalOption {
        (-A) unset appList;;
        (-L) appList=$2; shift; ret=1;;
        (-T) capturesystrace=1;;
+       (-B) echo $$ > /dev/cpuset/background/tasks;;
        (*)
                echo "$0: unrecognized option: $1"
                echo; echo "Usage: $0 [options]"
                echo "-A : use all known applications"
+               echo "-B : run in background cpuset"
                echo "-L applist : list of applications"
                echo "   default: $appList"
                echo "-N : no app startups, just fling"
@@ -44,6 +46,12 @@ case $DEVICE in
        upCount=6
        UP="70 400 70 100 $flingtime"
        DOWN="70 100 70 400 $flingtime";;
+(angler|ariel|mtp8996)
+       flingtime=150
+       downCount=4
+       upCount=3
+       UP="500 1200 500 550 $flingtime"
+       DOWN="500 550 500 1200 $flingtime";;
 (bullhead)
        flingtime=200
        downCount=5
@@ -122,7 +130,6 @@ do
        latency99=$5
        if [ ${totalDiff:=0} -eq 0 ]; then
                echo Error: could not read frame info with \"dumpsys gfxinfo\"
-               exit 1
        fi
 
        ((frameSum=frameSum+totalDiff))
@@ -132,7 +139,6 @@ do
        ((latency99Sum=latency99Sum+latency99))
        if [ "$totalDiff" -eq 0 ]; then
                echo Error: no frames detected. Is the display off?
-               exit 1
        fi
        ((jankPct=jankyDiff*100/totalDiff))
        resetJankyFrames
index a263e7d..6c0db35 100755 (executable)
 # Other options are described below.
 #
 iterations=1
-tracecategories="gfx view am input memreclaim"
+tracecategories="gfx am memreclaim"
 totaltimetest=0
 forcecoldstart=0
 waitTime=3.0
+memstats=0
 
-appList="gmail hangouts chrome youtube play home"
+appList="gmail maps chrome youtube play home"
 
 function processLocalOption {
        ret=0
@@ -38,6 +39,7 @@ function processLocalOption {
        (-L) appList=$2; shift; ret=1;;
        (-T) totaltimetest=1;;
        (-W) waitTime=$2; shift; ret=1;;
+       (-M) memstats=1;;
        (*)
                echo "$0: unrecognized option: $1"
                echo; echo "Usage: $0 [options]"
@@ -141,6 +143,7 @@ do
        if [ $iterations -gt 1 ]; then
                echo =========================================
                echo Iteration $cur of $iterations
+               date
                echo =========================================
        fi
        if [ $iterations -gt 1 -o $cur -eq 1 ]; then
@@ -160,8 +163,11 @@ do
                if [ $totaltimetest -eq 0 ]; then
                        tmpTraceOut="$tmpTraceOutBase-$app.out"
                        >$tmpTraceOut
-                       startInstramentation
+                       startInstramentation "$app-$cur"
                else
+                       if [ "$memstats" -gt 0 ]; then
+                               startInstramentation "$app-$cur" 0
+                       fi
                        if [ $appnum -eq 0 ]; then
                                printf "%-8s %5s(ms) %3s(ms) %s      %s\n" App Start Iter Jank Latency
                        fi
@@ -239,6 +245,8 @@ if [ $totaltimetest -gt 0 ]; then
        printf "%-10s %5.0f   %5.0f\n" TOTAL $totaltime $diffTime
 fi
 
+overallSum=0
+appCount=0
 if [ $iterations -gt 1 -a $totaltimetest -eq 0 ]; then
        echo
        echo =========================================
@@ -258,7 +266,14 @@ if [ $iterations -gt 1 -a $totaltimetest -eq 0 ]; then
                ((ave90=l90/iterations))
                ((ave95=l95/iterations))
                ((ave99=l99/iterations))
-               ((jankPct=100*janks/frames))
+               if [ $frames -gt 0 ]; then
+                       ((jankPct=100*janks/frames))
+               fi
                printf "%-12s %5d      %5d      %5d      %5d      %5d     %5d(%d%%) %d/%d/%d\n" $app $1 $ave $2 $4 $5 $janks $jankPct $ave90 $ave95 $ave99
+               ((overallSum=overallSum+ave))
+               ((appCount=appCount+1))
        done
+       if [ $appCount -gt 0 ]; then
+               printf "Average Start Time: %.2f\n", $(echo $overallSum $appCount | awk '{ printf "%.2f\n", $1/$2 }')
+       fi
 fi
diff --git a/tests/workloads/youtube.sh b/tests/workloads/youtube.sh
new file mode 100755 (executable)
index 0000000..558e53c
--- /dev/null
@@ -0,0 +1,149 @@
+#
+# Script to play a john oliver youtube video N times.
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+iterations=10
+app=youtube
+searchText="last week tonight with john oliver: online harassment"
+vidMinutes=15
+
+function processLocalOption {
+       ret=0
+       case "$1" in
+       (-S) searchText="$2"; shift;;
+       (-t) vidMinutes="$2"; shift;;
+       (*)
+               echo "$0: unrecognized option: $1"
+               echo; echo "Usage: $0 [options]"
+               echo "-i iterations"
+               echo "-S youtube search text"
+               echo "-d device"
+               echo "-t vidMinutes"
+               exit 1;;
+       esac
+       return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(angler)
+       searchButton="860 177"
+       selectFirstVideo="225 400"
+       enableControls="1000 610"
+       fullScreen="1011 632"
+       ;;
+(shamu)
+       searchButton="1200 160"
+       selectFirstVideo="480 653"
+       enableControls="1377 812"
+       fullScreen="1377 812"
+       ;;
+(bullhead|hammerhead)
+       searchButton="860 177"
+       selectFirstVideo="225 400"
+       enableControls="1000 610"
+       fullScreen="1011 632"
+       ;;
+(volantis)
+       searchButton="1356 93"
+       selectFirstVideo="378 264"
+       enableControls="1464 812"
+       fullScreen="1480 835"
+       ;;
+(ariel)
+       searchButton="1440 70"
+       selectFirstVideo="228 224"
+       enableControls="1528 880"
+       fullScreen="1528 880"
+       ;;
+
+(*)
+       echo "Error: No display information available for $DEVICE"
+       exit 1;;
+esac
+
+function swipe {
+       count=0
+       while [ $count -lt $2 ]
+       do
+               echo doSwipe...
+               doSwipe $1
+               ((count=count+1))
+       done
+       sleep 1
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames $(getPackageName $app)
+
+while [ $cur -le $iterations ]
+do
+       t=$(startActivity $app)
+       sleep 4.0
+       doTap $searchButton
+       sleep 1.0
+       doText "$searchText"
+       sleep 1.0
+       doKeyevent ENTER
+       sleep 5.0
+       doTap $selectFirstVideo
+       sleep 10.0
+       doTap $fullScreen
+       sleep 0.5
+       doTap $fullScreen
+       # 15 minutes
+       ((vidTime=60*vidMinutes))
+       sleep $vidTime
+       doKeyevent BACK
+       sleep 0.5
+       doKeyevent BACK
+       sleep 0.5
+       doKeyevent BACK
+       sleep 0.5
+
+       set -- $(getJankyFrames $(getPackageName $app))
+       totalDiff=$1
+       jankyDiff=$2
+       latency90=$3
+       latency95=$4
+       latency99=$5
+       if [ ${totalDiff:=0} -eq 0 ]; then
+               echo Error: could not read frame info with \"dumpsys gfxinfo\"
+       fi
+
+       ((frameSum=frameSum+totalDiff))
+       ((jankSum=jankSum+jankyDiff))
+       ((latency90Sum=latency90Sum+latency90))
+       ((latency95Sum=latency95Sum+latency95))
+       ((latency99Sum=latency99Sum+latency99))
+       if [ "$totalDiff" -eq 0 ]; then
+               echo Error: no frames detected. Is the display off?
+       fi
+       ((jankPct=jankyDiff*100/totalDiff))
+       resetJankyFrames $(getPackageName $app)
+
+
+       echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+       ((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)
diff --git a/timeinfo/timeinfo.cpp b/timeinfo/timeinfo.cpp
deleted file mode 100644 (file)
index b1af7ce..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-/* Report realtime, uptime, awake percentage, and sleep percentage to stdout.
- * Primarily called by powerdroid test harness.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "utils/SystemClock.h"
-
-
-int main(int argc, char *argv[])
-{
-
-    int64_t realtime, uptime;
-    int64_t awaketime, sleeptime;
-
-    uptime = android::uptimeMillis();
-    realtime = android::elapsedRealtime();
-
-    if (realtime == 0) {
-        realtime = 1;
-    }
-
-    awaketime = ((1000 * uptime / realtime) + 5) / 10;
-    sleeptime = ((1000 * (realtime - uptime) / realtime) + 5) / 10;
-
-    printf("%jd %jd %jd %jd\n", (intmax_t) realtime, (intmax_t) uptime, 
-            (intmax_t) awaketime, (intmax_t) sleeptime);
-
-}
-
-
-/* vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab */
index 75face6..7b3d13f 100644 (file)
@@ -1,6 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 
 ifeq ($(HOST_OS),linux)
+
 include $(CLEAR_VARS)
 LOCAL_MODULE := verify_boot_signature
 LOCAL_SRC_FILES := verify_boot_signature.c
@@ -9,7 +10,8 @@ LOCAL_MODULE_TAGS := optional
 LOCAL_SHARED_LIBRARIES := libcrypto-host
 LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
 include $(BUILD_HOST_EXECUTABLE)
-endif
+
+endif # HOST_OS == linux
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := generate_verity_key
@@ -100,6 +102,8 @@ LOCAL_MODULE := build_verity_tree
 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)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
index 03eb32a..3cf9499 100644 (file)
@@ -149,6 +149,7 @@ public class BootSignature extends ASN1Object
             throws Exception, IOException, CertificateEncodingException {
         ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
         certificate = s.readObject();
+        publicKey = cert.getPublicKey();
     }
 
     public byte[] generateSignableImage(byte[] image) throws IOException {
@@ -253,7 +254,7 @@ public class BootSignature extends ASN1Object
         Utils.write(image_with_metadata, outPath);
     }
 
-    public static void verifySignature(String imagePath) throws Exception {
+    public static void verifySignature(String imagePath, String certPath) throws Exception {
         byte[] image = Utils.read(imagePath);
         int signableSize = getSignableImageSize(image);
 
@@ -264,6 +265,11 @@ public class BootSignature extends ASN1Object
         byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
         BootSignature bootsig = new BootSignature(signature);
 
+        if (!certPath.isEmpty()) {
+            System.err.println("NOTE: verifying using public key from " + certPath);
+            bootsig.setCertificate(Utils.loadPEMCertificate(certPath));
+        }
+
         try {
             if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
                 System.err.println("Signature is VALID");
@@ -291,8 +297,15 @@ public class BootSignature extends ASN1Object
         Security.addProvider(new BouncyCastleProvider());
 
         if ("-verify".equals(args[0])) {
+            String certPath = "";
+
+            if (args.length >= 4 && "-certificate".equals(args[2])) {
+                /* args[3] is the path to a public key certificate */
+                certPath = args[3];
+            }
+
             /* args[1] is the path to a signed boot image */
-            verifySignature(args[1]);
+            verifySignature(args[1], certPath);
         } else {
             /* args[0] is the target name, typically /boot
                args[1] is the path to a boot image to sign
diff --git a/verity/NOTICE b/verity/NOTICE
new file mode 100644 (file)
index 0000000..9109eed
--- /dev/null
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2013-2015, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
index 937c206..6d80276 100644 (file)
@@ -258,10 +258,10 @@ public class Utils {
 
     static boolean verify(PublicKey key, byte[] input, byte[] signature,
             AlgorithmIdentifier algId) throws Exception {
-        String algName = ID_TO_ALG.get(algId.getObjectId().getId());
+        String algName = ID_TO_ALG.get(algId.getAlgorithm().getId());
 
         if (algName == null) {
-            throw new IllegalArgumentException("Unsupported algorithm " + algId.getObjectId());
+            throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
         }
 
         Signature verifier = Signature.getInstance(algName);
index 547e606..51e629a 100755 (executable)
@@ -44,7 +44,7 @@ def build_verity_table(block_device, data_blocks, root_hash, salt):
                 BLOCK_SIZE,
                 BLOCK_SIZE,
                 data_blocks,
-                data_blocks + (METADATA_SIZE / BLOCK_SIZE),
+                data_blocks,
                 root_hash,
                 salt)
     return table
index e7bfa40..c50e449 100644 (file)
@@ -16,6 +16,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
+
 struct sparse_hash_ctx {
     unsigned char *hashes;
     const unsigned char *salt;
@@ -353,7 +355,9 @@ int main(int argc, char **argv)
     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/fec/Android.mk b/verity/fec/Android.mk
new file mode 100644 (file)
index 0000000..c13f577
--- /dev/null
@@ -0,0 +1,41 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+ifeq ($(HOST_OS),linux)
+LOCAL_SANITIZE := integer
+endif
+LOCAL_MODULE := fec
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+    libsparse_host \
+    libz \
+    libcrypto_static \
+    libfec_host \
+    libfec_rs_host \
+    libext4_utils_host \
+    libsquashfs_utils_host
+LOCAL_SHARED_LIBRARIES := libbase
+LOCAL_CFLAGS += -Wall -Werror -O3
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_MODULE := fec
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto_static \
+    libfec \
+    libfec_rs \
+    libbase \
+    libext4_utils_static \
+    libsquashfs_utils \
+    libcutils
+LOCAL_CFLAGS += -Wall -Werror -O3 -DIMAGE_NO_SPARSE=1
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_EXECUTABLE)
diff --git a/verity/fec/image.cpp b/verity/fec/image.cpp
new file mode 100644 (file)
index 0000000..a378c93
--- /dev/null
@@ -0,0 +1,629 @@
+/*
+ * 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.
+ */
+
+#undef NDEBUG
+#define _LARGEFILE64_SOURCE
+
+extern "C" {
+    #include <fec.h>
+}
+
+#include <assert.h>
+#include <android-base/file.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <openssl/sha.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#ifndef IMAGE_NO_SPARSE
+#include <sparse/sparse.h>
+#endif
+#include "image.h"
+
+#if defined(__linux__)
+    #include <linux/fs.h>
+#elif defined(__APPLE__)
+    #include <sys/disk.h>
+    #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
+    #define O_LARGEFILE 0
+#endif
+
+void image_init(image *ctx)
+{
+    memset(ctx, 0, sizeof(*ctx));
+}
+
+static void mmap_image_free(image *ctx)
+{
+    if (ctx->input) {
+        munmap(ctx->input, (size_t)ctx->inp_size);
+        close(ctx->inp_fd);
+    }
+
+    if (ctx->fec_mmap_addr) {
+        munmap(ctx->fec_mmap_addr, FEC_BLOCKSIZE + ctx->fec_size);
+        close(ctx->fec_fd);
+    }
+
+    if (!ctx->inplace && ctx->output) {
+        delete[] ctx->output;
+    }
+}
+
+static void file_image_free(image *ctx)
+{
+    assert(ctx->input == ctx->output);
+
+    if (ctx->input) {
+        delete[] ctx->input;
+    }
+
+    if (ctx->fec) {
+        delete[] ctx->fec;
+    }
+}
+
+void image_free(image *ctx)
+{
+    if (ctx->mmap) {
+        mmap_image_free(ctx);
+    } else {
+        file_image_free(ctx);
+    }
+
+    image_init(ctx);
+}
+
+static uint64_t get_size(int fd)
+{
+    struct stat st;
+
+    if (fstat(fd, &st) == -1) {
+        FATAL("failed to fstat: %s\n", strerror(errno));
+    }
+
+    uint64_t size = 0;
+
+    if (S_ISBLK(st.st_mode)) {
+        if (ioctl(fd, BLKGETSIZE64, &size) == -1) {
+            FATAL("failed to ioctl(BLKGETSIZE64): %s\n", strerror(errno));
+        }
+    } else if (S_ISREG(st.st_mode)) {
+        size = st.st_size;
+    } else {
+        FATAL("unknown file mode: %d\n", (int)st.st_mode);
+    }
+
+    return size;
+}
+
+static void calculate_rounds(uint64_t size, image *ctx)
+{
+    if (!size) {
+        FATAL("empty file?\n");
+    } else if (size % FEC_BLOCKSIZE) {
+        FATAL("file size %" PRIu64 " is not a multiple of %u bytes\n",
+            size, FEC_BLOCKSIZE);
+    }
+
+    ctx->inp_size = size;
+    ctx->blocks = fec_div_round_up(ctx->inp_size, FEC_BLOCKSIZE);
+    ctx->rounds = fec_div_round_up(ctx->blocks, ctx->rs_n);
+}
+
+static void mmap_image_load(const std::vector<int>& fds, image *ctx,
+        bool output_needed)
+{
+    if (fds.size() != 1) {
+        FATAL("multiple input files not supported with mmap\n");
+    }
+
+    int fd = fds.front();
+
+    calculate_rounds(get_size(fd), ctx);
+
+    /* check that we can memory map the file; on 32-bit platforms we are
+       limited to encoding at most 4 GiB files */
+    if (ctx->inp_size > SIZE_MAX) {
+        FATAL("cannot mmap %" PRIu64 " bytes\n", ctx->inp_size);
+    }
+
+    if (ctx->verbose) {
+        INFO("memory mapping '%s' (size %" PRIu64 ")\n", ctx->fec_filename,
+            ctx->inp_size);
+    }
+
+    int flags = PROT_READ;
+
+    if (ctx->inplace) {
+        flags |= PROT_WRITE;
+    }
+
+    void *p = mmap(NULL, (size_t)ctx->inp_size, flags, MAP_SHARED, fd, 0);
+
+    if (p == MAP_FAILED) {
+        FATAL("failed to mmap '%s' (size %" PRIu64 "): %s\n",
+            ctx->fec_filename, ctx->inp_size, strerror(errno));
+    }
+
+    ctx->inp_fd = fd;
+    ctx->input = (uint8_t *)p;
+
+    if (ctx->inplace) {
+        ctx->output = ctx->input;
+    } else if (output_needed) {
+        if (ctx->verbose) {
+            INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+        }
+
+        ctx->output = new uint8_t[ctx->inp_size];
+
+        if (!ctx->output) {
+                FATAL("failed to allocate memory\n");
+        }
+
+        memcpy(ctx->output, ctx->input, ctx->inp_size);
+    }
+
+    /* fd is closed in mmap_image_free */
+}
+
+#ifndef IMAGE_NO_SPARSE
+static int process_chunk(void *priv, const void *data, int len)
+{
+    image *ctx = (image *)priv;
+    assert(len % FEC_BLOCKSIZE == 0);
+
+    if (data) {
+        memcpy(&ctx->input[ctx->pos], data, len);
+    }
+
+    ctx->pos += len;
+    return 0;
+}
+#endif
+
+static void file_image_load(const std::vector<int>& fds, image *ctx)
+{
+    uint64_t size = 0;
+#ifndef IMAGE_NO_SPARSE
+    std::vector<struct sparse_file *> files;
+#endif
+
+    for (auto fd : fds) {
+        uint64_t len = 0;
+
+#ifdef IMAGE_NO_SPARSE
+        if (ctx->sparse) {
+            FATAL("sparse files not supported\n");
+        }
+
+        len = get_size(fd);
+#else
+        struct sparse_file *file;
+
+        if (ctx->sparse) {
+            file = sparse_file_import(fd, false, false);
+        } else {
+            file = sparse_file_import_auto(fd, false, ctx->verbose);
+        }
+
+        if (!file) {
+            FATAL("failed to read file %s\n", ctx->fec_filename);
+        }
+
+        len = sparse_file_len(file, false, false);
+        files.push_back(file);
+#endif /* IMAGE_NO_SPARSE */
+
+        size += len;
+    }
+
+    calculate_rounds(size, ctx);
+
+    if (ctx->verbose) {
+        INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+    }
+
+    ctx->input = new uint8_t[ctx->inp_size];
+
+    if (!ctx->input) {
+        FATAL("failed to allocate memory\n");
+    }
+
+    memset(ctx->input, 0, ctx->inp_size);
+    ctx->output = ctx->input;
+    ctx->pos = 0;
+
+#ifdef IMAGE_NO_SPARSE
+    for (auto fd : fds) {
+        uint64_t len = get_size(fd);
+
+        if (!android::base::ReadFully(fd, &ctx->input[ctx->pos], len)) {
+            FATAL("failed to read: %s\n", strerror(errno));
+        }
+
+        ctx->pos += len;
+        close(fd);
+    }
+#else
+    for (auto file : files) {
+        sparse_file_callback(file, false, false, process_chunk, ctx);
+        sparse_file_destroy(file);
+    }
+
+    for (auto fd : fds) {
+        close(fd);
+    }
+#endif
+}
+
+bool image_load(const std::vector<std::string>& filenames, image *ctx,
+        bool output_needed)
+{
+    assert(ctx->roots > 0 && ctx->roots < FEC_RSM);
+    ctx->rs_n = FEC_RSM - ctx->roots;
+
+    int flags = O_RDONLY;
+
+    if (ctx->inplace) {
+        flags = O_RDWR;
+    }
+
+    std::vector<int> fds;
+
+    for (auto fn : filenames) {
+        int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), flags | O_LARGEFILE));
+
+        if (fd < 0) {
+            FATAL("failed to open file '%s': %s\n", fn.c_str(), strerror(errno));
+        }
+
+        fds.push_back(fd);
+    }
+
+    if (ctx->mmap) {
+        mmap_image_load(fds, ctx, output_needed);
+    } else {
+        file_image_load(fds, ctx);
+    }
+
+    return true;
+}
+
+bool image_save(const std::string& filename, image *ctx)
+{
+    if (ctx->inplace && ctx->mmap) {
+        return true; /* nothing to do */
+    }
+
+    /* TODO: support saving as a sparse file */
+    int fd = TEMP_FAILURE_RETRY(open(filename.c_str(),
+                O_WRONLY | O_CREAT | O_TRUNC, 0666));
+
+    if (fd < 0) {
+        FATAL("failed to open file '%s: %s'\n", filename.c_str(),
+            strerror(errno));
+    }
+
+    if (!android::base::WriteFully(fd, ctx->output, ctx->inp_size)) {
+        FATAL("failed to write to output: %s\n", strerror(errno));
+    }
+
+    close(fd);
+    return true;
+}
+
+static void mmap_image_ecc_new(image *ctx)
+{
+    if (ctx->verbose) {
+        INFO("mmaping '%s' (size %u)\n", ctx->fec_filename, ctx->fec_size);
+    }
+
+    int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+                O_RDWR | O_CREAT, 0666));
+
+    if (fd < 0) {
+        FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+            strerror(errno));
+    }
+
+    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+    size_t fec_size = FEC_BLOCKSIZE + ctx->fec_size;
+
+    if (ftruncate(fd, fec_size) == -1) {
+        FATAL("failed to ftruncate file '%s': %s\n", ctx->fec_filename,
+            strerror(errno));
+    }
+
+    if (ctx->verbose) {
+        INFO("memory mapping '%s' (size %zu)\n", ctx->fec_filename, fec_size);
+    }
+
+    void *p = mmap(NULL, fec_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+    if (p == MAP_FAILED) {
+        FATAL("failed to mmap '%s' (size %zu): %s\n", ctx->fec_filename,
+            fec_size, strerror(errno));
+    }
+
+    ctx->fec_fd = fd;
+    ctx->fec_mmap_addr = (uint8_t *)p;
+    ctx->fec = ctx->fec_mmap_addr;
+}
+
+static void file_image_ecc_new(image *ctx)
+{
+    if (ctx->verbose) {
+        INFO("allocating %u bytes of memory\n", ctx->fec_size);
+    }
+
+    ctx->fec = new uint8_t[ctx->fec_size];
+
+    if (!ctx->fec) {
+        FATAL("failed to allocate %u bytes\n", ctx->fec_size);
+    }
+}
+
+bool image_ecc_new(const std::string& filename, image *ctx)
+{
+    assert(ctx->rounds > 0); /* image_load should be called first */
+
+    ctx->fec_filename = filename.c_str();
+    ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
+
+    if (ctx->mmap) {
+        mmap_image_ecc_new(ctx);
+    } else {
+        file_image_ecc_new(ctx);
+    }
+
+    return true;
+}
+
+bool image_ecc_load(const std::string& filename, image *ctx)
+{
+    int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY));
+
+    if (fd < 0) {
+        FATAL("failed to open file '%s': %s\n", filename.c_str(),
+            strerror(errno));
+    }
+
+    if (lseek64(fd, -FEC_BLOCKSIZE, SEEK_END) < 0) {
+        FATAL("failed to seek to header in '%s': %s\n", filename.c_str(),
+            strerror(errno));
+    }
+
+    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+    uint8_t header[FEC_BLOCKSIZE];
+    fec_header *p = (fec_header *)header;
+
+    if (!android::base::ReadFully(fd, header, sizeof(header))) {
+        FATAL("failed to read %zd bytes from '%s': %s\n", sizeof(header),
+            filename.c_str(), strerror(errno));
+    }
+
+    if (p->magic != FEC_MAGIC) {
+        FATAL("invalid magic in '%s': %08x\n", filename.c_str(), p->magic);
+    }
+
+    if (p->version != FEC_VERSION) {
+        FATAL("unsupported version in '%s': %u\n", filename.c_str(),
+            p->version);
+    }
+
+    if (p->size != sizeof(fec_header)) {
+        FATAL("unexpected header size in '%s': %u\n", filename.c_str(),
+            p->size);
+    }
+
+    if (p->roots == 0 || p->roots >= FEC_RSM) {
+        FATAL("invalid roots in '%s': %u\n", filename.c_str(), p->roots);
+    }
+
+    if (p->fec_size % p->roots || p->fec_size % FEC_BLOCKSIZE) {
+        FATAL("invalid length in '%s': %u\n", filename.c_str(), p->fec_size);
+    }
+
+    ctx->roots = (int)p->roots;
+    ctx->rs_n = FEC_RSM - ctx->roots;
+
+    calculate_rounds(p->inp_size, ctx);
+
+    if (!image_ecc_new(filename, ctx)) {
+        FATAL("failed to allocate ecc\n");
+    }
+
+    if (p->fec_size != ctx->fec_size) {
+        FATAL("inconsistent header in '%s'\n", filename.c_str());
+    }
+
+    if (lseek64(fd, 0, SEEK_SET) < 0) {
+        FATAL("failed to rewind '%s': %s", filename.c_str(), strerror(errno));
+    }
+
+    if (!ctx->mmap && !android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
+        FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size,
+            filename.c_str(), strerror(errno));
+    }
+
+    close(fd);
+
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+    SHA256(ctx->fec, ctx->fec_size, hash);
+
+    if (memcmp(hash, p->hash, SHA256_DIGEST_LENGTH) != 0) {
+        FATAL("invalid ecc data\n");
+    }
+
+    return true;
+}
+
+bool image_ecc_save(image *ctx)
+{
+    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+    uint8_t header[FEC_BLOCKSIZE];
+    uint8_t *p = header;
+
+    if (ctx->mmap) {
+        p = (uint8_t *)&ctx->fec_mmap_addr[ctx->fec_size];
+    }
+
+    memset(p, 0, FEC_BLOCKSIZE);
+
+    fec_header *f = (fec_header *)p;
+
+    f->magic = FEC_MAGIC;
+    f->version = FEC_VERSION;
+    f->size = sizeof(fec_header);
+    f->roots = ctx->roots;
+    f->fec_size = ctx->fec_size;
+    f->inp_size = ctx->inp_size;
+
+    SHA256(ctx->fec, ctx->fec_size, f->hash);
+
+    /* store a copy of the fec_header at the end of the header block */
+    memcpy(&p[sizeof(header) - sizeof(fec_header)], p, sizeof(fec_header));
+
+    if (!ctx->mmap) {
+        assert(ctx->fec_filename);
+
+        int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+                    O_WRONLY | O_CREAT | O_TRUNC, 0666));
+
+        if (fd < 0) {
+            FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+                strerror(errno));
+        }
+
+        if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size) ||
+            !android::base::WriteFully(fd, header, sizeof(header))) {
+            FATAL("failed to write to output: %s\n", strerror(errno));
+        }
+
+        close(fd);
+    }
+
+    return true;
+}
+
+static void * process(void *cookie)
+{
+    image_proc_ctx *ctx = (image_proc_ctx *)cookie;
+    ctx->func(ctx);
+    return NULL;
+}
+
+bool image_process(image_proc_func func, image *ctx)
+{
+    int threads = ctx->threads;
+
+    if (threads < IMAGE_MIN_THREADS) {
+        threads = sysconf(_SC_NPROCESSORS_ONLN);
+
+        if (threads < IMAGE_MIN_THREADS) {
+            threads = IMAGE_MIN_THREADS;
+        }
+    }
+
+    assert(ctx->rounds > 0);
+
+    if ((uint64_t)threads > ctx->rounds) {
+        threads = (int)ctx->rounds;
+    }
+    if (threads > IMAGE_MAX_THREADS) {
+        threads = IMAGE_MAX_THREADS;
+    }
+
+    if (ctx->verbose) {
+        INFO("starting %d threads to compute RS(255, %d)\n", threads,
+            ctx->rs_n);
+    }
+
+    pthread_t pthreads[threads];
+    image_proc_ctx args[threads];
+
+    uint64_t current = 0;
+    uint64_t end = ctx->rounds * ctx->rs_n * FEC_BLOCKSIZE;
+    uint64_t rs_blocks_per_thread =
+        fec_div_round_up(ctx->rounds * FEC_BLOCKSIZE, threads);
+
+    if (ctx->verbose) {
+        INFO("computing %" PRIu64 " codes per thread\n", rs_blocks_per_thread);
+    }
+
+    for (int i = 0; i < threads; ++i) {
+        args[i].func = func;
+        args[i].id = i;
+        args[i].ctx = ctx;
+        args[i].rv = 0;
+        args[i].fec_pos = current * ctx->roots;
+        args[i].start = current * ctx->rs_n;
+        args[i].end = (current + rs_blocks_per_thread) * ctx->rs_n;
+
+        args[i].rs = init_rs_char(FEC_PARAMS(ctx->roots));
+
+        if (!args[i].rs) {
+            FATAL("failed to initialize encoder for thread %d\n", i);
+        }
+
+        if (args[i].end > end) {
+            args[i].end = end;
+        } else if (i == threads && args[i].end + rs_blocks_per_thread *
+                                        ctx->rs_n > end) {
+            args[i].end = end;
+        }
+
+        if (ctx->verbose) {
+            INFO("thread %d: [%" PRIu64 ", %" PRIu64 ")\n",
+                i, args[i].start, args[i].end);
+        }
+
+        assert(args[i].start < args[i].end);
+        assert((args[i].end - args[i].start) % ctx->rs_n == 0);
+
+        if (pthread_create(&pthreads[i], NULL, process, &args[i]) != 0) {
+            FATAL("failed to create thread %d\n", i);
+        }
+
+        current += rs_blocks_per_thread;
+    }
+
+    ctx->rv = 0;
+
+    for (int i = 0; i < threads; ++i) {
+        if (pthread_join(pthreads[i], NULL) != 0) {
+            FATAL("failed to join thread %d: %s\n", i, strerror(errno));
+        }
+
+        ctx->rv += args[i].rv;
+
+        if (args[i].rs) {
+            free_rs_char(args[i].rs);
+            args[i].rs = NULL;
+        }
+    }
+
+    return true;
+}
diff --git a/verity/fec/image.h b/verity/fec/image.h
new file mode 100644 (file)
index 0000000..f0211fd
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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 __FEC_H__
+#define __FEC_H__
+
+#include <utils/Compat.h>
+#include <string>
+#include <vector>
+#include <fec/io.h>
+#include <fec/ecc.h>
+
+#define IMAGE_MIN_THREADS     1
+#define IMAGE_MAX_THREADS     128
+
+#define INFO(x...) \
+    fprintf(stderr, x);
+#define FATAL(x...) { \
+    fprintf(stderr, x); \
+    exit(1); \
+}
+
+#define unlikely(x)    __builtin_expect(!!(x), 0)
+
+struct image {
+    /* if true, decode file in place instead of creating a new output file */
+    bool inplace;
+    /* if true, use memory mapping instead of copying all input into memory */
+    bool mmap;
+    /* if true, assume input is a sparse file */
+    bool sparse;
+    /* if true, print more verbose information to stderr */
+    bool verbose;
+    const char *fec_filename;
+    int fec_fd;
+    int inp_fd;
+    /* the number of Reed-Solomon generator polynomial roots, also the number
+       of parity bytes generated for each N bytes in RS(M, N) */
+    int roots;
+    /* for RS(M, N), N = M - roots */
+    int rs_n;
+    int threads;
+    uint32_t fec_size;
+    uint64_t blocks;
+    uint64_t inp_size;
+    uint64_t pos;
+    uint64_t rounds;
+    uint64_t rv;
+    uint8_t *fec;
+    uint8_t *fec_mmap_addr;
+    uint8_t *input;
+    uint8_t *output;
+};
+
+struct image_proc_ctx;
+typedef void (*image_proc_func)(image_proc_ctx *);
+
+struct image_proc_ctx {
+    image_proc_func func;
+    int id;
+    image *ctx;
+    uint64_t rv;
+    uint64_t fec_pos;
+    uint64_t start;
+    uint64_t end;
+    void *rs;
+};
+
+extern bool image_load(const std::vector<std::string>& filename, image *ctx,
+        bool output_needed);
+extern bool image_save(const std::string& filename, image *ctx);
+
+extern bool image_ecc_new(const std::string& filename, image *ctx);
+extern bool image_ecc_load(const std::string& filename, image *ctx);
+extern bool image_ecc_save(image *ctx);
+
+extern bool image_process(image_proc_func f, image *ctx);
+
+extern void image_init(image *ctx);
+extern void image_free(image *ctx);
+
+inline uint8_t image_get_interleaved_byte(uint64_t i, image *ctx)
+{
+    uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+    if (unlikely(offset >= ctx->inp_size)) {
+        return 0;
+    }
+
+    return ctx->input[offset];
+}
+
+inline void image_set_interleaved_byte(uint64_t i, image *ctx,
+        uint8_t value)
+{
+    uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+    if (unlikely(offset >= ctx->inp_size)) {
+        assert(value == 0);
+    } else if (ctx->output && ctx->output[offset] != value) {
+        ctx->output[offset] = value;
+    }
+}
+
+#endif // __FEC_H__
diff --git a/verity/fec/main.cpp b/verity/fec/main.cpp
new file mode 100644 (file)
index 0000000..50d807e
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * 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.
+ */
+
+extern "C" {
+    #include <fec.h>
+}
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <android-base/file.h>
+#include "image.h"
+
+enum {
+    MODE_ENCODE,
+    MODE_DECODE,
+    MODE_PRINTSIZE,
+    MODE_GETECCSTART,
+    MODE_GETVERITYSTART
+};
+
+static void encode_rs(struct image_proc_ctx *ctx)
+{
+    struct image *fcx = ctx->ctx;
+    int j;
+    uint8_t data[fcx->rs_n];
+    uint64_t i;
+
+    for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+        for (j = 0; j < fcx->rs_n; ++j) {
+            data[j] = image_get_interleaved_byte(i + j, fcx);
+        }
+
+        encode_rs_char(ctx->rs, data, &fcx->fec[ctx->fec_pos]);
+        ctx->fec_pos += fcx->roots;
+    }
+}
+
+static void decode_rs(struct image_proc_ctx *ctx)
+{
+    struct image *fcx = ctx->ctx;
+    int j, rv;
+    uint8_t data[fcx->rs_n + fcx->roots];
+    uint64_t i;
+
+    assert(sizeof(data) == FEC_RSM);
+
+    for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+        for (j = 0; j < fcx->rs_n; ++j) {
+            data[j] = image_get_interleaved_byte(i + j, fcx);
+        }
+
+        memcpy(&data[fcx->rs_n], &fcx->fec[ctx->fec_pos], fcx->roots);
+        rv = decode_rs_char(ctx->rs, data, NULL, 0);
+
+        if (rv < 0) {
+            FATAL("failed to recover [%" PRIu64 ", %" PRIu64 ")\n",
+                i, i + fcx->rs_n);
+        } else if (rv > 0) {
+            /* copy corrected data to output */
+            for (j = 0; j < fcx->rs_n; ++j) {
+                image_set_interleaved_byte(i + j, fcx, data[j]);
+            }
+
+            ctx->rv += rv;
+        }
+
+        ctx->fec_pos += fcx->roots;
+    }
+}
+
+static int usage()
+{
+    printf("fec: a tool for encoding and decoding files using RS(255, N).\n"
+           "\n"
+           "usage: fec <mode> [ <options> ] [ <data> <fec> [ <output> ] ]\n"
+           "mode:\n"
+           "  -e  --encode                      encode (default)\n"
+           "  -d  --decode                      decode\n"
+           "  -s, --print-fec-size=<data size>  print FEC size\n"
+           "  -E, --get-ecc-start=data          print ECC offset in data\n"
+           "  -V, --get-verity-start=data       print verity offset\n"
+           "options:\n"
+           "  -h                                show this help\n"
+           "  -v                                enable verbose logging\n"
+           "  -r, --roots=<bytes>               number of parity bytes\n"
+           "  -m, --mmap                        use memory mapping\n"
+           "  -j, --threads=<threads>           number of threads to use\n"
+           "  -S                                treat data as a sparse file\n"
+           "decoding options:\n"
+           "  -i, --inplace                     correct <data> in place\n"
+        );
+
+    return 1;
+}
+
+static uint64_t parse_arg(const char *arg, const char *name, uint64_t maxval)
+{
+    char* endptr;
+    errno = 0;
+
+    unsigned long long int value = strtoull(arg, &endptr, 0);
+
+    if (arg[0] == '\0' || *endptr != '\0' ||
+            (errno == ERANGE && value == ULLONG_MAX)) {
+        FATAL("invalid value of %s\n", name);
+    }
+    if (value > maxval) {
+        FATAL("value of roots too large (max. %" PRIu64 ")\n", maxval);
+    }
+
+    return (uint64_t)value;
+}
+
+static int print_size(image& ctx)
+{
+    /* output size including header */
+    printf("%" PRIu64 "\n", fec_ecc_get_size(ctx.inp_size, ctx.roots));
+    return 0;
+}
+
+static int get_start(int mode, const std::string& filename)
+{
+    fec::io fh(filename, O_RDONLY, FEC_VERITY_DISABLE);
+
+    if (!fh) {
+        FATAL("failed to open input\n");
+    }
+
+    if (mode == MODE_GETECCSTART) {
+        fec_ecc_metadata data;
+
+        if (!fh.get_ecc_metadata(data)) {
+            FATAL("no ecc data\n");
+        }
+
+        printf("%" PRIu64 "\n", data.start);
+    } else {
+        fec_verity_metadata data;
+
+        if (!fh.get_verity_metadata(data)) {
+            FATAL("no verity data\n");
+        }
+
+        printf("%" PRIu64 "\n", data.data_size);
+    }
+
+    return 0;
+}
+
+static int encode(image& ctx, const std::vector<std::string>& inp_filenames,
+        const std::string& fec_filename)
+{
+    if (ctx.inplace) {
+        FATAL("invalid parameters: inplace can only used when decoding\n");
+    }
+
+    if (!image_load(inp_filenames, &ctx, false)) {
+        FATAL("failed to read input\n");
+    }
+
+    if (!image_ecc_new(fec_filename, &ctx)) {
+        FATAL("failed to allocate ecc\n");
+    }
+
+    INFO("encoding RS(255, %d) to '%s' for input files:\n", ctx.rs_n,
+        fec_filename.c_str());
+
+    size_t n = 1;
+
+    for (auto fn : inp_filenames) {
+        INFO("\t%zu: '%s'\n", n++, fn.c_str());
+    }
+
+    if (ctx.verbose) {
+        INFO("\traw fec size: %u\n", ctx.fec_size);
+        INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+        INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+    }
+
+    if (!image_process(encode_rs, &ctx)) {
+        FATAL("failed to process input\n");
+    }
+
+    if (!image_ecc_save(&ctx)) {
+        FATAL("failed to write output\n");
+    }
+
+    image_free(&ctx);
+    return 0;
+}
+
+static int decode(image& ctx, const std::vector<std::string>& inp_filenames,
+        const std::string& fec_filename, std::string& out_filename)
+{
+    const std::string& inp_filename = inp_filenames.front();
+
+    if (ctx.inplace && ctx.sparse) {
+        FATAL("invalid parameters: inplace cannot be used with sparse "
+            "files\n");
+    }
+
+    if (!image_ecc_load(fec_filename, &ctx) ||
+            !image_load(inp_filenames, &ctx, !out_filename.empty())) {
+        FATAL("failed to read input\n");
+    }
+
+    if (ctx.inplace) {
+        INFO("correcting '%s' using RS(255, %d) from '%s'\n",
+            inp_filename.c_str(), ctx.rs_n, fec_filename.c_str());
+
+        out_filename = inp_filename;
+    } else {
+        INFO("decoding '%s' to '%s' using RS(255, %d) from '%s'\n",
+            inp_filename.c_str(),
+            out_filename.empty() ? out_filename.c_str() : "<none>", ctx.rs_n,
+            fec_filename.c_str());
+    }
+
+    if (ctx.verbose) {
+        INFO("\traw fec size: %u\n", ctx.fec_size);
+        INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+        INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+    }
+
+    if (!image_process(decode_rs, &ctx)) {
+        FATAL("failed to process input\n");
+    }
+
+    if (ctx.rv) {
+        INFO("corrected %" PRIu64 " errors\n", ctx.rv);
+    } else {
+        INFO("no errors found\n");
+    }
+
+    if (!out_filename.empty() && !image_save(out_filename, &ctx)) {
+        FATAL("failed to write output\n");
+    }
+
+    image_free(&ctx);
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    std::string fec_filename;
+    std::string out_filename;
+    std::vector<std::string> inp_filenames;
+    int mode = MODE_ENCODE;
+    image ctx;
+
+    image_init(&ctx);
+    ctx.roots = FEC_DEFAULT_ROOTS;
+
+    while (1) {
+        const static struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"encode", no_argument, 0, 'e'},
+            {"decode", no_argument, 0, 'd'},
+            {"sparse", no_argument, 0, 'S'},
+            {"roots", required_argument, 0, 'r'},
+            {"inplace", no_argument, 0, 'i'},
+            {"mmap", no_argument, 0, 'm'},
+            {"threads", required_argument, 0, 'j'},
+            {"print-fec-size", required_argument, 0, 's'},
+            {"get-ecc-start", required_argument, 0, 'E'},
+            {"get-verity-start", required_argument, 0, 'V'},
+            {"verbose", no_argument, 0, 'v'},
+            {NULL, 0, 0, 0}
+        };
+        int c = getopt_long(argc, argv, "hedSr:imj:s:E:V:v", long_options, NULL);
+        if (c < 0) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            return usage();
+        case 'S':
+            ctx.sparse = true;
+            break;
+        case 'e':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            break;
+        case 'd':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_DECODE;
+            break;
+        case 'r':
+            ctx.roots = (int)parse_arg(optarg, "roots", FEC_RSM);
+            break;
+        case 'i':
+            ctx.inplace = true;
+            break;
+        case 'm':
+            ctx.mmap = true;
+            break;
+        case 'j':
+            ctx.threads = (int)parse_arg(optarg, "threads", IMAGE_MAX_THREADS);
+            break;
+        case 's':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_PRINTSIZE;
+            ctx.inp_size = parse_arg(optarg, "print-fec-size", UINT64_MAX);
+            break;
+        case 'E':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_GETECCSTART;
+            inp_filenames.push_back(optarg);
+            break;
+        case 'V':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_GETVERITYSTART;
+            inp_filenames.push_back(optarg);
+            break;
+        case 'v':
+            ctx.verbose = true;
+            break;
+        case '?':
+            return usage();
+        default:
+            abort();
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    assert(ctx.roots > 0 && ctx.roots < FEC_RSM);
+
+    /* check for input / output parameters */
+    if (mode == MODE_ENCODE) {
+        /* allow multiple input files */
+        for (int i = 0; i < (argc - 1); ++i) {
+            inp_filenames.push_back(argv[i]);
+        }
+
+        if (inp_filenames.empty()) {
+            return usage();
+        }
+
+        /* the last one is the output file */
+        fec_filename = argv[argc - 1];
+    } else if (mode == MODE_DECODE) {
+        if (argc < 2 || argc > 3) {
+            return usage();
+        } else if (argc == 3) {
+            if (ctx.inplace) {
+                return usage();
+            }
+            out_filename = argv[2];
+        }
+
+        inp_filenames.push_back(argv[0]);
+        fec_filename = argv[1];
+    }
+
+    switch (mode) {
+    case MODE_PRINTSIZE:
+        return print_size(ctx);
+    case MODE_GETECCSTART:
+    case MODE_GETVERITYSTART:
+        return get_start(mode, inp_filenames.front());
+    case MODE_ENCODE:
+        return encode(ctx, inp_filenames, fec_filename);
+    case MODE_DECODE:
+        return decode(ctx, inp_filenames, fec_filename, out_filename);
+    default:
+        abort();
+    }
+
+    return 1;
+}
diff --git a/verity/fec/tests/fec.py b/verity/fec/tests/fec.py
new file mode 100644 (file)
index 0000000..71a1834
--- /dev/null
@@ -0,0 +1,89 @@
+# 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.
+
+import math
+import os
+import random
+import shutil
+import subprocess
+import sys
+import tempfile
+
+blocksize = 4096
+roots = 2
+
+def corrupt(image, offset, length):
+    print "corrupting %d bytes at offset %d" % (length, offset)
+    f = os.open(image, os.O_WRONLY)
+    os.lseek(f, offset, os.SEEK_SET)
+    os.write(f, os.urandom(length))
+    os.close(f)
+
+def corruptmax(image, roots):
+    size = os.stat(image).st_size
+
+    blocks = int(math.ceil(float(size) / blocksize))
+    rounds = int(math.ceil(float(blocks) / (255 - roots)))
+
+    max_errors = int(math.floor(rounds * roots / 2)) * blocksize
+    offset = random.randrange(0, size - max_errors)
+
+    corrupt(image, offset, max_errors)
+
+def encode(image, fec, roots):
+    if subprocess.call([ "fec", "--roots= " + str(roots), image, fec ]) != 0:
+        raise Exception("encoding failed")
+
+def decode(image, fec, output):
+    return subprocess.call([ "fec", "--decode", image, fec, output ])
+
+def compare(a, b):
+    return subprocess.call([ "cmp", "-s", a, b ])
+
+def simg2img(image, output):
+    print "creating a non-sparse copy of '%s' to '%s'" % (image, output)
+    if subprocess.call([ "simg2img", image, output]) != 0:
+        raise Exception("simg2img failed")
+
+def main(argv):
+    image = argv[0]
+
+    temp_img = tempfile.NamedTemporaryFile()
+    temp_cor = tempfile.NamedTemporaryFile()
+    temp_fec = tempfile.NamedTemporaryFile()
+    temp_out = tempfile.NamedTemporaryFile()
+
+    simg2img(image, temp_img.name)
+    simg2img(image, temp_cor.name)
+
+    encode(image, temp_fec.name, roots)
+    corruptmax(temp_cor.name, roots)
+
+    if decode(temp_cor.name, temp_fec.name, temp_out.name) != 0:
+        raise Exception("FAILED: failed to correct maximum expected errors")
+
+    if compare(temp_img.name, temp_out.name) != 0:
+        raise Exception("FAILED: corrected file not identical")
+    else:
+        print "corrected content matches original"
+
+    corrupt(temp_cor.name, 0, blocksize)
+
+    if decode(temp_cor.name, temp_fec.name, temp_out.name) == 0:
+        raise Exception("FAILED: corrected more than maximum number of errors?")
+
+    print "PASSED"
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
index fada61d..b706e3a 100644 (file)
@@ -72,14 +72,6 @@ IMPLEMENT_ASN1_FUNCTIONS(BootSignature)
 
 static BIO *g_error = NULL;
 
-#if defined(OPENSSL_IS_BORINGSSL) && !defined(BORINGSSL_201509)
-/* In BoringSSL, ERR_print_errors has been moved to the BIO functions in order
- * to avoid the incorrect dependency of ERR on BIO. */
-static void ERR_print_errors(BIO *bio) {
-    BIO_print_errors(bio);
-}
-#endif
-
 /**
  * Rounds n up to the nearest multiple of page_size
  * @param n The value to round
diff --git a/zram-perf/Android.mk b/zram-perf/Android.mk
new file mode 100644 (file)
index 0000000..f62e0a2
--- /dev/null
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_MODULE := zram-perf
+LOCAL_CFLAGS += -g -Wall -Werror -std=c++11 -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+LOCAL_SRC_FILES := \
+    zram-perf.cpp
+include $(BUILD_EXECUTABLE)
diff --git a/zram-perf/zram-perf.cpp b/zram-perf/zram-perf.cpp
new file mode 100644 (file)
index 0000000..32a03ad
--- /dev/null
@@ -0,0 +1,151 @@
+#include <iostream>
+#include <chrono>
+#include <numeric>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <unistd.h>
+#include <sys/swap.h>
+
+using namespace std;
+
+const char zram_blkdev_path[] = "/dev/block/zram0";
+const size_t sector_size = 512;
+const size_t page_size = 4096;
+
+void fillPageRand(uint32_t *page) {
+    int start = rand();
+    for (int i = 0; i < page_size / sizeof(int); i++) {
+        page[i] = start+i;
+    }
+}
+void fillPageCompressible(uint32_t *page) {
+    int val = rand() & 0xfff;
+    for (int i = 0; i < page_size / sizeof(int); i++) {
+        page[i] = val;
+    }
+}
+
+class AlignedAlloc {
+    void *m_ptr;
+public:
+    AlignedAlloc(size_t size, size_t align) {
+        posix_memalign(&m_ptr, align, size);
+    }
+    ~AlignedAlloc() {
+        free(m_ptr);
+    }
+    void *ptr() {
+        return m_ptr;
+    }
+};
+
+class BlockFd {
+    int m_fd = -1;
+public:
+    BlockFd(const char *path, bool direct) {
+        m_fd = open(path, O_RDWR | (direct ? O_DIRECT : 0));
+    }
+    size_t getSize() {
+        size_t blockSize = 0;
+        int result = ioctl(m_fd, BLKGETSIZE, &blockSize);
+        if (result < 0) {
+            cout << "ioctl failed" << endl;
+        }
+        return blockSize * sector_size;
+    }
+    ~BlockFd() {
+        if (m_fd >= 0) {
+            close(m_fd);
+        }
+    }
+    void fillWithCompressible() {
+        size_t devSize = getSize();
+        AlignedAlloc page(page_size, page_size);
+        for (uint64_t offset = 0; offset < devSize; offset += page_size) {
+            fillPageCompressible((uint32_t*)page.ptr());
+            ssize_t ret = write(m_fd, page.ptr(), page_size);
+            if (ret != page_size) {
+                cout << "write() failed" << endl;
+            }
+        }
+    }
+    void benchSequentialRead() {
+        chrono::time_point<chrono::high_resolution_clock> start, end;
+        size_t devSize = getSize();
+        size_t passes = 4;
+        AlignedAlloc page(page_size, page_size);
+
+        start = chrono::high_resolution_clock::now();
+        for (int i = 0; i < passes; i++) {
+            for (uint64_t offset = 0; offset < devSize; offset += page_size) {
+                if (offset == 0)
+                    lseek(m_fd, offset, SEEK_SET);
+                ssize_t ret = read(m_fd, page.ptr(), page_size);
+                if (ret != page_size) {
+                    cout << "read() failed" << endl;
+                }
+            }
+        }
+        end = chrono::high_resolution_clock::now();
+        size_t duration = chrono::duration_cast<chrono::microseconds>(end - start).count();
+        cout << "read: " << (double)devSize * passes / 1024.0 / 1024.0 / (duration / 1000.0 / 1000.0) << "MB/s" << endl;
+    }
+    void benchSequentialWrite() {
+        chrono::time_point<chrono::high_resolution_clock> start, end;
+        size_t devSize = getSize();
+        size_t passes = 4;
+        AlignedAlloc page(page_size, page_size);
+
+        start = chrono::high_resolution_clock::now();
+        for (int i = 0; i < passes; i++) {
+            for (uint64_t offset = 0; offset < devSize; offset += page_size) {
+                fillPageCompressible((uint32_t*)page.ptr());
+                if (offset == 0)
+                    lseek(m_fd, offset, SEEK_SET);
+                ssize_t ret = write(m_fd, page.ptr(), page_size);
+                if (ret != page_size) {
+                    cout << "write() failed" << endl;
+                }
+            }
+        }
+        end = chrono::high_resolution_clock::now();
+        size_t duration = chrono::duration_cast<chrono::microseconds>(end - start).count();
+        cout << "write: " << (double)devSize * passes / 1024.0 / 1024.0 / (duration / 1000.0 / 1000.0) << "MB/s" << endl;
+
+    }
+};
+
+int bench(bool direct)
+{
+    BlockFd zramDev{zram_blkdev_path, direct};
+
+    zramDev.fillWithCompressible();
+    zramDev.benchSequentialRead();
+    zramDev.benchSequentialWrite();
+    return 0;
+}
+
+int main(int argc, char *argv[])
+{
+    int result = swapoff(zram_blkdev_path);
+    if (result < 0) {
+        cout << "swapoff failed: " << strerror(errno) << endl;
+    }
+
+    bench(1);
+
+    result = system((string("mkswap ") + string(zram_blkdev_path)).c_str());
+    if (result < 0) {
+        cout << "mkswap failed: " <<  strerror(errno) << endl;
+        return -1;
+    }
+
+    result = swapon(zram_blkdev_path, 0);
+    if (result < 0) {
+        cout << "swapon failed: " <<  strerror(errno) << endl;
+        return -1;
+    }
+    return 0;
+}