--- /dev/null
+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)
--- /dev/null
+#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;
+}
--- /dev/null
+# 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)
--- /dev/null
+
+ 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
+
--- /dev/null
+/*
+ * 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,
+};
--- /dev/null
+/*
+ * 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';
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+# 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)
--- /dev/null
+
+ 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
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#
+# 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
--- /dev/null
+
+ 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
+
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);
}
/*
--- /dev/null
+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
--- /dev/null
+
+ 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
+
--- /dev/null
+#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;
+}
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
libext4_utils_src_files += \
key_control.cpp \
- ext4_crypt.cpp \
- unencrypted_properties.cpp
+ ext4_crypt.cpp
ifneq ($(HOST_OS),windows)
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 \
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 \
* 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;
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;
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)
{
}
#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);
}
/* 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
{
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;
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()
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)
/* 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)
{
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;
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;
+}
+
* 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;
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();
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
--- /dev/null
+/*
+ * 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;
+}
+++ /dev/null
-/*
- * 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
-}
#include <string.h>
#include <stdio.h>
-#ifdef HAVE_ANDROID_OS
+#ifdef __ANDROID__
#include <linux/capability.h>
#else
#include <private/android_filesystem_capability.h>
/*
- * 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>
#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)'.')
// 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;
#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;
}
--- /dev/null
+/*
+ * 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
+/*
+ * 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);
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
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);
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;
-}
+/*
+ * 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
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
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;
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
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);
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;
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 */
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 *));
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);
}
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;
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,
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)
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;
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;
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);
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);
* 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,
}
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);
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;
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;
{
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;
#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, ...)
{
* 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
}
static u32 build_default_directory_structure(const char *dir_path,
- struct selabel_handle *sehnd)
+ struct selabel_handle *sehnd)
{
u32 inode;
u32 root_inode;
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;
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;
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 {
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");
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();
} 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,
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);
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
}
#ifdef ANDROID
#include <private/android_filesystem_config.h>
+#include <private/canned_fs_config.h>
#endif
#ifndef USE_MINGW
#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
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");
}
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);
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);
}
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;
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
}
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
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
+++ /dev/null
-#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();
-}
+++ /dev/null
-#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);
#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
int wipe_block_device(int fd, s64 len);
+#ifdef __cplusplus
+}
+#endif
+
#endif
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)
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)
#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>
{
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;
}
}
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);
#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>
#include <sys/disk.h>
#endif
+#include "make_f2fs.h"
+
#ifndef USE_MINGW /* O_BINARY is windows-specific flag */
#define O_BINARY 0
#endif
-# 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)
--- /dev/null
+
+ 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
+
--- /dev/null
+// 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;
+}
--- /dev/null
+// 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);
+ });
+}
--- /dev/null
+// 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
--- /dev/null
+// 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;
+}
--- /dev/null
+// 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
--- /dev/null
+
+ 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
+
# 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)
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;
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;
LOCAL_MODULE_TAGS := debug
+LOCAL_CFLAGS := -Wno-unused-parameter
+
include $(BUILD_EXECUTABLE)
--- /dev/null
+# 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
--- /dev/null
+
+ 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
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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___ */
--- /dev/null
+/*
+ * 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___ */
--- /dev/null
+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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
# 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)
--- /dev/null
+/*
+ * 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);
+}
}
#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;
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;
}
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;
maps_count++;
}
+ free(line);
fclose(maps_f);
new_maps = realloc(maps, maps_count * sizeof(pm_map_t*));
# 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)
--- /dev/null
+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)
--- /dev/null
+
+ 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
+
--- /dev/null
+#!/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()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+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 :=
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+
+ 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
+
--- /dev/null
+/*
+ * 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);
+}
* 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
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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_);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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));
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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), "");
+}
--- /dev/null
+/*
+ * 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());
+}
--- /dev/null
+/*
+ * 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), "");
+}
--- /dev/null
+
+ 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
+
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();
--- /dev/null
+
+ 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
+
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
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));
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;
}
// 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) },
--- /dev/null
+#
+# 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)
--- /dev/null
+
+ 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
+
--- /dev/null
+#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()
--- /dev/null
+#include <stdio.h>
+
+int main() {
+ fprintf(stderr, "mmap-perf is unsupported for 32-bit architectures\n");
+ return -1;
+}
--- /dev/null
+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)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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_
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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, ¶meters)) { 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);
+}
--- /dev/null
+#!/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
--- /dev/null
+# 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)
+
--- /dev/null
+
+ 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
+
--- /dev/null
+Pagecache tools.
+
+dumpcache.c: dumps complete pagecache of device.
+pagecache.py: shows live info on files going in/out of pagecache.
--- /dev/null
+#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;
+}
--- /dev/null
+#!/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()
quipper/perf_reader.cc \
quipper/perf_parser.cc \
perf_data_converter.cc \
+ configreader.cc \
cpuconfig.cc \
perfprofdcore.cc \
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
--- /dev/null
+
+ 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
+
--- /dev/null
+/*
+**
+** 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;
+}
--- /dev/null
+/*
+**
+** 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
// 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;
+
}
--- /dev/null
+service perfprofd /system/xbin/perfprofd
+ class late_start
+ user root
+ group root wakelock
+ oneshot
+ writepid /dev/cpuset/system-background/tasks
#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
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");
}
//
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]);
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;
}
//
+ // 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?
}
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
}
// 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
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");
//
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);
}
//
//
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);
// 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);
** 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"
// 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
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
#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"
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)
{
//
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
--- /dev/null
+#
+# 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)
--- /dev/null
+
+ 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
+
--- /dev/null
+#!/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
# 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)
# 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)
}
}
+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;
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);
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;
}
}
--- /dev/null
+
+ 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
+
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;
}
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug
LOCAL_MODULE := sane_schedstat
+LOCAL_CFLAGS := -Wno-unused-parameter
include $(BUILD_EXECUTABLE)
--- /dev/null
+
+ 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
+
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
+#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;
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] == '/'
}
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);
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;
}
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(" # ");
}
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:
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;
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) {
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;
}
* 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':
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))
--- /dev/null
+
+ 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
+
#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_
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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_
#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);
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;
}
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();
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());
}
}
{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"},
};
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");
}
}
-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); });
+}
#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")}));
}
#include <string>
#include <vector>
-#include <base/logging.h>
+#include <android-base/logging.h>
#include "command.h"
};
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\"";
}
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());
}
}
printf("%s\n", command.LongHelpString().c_str());
}
-HelpCommand help_command;
+void RegisterHelpCommand() {
+ RegisterCommand("help", [] { return std::unique_ptr<Command>(new HelpCommand); });
+}
*/
#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");
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") {
}
};
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); });
+}
#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"}));
}
#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);
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 a 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;
}
}
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;
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;
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) {
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;
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);
}
}
}
// 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));
}
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()); });
+}
#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) {
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"}));
+}
--- /dev/null
+/*
+ * 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()); });
+}
--- /dev/null
+/*
+ * 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
+
*/
#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;
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) {
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); });
+}
#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"}));
+ }
}
#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;
#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 {
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_
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 {
}
};
-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());
}
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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_
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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_
#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");
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 == '-') {
++p;
}
}
- return result;
+ return std::vector<int>(cpu_set.begin(), cpu_set.end());
}
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.
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);
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) {
}
}
-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;
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;
(*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) {
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;
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)) {
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;
+}
#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;
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;
bool ProcessKernelSymbols(const std::string& symbol_file,
std::function<bool(const KernelSymbol&)> callback);
+bool CheckPerfEventLimit();
#endif // SIMPLE_PERF_ENVIRONMENT_H_
--- /dev/null
+/*
+ * 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;
+}
#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) {
"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)));
}
#include <string>
#include <unordered_map>
-#include <base/logging.h>
+#include <android-base/logging.h>
#include "event_type.h"
#include "utils.h"
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);
}
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);
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);
}
#ifndef SIMPLE_PERF_EVENT_ATTR_H_
#define SIMPLE_PERF_EVENT_ATTR_H_
-#include <stdint.h>
-#include <string>
+#include <stddef.h>
#include "perf_event.h"
#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() {
}
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 {
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))) {
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;
}
// 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.
// 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) {
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;
+}
#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"
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;
// 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_;
// 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_
#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() {
}
}
-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;
}
}
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;
}
return false;
}
*have_data = true;
- event_fd->DiscardMmapData(size);
}
return true;
}
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;
}
#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.
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_
#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;
<< "', 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;
}
#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_
{"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))},
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')
--- /dev/null
+/*
+ * 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_
#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",
+ ¶noid_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",
+ ¶noid_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;
+}
*/
#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);
--- /dev/null
+/*
+ * 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.
+ */
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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;
--- /dev/null
+/*
+ * 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;
+}
#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_
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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_
--- /dev/null
+/*
+**
+** 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);
+}
--- /dev/null
+/*
+ * 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_
--- /dev/null
+/*
+ * 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);
+}
*/
#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;
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;
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";
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;
}
#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_
--- /dev/null
+/*
+ * 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)));
+}
#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);
}
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>
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));
}
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) {
}
}
+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));
}
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);
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);
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);
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();
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;
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);
}
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) {
}
}
+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) {
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,
return record;
}
+ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
+ uint32_t ptid) {
+ ForkRecord record;
+ record.header.type = PERF_RECORD_FORK;
+ record.header.misc = 0;
+ record.data.pid = pid;
+ record.data.ppid = ppid;
+ record.data.tid = tid;
+ record.data.ptid = ptid;
+ record.data.time = 0;
+ size_t sample_id_size = record.sample_id.CreateContent(attr);
+ record.header.size = sizeof(record.header) + sizeof(record.data) + sample_id_size;
+ return record;
+}
+
BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
const std::string& filename) {
BuildIdRecord record;
record.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;
+}
#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>
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.
// 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:
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 {
} 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;
}
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.
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;
}
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_
+++ /dev/null
-/*
- * 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;
-}
#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
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_;
~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);
};
uint64_t size;
};
-static const char* PERF_MAGIC = "PERFILE2";
+constexpr char PERF_MAGIC[] = "PERFILE2";
struct FileHeader {
char magic[8];
--- /dev/null
+/*
+ * 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;
+}
#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"
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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+}
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>
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) {
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]);
+ }
+}
--- /dev/null
+#!/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()
--- /dev/null
+#
+# 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
--- /dev/null
+#
+# 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
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+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;
+}
--- /dev/null
+constexpr int LOOP_COUNT = 100000000;
+
+void Function1() {
+ for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+ }
+}
+
+int main() {
+ Function1();
+ return 0;
+}
--- /dev/null
+<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>
--- /dev/null
+#!/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()
--- /dev/null
+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;
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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_
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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_
--- /dev/null
+/*
+ * 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);
--- /dev/null
+#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;
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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_
#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;
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) {
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);
}
}
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;
+}
#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_
#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));
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() {
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]);
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() {
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;
}
#include <string>
#include <vector>
-#include <base/macros.h>
+#include <android-base/macros.h>
class Workload {
private:
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_;
}
}
bool CreateNewProcess();
- void WaitChildProcess(bool no_hang);
+ bool WaitChildProcess(bool wait_forever);
WorkState work_state_;
std::vector<std::string> args_;
#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) {
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");
+}
--- /dev/null
+
+ 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
+
#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)
return -1;
}
- if (ev.type == EV_KEY) {
+ if (ev.type == EV_KEY && ev.value == 1) {
*key_code = ev.code;
}
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);
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 {
ev_dispatch();
}
- if (time(NULL) - start >= LAST_TIMEOUT_S) {
+ if (android::uptimeMillis() - start >= LAST_TIMEOUT_MS) {
break;
}
} while (key_code != KEY_POWER);
include $(CLEAR_VARS)
LOCAL_MODULE := sound
LOCAL_SRC_FILES := playwav.c
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wno-unused-parameter
include $(BUILD_EXECUTABLE)
--- /dev/null
+
+ 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
+
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
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
--- /dev/null
+
+ 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
+
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
}
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
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"
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
#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;
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;
#ifndef _SQUASHFS_UTILS_H_
#define _SQUASHFS_UTILS_H_
+#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
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
}
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.
--- /dev/null
+<!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>
--- /dev/null
+#!/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()
#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>
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);
default:
break;
}
- } while ((attr = nla_next(attr, &attr_size)));
+ }
}
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:
default:
break;
}
- } while ((attr = nla_next(attr, &remaining)));
+ }
return NL_STOP;
}
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, ¶meter,
- 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;
}
}
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);
static unsigned int mixers;
static unsigned int timers;
-int getPcmNodes(void)
+unsigned int getPcmNodes(void)
{
DIR *d;
struct dirent *de;
TEST(pcmtest, CheckAudioDir) {
pcms = getPcmNodes();
- ASSERT_GT(pcms, 0);
+ ASSERT_GT(pcms, 0U);
}
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) {
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE_TAGS := eng tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativebenchmark
LOCAL_STATIC_LIBRARIES += \
libtestUtil
LOCAL_MODULE := binderAddInts
LOCAL_SRC_FILES := binderAddInts.cpp
-include $(BUILD_EXECUTABLE)
+include $(BUILD_NATIVE_BENCHMARK)
*/
/*
- * 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>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
+
+#include <benchmark/benchmark.h>
+
#include <utils/Log.h>
#include <testUtil.h>
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
};
// 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;
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, ¤t);
-
- // Calculate how long this operation took and update the stats
- struct timespec deltaTimespec = tsDelta(&start, ¤t);
- 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;
}
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); }
// 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;
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();
+}
+
+++ /dev/null
-# 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
+++ /dev/null
-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.
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-#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;
-}
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-#!/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
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)
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)
LOCAL_MODULE:= test-fb-refresh
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wno-unused-parameter
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)
#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 };
}
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() {
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)
--- /dev/null
+# 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)
--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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));
+}
--- /dev/null
+/*
+ * 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));
+}
--- /dev/null
+/*
+ * 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));
+}
--- /dev/null
+
+/*
+ * 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));
+}
--- /dev/null
+#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;
+}
--- /dev/null
+/*
+ * 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));
+}
+
# 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
return NULL;
}
- if (!bench->setSize(values["size"].int_value)) {
+ if (!bench->setSize(size)) {
printf("Failed to allocate buffers for benchmark.\n");
return NULL;
}
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);
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;
}
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"
"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
}
};
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"
"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
}
};
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"
"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
}
};
_buffer = NULL;
}
- if (_size == 0) {
+ if (size == 0) {
_size = DEFAULT_SINGLE_BUFFER_SIZE;
} else {
_size = size;
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"
"ldmfd sp!, {r0,r1,r2,r3,r4}\n"
:: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+ void bench(size_t) {
#endif
}
};
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"
"ldmfd sp!, {r0,r1,r2,r3,r4}\n"
:: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+ void bench(size_t) {
#endif
}
};
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"
"ldmfd sp!, {r0,r1,r2,r3,r4}\n"
:: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+ void bench(size_t) {
#endif
}
};
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"
"ldmfd sp!, {r0,r1,r2,r3}\n"
:: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+ void bench(size_t) {
#endif
}
};
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"
"ldmfd sp!, {r0,r1,r2,r3}\n"
:: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+ void bench(size_t) {
#endif
}
};
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"
"ldmfd sp!, {r0,r1,r2,r3}\n"
:: "r" (_buffer), "r" (_size), "r" (num_loops) : "r0", "r1", "r2");
+#else
+ void bench(size_t) {
#endif
}
};
startTime();
- float total = 0;
// Do ~1 billion ops
for (int ct=0; ct < (1000 * (1000 / 20)); ct++) {
for (int i=0; i < 1000; i++) {
startTime();
- float total = 0;
// Do ~1 billion ops
for (int ct=0; ct < (1000 * (1000 / 80)); ct++) {
for (int i=0; i < 1000; i++) {
}
#endif
-int fp_test(int argc, char** argv) {
+int fp_test(int, char**) {
test_mad();
#ifdef __ARM_NEON__
return 0;
}
-
-
-
-
#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);
" malloc [fill]\n"
" madvise\n"
" resampler\n"
- " crash\n"
" stack (stack smasher)\n"
" crawl\n"
, p);
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);
function_t function_table[] = {
{ "malloc", malloc_test },
{ "madvise", madvise_test },
- { "crash", crash_test },
{ "stack", stack_smasher_test },
{ "crawl", crawl_test },
{ "fp", fp_test },
{ "multithread_bandwidth", multithread_bandwidth },
};
-int main(int argc, char** argv)
-{
+int main(int argc, char** argv) {
if (argc == 1) {
usage(argv[0]);
return 0;
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;
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);
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;
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;
}
-
#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!");
}
--- /dev/null
+#!/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()
# 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",
MaybeRaiseSocketError(ret)
return ret
+
def Connect(s, to):
"""Python wrapper for connect."""
ret = libc.connect(s.fileno(), to.CPointer(), len(to))
"""
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):
__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))
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):
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))
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)
--- /dev/null
+#!/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()
--- /dev/null
+#!/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()
import sys
import cstruct
+import netlink
### Base netlink constants. See include/uapi/linux/netlink.h.
# 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
RTM_GETROUTE = 26
RTM_NEWNEIGH = 28
RTM_DELNEIGH = 29
+RTM_GETNEIGH = 30
RTM_NEWRULE = 32
RTM_DELRULE = 33
RTM_GETRULE = 34
# Route metric attributes.
RTAX_MTU = 2
+RTAX_HOPLIMIT = 10
# Data structure formats.
IfinfoMsg = 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
FRA_FWMARK = 10
FRA_SUPPRESS_PREFIXLEN = 14
FRA_TABLE = 15
+FRA_FWMASK = 16
FRA_OIFNAME = 17
FRA_UID_START = 18
FRA_UID_END = 19
IFLA_NUM_RX_QUEUES = 32
IFLA_CARRIER = 33
+
def CommandVerb(command):
return ["NEW", "DEL", "GET", "SET"][command % 4]
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))
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
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_")
# 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]
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:
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>".
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)
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,
"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."""
# 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):
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))
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)
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()
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
PRIORITY_UID = 100
PRIORITY_OIF = 200
PRIORITY_FWMARK = 300
+ PRIORITY_IIF = 400
PRIORITY_DEFAULT = 999
PRIORITY_UNREACHABLE = 1000
@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
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)
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),
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:
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":
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
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")
# 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
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)
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
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)
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.
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))
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.
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)
# 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.
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" % (
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):
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)
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)
# 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:
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)
# 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
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])
try:
CheckIPv6Connectivity(True)
+ self.SetIPv6SysctlOnAllIfaces("accept_ra", 1)
self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
CheckIPv6Connectivity(False)
finally:
# 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.
# 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)
# 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.
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"])
# 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:
finally:
self.SetMarkReflectSysctls(0)
- @unittest.skipUnless(HAVE_MARK_REFLECT, "no mark reflection")
def testIPv6UnmarkedSocketPMTU(self):
self.SetMarkReflectSysctls(1)
try:
@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)
--- /dev/null
+#!/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()
import fcntl
import os
+import random
+import re
from socket import * # pylint: disable=wildcard-import
import struct
import unittest
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
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"
"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
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):
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)
class RunAsUid(object):
-
"""Context guard to run a code block as a given UID."""
def __init__(self, uid):
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()
--- /dev/null
+#!/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
--- /dev/null
+#!/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)
+
import os
import posix
import random
-import re
from socket import * # pylint: disable=wildcard-import
import threading
import time
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),
# 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)
#!/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
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.
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
--- /dev/null
+#!/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
--- /dev/null
+#!/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()
# limitations under the License.
import errno
-import os
import random
from socket import * # pylint: disable=wildcard-import
import time
import csocket
import iproute
import multinetwork_base
-import multinetwork_test
+import packets
import net_test
# Setsockopt values.
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)
# [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):
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
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.
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
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
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()
--- /dev/null
+#!/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()
--- /dev/null
+#!/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)
pageinout_test.c \
thrashing_test.c
+LOCAL_CFLAGS := -std=gnu11
+
LOCAL_MODULE:= pagingtest
LOCAL_MODULE_TAGS := tests
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;
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));
}
}
- 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
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;
schedtest.c
LOCAL_MODULE := schedtest
+LOCAL_CFLAGS := -Wno-unused-parameter
include $(BUILD_EXECUTABLE)
--- /dev/null
+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)
--- /dev/null
+#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;
+}
# Copyright 2006 The Android Open Source Project
LOCAL_PATH:= $(call my-dir)
+
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= timetest.c
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)
+
--- /dev/null
+/*
+ * 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);
+}
LOCAL_SHARED_LIBRARIES += libcutils
LOCAL_MODULE:= uevents
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wno-unused-parameter
include $(BUILD_EXECUTABLE)
case $DEVICE in
-(shamu|hammerhead|bullhead)
+(shamu|hammerhead|bullhead|ariel)
# no scaling necessary
xmax=0
ymax=0;;
--- /dev/null
+#
+# 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}%\)
# 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 {
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}
output=${output:="./out"}
# clear the output file
-> $output
+if [ -f $output ]; then
+ > $output
+fi
# ADB commands
AM_FORCE_START="${ADB}am start -W -S"
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)
function resetJankyFrames {
_gfxapp=$1
- _gfxapp=${app:="com.android.systemui"}
+ _gfxapp=${_gfxapp:="com.android.systemui"}
${ADB}dumpsys gfxinfo $_gfxapp reset 2>&1 >/dev/null
}
}
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
}
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
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
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
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 {
--- /dev/null
+#!/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)
--- /dev/null
+# 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
--- /dev/null
+# 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
+
(-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"
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
latency99=$5
if [ ${totalDiff:=0} -eq 0 ]; then
echo Error: could not read frame info with \"dumpsys gfxinfo\"
- exit 1
fi
((frameSum=frameSum+totalDiff))
((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
# 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
(-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]"
if [ $iterations -gt 1 ]; then
echo =========================================
echo Iteration $cur of $iterations
+ date
echo =========================================
fi
if [ $iterations -gt 1 -o $cur -eq 1 ]; then
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
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 =========================================
((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
--- /dev/null
+#
+# 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}%\)
+++ /dev/null
-/*
- * 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 */
LOCAL_PATH:= $(call my-dir)
ifeq ($(HOST_OS),linux)
+
include $(CLEAR_VARS)
LOCAL_MODULE := verify_boot_signature
LOCAL_SRC_FILES := verify_boot_signature.c
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
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))
throws Exception, IOException, CertificateEncodingException {
ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
certificate = s.readObject();
+ publicKey = cert.getPublicKey();
}
public byte[] generateSignableImage(byte[] image) throws IOException {
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);
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");
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
--- /dev/null
+
+ 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
+
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);
BLOCK_SIZE,
BLOCK_SIZE,
data_blocks,
- data_blocks + (METADATA_SIZE / BLOCK_SIZE),
+ data_blocks,
root_hash,
salt)
return table
#include <string.h>
#include <unistd.h>
+#include <android-base/file.h>
+
struct sparse_hash_ctx {
unsigned char *hashes;
const unsigned char *salt;
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;
--- /dev/null
+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)
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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__
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+# 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:])
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
--- /dev/null
+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)
--- /dev/null
+#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;
+}