ANRdaemon: move trace result from /sdcard to /data am: d93aa41807 am: adfc967454 am: 20e3c1bc94
am: bcbe37b0ee
Change-Id: Ib2a3e4969d22f5ea2c046633a81ed322e20d29e1
diff --git a/ANRdaemon/Android.mk b/ANRdaemon/Android.mk
index 449df1e..24072e2 100644
--- a/ANRdaemon/Android.mk
+++ b/ANRdaemon/Android.mk
@@ -9,6 +9,7 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug
LOCAL_SHARED_LIBRARIES := \
+ liblog \
libbinder \
libcutils \
libutils \
diff --git a/boot_control_copy/boot_control_copy.c b/boot_control_copy/boot_control_copy.c
index 644e7de..7302243 100644
--- a/boot_control_copy/boot_control_copy.c
+++ b/boot_control_copy/boot_control_copy.c
@@ -92,7 +92,7 @@
return 0;
}
-#define COPY_BUF_SIZE 1024*1024
+#define COPY_BUF_SIZE (1024*1024)
static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
{
diff --git a/boot_control_copy/bootinfo.c b/boot_control_copy/bootinfo.c
index 396dd81..82a134f 100644
--- a/boot_control_copy/bootinfo.c
+++ b/boot_control_copy/bootinfo.c
@@ -116,12 +116,12 @@
return fd;
}
-// As per struct bootloader_message which is defined in
+// As per struct bootloader_message_ab 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)
+#define BOOTINFO_OFFSET offsetof(struct bootloader_message_ab, slot_suffix)
bool boot_info_load(BrilloBootInfo *out_info)
{
diff --git a/ext4_utils/ext4.h b/ext4_utils/ext4.h
index ac6f97e..974fb2d 100644
--- a/ext4_utils/ext4.h
+++ b/ext4_utils/ext4.h
@@ -302,11 +302,12 @@
#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
#define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS)
-#define EXT4_FITS_IN_INODE(ext4_inode, einode, field) ((offsetof(typeof(*ext4_inode), field) + sizeof((ext4_inode)->field)) <= (EXT4_GOOD_OLD_INODE_SIZE + (einode)->i_extra_isize))
+#define EXT4_FITS_IN_INODE(ext4_inode, einode, field) ((offsetof(typeof(*(ext4_inode)), field) + sizeof((ext4_inode)->field)) <= (EXT4_GOOD_OLD_INODE_SIZE + (einode)->i_extra_isize))
#define EXT4_INODE_SET_XTIME(xtime, inode, raw_inode) do { (raw_inode)->xtime = cpu_to_le32((inode)->xtime.tv_sec); if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) (raw_inode)->xtime ## _extra = ext4_encode_extra_time(&(inode)->xtime); } while (0)
#define EXT4_EINODE_SET_XTIME(xtime, einode, raw_inode) do { if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) (raw_inode)->xtime = cpu_to_le32((einode)->xtime.tv_sec); if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra)) (raw_inode)->xtime ## _extra = ext4_encode_extra_time(&(einode)->xtime); } while (0)
-#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode) do { (inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) ext4_decode_extra_time(&(inode)->xtime, raw_inode->xtime ## _extra); } while (0)
-#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode) do { if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) (einode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra)) ext4_decode_extra_time(&(einode)->xtime, raw_inode->xtime ## _extra); } while (0)
+#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode) do { (inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) ext4_decode_extra_time(&(inode)->xtime, (raw_inode)->xtime ## _extra); } while (0)
+
+#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode) do { if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) (einode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra)) ext4_decode_extra_time(&(einode)->xtime, (raw_inode)->xtime ## _extra); } while (0)
#define i_disk_version osd1.linux1.l_i_version
#define i_reserved1 osd1.linux1.l_i_reserved1
@@ -562,7 +563,7 @@
#define EXT4_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT4_DIR_ROUND) & ~EXT4_DIR_ROUND)
#define EXT4_MAX_REC_LEN ((1<<16)-1)
-#define is_dx(dir) (EXT4_HAS_COMPAT_FEATURE(dir->i_sb, EXT4_FEATURE_COMPAT_DIR_INDEX) && (EXT4_I(dir)->i_flags & EXT4_INDEX_FL))
+#define is_dx(dir) (EXT4_HAS_COMPAT_FEATURE((dir)->i_sb, EXT4_FEATURE_COMPAT_DIR_INDEX) && (EXT4_I(dir)->i_flags & EXT4_INDEX_FL))
#define EXT4_DIR_LINK_MAX(dir) (!is_dx(dir) && (dir)->i_nlink >= EXT4_LINK_MAX)
#define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1)
diff --git a/ext4_utils/ext4fixup.c b/ext4_utils/ext4fixup.c
index 184cd0d..4b40207 100644
--- a/ext4_utils/ext4fixup.c
+++ b/ext4_utils/ext4fixup.c
@@ -806,6 +806,7 @@
}
close(fd);
+ free(dirbuf);
return 0;
}
diff --git a/ext4_utils/key_control.h b/ext4_utils/key_control.h
index bbf0ace..748c32d 100644
--- a/ext4_utils/key_control.h
+++ b/ext4_utils/key_control.h
@@ -8,12 +8,12 @@
typedef int32_t key_serial_t;
// special process keyring shortcut IDs
-#define KEY_SPEC_THREAD_KEYRING -1 // key ID for thread-specific keyring
-#define KEY_SPEC_PROCESS_KEYRING -2 // key ID for process-specific keyring
-#define KEY_SPEC_SESSION_KEYRING -3 // key ID for session-specific keyring
-#define KEY_SPEC_USER_KEYRING -4 // key ID for UID-specific keyring
-#define KEY_SPEC_USER_SESSION_KEYRING -5 // key ID for UID-session keyring
-#define KEY_SPEC_GROUP_KEYRING -6 // key ID for GID-specific keyring
+#define KEY_SPEC_THREAD_KEYRING (-1) // key ID for thread-specific keyring
+#define KEY_SPEC_PROCESS_KEYRING (-2) // key ID for process-specific keyring
+#define KEY_SPEC_SESSION_KEYRING (-3) // key ID for session-specific keyring
+#define KEY_SPEC_USER_KEYRING (-4) // key ID for UID-specific keyring
+#define KEY_SPEC_USER_SESSION_KEYRING (-5) // key ID for UID-session keyring
+#define KEY_SPEC_GROUP_KEYRING (-6) // key ID for GID-specific keyring
key_serial_t add_key(const char *type,
const char *description,
diff --git a/ext4_utils/sha1.c b/ext4_utils/sha1.c
index 463ec38..5a8a02f 100644
--- a/ext4_utils/sha1.c
+++ b/ext4_utils/sha1.c
@@ -44,17 +44,17 @@
#else
# define blk0(i) block->l[i]
#endif
-#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
- ^block->l[(i+2)&15]^block->l[i&15],1))
+#define blk(i) (block->l[(i)&15] = rol(block->l[((i)+13)&15]^block->l[((i)+8)&15] \
+ ^block->l[((i)+2)&15]^block->l[(i)&15],1))
/*
* (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
*/
-#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
-#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
-#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
-#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
-#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+#define R0(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk0(i)+0x5A827999+rol(v,5);(w)=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk(i)+0x5A827999+rol(v,5);(w)=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0x6ED9EBA1+rol(v,5);(w)=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=((((w)|(x))&(y))|((w)&(x)))+blk(i)+0x8F1BBCDC+rol(v,5);(w)=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0xCA62C1D6+rol(v,5);(w)=rol(w,30);
typedef union {
u_char c[64];
diff --git a/f2fs_utils/Android.mk b/f2fs_utils/Android.mk
index 647c390..82c3ee0 100644
--- a/f2fs_utils/Android.mk
+++ b/f2fs_utils/Android.mk
@@ -76,7 +76,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := libf2fs_sparseblock
LOCAL_SRC_FILES := f2fs_sparseblock.c
-LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_C_INCLUDES := external/f2fs-tools/include \
system/core/include/log
include $(BUILD_SHARED_LIBRARY)
@@ -84,7 +84,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := f2fs_sparseblock
LOCAL_SRC_FILES := f2fs_sparseblock.c
-LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_C_INCLUDES := external/f2fs-tools/include \
system/core/include/log
include $(BUILD_EXECUTABLE)
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index e39a61f..e968ce0 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -26,9 +26,9 @@
#member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member) ); \
} while (0);
-#define segno_in_journal(sum, i) (sum->sit_j.entries[i].segno)
+#define segno_in_journal(sum, i) ((sum)->sit_j.entries[i].segno)
-#define sit_in_journal(sum, i) (sum->sit_j.entries[i].se)
+#define sit_in_journal(sum, i) ((sum)->sit_j.entries[i].se)
static void dbg_print_raw_sb_info(struct f2fs_super_block *sb)
{
diff --git a/kexec_tools/kexecload.c b/kexec_tools/kexecload.c
index 18f5e64..2bf3d18 100644
--- a/kexec_tools/kexecload.c
+++ b/kexec_tools/kexecload.c
@@ -21,7 +21,7 @@
// Physical buffer address cannot overlap with other regions
#define START_ADDRESS 0x44000000
-#define ROUND_TO_PAGE(address,pagesize) ((address + pagesize - 1) & (~(pagesize - 1)))
+#define ROUND_TO_PAGE(address,pagesize) (((address) + (pagesize) - 1) & (~((pagesize) - 1)))
/*
* Gives file position and resets current position to begining of file
diff --git a/ksmutils/lookup3.c b/ksmutils/lookup3.c
index e607295..8fcc325 100644
--- a/ksmutils/lookup3.c
+++ b/ksmutils/lookup3.c
@@ -114,12 +114,12 @@
*/
#define mix(a,b,c) \
{ \
- a -= c; a ^= rot(c, 4); c += b; \
- b -= a; b ^= rot(a, 6); a += c; \
- c -= b; c ^= rot(b, 8); b += a; \
- a -= c; a ^= rot(c,16); c += b; \
- b -= a; b ^= rot(a,19); a += c; \
- c -= b; c ^= rot(b, 4); b += a; \
+ (a) -= (c); (a) ^= rot(c, 4); (c) += (b); \
+ (b) -= (a); (b) ^= rot(a, 6); (a) += (c); \
+ (c) -= (b); (c) ^= rot(b, 8); (b) += (a); \
+ (a) -= (c); (a) ^= rot(c,16); (c) += (b); \
+ (b) -= (a); (b) ^= rot(a,19); (a) += (c); \
+ (c) -= (b); (c) ^= rot(b, 4); (b) += (a); \
}
/*
@@ -149,13 +149,13 @@
*/
#define final(a,b,c) \
{ \
- c ^= b; c -= rot(b,14); \
- a ^= c; a -= rot(c,11); \
- b ^= a; b -= rot(a,25); \
- c ^= b; c -= rot(b,16); \
- a ^= c; a -= rot(c,4); \
- b ^= a; b -= rot(a,14); \
- c ^= b; c -= rot(b,24); \
+ (c) ^= (b); (c) -= rot(b,14); \
+ (a) ^= (c); (a) -= rot(c,11); \
+ (b) ^= (a); (b) -= rot(a,25); \
+ (c) ^= (b); (c) -= rot(b,16); \
+ (a) ^= (c); (a) -= rot(c,4); \
+ (b) ^= (a); (b) -= rot(a,14); \
+ (c) ^= (b); (c) -= rot(b,24); \
}
/*
diff --git a/libfec/Android.mk b/libfec/Android.mk
index 45fb19e..7dcdb25 100644
--- a/libfec/Android.mk
+++ b/libfec/Android.mk
@@ -17,7 +17,7 @@
fec_process.cpp
common_static_libraries := \
- libmincrypt \
+ libcrypto_utils_static \
libcrypto_static \
libcutils \
libbase
diff --git a/libfec/fec_private.h b/libfec/fec_private.h
index 238c4e2..0d94228 100644
--- a/libfec/fec_private.h
+++ b/libfec/fec_private.h
@@ -23,17 +23,17 @@
#include <new>
#include <pthread.h>
#include <stdio.h>
-#include <string>
#include <string.h>
+#include <string>
#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 <crypto_utils/android_pubkey.h>
#include <fec/ecc.h>
+#include <fec/io.h>
+#include <openssl/sha.h>
+#include <utils/Compat.h>
/* processing parameters */
#define WORK_MIN_THREADS 1
@@ -59,7 +59,7 @@
struct verity_header {
uint32_t magic;
uint32_t version;
- uint8_t signature[RSANUMBYTES];
+ uint8_t signature[ANDROID_PUBKEY_MODULUS_SIZE];
uint32_t length;
};
diff --git a/libfec/fec_process.cpp b/libfec/fec_process.cpp
index 3b2846c..6e0ddd1 100644
--- a/libfec/fec_process.cpp
+++ b/libfec/fec_process.cpp
@@ -62,11 +62,13 @@
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 max_threads = fec_div_round_up(count, count_per_thread);
+
+ if ((size_t)threads > max_threads) {
+ threads = (int)max_threads;
}
- 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;
diff --git a/libfec/fec_read.cpp b/libfec/fec_read.cpp
index 2d29da8..0f5ec99 100644
--- a/libfec/fec_read.cpp
+++ b/libfec/fec_read.cpp
@@ -47,7 +47,9 @@
for (size_t m = 0; m < bytes_per_line; ++m) {
if (n + m < size) {
- sprintf(&hex[m * 3], "%02x ", data[n + m]);
+ ptrdiff_t offset = &hex[m * 3] - hex;
+ snprintf(hex + offset, sizeof(hex) - offset, "%02x ",
+ data[n + m]);
if (isprint(data[n + m])) {
prn[m] = data[n + m];
diff --git a/libfec/include/fec/io.h b/libfec/include/fec/io.h
index 670a5d7..7f8122c 100644
--- a/libfec/include/fec/io.h
+++ b/libfec/include/fec/io.h
@@ -24,7 +24,8 @@
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
-#include <mincrypt/rsa.h>
+
+#include <crypto_utils/android_pubkey.h>
#ifdef __cplusplus
extern "C" {
@@ -70,8 +71,8 @@
struct fec_verity_metadata {
bool disabled;
uint64_t data_size;
- uint8_t signature[RSANUMBYTES];
- uint8_t ecc_signature[RSANUMBYTES];
+ uint8_t signature[ANDROID_PUBKEY_MODULUS_SIZE];
+ uint8_t ecc_signature[ANDROID_PUBKEY_MODULUS_SIZE];
const char *table;
uint32_t table_length;
};
diff --git a/libfec/test/Android.mk b/libfec/test/Android.mk
index a2bba55..d78c6d2 100644
--- a/libfec/test/Android.mk
+++ b/libfec/test/Android.mk
@@ -11,6 +11,7 @@
LOCAL_STATIC_LIBRARIES := \
libfec_host \
libfec_rs_host \
+ libcrypto_utils_static \
libcrypto_static \
libext4_utils_host \
libsquashfs_utils_host \
diff --git a/libpagemap/include/pagemap/pagemap.h b/libpagemap/include/pagemap/pagemap.h
index 4de2b4b..61e59e2 100644
--- a/libpagemap/include/pagemap/pagemap.h
+++ b/libpagemap/include/pagemap/pagemap.h
@@ -186,7 +186,7 @@
uint64_t low, uint64_t hi,
uint64_t **range_out, size_t *len);
-#define _BITS(x, offset, bits) (((x) >> offset) & ((1LL << (bits)) - 1))
+#define _BITS(x, offset, bits) (((x) >> (offset)) & ((1LL << (bits)) - 1))
#define PM_PAGEMAP_PRESENT(x) (_BITS(x, 63, 1))
#define PM_PAGEMAP_SWAPPED(x) (_BITS(x, 62, 1))
diff --git a/librank/librank.c b/librank/librank.c
index 5c7c64e..9d1c026 100644
--- a/librank/librank.c
+++ b/librank/librank.c
@@ -135,7 +135,7 @@
if (library->mappings_count >= library->mappings_size) {
library->mappings = realloc(library->mappings,
- 2 * library->mappings_size * sizeof(struct mapping*));
+ 2 * library->mappings_size * sizeof(struct mapping_info*));
if (!library->mappings) {
fprintf(stderr, "Couldn't resize mappings array: %s\n", strerror(errno));
exit(EXIT_FAILURE);
diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp
index c9671e1..216ff9d 100644
--- a/memory_replay/Action.cpp
+++ b/memory_replay/Action.cpp
@@ -47,7 +47,7 @@
class AllocAction : public Action {
public:
- AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {}
+ explicit AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {}
protected:
uintptr_t key_pointer_ = 0;
@@ -152,7 +152,7 @@
class FreeAction : public AllocAction {
public:
- FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
+ explicit FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
}
bool DoesFree() override { return key_pointer_ != 0; }
diff --git a/micro_bench/micro_bench.cpp b/micro_bench/micro_bench.cpp
index c20f7d8..4d3177d 100644
--- a/micro_bench/micro_bench.cpp
+++ b/micro_bench/micro_bench.cpp
@@ -33,7 +33,7 @@
#define DEFAULT_DATA_SIZE 1000000000
// The amount of memory allocated for the cold benchmarks to use.
-#define DEFAULT_COLD_DATA_SIZE 128*1024*1024
+#define DEFAULT_COLD_DATA_SIZE (128*1024*1024)
// The default size of the stride between each buffer for cold benchmarks.
#define DEFAULT_COLD_STRIDE_SIZE 4096
@@ -197,9 +197,9 @@
#define MAINLOOP(cmd_data, BENCH, COMPUTE_AVG, PRINT_ITER, PRINT_AVG) \
uint64_t time_ns; \
- int iters = cmd_data.args[1]; \
- bool print_average = cmd_data.print_average; \
- bool print_each_iter = cmd_data.print_each_iter; \
+ int iters = (cmd_data).args[1]; \
+ bool print_average = (cmd_data).print_average; \
+ bool print_each_iter = (cmd_data).print_each_iter; \
double min = 0.0, max = 0.0, running_avg = 0.0, square_avg = 0.0; \
double avg; \
for (int i = 0; iters == -1 || i < iters; i++) { \
@@ -226,7 +226,7 @@
}
#define MAINLOOP_DATA(name, cmd_data, size, BENCH) \
- size_t copies = cmd_data.data_size/size; \
+ size_t copies = (cmd_data).data_size/(size); \
size_t j; \
MAINLOOP(cmd_data, \
for (j = 0; j < copies; j++) { \
@@ -239,14 +239,14 @@
std_dev, min, max));
#define MAINLOOP_COLD(name, cmd_data, size, num_incrs, BENCH) \
- size_t num_strides = num_buffers / num_incrs; \
- if ((num_buffers % num_incrs) != 0) { \
+ size_t num_strides = num_buffers / (num_incrs); \
+ if ((num_buffers % (num_incrs)) != 0) { \
num_strides--; \
} \
size_t copies = 1; \
- num_buffers = num_incrs * num_strides; \
- if (num_buffers * size < static_cast<size_t>(cmd_data.data_size)) { \
- copies = cmd_data.data_size / (num_buffers * size); \
+ num_buffers = (num_incrs) * num_strides; \
+ if (num_buffers * (size) < static_cast<size_t>((cmd_data).data_size)) { \
+ copies = (cmd_data).data_size / (num_buffers * (size)); \
} \
if (num_strides == 0) { \
printf("%s: Chosen options lead to no copies, aborting.\n", name); \
@@ -255,7 +255,7 @@
size_t j, k; \
MAINLOOP(cmd_data, \
for (j = 0; j < copies; j++) { \
- for (k = 0; k < num_incrs; k++) { \
+ for (k = 0; k < (num_incrs); k++) { \
BENCH; \
} \
}, \
@@ -271,8 +271,8 @@
// be executed once.
// BENCH - The actual code to benchmark and is timed.
#define BENCH_ONE_BUF(name, cmd_data, INIT, BENCH) \
- size_t size = cmd_data.args[0]; \
- uint8_t *buf = allocateAlignedMemory(size, cmd_data.dst_align, cmd_data.dst_or_mask); \
+ size_t size = (cmd_data).args[0]; \
+ uint8_t *buf = allocateAlignedMemory(size, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
if (!buf) \
return -1; \
INIT; \
@@ -285,14 +285,14 @@
// be executed once.
// BENCH - The actual code to benchmark and is timed.
#define BENCH_TWO_BUFS(name, cmd_data, INIT, BENCH) \
- size_t size = cmd_data.args[0]; \
- uint8_t *buf1 = allocateAlignedMemory(size, cmd_data.src_align, cmd_data.src_or_mask); \
+ size_t size = (cmd_data).args[0]; \
+ uint8_t *buf1 = allocateAlignedMemory(size, (cmd_data).src_align, (cmd_data).src_or_mask); \
if (!buf1) \
return -1; \
size_t total_size = size; \
- if (cmd_data.dst_str_size > 0) \
- total_size += cmd_data.dst_str_size; \
- uint8_t *buf2 = allocateAlignedMemory(total_size, cmd_data.dst_align, cmd_data.dst_or_mask); \
+ if ((cmd_data).dst_str_size > 0) \
+ total_size += (cmd_data).dst_str_size; \
+ uint8_t *buf2 = allocateAlignedMemory(total_size, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
if (!buf2) \
return -1; \
INIT; \
@@ -312,19 +312,19 @@
// be executed once.
// BENCH - The actual code to benchmark and is timed.
#define COLD_ONE_BUF(name, cmd_data, INIT, BENCH) \
- size_t size = cmd_data.args[0]; \
- size_t incr = getAlignmentIncrement(size, cmd_data.dst_align); \
- size_t num_buffers = cmd_data.cold_data_size / incr; \
+ size_t size = (cmd_data).args[0]; \
+ size_t incr = getAlignmentIncrement(size, (cmd_data).dst_align); \
+ size_t num_buffers = (cmd_data).cold_data_size / incr; \
size_t buffer_size = num_buffers * incr; \
- uint8_t *buffer = getColdBuffer(num_buffers, incr, cmd_data.dst_align, cmd_data.dst_or_mask); \
+ uint8_t *buffer = getColdBuffer(num_buffers, incr, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
if (!buffer) \
return -1; \
- size_t num_incrs = cmd_data.cold_stride_size / incr + 1; \
+ size_t num_incrs = (cmd_data).cold_stride_size / incr + 1; \
size_t stride_incr = incr * num_incrs; \
uint8_t *buf; \
size_t l; \
INIT; \
- MAINLOOP_COLD(name, cmd_data, size, num_incrs, \
+ MAINLOOP_COLD(name, (cmd_data), size, num_incrs, \
buf = buffer + k * incr; \
for (l = 0; l < num_strides; l++) { \
BENCH; \
@@ -345,31 +345,31 @@
// be executed once.
// BENCH - The actual code to benchmark and is timed.
#define COLD_TWO_BUFS(name, cmd_data, INIT, BENCH) \
- size_t size = cmd_data.args[0]; \
- size_t buf1_incr = getAlignmentIncrement(size, cmd_data.src_align); \
+ size_t size = (cmd_data).args[0]; \
+ size_t buf1_incr = getAlignmentIncrement(size, (cmd_data).src_align); \
size_t total_size = size; \
- if (cmd_data.dst_str_size > 0) \
- total_size += cmd_data.dst_str_size; \
- size_t buf2_incr = getAlignmentIncrement(total_size, cmd_data.dst_align); \
+ if ((cmd_data).dst_str_size > 0) \
+ total_size += (cmd_data).dst_str_size; \
+ size_t buf2_incr = getAlignmentIncrement(total_size, (cmd_data).dst_align); \
size_t max_incr = (buf1_incr > buf2_incr) ? buf1_incr : buf2_incr; \
- size_t num_buffers = cmd_data.cold_data_size / max_incr; \
+ size_t num_buffers = (cmd_data).cold_data_size / max_incr; \
size_t buffer1_size = num_buffers * buf1_incr; \
size_t buffer2_size = num_buffers * buf2_incr; \
- uint8_t *buffer1 = getColdBuffer(num_buffers, buf1_incr, cmd_data.src_align, cmd_data.src_or_mask); \
+ uint8_t *buffer1 = getColdBuffer(num_buffers, buf1_incr, (cmd_data).src_align, (cmd_data).src_or_mask); \
if (!buffer1) \
return -1; \
- uint8_t *buffer2 = getColdBuffer(num_buffers, buf2_incr, cmd_data.dst_align, cmd_data.dst_or_mask); \
+ uint8_t *buffer2 = getColdBuffer(num_buffers, buf2_incr, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
if (!buffer2) \
return -1; \
size_t min_incr = (buf1_incr < buf2_incr) ? buf1_incr : buf2_incr; \
- size_t num_incrs = cmd_data.cold_stride_size / min_incr + 1; \
+ size_t num_incrs = (cmd_data).cold_stride_size / min_incr + 1; \
size_t buf1_stride_incr = buf1_incr * num_incrs; \
size_t buf2_stride_incr = buf2_incr * num_incrs; \
size_t l; \
uint8_t *buf1; \
uint8_t *buf2; \
INIT; \
- MAINLOOP_COLD(name, cmd_data, size, num_incrs, \
+ MAINLOOP_COLD(name, (cmd_data), size, num_incrs, \
buf1 = buffer1 + k * buf1_incr; \
buf2 = buffer2 + k * buf2_incr; \
for (l = 0; l < num_strides; l++) { \
diff --git a/mmap-perf/mmapPerf.cpp b/mmap-perf/mmapPerf.cpp
index c3bacf5..d195850 100644
--- a/mmap-perf/mmapPerf.cpp
+++ b/mmap-perf/mmapPerf.cpp
@@ -23,7 +23,7 @@
int get() { return m_fd; }
void set(int fd) { m_fd = fd; }
Fd() {}
- Fd(int fd) : m_fd{fd} {}
+ explicit Fd(int fd) : m_fd{fd} {}
~Fd() {
if (m_fd >= 0)
close(m_fd);
diff --git a/perfprofd/Android.mk b/perfprofd/Android.mk
index 6409f83..0a07949 100644
--- a/perfprofd/Android.mk
+++ b/perfprofd/Android.mk
@@ -73,3 +73,5 @@
# Clean temp vars
perfprofd_cppflags :=
proto_header_dir :=
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/perfprofd/perf_data_converter.cc b/perfprofd/perf_data_converter.cc
index 9d99753..e3d3737 100644
--- a/perfprofd/perf_data_converter.cc
+++ b/perfprofd/perf_data_converter.cc
@@ -7,6 +7,29 @@
namespace wireless_android_logging_awp {
+typedef quipper::ParsedEvent::DSOAndOffset DSOAndOffset;
+typedef std::vector<DSOAndOffset> callchain;
+
+struct callchain_lt {
+ bool operator()(const callchain *c1, const callchain *c2) const {
+ if (c1->size() != c2->size()) {
+ return c1->size() < c2->size();
+ }
+ for (unsigned idx = 0; idx < c1->size(); ++idx) {
+ const DSOAndOffset *do1 = &(*c1)[idx];
+ const DSOAndOffset *do2 = &(*c2)[idx];
+ if (do1->offset() != do2->offset()) {
+ return do1->offset() < do2->offset();
+ }
+ int rc = do1->dso_name().compare(do2->dso_name());
+ if (rc) {
+ return rc < 0;
+ }
+ }
+ return false;
+ }
+};
+
struct RangeTarget {
RangeTarget(uint64 start, uint64 end, uint64 to)
: start(start), end(end), to(to) {}
@@ -28,6 +51,7 @@
struct BinaryProfile {
map<uint64, uint64> address_count_map;
map<RangeTarget, uint64> range_count_map;
+ map<const callchain *, uint64, callchain_lt> callchain_count_map;
};
wireless_android_play_playlog::AndroidPerfProfile
@@ -40,8 +64,16 @@
typedef map<string, BinaryProfile> ModuleProfileMap;
typedef map<string, ModuleProfileMap> ProgramProfileMap;
+
+ // Note: the callchain_count_map member in BinaryProfile contains
+ // pointers into callchains owned by "parser" above, meaning
+ // that once the parser is destroyed, callchain pointers in
+ // name_profile_map will become stale (e.g. keep these two
+ // together in the same region).
ProgramProfileMap name_profile_map;
uint64 total_samples = 0;
+ bool seen_branch_stack = false;
+ bool seen_callchain = false;
for (const auto &event : parser.parsed_events()) {
if (!event.raw_event ||
event.raw_event->header.type != PERF_RECORD_SAMPLE) {
@@ -49,17 +81,35 @@
}
string dso_name = event.dso_and_offset.dso_name();
string program_name;
- if (dso_name == "[kernel.kallsyms]_text") {
- program_name = "kernel";
- dso_name = "[kernel.kallsyms]";
+ const string kernel_name = "[kernel.kallsyms]";
+ if (dso_name.substr(0, kernel_name.length()) == kernel_name) {
+ dso_name = kernel_name;
+ program_name = "[kernel.kallsyms]";
} else if (event.command() == "") {
program_name = "unknown_program";
} else {
program_name = event.command();
}
- name_profile_map[program_name][dso_name].address_count_map[
- event.dso_and_offset.offset()]++;
total_samples++;
+ // We expect to see either all callchain events, all branch stack
+ // events, or all flat sample events, not a mix. For callchains,
+ // however, it can be the case that none of the IPs in a chain
+ // are mappable, in which case the parsed/mapped chain will appear
+ // empty (appearing as a flat sample).
+ if (!event.callchain.empty()) {
+ CHECK(!seen_branch_stack && "examining callchain");
+ seen_callchain = true;
+ const callchain *cc = &event.callchain;
+ name_profile_map[program_name][dso_name].callchain_count_map[cc]++;
+ } else if (!event.branch_stack.empty()) {
+ CHECK(!seen_callchain && "examining branch stack");
+ seen_branch_stack = true;
+ name_profile_map[program_name][dso_name].address_count_map[
+ event.dso_and_offset.offset()]++;
+ } else {
+ name_profile_map[program_name][dso_name].address_count_map[
+ event.dso_and_offset.offset()]++;
+ }
for (size_t i = 1; i < event.branch_stack.size(); i++) {
if (dso_name == event.branch_stack[i - 1].to.dso_name()) {
uint64 start = event.branch_stack[i].to.offset();
@@ -122,6 +172,16 @@
range_samples->set_to(range_count.first.to);
range_samples->set_count(range_count.second);
}
+ for (const auto &callchain_count :
+ module_profile.second.callchain_count_map) {
+ auto address_samples = module->add_address_samples();
+ address_samples->set_count(callchain_count.second);
+ for (const auto &d_o : *callchain_count.first) {
+ int32 module_id = name_id_map[d_o.dso_name()];
+ address_samples->add_load_module_id(module_id);
+ address_samples->add_address(d_o.offset());
+ }
+ }
}
}
return ret;
diff --git a/perfprofd/perfprofdcore.cc b/perfprofd/perfprofdcore.cc
index 134c05c..746ed41 100644
--- a/perfprofd/perfprofdcore.cc
+++ b/perfprofd/perfprofdcore.cc
@@ -549,7 +549,7 @@
}
// marshall arguments
- constexpr unsigned max_args = 12;
+ constexpr unsigned max_args = 13;
const char *argv[max_args];
unsigned slot = 0;
argv[slot++] = perf_path.c_str();
@@ -571,6 +571,9 @@
// system wide profiling
argv[slot++] = "-a";
+ // no need for kernel symbols
+ argv[slot++] = "--no-dump-kernel-symbols";
+
// sleep <duration>
argv[slot++] = "/system/bin/sleep";
std::string d_str = android::base::StringPrintf("%u", duration);
diff --git a/perfprofd/quipper/base/logging.h b/perfprofd/quipper/base/logging.h
index 2851d91..aaf01c1 100644
--- a/perfprofd/quipper/base/logging.h
+++ b/perfprofd/quipper/base/logging.h
@@ -173,7 +173,7 @@
// Helper macro which avoids evaluating the arguments to a stream if
// the condition doesn't hold.
#define LAZY_STREAM(stream, condition) \
- !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream)
+ !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream) /* NOLINT */
// We use the preprocessor's merging operator, "##", so that, e.g.,
// LOG(INFO) becomes the token COMPACT_GOOGLE_LOG_INFO. There's some funny
@@ -191,7 +191,7 @@
// The VLOG macros log with negative verbosities.
#define VLOG_STREAM(verbose_level) \
- logging::LogMessage(__FILE__, __LINE__, -verbose_level).stream()
+ logging::LogMessage(__FILE__, __LINE__, -(verbose_level)).stream()
#define VLOG(verbose_level) \
LAZY_STREAM(VLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level))
@@ -215,7 +215,7 @@
// The actual stream used isn't important.
#define EAT_STREAM_PARAMETERS \
- true ? (void) 0 : ::logging::LogMessageVoidify() & LOG_STREAM(FATAL)
+ true ? (void) 0 : ::logging::LogMessageVoidify() & LOG_STREAM(FATAL) /* NOLINT */
// CHECK dies with a fatal error if condition is not true. It is *not*
// controlled by NDEBUG, so the check will be executed regardless of
diff --git a/perfprofd/quipper/base/macros.h b/perfprofd/quipper/base/macros.h
index 57eaa81..be14792 100644
--- a/perfprofd/quipper/base/macros.h
+++ b/perfprofd/quipper/base/macros.h
@@ -250,7 +250,7 @@
// it is leaked so that its destructors are not called at exit. If you need
// thread-safe initialization, use base/lazy_instance.h instead.
#define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \
- static type& name = *new type arguments
+ static type& name = *new type arguments /* NOLINT */
} // base
diff --git a/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h b/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h
index 2ac2799..9f13906 100644
--- a/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h
+++ b/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h
@@ -26,7 +26,7 @@
typedef __u32 __bitwise __le32;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#endif
-#define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)]
+#define DECLARE_BITMAP(name,bits) unsigned long (name)[BITS_TO_LONGS(bits)]
struct list_head {
struct list_head * next, * prev;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
diff --git a/perfprofd/quipper/perf_internals.h b/perfprofd/quipper/perf_internals.h
index ef5a785..a779d3c 100644
--- a/perfprofd/quipper/perf_internals.h
+++ b/perfprofd/quipper/perf_internals.h
@@ -61,4 +61,17 @@
typedef perf_event event_t;
+//
+// Custom / user-specific records emitted by simpleperf.
+// These need to be kept in sync with the simpleperf sources.
+//
+enum simpleperf_record_type {
+ SIMPLE_PERF_RECORD_TYPE_START = 32768,
+ SIMPLE_PERF_RECORD_KERNEL_SYMBOL,
+ SIMPLE_PERF_RECORD_DSO,
+ SIMPLE_PERF_RECORD_SYMBOL,
+ SIMPLE_PERF_RECORD_SPLIT,
+ SIMPLE_PERF_RECORD_SPLIT_END,
+};
+
#endif
diff --git a/perfprofd/quipper/perf_parser.cc b/perfprofd/quipper/perf_parser.cc
index 504b4f0..c9ec189 100644
--- a/perfprofd/quipper/perf_parser.cc
+++ b/perfprofd/quipper/perf_parser.cc
@@ -215,6 +215,12 @@
VLOG(1) << "Parsed event type: " << event.header.type
<< ". Doing nothing.";
break;
+ case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+ case SIMPLE_PERF_RECORD_DSO:
+ case SIMPLE_PERF_RECORD_SYMBOL:
+ case SIMPLE_PERF_RECORD_SPLIT:
+ case SIMPLE_PERF_RECORD_SPLIT_END:
+ break;
default:
LOG(ERROR) << "Unknown event type: " << event.header.type;
return false;
@@ -286,12 +292,14 @@
mapping_failed = true;
}
- // Write the remapped data back to the raw event regardless of whether it was
- // entirely successfully remapped. A single failed remap should not
- // invalidate all the other remapped entries.
- if (!WritePerfSampleInfo(sample_info, parsed_event->raw_event)) {
- LOG(ERROR) << "Failed to write back remapped sample info.";
- return false;
+ if (options_.do_remap) {
+ // Write the remapped data back to the raw event regardless of
+ // whether it was entirely successfully remapped. A single failed
+ // remap should not invalidate all the other remapped entries.
+ if (!WritePerfSampleInfo(sample_info, parsed_event->raw_event)) {
+ LOG(ERROR) << "Failed to write back remapped sample info.";
+ return false;
+ }
}
return !mapping_failed;
diff --git a/perfprofd/quipper/perf_reader.cc b/perfprofd/quipper/perf_reader.cc
index 99731d4..48497d0 100644
--- a/perfprofd/quipper/perf_reader.cc
+++ b/perfprofd/quipper/perf_reader.cc
@@ -353,6 +353,8 @@
const uint64_t sample_fields,
const uint64_t read_format,
bool swap_bytes,
+ const perf_event_attr &attr0,
+ size_t n_attrs,
struct perf_sample* sample) {
const uint64_t* initial_array_ptr = array;
@@ -460,9 +462,34 @@
array = ReadBranchStack(array, swap_bytes, sample);
}
+ // { u64 abi,
+ // u64 regs[nr]; } && PERF_SAMPLE_REGS_USER
+ if (sample_fields & PERF_SAMPLE_REGS_USER) {
+ uint64_t abi = MaybeSwap(*array++, swap_bytes);
+ if (abi != 0) {
+ assert(n_attrs == 1);
+ uint64_t reg_mask = attr0.sample_regs_user;
+ size_t bit_nr = 0;
+ for (size_t i = 0; i < 64; ++i) {
+ if ((reg_mask >> i) & 1) {
+ bit_nr++;
+ }
+ }
+ array += bit_nr;
+ }
+ }
+
+ // { u64 size,
+ // u64 regs[nr]; } && PERF_SAMPLE_STACK_USER
+ if (sample_fields & PERF_SAMPLE_STACK_USER) {
+ uint64_t size = MaybeSwap(*array++, swap_bytes);
+ if (size != 0) {
+ array += (size / sizeof(uint64_t));
+ array += 1; // for dyn_size
+ }
+ }
+
static const u64 kUnimplementedSampleFields =
- PERF_SAMPLE_REGS_USER |
- PERF_SAMPLE_STACK_USER |
PERF_SAMPLE_WEIGHT |
PERF_SAMPLE_DATA_SRC |
PERF_SAMPLE_TRANSACTION;
@@ -616,6 +643,11 @@
}
}
+ //
+ // Unsupported sample types.
+ //
+ CHECK(!(sample_fields & PERF_SAMPLE_STACK_USER|PERF_SAMPLE_REGS_USER));
+
return (array - initial_array_ptr) * sizeof(uint64_t);
}
@@ -769,6 +801,11 @@
case PERF_RECORD_LOST:
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
+ case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+ case SIMPLE_PERF_RECORD_DSO:
+ case SIMPLE_PERF_RECORD_SYMBOL:
+ case SIMPLE_PERF_RECORD_SPLIT:
+ case SIMPLE_PERF_RECORD_SPLIT_END:
return true;
case PERF_RECORD_READ:
case PERF_RECORD_MAX:
@@ -788,6 +825,14 @@
return false;
}
+ // We want to completely ignore these records
+ if (event.header.type == SIMPLE_PERF_RECORD_KERNEL_SYMBOL ||
+ event.header.type == SIMPLE_PERF_RECORD_DSO ||
+ event.header.type == SIMPLE_PERF_RECORD_SYMBOL ||
+ event.header.type == SIMPLE_PERF_RECORD_SPLIT ||
+ event.header.type == SIMPLE_PERF_RECORD_SPLIT_END)
+ return true;
+
uint64_t sample_format = GetSampleFieldsForEventType(event.header.type,
sample_type_);
uint64_t offset = GetPerfSampleDataOffset(event);
@@ -797,6 +842,8 @@
sample_format,
read_format_,
is_cross_endian_,
+ attrs_[0].attr,
+ attrs_.size(),
sample);
size_t expected_size = event.header.size - offset;
@@ -1391,7 +1438,22 @@
if (is_cross_endian_)
ByteSwap(&size);
- if (size > sizeof(event_t)) {
+ //
+ // Upstream linux perf limits the size of an event record to 2^16 bytes,
+ // however simpleperf includes extensions to support larger (2^32) record
+ // sizes via a split record scheme (the larger records are split up
+ // into chunks and then embedded into a series of SIMPLE_PERF_RECORD_SPLIT
+ // records followed by a terminating SIMPLE_PERF_RECORD_SPLIT_END record.
+ // At the moment none of the larger records are of interest to perfprofd, so
+ // the main thing we're doing here is ignoring/bypassing them.
+ //
+ if (event.header.type == SIMPLE_PERF_RECORD_KERNEL_SYMBOL ||
+ event.header.type == SIMPLE_PERF_RECORD_DSO ||
+ event.header.type == SIMPLE_PERF_RECORD_SYMBOL ||
+ event.header.type == SIMPLE_PERF_RECORD_SPLIT ||
+ event.header.type == SIMPLE_PERF_RECORD_SPLIT_END)
+ size = sizeof(event_t);
+ else if (size > sizeof(event_t)) {
LOG(INFO) << "Data size: " << size << " sizeof(event_t): "
<< sizeof(event_t);
return false;
@@ -1452,6 +1514,8 @@
ByteSwap(&event_copy->read.time_running);
ByteSwap(&event_copy->read.id);
break;
+ case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+ break;
default:
LOG(FATAL) << "Unknown event type: " << type;
}
diff --git a/perfprofd/quipper/perf_utils.cc b/perfprofd/quipper/perf_utils.cc
index 02fa9e0..4f6fdc3 100644
--- a/perfprofd/quipper/perf_utils.cc
+++ b/perfprofd/quipper/perf_utils.cc
@@ -105,6 +105,7 @@
PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_CPU | PERF_SAMPLE_IDENTIFIER;
break;
case PERF_RECORD_SAMPLE:
+ case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
break;
default:
LOG(FATAL) << "Unknown event type " << event_type;
@@ -140,6 +141,9 @@
offset = sizeof(event.mmap2) - sizeof(event.mmap2.filename) +
GetUint64AlignedStringLength(event.mmap2.filename);
break;
+ case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+ offset = 0;
+ break;
default:
LOG(FATAL) << "Unknown/unsupported event type " << event.header.type;
break;
diff --git a/perfprofd/tests/Android.mk b/perfprofd/tests/Android.mk
index bdd82e0..45c2779 100644
--- a/perfprofd/tests/Android.mk
+++ b/perfprofd/tests/Android.mk
@@ -17,7 +17,7 @@
include $(BUILD_STATIC_LIBRARY)
#
-# Canned perf.data files needed by unit test.
+# Canned perf.data file needed by unit test.
#
include $(CLEAR_VARS)
LOCAL_MODULE := canned.perf.data
@@ -28,6 +28,17 @@
include $(BUILD_PREBUILT)
#
+# Second canned perf.data file needed by unit test.
+#
+include $(CLEAR_VARS)
+LOCAL_MODULE := callchain.canned.perf.data
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := DATA
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest/perfprofd_test
+LOCAL_SRC_FILES := callchain.canned.perf.data
+include $(BUILD_PREBUILT)
+
+#
# Unit test for perfprofd
#
include $(CLEAR_VARS)
diff --git a/perfprofd/tests/README.txt b/perfprofd/tests/README.txt
index 4d8db99..949e73f 100644
--- a/perfprofd/tests/README.txt
+++ b/perfprofd/tests/README.txt
@@ -1,13 +1,19 @@
-Native tests for 'perfprofd'. Please run with 'runtest perfprofd'
-(a.k.a. "$ANDROID_BUILD_TOP"/development/testrunner/runtest.py).
+Native tests for 'perfprofd'. Please run with
+
+ runtest --path=system/extras/perfprofd/tests
+
+(where runtest == $ANDROID_BUILD_TOP"/development/testrunner/runtest.py).
Notes:
-1. One of the testpoints in this test suite performs a live 'perf'
-run on the device; before invoking the test be sure that 'perf'
-has been built and installed on the device in /system/bin/perf
+1. Several of the testpoints in this unit tests perform a live 'simpleperf'
+run on the device (if you are using a userdebug build, simpleperf should
+already be available in /system/xbin/simpleperf).
-2. The daemon under test, perfprofd, is broken into a main function, a
+2. Part of the test is a system-wide profile, meaning that you will
+need to run 'adb root' prior to test execution.
+
+3. The daemon under test, perfprofd, is broken into a main function, a
"core" library, and a "utils library. Picture:
+-----------+ perfprofdmain.o
diff --git a/perfprofd/tests/callchain.canned.perf.data b/perfprofd/tests/callchain.canned.perf.data
new file mode 100644
index 0000000..8d84393
--- /dev/null
+++ b/perfprofd/tests/callchain.canned.perf.data
Binary files differ
diff --git a/perfprofd/tests/perfprofd_test.cc b/perfprofd/tests/perfprofd_test.cc
index 3a32204..efda4bc 100644
--- a/perfprofd/tests/perfprofd_test.cc
+++ b/perfprofd/tests/perfprofd_test.cc
@@ -25,6 +25,7 @@
#include <fcntl.h>
#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
#include "perfprofdcore.h"
#include "configreader.h"
@@ -570,6 +571,81 @@
}
}
+TEST_F(PerfProfdTest, CallchainRunWithCannedPerf)
+{
+ // This test makes sure that the perf.data converter
+ // can handle call chains.
+ //
+ std::string input_perf_data(test_dir);
+ input_perf_data += "/callchain.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(), config, 0);
+ EXPECT_EQ(OK_PROFILE_COLLECTION, result);
+
+ // Read and decode the resulting perf.data.encoded file
+ wireless_android_play_playlog::AndroidPerfProfile encodedProfile;
+ readEncodedProfile("BasicRunWithCannedPerf",
+ encodedProfile);
+
+
+ // Expect 4 programs 8 load modules
+ EXPECT_EQ(4, encodedProfile.programs_size());
+ EXPECT_EQ(8, encodedProfile.load_modules_size());
+
+ // Check a couple of load modules
+ { const auto &lm0 = encodedProfile.load_modules(0);
+ std::string act_lm0 = encodedLoadModuleToString(lm0);
+ std::string sqact0 = squeezeWhite(act_lm0, "actual for lm 0");
+ const std::string expected_lm0 = RAW_RESULT(
+ name: "/system/bin/dex2oat"
+ build_id: "ee12bd1a1de39422d848f249add0afc4"
+ );
+ std::string sqexp0 = squeezeWhite(expected_lm0, "expected_lm0");
+ EXPECT_STREQ(sqexp0.c_str(), sqact0.c_str());
+ }
+ { const auto &lm1 = encodedProfile.load_modules(1);
+ std::string act_lm1 = encodedLoadModuleToString(lm1);
+ std::string sqact1 = squeezeWhite(act_lm1, "actual for lm 1");
+ const std::string expected_lm1 = RAW_RESULT(
+ name: "/system/bin/linker"
+ build_id: "a36715f673a4a0aa76ef290124c516cc"
+ );
+ std::string sqexp1 = squeezeWhite(expected_lm1, "expected_lm1");
+ EXPECT_STREQ(sqexp1.c_str(), sqact1.c_str());
+ }
+
+ // Examine some of the samples now
+ { const auto &p0 = encodedProfile.programs(0);
+ const auto &lm1 = p0.modules(0);
+ std::string act_lm1 = encodedModuleSamplesToString(lm1);
+ std::string sqact1 = squeezeWhite(act_lm1, "actual for lm1");
+ const std::string expected_lm1 = RAW_RESULT(
+ load_module_id: 0
+ address_samples { address: 108552 count: 2 }
+ );
+ std::string sqexp1 = squeezeWhite(expected_lm1, "expected_lm1");
+ EXPECT_STREQ(sqexp1.c_str(), sqact1.c_str());
+ }
+ { const auto &p4 = encodedProfile.programs(3);
+ const auto &lm2 = p4.modules(1);
+ std::string act_lm2 = encodedModuleSamplesToString(lm2);
+ std::string sqact2 = squeezeWhite(act_lm2, "actual for lm2");
+ const std::string expected_lm2 = RAW_RESULT(
+ load_module_id: 2 address_samples { address: 403913 count: 1 } address_samples { address: 840761 count: 1 } address_samples { address: 846481 count: 1 } address_samples { address: 999053 count: 1 } address_samples { address: 1012959 count: 1 } address_samples { address: 1524309 count: 1 } address_samples { address: 1580779 count: 1 } address_samples { address: 4287986288 count: 1 }
+ );
+ std::string sqexp2 = squeezeWhite(expected_lm2, "expected_lm2");
+ EXPECT_STREQ(sqexp2.c_str(), sqact2.c_str());
+ }
+}
+
TEST_F(PerfProfdTest, BasicRunWithLivePerf)
{
//
@@ -685,6 +761,68 @@
expected, "BasicRunWithLivePerf", true);
}
+TEST_F(PerfProfdTest, CallChainRunWithLivePerf)
+{
+ //
+ // Callchain profiles are only supported on certain devices.
+ // For now this test is stubbed out except when run on "angler".
+ //
+ char propBuf[PROPERTY_VALUE_MAX];
+ propBuf[0] = '\0';
+ property_get("ro.hardware", propBuf, "");
+ if (strcmp(propBuf, "angler")) {
+ return;
+ }
+
+ //
+ // Collect a callchain profile, so as to exercise the code in
+ // perf_data post-processing that digests callchains.
+ //
+ PerfProfdRunner runner;
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ std::string cfparam("config_directory="); cfparam += test_dir;
+ runner.addToConfig(cfparam);
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=12345678");
+ runner.addToConfig("max_unprocessed_profiles=100");
+ runner.addToConfig("collection_interval=9999");
+ runner.addToConfig("stack_profile=1");
+ runner.addToConfig("sample_duration=2");
+
+ // Create semaphore file
+ runner.create_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ EXPECT_EQ(0, daemon_main_return_code);
+
+ // Read and decode the resulting perf.data.encoded file
+ wireless_android_play_playlog::AndroidPerfProfile encodedProfile;
+ readEncodedProfile("CallChainRunWithLivePerf", encodedProfile);
+
+ // Examine what we get back. Since it's a live profile, we can't
+ // really do much in terms of verifying the contents.
+ EXPECT_LT(0, encodedProfile.programs_size());
+
+ // Verify log contents
+ const std::string expected = RAW_RESULT(
+ I: starting Android Wide Profiling daemon
+ I: config file path set to /data/nativetest/perfprofd_test/perfprofd.conf
+ I: random seed set to 12345678
+ I: sleep 674 seconds
+ I: initiating profile collection
+ I: profile collection complete
+ I: sleep 9325 seconds
+ I: finishing Android Wide Profiling daemon
+ );
+ // check to make sure log excerpt matches
+ compareLogMessages(mock_perfprofdutils_getlogged(),
+ expected, "CallChainRunWithLivePerf", true);
+}
+
int main(int argc, char **argv) {
executable_path = argv[0];
// switch to / before starting testing (perfprofd
diff --git a/postinst/Android.mk b/postinst/Android.mk
index b0dec05..c804cfc 100644
--- a/postinst/Android.mk
+++ b/postinst/Android.mk
@@ -21,10 +21,4 @@
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := postinst.sh
-
-# Create a symlink from /postinst to our default post-install script in the
-# same filesystem as /postinst.
-# TODO(deymo): Remove this symlink and add the path to the product config.
-LOCAL_POST_INSTALL_CMD := \
- $(hide) ln -sf bin/postinst_example $(TARGET_OUT)/postinst
include $(BUILD_PREBUILT)
diff --git a/postinst/postinst.sh b/postinst/postinst.sh
index eb98e79..5bbcf9c 100644
--- a/postinst/postinst.sh
+++ b/postinst/postinst.sh
@@ -18,18 +18,54 @@
# 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.
-#
+# marking the new slot as active. To enable running this program, insert these
+# lines in your product's .mk file (without the # at the beginning):
+
+# AB_OTA_POSTINSTALL_CONFIG += \
+# RUN_POSTINSTALL_system=true \
+# POSTINSTALL_PATH_system=bin/postinst_example \
+# FILESYSTEM_TYPE_system=ext4 \
+
# 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.
+# the script, including the 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).
-#
+# The script will run from the "postinstall" SELinux domain, from the old system
+# environment (kernel, SELinux rules, etc). New rules and domains introduced by
+# the new system won't be available when this script runs, instead, all the
+# files in the mounted directory will have the attribute "postinstall_file". All
+# the files accessed from here would need to be allowed in the old system or
+# those accesses will fail. For example, the absolute path used in the 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 an example).
+
+# We get called with two parameters: <target_slot> <status_fd>
+# * <target_slot> is the slot where the new system was just copied. This is
+# normally either 0 or 1. You can get the target suffix running
+# `bootctl get-suffix ${target_slot}`
+# * <status_fd> is a file descriptor number where this script can write to to
+# report the progress of the process. See examples below.
+
+target_slot="$1"
+status_fd="$2"
+
+my_dir=$(dirname "$0")
+
+# We can notify the updater of the progress of our program by writing to the
+# status file descriptor "global_progress <frac>\n".
+print -u${status_fd} "global_progress 0"
+
+echo "The output of this program will show up in the logs." >&2
+
+# We are half way done, so we set 0.5.
+print -u${status_fd} "global_progress 0.5"
+
+echo "Note that this program runs from ${my_dir}"
+
+# Actually, we were done.
+print -u${status_fd} "global_progress 1.0"
+
# 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.
diff --git a/procmem/procmem.c b/procmem/procmem.c
index 28055d8..17a7212 100644
--- a/procmem/procmem.c
+++ b/procmem/procmem.c
@@ -50,12 +50,12 @@
/* maps and such */
pm_map_t **maps; size_t num_maps;
- struct map_info **mis;
+ struct map_info **mis = NULL;
struct map_info *mi;
/* pagemap information */
uint64_t *pagemap; size_t num_pages;
- unsigned long address; uint64_t mapentry;
+ uint64_t mapentry;
uint64_t count, flags;
/* totals */
@@ -190,7 +190,6 @@
mi->shared_clean = mi->shared_dirty = mi->private_clean = mi->private_dirty = 0;
for (j = 0; j < num_pages; j++) {
- address = pm_map_start(mi->map) + j * ker->pagesize;
mapentry = pagemap[j];
if (PM_PAGEMAP_PRESENT(mapentry) && !PM_PAGEMAP_SWAPPED(mapentry)) {
@@ -298,6 +297,7 @@
);
}
+ free(mis);
return 0;
}
diff --git a/procrank/Android.mk b/procrank/Android.mk
index f1eb3b6..8a235fd 100644
--- a/procrank/Android.mk
+++ b/procrank/Android.mk
@@ -15,9 +15,9 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := procrank.c
+LOCAL_SRC_FILES := procrank.cpp
LOCAL_CFLAGS := -Wall -Wextra -Wformat=2 -Werror
-LOCAL_SHARED_LIBRARIES := libpagemap
+LOCAL_SHARED_LIBRARIES := libpagemap libbase
LOCAL_MODULE := procrank
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug
diff --git a/procrank/procrank.c b/procrank/procrank.cpp
similarity index 62%
rename from procrank/procrank.c
rename to procrank/procrank.cpp
index a6f8342..36f4359 100644
--- a/procrank/procrank.c
+++ b/procrank/procrank.cpp
@@ -1,18 +1,18 @@
-/*
- * 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.
- */
+//
+// 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.
+//
#include <dirent.h>
#include <errno.h>
@@ -24,16 +24,24 @@
#include <sys/types.h>
#include <unistd.h>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <pagemap/pagemap.h>
struct proc_info {
pid_t pid;
pm_memusage_t usage;
uint64_t wss;
+ int oomadj;
};
static void usage(char *myname);
-static int getprocname(pid_t pid, char *buf, int len);
+static std::string getprocname(pid_t pid);
+static int getoomadj(pid_t pid);
+static bool getminfree(std::vector<uint64_t>* minfree, std::vector<int>* adj);
static int numcmp(uint64_t a, uint64_t b);
#define declare_sort(field) \
@@ -44,6 +52,7 @@
declare_sort(pss);
declare_sort(uss);
declare_sort(swap);
+declare_sort(oomadj);
int (*compfn)(const void *a, const void *b);
static int order;
@@ -94,7 +103,7 @@
"Slab:",
"SwapTotal:",
"SwapFree:",
- "ZRam:", /* not read from meminfo but from /sys/block/zram0 */
+ "ZRam:", // not read from meminfo but from /sys/block/zram0
"Mapped:",
"VmallocUsed:",
"PageTables:",
@@ -177,7 +186,6 @@
pm_kernel_t *ker;
pm_process_t *proc;
pid_t *pids;
- struct proc_info **procs;
size_t num_procs;
uint64_t total_pss;
uint64_t total_uss;
@@ -185,19 +193,19 @@
uint64_t total_pswap;
uint64_t total_uswap;
uint64_t total_zswap;
- char cmdline[256]; // this must be within the range of int
int error;
bool has_swap = false, has_zram = false;
uint64_t required_flags = 0;
uint64_t flags_mask = 0;
- #define WS_OFF 0
- #define WS_ONLY 1
- #define WS_RESET 2
- int ws;
-
int arg;
- size_t i, j;
+ size_t i;
+
+ enum {
+ WS_OFF,
+ WS_ONLY,
+ WS_RESET,
+ } ws;
uint64_t mem[MEMINFO_COUNT] = { };
pm_proportional_swap_t *p_swap;
@@ -207,6 +215,7 @@
compfn = &sort_by_pss;
order = -1;
ws = WS_OFF;
+ bool oomadj = false;
for (arg = 1; arg < argc; arg++) {
if (!strcmp(argv[arg], "-v")) { compfn = &sort_by_vss; continue; }
@@ -214,6 +223,7 @@
if (!strcmp(argv[arg], "-p")) { compfn = &sort_by_pss; continue; }
if (!strcmp(argv[arg], "-u")) { compfn = &sort_by_uss; continue; }
if (!strcmp(argv[arg], "-s")) { compfn = &sort_by_swap; continue; }
+ if (!strcmp(argv[arg], "-o")) { compfn = &sort_by_oomadj; oomadj = true; continue; }
if (!strcmp(argv[arg], "-c")) { required_flags = 0; flags_mask = PM_PAGE_SWAPBACKED; continue; }
if (!strcmp(argv[arg], "-C")) { required_flags = flags_mask = PM_PAGE_SWAPBACKED; continue; }
if (!strcmp(argv[arg], "-k")) { required_flags = flags_mask = PM_PAGE_KSM; continue; }
@@ -242,21 +252,12 @@
exit(EXIT_FAILURE);
}
- procs = calloc(num_procs, sizeof(struct proc_info*));
- if (procs == NULL) {
- fprintf(stderr, "calloc: %s", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
+ std::vector<proc_info> procs(num_procs);
for (i = 0; i < num_procs; i++) {
- procs[i] = malloc(sizeof(struct proc_info));
- if (procs[i] == NULL) {
- fprintf(stderr, "malloc: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
- procs[i]->pid = pids[i];
- pm_memusage_zero(&procs[i]->usage);
- pm_memusage_pswap_init_handle(&procs[i]->usage, p_swap);
+ procs[i].pid = pids[i];
+ procs[i].oomadj = getoomadj(pids[i]);
+ pm_memusage_zero(&procs[i].usage);
+ pm_memusage_pswap_init_handle(&procs[i].usage, p_swap);
error = pm_process_create(ker, pids[i], &proc);
if (error) {
fprintf(stderr, "warning: could not create process interface for %d\n", pids[i]);
@@ -265,11 +266,11 @@
switch (ws) {
case WS_OFF:
- error = pm_process_usage_flags(proc, &procs[i]->usage, flags_mask,
+ error = pm_process_usage_flags(proc, &procs[i].usage, flags_mask,
required_flags);
break;
case WS_ONLY:
- error = pm_process_workingset(proc, &procs[i]->usage, 0);
+ error = pm_process_workingset(proc, &procs[i].usage, 0);
break;
case WS_RESET:
error = pm_process_workingset(proc, NULL, 1);
@@ -280,7 +281,7 @@
fprintf(stderr, "warning: could not read usage for %d\n", pids[i]);
}
- if (ws != WS_RESET && procs[i]->usage.swap) {
+ if (ws != WS_RESET && procs[i].usage.swap) {
has_swap = true;
}
@@ -291,17 +292,14 @@
if (ws == WS_RESET) exit(0);
- j = 0;
- for (i = 0; i < num_procs; i++) {
- if (procs[i]->usage.vss) {
- procs[j++] = procs[i];
- } else {
- free(procs[i]);
- }
- }
- num_procs = j;
+ procs.erase(std::remove_if(procs.begin(),
+ procs.end(),
+ [](auto proc){
+ return proc.usage.vss == 0;
+ }),
+ procs.end());
- qsort(procs, num_procs, sizeof(procs[0]), compfn);
+ qsort(procs.data(), procs.size(), sizeof(procs[0]), compfn);
if (has_swap) {
uint64_t zram_mem_used = get_zram_mem_used();
@@ -314,6 +312,9 @@
}
printf("%5s ", "PID");
+ if (oomadj) {
+ printf("%5s ", "oom");
+ }
if (ws) {
printf("%7s %7s %7s ", "WRss", "WPss", "WUss");
if (has_swap) {
@@ -341,47 +342,85 @@
total_uswap = 0;
total_zswap = 0;
- for (i = 0; i < num_procs; i++) {
- if (getprocname(procs[i]->pid, cmdline, (int)sizeof(cmdline)) < 0) {
- /*
- * Something is probably seriously wrong if writing to the stack
- * failed.
- */
- free(procs[i]);
- continue;
+ std::vector<uint64_t> lmk_minfree;
+ std::vector<int> lmk_adj;
+ if (oomadj) {
+ getminfree(&lmk_minfree, &lmk_adj);
+ }
+ auto lmk_minfree_it = lmk_minfree.cbegin();
+ auto lmk_adj_it = lmk_adj.cbegin();
+
+ auto print_oomadj_totals = [&](int adj){
+ for (; lmk_adj_it != lmk_adj.cend() && lmk_minfree_it != lmk_minfree.cend() &&
+ adj > *lmk_adj_it; lmk_adj_it++, lmk_minfree_it++) {
+ // Print the cumulative total line
+ printf("%5s ", ""); // pid
+
+ printf("%5s ", ""); // oomadj
+
+ if (ws) {
+ printf("%7s %6" PRIu64 "K %6" PRIu64 "K ",
+ "", total_pss / 1024, total_uss / 1024);
+ } else {
+ printf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ",
+ "", "", total_pss / 1024, total_uss / 1024);
+ }
+
+ if (has_swap) {
+ printf("%6" PRIu64 "K ", total_swap / 1024);
+ printf("%6" PRIu64 "K ", total_pswap / 1024);
+ printf("%6" PRIu64 "K ", total_uswap / 1024);
+ if (has_zram) {
+ printf("%6" PRIu64 "K ", total_zswap / 1024);
+ }
+ }
+
+ printf("TOTAL for oomadj < %d (%6" PRIu64 "K)\n", *lmk_adj_it, *lmk_minfree_it / 1024);
+ }
+ };
+
+ for (auto& proc: procs) {
+ if (oomadj) {
+ print_oomadj_totals(proc.oomadj);
}
- total_pss += procs[i]->usage.pss;
- total_uss += procs[i]->usage.uss;
- total_swap += procs[i]->usage.swap;
+ std::string cmdline = getprocname(proc.pid);
- printf("%5d ", procs[i]->pid);
+ total_pss += proc.usage.pss;
+ total_uss += proc.usage.uss;
+ total_swap += proc.usage.swap;
+
+ printf("%5d ", proc.pid);
+
+ if (oomadj) {
+ printf("%5d ", proc.oomadj);
+ }
if (ws) {
printf("%6zuK %6zuK %6zuK ",
- procs[i]->usage.rss / 1024,
- procs[i]->usage.pss / 1024,
- procs[i]->usage.uss / 1024
+ proc.usage.rss / 1024,
+ proc.usage.pss / 1024,
+ proc.usage.uss / 1024
);
} else {
printf("%7zuK %6zuK %6zuK %6zuK ",
- procs[i]->usage.vss / 1024,
- procs[i]->usage.rss / 1024,
- procs[i]->usage.pss / 1024,
- procs[i]->usage.uss / 1024
+ proc.usage.vss / 1024,
+ proc.usage.rss / 1024,
+ proc.usage.pss / 1024,
+ proc.usage.uss / 1024
);
}
if (has_swap) {
pm_swapusage_t su;
- pm_memusage_pswap_get_usage(&procs[i]->usage, &su);
- printf("%6zuK ", procs[i]->usage.swap / 1024);
+ pm_memusage_pswap_get_usage(&proc.usage, &su);
+ printf("%6zuK ", proc.usage.swap / 1024);
printf("%6zuK ", su.proportional / 1024);
printf("%6zuK ", su.unique / 1024);
total_pswap += su.proportional;
total_uswap += su.unique;
- pm_memusage_pswap_free(&procs[i]->usage);
+ pm_memusage_pswap_free(&proc.usage);
if (has_zram) {
size_t zpswap = su.proportional * zram_cr;
printf("%6zuK ", zpswap / 1024);
@@ -389,17 +428,22 @@
}
}
- printf("%s\n", cmdline);
-
- free(procs[i]);
+ printf("%s\n", cmdline.c_str());
}
- free(procs);
pm_memusage_pswap_destroy(p_swap);
- /* Print the separator line */
+ if (oomadj) {
+ print_oomadj_totals(INT_MAX);
+ }
+
+ // Print the separator line
printf("%5s ", "");
+ if (oomadj) {
+ printf("%5s ", "");
+ }
+
if (ws) {
printf("%7s %7s %7s ", "", "------", "------");
} else {
@@ -415,8 +459,13 @@
printf("%s\n", "------");
- /* Print the total line */
+ // Print the total line
printf("%5s ", "");
+
+ if (oomadj) {
+ printf("%5s ", "");
+ }
+
if (ws) {
printf("%7s %6" PRIu64 "K %6" PRIu64 "K ",
"", total_pss / 1024, total_uss / 1024);
@@ -466,67 +515,67 @@
" -k Only show pages collapsed by KSM\n"
" -w Display statistics for working set only.\n"
" -W Reset working set of all processes.\n"
+ " -o Show and sort by oom score against lowmemorykiller thresholds.\n"
" -h Display this help screen.\n",
myname);
}
-/*
- * Get the process name for a given PID. Inserts the process name into buffer
- * buf of length len. The size of the buffer must be greater than zero to get
- * any useful output.
- *
- * Note that fgets(3) only declares length as an int, so our buffer size is
- * also declared as an int.
- *
- * Returns 0 on success, a positive value on partial success, and -1 on
- * failure. Other interesting values:
- * 1 on failure to create string to examine proc cmdline entry
- * 2 on failure to open proc cmdline entry
- * 3 on failure to read proc cmdline entry
- */
-static int getprocname(pid_t pid, char *buf, int len) {
- char *filename;
- FILE *f;
- int rc = 0;
- static const char* unknown_cmdline = "<unknown>";
+// Get the process name for a given PID.
+static std::string getprocname(pid_t pid) {
+ std::string filename = android::base::StringPrintf("/proc/%d/cmdline", pid);
- if (len <= 0) {
- return -1;
+ std::string procname;
+
+ if (!android::base::ReadFileToString(filename, &procname)) {
+ // The process went away before we could read its process name.
+ procname = "<unknown>";
}
- if (asprintf(&filename, "/proc/%d/cmdline", pid) < 0) {
- rc = 1;
- goto exit;
+ return procname;
+}
+
+static int getoomadj(pid_t pid) {
+ std::string filename = android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
+ std::string oomadj;
+
+ if (!android::base::ReadFileToString(filename, &oomadj)) {
+ return -1001;
}
- f = fopen(filename, "r");
- if (f == NULL) {
- rc = 2;
- goto releasefilename;
+ return strtol(oomadj.c_str(), NULL, 10);
+}
+
+static bool getminfree(std::vector<uint64_t>* minfree, std::vector<int>* adj) {
+ std::string minfree_str;
+ std::string adj_str;
+
+ if (!android::base::ReadFileToString("/sys/module/lowmemorykiller/parameters/minfree", &minfree_str)) {
+ return false;
}
- if (fgets(buf, len, f) == NULL) {
- rc = 3;
- goto closefile;
+ if (!android::base::ReadFileToString("/sys/module/lowmemorykiller/parameters/adj", &adj_str)) {
+ return false;
}
-closefile:
- (void) fclose(f);
-releasefilename:
- free(filename);
-exit:
- if (rc != 0) {
- /*
- * The process went away before we could read its process name. Try
- * to give the user "<unknown>" here, but otherwise they get to look
- * at a blank.
- */
- if (strlcpy(buf, unknown_cmdline, (size_t)len) >= (size_t)len) {
- rc = 4;
- }
- }
+ std::vector<std::string> minfree_vec = android::base::Split(minfree_str, ",");
+ std::vector<std::string> adj_vec = android::base::Split(adj_str, ",");
- return rc;
+ minfree->clear();
+ minfree->resize(minfree_vec.size());
+ adj->clear();
+ adj->resize(adj_vec.size());
+
+ std::transform(minfree_vec.begin(), minfree_vec.end(), minfree->begin(),
+ [](const std::string& s) -> uint64_t {
+ return strtoull(s.c_str(), NULL, 10) * PAGE_SIZE;
+ });
+
+ std::transform(adj_vec.begin(), adj_vec.end(), adj->begin(),
+ [](const std::string& s) -> int {
+ return strtol(s.c_str(), NULL, 10);
+ });
+
+ return true;
}
static int numcmp(uint64_t a, uint64_t b) {
@@ -535,11 +584,17 @@
return 0;
}
+static int snumcmp(int64_t a, int64_t b) {
+ if (a < b) return -1;
+ if (a > b) return 1;
+ return 0;
+}
+
#define create_sort(field, compfn) \
static int sort_by_ ## field (const void *a, const void *b) { \
return order * compfn( \
- (*((struct proc_info**)a))->usage.field, \
- (*((struct proc_info**)b))->usage.field \
+ ((struct proc_info*)(a))->usage.field, \
+ ((struct proc_info*)(b))->usage.field \
); \
}
@@ -548,3 +603,11 @@
create_sort(pss, numcmp)
create_sort(uss, numcmp)
create_sort(swap, numcmp)
+
+static int sort_by_oomadj (const void *a, const void *b) {
+ // Negative oomadj is higher priority, reverse the sort order
+ return -1 * order * snumcmp(
+ ((struct proc_info*)a)->oomadj,
+ ((struct proc_info*)b)->oomadj
+ );
+}
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index 9eb4be8..045aea7 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -30,24 +30,31 @@
LLVM_ROOT_PATH := external/llvm
include $(LLVM_ROOT_PATH)/llvm.mk
-simpleperf_shared_libraries_target := \
- libbacktrace \
- libunwind \
- libbase \
- liblog \
- libutils \
- libLLVM \
-
simpleperf_static_libraries_target := \
libbacktrace_offline \
- liblzma \
+ libbacktrace \
+ libunwind \
libziparchive \
libz \
+ libbase \
+ libcutils \
+ liblog \
+ libutils \
+ liblzma \
+ libLLVMObject \
+ libLLVMBitReader \
+ libLLVMMC \
+ libLLVMMCParser \
+ libLLVMCore \
+ libLLVMSupport \
+ libprotobuf-cpp-lite \
+ libc \
simpleperf_static_libraries_host := \
libziparchive-host \
libbase \
liblog \
+ liblzma \
libz \
libutils \
libLLVMObject \
@@ -56,23 +63,24 @@
libLLVMMCParser \
libLLVMCore \
libLLVMSupport \
+ libprotobuf-cpp-lite \
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_kmem.cpp \
cmd_report.cpp \
+ cmd_report_sample.cpp \
command.cpp \
dso.cpp \
event_attr.cpp \
@@ -82,8 +90,9 @@
read_elf.cpp \
record.cpp \
record_file_reader.cpp \
- sample_tree.cpp \
+ report_sample.proto \
thread_tree.cpp \
+ tracing.cpp \
utils.cpp \
libsimpleperf_src_files_linux := \
@@ -115,8 +124,8 @@
$(libsimpleperf_src_files_linux) \
LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_target)
-LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
-LOCAL_MULTILIB := first
+LOCAL_MULTILIB := both
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
include $(LLVM_DEVICE_BUILD_MK)
include $(BUILD_STATIC_LIBRARY)
@@ -137,6 +146,8 @@
LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
LOCAL_MULTILIB := first
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
+LOCAL_CXX_STL := libc++_static
include $(LLVM_HOST_BUILD_MK)
include $(BUILD_HOST_STATIC_LIBRARY)
@@ -153,10 +164,20 @@
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
+ifdef TARGET_2ND_ARCH
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := simpleperf32
+LOCAL_MODULE_STEM_64 := simpleperf
+endif
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(LLVM_DEVICE_BUILD_MK)
include $(BUILD_EXECUTABLE)
+$(call dist-for-goals,sdk,$(ALL_MODULES.simpleperf.BUILT))
+ifdef TARGET_2ND_ARCH
+$(call dist-for-goals,sdk,$(ALL_MODULES.simpleperf$(TARGET_2ND_ARCH_MODULE_SUFFIX).BUILT))
+endif
+
# simpleperf host
include $(CLEAR_VARS)
LOCAL_MODULE := simpleperf
@@ -170,20 +191,26 @@
LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
LOCAL_MULTILIB := first
+LOCAL_CXX_STL := libc++_static
include $(LLVM_HOST_BUILD_MK)
include $(BUILD_HOST_EXECUTABLE)
+$(call dist-for-goals,sdk,$(LOCAL_BUILT_MODULE):simpleperf_host)
+$(call dist-for-goals,win_sdk,$(ALL_MODULES.host_cross_simpleperf.BUILT))
# simpleperf_unit_test
# =========================================================
simpleperf_unit_test_src_files := \
+ cmd_kmem_test.cpp \
cmd_report_test.cpp \
+ cmd_report_sample_test.cpp \
command_test.cpp \
gtest_main.cpp \
read_apk_test.cpp \
read_elf_test.cpp \
record_test.cpp \
sample_tree_test.cpp \
+ utils_test.cpp \
simpleperf_unit_test_src_files_linux := \
cmd_dumprecord_test.cpp \
@@ -204,8 +231,15 @@
$(simpleperf_unit_test_src_files_linux) \
LOCAL_STATIC_LIBRARIES += libsimpleperf $(simpleperf_static_libraries_target)
-LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
+LOCAL_POST_LINK_CMD = \
+ TMP_FILE=`mktemp $(OUT_DIR)/simpleperf-post-link-XXXXXXXXXX` && \
+ (cd $(LOCAL_PATH)/testdata && zip - -0 -r .) > $$TMP_FILE && \
+ $($(LOCAL_2ND_ARCH_VAR_PREFIX)TARGET_OBJCOPY) --add-section .testzipdata=$$TMP_FILE $(linked_module) && \
+ rm -f $$TMP_FILE
+
LOCAL_MULTILIB := first
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(LLVM_DEVICE_BUILD_MK)
include $(BUILD_NATIVE_TEST)
# simpleperf_unit_test host
@@ -238,8 +272,9 @@
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
+LOCAL_MULTILIB := both
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(LLVM_DEVICE_BUILD_MK)
include $(BUILD_NATIVE_TEST)
# simpleperf_cpu_hotplug_test linux host
@@ -270,11 +305,11 @@
include $(CLEAR_VARS)
LOCAL_CLANG := true
LOCAL_MODULE := libsimpleperf_cts_test
-LOCAL_CPPFLAGS := $(simpleperf_cppflags_target) -DIN_CTS_TEST
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
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
+LOCAL_FORCE_STATIC_EXECUTABLE := true
include $(LLVM_DEVICE_BUILD_MK)
include $(BUILD_STATIC_TEST_LIBRARY)
@@ -283,7 +318,7 @@
LOCAL_CLANG := true
LOCAL_MODULE := libsimpleperf_cts_test
LOCAL_MODULE_HOST_OS := linux
-LOCAL_CPPFLAGS := $(simpleperf_cppflags_host) -DIN_CTS_TEST
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
LOCAL_SRC_FILES := $(libsimpleperf_cts_test_src_files)
LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_host)
diff --git a/simpleperf/SampleComparator.h b/simpleperf/SampleComparator.h
new file mode 100644
index 0000000..8f9b2e4
--- /dev/null
+++ b/simpleperf/SampleComparator.h
@@ -0,0 +1,104 @@
+/*
+ * 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_SAMPLE_COMPARATOR_H_
+#define SIMPLE_PERF_SAMPLE_COMPARATOR_H_
+
+#include <string.h>
+
+#include <vector>
+
+// The compare functions below are used to compare two samples by their item
+// content.
+
+template <typename T>
+int Compare(const T& a, const T& b) {
+ if (a != b) {
+ return a < b ? -1 : 1;
+ }
+ return 0;
+}
+
+#define BUILD_COMPARE_VALUE_FUNCTION(function_name, compare_part) \
+ template <typename EntryT> \
+ int function_name(const EntryT* sample1, const EntryT* sample2) { \
+ return Compare(sample1->compare_part, sample2->compare_part); \
+ }
+
+#define BUILD_COMPARE_VALUE_FUNCTION_REVERSE(function_name, compare_part) \
+ template <typename EntryT> \
+ int function_name(const EntryT* sample1, const EntryT* sample2) { \
+ return Compare(sample2->compare_part, sample1->compare_part); \
+ }
+
+#define BUILD_COMPARE_STRING_FUNCTION(function_name, compare_part) \
+ template <typename EntryT> \
+ int function_name(const EntryT* sample1, const EntryT* sample2) { \
+ return strcmp(sample1->compare_part, sample2->compare_part); \
+ }
+
+BUILD_COMPARE_VALUE_FUNCTION(ComparePid, thread->pid);
+BUILD_COMPARE_VALUE_FUNCTION(CompareTid, thread->tid);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareSampleCount, sample_count);
+BUILD_COMPARE_STRING_FUNCTION(CompareComm, thread_comm);
+BUILD_COMPARE_STRING_FUNCTION(CompareDso, map->dso->Path().c_str());
+BUILD_COMPARE_STRING_FUNCTION(CompareSymbol, symbol->DemangledName());
+BUILD_COMPARE_STRING_FUNCTION(CompareDsoFrom,
+ branch_from.map->dso->Path().c_str());
+BUILD_COMPARE_STRING_FUNCTION(CompareSymbolFrom,
+ branch_from.symbol->DemangledName());
+
+template <typename EntryT>
+int CompareTotalPeriod(const EntryT* sample1, const EntryT* sample2) {
+ uint64_t period1 = sample1->period + sample1->accumulated_period;
+ uint64_t period2 = sample2->period + sample2->accumulated_period;
+ return Compare(period2, period1);
+}
+
+// SampleComparator is a class using a collection of compare functions to
+// compare two samples.
+
+template <typename EntryT>
+class SampleComparator {
+ public:
+ typedef int (*compare_sample_func_t)(const EntryT*, const EntryT*);
+
+ void AddCompareFunction(compare_sample_func_t func) {
+ compare_v_.push_back(func);
+ }
+
+ void AddComparator(const SampleComparator<EntryT>& other) {
+ compare_v_.insert(compare_v_.end(), other.compare_v_.begin(),
+ other.compare_v_.end());
+ }
+
+ bool operator()(const EntryT* sample1, const EntryT* sample2) {
+ for (const auto& func : compare_v_) {
+ int ret = func(sample1, sample2);
+ if (ret != 0) {
+ return ret < 0;
+ }
+ }
+ return false;
+ }
+
+ bool empty() const { return compare_v_.empty(); }
+
+ private:
+ std::vector<compare_sample_func_t> compare_v_;
+};
+
+#endif // SIMPLE_PERF_SAMPLE_COMPARATOR_H_
diff --git a/simpleperf/SampleDisplayer.h b/simpleperf/SampleDisplayer.h
new file mode 100644
index 0000000..168c9e2
--- /dev/null
+++ b/simpleperf/SampleDisplayer.h
@@ -0,0 +1,229 @@
+/*
+ * 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_SAMPLE_DISPLAYER_H_
+#define SIMPLE_PERF_SAMPLE_DISPLAYER_H_
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+// The display functions below are used to show items in a sample.
+
+template <typename EntryT, typename InfoT>
+std::string DisplayAccumulatedOverhead(const EntryT* sample,
+ const InfoT* info) {
+ uint64_t period = sample->period + sample->accumulated_period;
+ uint64_t total_period = info->total_period;
+ double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
+ return android::base::StringPrintf("%.2f%%", percentage);
+}
+
+template <typename EntryT, typename InfoT>
+std::string DisplaySelfOverhead(const EntryT* sample, const InfoT* info) {
+ uint64_t period = sample->period;
+ uint64_t total_period = info->total_period;
+ double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
+ return android::base::StringPrintf("%.2f%%", percentage);
+}
+
+#define BUILD_DISPLAY_UINT64_FUNCTION(function_name, display_part) \
+ template <typename EntryT> \
+ std::string function_name(const EntryT* sample) { \
+ return android::base::StringPrintf("%" PRIu64, sample->display_part); \
+ }
+
+#define BUILD_DISPLAY_HEX64_FUNCTION(function_name, display_part) \
+ template <typename EntryT> \
+ std::string function_name(const EntryT* sample) { \
+ return android::base::StringPrintf("0x%" PRIx64, sample->display_part);\
+}
+
+BUILD_DISPLAY_UINT64_FUNCTION(DisplaySampleCount, sample_count);
+
+template <typename EntryT>
+std::string DisplayPid(const EntryT* sample) {
+ return android::base::StringPrintf("%d", sample->thread->pid);
+}
+
+template <typename EntryT>
+std::string DisplayTid(const EntryT* sample) {
+ return android::base::StringPrintf("%d", sample->thread->tid);
+}
+
+template <typename EntryT>
+std::string DisplayComm(const EntryT* sample) {
+ return sample->thread_comm;
+}
+
+template <typename EntryT>
+std::string DisplayDso(const EntryT* sample) {
+ return sample->map->dso->Path();
+}
+
+template <typename EntryT>
+std::string DisplaySymbol(const EntryT* sample) {
+ return sample->symbol->DemangledName();
+}
+
+template <typename EntryT>
+std::string DisplayDsoFrom(const EntryT* sample) {
+ return sample->branch_from.map->dso->Path();
+}
+
+template <typename EntryT>
+std::string DisplaySymbolFrom(const EntryT* sample) {
+ return sample->branch_from.symbol->DemangledName();
+}
+
+template <typename CallChainNodeT>
+void DisplayCallGraphEntry(FILE* fp, size_t depth, std::string prefix,
+ const std::unique_ptr<CallChainNodeT>& node,
+ uint64_t parent_period, bool last) {
+ if (depth > 20) {
+ LOG(WARNING) << "truncated callgraph at depth " << depth;
+ return;
+ }
+ prefix += "|";
+ fprintf(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("--%.2f%%-- ", percentage);
+ }
+ fprintf(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(fp, "%s%s\n", prefix.c_str(),
+ node->chain[i]->symbol->DemangledName());
+ }
+ for (size_t i = 0; i < node->children.size(); ++i) {
+ DisplayCallGraphEntry(fp, depth + 1, prefix, node->children[i],
+ node->children_period,
+ (i + 1 == node->children.size()));
+ }
+}
+
+template <typename EntryT>
+void DisplayCallgraph(FILE* fp, const EntryT* sample) {
+ std::string prefix = " ";
+ fprintf(fp, "%s|\n", prefix.c_str());
+ fprintf(fp, "%s-- %s\n", prefix.c_str(), sample->symbol->DemangledName());
+ prefix.append(3, ' ');
+ for (size_t i = 0; i < sample->callchain.children.size(); ++i) {
+ DisplayCallGraphEntry(fp, 1, prefix, sample->callchain.children[i],
+ sample->callchain.children_period,
+ (i + 1 == sample->callchain.children.size()));
+ }
+}
+
+// SampleDisplayer is a class using a collections of display functions to show a
+// sample.
+
+template <typename EntryT, typename InfoT>
+class SampleDisplayer {
+ public:
+ typedef std::string (*display_sample_func_t)(const EntryT*);
+ typedef std::string (*display_sample_with_info_func_t)(const EntryT*,
+ const InfoT*);
+ typedef void (*exclusive_display_sample_func_t)(FILE* fp, const EntryT*);
+
+ private:
+ struct Item {
+ std::string name;
+ size_t width;
+ display_sample_func_t func;
+ display_sample_with_info_func_t func_with_info;
+ };
+
+ public:
+ void SetInfo(const InfoT* info) { info_ = info; }
+
+ void AddDisplayFunction(const std::string& name, display_sample_func_t func) {
+ Item item;
+ item.name = name;
+ item.width = name.size();
+ item.func = func;
+ item.func_with_info = nullptr;
+ display_v_.push_back(item);
+ }
+
+ void AddDisplayFunction(const std::string& name,
+ display_sample_with_info_func_t func_with_info) {
+ Item item;
+ item.name = name;
+ item.width = name.size();
+ item.func = nullptr;
+ item.func_with_info = func_with_info;
+ display_v_.push_back(item);
+ }
+
+ void AddExclusiveDisplayFunction(exclusive_display_sample_func_t func) {
+ exclusive_display_v_.push_back(func);
+ }
+
+ void AdjustWidth(const EntryT* sample) {
+ for (auto& item : display_v_) {
+ std::string data = (item.func != nullptr)
+ ? item.func(sample)
+ : item.func_with_info(sample, info_);
+ item.width = std::max(item.width, data.size());
+ }
+ }
+
+ void PrintNames(FILE* fp) {
+ for (size_t i = 0; i < display_v_.size(); ++i) {
+ auto& item = display_v_[i];
+ if (i != display_v_.size() - 1) {
+ fprintf(fp, "%-*s ", static_cast<int>(item.width), item.name.c_str());
+ } else {
+ fprintf(fp, "%s\n", item.name.c_str());
+ }
+ }
+ }
+
+ void PrintSample(FILE* fp, const EntryT* sample) {
+ for (size_t i = 0; i < display_v_.size(); ++i) {
+ auto& item = display_v_[i];
+ std::string data = (item.func != nullptr)
+ ? item.func(sample)
+ : item.func_with_info(sample, info_);
+ if (i != display_v_.size() - 1) {
+ fprintf(fp, "%-*s ", static_cast<int>(item.width), data.c_str());
+ } else {
+ fprintf(fp, "%s\n", data.c_str());
+ }
+ }
+ for (auto& func : exclusive_display_v_) {
+ func(fp, sample);
+ }
+ }
+
+ private:
+ const InfoT* info_;
+ std::vector<Item> display_v_;
+ std::vector<exclusive_display_sample_func_t> exclusive_display_v_;
+};
+
+#endif // SIMPLE_PERF_SAMPLE_DISPLAYER_H_
diff --git a/simpleperf/build_id.h b/simpleperf/build_id.h
index bbd13c4..9f360bd 100644
--- a/simpleperf/build_id.h
+++ b/simpleperf/build_id.h
@@ -23,6 +23,10 @@
constexpr size_t BUILD_ID_SIZE = 20;
+// Shared libraries can have a section called .note.gnu.build-id, containing
+// a ~20 bytes unique id. Build id is used to compare if two shared libraries
+// are actually the same. BuildId class is the representation of build id in
+// memory.
class BuildId {
public:
static size_t Size() {
diff --git a/simpleperf/callchain.cpp b/simpleperf/callchain.cpp
deleted file mode 100644
index 8fdafc4..0000000
--- a/simpleperf/callchain.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "callchain.h"
-
-#include <string.h>
-
-#include <queue>
-
-#include <android-base/logging.h>
-#include "sample_tree.h"
-
-static bool MatchSampleByName(const SampleEntry* sample1, const SampleEntry* sample2) {
- return strcmp(sample1->symbol->Name(), sample2->symbol->Name()) == 0;
-}
-
-static size_t GetMatchingLengthInNode(const CallChainNode* node,
- const std::vector<SampleEntry*>& chain, size_t chain_start) {
- size_t i, j;
- for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size(); ++i, ++j) {
- if (!MatchSampleByName(node->chain[i], chain[j])) {
- break;
- }
- }
- return i;
-}
-
-static CallChainNode* FindMatchingNode(const std::vector<std::unique_ptr<CallChainNode>>& nodes,
- const SampleEntry* sample) {
- for (auto& node : nodes) {
- if (MatchSampleByName(node->chain.front(), sample)) {
- return node.get();
- }
- }
- return nullptr;
-}
-
-static std::unique_ptr<CallChainNode> AllocateNode(const std::vector<SampleEntry*>& chain,
- size_t chain_start, uint64_t period,
- uint64_t children_period) {
- std::unique_ptr<CallChainNode> node(new CallChainNode);
- for (size_t i = chain_start; i < chain.size(); ++i) {
- node->chain.push_back(chain[i]);
- }
- node->period = period;
- node->children_period = children_period;
- return node;
-}
-
-static void SplitNode(CallChainNode* parent, size_t parent_length) {
- std::unique_ptr<CallChainNode> child =
- AllocateNode(parent->chain, parent_length, parent->period, parent->children_period);
- child->children = std::move(parent->children);
- parent->period = 0;
- parent->children_period = child->period + child->children_period;
- parent->chain.resize(parent_length);
- parent->children.clear();
- parent->children.push_back(std::move(child));
-}
-
-void CallChainRoot::AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period) {
- children_period += period;
- CallChainNode* p = FindMatchingNode(children, callchain[0]);
- if (p == nullptr) {
- std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, 0, period, 0);
- children.push_back(std::move(new_node));
- return;
- }
- size_t callchain_pos = 0;
- while (true) {
- size_t match_length = GetMatchingLengthInNode(p, callchain, callchain_pos);
- CHECK_GT(match_length, 0u);
- callchain_pos += match_length;
- bool find_child = true;
- if (match_length < p->chain.size()) {
- SplitNode(p, match_length);
- find_child = false; // No need to find matching node in p->children.
- }
- if (callchain_pos == callchain.size()) {
- p->period += period;
- return;
- }
- p->children_period += period;
- if (find_child) {
- CallChainNode* np = FindMatchingNode(p->children, callchain[callchain_pos]);
- if (np != nullptr) {
- p = np;
- continue;
- }
- }
- std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, callchain_pos, period, 0);
- p->children.push_back(std::move(new_node));
- break;
- }
-}
-
-static bool CompareNodeByPeriod(const std::unique_ptr<CallChainNode>& n1,
- const std::unique_ptr<CallChainNode>& n2) {
- uint64_t period1 = n1->period + n1->children_period;
- uint64_t period2 = n2->period + n2->children_period;
- return period1 > period2;
-}
-
-void CallChainRoot::SortByPeriod() {
- std::queue<std::vector<std::unique_ptr<CallChainNode>>*> queue;
- queue.push(&children);
- while (!queue.empty()) {
- std::vector<std::unique_ptr<CallChainNode>>* v = queue.front();
- queue.pop();
- std::sort(v->begin(), v->end(), CompareNodeByPeriod);
- for (auto& node : *v) {
- queue.push(&node->children);
- }
- }
-}
diff --git a/simpleperf/callchain.h b/simpleperf/callchain.h
index 4b8a9d4..54820b1 100644
--- a/simpleperf/callchain.h
+++ b/simpleperf/callchain.h
@@ -17,27 +17,140 @@
#ifndef SIMPLE_PERF_CALLCHAIN_H_
#define SIMPLE_PERF_CALLCHAIN_H_
+#include <string.h>
+
+#include <algorithm>
#include <memory>
+#include <queue>
#include <vector>
-struct SampleEntry;
+#include <android-base/logging.h>
+template <typename EntryT>
struct CallChainNode {
uint64_t period;
uint64_t children_period;
- std::vector<SampleEntry*> chain;
+ std::vector<EntryT*> chain;
std::vector<std::unique_ptr<CallChainNode>> children;
};
+template <typename EntryT>
struct CallChainRoot {
+ typedef CallChainNode<EntryT> NodeT;
uint64_t children_period;
- std::vector<std::unique_ptr<CallChainNode>> children;
+ std::vector<std::unique_ptr<NodeT>> children;
- CallChainRoot() : children_period(0) {
+ CallChainRoot() : children_period(0) {}
+
+ void AddCallChain(const std::vector<EntryT*>& callchain, uint64_t period) {
+ children_period += period;
+ NodeT* p = FindMatchingNode(children, callchain[0]);
+ if (p == nullptr) {
+ std::unique_ptr<NodeT> 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) {
+ NodeT* np = FindMatchingNode(p->children, callchain[callchain_pos]);
+ if (np != nullptr) {
+ p = np;
+ continue;
+ }
+ }
+ std::unique_ptr<NodeT> new_node =
+ AllocateNode(callchain, callchain_pos, period, 0);
+ p->children.push_back(std::move(new_node));
+ break;
+ }
}
- void AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period);
- void SortByPeriod();
+ void SortByPeriod() {
+ std::queue<std::vector<std::unique_ptr<NodeT>>*> queue;
+ queue.push(&children);
+ while (!queue.empty()) {
+ std::vector<std::unique_ptr<NodeT>>* v = queue.front();
+ queue.pop();
+ std::sort(v->begin(), v->end(), CallChainRoot::CompareNodeByPeriod);
+ for (auto& node : *v) {
+ if (!node->children.empty()) {
+ queue.push(&node->children);
+ }
+ }
+ }
+ }
+
+ private:
+ NodeT* FindMatchingNode(const std::vector<std::unique_ptr<NodeT>>& nodes,
+ const EntryT* sample) {
+ for (auto& node : nodes) {
+ if (MatchSampleByName(node->chain.front(), sample)) {
+ return node.get();
+ }
+ }
+ return nullptr;
+ }
+
+ size_t GetMatchingLengthInNode(NodeT* node, const std::vector<EntryT*>& 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;
+ }
+
+ bool MatchSampleByName(const EntryT* sample1, const EntryT* sample2) {
+ return strcmp(sample1->symbol->Name(), sample2->symbol->Name()) == 0;
+ }
+
+ void SplitNode(NodeT* parent, size_t parent_length) {
+ std::unique_ptr<NodeT> 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));
+ }
+
+ std::unique_ptr<NodeT> AllocateNode(const std::vector<EntryT*>& chain,
+ size_t chain_start, uint64_t period,
+ uint64_t children_period) {
+ std::unique_ptr<NodeT> node(new NodeT);
+ 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 bool CompareNodeByPeriod(const std::unique_ptr<NodeT>& n1,
+ const std::unique_ptr<NodeT>& n2) {
+ uint64_t period1 = n1->period + n1->children_period;
+ uint64_t period2 = n2->period + n2->children_period;
+ return period1 > period2;
+ }
};
#endif // SIMPLE_PERF_CALLCHAIN_H_
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index f19cfd3..3e89b82 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -157,20 +157,14 @@
}
void DumpRecordCommand::DumpAttrSection() {
- const std::vector<FileAttr>& attrs = record_file_reader_->AttrSection();
+ std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
for (size_t i = 0; i < attrs.size(); ++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;
- if (!record_file_reader_->ReadIdsForAttr(attr, &ids)) {
- return;
- }
- if (!ids.empty()) {
+ printf("attr %zu:\n", i + 1);
+ DumpPerfEventAttr(*attr.attr, 1);
+ if (!attr.ids.empty()) {
printf(" ids:");
- for (const auto& id : ids) {
+ for (const auto& id : attr.ids) {
printf(" %" PRId64, id);
}
printf("\n");
diff --git a/simpleperf/cmd_help.cpp b/simpleperf/cmd_help.cpp
index 0bb6231..d6a97b4 100644
--- a/simpleperf/cmd_help.cpp
+++ b/simpleperf/cmd_help.cpp
@@ -26,10 +26,12 @@
public:
HelpCommand()
: Command("help", "print help information for simpleperf",
- "Usage: simpleperf help [subcommand]\n"
- " Without subcommand, print short help string for every subcommand.\n"
- " With subcommand, print long help string for the subcommand.\n\n") {
- }
+ // clang-format off
+"Usage: simpleperf help [subcommand]\n"
+" Without subcommand, print short help string for every subcommand.\n"
+" With subcommand, print long help string for the subcommand.\n\n"
+ // clang-format on
+ ) {}
bool Run(const std::vector<std::string>& args) override;
@@ -44,7 +46,9 @@
} else {
std::unique_ptr<Command> cmd = CreateCommandInstance(args[0]);
if (cmd == nullptr) {
- LOG(ERROR) << "malformed command line: can't find help string for unknown command " << args[0];
+ LOG(ERROR) << "malformed command line: can't find help string for "
+ "unknown command "
+ << args[0];
LOG(ERROR) << "try using \"--help\"";
return false;
} else {
@@ -56,13 +60,16 @@
void HelpCommand::PrintShortHelp() {
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");
+ // clang-format off
+"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, info, error, fatal.\n"
+" Default is info.\n"
+ "subcommands:\n"
+ // clang-format on
+ );
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());
@@ -74,5 +81,6 @@
}
void RegisterHelpCommand() {
- RegisterCommand("help", [] { return std::unique_ptr<Command>(new HelpCommand); });
+ RegisterCommand("help",
+ [] { return std::unique_ptr<Command>(new HelpCommand); });
}
diff --git a/simpleperf/cmd_kmem.cpp b/simpleperf/cmd_kmem.cpp
new file mode 100644
index 0000000..3c3023f
--- /dev/null
+++ b/simpleperf/cmd_kmem.cpp
@@ -0,0 +1,711 @@
+/*
+ * 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 "command.h"
+
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "callchain.h"
+#include "event_attr.h"
+#include "event_type.h"
+#include "record_file.h"
+#include "sample_tree.h"
+#include "tracing.h"
+#include "utils.h"
+
+namespace {
+
+struct SlabSample {
+ const Symbol* symbol; // the function making allocation
+ uint64_t ptr; // the start address of the allocated space
+ uint64_t bytes_req; // requested space size
+ uint64_t bytes_alloc; // allocated space size
+ uint64_t sample_count; // count of allocations
+ uint64_t gfp_flags; // flags used for allocation
+ uint64_t cross_cpu_allocations; // count of allocations freed not on the
+ // cpu allocating them
+ CallChainRoot<SlabSample> callchain; // a callchain tree representing all
+ // callchains in this sample
+ SlabSample(const Symbol* symbol, uint64_t ptr, uint64_t bytes_req,
+ uint64_t bytes_alloc, uint64_t sample_count, uint64_t gfp_flags,
+ uint64_t cross_cpu_allocations)
+ : symbol(symbol),
+ ptr(ptr),
+ bytes_req(bytes_req),
+ bytes_alloc(bytes_alloc),
+ sample_count(sample_count),
+ gfp_flags(gfp_flags),
+ cross_cpu_allocations(cross_cpu_allocations) {}
+};
+
+struct SlabAccumulateInfo {
+ uint64_t bytes_req;
+ uint64_t bytes_alloc;
+};
+
+BUILD_COMPARE_VALUE_FUNCTION(ComparePtr, ptr);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesReq, bytes_req);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesAlloc, bytes_alloc);
+BUILD_COMPARE_VALUE_FUNCTION(CompareGfpFlags, gfp_flags);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareCrossCpuAllocations,
+ cross_cpu_allocations);
+
+BUILD_DISPLAY_HEX64_FUNCTION(DisplayPtr, ptr);
+BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesReq, bytes_req);
+BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesAlloc, bytes_alloc);
+BUILD_DISPLAY_HEX64_FUNCTION(DisplayGfpFlags, gfp_flags);
+BUILD_DISPLAY_UINT64_FUNCTION(DisplayCrossCpuAllocations,
+ cross_cpu_allocations);
+
+static int CompareFragment(const SlabSample* sample1,
+ const SlabSample* sample2) {
+ uint64_t frag1 = sample1->bytes_alloc - sample1->bytes_req;
+ uint64_t frag2 = sample2->bytes_alloc - sample2->bytes_req;
+ return Compare(frag2, frag1);
+}
+
+static std::string DisplayFragment(const SlabSample* sample) {
+ return android::base::StringPrintf("%" PRIu64,
+ sample->bytes_alloc - sample->bytes_req);
+}
+
+struct SlabSampleTree {
+ std::vector<SlabSample*> samples;
+ uint64_t total_requested_bytes;
+ uint64_t total_allocated_bytes;
+ uint64_t nr_allocations;
+ uint64_t nr_frees;
+ uint64_t nr_cross_cpu_allocations;
+};
+
+struct SlabFormat {
+ enum {
+ KMEM_ALLOC,
+ KMEM_FREE,
+ } type;
+ TracingFieldPlace call_site;
+ TracingFieldPlace ptr;
+ TracingFieldPlace bytes_req;
+ TracingFieldPlace bytes_alloc;
+ TracingFieldPlace gfp_flags;
+};
+
+class SlabSampleTreeBuilder
+ : public SampleTreeBuilder<SlabSample, SlabAccumulateInfo> {
+ public:
+ SlabSampleTreeBuilder(SampleComparator<SlabSample> sample_comparator,
+ ThreadTree* thread_tree)
+ : SampleTreeBuilder(sample_comparator),
+ thread_tree_(thread_tree),
+ total_requested_bytes_(0),
+ total_allocated_bytes_(0),
+ nr_allocations_(0),
+ nr_cross_cpu_allocations_(0) {}
+
+ SlabSampleTree GetSampleTree() const {
+ SlabSampleTree sample_tree;
+ sample_tree.samples = GetSamples();
+ sample_tree.total_requested_bytes = total_requested_bytes_;
+ sample_tree.total_allocated_bytes = total_allocated_bytes_;
+ sample_tree.nr_allocations = nr_allocations_;
+ sample_tree.nr_frees = nr_frees_;
+ sample_tree.nr_cross_cpu_allocations = nr_cross_cpu_allocations_;
+ return sample_tree;
+ }
+
+ void AddSlabFormat(const std::vector<uint64_t>& event_ids,
+ SlabFormat format) {
+ std::unique_ptr<SlabFormat> p(new SlabFormat(format));
+ for (auto id : event_ids) {
+ event_id_to_format_map_[id] = p.get();
+ }
+ formats_.push_back(std::move(p));
+ }
+
+ protected:
+ SlabSample* CreateSample(const SampleRecord& r, bool in_kernel,
+ SlabAccumulateInfo* acc_info) override {
+ if (!in_kernel) {
+ // Normally we don't parse records in user space because tracepoint
+ // events all happen in kernel. But if r.ip_data.ip == 0, it may be
+ // a kernel record failed to dump ip register and is still useful.
+ if (r.ip_data.ip == 0) {
+ // It seems we are on a kernel can't dump regset for tracepoint events
+ // because of lacking perf_arch_fetch_caller_regs(). We can't get
+ // callchain, but we can still do a normal report.
+ static bool first = true;
+ if (first) {
+ first = false;
+ if (accumulate_callchain_) {
+ // The kernel doesn't seem to support dumping registers for
+ // tracepoint events because of lacking
+ // perf_arch_fetch_caller_regs().
+ LOG(WARNING) << "simpleperf may not get callchains for tracepoint"
+ << " events because of lacking kernel support.";
+ }
+ }
+ } else {
+ return nullptr;
+ }
+ }
+ uint64_t id = r.id_data.id;
+ auto it = event_id_to_format_map_.find(id);
+ if (it == event_id_to_format_map_.end()) {
+ return nullptr;
+ }
+ const char* raw_data = r.raw_data.data.data();
+ SlabFormat* format = it->second;
+ if (format->type == SlabFormat::KMEM_ALLOC) {
+ uint64_t call_site = format->call_site.ReadFromData(raw_data);
+ const Symbol* symbol = thread_tree_->FindKernelSymbol(call_site);
+ uint64_t ptr = format->ptr.ReadFromData(raw_data);
+ uint64_t bytes_req = format->bytes_req.ReadFromData(raw_data);
+ uint64_t bytes_alloc = format->bytes_alloc.ReadFromData(raw_data);
+ uint64_t gfp_flags = format->gfp_flags.ReadFromData(raw_data);
+ SlabSample* sample =
+ InsertSample(std::unique_ptr<SlabSample>(new SlabSample(
+ symbol, ptr, bytes_req, bytes_alloc, 1, gfp_flags, 0)));
+ alloc_cpu_record_map_.insert(
+ std::make_pair(ptr, std::make_pair(r.cpu_data.cpu, sample)));
+ acc_info->bytes_req = bytes_req;
+ acc_info->bytes_alloc = bytes_alloc;
+ return sample;
+ } else if (format->type == SlabFormat::KMEM_FREE) {
+ uint64_t ptr = format->ptr.ReadFromData(raw_data);
+ auto it = alloc_cpu_record_map_.find(ptr);
+ if (it != alloc_cpu_record_map_.end()) {
+ SlabSample* sample = it->second.second;
+ if (r.cpu_data.cpu != it->second.first) {
+ sample->cross_cpu_allocations++;
+ nr_cross_cpu_allocations_++;
+ }
+ alloc_cpu_record_map_.erase(it);
+ }
+ nr_frees_++;
+ }
+ return nullptr;
+ }
+
+ SlabSample* CreateBranchSample(const SampleRecord&,
+ const BranchStackItemType&) override {
+ return nullptr;
+ }
+
+ SlabSample* CreateCallChainSample(
+ const SlabSample* sample, uint64_t ip, bool in_kernel,
+ const std::vector<SlabSample*>& callchain,
+ const SlabAccumulateInfo& acc_info) override {
+ if (!in_kernel) {
+ return nullptr;
+ }
+ const Symbol* symbol = thread_tree_->FindKernelSymbol(ip);
+ return InsertCallChainSample(
+ std::unique_ptr<SlabSample>(
+ new SlabSample(symbol, sample->ptr, acc_info.bytes_req,
+ acc_info.bytes_alloc, 1, sample->gfp_flags, 0)),
+ callchain);
+ }
+
+ const ThreadEntry* GetThreadOfSample(SlabSample*) override { return nullptr; }
+
+ void InsertCallChainForSample(SlabSample* sample,
+ const std::vector<SlabSample*>& callchain,
+ const SlabAccumulateInfo&) override {
+ // Decide the percentage of callchain by the sample_count, so use 1 as the
+ // period when calling AddCallChain().
+ sample->callchain.AddCallChain(callchain, 1);
+ }
+
+ void UpdateSummary(const SlabSample* sample) override {
+ total_requested_bytes_ += sample->bytes_req;
+ total_allocated_bytes_ += sample->bytes_alloc;
+ nr_allocations_++;
+ }
+
+ void MergeSample(SlabSample* sample1, SlabSample* sample2) override {
+ sample1->bytes_req += sample2->bytes_req;
+ sample1->bytes_alloc += sample2->bytes_alloc;
+ sample1->sample_count += sample2->sample_count;
+ }
+
+ private:
+ ThreadTree* thread_tree_;
+ uint64_t total_requested_bytes_;
+ uint64_t total_allocated_bytes_;
+ uint64_t nr_allocations_;
+ uint64_t nr_frees_;
+ uint64_t nr_cross_cpu_allocations_;
+
+ std::unordered_map<uint64_t, SlabFormat*> event_id_to_format_map_;
+ std::vector<std::unique_ptr<SlabFormat>> formats_;
+ std::unordered_map<uint64_t, std::pair<uint32_t, SlabSample*>>
+ alloc_cpu_record_map_;
+};
+
+using SlabSampleTreeSorter = SampleTreeSorter<SlabSample>;
+using SlabSampleTreeDisplayer = SampleTreeDisplayer<SlabSample, SlabSampleTree>;
+
+struct EventAttrWithName {
+ perf_event_attr attr;
+ std::string name;
+ std::vector<uint64_t> event_ids;
+};
+
+class KmemCommand : public Command {
+ public:
+ KmemCommand()
+ : Command(
+ "kmem", "collect kernel memory allocation information",
+ // clang-format off
+"Usage: kmem (record [record options] | report [report options])\n"
+"kmem record\n"
+"-g Enable call graph recording. Same as '--call-graph fp'.\n"
+"--slab Collect slab allocation information. Default option.\n"
+"Other record options provided by simpleperf record command are also available.\n"
+"kmem report\n"
+"--children Print the accumulated allocation info appeared in the callchain.\n"
+" Can be used on perf.data recorded with `--call-graph fp` option.\n"
+"-g [callee|caller] Print call graph for perf.data recorded with\n"
+" `--call-graph fp` option. If callee mode is used, the graph\n"
+" shows how functions are called from others. Otherwise, the\n"
+" graph shows how functions call others. Default is callee\n"
+" mode. The percentage shown in the graph is determined by\n"
+" the hit count of the callchain.\n"
+"-i Specify path of record file, default is perf.data\n"
+"-o report_file_name Set report file name, default is stdout.\n"
+"--slab Report slab allocation information. Default option.\n"
+"--slab-sort key1,key2,...\n"
+" Select the keys to sort and print slab allocation information.\n"
+" Should be used with --slab option. Possible keys include:\n"
+" hit -- the allocation count.\n"
+" caller -- the function calling allocation.\n"
+" ptr -- the address of the allocated space.\n"
+" bytes_req -- the total requested space size.\n"
+" bytes_alloc -- the total allocated space size.\n"
+" fragment -- the extra allocated space size\n"
+" (bytes_alloc - bytes_req).\n"
+" gfp_flags -- the flags used for allocation.\n"
+" pingpong -- the count of allocations that are freed not on\n"
+" the cpu allocating them.\n"
+" The default slab sort keys are:\n"
+" hit,caller,bytes_req,bytes_alloc,fragment,pingpong.\n"
+ // clang-format on
+ ),
+ is_record_(false),
+ use_slab_(false),
+ accumulate_callchain_(false),
+ print_callgraph_(false),
+ callgraph_show_callee_(false),
+ record_filename_("perf.data"),
+ record_file_arch_(GetBuildArch()) {}
+
+ bool Run(const std::vector<std::string>& args);
+
+ private:
+ bool ParseOptions(const std::vector<std::string>& args,
+ std::vector<std::string>* left_args);
+ bool RecordKmemInfo(const std::vector<std::string>& record_args);
+ bool ReportKmemInfo();
+ bool PrepareToBuildSampleTree();
+ void ReadEventAttrsFromRecordFile();
+ bool ReadFeaturesFromRecordFile();
+ bool ReadSampleTreeFromRecordFile();
+ bool ProcessRecord(std::unique_ptr<Record> record);
+ void ProcessTracingData(const std::vector<char>& data);
+ bool PrintReport();
+ void PrintReportContext(FILE* fp);
+ void PrintSlabReportContext(FILE* fp);
+
+ bool is_record_;
+ bool use_slab_;
+ std::vector<std::string> slab_sort_keys_;
+ bool accumulate_callchain_;
+ bool print_callgraph_;
+ bool callgraph_show_callee_;
+
+ std::string record_filename_;
+ std::unique_ptr<RecordFileReader> record_file_reader_;
+ std::vector<EventAttrWithName> event_attrs_;
+ std::string record_cmdline_;
+ ArchType record_file_arch_;
+
+ ThreadTree thread_tree_;
+ SlabSampleTree slab_sample_tree_;
+ std::unique_ptr<SlabSampleTreeBuilder> slab_sample_tree_builder_;
+ std::unique_ptr<SlabSampleTreeSorter> slab_sample_tree_sorter_;
+ std::unique_ptr<SlabSampleTreeDisplayer> slab_sample_tree_displayer_;
+
+ std::string report_filename_;
+};
+
+bool KmemCommand::Run(const std::vector<std::string>& args) {
+ std::vector<std::string> left_args;
+ if (!ParseOptions(args, &left_args)) {
+ return false;
+ }
+ if (!use_slab_) {
+ use_slab_ = true;
+ }
+ if (is_record_) {
+ return RecordKmemInfo(left_args);
+ }
+ return ReportKmemInfo();
+}
+
+bool KmemCommand::ParseOptions(const std::vector<std::string>& args,
+ std::vector<std::string>* left_args) {
+ if (args.empty()) {
+ LOG(ERROR) << "No subcommand specified";
+ return false;
+ }
+ if (args[0] == "record") {
+ if (!IsRoot()) {
+ LOG(ERROR) << "simpleperf kmem record command needs root privilege";
+ return false;
+ }
+ is_record_ = true;
+ size_t i;
+ for (i = 1; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
+ if (args[i] == "-g") {
+ left_args->push_back("--call-graph");
+ left_args->push_back("fp");
+ } else if (args[i] == "--slab") {
+ use_slab_ = true;
+ } else {
+ left_args->push_back(args[i]);
+ }
+ }
+ left_args->insert(left_args->end(), args.begin() + i, args.end());
+ } else if (args[0] == "report") {
+ is_record_ = false;
+ for (size_t i = 1; i < args.size(); ++i) {
+ if (args[i] == "--children") {
+ accumulate_callchain_ = true;
+ } else if (args[i] == "-g") {
+ print_callgraph_ = true;
+ accumulate_callchain_ = true;
+ callgraph_show_callee_ = 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] == "-o") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ report_filename_ = args[i];
+ } else if (args[i] == "--slab") {
+ use_slab_ = true;
+ } else if (args[i] == "--slab-sort") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ slab_sort_keys_ = android::base::Split(args[i], ",");
+ } else {
+ ReportUnknownOption(args, i);
+ return false;
+ }
+ }
+ } else {
+ LOG(ERROR) << "Unknown subcommand for " << Name() << ": " << args[0]
+ << ". Try `simpleperf help " << Name() << "`";
+ return false;
+ }
+ return true;
+}
+
+bool KmemCommand::RecordKmemInfo(const std::vector<std::string>& record_args) {
+ std::vector<std::string> args;
+ if (use_slab_) {
+ std::vector<std::string> trace_events = {
+ "kmem:kmalloc", "kmem:kmem_cache_alloc",
+ "kmem:kmalloc_node", "kmem:kmem_cache_alloc_node",
+ "kmem:kfree", "kmem:kmem_cache_free"};
+ for (const auto& name : trace_events) {
+ if (ParseEventType(name)) {
+ args.insert(args.end(), {"-e", name});
+ }
+ }
+ }
+ if (args.empty()) {
+ LOG(ERROR) << "Kernel allocation related trace events are not supported.";
+ return false;
+ }
+ args.push_back("-a");
+ args.insert(args.end(), record_args.begin(), record_args.end());
+ std::unique_ptr<Command> record_cmd = CreateCommandInstance("record");
+ if (record_cmd == nullptr) {
+ LOG(ERROR) << "record command isn't available";
+ return false;
+ }
+ return record_cmd->Run(args);
+}
+
+bool KmemCommand::ReportKmemInfo() {
+ if (!PrepareToBuildSampleTree()) {
+ return false;
+ }
+ record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+ if (record_file_reader_ == nullptr) {
+ return false;
+ }
+ ReadEventAttrsFromRecordFile();
+ if (!ReadFeaturesFromRecordFile()) {
+ return false;
+ }
+ if (!ReadSampleTreeFromRecordFile()) {
+ return false;
+ }
+ if (!PrintReport()) {
+ return false;
+ }
+ return true;
+}
+
+bool KmemCommand::PrepareToBuildSampleTree() {
+ if (use_slab_) {
+ if (slab_sort_keys_.empty()) {
+ slab_sort_keys_ = {"hit", "caller", "bytes_req",
+ "bytes_alloc", "fragment", "pingpong"};
+ }
+ SampleComparator<SlabSample> comparator;
+ SampleComparator<SlabSample> sort_comparator;
+ SampleDisplayer<SlabSample, SlabSampleTree> displayer;
+ std::string accumulated_name = accumulate_callchain_ ? "Accumulated_" : "";
+
+ if (print_callgraph_) {
+ displayer.AddExclusiveDisplayFunction(DisplayCallgraph);
+ }
+
+ for (const auto& key : slab_sort_keys_) {
+ if (key == "hit") {
+ sort_comparator.AddCompareFunction(CompareSampleCount);
+ displayer.AddDisplayFunction(accumulated_name + "Hit",
+ DisplaySampleCount);
+ } else if (key == "caller") {
+ comparator.AddCompareFunction(CompareSymbol);
+ displayer.AddDisplayFunction("Caller", DisplaySymbol);
+ } else if (key == "ptr") {
+ comparator.AddCompareFunction(ComparePtr);
+ displayer.AddDisplayFunction("Ptr", DisplayPtr);
+ } else if (key == "bytes_req") {
+ sort_comparator.AddCompareFunction(CompareBytesReq);
+ displayer.AddDisplayFunction(accumulated_name + "BytesReq",
+ DisplayBytesReq);
+ } else if (key == "bytes_alloc") {
+ sort_comparator.AddCompareFunction(CompareBytesAlloc);
+ displayer.AddDisplayFunction(accumulated_name + "BytesAlloc",
+ DisplayBytesAlloc);
+ } else if (key == "fragment") {
+ sort_comparator.AddCompareFunction(CompareFragment);
+ displayer.AddDisplayFunction(accumulated_name + "Fragment",
+ DisplayFragment);
+ } else if (key == "gfp_flags") {
+ comparator.AddCompareFunction(CompareGfpFlags);
+ displayer.AddDisplayFunction("GfpFlags", DisplayGfpFlags);
+ } else if (key == "pingpong") {
+ sort_comparator.AddCompareFunction(CompareCrossCpuAllocations);
+ displayer.AddDisplayFunction("Pingpong", DisplayCrossCpuAllocations);
+ } else {
+ LOG(ERROR) << "Unknown sort key for slab allocation: " << key;
+ return false;
+ }
+ slab_sample_tree_builder_.reset(
+ new SlabSampleTreeBuilder(comparator, &thread_tree_));
+ slab_sample_tree_builder_->SetCallChainSampleOptions(
+ accumulate_callchain_, print_callgraph_, !callgraph_show_callee_,
+ false);
+ sort_comparator.AddComparator(comparator);
+ slab_sample_tree_sorter_.reset(new SlabSampleTreeSorter(sort_comparator));
+ slab_sample_tree_displayer_.reset(new SlabSampleTreeDisplayer(displayer));
+ }
+ }
+ return true;
+}
+
+void KmemCommand::ReadEventAttrsFromRecordFile() {
+ std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
+ for (const auto& attr_with_id : attrs) {
+ EventAttrWithName attr;
+ attr.attr = *attr_with_id.attr;
+ attr.event_ids = attr_with_id.ids;
+ attr.name = GetEventNameByAttr(attr.attr);
+ event_attrs_.push_back(attr);
+ }
+}
+
+bool KmemCommand::ReadFeaturesFromRecordFile() {
+ 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, ' ');
+ }
+ if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
+ std::vector<char> tracing_data;
+ if (!record_file_reader_->ReadFeatureSection(
+ PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) {
+ return false;
+ }
+ ProcessTracingData(tracing_data);
+ }
+ return true;
+}
+
+bool KmemCommand::ReadSampleTreeFromRecordFile() {
+ if (!record_file_reader_->ReadDataSection(
+ [this](std::unique_ptr<Record> record) {
+ return ProcessRecord(std::move(record));
+ })) {
+ return false;
+ }
+ if (use_slab_) {
+ slab_sample_tree_ = slab_sample_tree_builder_->GetSampleTree();
+ slab_sample_tree_sorter_->Sort(slab_sample_tree_.samples, print_callgraph_);
+ }
+ return true;
+}
+
+bool KmemCommand::ProcessRecord(std::unique_ptr<Record> record) {
+ thread_tree_.Update(*record);
+ if (record->type() == PERF_RECORD_SAMPLE) {
+ if (use_slab_) {
+ slab_sample_tree_builder_->ProcessSampleRecord(
+ *static_cast<const SampleRecord*>(record.get()));
+ }
+ } else if (record->type() == PERF_RECORD_TRACING_DATA) {
+ const auto& r = *static_cast<TracingDataRecord*>(record.get());
+ ProcessTracingData(r.data);
+ }
+ return true;
+}
+
+void KmemCommand::ProcessTracingData(const std::vector<char>& data) {
+ Tracing tracing(data);
+ for (auto& attr : event_attrs_) {
+ if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
+ uint64_t trace_event_id = attr.attr.config;
+ attr.name = tracing.GetTracingEventNameHavingId(trace_event_id);
+ TracingFormat format = tracing.GetTracingFormatHavingId(trace_event_id);
+ if (use_slab_) {
+ if (format.name == "kmalloc" || format.name == "kmem_cache_alloc" ||
+ format.name == "kmalloc_node" ||
+ format.name == "kmem_cache_alloc_node") {
+ SlabFormat f;
+ f.type = SlabFormat::KMEM_ALLOC;
+ format.GetField("call_site", f.call_site);
+ format.GetField("ptr", f.ptr);
+ format.GetField("bytes_req", f.bytes_req);
+ format.GetField("bytes_alloc", f.bytes_alloc);
+ format.GetField("gfp_flags", f.gfp_flags);
+ slab_sample_tree_builder_->AddSlabFormat(attr.event_ids, f);
+ } else if (format.name == "kfree" || format.name == "kmem_cache_free") {
+ SlabFormat f;
+ f.type = SlabFormat::KMEM_FREE;
+ format.GetField("call_site", f.call_site);
+ format.GetField("ptr", f.ptr);
+ slab_sample_tree_builder_->AddSlabFormat(attr.event_ids, f);
+ }
+ }
+ }
+ }
+}
+
+bool KmemCommand::PrintReport() {
+ std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose);
+ FILE* report_fp = stdout;
+ if (!report_filename_.empty()) {
+ file_handler.reset(fopen(report_filename_.c_str(), "w"));
+ if (file_handler == nullptr) {
+ PLOG(ERROR) << "failed to open " << report_filename_;
+ return false;
+ }
+ report_fp = file_handler.get();
+ }
+ PrintReportContext(report_fp);
+ if (use_slab_) {
+ fprintf(report_fp, "\n\n");
+ PrintSlabReportContext(report_fp);
+ slab_sample_tree_displayer_->DisplaySamples(
+ report_fp, slab_sample_tree_.samples, &slab_sample_tree_);
+ }
+ return true;
+}
+
+void KmemCommand::PrintReportContext(FILE* fp) {
+ if (!record_cmdline_.empty()) {
+ fprintf(fp, "Cmdline: %s\n", record_cmdline_.c_str());
+ }
+ fprintf(fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
+ for (const auto& attr : event_attrs_) {
+ fprintf(fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
+ attr.attr.type, attr.attr.config);
+ }
+}
+
+void KmemCommand::PrintSlabReportContext(FILE* fp) {
+ fprintf(fp, "Slab allocation information:\n");
+ fprintf(fp, "Total requested bytes: %" PRIu64 "\n",
+ slab_sample_tree_.total_requested_bytes);
+ fprintf(fp, "Total allocated bytes: %" PRIu64 "\n",
+ slab_sample_tree_.total_allocated_bytes);
+ uint64_t fragment = slab_sample_tree_.total_allocated_bytes -
+ slab_sample_tree_.total_requested_bytes;
+ double percentage = 0.0;
+ if (slab_sample_tree_.total_allocated_bytes != 0) {
+ percentage = 100.0 * fragment / slab_sample_tree_.total_allocated_bytes;
+ }
+ fprintf(fp, "Total fragment: %" PRIu64 ", %f%%\n", fragment, percentage);
+ fprintf(fp, "Total allocations: %" PRIu64 "\n",
+ slab_sample_tree_.nr_allocations);
+ fprintf(fp, "Total frees: %" PRIu64 "\n", slab_sample_tree_.nr_frees);
+ percentage = 0.0;
+ if (slab_sample_tree_.nr_allocations != 0) {
+ percentage = 100.0 * slab_sample_tree_.nr_cross_cpu_allocations /
+ slab_sample_tree_.nr_allocations;
+ }
+ fprintf(fp, "Total cross cpu allocation/free: %" PRIu64 ", %f%%\n",
+ slab_sample_tree_.nr_cross_cpu_allocations, percentage);
+ fprintf(fp, "\n");
+}
+
+} // namespace
+
+void RegisterKmemCommand() {
+ RegisterCommand("kmem",
+ [] { return std::unique_ptr<Command>(new KmemCommand()); });
+}
diff --git a/simpleperf/cmd_kmem_test.cpp b/simpleperf/cmd_kmem_test.cpp
new file mode 100644
index 0000000..dd18858
--- /dev/null
+++ b/simpleperf/cmd_kmem_test.cpp
@@ -0,0 +1,148 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.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"
+
+static std::unique_ptr<Command> KmemCmd() {
+ return CreateCommandInstance("kmem");
+}
+
+struct ReportResult {
+ bool success;
+ std::string content;
+ std::vector<std::string> lines;
+};
+
+static void KmemReportRawFile(const std::string& perf_data,
+ const std::vector<std::string>& additional_args,
+ ReportResult* result) {
+ result->success = false;
+ TemporaryFile tmp_file;
+ std::vector<std::string> args = {"report", "-i", perf_data, "-o",
+ tmp_file.path};
+ args.insert(args.end(), additional_args.begin(), additional_args.end());
+ ASSERT_TRUE(KmemCmd()->Run(args));
+ ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &result->content));
+ ASSERT_TRUE(!result->content.empty());
+ std::vector<std::string> raw_lines =
+ android::base::Split(result->content, "\n");
+ result->lines.clear();
+ for (const auto& line : raw_lines) {
+ std::string s = android::base::Trim(line);
+ if (!s.empty()) {
+ result->lines.push_back(s);
+ }
+ }
+ ASSERT_GE(result->lines.size(), 2u);
+ result->success = true;
+}
+
+static void KmemReportFile(const std::string& perf_data,
+ const std::vector<std::string>& additional_args,
+ ReportResult* result) {
+ KmemReportRawFile(GetTestData(perf_data), additional_args, result);
+}
+
+#if defined(__linux__)
+
+static bool RunKmemRecordCmd(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.begin(), "record");
+ v.insert(v.end(), {"-o", out_file, "sleep", SLEEP_SEC});
+ return KmemCmd()->Run(v);
+}
+
+TEST(kmem_cmd, record_slab) {
+ TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab"})));
+}
+
+TEST(kmem_cmd, record_fp_callchain_sampling) {
+ TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab", "-g"})));
+ TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab", "--call-graph", "fp"})));
+}
+
+TEST(kmem_cmd, record_and_report) {
+ TemporaryFile tmp_file;
+ TEST_IN_ROOT({
+ ASSERT_TRUE(RunKmemRecordCmd({"--slab"}, tmp_file.path));
+ ReportResult result;
+ KmemReportRawFile(tmp_file.path, {}, &result);
+ ASSERT_TRUE(result.success);
+ });
+}
+
+TEST(kmem_cmd, record_and_report_callgraph) {
+ TemporaryFile tmp_file;
+ TEST_IN_ROOT({
+ ASSERT_TRUE(RunKmemRecordCmd({"--slab", "-g"}, tmp_file.path));
+ ReportResult result;
+ KmemReportRawFile(tmp_file.path, {"-g"}, &result);
+ ASSERT_TRUE(result.success);
+ });
+}
+
+#endif
+
+TEST(kmem_cmd, report) {
+ ReportResult result;
+ KmemReportFile(PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD, {}, &result);
+ ASSERT_TRUE(result.success);
+ ASSERT_NE(result.content.find("kmem:kmalloc"), std::string::npos);
+ ASSERT_NE(result.content.find("__alloc_skb"), std::string::npos);
+}
+
+TEST(kmem_cmd, report_all_sort_options) {
+ ReportResult result;
+ KmemReportFile(
+ PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD,
+ {"--slab-sort",
+ "hit,caller,ptr,bytes_req,bytes_alloc,fragment,gfp_flags,pingpong"},
+ &result);
+ ASSERT_TRUE(result.success);
+ ASSERT_NE(result.content.find("Ptr"), std::string::npos);
+ ASSERT_NE(result.content.find("GfpFlags"), std::string::npos);
+}
+
+TEST(kmem_cmd, report_callgraph) {
+ ReportResult result;
+ KmemReportFile(PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD, {"-g"}, &result);
+ ASSERT_TRUE(result.success);
+ ASSERT_NE(result.content.find("kmem:kmalloc"), std::string::npos);
+ ASSERT_NE(result.content.find("__alloc_skb"), std::string::npos);
+ ASSERT_NE(result.content.find("system_call_fastpath"), std::string::npos);
+}
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index ae025b0..14283f8 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -25,6 +25,8 @@
#include <vector>
#include <android-base/logging.h>
+#include <android-base/file.h>
+#include <android-base/parseint.h>
#include <android-base/strings.h>
#include "command.h"
@@ -38,6 +40,7 @@
#include "record_file.h"
#include "scoped_signal_handler.h"
#include "thread_tree.h"
+#include "tracing.h"
#include "utils.h"
#include "workload.h"
@@ -53,70 +56,86 @@
};
static volatile bool signaled;
-static void signal_handler(int) {
- signaled = true;
-}
+static void signal_handler(int) { signaled = true; }
-// Used in cpu-hotplug test.
-bool system_wide_perf_event_open_failed = false;
+constexpr uint64_t DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT = 4000;
+constexpr uint64_t DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT = 1;
class RecordCommand : public Command {
public:
RecordCommand()
: Command(
"record", "record sampling info in perf.data",
- "Usage: simpleperf record [options] [command [command-args]]\n"
- " Gather sampling information when running [command].\n"
- " -a System-wide collection.\n"
- " -b Enable take branch stack sampling. Same as '-j any'\n"
- " -c count Set event sample period.\n"
- " --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),
+ // clang-format off
+"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. It means recording one sample when\n"
+" [count] events happen. Can't be used with -f/-F option.\n"
+" For tracepoint events, the default option is -c 1.\n"
+"--call-graph fp | dwarf[,<dump_stack_size>]\n"
+" Enable call graph recording. Use frame pointer or dwarf debug\n"
+" frame as the method to parse call graph in stack.\n"
+" 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"
+"--dump-symbols Dump symbols in perf.data. By default perf.data doesn't contain\n"
+" symbol information for samples. This option is used when there\n"
+" is no symbol information in report environment.\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 how\n"
+" the event should be monitored.\n"
+" Possible modifiers are:\n"
+" u - monitor user space events only\n"
+" k - monitor kernel space events only\n"
+"-f freq Set event sample frequency. It means recording at most [freq]\n"
+" samples every second. For non-tracepoint events, the default\n"
+" option is -f 4000.\n"
+"-F freq Same as '-f freq'.\n"
+"-g Same as '--call-graph dwarf'.\n"
+"--group event1[:modifier],event2[:modifier2],...\n"
+" Similar to -e option. But events specified in the same --group\n"
+" option are monitored as a group, and scheduled in and out at the\n"
+" same time.\n"
+"-j branch_filter1,branch_filter2,...\n"
+" Enable taken branch stack sampling. Each sample captures a series\n"
+" 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, any_call,\n"
+" any_ret, ind_call.\n"
+"-m mmap_pages Set the size of the buffer used to receiving sample data from\n"
+" the kernel. It should be a power of 2. The default value for\n"
+" system wide profiling is 256. The default value for non system\n"
+" wide profiling is 16.\n"
+"--no-dump-kernel-symbols Don't dump kernel symbols in perf.data. By default\n"
+" kernel symbols will be dumped when needed.\n"
+"--no-inherit Don't record created child threads/processes.\n"
+"--no-unwind If `--call-graph dwarf` option is used, then the user's stack\n"
+" will be unwound by default. Use this option to disable the\n"
+" unwinding of the user's stack.\n"
+"-o record_file_name Set record file name, default is perf.data.\n"
+"-p pid1,pid2,... Record events on existing processes. Mutually exclusive\n"
+" with -a.\n"
+"--post-unwind If `--call-graph dwarf` option is used, then the user's stack\n"
+" will be unwound while recording by default. But it may lose\n"
+" records as stacking unwinding can be time consuming. Use this\n"
+" option to unwind the user's stack after recording.\n"
+"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
+ // clang-format on
+ ),
+ use_sample_freq_(false),
+ sample_freq_(0),
+ use_sample_period_(false),
+ sample_period_(0),
system_wide_collection_(false),
branch_sampling_(0),
fp_callchain_sampling_(false),
@@ -125,9 +144,12 @@
unwind_dwarf_callchain_(true),
post_unwind_(false),
child_inherit_(true),
- perf_mmap_pages_(16),
+ can_dump_kernel_symbols_(true),
+ dump_symbols_(false),
+ perf_mmap_pages_(0),
record_filename_("perf.data"),
- sample_record_count_(0) {
+ sample_record_count_(0),
+ lost_record_count_(0) {
signaled = false;
scoped_signal_handler_.reset(
new ScopedSignalHandler({SIGCHLD, SIGINT, SIGTERM}, signal_handler));
@@ -135,28 +157,32 @@
bool Run(const std::vector<std::string>& args);
- static bool ReadMmapDataCallback(const char* data, size_t size);
-
private:
- bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
- bool AddMeasuredEventType(const std::string& event_type_name);
- bool SetEventSelection();
+ bool ParseOptions(const std::vector<std::string>& args,
+ std::vector<std::string>* non_option_args);
+ bool SetEventSelectionFlags();
bool CreateAndInitRecordFile();
- std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename);
- bool DumpKernelAndModuleMmaps();
- bool DumpThreadCommAndMmaps(bool all_threads, const std::vector<pid_t>& selected_threads);
- bool CollectRecordsFromKernel(const char* data, size_t size);
+ std::unique_ptr<RecordFileWriter> CreateRecordFile(
+ const std::string& filename);
+ bool DumpKernelSymbol();
+ bool DumpTracingData();
+ bool DumpKernelAndModuleMmaps(const perf_event_attr& attr, uint64_t event_id);
+ bool DumpThreadCommAndMmaps(const perf_event_attr& attr, uint64_t event_id,
+ bool all_threads,
+ const std::vector<pid_t>& selected_threads);
bool ProcessRecord(Record* record);
+ bool DumpSymbolForRecord(const SampleRecord& r, bool for_callchain);
void UpdateRecordForEmbeddedElfPath(Record* record);
- void UnwindRecord(Record* record);
+ bool 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);
+ 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.
+ bool use_sample_freq_;
+ uint64_t sample_freq_; // Sample 'sample_freq_' times per second.
+ bool use_sample_period_;
uint64_t sample_period_; // Sample once when 'sample_period_' events occur.
bool system_wide_collection_;
@@ -167,15 +193,15 @@
bool unwind_dwarf_callchain_;
bool post_unwind_;
bool child_inherit_;
+ bool can_dump_kernel_symbols_;
+ bool dump_symbols_;
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 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_;
@@ -185,6 +211,7 @@
std::unique_ptr<ScopedSignalHandler> scoped_signal_handler_;
uint64_t sample_record_count_;
+ uint64_t lost_record_count_;
};
bool RecordCommand::Run(const std::vector<std::string>& args) {
@@ -197,12 +224,12 @@
if (!ParseOptions(args, &workload_args)) {
return false;
}
- if (measured_event_types_.empty()) {
- if (!AddMeasuredEventType(default_measured_event_type)) {
+ if (event_selection_set_.empty()) {
+ if (!event_selection_set_.AddEventType(default_measured_event_type)) {
return false;
}
}
- if (!SetEventSelection()) {
+ if (!SetEventSelectionFlags()) {
return false;
}
@@ -219,20 +246,21 @@
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";
+ 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.
+ // 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_.OpenEventFilesForCpus(cpus_)) {
- system_wide_perf_event_open_failed = true;
return false;
}
} else {
- if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) {
+ if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_,
+ cpus_)) {
return false;
}
}
@@ -240,23 +268,23 @@
return false;
}
std::vector<pollfd> pollfds;
- event_selection_set_.PreparePollForEventFiles(&pollfds);
+ event_selection_set_.PrepareToPollForEventFiles(&pollfds);
// 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.
+ // 5. Write records in mmap buffers of perf_event_files to output file while
+ // workload is running.
if (workload != nullptr && !workload->Start()) {
return false;
}
- 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);
+ auto callback =
+ std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1);
+ event_selection_set_.PrepareToReadMmapEventData(callback);
while (true) {
- if (!event_selection_set_.ReadMmapEventData(callback)) {
+ if (!event_selection_set_.ReadMmapEventData()) {
return false;
}
if (signaled) {
@@ -264,12 +292,7 @@
}
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;
- }
- }
+ event_selection_set_.FinishReadMmapEventData();
// 6. Dump additional features, and close record file.
if (!DumpAdditionalFeatures(args)) {
@@ -285,15 +308,30 @@
return false;
}
}
- LOG(VERBOSE) << "Record " << sample_record_count_ << " samples.";
+
+ // 8. Show brief record result.
+ LOG(INFO) << "Samples recorded: " << sample_record_count_
+ << ". Samples lost: " << lost_record_count_ << ".";
+ if (sample_record_count_ + lost_record_count_ != 0) {
+ double lost_percent = static_cast<double>(lost_record_count_) /
+ (lost_record_count_ + sample_record_count_);
+ constexpr double LOST_PERCENT_WARNING_BAR = 0.1;
+ if (lost_percent >= LOST_PERCENT_WARNING_BAR) {
+ LOG(WARNING) << "Lost " << (lost_percent * 100) << "% of samples, "
+ << "consider increasing mmap_pages(-m), "
+ << "or decreasing sample frequency(-f), "
+ << "or increasing sample period(-c).";
+ }
+ }
return true;
}
bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
std::vector<std::string>* non_option_args) {
std::set<pid_t> tid_set;
+ size_t mmap_pages = 0;
size_t i;
- for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
+ for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
if (args[i] == "-a") {
system_wide_collection_ = true;
} else if (args[i] == "-b") {
@@ -308,7 +346,7 @@
LOG(ERROR) << "Invalid sample period: '" << args[i] << "'";
return false;
}
- use_sample_freq_ = false;
+ use_sample_period_ = true;
} else if (args[i] == "--call-graph") {
if (!NextArgumentOrError(args, &i)) {
return false;
@@ -324,17 +362,20 @@
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];
+ 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.";
+ 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];
+ LOG(ERROR) << "unexpected argument for --call-graph option: "
+ << args[i];
return false;
}
} else if (args[i] == "--cpu") {
@@ -342,13 +383,15 @@
return false;
}
cpus_ = GetCpusFromString(args[i]);
+ } else if (args[i] == "--dump-symbols") {
+ dump_symbols_ = true;
} else if (args[i] == "-e") {
if (!NextArgumentOrError(args, &i)) {
return false;
}
std::vector<std::string> event_types = android::base::Split(args[i], ",");
for (auto& event_type : event_types) {
- if (!AddMeasuredEventType(event_type)) {
+ if (!event_selection_set_.AddEventType(event_type)) {
return false;
}
}
@@ -356,21 +399,31 @@
if (!NextArgumentOrError(args, &i)) {
return false;
}
- char* endptr;
- sample_freq_ = strtoull(args[i].c_str(), &endptr, 0);
- if (*endptr != '\0' || sample_freq_ == 0) {
- LOG(ERROR) << "Invalid sample frequency: '" << args[i] << "'";
+ if (!android::base::ParseUint(args[i].c_str(), &sample_freq_)) {
+ LOG(ERROR) << "Invalid sample frequency: " << args[i];
+ return false;
+ }
+ if (!CheckSampleFrequency(sample_freq_)) {
return false;
}
use_sample_freq_ = true;
} else if (args[i] == "-g") {
fp_callchain_sampling_ = false;
dwarf_callchain_sampling_ = true;
+ } else if (args[i] == "--group") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ std::vector<std::string> event_types = android::base::Split(args[i], ",");
+ if (!event_selection_set_.AddEventGroup(event_types)) {
+ return false;
+ }
} else if (args[i] == "-j") {
if (!NextArgumentOrError(args, &i)) {
return false;
}
- std::vector<std::string> branch_sampling_types = android::base::Split(args[i], ",");
+ 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()) {
@@ -389,7 +442,9 @@
LOG(ERROR) << "Invalid mmap_pages: '" << args[i] << "'";
return false;
}
- perf_mmap_pages_ = pages;
+ mmap_pages = pages;
+ } else if (args[i] == "--no-dump-kernel-symbols") {
+ can_dump_kernel_symbols_ = false;
} else if (args[i] == "--no-inherit") {
child_inherit_ = false;
} else if (args[i] == "--no-unwind") {
@@ -421,16 +476,23 @@
}
}
+ if (use_sample_freq_ && use_sample_period_) {
+ LOG(ERROR) << "-f option can't be used with -c option.";
+ return false;
+ }
+
if (!dwarf_callchain_sampling_) {
if (!unwind_dwarf_callchain_) {
- LOG(ERROR) << "--no-unwind is only used with `--call-graph dwarf` option.";
+ 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.";
+ LOG(ERROR)
+ << "--post-unwind is only used with `--call-graph dwarf` option.";
return false;
}
if (!unwind_dwarf_callchain_) {
@@ -439,13 +501,30 @@
}
}
- monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end());
+ 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.";
+ LOG(ERROR) << "Record system wide and existing processes/threads can't be "
+ "used at the same time.";
return false;
}
+ if (system_wide_collection_ && !IsRoot()) {
+ LOG(ERROR) << "System wide profiling needs root privilege.";
+ return false;
+ }
+
+ if (dump_symbols_ && can_dump_kernel_symbols_) {
+ // No need to dump kernel symbols as we will dump all required symbols.
+ can_dump_kernel_symbols_ = false;
+ }
+
+ if (mmap_pages != 0) {
+ perf_mmap_pages_ = mmap_pages;
+ } else {
+ perf_mmap_pages_ = (system_wide_collection_ ? 256 : 16);
+ }
+
if (non_option_args != nullptr) {
non_option_args->clear();
for (; i < args.size(); ++i) {
@@ -455,26 +534,25 @@
return true;
}
-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_types_.push_back(*event_type_modifier);
- return true;
-}
-
-bool RecordCommand::SetEventSelection() {
- for (auto& event_type : measured_event_types_) {
- if (!event_selection_set_.AddEventType(event_type)) {
- return false;
+bool RecordCommand::SetEventSelectionFlags() {
+ for (const auto& group : event_selection_set_.groups()) {
+ for (const auto& selection : group) {
+ if (use_sample_freq_) {
+ event_selection_set_.SetSampleFreq(selection, sample_freq_);
+ } else if (use_sample_period_) {
+ event_selection_set_.SetSamplePeriod(selection, sample_period_);
+ } else {
+ if (selection.event_type_modifier.event_type.type ==
+ PERF_TYPE_TRACEPOINT) {
+ event_selection_set_.SetSamplePeriod(
+ selection, DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT);
+ } else {
+ event_selection_set_.SetSampleFreq(
+ selection, DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+ }
+ }
}
}
- 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;
@@ -482,7 +560,8 @@
if (fp_callchain_sampling_) {
event_selection_set_.EnableFpCallChainSampling();
} else if (dwarf_callchain_sampling_) {
- if (!event_selection_set_.EnableDwarfCallChainSampling(dump_stack_size_in_dwarf_sampling_)) {
+ if (!event_selection_set_.EnableDwarfCallChainSampling(
+ dump_stack_size_in_dwarf_sampling_)) {
return false;
}
}
@@ -495,33 +574,47 @@
if (record_file_writer_ == nullptr) {
return false;
}
- if (!DumpKernelAndModuleMmaps()) {
+ // Use first perf_event_attr and first event id to dump mmap and comm records.
+ const EventSelection& selection = event_selection_set_.groups()[0][0];
+ const perf_event_attr& attr = selection.event_attr;
+ const std::vector<std::unique_ptr<EventFd>>& fds = selection.event_fds;
+ uint64_t event_id = fds[0]->Id();
+ if (!DumpKernelSymbol()) {
return false;
}
- if (!DumpThreadCommAndMmaps(system_wide_collection_, monitored_threads_)) {
+ if (!DumpTracingData()) {
+ return false;
+ }
+ if (!DumpKernelAndModuleMmaps(attr, event_id)) {
+ return false;
+ }
+ if (!DumpThreadCommAndMmaps(attr, event_id, system_wide_collection_,
+ monitored_threads_)) {
return false;
}
return true;
}
-std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename) {
- std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
+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());
+ for (const auto& group : event_selection_set_.groups()) {
+ for (const auto& selection : group) {
+ AttrWithId attr_id;
+ attr_id.attr = &selection.event_attr;
+ CHECK(attr_id.attr != nullptr);
+ const std::vector<std::unique_ptr<EventFd>>& fds = selection.event_fds;
+ for (const auto& fd : fds) {
+ attr_id.ids.push_back(fd->Id());
+ }
+ attr_ids.push_back(attr_id);
}
- attr_ids.push_back(attr_id);
}
if (!writer->WriteAttrSection(attr_ids)) {
return nullptr;
@@ -529,21 +622,72 @@
return writer;
}
-bool RecordCommand::DumpKernelAndModuleMmaps() {
+bool RecordCommand::DumpKernelSymbol() {
+ if (can_dump_kernel_symbols_) {
+ std::string kallsyms;
+ bool need_kernel_symbol = false;
+ for (const auto& group : event_selection_set_.groups()) {
+ for (const auto& selection : group) {
+ if (!selection.event_type_modifier.exclude_kernel) {
+ need_kernel_symbol = true;
+ }
+ }
+ }
+ if (need_kernel_symbol) {
+ if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) {
+ PLOG(ERROR) << "failed to read /proc/kallsyms";
+ return false;
+ }
+ }
+ KernelSymbolRecord r = KernelSymbolRecord::Create(std::move(kallsyms));
+ if (!ProcessRecord(&r)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool RecordCommand::DumpTracingData() {
+ std::vector<const EventType*> tracepoint_event_types;
+ for (const auto& group : event_selection_set_.groups()) {
+ for (const auto& selection : group) {
+ if (selection.event_type_modifier.event_type.type ==
+ PERF_TYPE_TRACEPOINT) {
+ tracepoint_event_types.push_back(
+ &selection.event_type_modifier.event_type);
+ }
+ }
+ }
+ if (tracepoint_event_types.empty()) {
+ return true; // No need to dump tracing data.
+ }
+ std::vector<char> tracing_data;
+ if (!GetTracingData(tracepoint_event_types, &tracing_data)) {
+ return false;
+ }
+ TracingDataRecord record = TracingDataRecord::Create(std::move(tracing_data));
+ if (!ProcessRecord(&record)) {
+ return false;
+ }
+ return true;
+}
+
+bool RecordCommand::DumpKernelAndModuleMmaps(const perf_event_attr& attr,
+ uint64_t event_id) {
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);
+ MmapRecord mmap_record =
+ MmapRecord::Create(attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
+ kernel_mmap.len, 0, kernel_mmap.filepath, event_id);
if (!ProcessRecord(&mmap_record)) {
return false;
}
for (auto& module_mmap : module_mmaps) {
- MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, module_mmap.start_addr,
- module_mmap.len, 0, module_mmap.filepath);
+ MmapRecord mmap_record =
+ MmapRecord::Create(attr, true, UINT_MAX, 0, module_mmap.start_addr,
+ module_mmap.len, 0, module_mmap.filepath, event_id);
if (!ProcessRecord(&mmap_record)) {
return false;
}
@@ -551,8 +695,9 @@
return true;
}
-bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads,
- const std::vector<pid_t>& selected_threads) {
+bool RecordCommand::DumpThreadCommAndMmaps(
+ const perf_event_attr& attr, uint64_t event_id, bool all_threads,
+ const std::vector<pid_t>& selected_threads) {
std::vector<ThreadComm> thread_comms;
if (!GetThreadComms(&thread_comms)) {
return false;
@@ -569,18 +714,17 @@
}
}
- 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()) {
+ if (!all_threads &&
+ dump_processes.find(thread.pid) == dump_processes.end()) {
continue;
}
- CommRecord record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
+ CommRecord record =
+ CommRecord::Create(attr, thread.pid, thread.tid, thread.comm, event_id);
if (!ProcessRecord(&record)) {
return false;
}
@@ -593,9 +737,9 @@
if (thread_mmap.executable == 0) {
continue; // No need to dump non-executable mmap info.
}
- MmapRecord record =
- CreateMmapRecord(*attr, false, thread.pid, thread.tid, thread_mmap.start_addr,
- thread_mmap.len, thread_mmap.pgoff, thread_mmap.name);
+ MmapRecord record = MmapRecord::Create(
+ attr, false, thread.pid, thread.tid, thread_mmap.start_addr,
+ thread_mmap.len, thread_mmap.pgoff, thread_mmap.name, event_id);
if (!ProcessRecord(&record)) {
return false;
}
@@ -610,11 +754,13 @@
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);
+ ForkRecord fork_record = ForkRecord::Create(
+ attr, thread.pid, thread.tid, thread.pid, thread.pid, event_id);
if (!ProcessRecord(&fork_record)) {
return false;
}
- CommRecord comm_record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
+ CommRecord comm_record =
+ CommRecord::Create(attr, thread.pid, thread.tid, thread.comm, event_id);
if (!ProcessRecord(&comm_record)) {
return false;
}
@@ -622,39 +768,64 @@
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 RecordCommand::ProcessRecord(Record* record) {
UpdateRecordForEmbeddedElfPath(record);
- BuildThreadTree(*record, &thread_tree_);
+ thread_tree_.Update(*record);
CollectHitFileInfo(record);
if (unwind_dwarf_callchain_ && !post_unwind_) {
- UnwindRecord(record);
+ if (!UnwindRecord(record)) {
+ return false;
+ }
}
if (record->type() == PERF_RECORD_SAMPLE) {
sample_record_count_++;
+ if (dump_symbols_) {
+ auto& r = *static_cast<SampleRecord*>(record);
+ if (!DumpSymbolForRecord(r, false)) {
+ return false;
+ }
+ }
+ } else if (record->type() == PERF_RECORD_LOST) {
+ lost_record_count_ += static_cast<LostRecord*>(record)->lost;
}
- bool result = record_file_writer_->WriteData(record->BinaryFormat());
+ bool result = record_file_writer_->WriteRecord(*record);
return result;
}
-template<class RecordType>
+bool RecordCommand::DumpSymbolForRecord(const SampleRecord& r,
+ bool for_callchain) {
+ const ThreadEntry* thread =
+ thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+ const std::vector<uint64_t>& ips = for_callchain
+ ? r.callchain_data.ips
+ : std::vector<uint64_t>{r.ip_data.ip};
+ for (auto& ip : ips) {
+ const MapEntry* map = thread_tree_.FindMap(thread, ip, r.InKernel());
+ if (!map->dso->HasDumped()) {
+ map->dso->SetDumped();
+ DsoRecord dso_record =
+ DsoRecord::Create(map->dso->type(), map->dso->id(), map->dso->Path());
+ if (!record_file_writer_->WriteRecord(dso_record)) {
+ return false;
+ }
+ }
+ const Symbol* symbol = thread_tree_.FindSymbol(map, ip);
+ if (!symbol->HasDumped()) {
+ symbol->SetDumped();
+ SymbolRecord symbol_record = SymbolRecord::Create(
+ symbol->addr, symbol->len, symbol->Name(), map->dso->id());
+ if (!record_file_writer_->WriteRecord(symbol_record)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+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) {
+ if (!r.InKernel() && 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]
@@ -665,7 +836,8 @@
// 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);
+ 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();
@@ -683,31 +855,46 @@
}
}
-void RecordCommand::UnwindRecord(Record* record) {
+bool 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) &&
+ 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);
+ 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);
+ ArchType arch = GetArchForAbi(GetBuildArch(), r.regs_user_data.abi);
+ // Normally do strict arch check when unwinding stack. But allow unwinding
+ // 32-bit processes on 64-bit devices for system wide profiling.
+ bool strict_arch_check = !system_wide_collection_;
+ std::vector<uint64_t> unwind_ips =
+ UnwindCallChain(arch, *thread, regs, stack, strict_arch_check);
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.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();
+ if (!DumpSymbolForRecord(r, true)) {
+ return false;
+ }
}
}
+ return true;
}
bool RecordCommand::PostUnwind(const std::vector<std::string>& args) {
- thread_tree_.Clear();
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_filename_);
+ thread_tree_.ClearThreadAndMap();
+ std::unique_ptr<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(record_filename_);
if (reader == nullptr) {
return false;
}
@@ -718,9 +905,11 @@
}
bool result = reader->ReadDataSection(
[this](std::unique_ptr<Record> record) {
- BuildThreadTree(*record, &thread_tree_);
- UnwindRecord(record.get());
- return record_file_writer_->WriteData(record->BinaryFormat());
+ thread_tree_.Update(*record);
+ if (!UnwindRecord(record.get())) {
+ return false;
+ }
+ return record_file_writer_->WriteRecord(*record);
},
false);
if (!result) {
@@ -738,13 +927,15 @@
return false;
}
if (rename(tmp_filename.c_str(), record_filename_.c_str()) != 0) {
- PLOG(ERROR) << "failed to rename " << tmp_filename << " to " << record_filename_;
+ PLOG(ERROR) << "failed to rename " << tmp_filename << " to "
+ << record_filename_;
return false;
}
return true;
}
-bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args) {
+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;
@@ -757,10 +948,12 @@
PLOG(ERROR) << "uname() failed";
return false;
}
- if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE, uname_buf.release)) {
+ if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE,
+ uname_buf.release)) {
return false;
}
- if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH, uname_buf.machine)) {
+ if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH,
+ uname_buf.machine)) {
return false;
}
@@ -773,7 +966,8 @@
if (!record_file_writer_->WriteCmdlineFeature(cmdline)) {
return false;
}
- if (branch_sampling_ != 0 && !record_file_writer_->WriteBranchStackFeature()) {
+ if (branch_sampling_ != 0 &&
+ !record_file_writer_->WriteBranchStackFeature()) {
return false;
}
return true;
@@ -789,8 +983,8 @@
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));
+ build_id_records.push_back(BuildIdRecord::Create(
+ true, UINT_MAX, build_id, DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID));
} else {
std::string path = filename;
std::string module_name = basename(&path[0]);
@@ -801,7 +995,8 @@
LOG(DEBUG) << "can't read build_id for module " << module_name;
continue;
}
- build_id_records.push_back(CreateBuildIdRecord(true, UINT_MAX, build_id, filename));
+ build_id_records.push_back(
+ BuildIdRecord::Create(true, UINT_MAX, build_id, filename));
}
}
// Add build_ids for user elf files.
@@ -811,7 +1006,8 @@
}
auto tuple = SplitUrlInApk(filename);
if (std::get<0>(tuple)) {
- if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple), &build_id)) {
+ if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple),
+ &build_id)) {
LOG(DEBUG) << "can't read build_id from file " << filename;
continue;
}
@@ -821,7 +1017,8 @@
continue;
}
}
- build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, filename));
+ build_id_records.push_back(
+ BuildIdRecord::Create(false, UINT_MAX, build_id, filename));
}
if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) {
return false;
@@ -832,8 +1029,9 @@
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);
+ bool in_kernel = r.InKernel();
+ 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());
@@ -844,5 +1042,6 @@
}
void RegisterRecordCommand() {
- RegisterCommand("record", [] { return std::unique_ptr<Command>(new RecordCommand()); });
+ RegisterCommand("record",
+ [] { return std::unique_ptr<Command>(new RecordCommand()); });
}
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 1a468e2..a4e8c2d 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -19,6 +19,7 @@
#include <android-base/stringprintf.h>
#include <android-base/test_utils.h>
+#include <map>
#include <memory>
#include "command.h"
@@ -35,7 +36,8 @@
return CreateCommandInstance("record");
}
-static bool RunRecordCmd(std::vector<std::string> v, const char* output_file = nullptr) {
+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) {
@@ -48,14 +50,10 @@
return RecordCmd()->Run(v);
}
-TEST(record_cmd, no_options) {
- ASSERT_TRUE(RunRecordCmd({}));
-}
+TEST(record_cmd, no_options) { ASSERT_TRUE(RunRecordCmd({})); }
TEST(record_cmd, system_wide_option) {
- if (IsRoot()) {
- ASSERT_TRUE(RunRecordCmd({"-a"}));
- }
+ TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a"})));
}
TEST(record_cmd, sample_period_option) {
@@ -79,14 +77,16 @@
TEST(record_cmd, dump_kernel_mmap) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ std::unique_ptr<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader != nullptr);
std::vector<std::unique_ptr<Record>> records = reader->DataSection();
ASSERT_GT(records.size(), 0U);
bool have_kernel_mmap = false;
for (auto& record : records) {
- if (record->header.type == PERF_RECORD_MMAP) {
- const MmapRecord* mmap_record = static_cast<const MmapRecord*>(record.get());
+ if (record->type() == PERF_RECORD_MMAP) {
+ const MmapRecord* mmap_record =
+ static_cast<const MmapRecord*>(record.get());
if (mmap_record->filename == DEFAULT_KERNEL_MMAP_NAME) {
have_kernel_mmap = true;
break;
@@ -99,17 +99,17 @@
TEST(record_cmd, dump_build_id_feature) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(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.features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8)));
+ 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_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "-e", "sched:sched_switch"})));
}
TEST(record_cmd, branch_sampling) {
@@ -120,8 +120,8 @@
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.";
+ GTEST_LOG_(INFO) << "This test does nothing as branch stack sampling is "
+ "not supported on this device.";
}
}
@@ -133,14 +133,27 @@
ASSERT_TRUE(RunRecordCmd({"--call-graph", "fp"}));
}
+TEST(record_cmd, system_wide_fp_callchain_sampling) {
+ TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "--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.";
+ GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+ "not supported on this device.";
+ }
+}
+
+TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
+ if (IsDwarfCallChainSamplingSupported()) {
+ TEST_IN_ROOT(RunRecordCmd({"-a", "--call-graph", "dwarf"}));
+ } else {
+ GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+ "not supported on this device.";
}
}
@@ -148,8 +161,8 @@
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.";
+ GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+ "not supported on this device.";
}
ASSERT_FALSE(RunRecordCmd({"--no-unwind"}));
}
@@ -158,8 +171,8 @@
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.";
+ GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+ "not supported on this device.";
}
ASSERT_FALSE(RunRecordCmd({"--post-unwind"}));
ASSERT_FALSE(
@@ -169,8 +182,8 @@
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());
+ std::string pid_list = android::base::StringPrintf(
+ "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
ASSERT_TRUE(RunRecordCmd({"-p", pid_list}));
}
@@ -178,15 +191,12 @@
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;
+ std::string tid_list = android::base::StringPrintf(
+ "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
}
-TEST(record_cmd, no_monitored_threads) {
- ASSERT_FALSE(RecordCmd()->Run({""}));
-}
+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"}));
@@ -195,9 +205,7 @@
TEST(record_cmd, cpu_option) {
ASSERT_TRUE(RunRecordCmd({"--cpu", "0"}));
- if (IsRoot()) {
- ASSERT_TRUE(RunRecordCmd({"--cpu", "0", "-a"}));
- }
+ TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"--cpu", "0", "-a"})));
}
TEST(record_cmd, mmap_page_option) {
@@ -205,3 +213,87 @@
ASSERT_FALSE(RunRecordCmd({"-m", "0"}));
ASSERT_FALSE(RunRecordCmd({"-m", "7"}));
}
+
+static void CheckKernelSymbol(const std::string& path, bool need_kallsyms,
+ bool* success) {
+ *success = false;
+ std::unique_ptr<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(path);
+ ASSERT_TRUE(reader != nullptr);
+ std::vector<std::unique_ptr<Record>> records = reader->DataSection();
+ bool has_kernel_symbol_records = false;
+ for (const auto& record : records) {
+ if (record->type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
+ has_kernel_symbol_records = true;
+ }
+ }
+ ASSERT_EQ(need_kallsyms, has_kernel_symbol_records);
+ *success = true;
+}
+
+TEST(record_cmd, kernel_symbol) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
+ bool success;
+ CheckKernelSymbol(tmpfile.path, true, &success);
+ ASSERT_TRUE(success);
+ ASSERT_TRUE(RunRecordCmd({"--no-dump-kernel-symbols"}, tmpfile.path));
+ CheckKernelSymbol(tmpfile.path, false, &success);
+ ASSERT_TRUE(success);
+}
+
+// Check if the dso/symbol records in perf.data matches our expectation.
+static void CheckDsoSymbolRecords(const std::string& path,
+ bool need_dso_symbol_records, bool* success) {
+ *success = false;
+ std::unique_ptr<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(path);
+ ASSERT_TRUE(reader != nullptr);
+ std::vector<std::unique_ptr<Record>> records = reader->DataSection();
+ bool has_dso_record = false;
+ bool has_symbol_record = false;
+ std::map<uint64_t, bool> dso_hit_map;
+ for (const auto& record : records) {
+ if (record->type() == SIMPLE_PERF_RECORD_DSO) {
+ has_dso_record = true;
+ uint64_t dso_id = static_cast<const DsoRecord*>(record.get())->dso_id;
+ ASSERT_EQ(dso_hit_map.end(), dso_hit_map.find(dso_id));
+ dso_hit_map.insert(std::make_pair(dso_id, false));
+ } else if (record->type() == SIMPLE_PERF_RECORD_SYMBOL) {
+ has_symbol_record = true;
+ uint64_t dso_id = static_cast<const SymbolRecord*>(record.get())->dso_id;
+ auto it = dso_hit_map.find(dso_id);
+ ASSERT_NE(dso_hit_map.end(), it);
+ it->second = true;
+ }
+ }
+ for (auto& pair : dso_hit_map) {
+ ASSERT_TRUE(pair.second);
+ }
+ if (need_dso_symbol_records) {
+ ASSERT_TRUE(has_dso_record);
+ ASSERT_TRUE(has_symbol_record);
+ } else {
+ ASSERT_FALSE(has_dso_record);
+ ASSERT_FALSE(has_symbol_record);
+ }
+ *success = true;
+}
+
+TEST(record_cmd, dump_symbols) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
+ bool success;
+ CheckDsoSymbolRecords(tmpfile.path, false, &success);
+ ASSERT_TRUE(success);
+ ASSERT_TRUE(RunRecordCmd({"--dump-symbols"}, tmpfile.path));
+ CheckDsoSymbolRecords(tmpfile.path, true, &success);
+ ASSERT_TRUE(success);
+}
+
+TEST(record_cmd, group_option) {
+ ASSERT_TRUE(RunRecordCmd({"--group", "cpu-cycles,cpu-clock"}));
+ ASSERT_TRUE(RunRecordCmd({"--group", "cpu-cycles,cpu-clock", "--group",
+ "cpu-cycles:u,cpu-clock:u", "--group",
+ "cpu-cycles:k,cpu-clock:k"}));
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 3d778ab..ef2a93d 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -39,285 +39,268 @@
#include "record_file.h"
#include "sample_tree.h"
#include "thread_tree.h"
+#include "tracing.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") {
- }
-};
+namespace {
static std::set<std::string> branch_sort_keys = {
"dso_from", "dso_to", "symbol_from", "symbol_to",
};
+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;
+ // accumuated when appearing in other sample's callchain
+ uint64_t accumulated_period;
+ uint64_t sample_count;
+ const ThreadEntry* thread;
+ const char* thread_comm;
+ const MapEntry* map;
+ const Symbol* symbol;
+ BranchFromEntry branch_from;
+ // a callchain tree representing all callchains in the sample
+ CallChainRoot<SampleEntry> callchain;
+
+ 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;
+};
+
+struct SampleTree {
+ std::vector<SampleEntry*> samples;
+ uint64_t total_samples;
+ uint64_t total_period;
+};
+
+class ReportCmdSampleTreeBuilder
+ : public SampleTreeBuilder<SampleEntry, uint64_t> {
+ public:
+ ReportCmdSampleTreeBuilder(SampleComparator<SampleEntry> sample_comparator,
+ ThreadTree* thread_tree)
+ : SampleTreeBuilder(sample_comparator),
+ thread_tree_(thread_tree),
+ 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) {
+ pid_filter_ = pid_filter;
+ tid_filter_ = tid_filter;
+ comm_filter_ = comm_filter;
+ dso_filter_ = dso_filter;
+ }
+
+ SampleTree GetSampleTree() const {
+ SampleTree sample_tree;
+ sample_tree.samples = GetSamples();
+ sample_tree.total_samples = total_samples_;
+ sample_tree.total_period = total_period_;
+ return sample_tree;
+ }
+
+ protected:
+ SampleEntry* CreateSample(const SampleRecord& r, bool in_kernel,
+ uint64_t* acc_info) override {
+ 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);
+ const Symbol* symbol = thread_tree_->FindSymbol(map, r.ip_data.ip);
+ *acc_info = r.period_data.period;
+ return InsertSample(std::unique_ptr<SampleEntry>(
+ new SampleEntry(r.ip_data.ip, r.time_data.time, r.period_data.period, 0,
+ 1, thread, map, symbol)));
+ }
+
+ SampleEntry* CreateBranchSample(const SampleRecord& r,
+ const BranchStackItemType& item) override {
+ const ThreadEntry* thread =
+ thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+ const MapEntry* from_map = thread_tree_->FindMap(thread, item.from);
+ const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, item.from);
+ const MapEntry* to_map = thread_tree_->FindMap(thread, item.to);
+ const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, item.to);
+ std::unique_ptr<SampleEntry> sample(
+ new SampleEntry(item.to, r.time_data.time, r.period_data.period, 0, 1,
+ thread, to_map, to_symbol));
+ sample->branch_from.ip = item.from;
+ sample->branch_from.map = from_map;
+ sample->branch_from.symbol = from_symbol;
+ sample->branch_from.flags = item.flags;
+ return InsertSample(std::move(sample));
+ }
+
+ SampleEntry* CreateCallChainSample(const SampleEntry* sample, uint64_t ip,
+ bool in_kernel,
+ const std::vector<SampleEntry*>& callchain,
+ const uint64_t& acc_info) override {
+ const ThreadEntry* thread = sample->thread;
+ const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
+ const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
+ std::unique_ptr<SampleEntry> callchain_sample(
+ new SampleEntry(ip, sample->time, 0, acc_info, 0, thread, map, symbol));
+ return InsertCallChainSample(std::move(callchain_sample), callchain);
+ }
+
+ const ThreadEntry* GetThreadOfSample(SampleEntry* sample) override {
+ return sample->thread;
+ }
+
+ void InsertCallChainForSample(SampleEntry* sample,
+ const std::vector<SampleEntry*>& callchain,
+ const uint64_t& acc_info) override {
+ sample->callchain.AddCallChain(callchain, acc_info);
+ }
+
+ bool FilterSample(const SampleEntry* sample) override {
+ if (!pid_filter_.empty() &&
+ pid_filter_.find(sample->thread->pid) == pid_filter_.end()) {
+ return false;
+ }
+ if (!tid_filter_.empty() &&
+ tid_filter_.find(sample->thread->tid) == tid_filter_.end()) {
+ return false;
+ }
+ if (!comm_filter_.empty() &&
+ comm_filter_.find(sample->thread_comm) == comm_filter_.end()) {
+ return false;
+ }
+ if (!dso_filter_.empty() &&
+ dso_filter_.find(sample->map->dso->Path()) == dso_filter_.end()) {
+ return false;
+ }
+ return true;
+ }
+
+ void UpdateSummary(const SampleEntry* sample) override {
+ total_samples_ += sample->sample_count;
+ total_period_ += sample->period;
+ }
+
+ void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override {
+ sample1->period += sample2->period;
+ sample1->accumulated_period += sample2->accumulated_period;
+ sample1->sample_count += sample2->sample_count;
+ }
+
+ private:
+ ThreadTree* thread_tree_;
+
+ 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_;
+};
+
+using ReportCmdSampleTreeSorter = SampleTreeSorter<SampleEntry>;
+using ReportCmdSampleTreeDisplayer =
+ SampleTreeDisplayer<SampleEntry, SampleTree>;
+
+struct EventAttrWithName {
+ perf_event_attr attr;
+ std::string name;
+ std::vector<uint64_t> event_ids;
+};
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"),
+ // clang-format off
+"Usage: simpleperf report [options]\n"
+"-b Use the branch-to addresses in sampled take branches instead of the\n"
+" instruction addresses. Only valid for perf.data recorded with -b/-j\n"
+" option.\n"
+"--children Print the overhead accumulated by appearing in the callchain.\n"
+"--comms comm1,comm2,... Report only for selected comms.\n"
+"--dsos dso1,dso2,... Report only for selected dsos.\n"
+"-g [callee|caller] Print call graph. If callee mode is used, the graph\n"
+" shows how functions are called from others. Otherwise,\n"
+" the graph shows how functions call others.\n"
+" 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"
+"--pids pid1,pid2,... Report only for selected pids.\n"
+"--sort key1,key2,... Select the keys to sort and print the report.\n"
+" Possible keys include pid, tid, comm, dso, symbol,\n"
+" dso_from, dso_to, symbol_from, symbol_to.\n"
+" dso_from, dso_to, symbol_from, symbol_to can only be\n"
+" used with -b option.\n"
+" Default keys are \"comm,pid,tid,dso,symbol\"\n"
+"--symfs <dir> Look for files with symbols relative to this directory.\n"
+"--tids tid1,tid2,... Report only for selected tids.\n"
+"--vmlinux <file> Parse kernel symbols from <file>.\n"
+ // clang-format on
+ ),
record_filename_("perf.data"),
record_file_arch_(GetBuildArch()),
use_branch_address_(false),
+ system_wide_collection_(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));
- }
+ callgraph_show_callee_(true) {}
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 ReadSampleTreeFromRecordFile();
+ bool ProcessRecord(std::unique_ptr<Record> record);
+ bool ProcessTracingData(const std::vector<char>& data);
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);
+ void PrintReportContext(FILE* fp);
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_;
+ std::vector<EventAttrWithName> event_attrs_;
ThreadTree thread_tree_;
- std::unique_ptr<SampleTree> sample_tree_;
+ SampleTree sample_tree_;
+ std::unique_ptr<ReportCmdSampleTreeBuilder> sample_tree_builder_;
+ std::unique_ptr<ReportCmdSampleTreeSorter> sample_tree_sorter_;
+ std::unique_ptr<ReportCmdSampleTreeDisplayer> sample_tree_displayer_;
bool use_branch_address_;
std::string record_cmdline_;
+ bool system_wide_collection_;
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) {
@@ -339,7 +322,9 @@
return false;
}
ScopedCurrentArch scoped_arch(record_file_arch_);
- ReadSampleTreeFromRecordFile();
+ if (!ReadSampleTreeFromRecordFile()) {
+ return false;
+ }
// 3. Show collected information.
if (!PrintReport()) {
@@ -366,7 +351,8 @@
} 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);
+ std::unordered_set<std::string>& filter =
+ (args[i] == "--comms" ? comm_filter : dso_filter);
if (!NextArgumentOrError(args, &i)) {
return false;
}
@@ -418,7 +404,8 @@
}
ids.push_back(id);
}
- std::unordered_set<int>& filter = (args[i] == "--pids" ? pid_filter : tid_filter);
+ std::unordered_set<int>& filter =
+ (args[i] == "--pids" ? pid_filter : tid_filter);
filter.insert(ids.begin(), ids.end());
} else if (args[i] == "--sort") {
@@ -451,191 +438,111 @@
Dso::SetVmlinux(vmlinux);
}
- if (!accumulate_callchain_) {
- displayable_items_.push_back(
- std::unique_ptr<Displayable>(new SelfOverheadItem(*sample_tree_, "Overhead")));
+ SampleDisplayer<SampleEntry, SampleTree> displayer;
+ SampleComparator<SampleEntry> comparator;
+
+ if (accumulate_callchain_) {
+ displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead);
+ displayer.AddDisplayFunction("Self", DisplaySelfOverhead);
} 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_)));
+ displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead);
+ }
+ if (print_callgraph_) {
+ displayer.AddExclusiveDisplayFunction(DisplayCallgraph);
}
if (print_sample_count) {
- displayable_items_.push_back(std::unique_ptr<Displayable>(new SampleCountItem));
+ displayer.AddDisplayFunction("Sample", DisplaySampleCount);
}
+
for (auto& key : sort_keys) {
- if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) {
+ 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);
+ comparator.AddCompareFunction(ComparePid);
+ displayer.AddDisplayFunction("Pid", DisplayPid);
} else if (key == "tid") {
- TidItem* item = new TidItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareTid);
+ displayer.AddDisplayFunction("Tid", DisplayTid);
} else if (key == "comm") {
- CommItem* item = new CommItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareComm);
+ displayer.AddDisplayFunction("Command", DisplayComm);
} else if (key == "dso") {
- DsoItem* item = new DsoItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareDso);
+ displayer.AddDisplayFunction("Shared Object", DisplayDso);
} else if (key == "symbol") {
- SymbolItem* item = new SymbolItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareSymbol);
+ displayer.AddDisplayFunction("Symbol", DisplaySymbol);
} else if (key == "dso_from") {
- DsoFromItem* item = new DsoFromItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareDsoFrom);
+ displayer.AddDisplayFunction("Source Shared Object", DisplayDsoFrom);
} else if (key == "dso_to") {
- DsoToItem* item = new DsoToItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareDso);
+ displayer.AddDisplayFunction("Target Shared Object", DisplayDso);
} else if (key == "symbol_from") {
- SymbolFromItem* item = new SymbolFromItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareSymbolFrom);
+ displayer.AddDisplayFunction("Source Symbol", DisplaySymbolFrom);
} else if (key == "symbol_to") {
- SymbolToItem* item = new SymbolToItem;
- displayable_items_.push_back(std::unique_ptr<Displayable>(item));
- comparable_items_.push_back(item);
+ comparator.AddCompareFunction(CompareSymbol);
+ displayer.AddDisplayFunction("Target Symbol", DisplaySymbol);
} else {
LOG(ERROR) << "Unknown sort key: " << key;
return false;
}
}
- sample_tree_->SetFilters(pid_filter, tid_filter, comm_filter, dso_filter);
+
+ sample_tree_builder_.reset(
+ new ReportCmdSampleTreeBuilder(comparator, &thread_tree_));
+ sample_tree_builder_->SetFilters(pid_filter, tid_filter, comm_filter,
+ dso_filter);
+
+ SampleComparator<SampleEntry> sort_comparator;
+ sort_comparator.AddCompareFunction(CompareTotalPeriod);
+ sort_comparator.AddComparator(comparator);
+ sample_tree_sorter_.reset(new ReportCmdSampleTreeSorter(sort_comparator));
+ sample_tree_displayer_.reset(new ReportCmdSampleTreeDisplayer(displayer));
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;
+ std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
+ for (const auto& attr_with_id : attrs) {
+ EventAttrWithName attr;
+ attr.attr = *attr_with_id.attr;
+ attr.event_ids = attr_with_id.ids;
+ attr.name = GetEventNameByAttr(attr.attr);
+ event_attrs_.push_back(attr);
}
- 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;
+ if (use_branch_address_) {
+ bool has_branch_stack = true;
+ for (const auto& attr : event_attrs_) {
+ if ((attr.attr.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
+ has_branch_stack = false;
+ break;
+ }
+ }
+ if (!has_branch_stack) {
+ 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<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);
+ std::string arch =
+ record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
if (!arch.empty()) {
record_file_arch_ = GetArchType(arch);
if (record_file_arch_ == ARCH_UNSUPPORTED) {
@@ -646,138 +553,118 @@
std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
if (!cmdline.empty()) {
record_cmdline_ = android::base::Join(cmdline, ' ');
+ // TODO: the code to detect system wide collection option is fragile, remove
+ // it once we can do cross unwinding.
+ for (size_t i = 0; i < cmdline.size(); i++) {
+ std::string& s = cmdline[i];
+ if (s == "-a") {
+ system_wide_collection_ = true;
+ break;
+ } else if (s == "--call-graph" || s == "--cpu" || s == "-e" ||
+ s == "-f" || s == "-F" || s == "-j" || s == "-m" ||
+ s == "-o" || s == "-p" || s == "-t") {
+ i++;
+ } else if (!s.empty() && s[0] != '-') {
+ break;
+ }
+ }
+ }
+ if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
+ std::vector<char> tracing_data;
+ if (!record_file_reader_->ReadFeatureSection(
+ PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) {
+ return false;
+ }
+ if (!ProcessTracingData(tracing_data)) {
+ return false;
+ }
}
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;
+bool ReportCommand::ReadSampleTreeFromRecordFile() {
+ thread_tree_.AddThread(0, 0, "swapper");
+ sample_tree_builder_->SetBranchSampleOption(use_branch_address_);
+ // Normally do strict arch check when unwinding stack. But allow unwinding
+ // 32-bit processes on 64-bit devices for system wide profiling.
+ bool strict_unwind_arch_check = !system_wide_collection_;
+ sample_tree_builder_->SetCallChainSampleOptions(
+ accumulate_callchain_, print_callgraph_, !callgraph_show_callee_,
+ strict_unwind_arch_check);
+ if (!record_file_reader_->ReadDataSection(
+ [this](std::unique_ptr<Record> record) {
+ return ProcessRecord(std::move(record));
+ })) {
+ return false;
+ }
+ sample_tree_ = sample_tree_builder_->GetSampleTree();
+ sample_tree_sorter_->Sort(sample_tree_.samples, print_callgraph_);
+ return true;
+}
+
+bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
+ thread_tree_.Update(*record);
+ if (record->type() == PERF_RECORD_SAMPLE) {
+ sample_tree_builder_->ProcessSampleRecord(
+ *static_cast<const SampleRecord*>(record.get()));
+ } else if (record->type() == PERF_RECORD_TRACING_DATA) {
+ const auto& r = *static_cast<TracingDataRecord*>(record.get());
+ if (!ProcessTracingData(r.data)) {
+ return false;
}
}
- return 0;
+ return true;
+}
+
+bool ReportCommand::ProcessTracingData(const std::vector<char>& data) {
+ Tracing tracing(data);
+ for (auto& attr : event_attrs_) {
+ if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
+ uint64_t trace_event_id = attr.attr.config;
+ attr.name = tracing.GetTracingEventNameHavingId(trace_event_id);
+ }
+ }
+ return true;
}
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) {
+ FILE* report_fp = stdout;
+ if (!report_filename_.empty()) {
+ 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_);
+ 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) {
+ PrintReportContext(report_fp);
+ sample_tree_displayer_->DisplaySamples(report_fp, sample_tree_.samples,
+ &sample_tree_);
+ 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);
- }
+void ReportCommand::PrintReportContext(FILE* report_fp) {
if (!record_cmdline_.empty()) {
- fprintf(report_fp_, "Cmdline: %s\n", record_cmdline_.c_str());
+ 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());
+ fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
+ for (const auto& attr : event_attrs_) {
+ fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
+ attr.attr.type, attr.attr.config);
+ }
+ fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree_.total_samples);
+ fprintf(report_fp, "Event count: %" PRIu64 "\n\n", sample_tree_.total_period);
}
-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()));
- }
-}
+} // namespace
void RegisterReportCommand() {
- RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); });
+ RegisterCommand("report",
+ [] { return std::unique_ptr<Command>(new ReportCommand()); });
}
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp
new file mode 100644
index 0000000..3bb1290
--- /dev/null
+++ b/simpleperf/cmd_report_sample.cpp
@@ -0,0 +1,378 @@
+/*
+ * 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 <inttypes.h>
+
+#include <memory>
+
+#include "system/extras/simpleperf/report_sample.pb.h"
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+#include "command.h"
+#include "record_file.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+namespace proto = simpleperf_report_proto;
+
+namespace {
+
+class ProtobufFileWriter : public google::protobuf::io::CopyingOutputStream {
+ public:
+ ProtobufFileWriter(FILE* out_fp) : out_fp_(out_fp) {}
+
+ bool Write(const void* buffer, int size) override {
+ return fwrite(buffer, size, 1, out_fp_) == 1;
+ }
+
+ private:
+ FILE* out_fp_;
+};
+
+class ProtobufFileReader : public google::protobuf::io::CopyingInputStream {
+ public:
+ ProtobufFileReader(FILE* in_fp) : in_fp_(in_fp) {}
+
+ int Read(void* buffer, int size) override {
+ return fread(buffer, 1, size, in_fp_);
+ }
+
+ private:
+ FILE* in_fp_;
+};
+
+class ReportSampleCommand : public Command {
+ public:
+ ReportSampleCommand()
+ : Command(
+ "report-sample", "report raw sample information in perf.data",
+ // clang-format off
+"Usage: simpleperf report-sample [options]\n"
+"--dump-protobuf-report <file>\n"
+" Dump report file generated by\n"
+" `simpleperf report-sample --protobuf -o <file>`.\n"
+"-i <file> Specify path of record file, default is perf.data.\n"
+"-o report_file_name Set report file name, default is stdout.\n"
+"--protobuf Use protobuf format in report_sample.proto to output samples.\n"
+" Need to set a report_file_name when using this option.\n"
+"--show-callchain Print callchain samples.\n"
+ // clang-format on
+ ),
+ record_filename_("perf.data"),
+ show_callchain_(false),
+ use_protobuf_(false),
+ report_fp_(nullptr),
+ coded_os_(nullptr),
+ sample_count_(0) {}
+
+ bool Run(const std::vector<std::string>& args) override;
+
+ private:
+ bool ParseOptions(const std::vector<std::string>& args);
+ bool DumpProtobufReport(const std::string& filename);
+ bool ProcessRecord(std::unique_ptr<Record> record);
+ bool PrintSampleRecordInProtobuf(const SampleRecord& record);
+ bool PrintSampleRecord(const SampleRecord& record);
+
+ std::string record_filename_;
+ std::unique_ptr<RecordFileReader> record_file_reader_;
+ std::string dump_protobuf_report_file_;
+ bool show_callchain_;
+ bool use_protobuf_;
+ ThreadTree thread_tree_;
+ std::string report_filename_;
+ FILE* report_fp_;
+ google::protobuf::io::CodedOutputStream* coded_os_;
+ size_t sample_count_;
+};
+
+bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
+ // 1. Parse options.
+ if (!ParseOptions(args)) {
+ return false;
+ }
+ if (!dump_protobuf_report_file_.empty()) {
+ return DumpProtobufReport(dump_protobuf_report_file_);
+ }
+ if (use_protobuf_) {
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ }
+
+ // 2. Open record file.
+ record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+ if (record_file_reader_ == nullptr) {
+ return false;
+ }
+
+ // 3. Prepare report output stream.
+ report_fp_ = stdout;
+ std::unique_ptr<FILE, decltype(&fclose)> fp(nullptr, fclose);
+ std::unique_ptr<ProtobufFileWriter> protobuf_writer;
+ std::unique_ptr<google::protobuf::io::CopyingOutputStreamAdaptor> protobuf_os;
+ std::unique_ptr<google::protobuf::io::CodedOutputStream> protobuf_coded_os;
+ if (!report_filename_.empty()) {
+ fp.reset(fopen(report_filename_.c_str(), use_protobuf_ ? "wb" : "w"));
+ if (fp == nullptr) {
+ PLOG(ERROR) << "failed to open " << report_filename_;
+ return false;
+ }
+ report_fp_ = fp.get();
+ }
+ if (use_protobuf_) {
+ protobuf_writer.reset(new ProtobufFileWriter(report_fp_));
+ protobuf_os.reset(new google::protobuf::io::CopyingOutputStreamAdaptor(
+ protobuf_writer.get()));
+ protobuf_coded_os.reset(
+ new google::protobuf::io::CodedOutputStream(protobuf_os.get()));
+ coded_os_ = protobuf_coded_os.get();
+ }
+
+ // 4. Read record file, and print samples online.
+ if (!record_file_reader_->ReadDataSection(
+ [this](std::unique_ptr<Record> record) {
+ return ProcessRecord(std::move(record));
+ })) {
+ return false;
+ }
+ LOG(INFO) << "report " << sample_count_ << " samples in all.";
+
+ if (use_protobuf_) {
+ coded_os_->WriteLittleEndian32(0);
+ if (coded_os_->HadError()) {
+ LOG(ERROR) << "print protobuf report failed";
+ return false;
+ }
+ protobuf_coded_os.reset(nullptr);
+ google::protobuf::ShutdownProtobufLibrary();
+ } else {
+ fflush(report_fp_);
+ }
+ if (ferror(report_fp_) != 0) {
+ PLOG(ERROR) << "print report failed";
+ return false;
+ }
+ return true;
+}
+
+bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
+ for (size_t i = 0; i < args.size(); ++i) {
+ if (args[i] == "--dump-protobuf-report") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ dump_protobuf_report_file_ = args[i];
+ } else if (args[i] == "-i") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ record_filename_ = args[i];
+ } else if (args[i] == "-o") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ report_filename_ = args[i];
+ } else if (args[i] == "--protobuf") {
+ use_protobuf_ = true;
+ } else if (args[i] == "--show-callchain") {
+ show_callchain_ = true;
+ } else {
+ ReportUnknownOption(args, i);
+ return false;
+ }
+ }
+
+ if (use_protobuf_ && report_filename_.empty()) {
+ LOG(ERROR) << "please specify a report filename to write protobuf data";
+ return false;
+ }
+ return true;
+}
+
+bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "rb"),
+ fclose);
+ if (fp == nullptr) {
+ PLOG(ERROR) << "failed to open " << filename;
+ return false;
+ }
+ ProtobufFileReader protobuf_reader(fp.get());
+ google::protobuf::io::CopyingInputStreamAdaptor adaptor(&protobuf_reader);
+ google::protobuf::io::CodedInputStream coded_is(&adaptor);
+ while (true) {
+ uint32_t size;
+ if (!coded_is.ReadLittleEndian32(&size)) {
+ PLOG(ERROR) << "failed to read " << filename;
+ return false;
+ }
+ if (size == 0) {
+ break;
+ }
+ auto limit = coded_is.PushLimit(size);
+ proto::Record proto_record;
+ if (!proto_record.ParseFromCodedStream(&coded_is)) {
+ PLOG(ERROR) << "failed to read " << filename;
+ return false;
+ }
+ coded_is.PopLimit(limit);
+ if (proto_record.type() != proto::Record_Type_SAMPLE) {
+ LOG(ERROR) << "unexpected record type " << proto_record.type();
+ return false;
+ }
+ auto& sample = proto_record.sample();
+ static size_t sample_count = 0;
+ PrintIndented(0, "sample %zu:\n", ++sample_count);
+ PrintIndented(1, "time: %" PRIu64 "\n", sample.time());
+ PrintIndented(1, "callchain:\n");
+ for (int j = 0; j < sample.callchain_size(); ++j) {
+ const proto::Sample_CallChainEntry& callchain = sample.callchain(j);
+ PrintIndented(2, "ip: %" PRIx64 "\n", callchain.ip());
+ PrintIndented(2, "dso: %s\n", callchain.file().c_str());
+ PrintIndented(2, "symbol: %s\n", callchain.symbol().c_str());
+ }
+ }
+ google::protobuf::ShutdownProtobufLibrary();
+ return true;
+}
+
+bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) {
+ thread_tree_.Update(*record);
+ if (record->type() == PERF_RECORD_SAMPLE) {
+ sample_count_++;
+ auto& r = *static_cast<const SampleRecord*>(record.get());
+ if (use_protobuf_) {
+ return PrintSampleRecordInProtobuf(r);
+ } else {
+ return PrintSampleRecord(r);
+ }
+ }
+ return true;
+}
+
+bool ReportSampleCommand::PrintSampleRecordInProtobuf(const SampleRecord& r) {
+ proto::Record proto_record;
+ proto_record.set_type(proto::Record_Type_SAMPLE);
+ proto::Sample* sample = proto_record.mutable_sample();
+ sample->set_time(r.time_data.time);
+ proto::Sample_CallChainEntry* callchain = sample->add_callchain();
+ callchain->set_ip(r.ip_data.ip);
+
+ bool in_kernel = r.InKernel();
+ 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);
+ const Symbol* symbol = thread_tree_.FindSymbol(map, r.ip_data.ip);
+ callchain->set_symbol(symbol->DemangledName());
+ callchain->set_file(map->dso->Path());
+
+ if (show_callchain_) {
+ const std::vector<uint64_t>& ips = r.callchain_data.ips;
+ 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(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
+ << ip << std::dec;
+ }
+ } else {
+ if (first_ip) {
+ first_ip = false;
+ // Remove duplication with sample ip.
+ if (ip == r.ip_data.ip) {
+ continue;
+ }
+ }
+ const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
+ const Symbol* symbol = thread_tree_.FindSymbol(map, ip);
+ callchain = sample->add_callchain();
+ callchain->set_ip(ip);
+ callchain->set_symbol(symbol->DemangledName());
+ callchain->set_file(map->dso->Path());
+ }
+ }
+ }
+ coded_os_->WriteLittleEndian32(proto_record.ByteSize());
+ if (!proto_record.SerializeToCodedStream(coded_os_)) {
+ LOG(ERROR) << "failed to write sample to protobuf";
+ return false;
+ }
+ return true;
+}
+
+bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r) {
+ bool in_kernel = r.InKernel();
+ 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);
+ const Symbol* symbol = thread_tree_.FindSymbol(map, r.ip_data.ip);
+ FprintIndented(report_fp_, 0, "sample:\n");
+ FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", r.time_data.time);
+ FprintIndented(report_fp_, 1, "ip: %" PRIx64 "\n", r.ip_data.ip);
+ FprintIndented(report_fp_, 1, "dso: %s\n", map->dso->Path().c_str());
+ FprintIndented(report_fp_, 1, "symbol: %s\n", symbol->DemangledName());
+
+ if (show_callchain_) {
+ FprintIndented(report_fp_, 1, "callchain:\n");
+ const std::vector<uint64_t>& ips = r.callchain_data.ips;
+ 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(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
+ << ip;
+ }
+ } else {
+ if (first_ip) {
+ first_ip = false;
+ // Remove duplication with sample ip.
+ if (ip == r.ip_data.ip) {
+ continue;
+ }
+ }
+ const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
+ const Symbol* symbol = thread_tree_.FindSymbol(map, ip);
+ FprintIndented(report_fp_, 2, "ip: %" PRIx64 "\n", ip);
+ FprintIndented(report_fp_, 2, "dso: %s\n", map->dso->Path().c_str());
+ FprintIndented(report_fp_, 2, "symbol: %s\n", symbol->DemangledName());
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+void RegisterReportSampleCommand() {
+ RegisterCommand("report-sample", [] {
+ return std::unique_ptr<Command>(new ReportSampleCommand());
+ });
+}
diff --git a/simpleperf/cmd_report_sample_test.cpp b/simpleperf/cmd_report_sample_test.cpp
new file mode 100644
index 0000000..42df179
--- /dev/null
+++ b/simpleperf/cmd_report_sample_test.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include <android-base/test_utils.h>
+
+#include "command.h"
+#include "get_test_data.h"
+
+static std::unique_ptr<Command> ReportSampleCmd() {
+ return CreateCommandInstance("report-sample");
+}
+
+TEST(cmd_report_sample, text) {
+ ASSERT_TRUE(
+ ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS)}));
+}
+
+TEST(cmd_report_sample, output_option) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(ReportSampleCmd()->Run(
+ {"-i", GetTestData(PERF_DATA_WITH_SYMBOLS), "-o", tmpfile.path}));
+}
+
+TEST(cmd_report_sample, show_callchain_option) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(ReportSampleCmd()->Run({"-i", GetTestData(CALLGRAPH_FP_PERF_DATA),
+ "-o", tmpfile.path, "--show-callchain"}));
+}
+
+TEST(cmd_report_sample, protobuf_option) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS),
+ "-o", tmpfile.path, "--protobuf"}));
+ ASSERT_TRUE(ReportSampleCmd()->Run({"--dump-protobuf-report", tmpfile.path}));
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index bcf8b6e..f987a7a 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -36,16 +36,18 @@
class ReportCommandTest : public ::testing::Test {
protected:
- void Report(const std::string perf_data,
- const std::vector<std::string>& add_args = std::vector<std::string>()) {
+ 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>()) {
+ 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};
+ 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));
@@ -74,11 +76,18 @@
ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
}
+TEST_F(ReportCommandTest, report_symbol_from_elf_file_with_mini_debug_info) {
+ Report(PERF_DATA_WITH_MINI_DEBUG_INFO);
+ 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) {
+ while (line_index < lines.size() &&
+ lines[line_index].find("Pid") == std::string::npos) {
line_index++;
}
ASSERT_LT(line_index + 2, lines.size());
@@ -88,7 +97,8 @@
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) {
+ while (line_index < lines.size() &&
+ lines[line_index].find("Overhead") == std::string::npos) {
line_index++;
}
ASSERT_LT(line_index + 1, lines.size());
@@ -106,7 +116,8 @@
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) {
+ if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second,
+ name) == 3) {
map.insert(std::make_pair(name, pair));
}
}
@@ -157,9 +168,11 @@
ASSERT_TRUE(CheckCallerMode(lines));
}
-static bool AllItemsWithString(std::vector<std::string>& lines, const std::vector<std::string>& strs) {
+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) {
+ 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()) {
@@ -248,19 +261,41 @@
}
}
}
- ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>("GlobalFunc", "CalledFunc")),
+ 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")),
+ 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(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
+ std::string::npos);
ASSERT_NE(content.find("Func2"), std::string::npos);
}
+TEST_F(ReportCommandTest, report_more_than_one_event_types) {
+ Report(PERF_DATA_WITH_TWO_EVENT_TYPES);
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("cpu-cycles"), std::string::npos);
+ ASSERT_NE(content.find("cpu-clock"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, report_kernel_symbol) {
+ Report(PERF_DATA_WITH_KERNEL_SYMBOL);
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("perf_event_aux"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, report_dumped_symbols) {
+ Report(PERF_DATA_WITH_SYMBOLS);
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("page_fault"), std::string::npos);
+}
+
#if defined(__linux__)
static std::unique_ptr<Command> RecordCmd() {
@@ -270,27 +305,30 @@
TEST_F(ReportCommandTest, dwarf_callgraph) {
if (IsDwarfCallChainSamplingSupported()) {
TemporaryFile tmp_file;
- ASSERT_TRUE(RecordCmd()->Run({"-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
+ 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.";
+ 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.
+ // 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(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";
+ GTEST_LOG_(INFO)
+ << "This test does nothing as it is only run on arm64 devices";
}
}
#endif
-
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index 488e731..64ed60e 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -38,6 +38,8 @@
#include "utils.h"
#include "workload.h"
+namespace {
+
static std::vector<std::string> default_measured_event_types{
"cpu-cycles", "stalled-cycles-frontend", "stalled-cycles-backend",
"instructions", "branch-instructions", "branch-misses",
@@ -45,33 +47,36 @@
};
static volatile bool signaled;
-static void signal_handler(int) {
- signaled = true;
-}
+static void signal_handler(int) { signaled = true; }
class StatCommand : public Command {
public:
StatCommand()
- : Command("stat", "gather performance counter information",
- "Usage: simpleperf stat [options] [command [command-args]]\n"
- " Gather performance counter information of running [command].\n"
- " -a Collect system-wide information.\n"
- " --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"),
+ : Command(
+ "stat", "gather performance counter information",
+ // clang-format off
+"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"
+"--group event1[:modifier],event2[:modifier2],...\n"
+" Similar to -e option. But events specified in the same --group\n"
+" option are monitored as a group, and scheduled in and out at the\n"
+" same time.\n"
+"--no-inherit Don't stat created child threads/processes.\n"
+"-p pid1,pid2,... Stat events on existing processes. Mutually exclusive with -a.\n"
+"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\n"
+"--verbose Show result in verbose mode.\n"
+ // clang-format on
+ ),
verbose_mode_(false),
system_wide_collection_(false),
child_inherit_(true) {
@@ -83,18 +88,18 @@
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 ParseOptions(const std::vector<std::string>& args,
+ std::vector<std::string>* non_option_args);
bool AddDefaultMeasuredEventTypes();
- bool SetEventSelection();
- bool ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec);
+ void SetEventSelectionFlags();
+ bool ShowCounters(const std::vector<CountersInfo>& counters,
+ double duration_in_sec);
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_;
@@ -110,14 +115,12 @@
if (!ParseOptions(args, &workload_args)) {
return false;
}
- if (measured_event_types_.empty()) {
+ if (event_selection_set_.empty()) {
if (!AddDefaultMeasuredEventTypes()) {
return false;
}
}
- if (!SetEventSelection()) {
- return false;
- }
+ SetEventSelectionFlags();
// 2. Create workload.
std::unique_ptr<Workload> workload;
@@ -132,7 +135,8 @@
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";
+ LOG(ERROR)
+ << "No threads to monitor. Try `simpleperf help stat` for help\n";
return false;
}
}
@@ -143,7 +147,11 @@
return false;
}
} else {
- if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) {
+ if (cpus_.empty()) {
+ cpus_ = {-1};
+ }
+ if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_,
+ cpus_)) {
return false;
}
}
@@ -164,7 +172,9 @@
return false;
}
double duration_in_sec =
- std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
+ std::chrono::duration_cast<std::chrono::duration<double>>(end_time -
+ start_time)
+ .count();
if (!ShowCounters(counters, duration_in_sec)) {
return false;
}
@@ -189,10 +199,18 @@
}
std::vector<std::string> event_types = android::base::Split(args[i], ",");
for (auto& event_type : event_types) {
- if (!AddMeasuredEventType(event_type)) {
+ if (!event_selection_set_.AddEventType(event_type)) {
return false;
}
}
+ } else if (args[i] == "--group") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ std::vector<std::string> event_types = android::base::Split(args[i], ",");
+ if (!event_selection_set_.AddEventGroup(event_types)) {
+ return false;
+ }
} else if (args[i] == "--no-inherit") {
child_inherit_ = false;
} else if (args[i] == "-p") {
@@ -217,9 +235,11 @@
}
}
- monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end());
+ 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.";
+ LOG(ERROR) << "Stat system wide and existing processes/threads can't be "
+ "used at the same time.";
return false;
}
@@ -232,42 +252,31 @@
return true;
}
-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;
- }
- measured_event_types_.push_back(*event_type_modifier);
- return true;
-}
-
bool StatCommand::AddDefaultMeasuredEventTypes() {
for (auto& name : default_measured_event_types) {
- // It is not an error when some event types in the default list are not supported by the kernel.
+ // It is not an error when some event types in the default list are not
+ // supported by the kernel.
const EventType* type = FindEventTypeByName(name);
- if (type != nullptr && IsEventAttrSupportedByKernel(CreateDefaultPerfEventAttr(*type))) {
- AddMeasuredEventType(name);
+ if (type != nullptr &&
+ IsEventAttrSupportedByKernel(CreateDefaultPerfEventAttr(*type))) {
+ if (!event_selection_set_.AddEventType(name)) {
+ return false;
+ }
}
}
- if (measured_event_types_.empty()) {
+ if (event_selection_set_.empty()) {
LOG(ERROR) << "Failed to add any supported default measured types";
return false;
}
return true;
}
-bool StatCommand::SetEventSelection() {
- for (auto& event_type : measured_event_types_) {
- if (!event_selection_set_.AddEventType(event_type)) {
- return false;
- }
- }
+void StatCommand::SetEventSelectionFlags() {
event_selection_set_.SetInherit(child_inherit_);
- return true;
}
-static std::string ReadableCountValue(uint64_t count,
- const EventTypeAndModifier& event_type_modifier) {
+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;
@@ -285,36 +294,57 @@
}
struct CounterSummary {
- const EventTypeAndModifier* event_type;
+ const EventSelection* selection;
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;
+static const CounterSummary* FindMatchedSummary(
+ const std::vector<CounterSummary>& summaries, const std::string& type_name,
+ const CounterSummary& summary) {
+ std::string modifier = summary.selection->event_type_modifier.modifier;
+ for (const auto& t : summaries) {
+ if (t.selection->event_type_modifier.event_type.name == type_name &&
+ t.selection->event_type_modifier.modifier == modifier) {
+ // The matched summary should be in the same group as summary,
+ // or both have scale == 1.0 (enabled all the time).
+ if (t.selection->group_id == summary.selection->group_id ||
+ (t.scale == 1.0 && summary.scale == 1.0)) {
+ return &t;
+ }
+ }
+ }
+ return nullptr;
+}
+
+static std::string GetCommentForSummary(
+ const CounterSummary& summary, const std::vector<CounterSummary>& summaries,
+ double duration_in_sec) {
+ const EventTypeAndModifier& type_modifier =
+ summary.selection->event_type_modifier;
+ const std::string& type_name = type_modifier.event_type.name;
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);
+ double used_cpus = run_sec / (duration_in_sec / summary.scale);
+ return android::base::StringPrintf("%lf cpus used", used_cpus);
}
if (type_name == "cpu-clock") {
return "";
}
if (type_name == "cpu-cycles") {
- double hz = summary.count / duration_in_sec;
+ double hz = summary.count / (duration_in_sec / summary.scale);
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);
- }
+ const CounterSummary* cpu_cycles_summary =
+ FindMatchedSummary(summaries, "cpu-cycles", summary);
+ if (cpu_cycles_summary != nullptr) {
+ double cycles_per_instruction =
+ static_cast<double>(cpu_cycles_summary->count) / summary.count;
+ return android::base::StringPrintf("%lf cycles per instruction",
+ cycles_per_instruction);
}
}
if (android::base::EndsWith(type_name, "-misses")) {
@@ -326,14 +356,15 @@
} 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);
- }
+ const CounterSummary* matched_summary =
+ FindMatchedSummary(summaries, s, summary);
+ if (matched_summary != nullptr && matched_summary->count != 0) {
+ double miss_rate =
+ static_cast<double>(summary.count) / matched_summary->count;
+ return android::base::StringPrintf("%lf%% miss rate", miss_rate * 100);
}
}
- double rate = summary.count / duration_in_sec;
+ double rate = summary.count / (duration_in_sec / summary.scale);
if (rate > 1e9) {
return android::base::StringPrintf("%.3lf G/sec", rate / 1e9);
}
@@ -346,19 +377,22 @@
return android::base::StringPrintf("%.3lf /sec", rate);
}
-bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec) {
+bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
+ double duration_in_sec) {
printf("Performance counter statistics:\n\n");
if (verbose_mode_) {
for (auto& counters_info : counters) {
- const EventTypeAndModifier* event_type = counters_info.event_type;
+ const EventTypeAndModifier& event_type =
+ counters_info.selection->event_type_modifier;
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);
+ 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);
}
}
}
@@ -369,25 +403,24 @@
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;
- }
- 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 {
- scale = static_cast<double>(time_enabled_sum) / time_running_sum;
- scaled_count = static_cast<uint64_t>(scale * value_sum);
+ // If time_running is 0, the program has never run on this event and we
+ // shouldn't summarize it.
+ if (counter_info.counter.time_running != 0) {
+ value_sum += counter_info.counter.value;
+ time_enabled_sum += counter_info.counter.time_enabled;
+ time_running_sum += counter_info.counter.time_running;
}
}
+ double scale = 1.0;
+ if (time_running_sum < time_enabled_sum && time_running_sum != 0) {
+ scale = static_cast<double>(time_enabled_sum) / time_running_sum;
+ }
CounterSummary summary;
- summary.event_type = counters_info.event_type;
- summary.count = scaled_count;
+ summary.selection = counters_info.selection;
+ summary.count = value_sum;
summary.scale = scale;
- summary.readable_count_str = ReadableCountValue(summary.count, *summary.event_type);
+ summary.readable_count_str = ReadableCountValue(
+ summary.count, summary.selection->event_type_modifier);
summaries.push_back(summary);
}
@@ -399,22 +432,31 @@
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());
+ count_column_width =
+ std::max(count_column_width, summary.readable_count_str.size());
+ name_column_width = std::max(
+ name_column_width, summary.selection->event_type_modifier.name.size());
+ comment_column_width =
+ std::max(comment_column_width, summary.comment.size());
}
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(" %*s %-*s # %-*s (%.0lf%%)\n",
+ static_cast<int>(count_column_width),
+ summary.readable_count_str.c_str(),
+ static_cast<int>(name_column_width),
+ summary.selection->event_type_modifier.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;
}
+} // namespace
+
void RegisterStatCommand() {
- RegisterCommand("stat", [] { return std::unique_ptr<Command>(new StatCommand); });
+ RegisterCommand("stat",
+ [] { return std::unique_ptr<Command>(new StatCommand); });
}
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 45ed3dc..d0adfe5 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -26,18 +26,14 @@
return CreateCommandInstance("stat");
}
-TEST(stat_cmd, no_options) {
- ASSERT_TRUE(StatCmd()->Run({"sleep", "1"}));
-}
+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_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "sleep", "1"})));
}
TEST(stat_cmd, verbose_option) {
@@ -45,16 +41,17 @@
}
TEST(stat_cmd, tracepoint_event) {
- if (IsRoot()) {
- ASSERT_TRUE(StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"}));
- }
+ TEST_IN_ROOT(ASSERT_TRUE(
+ StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"})));
}
TEST(stat_cmd, event_modifier) {
- ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
+ ASSERT_TRUE(
+ StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
}
-void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads) {
+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.
@@ -68,8 +65,8 @@
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());
+ std::string pid_list = android::base::StringPrintf(
+ "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
ASSERT_TRUE(StatCmd()->Run({"-p", pid_list, "sleep", "1"}));
}
@@ -77,18 +74,22 @@
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());
+ 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(stat_cmd, no_monitored_threads) {
- ASSERT_FALSE(StatCmd()->Run({""}));
-}
+TEST(stat_cmd, no_monitored_threads) { ASSERT_FALSE(StatCmd()->Run({""})); }
TEST(stat_cmd, cpu_option) {
ASSERT_TRUE(StatCmd()->Run({"--cpu", "0", "sleep", "1"}));
- if (IsRoot()) {
- ASSERT_TRUE(StatCmd()->Run({"--cpu", "0", "-a", "sleep", "1"}));
- }
+ TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"--cpu", "0", "-a", "sleep", "1"})));
+}
+
+TEST(stat_cmd, group_option) {
+ ASSERT_TRUE(
+ StatCmd()->Run({"--group", "cpu-cycles,cpu-clock", "sleep", "1"}));
+ ASSERT_TRUE(StatCmd()->Run({"--group", "cpu-cycles,cpu-clock", "--group",
+ "cpu-cycles:u,cpu-clock:u", "--group",
+ "cpu-cycles:k,cpu-clock:k", "sleep", "1"}));
}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index 3416653..a5ae2a8 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -72,8 +72,10 @@
extern void RegisterDumpRecordCommand();
extern void RegisterHelpCommand();
extern void RegisterListCommand();
+extern void RegisterKmemCommand();
extern void RegisterRecordCommand();
extern void RegisterReportCommand();
+extern void RegisterReportSampleCommand();
extern void RegisterStatCommand();
class CommandRegister {
@@ -81,7 +83,9 @@
CommandRegister() {
RegisterDumpRecordCommand();
RegisterHelpCommand();
+ RegisterKmemCommand();
RegisterReportCommand();
+ RegisterReportSampleCommand();
#if defined(__linux__)
RegisterListCommand();
RegisterRecordCommand();
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index 61cbc80..56962b1 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -24,7 +24,6 @@
#include <atomic>
#include <chrono>
-#include <condition_variable>
#include <thread>
#include <unordered_map>
@@ -32,14 +31,10 @@
#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");
-}
+#include "utils.h"
#if defined(__BIONIC__)
class ScopedMpdecisionKiller {
@@ -92,23 +87,61 @@
};
#endif
-static bool IsCpuOnline(int cpu) {
+static bool IsCpuOnline(int cpu, bool* has_error) {
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;
+ bool ret = android::base::ReadFileToString(filename, &content);
+ if (!ret) {
+ PLOG(ERROR) << "failed to read file " << filename;
+ *has_error = true;
+ return false;
+ }
+ *has_error = false;
return (content.find('1') != std::string::npos);
}
-static void SetCpuOnline(int cpu, bool online) {
- if (IsCpuOnline(cpu) == online) {
- return;
+static bool SetCpuOnline(int cpu, bool online) {
+ bool has_error;
+ bool ret = IsCpuOnline(cpu, &has_error);
+ if (has_error) {
+ return false;
+ }
+ if (ret == online) {
+ return true;
}
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";
+ ret = android::base::WriteStringToFile(content, filename);
+ if (!ret) {
+ ret = IsCpuOnline(cpu, &has_error);
+ if (has_error) {
+ return false;
+ }
+ if (online == ret) {
+ return true;
+ }
+ PLOG(ERROR) << "failed to write " << content << " to " << filename;
+ return false;
+ }
+ // Kernel needs time to offline/online cpus, so use a loop to wait here.
+ size_t retry_count = 0;
+ while (true) {
+ ret = IsCpuOnline(cpu, &has_error);
+ if (has_error) {
+ return false;
+ }
+ if (ret == online) {
+ break;
+ }
+ LOG(ERROR) << "reading cpu retry count = " << retry_count << ", requested = " << online
+ << ", real = " << ret;
+ if (++retry_count == 10000) {
+ LOG(ERROR) << "setting cpu " << cpu << (online ? " online" : " offline") << " seems not to take effect";
+ return false;
+ }
+ usleep(1000);
+ }
+ return true;
}
static int GetCpuCount() {
@@ -119,7 +152,12 @@
public:
CpuOnlineRestorer() {
for (int cpu = 1; cpu < GetCpuCount(); ++cpu) {
- online_map_[cpu] = IsCpuOnline(cpu);
+ bool has_error;
+ bool ret = IsCpuOnline(cpu, &has_error);
+ if (has_error) {
+ continue;
+ }
+ online_map_[cpu] = ret;
}
}
@@ -133,6 +171,26 @@
std::unordered_map<int, bool> online_map_;
};
+bool FindAHotpluggableCpu(int* hotpluggable_cpu) {
+ if (!IsRoot()) {
+ GTEST_LOG_(INFO) << "This test needs root privilege to hotplug cpu.";
+ return false;
+ }
+ for (int cpu = 1; cpu < GetCpuCount(); ++cpu) {
+ bool has_error;
+ bool online = IsCpuOnline(cpu, &has_error);
+ if (has_error) {
+ continue;
+ }
+ if (SetCpuOnline(cpu, !online)) {
+ *hotpluggable_cpu = cpu;
+ return true;
+ }
+ }
+ GTEST_LOG_(INFO) << "There is no hotpluggable cpu.";
+ return false;
+}
+
struct CpuToggleThreadArg {
int toggle_cpu;
std::atomic<bool> end_flag;
@@ -140,93 +198,94 @@
static void CpuToggleThread(CpuToggleThreadArg* arg) {
while (!arg->end_flag) {
- SetCpuOnline(arg->toggle_cpu, true);
- sleep(1);
- SetCpuOnline(arg->toggle_cpu, false);
- sleep(1);
+ CHECK(SetCpuOnline(arg->toggle_cpu, true));
+ CHECK(SetCpuOnline(arg->toggle_cpu, false));
}
}
-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 hotpluger.
+ int test_cpu;
+ if (!FindAHotpluggableCpu(&test_cpu)) {
+ return;
}
- // 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;
+ std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+ ASSERT_TRUE(event_type_modifier != nullptr);
+ perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+ attr.disabled = 0;
+ attr.enable_on_exec = 0;
+ const std::chrono::minutes test_duration(2); // Test for 2 minutes.
auto end_time = std::chrono::steady_clock::now() + test_duration;
size_t iterations = 0;
+
while (std::chrono::steady_clock::now() < end_time) {
+ std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, false);
+ if (event_fd == nullptr) {
+ // Failed to open because the test_cpu is offline.
+ continue;
+ }
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));
+ GTEST_LOG_(INFO) << "Test offline while recording for " << iterations << " times.";
}
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;
+// http://b/25193162.
+TEST(cpu_offline, offline_while_ioctl_enable) {
+ 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;
}
+ // Start cpu hotpluger.
+ int test_cpu;
+ if (!FindAHotpluggableCpu(&test_cpu)) {
+ return;
+ }
+ 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);
+
+ std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+ ASSERT_TRUE(event_type_modifier != nullptr);
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
- return EventFd::OpenEventFile(attr, getpid(), cpu);
+ attr.disabled = 1;
+ attr.enable_on_exec = 0;
+
+ const std::chrono::minutes test_duration(2); // Test for 2 minutes.
+ auto end_time = std::chrono::steady_clock::now() + test_duration;
+ size_t iterations = 0;
+
+ while (std::chrono::steady_clock::now() < end_time) {
+ std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, false);
+ if (event_fd == nullptr) {
+ // Failed to open because the test_cpu is offline.
+ continue;
+ }
+ // Wait a little for the event to be installed on test_cpu's perf context.
+ usleep(1000);
+ ASSERT_TRUE(event_fd->EnableEvent());
+ iterations++;
+ GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations << " times.";
+ }
+ cpu_toggle_arg.end_flag = true;
+ cpu_toggle_thread.join();
}
// http://b/19863147.
@@ -238,17 +297,24 @@
GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system.";
return;
}
+ int test_cpu;
+ if (!FindAHotpluggableCpu(&test_cpu)) {
+ return;
+ }
+ std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+ perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+ attr.disabled = 0;
+ attr.enable_on_exec = 0;
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(SetCpuOnline(test_cpu, true));
+ std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
ASSERT_TRUE(event_fd != nullptr);
- SetCpuOnline(toggle_cpu, false);
+ ASSERT_TRUE(SetCpuOnline(test_cpu, false));
event_fd = nullptr;
- event_fd = OpenHardwareEventOnCpu(record_cpu);
+ event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
ASSERT_TRUE(event_fd != nullptr);
}
}
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 9c33667..45ae124 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -23,6 +23,7 @@
#include <limits>
#include <vector>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include "environment.h"
@@ -36,8 +37,8 @@
: addr(addr),
len(len),
name_(symbol_name_allocator.AllocateString(name)),
- demangled_name_(nullptr) {
-}
+ demangled_name_(nullptr),
+ has_dumped_(false) {}
const char* Symbol::DemangledName() const {
if (demangled_name_ == nullptr) {
@@ -54,14 +55,14 @@
bool Dso::demangle_ = true;
std::string Dso::symfs_dir_;
std::string Dso::vmlinux_;
+std::string Dso::kallsyms_;
std::unordered_map<std::string, BuildId> Dso::build_id_map_;
size_t Dso::dso_count_;
-void Dso::SetDemangle(bool demangle) {
- demangle_ = demangle;
-}
+void Dso::SetDemangle(bool demangle) { demangle_ = demangle; }
-extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status);
+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_) {
@@ -106,14 +107,14 @@
return true;
}
-void Dso::SetVmlinux(const std::string& vmlinux) {
- vmlinux_ = vmlinux;
-}
+void Dso::SetVmlinux(const std::string& vmlinux) { vmlinux_ = vmlinux; }
-void Dso::SetBuildIds(const std::vector<std::pair<std::string, BuildId>>& build_ids) {
+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();
+ LOG(DEBUG) << "build_id_map: " << pair.first << ", "
+ << pair.second.ToString();
map.insert(pair);
}
build_id_map_ = std::move(map);
@@ -127,46 +128,53 @@
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));
+std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type,
+ const std::string& dso_path) {
+ static uint64_t id = 0;
+ return std::unique_ptr<Dso>(new Dso(dso_type, ++id, dso_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::Dso(DsoType type, uint64_t id, const std::string& path)
+ : type_(type),
+ id_(id),
+ path_(path),
+ min_vaddr_(std::numeric_limits<uint64_t>::max()),
+ is_loaded_(false),
+ has_dumped_(false) {
dso_count_++;
}
Dso::~Dso() {
if (--dso_count_ == 0) {
+ // Clean up global variables when no longer used.
symbol_name_allocator.Clear();
+ demangle_ = true;
+ symfs_dir_.clear();
+ vmlinux_.clear();
+ kallsyms_.clear();
+ build_id_map_.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_;
-}
+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;
+ // If symbols has been read from SymbolRecords, no need to load them from
+ // dso.
+ if (symbols_.empty()) {
+ if (!Load()) {
+ LOG(DEBUG) << "failed to load dso: " << path_;
+ return nullptr;
+ }
}
}
+ if (symbols_.empty()) {
+ return nullptr;
+ }
- auto it = std::upper_bound(symbols_.begin(), symbols_.end(), Symbol("", vaddr_in_dso, 0),
- SymbolComparator());
+ auto it = symbols_.upper_bound(Symbol("", vaddr_in_dso, 0));
if (it != symbols_.begin()) {
--it;
if (it->addr <= vaddr_in_dso && it->addr + it->len > vaddr_in_dso) {
@@ -183,7 +191,8 @@
BuildId build_id = GetExpectedBuildId(GetAccessiblePath());
uint64_t addr;
- if (ReadMinExecutableVirtualAddressFromElfFile(GetAccessiblePath(), build_id, &addr)) {
+ if (ReadMinExecutableVirtualAddressFromElfFile(GetAccessiblePath(),
+ build_id, &addr)) {
min_vaddr_ = addr;
}
}
@@ -210,14 +219,14 @@
}
}
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');
+ return (symbol.type == 'T' || symbol.type == 't' || symbol.type == 'W' ||
+ symbol.type == 'w');
}
bool Dso::KernelSymbolCallback(const KernelSymbol& kernel_symbol, Dso* dso) {
@@ -229,39 +238,64 @@
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));
+ 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));
+ ParseSymbolsFromElfFile(
+ vmlinux_, build_id,
+ std::bind(VmlinuxSymbolCallback, std::placeholders::_1, this));
+ } else if (!kallsyms_.empty()) {
+ ProcessKernelSymbols(kallsyms_, std::bind(&KernelSymbolCallback,
+ std::placeholders::_1, this));
+ bool all_zero = true;
+ for (const auto& symbol : symbols_) {
+ if (symbol.addr != 0) {
+ all_zero = false;
+ break;
+ }
+ }
+ if (all_zero) {
+ LOG(WARNING)
+ << "Symbol addresses in /proc/kallsyms on device are all zero. "
+ "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
+ symbols_.clear();
+ return false;
+ }
} 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();
+ 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_) {
+ std::string kallsyms;
+ if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) {
+ LOG(DEBUG) << "failed to read /proc/kallsyms";
+ return false;
+ }
+ ProcessKernelSymbols(kallsyms, std::bind(&KernelSymbolCallback,
+ std::placeholders::_1, this));
+ bool all_zero = true;
+ for (const auto& symbol : symbols_) {
if (symbol.addr != 0) {
- allZero = false;
+ all_zero = false;
break;
}
}
- if (allZero) {
- LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. Check "
- "/proc/sys/kernel/kptr_restrict if possible.";
+ if (all_zero) {
+ LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. "
+ "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
symbols_.clear();
return false;
}
@@ -272,7 +306,8 @@
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));
+ dso->InsertSymbol(
+ Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len));
}
}
@@ -285,12 +320,14 @@
BuildId build_id = GetExpectedBuildId(path_);
ParseSymbolsFromElfFile(
symfs_dir_ + path_, build_id,
- std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForKernelModule));
+ 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);
+ return elf_symbol.is_func ||
+ (elf_symbol.is_label && elf_symbol.is_in_text_section);
}
bool Dso::LoadElfFile() {
@@ -301,12 +338,14 @@
// 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));
+ std::bind(ElfFileSymbolCallback, std::placeholders::_1, this,
+ SymbolFilterForDso));
}
if (!loaded) {
loaded = ParseSymbolsFromElfFile(
GetAccessiblePath(), build_id,
- std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForDso));
+ std::bind(ElfFileSymbolCallback, std::placeholders::_1, this,
+ SymbolFilterForDso));
}
return loaded;
}
@@ -316,14 +355,13 @@
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));
+ 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::InsertSymbol(const Symbol& symbol) { symbols_.insert(symbol); }
void Dso::FixupSymbolLength() {
Symbol* prev_symbol = nullptr;
@@ -331,9 +369,22 @@
if (prev_symbol != nullptr && prev_symbol->len == 0) {
prev_symbol->len = symbol.addr - prev_symbol->addr;
}
- prev_symbol = &symbol;
+ prev_symbol = const_cast<Symbol*>(&symbol);
}
if (prev_symbol != nullptr && prev_symbol->len == 0) {
- prev_symbol->len = std::numeric_limits<unsigned long long>::max() - prev_symbol->addr;
+ prev_symbol->len = std::numeric_limits<uint64_t>::max() - prev_symbol->addr;
+ }
+}
+
+const char* DsoTypeToString(DsoType dso_type) {
+ switch (dso_type) {
+ case DSO_KERNEL:
+ return "dso_kernel";
+ case DSO_KERNEL_MODULE:
+ return "dso_kernel_module";
+ case DSO_ELF_FILE:
+ return "dso_elf_file";
+ default:
+ return "unknown";
}
}
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index 9697319..e823aca 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -18,6 +18,7 @@
#define SIMPLE_PERF_DSO_H_
#include <memory>
+#include <set>
#include <string>
#include <unordered_map>
#include <vector>
@@ -29,15 +30,24 @@
uint64_t len;
Symbol(const std::string& name, uint64_t addr, uint64_t len);
- const char* Name() const {
- return name_;
- }
+ const char* Name() const { return name_; }
const char* DemangledName() const;
+ bool HasDumped() const { return has_dumped_; }
+
+ void SetDumped() const { has_dumped_ = true; }
+
private:
const char* name_;
mutable const char* demangled_name_;
+ mutable bool has_dumped_;
+};
+
+struct SymbolComparator {
+ bool operator()(const Symbol& symbol1, const Symbol& symbol2) {
+ return symbol1.addr < symbol2.addr;
+ }
};
enum DsoType {
@@ -55,16 +65,29 @@
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 void SetKallsyms(std::string kallsyms) {
+ if (!kallsyms.empty()) {
+ kallsyms_ = std::move(kallsyms);
+ }
+ }
+ 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 = "");
+ static std::unique_ptr<Dso> CreateDso(DsoType dso_type,
+ const std::string& dso_path);
~Dso();
+ DsoType type() const { return type_; }
+
+ uint64_t id() const { return id_; }
+
// Return the path recorded in perf.data.
- const std::string& Path() const {
- return path_;
- }
+ const std::string& Path() const { return path_; }
+
+ bool HasDumped() const { return has_dumped_; }
+
+ void SetDumped() { has_dumped_ = true; }
// Return the accessible path. It may be the same as Path(), or
// return the path with prefix set by SetSymFsDir().
@@ -74,6 +97,7 @@
uint64_t MinVirtualAddress();
const Symbol* FindSymbol(uint64_t vaddr_in_dso);
+ void InsertSymbol(const Symbol& symbol);
private:
static BuildId GetExpectedBuildId(const std::string& filename);
@@ -85,23 +109,27 @@
static bool demangle_;
static std::string symfs_dir_;
static std::string vmlinux_;
+ static std::string kallsyms_;
static std::unordered_map<std::string, BuildId> build_id_map_;
static size_t dso_count_;
- Dso(DsoType type, const std::string& path);
+ Dso(DsoType type, uint64_t id, 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 uint64_t id_;
const std::string path_;
uint64_t min_vaddr_;
- std::vector<Symbol> symbols_;
+ std::set<Symbol, SymbolComparator> symbols_;
bool is_loaded_;
+ bool has_dumped_;
};
+const char* DsoTypeToString(DsoType dso_type);
+
#endif // SIMPLE_PERF_DSO_H_
diff --git a/simpleperf/dwarf_unwind.cpp b/simpleperf/dwarf_unwind.cpp
index ae2e1a1..ff3c1fe 100644
--- a/simpleperf/dwarf_unwind.cpp
+++ b/simpleperf/dwarf_unwind.cpp
@@ -27,7 +27,7 @@
do { \
uint64_t value; \
if (GetRegValue(regs, perf_regno, &value)) { \
- dst = value; \
+ (dst) = value; \
} \
} while (0)
@@ -95,10 +95,12 @@
}
std::vector<uint64_t> UnwindCallChain(ArchType arch, const ThreadEntry& thread,
- const RegSet& regs, const std::vector<char>& stack) {
+ const RegSet& regs, const std::vector<char>& stack,
+ bool strict_arch_check) {
std::vector<uint64_t> result;
- if (arch != GetBuildArch()) {
- LOG(ERROR) << "can't unwind data recorded on a different architecture";
+ if (!IsArchTheSame(arch, GetBuildArch(), strict_arch_check)) {
+ LOG(FATAL) << "simpleperf is built in arch " << GetArchString(GetBuildArch())
+ << ", and can't do stack unwinding for arch " << GetArchString(arch);
return result;
}
uint64_t sp_reg_value;
diff --git a/simpleperf/dwarf_unwind.h b/simpleperf/dwarf_unwind.h
index 6982b05..ba40b87 100644
--- a/simpleperf/dwarf_unwind.h
+++ b/simpleperf/dwarf_unwind.h
@@ -28,6 +28,6 @@
using ThreadEntry = simpleperf::ThreadEntry;
std::vector<uint64_t> UnwindCallChain(ArchType arch, const ThreadEntry& thread, const RegSet& regs,
- const std::vector<char>& stack);
+ const std::vector<char>& stack, bool strict_arch_check);
#endif // SIMPLE_PERF_DWARF_UNWIND_H_
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 5a8552a..0024f96 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -40,7 +40,7 @@
class LineReader {
public:
- LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
+ explicit LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
}
~LineReader() {
@@ -88,9 +88,9 @@
const char* p = s.c_str();
char* endp;
int last_cpu;
- long cpu;
+ int cpu;
// Parse line like: 0,1-3, 5, 7-8
- while ((cpu = strtol(p, &endp, 10)) != 0 || endp != p) {
+ while ((cpu = static_cast<int>(strtol(p, &endp, 10))) != 0 || endp != p) {
if (have_dash && !cpu_set.empty()) {
for (int t = last_cpu + 1; t < cpu; ++t) {
cpu_set.insert(t);
@@ -110,41 +110,6 @@
return std::vector<int>(cpu_set.begin(), cpu_set.end());
}
-bool ProcessKernelSymbols(const std::string& symbol_file,
- std::function<bool(const KernelSymbol&)> callback) {
- FILE* fp = fopen(symbol_file.c_str(), "re");
- if (fp == nullptr) {
- PLOG(ERROR) << "failed to open file " << symbol_file;
- return false;
- }
- LineReader reader(fp);
- char* line;
- while ((line = reader.ReadLine()) != nullptr) {
- // Parse line like: ffffffffa005c4e4 d __warned.41698 [libsas]
- char name[reader.MaxLineSize()];
- char module[reader.MaxLineSize()];
- strcpy(module, "");
-
- KernelSymbol symbol;
- if (sscanf(line, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module) < 3) {
- continue;
- }
- symbol.name = name;
- size_t module_len = strlen(module);
- if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
- module[module_len - 1] = '\0';
- symbol.module = &module[1];
- } else {
- symbol.module = nullptr;
- }
-
- if (callback(symbol)) {
- return true;
- }
- }
- return false;
-}
-
static std::vector<KernelMmap> GetLoadedModules() {
std::vector<KernelMmap> result;
FILE* fp = fopen("/proc/modules", "re");
@@ -166,6 +131,16 @@
result.push_back(map);
}
}
+ bool all_zero = true;
+ for (const auto& map : result) {
+ if (map.start_addr != 0) {
+ all_zero = false;
+ }
+ }
+ if (all_zero) {
+ LOG(DEBUG) << "addresses in /proc/modules are all zero, so ignore kernel modules";
+ return std::vector<KernelMmap>();
+ }
return result;
}
@@ -226,7 +201,7 @@
}
if (module_mmaps->size() == 0) {
- kernel_mmap->len = std::numeric_limits<unsigned long long>::max() - kernel_mmap->start_addr;
+ kernel_mmap->len = std::numeric_limits<uint64_t>::max() - kernel_mmap->start_addr;
} else {
std::sort(
module_mmaps->begin(), module_mmaps->end(),
@@ -246,7 +221,7 @@
}
}
module_mmaps->back().len =
- std::numeric_limits<unsigned long long>::max() - module_mmaps->back().start_addr;
+ std::numeric_limits<uint64_t>::max() - module_mmaps->back().start_addr;
}
}
@@ -477,3 +452,28 @@
#endif
return true;
}
+
+bool CheckSampleFrequency(uint64_t sample_freq) {
+ if (sample_freq == 0) {
+ LOG(ERROR) << "Sample frequency can't be zero.";
+ return false;
+ }
+ std::string s;
+ if (!android::base::ReadFileToString("/proc/sys/kernel/perf_event_max_sample_rate", &s)) {
+ PLOG(WARNING) << "failed to read /proc/sys/kernel/perf_event_max_sample_rate";
+ // Omit the check if perf_event_max_sample_rate doesn't exist.
+ return true;
+ }
+ s = android::base::Trim(s);
+ uint64_t max_sample_freq;
+ if (!android::base::ParseUint(s.c_str(), &max_sample_freq)) {
+ LOG(ERROR) << "failed to parse /proc/sys/kernel/perf_event_max_sample_rate: " << s;
+ return false;
+ }
+ if (sample_freq > max_sample_freq) {
+ LOG(ERROR) << "Sample frequency " << sample_freq << " is out of range [1, "
+ << max_sample_freq << "]";
+ return false;
+ }
+ return true;
+}
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 6da632b..7c0e279 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -29,7 +29,7 @@
std::vector<int> GetOnlineCpus();
std::vector<int> GetCpusFromString(const std::string& s);
-constexpr char DEFAULT_KERNEL_MMAP_NAME[] = "[kernel.kallsyms]_text";
+constexpr char DEFAULT_KERNEL_MMAP_NAME[] = "[kernel.kallsyms]";
struct KernelMmap {
std::string name;
@@ -69,16 +69,7 @@
bool GetExecPath(std::string* exec_path);
-// Expose the following functions for unit tests.
-struct KernelSymbol {
- uint64_t addr;
- char type;
- const char* name;
- const char* module; // If nullptr, the symbol is not in a kernel module.
-};
-
-bool ProcessKernelSymbols(const std::string& symbol_file,
- std::function<bool(const KernelSymbol&)> callback);
bool CheckPerfEventLimit();
+bool CheckSampleFrequency(uint64_t sample_freq);
#endif // SIMPLE_PERF_ENVIRONMENT_H_
diff --git a/simpleperf/environment_fake.cpp b/simpleperf/environment_fake.cpp
deleted file mode 100644
index fdcf814..0000000
--- a/simpleperf/environment_fake.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// 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;
-}
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
index 6bca7b8..9b4cbab 100644
--- a/simpleperf/environment_test.cpp
+++ b/simpleperf/environment_test.cpp
@@ -16,10 +16,6 @@
#include <gtest/gtest.h>
-#include <functional>
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-
#include "environment.h"
TEST(environment, GetCpusFromString) {
@@ -28,47 +24,3 @@
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 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) {
- std::string data =
- "ffffffffa005c4e4 d __warned.41698 [libsas]\n"
- "aaaaaaaaaaaaaaaa T _text\n"
- "cccccccccccccccc c ccccc\n";
- 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.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.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-
- expected_symbol.name = "non_existent_symbol";
- ASSERT_FALSE(ProcessKernelSymbols(
- tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index c9449b1..10cef52 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -87,12 +87,10 @@
// 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 | PERF_SAMPLE_CPU;
+ attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_PERIOD |
+ PERF_SAMPLE_CPU | PERF_SAMPLE_ID;
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;
}
@@ -100,12 +98,7 @@
}
void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
- std::string event_name = "unknown";
- const EventType* event_type = FindEventTypeByConfig(attr.type, attr.config);
- if (event_type != nullptr) {
- event_name = event_type->name;
- }
-
+ std::string event_name = GetEventNameByAttr(attr);
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);
@@ -145,3 +138,101 @@
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);
}
+
+bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+ size_t* event_id_pos_in_sample_records,
+ size_t* event_id_reverse_pos_in_non_sample_records) {
+ // When there are more than one perf_event_attrs, we need to read event id
+ // in each record to decide current record should use which attr. So
+ // we need to determine the event id position in a record here.
+ std::vector<uint64_t> sample_types;
+ for (const auto& attr : attrs) {
+ sample_types.push_back(attr.sample_type);
+ }
+ // First determine event_id_pos_in_sample_records.
+ // If PERF_SAMPLE_IDENTIFIER is enabled, it is just after perf_event_header.
+ // If PERF_SAMPLE_ID is enabled, then PERF_SAMPLE_IDENTIFIER | IP | TID | TIME | ADDR
+ // should also be the same.
+ bool identifier_enabled = true;
+ bool id_enabled = true;
+ uint64_t flags_before_id_mask = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IP | PERF_SAMPLE_TID |
+ PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR;
+ uint64_t flags_before_id = sample_types[0] & flags_before_id_mask;
+ bool flags_before_id_are_the_same = true;
+ for (auto type : sample_types) {
+ identifier_enabled &= (type & PERF_SAMPLE_IDENTIFIER) != 0;
+ id_enabled &= (type & PERF_SAMPLE_ID) != 0;
+ flags_before_id_are_the_same &= (type & flags_before_id_mask) == flags_before_id;
+ }
+ if (identifier_enabled) {
+ *event_id_pos_in_sample_records = sizeof(perf_event_header);
+ } else if (id_enabled && flags_before_id_are_the_same) {
+ uint64_t pos = sizeof(perf_event_header);
+ while (flags_before_id != 0) {
+ // Each flags takes 8 bytes in sample records.
+ flags_before_id &= flags_before_id - 1;
+ pos += 8;
+ }
+ *event_id_pos_in_sample_records = pos;
+ } else {
+ LOG(ERROR) << "perf_event_attrs don't have a common event id position in sample records";
+ return false;
+ }
+
+ // Secondly determine event_id_reverse_pos_in_non_sample_record.
+ // If sample_id_all is not enabled, there is no event id in non sample records.
+ // If PERF_SAMPLE_IDENTIFIER is enabled, it is at the last 8 bytes of the record.
+ // If PERF_SAMPLE_ID is enabled, then PERF_SAMPLE_IDENTIFIER | CPU | STREAM_ID should
+ // also be the same.
+ bool sample_id_all_enabled = true;
+ for (const auto& attr : attrs) {
+ if (attr.sample_id_all == 0) {
+ sample_id_all_enabled = false;
+ }
+ }
+ if (!sample_id_all_enabled) {
+ LOG(ERROR) << "there are perf_event_attrs not enabling sample_id_all, so can't determine "
+ << "perf_event_attr for non sample records";
+ return false;
+ }
+ uint64_t flags_after_id_mask = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_CPU | PERF_SAMPLE_STREAM_ID;
+ uint64_t flags_after_id = sample_types[0] & flags_after_id_mask;
+ bool flags_after_id_are_the_same = true;
+ for (auto type : sample_types) {
+ flags_after_id_are_the_same &= (type & flags_after_id_mask) == flags_after_id;
+ }
+ if (identifier_enabled) {
+ *event_id_reverse_pos_in_non_sample_records = 8;
+ } else if (id_enabled && flags_after_id_are_the_same) {
+ uint64_t pos = 8;
+ while (flags_after_id != 0) {
+ // Each flag takes 8 bytes in sample_id of non sample records.
+ flags_after_id &= flags_after_id - 1;
+ pos += 8;
+ }
+ *event_id_reverse_pos_in_non_sample_records = pos;
+ } else {
+ LOG(ERROR) << "perf_event_attrs don't have a common event id reverse position in non sample records";
+ return false;
+ }
+ return true;
+}
+
+bool IsTimestampSupported(const perf_event_attr& attr) {
+ return attr.sample_id_all && (attr.sample_type & PERF_SAMPLE_TIME);
+}
+
+std::string GetEventNameByAttr(const perf_event_attr& attr) {
+ for (const auto& event_type : GetAllEventTypes()) {
+ if (event_type.type == attr.type && event_type.config == attr.config) {
+ std::string name = event_type.name;
+ if (attr.exclude_user && !attr.exclude_kernel) {
+ name += ":k";
+ } else if (attr.exclude_kernel && !attr.exclude_user) {
+ name += ":u";
+ }
+ return name;
+ }
+ }
+ return "unknown";
+}
diff --git a/simpleperf/event_attr.h b/simpleperf/event_attr.h
index 79d3df4..030f7c9 100644
--- a/simpleperf/event_attr.h
+++ b/simpleperf/event_attr.h
@@ -19,11 +19,20 @@
#include <stddef.h>
+#include <string>
+#include <vector>
+
#include "perf_event.h"
struct EventType;
perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type);
void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent = 0);
+bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+ size_t* event_id_pos_in_sample_records,
+ size_t* event_id_reverse_pos_in_non_sample_records);
+bool IsTimestampSupported(const perf_event_attr& attr);
+// Return event name with modifier if the event is found, otherwise return "unknown".
+std::string GetEventNameByAttr(const perf_event_attr& attr);
#endif // SIMPLE_PERF_EVENT_ATTR_H_
diff --git a/simpleperf/event_fd.cpp b/simpleperf/event_fd.cpp
index 808639b..58e04dc 100644
--- a/simpleperf/event_fd.cpp
+++ b/simpleperf/event_fd.cpp
@@ -31,6 +31,7 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include "event_attr.h"
#include "event_type.h"
#include "perf_event.h"
#include "utils.h"
@@ -38,36 +39,36 @@
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) {
+ unsigned long flags) { // NOLINT
return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
}
std::unique_ptr<EventFd> EventFd::OpenEventFile(const perf_event_attr& attr, pid_t tid, int cpu,
- bool report_error) {
+ EventFd* group_event_fd, bool report_error) {
perf_event_attr perf_attr = attr;
- std::string event_name = "unknown event";
- const EventType* event_type = FindEventTypeByConfig(perf_attr.type, perf_attr.config);
- if (event_type != nullptr) {
- event_name = event_type->name;
+ std::string event_name = GetEventNameByAttr(attr);
+ int group_fd = -1;
+ if (group_event_fd != nullptr) {
+ group_fd = group_event_fd->perf_event_fd_;
}
- int perf_event_fd = perf_event_open(&perf_attr, tid, cpu, -1, 0);
+ int perf_event_fd = perf_event_open(&perf_attr, tid, cpu, group_fd, 0);
if (perf_event_fd == -1) {
if (report_error) {
PLOG(ERROR) << "open perf_event_file (event " << event_name << ", tid " << tid << ", cpu "
- << cpu << ") failed";
+ << cpu << ", group_fd " << group_fd << ") failed";
} else {
PLOG(DEBUG) << "open perf_event_file (event " << event_name << ", tid " << tid << ", cpu "
- << cpu << ") failed";
+ << cpu << ", group_fd " << group_fd << ") failed";
}
return nullptr;
}
if (fcntl(perf_event_fd, F_SETFD, FD_CLOEXEC) == -1) {
if (report_error) {
PLOG(ERROR) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", tid "
- << tid << ", cpu " << cpu << ") failed";
+ << tid << ", cpu " << cpu << ", group_fd " << group_fd << ") failed";
} else {
PLOG(DEBUG) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", tid "
- << tid << ", cpu " << cpu << ") failed";
+ << tid << ", cpu " << cpu << ", group_fd " << group_fd << ") failed";
}
return nullptr;
}
@@ -96,6 +97,15 @@
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::ReadCounter(PerfCounter* counter) const {
CHECK(counter != nullptr);
if (!android::base::ReadFully(perf_event_fd_, counter, sizeof(*counter))) {
@@ -177,13 +187,13 @@
mmap_metadata_page_->data_tail += discard_size;
}
-void EventFd::PreparePollForMmapData(pollfd* poll_fd) {
+void EventFd::PrepareToPollForMmapData(pollfd* poll_fd) {
memset(poll_fd, 0, sizeof(pollfd));
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);
+ auto event_fd = EventFd::OpenEventFile(attr, getpid(), -1, nullptr, false);
return event_fd != nullptr;
}
diff --git a/simpleperf/event_fd.h b/simpleperf/event_fd.h
index c1a7d75..c54c3e6 100644
--- a/simpleperf/event_fd.h
+++ b/simpleperf/event_fd.h
@@ -40,10 +40,13 @@
class EventFd {
public:
static std::unique_ptr<EventFd> OpenEventFile(const perf_event_attr& attr, pid_t tid, int cpu,
- bool report_error = true);
+ EventFd* group_event_fd, bool report_error = true);
~EventFd();
+ // Give information about this perf_event_file, like (event_name, tid, cpu).
+ std::string Name() const;
+
uint64_t Id() const;
pid_t ThreadId() const {
@@ -54,6 +57,9 @@
return cpu_;
}
+ // It tells the kernel to start counting and recording events specified by this file.
+ bool EnableEvent();
+
bool ReadCounter(PerfCounter* counter) const;
// Call mmap() for this perf_event_file, so we can read sampled records from mapped area.
@@ -65,7 +71,7 @@
size_t GetAvailableMmapData(char** pdata);
// Prepare pollfd for poll() to wait on available mmap_data.
- void PreparePollForMmapData(pollfd* poll_fd);
+ void PrepareToPollForMmapData(pollfd* poll_fd);
private:
EventFd(int perf_event_fd, const std::string& event_name, pid_t tid, int cpu)
@@ -78,9 +84,6 @@
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);
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index fad8b1e..1c83250 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -43,117 +43,170 @@
return false;
}
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
- attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+ 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);
}
-bool EventSelectionSet::AddEventType(const EventTypeAndModifier& event_type_modifier) {
- EventSelection selection;
- 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";
+bool EventSelectionSet::BuildAndCheckEventSelection(
+ const std::string& event_name, EventSelection* selection) {
+ std::unique_ptr<EventTypeAndModifier> event_type = ParseEventType(event_name);
+ if (event_type == nullptr) {
return false;
}
- selections_.push_back(std::move(selection));
+ selection->event_type_modifier = *event_type;
+ selection->event_attr = CreateDefaultPerfEventAttr(event_type->event_type);
+ selection->event_attr.exclude_user = event_type->exclude_user;
+ selection->event_attr.exclude_kernel = event_type->exclude_kernel;
+ selection->event_attr.exclude_hv = event_type->exclude_hv;
+ selection->event_attr.exclude_host = event_type->exclude_host;
+ selection->event_attr.exclude_guest = event_type->exclude_guest;
+ selection->event_attr.precise_ip = event_type->precise_ip;
+ if (!IsEventAttrSupportedByKernel(selection->event_attr)) {
+ LOG(ERROR) << "Event type '" << event_type->name
+ << "' is not supported by the kernel";
+ return false;
+ }
+ selection->event_fds.clear();
+
+ for (const auto& group : groups_) {
+ for (const auto& sel : group) {
+ if (sel.event_type_modifier.name == selection->event_type_modifier.name) {
+ LOG(ERROR) << "Event type '" << sel.event_type_modifier.name
+ << "' appears more than once";
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool EventSelectionSet::AddEventType(const std::string& event_name) {
+ return AddEventGroup(std::vector<std::string>(1, event_name));
+}
+
+bool EventSelectionSet::AddEventGroup(
+ const std::vector<std::string>& event_names) {
+ EventSelectionGroup group;
+ for (const auto& event_name : event_names) {
+ EventSelection selection;
+ if (!BuildAndCheckEventSelection(event_name, &selection)) {
+ return false;
+ }
+ selection.selection_id = group.size();
+ selection.group_id = groups_.size();
+ group.push_back(std::move(selection));
+ }
+ groups_.push_back(std::move(group));
UnionSampleType();
return true;
}
-// Union the sample type of different event attrs can make reading sample records in perf.data
+// 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 (const auto& group : groups_) {
+ for (const auto& selection : group) {
+ sample_type |= selection.event_attr.sample_type;
+ }
}
- for (auto& selection : selections_) {
- selection.event_attr.sample_type = sample_type;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ 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;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ // 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;
+ }
}
}
}
bool EventSelectionSet::GetEnableOnExec() {
- for (auto& selection : selections_) {
- if (selection.event_attr.enable_on_exec == 0) {
- return false;
+ for (const auto& group : groups_) {
+ for (const auto& selection : group) {
+ if (selection.event_attr.enable_on_exec == 0) {
+ return false;
+ }
}
}
return true;
}
void EventSelectionSet::SampleIdAll() {
- for (auto& selection : selections_) {
- selection.event_attr.sample_id_all = 1;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ selection.event_attr.sample_id_all = 1;
+ }
}
}
-void EventSelectionSet::SetSampleFreq(uint64_t sample_freq) {
- for (auto& selection : selections_) {
- perf_event_attr& attr = selection.event_attr;
- attr.freq = 1;
- attr.sample_freq = sample_freq;
- }
+void EventSelectionSet::SetSampleFreq(const EventSelection& selection,
+ uint64_t sample_freq) {
+ EventSelection& sel = groups_[selection.group_id][selection.selection_id];
+ sel.event_attr.freq = 1;
+ sel.event_attr.sample_freq = sample_freq;
}
-void EventSelectionSet::SetSamplePeriod(uint64_t sample_period) {
- for (auto& selection : selections_) {
- perf_event_attr& attr = selection.event_attr;
- attr.freq = 0;
- attr.sample_period = sample_period;
- }
+void EventSelectionSet::SetSamplePeriod(const EventSelection& selection,
+ uint64_t sample_period) {
+ EventSelection& sel = groups_[selection.group_id][selection.selection_id];
+ sel.event_attr.freq = 0;
+ sel.event_attr.sample_period = sample_period;
}
bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
if (branch_sample_type != 0 &&
- (branch_sample_type & (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
- PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
- LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex << branch_sample_type;
+ (branch_sample_type &
+ (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
+ PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
+ LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex
+ << branch_sample_type;
return false;
}
if (branch_sample_type != 0 && !IsBranchSamplingSupported()) {
LOG(ERROR) << "branch stack sampling is not supported on this device.";
return false;
}
- for (auto& selection : selections_) {
- perf_event_attr& attr = selection.event_attr;
- if (branch_sample_type != 0) {
- attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
- } else {
- attr.sample_type &= ~PERF_SAMPLE_BRANCH_STACK;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ 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;
}
- attr.branch_sample_type = branch_sample_type;
}
return true;
}
void EventSelectionSet::EnableFpCallChainSampling() {
- for (auto& selection : selections_) {
- selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+ }
}
}
@@ -162,26 +215,33 @@
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;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ 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);
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ 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()) {
+ if (std::find(online_cpus.begin(), online_cpus.end(), cpu) ==
+ online_cpus.end()) {
LOG(ERROR) << "cpu " << cpu << " is not online.";
return false;
}
@@ -193,10 +253,11 @@
return OpenEventFilesForThreadsOnCpus({-1}, cpus);
}
-bool EventSelectionSet::OpenEventFilesForThreadsOnCpus(const std::vector<pid_t>& threads,
- std::vector<int> cpus) {
+bool EventSelectionSet::OpenEventFilesForThreadsOnCpus(
+ const std::vector<pid_t>& threads, std::vector<int> cpus) {
if (!cpus.empty()) {
- if (!CheckIfCpusOnline(cpus)) {
+ // cpus = {-1} means open an event file for all cpus.
+ if (!(cpus.size() == 1 && cpus[0] == -1) && !CheckIfCpusOnline(cpus)) {
return false;
}
} else {
@@ -207,23 +268,45 @@
bool EventSelectionSet::OpenEventFiles(const std::vector<pid_t>& threads,
const std::vector<int>& cpus) {
- for (auto& selection : selections_) {
- for (auto& tid : threads) {
+ for (auto& group : groups_) {
+ for (const 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));
+ std::string failed_event_type;
+ for (const auto& cpu : cpus) {
+ std::vector<std::unique_ptr<EventFd>> event_fds;
+ // Given a tid and cpu, events on the same group should be all opened
+ // successfully or all failed to open.
+ for (auto& selection : group) {
+ EventFd* group_fd = nullptr;
+ if (selection.selection_id != 0) {
+ group_fd = event_fds[0].get();
+ }
+ std::unique_ptr<EventFd> event_fd =
+ EventFd::OpenEventFile(selection.event_attr, tid, cpu, group_fd);
+ if (event_fd != nullptr) {
+ LOG(VERBOSE) << "OpenEventFile for " << event_fd->Name();
+ event_fds.push_back(std::move(event_fd));
+ } else {
+ failed_event_type = selection.event_type_modifier.name;
+ break;
+ }
+ }
+ if (event_fds.size() == group.size()) {
+ for (size_t i = 0; i < group.size(); ++i) {
+ group[i].event_fds.push_back(std::move(event_fds[i]));
+ }
++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.
+ // 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));
+ << failed_event_type << " for "
+ << (tid == -1 ? "all threads" : android::base::StringPrintf(
+ " thread %d", tid));
return false;
}
}
@@ -233,97 +316,123 @@
bool EventSelectionSet::ReadCounters(std::vector<CountersInfo>* counters) {
counters->clear();
- for (auto& selection : selections_) {
- CountersInfo counters_info;
- counters_info.event_type = &selection.event_type_modifier;
- for (auto& event_fd : selection.event_fds) {
- CountersInfo::CounterInfo counter_info;
- if (!event_fd->ReadCounter(&counter_info.counter)) {
- return false;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ CountersInfo counters_info;
+ counters_info.selection = &selection;
+ for (auto& event_fd : selection.event_fds) {
+ CountersInfo::CounterInfo counter_info;
+ if (!event_fd->ReadCounter(&counter_info.counter)) {
+ return false;
+ }
+ counter_info.tid = event_fd->ThreadId();
+ counter_info.cpu = event_fd->Cpu();
+ counters_info.counters.push_back(counter_info);
}
- counter_info.tid = event_fd->ThreadId();
- counter_info.cpu = event_fd->Cpu();
- counters_info.counters.push_back(counter_info);
+ counters->push_back(counters_info);
}
- counters->push_back(counters_info);
}
return true;
}
-void EventSelectionSet::PreparePollForEventFiles(std::vector<pollfd>* pollfds) {
- for (auto& selection : selections_) {
- for (auto& event_fd : selection.event_fds) {
- pollfd poll_fd;
- event_fd->PreparePollForMmapData(&poll_fd);
- pollfds->push_back(poll_fd);
+void EventSelectionSet::PrepareToPollForEventFiles(
+ std::vector<pollfd>* pollfds) {
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ for (auto& event_fd : selection.event_fds) {
+ pollfd poll_fd;
+ event_fd->PrepareToPollForMmapData(&poll_fd);
+ pollfds->push_back(poll_fd);
+ }
}
}
}
bool EventSelectionSet::MmapEventFiles(size_t mmap_pages) {
- for (auto& selection : selections_) {
- for (auto& event_fd : selection.event_fds) {
- if (!event_fd->MmapContent(mmap_pages)) {
- return false;
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ for (auto& event_fd : selection.event_fds) {
+ if (!event_fd->MmapContent(mmap_pages)) {
+ return false;
+ }
}
}
}
return true;
}
-static bool ReadMmapEventDataForFd(std::unique_ptr<EventFd>& event_fd,
- std::function<bool(const char*, size_t)> callback,
- bool* have_data) {
- *have_data = false;
+void EventSelectionSet::PrepareToReadMmapEventData(
+ std::function<bool(Record*)> callback) {
+ record_callback_ = callback;
+ bool has_timestamp = true;
+ for (const auto& group : groups_) {
+ for (const auto& selection : group) {
+ if (!IsTimestampSupported(selection.event_attr)) {
+ has_timestamp = false;
+ break;
+ }
+ }
+ }
+ record_cache_.reset(new RecordCache(has_timestamp));
+
+ for (const auto& group : groups_) {
+ for (const auto& selection : group) {
+ for (const auto& event_fd : selection.event_fds) {
+ int event_id = event_fd->Id();
+ event_id_to_attr_map_[event_id] = &selection.event_attr;
+ }
+ }
+ }
+}
+
+bool EventSelectionSet::ReadMmapEventData() {
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ for (auto& event_fd : selection.event_fds) {
+ bool has_data = true;
+ while (has_data) {
+ if (!ReadMmapEventDataForFd(event_fd, selection.event_attr,
+ &has_data)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool EventSelectionSet::ReadMmapEventDataForFd(
+ std::unique_ptr<EventFd>& event_fd, const perf_event_attr& attr,
+ bool* has_data) {
+ *has_data = false;
while (true) {
char* data;
size_t size = event_fd->GetAvailableMmapData(&data);
if (size == 0) {
break;
}
- if (!callback(data, size)) {
+ std::vector<std::unique_ptr<Record>> records =
+ ReadRecordsFromBuffer(attr, data, size);
+ record_cache_->Push(std::move(records));
+ std::unique_ptr<Record> r = record_cache_->Pop();
+ while (r != nullptr) {
+ if (!record_callback_(r.get())) {
+ return false;
+ }
+ r = record_cache_->Pop();
+ }
+ *has_data = true;
+ }
+ return true;
+}
+
+bool EventSelectionSet::FinishReadMmapEventData() {
+ std::vector<std::unique_ptr<Record>> records = record_cache_->PopAll();
+ for (auto& r : records) {
+ if (!record_callback_(r.get())) {
return false;
}
- *have_data = true;
}
return true;
}
-
-bool EventSelectionSet::ReadMmapEventData(std::function<bool(const char*, size_t)> callback) {
- for (auto& selection : selections_) {
- for (auto& event_fd : selection.event_fds) {
- while (true) {
- bool have_data;
- if (!ReadMmapEventDataForFd(event_fd, callback, &have_data)) {
- return false;
- }
- if (!have_data) {
- break;
- }
- }
- }
- }
- return true;
-}
-
-EventSelectionSet::EventSelection* EventSelectionSet::FindSelectionByType(
- const EventTypeAndModifier& event_type_modifier) {
- for (auto& selection : selections_) {
- if (selection.event_type_modifier.name == event_type_modifier.name) {
- return &selection;
- }
- }
- return nullptr;
-}
-
-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 EventTypeAndModifier& event_type_modifier) {
- EventSelection* selection = FindSelectionByType(event_type_modifier);
- return (selection != nullptr) ? &selection->event_fds : nullptr;
-}
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index 746abfa..d393a3b 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -19,6 +19,7 @@
#include <functional>
#include <map>
+#include <unordered_map>
#include <vector>
#include <android-base/macros.h>
@@ -26,9 +27,19 @@
#include "event_fd.h"
#include "event_type.h"
#include "perf_event.h"
+#include "record.h"
+
+struct EventSelection {
+ uint32_t group_id;
+ uint32_t selection_id;
+ EventTypeAndModifier event_type_modifier;
+ perf_event_attr event_attr;
+ std::vector<std::unique_ptr<EventFd>> event_fds;
+};
+typedef std::vector<EventSelection> EventSelectionGroup;
struct CountersInfo {
- const EventTypeAndModifier* event_type;
+ const EventSelection* selection;
struct CounterInfo {
pid_t tid;
int cpu;
@@ -53,17 +64,22 @@
EventSelectionSet() {
}
- bool Empty() const {
- return selections_.empty();
+ bool empty() const {
+ return groups_.empty();
}
- bool AddEventType(const EventTypeAndModifier& event_type_modifier);
+ const std::vector<EventSelectionGroup>& groups() {
+ return groups_;
+ }
+
+ bool AddEventType(const std::string& event_name);
+ bool AddEventGroup(const std::vector<std::string>& event_names);
void SetEnableOnExec(bool enable);
bool GetEnableOnExec();
void SampleIdAll();
- void SetSampleFreq(uint64_t sample_freq);
- void SetSamplePeriod(uint64_t sample_period);
+ void SetSampleFreq(const EventSelection& selection, uint64_t sample_freq);
+ void SetSamplePeriod(const EventSelection& selection, uint64_t sample_period);
bool SetBranchSampling(uint64_t branch_sample_type);
void EnableFpCallChainSampling();
bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);
@@ -72,26 +88,25 @@
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);
+ void PrepareToPollForEventFiles(std::vector<pollfd>* pollfds);
bool MmapEventFiles(size_t mmap_pages);
- bool ReadMmapEventData(std::function<bool(const char*, size_t)> callback);
-
- const perf_event_attr* FindEventAttrByType(const EventTypeAndModifier& event_type_modifier);
- const std::vector<std::unique_ptr<EventFd>>* FindEventFdsByType(
- const EventTypeAndModifier& event_type_modifier);
+ void PrepareToReadMmapEventData(std::function<bool (Record*)> callback);
+ bool ReadMmapEventData();
+ bool FinishReadMmapEventData();
private:
+ bool BuildAndCheckEventSelection(const std::string& event_name,
+ EventSelection* selection);
void UnionSampleType();
bool OpenEventFiles(const std::vector<pid_t>& threads, const std::vector<int>& cpus);
+ bool ReadMmapEventDataForFd(std::unique_ptr<EventFd>& event_fd, const perf_event_attr& attr,
+ bool* has_data);
- struct EventSelection {
- EventTypeAndModifier event_type_modifier;
- perf_event_attr event_attr;
- std::vector<std::unique_ptr<EventFd>> event_fds;
- };
- EventSelection* FindSelectionByType(const EventTypeAndModifier& event_type_modifier);
+ std::vector<EventSelectionGroup> groups_;
- std::vector<EventSelection> selections_;
+ std::function<bool (Record*)> record_callback_;
+ std::unique_ptr<RecordCache> record_cache_;
+ std::unordered_map<uint64_t, const perf_event_attr*> event_id_to_attr_map_;
DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
};
diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp
index 2eaafa2..83b900e 100644
--- a/simpleperf/event_type.cpp
+++ b/simpleperf/event_type.cpp
@@ -74,15 +74,6 @@
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* FindEventTypeByName(const std::string& name) {
const EventType* result = nullptr;
for (auto& event_type : GetAllEventTypes()) {
diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h
index 4100125..12d83b3 100644
--- a/simpleperf/event_type.h
+++ b/simpleperf/event_type.h
@@ -41,7 +41,6 @@
};
const std::vector<EventType>& GetAllEventTypes();
-const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config);
const EventType* FindEventTypeByName(const std::string& name);
struct EventTypeAndModifier {
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
index 4aba379..36b6ed1 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -24,10 +24,9 @@
std::string GetTestData(const std::string& filename);
const std::string& GetTestDataDir();
-bool IsRoot();
-
-// The source code of elf is testdata/elf_file_source.cpp.
+// The source code of elf and elf_with_mini_debug_info is testdata/elf_file_source.cpp.
static const std::string ELF_FILE = "elf";
+static const std::string ELF_FILE_WITH_MINI_DEBUG_INFO = "elf_with_mini_debug_info";
// 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).
@@ -36,6 +35,9 @@
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";
+// perf_with_mini_debug_info.data is generated by sampling on one process running
+// elf_with_mini_debug_info.
+static const std::string PERF_DATA_WITH_MINI_DEBUG_INFO = "perf_with_mini_debug_info.data";
static BuildId elf_file_build_id("0b12a384a9f4a3f3659b7171ca615dbec3a81f71");
@@ -63,4 +65,16 @@
static BuildId native_lib_build_id("8ed5755a7fdc07586ca228b8ee21621bce2c7a97");
+// perf_with_two_event_types.data is generated by sampling using -e cpu-cycles,cpu-clock option.
+static const std::string PERF_DATA_WITH_TWO_EVENT_TYPES = "perf_with_two_event_types.data";
+
+// perf_with_kernel_symbol.data is generated by `sudo simpleperf record ls -l`.
+static const std::string PERF_DATA_WITH_KERNEL_SYMBOL = "perf_with_kernel_symbol.data";
+
+// perf_with_symbols.data is generated by `sudo simpleperf record --dump-symbols sleep 1`.
+static const std::string PERF_DATA_WITH_SYMBOLS = "perf_with_symbols.data";
+
+// perf_kmem_slab_callgraph.data is generated by `simpleperf kmem record --slab --call-graph fp -f 100 sleep 0.0001`.
+static const std::string PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD = "perf_with_kmem_slab_callgraph.data";
+
#endif // SIMPLE_PERF_GET_TEST_DATA_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 444a1c2..87f5eb1 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -33,7 +33,7 @@
static std::string testdata_dir;
-#if defined(IN_CTS_TEST)
+#if defined(__ANDROID__)
static const std::string testdata_section = ".testzipdata";
static bool ExtractTestDataFromElfSection() {
@@ -95,7 +95,6 @@
return true;
}
-#if defined(__ANDROID__)
class SavedPerfHardenProperty {
public:
SavedPerfHardenProperty() {
@@ -108,10 +107,7 @@
~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;
- }
+ __system_property_set("security.perf_harden", prop_value_);
// Sleep one second to wait for security.perf_harden changing
// /proc/sys/kernel/perf_event_paranoid.
sleep(1);
@@ -131,8 +127,8 @@
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);
@@ -158,7 +154,7 @@
}
android::base::ScopedLogSeverity severity(log_severity);
-#if defined(IN_CTS_TEST)
+#if defined(__ANDROID__)
std::unique_ptr<TemporaryDir> tmp_dir;
if (!::testing::GTEST_FLAG(list_tests) && testdata_dir.empty()) {
tmp_dir.reset(new TemporaryDir);
@@ -169,13 +165,12 @@
}
}
-#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;
diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp
index 0a73c2d..32f5feb 100644
--- a/simpleperf/main.cpp
+++ b/simpleperf/main.cpp
@@ -27,7 +27,7 @@
int main(int argc, char** argv) {
InitLogging(argv, android::base::StderrLogger);
std::vector<std::string> args;
- android::base::LogSeverity log_severity = android::base::WARNING;
+ android::base::LogSeverity log_severity = android::base::INFO;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
index 7551d36..24af5ce 100644
--- a/simpleperf/nonlinux_support/nonlinux_support.cpp
+++ b/simpleperf/nonlinux_support/nonlinux_support.cpp
@@ -21,14 +21,10 @@
#include "environment.h"
std::vector<uint64_t> UnwindCallChain(ArchType, const ThreadEntry&, const RegSet&,
- const std::vector<char>&) {
+ const std::vector<char>&, bool) {
return std::vector<uint64_t>();
}
-bool ProcessKernelSymbols(const std::string&, std::function<bool(const KernelSymbol&)>) {
- return false;
-}
-
bool GetKernelBuildId(BuildId*) {
return false;
}
diff --git a/simpleperf/perf_regs.cpp b/simpleperf/perf_regs.cpp
index 29d144e..e96b83c 100644
--- a/simpleperf/perf_regs.cpp
+++ b/simpleperf/perf_regs.cpp
@@ -21,6 +21,8 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include "perf_event.h"
+
ArchType ScopedCurrentArch::current_arch = GetBuildArch();
ArchType GetArchType(const std::string& arch) {
@@ -37,6 +39,53 @@
return ARCH_UNSUPPORTED;
}
+ArchType GetArchForAbi(ArchType machine_arch, int abi) {
+ if (abi == PERF_SAMPLE_REGS_ABI_32) {
+ if (machine_arch == ARCH_X86_64) {
+ return ARCH_X86_32;
+ }
+ if (machine_arch == ARCH_ARM64) {
+ return ARCH_ARM;
+ }
+ }
+ return machine_arch;
+}
+
+std::string GetArchString(ArchType arch) {
+ switch (arch) {
+ case ARCH_X86_32:
+ return "x86";
+ case ARCH_X86_64:
+ return "x86_64";
+ case ARCH_ARM64:
+ return "arm64";
+ case ARCH_ARM:
+ return "arm";
+ default:
+ break;
+ }
+ return "unknown";
+}
+
+// If strict_check, must have arch1 == arch2.
+// Otherwise, allow X86_32 with X86_64, ARM with ARM64.
+bool IsArchTheSame(ArchType arch1, ArchType arch2, bool strict_check) {
+ if (strict_check) {
+ return arch1 == arch2;
+ }
+ switch (arch1) {
+ case ARCH_X86_32:
+ case ARCH_X86_64:
+ return arch2 == ARCH_X86_32 || arch2 == ARCH_X86_64;
+ case ARCH_ARM64:
+ case ARCH_ARM:
+ return arch2 == ARCH_ARM64 || arch2 == ARCH_ARM;
+ default:
+ break;
+ }
+ return arch1 == arch2;
+}
+
uint64_t GetSupportedRegMask(ArchType arch) {
switch (arch) {
case ARCH_X86_32:
diff --git a/simpleperf/perf_regs.h b/simpleperf/perf_regs.h
index 9fc610f..7705e50 100644
--- a/simpleperf/perf_regs.h
+++ b/simpleperf/perf_regs.h
@@ -56,6 +56,9 @@
}
ArchType GetArchType(const std::string& arch);
+ArchType GetArchForAbi(ArchType machine_arch, int abi);
+std::string GetArchString(ArchType arch);
+bool IsArchTheSame(ArchType arch1, ArchType arch2, bool strict_check);
uint64_t GetSupportedRegMask(ArchType arch);
std::string GetRegName(size_t regno, ArchType arch);
diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp
index 05b06aa..f59ec37 100644
--- a/simpleperf/read_elf.cpp
+++ b/simpleperf/read_elf.cpp
@@ -75,8 +75,8 @@
p += 4;
uint32_t type = *reinterpret_cast<const uint32_t*>(p);
p += 4;
- namesz = ALIGN(namesz, 4);
- descsz = ALIGN(descsz, 4);
+ namesz = Align(namesz, 4);
+ descsz = Align(descsz, 4);
CHECK_LE(p + namesz + descsz, end);
if ((type == NT_GNU_BUILD_ID) && (strcmp(p, ELF_NOTE_GNU) == 0)) {
*build_id = BuildId(p + namesz, descsz);
@@ -101,17 +101,17 @@
}
template <class ELFT>
-bool GetBuildIdFromELFFile(const llvm::object::ELFFile<ELFT>* elf, BuildId* build_id) {
- for (auto section_iterator = elf->section_begin(); section_iterator != elf->section_end();
- ++section_iterator) {
- if (section_iterator->sh_type == llvm::ELF::SHT_NOTE) {
- auto contents = elf->getSectionContents(&*section_iterator);
- if (contents.getError()) {
+bool GetBuildIdFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf, BuildId* build_id) {
+ for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
+ const llvm::object::ELFSectionRef& section_ref = *it;
+ if (section_ref.getType() == llvm::ELF::SHT_NOTE) {
+ llvm::StringRef data;
+ if (it->getContents(data)) {
LOG(DEBUG) << "read note section error";
continue;
}
- if (GetBuildIdFromNoteSection(reinterpret_cast<const char*>(contents->data()),
- contents->size(), build_id)) {
+ if (GetBuildIdFromNoteSection(reinterpret_cast<const char*>(data.data()),
+ data.size(), build_id)) {
return true;
}
}
@@ -122,9 +122,9 @@
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);
+ result = GetBuildIdFromELFFile(elf, build_id);
} else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) {
- result = GetBuildIdFromELFFile(elf->getELFFile(), build_id);
+ result = GetBuildIdFromELFFile(elf, build_id);
} else {
LOG(ERROR) << "unknown elf format in file " << obj->getFileName().data();
return false;
@@ -180,6 +180,23 @@
return ret;
}
+static BinaryRet OpenObjectFileFromString(const std::string& s, const std::string& content_name) {
+ BinaryRet ret;
+ auto buffer = llvm::MemoryBuffer::getMemBuffer(s);
+ auto binary_or_err = llvm::object::createBinary(buffer->getMemBufferRef());
+ if (!binary_or_err) {
+ LOG(ERROR) << content_name << " 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));
+ ret.obj = llvm::dyn_cast<llvm::object::ObjectFile>(ret.binary.getBinary());
+ if (ret.obj == nullptr) {
+ LOG(ERROR) << content_name << " is not an object file";
+ }
+ return ret;
+}
+
bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id) {
if (!IsValidElfPath(filename)) {
return false;
@@ -198,6 +215,31 @@
return GetBuildIdFromObjectFile(ret.obj, build_id);
}
+template <class ELFT>
+bool ReadSectionFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf, const std::string& section_name,
+ std::string* content, bool report_error = true) {
+ for (llvm::object::section_iterator it = elf->section_begin(); it != elf->section_end(); ++it) {
+ llvm::StringRef name;
+ if (it->getName(name) || name != section_name) {
+ continue;
+ }
+ llvm::StringRef data;
+ std::error_code err = it->getContents(data);
+ if (err) {
+ if (report_error) {
+ LOG(ERROR) << "failed to read section " << section_name << ": " << err;
+ }
+ return false;
+ }
+ *content = data;
+ return true;
+ }
+ if (report_error) {
+ LOG(ERROR) << "can't find section " << section_name;
+ }
+ return false;
+}
+
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
@@ -205,48 +247,41 @@
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) {
+void ReadSymbolTable(llvm::object::symbol_iterator sym_begin,
+ llvm::object::symbol_iterator sym_end,
+ std::function<void(const ElfFileSymbol&)> callback,
+ bool is_arm) {
+ for (; sym_begin != sym_end; ++sym_begin) {
ElfFileSymbol symbol;
- auto elf_symbol = static_cast<const llvm::object::ELFSymbolRef*>(&*begin);
- auto section_it = elf_symbol->getSection();
- if (!section_it) {
+ auto symbol_ref = static_cast<const llvm::object::ELFSymbolRef*>(&*sym_begin);
+ llvm::ErrorOr<llvm::object::section_iterator> section_it_or_err = symbol_ref->getSection();
+ if (!section_it_or_err) {
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()) {
+ llvm::StringRef section_name;
+ if (section_it_or_err.get()->getName(section_name) || section_name.empty()) {
continue;
}
- symbol.name = symbol_name.get();
- symbol.vaddr = elf_symbol->getValue();
+ if (section_name == ".text") {
+ symbol.is_in_text_section = true;
+ }
+ llvm::ErrorOr<llvm::StringRef> symbol_name_or_err = symbol_ref->getName();
+ if (!symbol_name_or_err || symbol_name_or_err.get().empty()) {
+ continue;
+ }
+
+ symbol.name = symbol_name_or_err.get();
+ symbol.vaddr = symbol_ref->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.len = symbol_ref->getSize();
+ llvm::object::SymbolRef::Type symbol_type = symbol_ref->getType();
+ if (symbol_type == llvm::object::SymbolRef::ST_Function) {
symbol.is_func = true;
- } else if (type == llvm::ELF::STT_NOTYPE) {
+ } else if (symbol_type == llvm::object::SymbolRef::ST_Unknown) {
if (symbol.is_in_text_section) {
symbol.is_label = true;
if (is_arm) {
@@ -265,6 +300,34 @@
}
}
+template <class ELFT>
+void ParseSymbolsFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf,
+ std::function<void(const ElfFileSymbol&)> callback) {
+ auto machine = elf->getELFFile()->getHeader()->e_machine;
+ bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
+ if (elf->symbol_begin() != elf->symbol_end()) {
+ ReadSymbolTable(elf->symbol_begin(), elf->symbol_end(), callback, is_arm);
+ } else if (elf->dynamic_symbol_begin()->getRawDataRefImpl() != llvm::object::DataRefImpl()) {
+ ReadSymbolTable(elf->dynamic_symbol_begin(), elf->dynamic_symbol_end(), callback, is_arm);
+ }
+ std::string debugdata;
+ if (ReadSectionFromELFFile(elf, ".gnu_debugdata", &debugdata, false)) {
+ LOG(VERBOSE) << "Read .gnu_debugdata from " << elf->getFileName().str();
+ std::string decompressed_data;
+ if (XzDecompress(debugdata, &decompressed_data)) {
+ std::string content_name = std::string(".gnu_debugdata in ") + elf->getFileName().str();
+ BinaryRet ret = OpenObjectFileFromString(decompressed_data, content_name);
+ if (ret.obj != nullptr) {
+ 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);
+ }
+ }
+ }
+ }
+}
+
bool MatchBuildId(llvm::object::ObjectFile* obj, const BuildId& expected_build_id,
const std::string& debug_filename) {
if (expected_build_id.IsEmpty()) {
@@ -294,6 +357,7 @@
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) {
+ LOG(VERBOSE) << "Parse symbols from file " << filename;
BinaryRet ret = OpenObjectFile(filename, file_offset, file_size);
if (ret.obj == nullptr || !MatchBuildId(ret.obj, expected_build_id, filename)) {
return false;
@@ -354,25 +418,6 @@
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)) {
@@ -384,9 +429,9 @@
}
bool result = false;
if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) {
- result = ReadSectionFromELFFile(elf->getELFFile(), section_name, content);
+ result = ReadSectionFromELFFile(elf, section_name, content);
} else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) {
- result = ReadSectionFromELFFile(elf->getELFFile(), section_name, content);
+ result = ReadSectionFromELFFile(elf, section_name, content);
} else {
LOG(ERROR) << "unknown elf format in file" << filename;
return false;
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
index 929540f..f2649e0 100644
--- a/simpleperf/read_elf_test.cpp
+++ b/simpleperf/read_elf_test.cpp
@@ -20,6 +20,7 @@
#include <map>
#include "get_test_data.h"
+#include "test_util.h"
TEST(read_elf, GetBuildIdFromElfFile) {
BuildId build_id;
@@ -38,16 +39,24 @@
(*symbols)[symbol.name] = symbol;
}
-void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
+static void CheckGlobalVariableSymbols(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");
+}
+
+static void CheckFunctionSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
+ auto pos = symbols.find("GlobalFunc");
ASSERT_NE(pos, symbols.end());
ASSERT_TRUE(pos->second.is_func);
ASSERT_TRUE(pos->second.is_in_text_section);
}
+void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
+ CheckGlobalVariableSymbols(symbols);
+ CheckFunctionSymbols(symbols);
+}
+
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,
@@ -77,6 +86,13 @@
CheckElfFileSymbols(symbols);
}
+TEST(read_elf, ParseSymbolFromMiniDebugInfoElfFile) {
+ std::map<std::string, ElfFileSymbol> symbols;
+ ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData(ELF_FILE_WITH_MINI_DEBUG_INFO), BuildId(),
+ std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ CheckFunctionSymbols(symbols);
+}
+
TEST(read_elf, arm_mapping_symbol) {
ASSERT_TRUE(IsArmMappingSymbol("$a"));
ASSERT_FALSE(IsArmMappingSymbol("$b"));
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index fca2403..e9986e5 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -23,18 +23,29 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include "dso.h"
#include "environment.h"
#include "perf_regs.h"
+#include "tracing.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"},
+ {PERF_RECORD_TRACING_DATA, "tracing_data"},
+ {SIMPLE_PERF_RECORD_KERNEL_SYMBOL, "kernel_symbol"},
+ {SIMPLE_PERF_RECORD_DSO, "dso"},
+ {SIMPLE_PERF_RECORD_SYMBOL, "symbol"},
};
auto it = record_type_names.find(record_type);
@@ -57,6 +68,11 @@
p += sizeof(T);
}
+template <>
+void MoveToBinaryFormat(const RecordHeader& data, char*& p) {
+ data.MoveToBinaryFormat(p);
+}
+
template <class T>
void MoveToBinaryFormat(const T* data_p, size_t n, char*& p) {
size_t size = n * sizeof(T);
@@ -64,19 +80,19 @@
p += size;
}
-SampleId::SampleId() {
- memset(this, 0, sizeof(SampleId));
-}
+SampleId::SampleId() { memset(this, 0, sizeof(SampleId)); }
// Return sample_id size in binary format.
-size_t SampleId::CreateContent(const perf_event_attr& attr) {
+size_t SampleId::CreateContent(const perf_event_attr& attr, uint64_t event_id) {
sample_id_all = attr.sample_id_all;
sample_type = attr.sample_type;
+ id_data.id = event_id;
// Other data are not necessary. TODO: Set missing SampleId data.
return Size();
}
-void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end) {
+void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p,
+ const char* end) {
sample_id_all = attr.sample_id_all;
sample_type = attr.sample_type;
if (sample_id_all) {
@@ -95,7 +111,9 @@
if (sample_type & PERF_SAMPLE_CPU) {
MoveFromBinaryFormat(cpu_data, p);
}
- // TODO: Add parsing of PERF_SAMPLE_IDENTIFIER.
+ if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+ MoveFromBinaryFormat(id_data, p);
+ }
}
CHECK_LE(p, end);
if (p < end) {
@@ -126,19 +144,22 @@
void SampleId::Dump(size_t indent) const {
if (sample_id_all) {
if (sample_type & PERF_SAMPLE_TID) {
- PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid, tid_data.tid);
+ PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid,
+ tid_data.tid);
}
if (sample_type & PERF_SAMPLE_TIME) {
PrintIndented(indent, "sample_id: time %" PRId64 "\n", time_data.time);
}
- if (sample_type & PERF_SAMPLE_ID) {
- PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", id_data.id);
+ if (sample_type & (PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER)) {
+ PrintIndented(indent, "sample_id: id %" PRId64 "\n", id_data.id);
}
if (sample_type & PERF_SAMPLE_STREAM_ID) {
- PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", stream_id_data.stream_id);
+ PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n",
+ stream_id_data.stream_id);
}
if (sample_type & PERF_SAMPLE_CPU) {
- PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu, cpu_data.res);
+ PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu,
+ cpu_data.res);
}
}
}
@@ -161,134 +182,164 @@
if (sample_type & PERF_SAMPLE_CPU) {
size += sizeof(PerfSampleCpuType);
}
+ if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+ size += sizeof(PerfSampleIdType);
+ }
}
return size;
}
-Record::Record() {
- memset(&header, 0, sizeof(header));
-}
-
-Record::Record(const perf_event_header* pheader) {
- header = *pheader;
-}
-
void Record::Dump(size_t indent) const {
PrintIndented(indent, "record %s: type %u, misc %u, size %u\n",
- RecordTypeToString(header.type).c_str(), header.type, header.misc, header.size);
+ RecordTypeToString(type()).c_str(), type(), misc(), size());
DumpData(indent + 1);
sample_id.Dump(indent + 1);
}
-uint64_t Record::Timestamp() const {
- return sample_id.time_data.time;
-}
+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);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+MmapRecord::MmapRecord(const perf_event_attr& attr, const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
MoveFromBinaryFormat(data, p);
filename = p;
- p += ALIGN(filename.size() + 1, 8);
+ p += Align(filename.size() + 1, 8);
CHECK_LE(p, end);
sample_id.ReadFromBinaryFormat(attr, p, end);
}
std::vector<char> MmapRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data, p);
strcpy(p, filename.c_str());
- p += ALIGN(filename.size() + 1, 8);
+ 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();
+ SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) +
+ sample_id.Size());
}
void MmapRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid,
- data.tid, data.addr, data.len);
- PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data.pgoff, filename.c_str());
+ 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());
}
-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;
+MmapRecord MmapRecord::Create(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, uint64_t event_id) {
+ MmapRecord record;
+ record.SetTypeAndMisc(PERF_RECORD_MMAP, in_kernel ? PERF_RECORD_MISC_KERNEL
+ : PERF_RECORD_MISC_USER);
+ record.data.pid = pid;
+ record.data.tid = tid;
+ record.data.addr = addr;
+ record.data.len = len;
+ record.data.pgoff = pgoff;
+ record.filename = filename;
+ size_t sample_id_size = record.sample_id.CreateContent(attr, event_id);
+ record.SetSize(record.header_size() + sizeof(record.data) +
+ Align(record.filename.size() + 1, 8) + sample_id_size);
+ return record;
+}
+
+Mmap2Record::Mmap2Record(const perf_event_attr& attr, const char* p)
+ : Record(p) {
+ const char* end = p + size();
+ p += header_size();
MoveFromBinaryFormat(data, p);
filename = p;
- p += ALIGN(filename.size() + 1, 8);
+ 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);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data, p);
strcpy(p, filename.c_str());
- p += ALIGN(filename.size() + 1, 8);
+ p += Align(filename.size() + 1, 8);
sample_id.WriteToBinaryFormat(p);
return buf;
}
void Mmap2Record::AdjustSizeBasedOnData() {
- header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size();
+ SetSize(header_size() + 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",
+ "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());
+ PrintIndented(indent, "prot %u, flags %u, filenames %s\n", data.prot,
+ data.flags, filename.c_str());
}
-CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* pheader)
- : Record(pheader) {
- const char* p = reinterpret_cast<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+CommRecord::CommRecord(const perf_event_attr& attr, const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
MoveFromBinaryFormat(data, p);
comm = p;
- p += ALIGN(strlen(p) + 1, 8);
+ p += Align(strlen(p) + 1, 8);
CHECK_LE(p, end);
sample_id.ReadFromBinaryFormat(attr, p, end);
}
std::vector<char> CommRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data, p);
strcpy(p, comm.c_str());
- p += ALIGN(comm.size() + 1, 8);
+ p += Align(comm.size() + 1, 8);
sample_id.WriteToBinaryFormat(p);
return buf;
}
void CommRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "pid %u, tid %u, comm %s\n", data.pid, data.tid, comm.c_str());
+ 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;
+CommRecord CommRecord::Create(const perf_event_attr& attr, uint32_t pid,
+ uint32_t tid, const std::string& comm,
+ uint64_t event_id) {
+ CommRecord record;
+ record.SetTypeAndMisc(PERF_RECORD_COMM, 0);
+ record.data.pid = pid;
+ record.data.tid = tid;
+ record.comm = comm;
+ size_t sample_id_size = record.sample_id.CreateContent(attr, event_id);
+ record.SetSize(record.header_size() + sizeof(record.data) +
+ Align(record.comm.size() + 1, 8) + sample_id_size);
+ return record;
+}
+
+ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, const char* p)
+ : Record(p) {
+ const char* end = p + size();
+ p += header_size();
MoveFromBinaryFormat(data, p);
CHECK_LE(p, end);
sample_id.ReadFromBinaryFormat(attr, p, end);
}
std::vector<char> ExitOrForkRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data, p);
@@ -297,16 +348,57 @@
}
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);
+ PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data.pid,
+ data.ppid, data.tid, data.ptid);
}
-SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader)
- : Record(pheader) {
- const char* p = reinterpret_cast<const char*>(pheader + 1);
- const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+ForkRecord ForkRecord::Create(const perf_event_attr& attr, uint32_t pid,
+ uint32_t tid, uint32_t ppid, uint32_t ptid,
+ uint64_t event_id) {
+ ForkRecord record;
+ record.SetTypeAndMisc(PERF_RECORD_FORK, 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, event_id);
+ record.SetSize(record.header_size() + sizeof(record.data) + sample_id_size);
+ return record;
+}
+
+LostRecord::LostRecord(const perf_event_attr& attr, const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
+ MoveFromBinaryFormat(id, p);
+ MoveFromBinaryFormat(lost, p);
+ CHECK_LE(p, end);
+ sample_id.ReadFromBinaryFormat(attr, p, end);
+}
+
+std::vector<char> LostRecord::BinaryFormat() const {
+ std::vector<char> buf(size());
+ char* p = buf.data();
+ MoveToBinaryFormat(header, p);
+ MoveToBinaryFormat(id, p);
+ MoveToBinaryFormat(lost, p);
+ sample_id.WriteToBinaryFormat(p);
+ return buf;
+}
+
+void LostRecord::DumpData(size_t indent) const {
+ PrintIndented(indent, "id %" PRIu64 ", lost %" PRIu64 "\n", id, lost);
+}
+
+SampleRecord::SampleRecord(const perf_event_attr& attr, const char* p)
+ : Record(p) {
+ const char* end = p + size();
+ p += header_size();
sample_type = attr.sample_type;
+ if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+ MoveFromBinaryFormat(id_data, p);
+ }
if (sample_type & PERF_SAMPLE_IP) {
MoveFromBinaryFormat(ip_data, p);
}
@@ -384,9 +476,12 @@
}
std::vector<char> SampleRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
+ if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+ MoveToBinaryFormat(id_data, p);
+ }
if (sample_type & PERF_SAMPLE_IP) {
MoveToBinaryFormat(ip_data, p);
}
@@ -429,7 +524,8 @@
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);
+ MoveToBinaryFormat(regs_user_data.regs.data(), regs_user_data.regs.size(),
+ p);
}
}
if (sample_type & PERF_SAMPLE_STACK_USER) {
@@ -441,17 +537,18 @@
}
}
- // 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.
+ // 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;
+ LOG(DEBUG) << "Record (type " << RecordTypeToString(type())
+ << ") size is changed from " << this->size() << " to " << size;
+ SetSize(size);
}
void SampleRecord::DumpData(size_t indent) const {
@@ -468,7 +565,7 @@
if (sample_type & PERF_SAMPLE_ADDR) {
PrintIndented(indent, "addr %p\n", reinterpret_cast<void*>(addr_data.addr));
}
- if (sample_type & PERF_SAMPLE_ID) {
+ if (sample_type & (PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER)) {
PrintIndented(indent, "id %" PRId64 "\n", id_data.id);
}
if (sample_type & PERF_SAMPLE_STREAM_ID) {
@@ -481,23 +578,27 @@
PrintIndented(indent, "period %" PRId64 "\n", period_data.period);
}
if (sample_type & PERF_SAMPLE_CALLCHAIN) {
- PrintIndented(indent, "callchain nr=%" PRIu64 "\n", callchain_data.ips.size());
+ 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());
+ 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());
+ 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",
+ PrintIndented(indent + 1, "from 0x%" PRIx64 ", to 0x%" PRIx64
+ ", flags 0x%" PRIx64 "\n",
item.from, item.to, item.flags);
}
}
@@ -505,16 +606,18 @@
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++]);
+ 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* 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, "");
@@ -527,30 +630,27 @@
}
}
-uint64_t SampleRecord::Timestamp() const {
- return time_data.time;
-}
+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;
+BuildIdRecord::BuildIdRecord(const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
MoveFromBinaryFormat(pid, p);
build_id = BuildId(p, BUILD_ID_SIZE);
- p += ALIGN(build_id.Size(), 8);
+ p += Align(build_id.Size(), 8);
filename = p;
- p += ALIGN(filename.size() + 1, 64);
+ p += Align(filename.size() + 1, 64);
CHECK_EQ(p, end);
}
std::vector<char> BuildIdRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(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);
+ p += Align(build_id.Size(), 8);
strcpy(p, filename.c_str());
- p += ALIGN(filename.size() + 1, 64);
return buf;
}
@@ -560,145 +660,245 @@
PrintIndented(indent, "filename %s\n", filename.c_str());
}
-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);
+BuildIdRecord BuildIdRecord::Create(bool in_kernel, pid_t pid,
+ const BuildId& build_id,
+ const std::string& filename) {
+ BuildIdRecord record;
+ record.SetTypeAndMisc(PERF_RECORD_BUILD_ID, in_kernel
+ ? PERF_RECORD_MISC_KERNEL
+ : PERF_RECORD_MISC_USER);
+ record.pid = pid;
+ record.build_id = build_id;
+ record.filename = filename;
+ record.SetSize(record.header_size() + sizeof(record.pid) +
+ Align(record.build_id.Size(), 8) +
+ Align(filename.size() + 1, 64));
+ return record;
+}
+
+KernelSymbolRecord::KernelSymbolRecord(const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
+ uint32_t size;
+ MoveFromBinaryFormat(size, p);
+ kallsyms.resize(size);
+ if (size != 0u) {
+ memcpy(&kallsyms[0], p, size);
+ }
+ p += Align(size, 8);
+ CHECK_EQ(p, end);
+}
+
+std::vector<char> KernelSymbolRecord::BinaryFormat() const {
+ std::vector<char> buf(size());
+ char* p = buf.data();
+ MoveToBinaryFormat(header, p);
+ uint32_t size = static_cast<uint32_t>(kallsyms.size());
+ MoveToBinaryFormat(size, p);
+ memcpy(p, kallsyms.data(), size);
+ return buf;
+}
+
+void KernelSymbolRecord::DumpData(size_t indent) const {
+ PrintIndented(indent, "kallsyms: %s\n", kallsyms.c_str());
+}
+
+KernelSymbolRecord KernelSymbolRecord::Create(std::string kallsyms) {
+ KernelSymbolRecord r;
+ r.SetTypeAndMisc(SIMPLE_PERF_RECORD_KERNEL_SYMBOL, 0);
+ r.kallsyms = std::move(kallsyms);
+ r.SetSize(r.header_size() + 4 + Align(r.kallsyms.size(), 8));
+ return r;
+}
+
+DsoRecord::DsoRecord(const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
+ MoveFromBinaryFormat(dso_type, p);
+ MoveFromBinaryFormat(dso_id, p);
+ dso_name = p;
+ p += Align(dso_name.size() + 1, 8);
+ CHECK_EQ(p, end);
+}
+
+std::vector<char> DsoRecord::BinaryFormat() const {
+ std::vector<char> buf(size());
+ char* p = buf.data();
+ MoveToBinaryFormat(header, p);
+ MoveToBinaryFormat(dso_type, p);
+ MoveToBinaryFormat(dso_id, p);
+ strcpy(p, dso_name.c_str());
+ return buf;
+}
+
+void DsoRecord::DumpData(size_t indent) const {
+ PrintIndented(indent, "dso_type: %s(%" PRIu64 ")\n",
+ DsoTypeToString(static_cast<DsoType>(dso_type)), dso_type);
+ PrintIndented(indent, "dso_id: %" PRIu64 "\n", dso_id);
+ PrintIndented(indent, "dso_name: %s\n", dso_name.c_str());
+}
+
+DsoRecord DsoRecord::Create(uint64_t dso_type, uint64_t dso_id,
+ const std::string& dso_name) {
+ DsoRecord record;
+ record.SetTypeAndMisc(SIMPLE_PERF_RECORD_DSO, 0);
+ record.dso_type = dso_type;
+ record.dso_id = dso_id;
+ record.dso_name = dso_name;
+ record.SetSize(record.header_size() + 2 * sizeof(uint64_t) +
+ Align(record.dso_name.size() + 1, 8));
+ return record;
+}
+
+SymbolRecord::SymbolRecord(const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
+ MoveFromBinaryFormat(addr, p);
+ MoveFromBinaryFormat(len, p);
+ MoveFromBinaryFormat(dso_id, p);
+ name = p;
+ p += Align(name.size() + 1, 8);
+ CHECK_EQ(p, end);
+}
+
+std::vector<char> SymbolRecord::BinaryFormat() const {
+ std::vector<char> buf(size());
+ char* p = buf.data();
+ MoveToBinaryFormat(header, p);
+ MoveToBinaryFormat(addr, p);
+ MoveToBinaryFormat(len, p);
+ MoveToBinaryFormat(dso_id, p);
+ strcpy(p, name.c_str());
+ return buf;
+}
+
+void SymbolRecord::DumpData(size_t indent) const {
+ PrintIndented(indent, "name: %s\n", name.c_str());
+ PrintIndented(indent, "addr: 0x%" PRIx64 "\n", addr);
+ PrintIndented(indent, "len: 0x%" PRIx64 "\n", len);
+ PrintIndented(indent, "dso_id: %" PRIu64 "\n", dso_id);
+}
+
+SymbolRecord SymbolRecord::Create(uint64_t addr, uint64_t len,
+ const std::string& name, uint64_t dso_id) {
+ SymbolRecord record;
+ record.SetTypeAndMisc(SIMPLE_PERF_RECORD_SYMBOL, 0);
+ record.addr = addr;
+ record.len = len;
+ record.dso_id = dso_id;
+ record.name = name;
+ record.SetSize(record.header_size() + 3 * sizeof(uint64_t) +
+ Align(record.name.size() + 1, 8));
+ return record;
+}
+
+TracingDataRecord::TracingDataRecord(const char* p) : Record(p) {
+ const char* end = p + size();
+ p += header_size();
+ uint32_t size;
+ MoveFromBinaryFormat(size, p);
+ data.resize(size);
+ memcpy(data.data(), p, size);
+ p += Align(size, 64);
+ CHECK_EQ(p, end);
+}
+
+std::vector<char> TracingDataRecord::BinaryFormat() const {
+ std::vector<char> buf(size());
+ char* p = buf.data();
+ MoveToBinaryFormat(header, p);
+ uint32_t size = static_cast<uint32_t>(data.size());
+ MoveToBinaryFormat(size, p);
+ memcpy(p, data.data(), size);
+ return buf;
+}
+
+void TracingDataRecord::DumpData(size_t indent) const {
+ Tracing tracing(data);
+ tracing.Dump(indent);
+}
+
+TracingDataRecord TracingDataRecord::Create(std::vector<char> tracing_data) {
+ TracingDataRecord record;
+ record.SetTypeAndMisc(PERF_RECORD_TRACING_DATA, 0);
+ record.data = std::move(tracing_data);
+ record.SetSize(record.header_size() + sizeof(uint32_t) +
+ Align(record.data.size(), 64));
+ return record;
+}
+
+UnknownRecord::UnknownRecord(const char* p) : Record(p) {
+ data.insert(data.end(), p + header_size(), p + size());
}
std::vector<char> UnknownRecord::BinaryFormat() const {
- std::vector<char> buf(header.size);
+ std::vector<char> buf(size());
char* p = buf.data();
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(data.data(), data.size(), p);
return buf;
}
-void UnknownRecord::DumpData(size_t) const {
-}
+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) {
+std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr,
+ uint32_t type, const char* p) {
+ switch (type) {
case PERF_RECORD_MMAP:
- return std::unique_ptr<Record>(new MmapRecord(attr, pheader));
+ return std::unique_ptr<Record>(new MmapRecord(attr, p));
case PERF_RECORD_MMAP2:
- return std::unique_ptr<Record>(new Mmap2Record(attr, pheader));
+ return std::unique_ptr<Record>(new Mmap2Record(attr, p));
case PERF_RECORD_COMM:
- return std::unique_ptr<Record>(new CommRecord(attr, pheader));
+ return std::unique_ptr<Record>(new CommRecord(attr, p));
case PERF_RECORD_EXIT:
- return std::unique_ptr<Record>(new ExitRecord(attr, pheader));
+ return std::unique_ptr<Record>(new ExitRecord(attr, p));
case PERF_RECORD_FORK:
- return std::unique_ptr<Record>(new ForkRecord(attr, pheader));
+ return std::unique_ptr<Record>(new ForkRecord(attr, p));
+ case PERF_RECORD_LOST:
+ return std::unique_ptr<Record>(new LostRecord(attr, p));
case PERF_RECORD_SAMPLE:
- return std::unique_ptr<Record>(new SampleRecord(attr, pheader));
+ return std::unique_ptr<Record>(new SampleRecord(attr, p));
+ case PERF_RECORD_TRACING_DATA:
+ return std::unique_ptr<Record>(new TracingDataRecord(p));
+ case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+ return std::unique_ptr<Record>(new KernelSymbolRecord(p));
+ case SIMPLE_PERF_RECORD_DSO:
+ return std::unique_ptr<Record>(new DsoRecord(p));
+ case SIMPLE_PERF_RECORD_SYMBOL:
+ return std::unique_ptr<Record>(new SymbolRecord(p));
default:
- return std::unique_ptr<Record>(new UnknownRecord(pheader));
+ return std::unique_ptr<Record>(new UnknownRecord(p));
}
}
-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>> 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;
+ RecordHeader header(p);
+ CHECK_LE(p + header.size, end);
+ CHECK_NE(0u, header.size);
+ result.push_back(ReadRecordFromBuffer(attr, header.type, p));
+ 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,
- uint64_t addr, uint64_t len, uint64_t pgoff,
- const std::string& filename) {
- MmapRecord record;
- record.header.type = PERF_RECORD_MMAP;
- record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
- record.data.pid = pid;
- record.data.tid = tid;
- record.data.addr = addr;
- record.data.len = len;
- record.data.pgoff = pgoff;
- record.filename = filename;
- size_t sample_id_size = record.sample_id.CreateContent(attr);
- record.header.size = sizeof(record.header) + sizeof(record.data) +
- ALIGN(record.filename.size() + 1, 8) + sample_id_size;
- return record;
-}
-
-CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
- const std::string& comm) {
- CommRecord record;
- record.header.type = PERF_RECORD_COMM;
- record.header.misc = 0;
- record.data.pid = pid;
- record.data.tid = tid;
- record.comm = comm;
- size_t sample_id_size = record.sample_id.CreateContent(attr);
- record.header.size = sizeof(record.header) + sizeof(record.data) +
- ALIGN(record.comm.size() + 1, 8) + sample_id_size;
- return record;
-}
-
-ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
- uint32_t ptid) {
- ForkRecord record;
- record.header.type = PERF_RECORD_FORK;
- record.header.misc = 0;
- record.data.pid = pid;
- record.data.ppid = ppid;
- record.data.tid = tid;
- record.data.ptid = ptid;
- record.data.time = 0;
- size_t sample_id_size = record.sample_id.CreateContent(attr);
- record.header.size = sizeof(record.header) + sizeof(record.data) + sample_id_size;
- return record;
-}
-
-BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
- const std::string& filename) {
- BuildIdRecord record;
- record.header.type = PERF_RECORD_BUILD_ID;
- record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
- record.pid = pid;
- record.build_id = build_id;
- record.filename = filename;
- record.header.size = sizeof(record.header) + sizeof(record.pid) +
- ALIGN(record.build_id.Size(), 8) + ALIGN(filename.size() + 1, 64);
- return record;
-}
-
-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);
+bool RecordCache::RecordWithSeq::IsHappensBefore(
+ const RecordWithSeq& other) const {
+ bool is_sample = (record->type() == PERF_RECORD_SAMPLE);
+ bool is_other_sample = (other.record->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 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;
}
@@ -711,37 +911,30 @@
return r2.IsHappensBefore(r1);
}
-RecordCache::RecordCache(const perf_event_attr& attr, size_t min_cache_size,
+RecordCache::RecordCache(bool has_timestamp, 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)),
+ : has_timestamp_(has_timestamp),
min_cache_size_(min_cache_size),
min_time_diff_in_ns_(min_time_diff_in_ns),
last_time_(0),
cur_seq_(0),
- queue_(RecordComparator()) {
-}
+ queue_(RecordComparator()) {}
-RecordCache::~RecordCache() {
- PopAll();
-}
+RecordCache::~RecordCache() { PopAll(); }
-void RecordCache::Push(const char* data, size_t size) {
- std::vector<std::unique_ptr<Record>> records = ReadRecordsFromBuffer(attr_, data, size);
+void RecordCache::Push(std::unique_ptr<Record> record) {
if (has_timestamp_) {
- for (const auto& r : records) {
- last_time_ = std::max(last_time_, r->Timestamp());
- }
+ last_time_ = std::max(last_time_, record->Timestamp());
}
+ queue_.push(CreateRecordWithSeq(record.release()));
+}
+
+void RecordCache::Push(std::vector<std::unique_ptr<Record>> records) {
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;
@@ -765,7 +958,7 @@
return result;
}
-RecordCache::RecordWithSeq RecordCache::CreateRecordWithSeq(Record *r) {
+RecordCache::RecordWithSeq RecordCache::CreateRecordWithSeq(Record* r) {
RecordWithSeq result;
result.seq = cur_seq_++;
result.record = r;
diff --git a/simpleperf/record.h b/simpleperf/record.h
index a94a917..3b7a717 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -25,6 +25,8 @@
#include <string>
#include <vector>
+#include <android-base/logging.h>
+
#include "build_id.h"
#include "perf_event.h"
@@ -34,13 +36,35 @@
struct ThreadMmap;
enum user_record_type {
+ PERF_RECORD_USER_DEFINED_TYPE_START = 64,
PERF_RECORD_ATTR = 64,
PERF_RECORD_EVENT_TYPE,
PERF_RECORD_TRACING_DATA,
PERF_RECORD_BUILD_ID,
PERF_RECORD_FINISHED_ROUND,
+
+ SIMPLE_PERF_RECORD_TYPE_START = 32768,
+ SIMPLE_PERF_RECORD_KERNEL_SYMBOL,
+ SIMPLE_PERF_RECORD_DSO,
+ SIMPLE_PERF_RECORD_SYMBOL,
+ SIMPLE_PERF_RECORD_SPLIT,
+ SIMPLE_PERF_RECORD_SPLIT_END,
};
+// perf_event_header uses u16 to store record size. However, that is not
+// enough for storing records like KERNEL_SYMBOL or TRACING_DATA. So define
+// a simpleperf_record_header struct to store record header for simpleperf
+// defined records (type > SIMPLE_PERF_RECORD_TYPE_START).
+struct simpleperf_record_header {
+ uint32_t type;
+ uint16_t size1;
+ uint16_t size0;
+};
+
+static_assert(
+ sizeof(simpleperf_record_header) == sizeof(perf_event_header),
+ "simpleperf_record_header should have the same size as perf_event_header");
+
struct PerfSampleIpType {
uint64_t ip;
};
@@ -81,12 +105,13 @@
std::vector<char> data;
};
+struct BranchStackItemType {
+ uint64_t from;
+ uint64_t to;
+ uint64_t flags;
+};
+
struct PerfSampleBranchStackType {
- struct BranchStackItemType {
- uint64_t from;
- uint64_t to;
- uint64_t flags;
- };
std::vector<BranchStackItemType> stack;
};
@@ -101,26 +126,69 @@
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.
+struct RecordHeader {
+ public:
+ uint32_t type;
+ uint16_t misc;
+ uint32_t size;
+
+ RecordHeader() : type(0), misc(0), size(0) {}
+
+ RecordHeader(const char* p) {
+ auto pheader = reinterpret_cast<const perf_event_header*>(p);
+ if (pheader->type < SIMPLE_PERF_RECORD_TYPE_START) {
+ type = pheader->type;
+ misc = pheader->misc;
+ size = pheader->size;
+ } else {
+ auto sheader = reinterpret_cast<const simpleperf_record_header*>(p);
+ type = sheader->type;
+ misc = 0;
+ size = (sheader->size1 << 16) | sheader->size0;
+ }
+ }
+
+ void MoveToBinaryFormat(char*& p) const {
+ if (type < SIMPLE_PERF_RECORD_TYPE_START) {
+ auto pheader = reinterpret_cast<perf_event_header*>(p);
+ pheader->type = type;
+ pheader->misc = misc;
+ CHECK_LT(size, 1u << 16);
+ pheader->size = static_cast<uint16_t>(size);
+ } else {
+ auto sheader = reinterpret_cast<simpleperf_record_header*>(p);
+ sheader->type = type;
+ CHECK_EQ(misc, 0u);
+ sheader->size1 = size >> 16;
+ sheader->size0 = size & 0xffff;
+ }
+ p += sizeof(perf_event_header);
+ }
+};
+
+// SampleId is optional at the end of a record in binary format. Its content is
+// determined by sample_id_all and sample_type in perf_event_attr. To avoid the
+// complexity of referring to perf_event_attr each time, we copy sample_id_all
+// and sample_type inside the SampleId structure.
struct SampleId {
bool sample_id_all;
uint64_t sample_type;
- PerfSampleTidType tid_data; // Valid if sample_id_all && PERF_SAMPLE_TID.
- PerfSampleTimeType time_data; // Valid if sample_id_all && PERF_SAMPLE_TIME.
- PerfSampleIdType id_data; // Valid if sample_id_all && PERF_SAMPLE_ID.
- PerfSampleStreamIdType stream_id_data; // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID.
- PerfSampleCpuType cpu_data; // Valid if sample_id_all && PERF_SAMPLE_CPU.
+ PerfSampleTidType tid_data; // Valid if sample_id_all && PERF_SAMPLE_TID.
+ PerfSampleTimeType time_data; // Valid if sample_id_all && PERF_SAMPLE_TIME.
+ PerfSampleIdType id_data; // Valid if sample_id_all && PERF_SAMPLE_ID.
+ PerfSampleStreamIdType
+ stream_id_data; // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID.
+ PerfSampleCpuType cpu_data; // Valid if sample_id_all && PERF_SAMPLE_CPU.
SampleId();
// Create the content of sample_id. It depends on the attr we use.
- size_t CreateContent(const perf_event_attr& attr);
+ size_t CreateContent(const perf_event_attr& attr, uint64_t event_id);
// Parse sample_id from binary format in the buffer pointed by p.
- void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end);
+ void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p,
+ const char* end);
// Write the binary format of sample_id to the buffer pointed by p.
void WriteToBinaryFormat(char*& p) const;
@@ -128,29 +196,41 @@
size_t Size() const;
};
-// Usually one record contains the following three parts in order in binary format:
-// perf_event_header (at the head of a record, containing type and size information)
+// Usually one record contains the following three parts in order in binary
+// format:
+// RecordHeader (at the head of a record, containing type and size info)
// data depends on the record type
-// sample_id (optional part at the end of a record)
-// We hold the common parts (perf_event_header and sample_id) in the base class Record, and
-// hold the type specific data part in classes derived from Record.
+// SampleId (optional part at the end of a record)
+// We hold the common parts (RecordHeader and SampleId) in the base class
+// Record, and hold the type specific data part in classes derived from Record.
struct Record {
- perf_event_header header;
+ RecordHeader header;
SampleId sample_id;
- Record();
- Record(const perf_event_header* pheader);
+ Record() {}
+ Record(const char* p) : header(p) {}
- virtual ~Record() {
+ virtual ~Record() {}
+
+ uint32_t type() const { return header.type; }
+
+ uint16_t misc() const { return header.misc; }
+
+ uint32_t size() const { return header.size; }
+
+ static uint32_t header_size() { return sizeof(perf_event_header); }
+
+ bool InKernel() const {
+ return (header.misc & PERF_RECORD_MISC_CPUMODE_MASK) ==
+ PERF_RECORD_MISC_KERNEL;
}
- size_t size() const {
- return header.size;
+ void SetTypeAndMisc(uint32_t type, uint16_t misc) {
+ header.type = type;
+ header.misc = misc;
}
- uint32_t type() const {
- return header.type;
- }
+ void SetSize(uint32_t size) { header.size = size; }
void Dump(size_t indent = 0) const;
virtual std::vector<char> BinaryFormat() const = 0;
@@ -172,10 +252,15 @@
MmapRecord() { // For CreateMmapRecord.
}
- MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+ MmapRecord(const perf_event_attr& attr, const char* p);
std::vector<char> BinaryFormat() const override;
void AdjustSizeBasedOnData();
+ static MmapRecord Create(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, uint64_t event_id);
+
protected:
void DumpData(size_t indent) const override;
};
@@ -194,10 +279,9 @@
} data;
std::string filename;
- Mmap2Record() {
- }
+ Mmap2Record() {}
- Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader);
+ Mmap2Record(const perf_event_attr& attr, const char* p);
std::vector<char> BinaryFormat() const override;
void AdjustSizeBasedOnData();
@@ -211,12 +295,15 @@
} data;
std::string comm;
- CommRecord() {
- }
+ CommRecord() {}
- CommRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+ CommRecord(const perf_event_attr& attr, const char* p);
std::vector<char> BinaryFormat() const override;
+ static CommRecord Create(const perf_event_attr& attr, uint32_t pid,
+ uint32_t tid, const std::string& comm,
+ uint64_t event_id);
+
protected:
void DumpData(size_t indent) const override;
};
@@ -228,9 +315,8 @@
uint64_t time;
} data;
- ExitOrForkRecord() {
- }
- ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader);
+ ExitOrForkRecord() {}
+ ExitOrForkRecord(const perf_event_attr& attr, const char* p);
std::vector<char> BinaryFormat() const override;
protected:
@@ -238,21 +324,36 @@
};
struct ExitRecord : public ExitOrForkRecord {
- ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader)
- : ExitOrForkRecord(attr, pheader) {
- }
+ ExitRecord(const perf_event_attr& attr, const char* p)
+ : ExitOrForkRecord(attr, p) {}
};
struct ForkRecord : public ExitOrForkRecord {
- ForkRecord() {
- }
- ForkRecord(const perf_event_attr& attr, const perf_event_header* pheader)
- : ExitOrForkRecord(attr, pheader) {
- }
+ ForkRecord() {}
+ ForkRecord(const perf_event_attr& attr, const char* p)
+ : ExitOrForkRecord(attr, p) {}
+
+ static ForkRecord Create(const perf_event_attr& attr, uint32_t pid,
+ uint32_t tid, uint32_t ppid, uint32_t ptid,
+ uint64_t event_id);
+};
+
+struct LostRecord : public Record {
+ uint64_t id;
+ uint64_t lost;
+
+ LostRecord() {}
+
+ LostRecord(const perf_event_attr& attr, const char* p);
+ std::vector<char> BinaryFormat() const override;
+
+ protected:
+ void DumpData(size_t indent) const override;
};
struct SampleRecord : public Record {
- uint64_t sample_type; // sample_type is a bit mask determining which fields below are valid.
+ uint64_t sample_type; // sample_type is a bit mask determining which fields
+ // below are valid.
PerfSampleIpType ip_data; // Valid if PERF_SAMPLE_IP.
PerfSampleTidType tid_data; // Valid if PERF_SAMPLE_TID.
@@ -263,13 +364,14 @@
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.
+ 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);
+ SampleRecord(const perf_event_attr& attr, const char* p);
std::vector<char> BinaryFormat() const override;
void AdjustSizeBasedOnData();
uint64_t Timestamp() const override;
@@ -278,62 +380,136 @@
void DumpData(size_t indent) const override;
};
-// BuildIdRecord is defined in user-space, stored in BuildId feature section in record file.
+// BuildIdRecord is defined in user-space, stored in BuildId feature section in
+// record file.
struct BuildIdRecord : public Record {
uint32_t pid;
BuildId build_id;
std::string filename;
- BuildIdRecord() {
- }
+ BuildIdRecord() {}
- BuildIdRecord(const perf_event_header* pheader);
+ BuildIdRecord(const char* p);
std::vector<char> BinaryFormat() const override;
+ static BuildIdRecord Create(bool in_kernel, pid_t pid,
+ const BuildId& build_id,
+ const std::string& filename);
+
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 KernelSymbolRecord : public Record {
+ std::string kallsyms;
+
+ KernelSymbolRecord() {}
+
+ KernelSymbolRecord(const char* p);
+ std::vector<char> BinaryFormat() const override;
+
+ static KernelSymbolRecord Create(std::string kallsyms);
+
+ protected:
+ void DumpData(size_t indent) const override;
+};
+
+struct DsoRecord : public Record {
+ uint64_t dso_type;
+ uint64_t dso_id;
+ std::string dso_name;
+
+ DsoRecord() {}
+
+ DsoRecord(const char* p);
+ std::vector<char> BinaryFormat() const override;
+
+ static DsoRecord Create(uint64_t dso_type, uint64_t dso_id,
+ const std::string& dso_name);
+
+ protected:
+ void DumpData(size_t indent) const override;
+};
+
+struct SymbolRecord : public Record {
+ uint64_t addr;
+ uint64_t len;
+ uint64_t dso_id;
+ std::string name;
+
+ SymbolRecord() {}
+
+ SymbolRecord(const char* p);
+ std::vector<char> BinaryFormat() const override;
+
+ static SymbolRecord Create(uint64_t addr, uint64_t len,
+ const std::string& name, uint64_t dso_id);
+
+ protected:
+ void DumpData(size_t indent) const override;
+};
+
+struct TracingDataRecord : public Record {
+ std::vector<char> data;
+
+ TracingDataRecord() {}
+
+ TracingDataRecord(const char* p);
+ std::vector<char> BinaryFormat() const override;
+
+ static TracingDataRecord Create(std::vector<char> tracing_data);
+
+ 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);
+ UnknownRecord(const char* p);
std::vector<char> BinaryFormat() const override;
protected:
void DumpData(size_t indent) const override;
};
+std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr,
+ uint32_t type, const char* p);
+std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(
+ const perf_event_attr& attr, const char* buf, size_t buf_size);
+
// 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.
+// 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,
+ RecordCache(bool has_timestamp, 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);
+ void Push(std::vector<std::unique_ptr<Record>> records);
std::unique_ptr<Record> Pop();
std::vector<std::unique_ptr<Record>> PopAll();
private:
struct RecordWithSeq {
uint32_t seq;
- Record *record;
+ Record* record;
bool IsHappensBefore(const RecordWithSeq& other) const;
};
@@ -342,29 +518,15 @@
bool operator()(const RecordWithSeq& r1, const RecordWithSeq& r2);
};
- RecordWithSeq CreateRecordWithSeq(Record *r);
+ 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_;
+ RecordComparator> queue_;
};
-std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr& attr,
- const char* buf, size_t buf_size);
-std::unique_ptr<Record> ReadRecordFromFile(const perf_event_attr& attr, FILE* fp);
-MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
- uint64_t addr, uint64_t len, uint64_t pgoff,
- const std::string& filename);
-CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
- const std::string& comm);
-ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
- uint32_t ptid);
-BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
- const std::string& filename);
-
#endif // SIMPLE_PERF_RECORD_H_
diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h
index 03768dc..a15fad2 100644
--- a/simpleperf/record_equal_test.h
+++ b/simpleperf/record_equal_test.h
@@ -31,13 +31,15 @@
}
static void CheckRecordEqual(const Record& r1, const Record& r2) {
- ASSERT_EQ(0, memcmp(&r1.header, &r2.header, sizeof(r1.header)));
+ ASSERT_EQ(r1.type(), r2.type());
+ ASSERT_EQ(r1.misc(), r2.misc());
+ ASSERT_EQ(r1.size(), r2.size());
ASSERT_EQ(0, memcmp(&r1.sample_id, &r2.sample_id, sizeof(r1.sample_id)));
- if (r1.header.type == PERF_RECORD_MMAP) {
+ if (r1.type() == PERF_RECORD_MMAP) {
CheckMmapRecordDataEqual(static_cast<const MmapRecord&>(r1), static_cast<const MmapRecord&>(r2));
- } else if (r1.header.type == PERF_RECORD_COMM) {
+ } else if (r1.type() == PERF_RECORD_COMM) {
CheckCommRecordDataEqual(static_cast<const CommRecord&>(r1), static_cast<const CommRecord&>(r2));
- } else if (r1.header.type == PERF_RECORD_BUILD_ID) {
+ } else if (r1.type() == PERF_RECORD_BUILD_ID) {
CheckBuildIdRecordDataEqual(static_cast<const BuildIdRecord&>(r1),
static_cast<const BuildIdRecord&>(r2));
}
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index c0f53b1..3b6df7e 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -23,6 +23,7 @@
#include <map>
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
#include <android-base/macros.h>
@@ -44,11 +45,7 @@
~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());
- }
+ bool WriteRecord(const Record& record);
bool WriteFeatureHeader(size_t feature_count);
bool WriteBuildIdFeature(const std::vector<BuildIdRecord>& build_id_records);
@@ -67,6 +64,7 @@
std::vector<std::string>* hit_kernel_modules,
std::vector<std::string>* hit_user_files);
bool WriteFileHeader();
+ bool WriteData(const void* buf, size_t len);
bool Write(const void* buf, size_t len);
bool SeekFileEnd(uint64_t* file_end);
bool WriteFeatureBegin(uint64_t* start_offset);
@@ -99,15 +97,22 @@
return header_;
}
- const std::vector<PerfFileFormat::FileAttr>& AttrSection() const {
- return file_attrs_;
+ std::vector<AttrWithId> AttrSection() const {
+ std::vector<AttrWithId> result(file_attrs_.size());
+ for (size_t i = 0; i < file_attrs_.size(); ++i) {
+ result[i].attr = &file_attrs_[i].attr;
+ result[i].ids = event_ids_for_file_attrs_[i];
+ }
+ return result;
}
const std::map<int, PerfFileFormat::SectionDesc>& FeatureSectionDescriptors() const {
return feature_section_descriptors_;
}
-
- bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
+ bool HasFeature(int feature) const {
+ return feature_section_descriptors_.find(feature) != feature_section_descriptors_.end();
+ }
+ bool ReadFeatureSection(int feature, std::vector<char>* data);
// 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();
@@ -122,16 +127,23 @@
RecordFileReader(const std::string& filename, FILE* fp);
bool ReadHeader();
bool ReadAttrSection();
+ bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
bool ReadFeatureSectionDescriptors();
- bool ReadFeatureSection(int feature, std::vector<char>* data);
+ std::unique_ptr<Record> ReadRecord(size_t* nbytes_read);
+ bool Read(void* buf, size_t len);
const std::string filename_;
FILE* record_fp_;
PerfFileFormat::FileHeader header_;
std::vector<PerfFileFormat::FileAttr> file_attrs_;
+ std::vector<std::vector<uint64_t>> event_ids_for_file_attrs_;
+ std::unordered_map<uint64_t, perf_event_attr*> event_id_to_attr_map_;
std::map<int, PerfFileFormat::SectionDesc> feature_section_descriptors_;
+ size_t event_id_pos_in_sample_records_;
+ size_t event_id_reverse_pos_in_non_sample_records_;
+
DISALLOW_COPY_AND_ASSIGN(RecordFileReader);
};
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index f126a6b..68cee21 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -23,7 +23,7 @@
#include <android-base/logging.h>
-#include "perf_event.h"
+#include "event_attr.h"
#include "record.h"
#include "utils.h"
@@ -45,7 +45,8 @@
}
RecordFileReader::RecordFileReader(const std::string& filename, FILE* fp)
- : filename_(filename), record_fp_(fp) {
+ : filename_(filename), record_fp_(fp), event_id_pos_in_sample_records_(0),
+ event_id_reverse_pos_in_non_sample_records_(0) {
}
RecordFileReader::~RecordFileReader() {
@@ -65,11 +66,7 @@
}
bool RecordFileReader::ReadHeader() {
- if (fread(&header_, sizeof(header_), 1, record_fp_) != 1) {
- PLOG(ERROR) << "failed to read file " << filename_;
- return false;
- }
- return true;
+ return Read(&header_, sizeof(header_));
}
bool RecordFileReader::ReadAttrSection() {
@@ -88,8 +85,7 @@
}
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_;
+ if (!Read(buf.data(), buf.size())) {
return false;
}
// The size of perf_event_attr is changing between different linux kernel versions.
@@ -102,6 +98,26 @@
memcpy(&attr.ids, &buf[perf_event_attr_size], section_desc_size);
file_attrs_.push_back(attr);
}
+ if (file_attrs_.size() > 1) {
+ std::vector<perf_event_attr> attrs;
+ for (const auto& file_attr : file_attrs_) {
+ attrs.push_back(file_attr.attr);
+ }
+ if (!GetCommonEventIdPositionsForAttrs(attrs, &event_id_pos_in_sample_records_,
+ &event_id_reverse_pos_in_non_sample_records_)) {
+ return false;
+ }
+ }
+ for (size_t i = 0; i < file_attrs_.size(); ++i) {
+ std::vector<uint64_t> ids;
+ if (!ReadIdsForAttr(file_attrs_[i], &ids)) {
+ return false;
+ }
+ event_ids_for_file_attrs_.push_back(ids);
+ for (auto id : ids) {
+ event_id_to_attr_map_[id] = &file_attrs_[i].attr;
+ }
+ }
return true;
}
@@ -121,8 +137,7 @@
}
for (const auto& id : features) {
SectionDesc desc;
- if (fread(&desc, sizeof(desc), 1, record_fp_) != 1) {
- PLOG(ERROR) << "failed to read " << filename_;
+ if (!Read(&desc, sizeof(desc))) {
return false;
}
feature_section_descriptors_.emplace(id, desc);
@@ -137,8 +152,7 @@
return false;
}
ids->resize(id_count);
- if (fread(ids->data(), attr.ids.size, 1, record_fp_) != 1) {
- PLOG(ERROR) << "failed to read file " << filename_;
+ if (!Read(ids->data(), attr.ids.size)) {
return false;
}
return true;
@@ -150,13 +164,19 @@
PLOG(ERROR) << "failed to fseek()";
return false;
}
- RecordCache cache(file_attrs_[0].attr);
+ bool has_timestamp = true;
+ for (const auto& attr : file_attrs_) {
+ if (!IsTimestampSupported(attr.attr)) {
+ has_timestamp = false;
+ break;
+ }
+ }
+ RecordCache cache(has_timestamp);
for (size_t nbytes_read = 0; nbytes_read < header_.data.size;) {
- std::unique_ptr<Record> record = ReadRecordFromFile(file_attrs_[0].attr, record_fp_);
+ std::unique_ptr<Record> record = ReadRecord(&nbytes_read);
if (record == nullptr) {
return false;
}
- nbytes_read += record->size();
if (sorted) {
cache.Push(std::move(record));
record = cache.Pop();
@@ -180,6 +200,77 @@
return true;
}
+std::unique_ptr<Record> RecordFileReader::ReadRecord(size_t* nbytes_read) {
+ std::vector<char> buf(Record::header_size());
+ if (!Read(buf.data(), buf.size())) {
+ return nullptr;
+ }
+ RecordHeader header(buf.data());
+ if (header.type == SIMPLE_PERF_RECORD_SPLIT) {
+ // Read until meeting a RECORD_SPLIT_END record.
+ buf.clear();
+ size_t cur_size = 0;
+ char header_buf[Record::header_size()];
+ while (header.type == SIMPLE_PERF_RECORD_SPLIT) {
+ size_t bytes_to_read = header.size - Record::header_size();
+ buf.resize(cur_size + bytes_to_read);
+ if (!Read(&buf[cur_size], bytes_to_read)) {
+ return nullptr;
+ }
+ cur_size += bytes_to_read;
+ *nbytes_read += header.size;
+ if (!Read(header_buf, Record::header_size())) {
+ return nullptr;
+ }
+ header = RecordHeader(header_buf);
+ }
+ if (header.type != SIMPLE_PERF_RECORD_SPLIT_END) {
+ LOG(ERROR) << "SPLIT records are not followed by a SPLIT_END record.";
+ return nullptr;
+ }
+ *nbytes_read += header.size;
+ header = RecordHeader(buf.data());
+ } else if (Record::header_size() < header.size) {
+ buf.resize(header.size);
+ if (!Read(&buf[Record::header_size()], header.size - Record::header_size())) {
+ return nullptr;
+ }
+ *nbytes_read += header.size;
+ }
+
+ const perf_event_attr* attr = &file_attrs_[0].attr;
+ if (file_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
+ bool has_event_id = false;
+ uint64_t event_id;
+ if (header.type == PERF_RECORD_SAMPLE) {
+ if (header.size > event_id_pos_in_sample_records_ + sizeof(uint64_t)) {
+ has_event_id = true;
+ event_id = *reinterpret_cast<uint64_t*>(&buf[event_id_pos_in_sample_records_]);
+ }
+ } else {
+ if (header.size > event_id_reverse_pos_in_non_sample_records_) {
+ has_event_id = true;
+ event_id = *reinterpret_cast<uint64_t*>(&buf[header.size - event_id_reverse_pos_in_non_sample_records_]);
+ }
+ }
+ if (has_event_id) {
+ auto it = event_id_to_attr_map_.find(event_id);
+ if (it != event_id_to_attr_map_.end()) {
+ attr = it->second;
+ }
+ }
+ }
+ return ReadRecordFromBuffer(*attr, header.type, buf.data());
+}
+
+bool RecordFileReader::Read(void* buf, size_t len) {
+ if (fread(buf, len, 1, record_fp_) != 1) {
+ PLOG(FATAL) << "failed to read file " << filename_;
+ 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);
@@ -188,12 +279,14 @@
}
SectionDesc section = it->second;
data->resize(section.size);
+ if (section.size == 0) {
+ return true;
+ }
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_;
+ if (!Read(data->data(), data->size())) {
return false;
}
return true;
@@ -229,13 +322,12 @@
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);
+ BuildIdRecord record(p);
// Set type explicitly as the perf.data produced by perf doesn't set it.
- record.header.type = PERF_RECORD_BUILD_ID;
+ record.SetTypeAndMisc(PERF_RECORD_BUILD_ID, record.misc());
result.push_back(record);
- p += header->size;
+ CHECK_LE(p + record.size(), end);
+ p += record.size();
}
return result;
}
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index 4648a64..885691d 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -38,6 +38,7 @@
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
ASSERT_TRUE(event_type_modifier != nullptr);
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+ attr.sample_id_all = 1;
attrs_.push_back(std::unique_ptr<perf_event_attr>(new perf_event_attr(attr)));
AttrWithId attr_id;
attr_id.attr = attrs_.back().get();
@@ -60,9 +61,9 @@
ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
// Write data section.
- MmapRecord mmap_record = CreateMmapRecord(*(attr_ids_[0].attr), true, 1, 1, 0x1000, 0x2000,
- 0x3000, "mmap_record_example");
- ASSERT_TRUE(writer->WriteData(mmap_record.BinaryFormat()));
+ MmapRecord mmap_record = MmapRecord::Create(*(attr_ids_[0].attr), true, 1, 1, 0x1000, 0x2000,
+ 0x3000, "mmap_record_example", attr_ids_[0].ids[0]);
+ ASSERT_TRUE(writer->WriteRecord(mmap_record));
// Write feature section.
ASSERT_TRUE(writer->WriteFeatureHeader(1));
@@ -71,19 +72,17 @@
p[i] = i;
}
BuildId build_id(p);
- BuildIdRecord build_id_record = CreateBuildIdRecord(false, getpid(), build_id, "init");
+ BuildIdRecord build_id_record = BuildIdRecord::Create(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(tmpfile_.path);
ASSERT_TRUE(reader != nullptr);
- 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);
+ std::vector<AttrWithId> attrs = reader->AttrSection();
+ ASSERT_EQ(1u, attrs.size());
+ ASSERT_EQ(0, memcmp(attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+ ASSERT_EQ(attrs[0].ids, attr_ids_[0].ids);
// Read and check data section.
std::vector<std::unique_ptr<Record>> records = reader->DataSection();
@@ -111,15 +110,16 @@
// Write data section.
MmapRecord r1 =
- CreateMmapRecord(*(attr_ids_[0].attr), true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1");
+ MmapRecord::Create(*(attr_ids_[0].attr), true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1",
+ attr_ids_[0].ids[0]);
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->WriteRecord(r1));
+ ASSERT_TRUE(writer->WriteRecord(r2));
+ ASSERT_TRUE(writer->WriteRecord(r3));
ASSERT_TRUE(writer->Close());
// Read from a record file.
@@ -150,12 +150,10 @@
// 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);
+ std::vector<AttrWithId> attrs = reader->AttrSection();
+ ASSERT_EQ(3u, attrs.size());
+ for (size_t i = 0; i < attrs.size(); ++i) {
+ ASSERT_EQ(0, memcmp(attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+ ASSERT_EQ(attrs[i].ids, attr_ids_[i].ids);
}
}
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
index dddd0b0..125bfad 100644
--- a/simpleperf/record_file_writer.cpp
+++ b/simpleperf/record_file_writer.cpp
@@ -77,7 +77,7 @@
}
// Write id section.
- long id_section_offset = ftell(record_fp_);
+ off_t id_section_offset = ftello(record_fp_);
if (id_section_offset == -1) {
return false;
}
@@ -88,7 +88,7 @@
}
// Write attr section.
- long attr_section_offset = ftell(record_fp_);
+ off_t attr_section_offset = ftello(record_fp_);
if (attr_section_offset == -1) {
return false;
}
@@ -103,7 +103,7 @@
}
}
- long data_section_offset = ftell(record_fp_);
+ off_t data_section_offset = ftello(record_fp_);
if (data_section_offset == -1) {
return false;
}
@@ -117,6 +117,45 @@
return true;
}
+bool RecordFileWriter::WriteRecord(const Record& record) {
+ std::vector<char> data = record.BinaryFormat();
+ // linux-tools-perf only accepts records with size <= 65535 bytes. To make
+ // perf.data generated by simpleperf be able to be parsed by linux-tools-perf,
+ // Split simpleperf custom records which are > 65535 into a bunch of
+ // RECORD_SPLIT records, followed by a RECORD_SPLIT_END record.
+ constexpr uint32_t RECORD_SIZE_LIMIT = 65535;
+ if (record.size() <= RECORD_SIZE_LIMIT) {
+ WriteData(data.data(), data.size());
+ return true;
+ }
+ CHECK_GT(record.type(), SIMPLE_PERF_RECORD_TYPE_START);
+ const char* p = data.data();
+ uint32_t left_bytes = static_cast<uint32_t>(data.size());
+ RecordHeader header;
+ header.type = SIMPLE_PERF_RECORD_SPLIT;
+ char header_buf[Record::header_size()];
+ char* header_p;
+ while (left_bytes > 0) {
+ uint32_t bytes_to_write = std::min(RECORD_SIZE_LIMIT - Record::header_size(), left_bytes);
+ header.size = bytes_to_write + Record::header_size();
+ header_p = header_buf;
+ header.MoveToBinaryFormat(header_p);
+ if (!WriteData(header_buf, Record::header_size())) {
+ return false;
+ }
+ if (!WriteData(p, bytes_to_write)) {
+ return false;
+ }
+ p += bytes_to_write;
+ left_bytes -= bytes_to_write;
+ }
+ header.type = SIMPLE_PERF_RECORD_SPLIT_END;
+ header.size = Record::header_size();
+ header_p = header_buf;
+ header.MoveToBinaryFormat(header_p);
+ return WriteData(header_buf, Record::header_size());
+}
+
bool RecordFileWriter::WriteData(const void* buf, size_t len) {
if (!Write(buf, len)) {
return false;
@@ -138,9 +177,9 @@
PLOG(ERROR) << "fseek() failed";
return false;
}
- long offset = ftell(record_fp_);
+ off_t offset = ftello(record_fp_);
if (offset == -1) {
- PLOG(ERROR) << "ftell() failed";
+ PLOG(ERROR) << "ftello() failed";
return false;
}
*file_end = static_cast<uint64_t>(offset);
@@ -180,7 +219,7 @@
if (!WriteFeatureBegin(&start_offset)) {
return false;
}
- uint32_t len = static_cast<uint32_t>(ALIGN(s.size() + 1, 64));
+ uint32_t len = static_cast<uint32_t>(Align(s.size() + 1, 64));
if (!Write(&len, sizeof(len))) {
return false;
}
@@ -202,7 +241,7 @@
return false;
}
for (auto& arg : cmdline) {
- uint32_t len = static_cast<uint32_t>(ALIGN(arg.size() + 1, 64));
+ uint32_t len = static_cast<uint32_t>(Align(arg.size() + 1, 64));
if (!Write(&len, sizeof(len))) {
return false;
}
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
index 76eebe9..ecfc4d0 100644
--- a/simpleperf/record_test.cpp
+++ b/simpleperf/record_test.cpp
@@ -46,20 +46,20 @@
TEST_F(RecordTest, MmapRecordMatchBinary) {
MmapRecord record =
- CreateMmapRecord(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000, "MmapRecord");
+ MmapRecord::Create(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000, "MmapRecord", 0);
CheckRecordMatchBinary(record);
}
TEST_F(RecordTest, CommRecordMatchBinary) {
- CommRecord record = CreateCommRecord(event_attr, 1, 2, "CommRecord");
+ CommRecord record = CommRecord::Create(event_attr, 1, 2, "CommRecord", 0);
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");
+ RecordCache cache(true, 2, 2);
+ MmapRecord r1 = MmapRecord::Create(event_attr, true, 1, 1, 0x100, 0x200, 0x300, "mmap_record1", 0);
MmapRecord r2 = r1;
MmapRecord r3 = r1;
MmapRecord r4 = r1;
@@ -67,25 +67,21 @@
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());
+ cache.Push(std::unique_ptr<Record>(new MmapRecord(r1)));
ASSERT_EQ(nullptr, cache.Pop());
// Push r2.
- cache.Push(buf2.data(), buf2.size());
+ cache.Push(std::unique_ptr<Record>(new MmapRecord(r2)));
// 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());
+ cache.Push(std::unique_ptr<Record>(new MmapRecord(r3)));
ASSERT_EQ(nullptr, cache.Pop());
// Push r4.
- cache.Push(buf4.data(), buf4.size());
+ cache.Push(std::unique_ptr<Record>(new MmapRecord(r4)));
// Pop r1.
popped_r = cache.Pop();
ASSERT_TRUE(popped_r != nullptr);
@@ -104,13 +100,12 @@
TEST_F(RecordTest, RecordCache_FIFO) {
event_attr.sample_id_all = 1;
event_attr.sample_type |= PERF_SAMPLE_TIME;
- RecordCache cache(event_attr, 2, 2);
+ RecordCache cache(true, 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");
+ MmapRecord r = MmapRecord::Create(event_attr, true, 1, i, 0x100, 0x200, 0x300, "mmap_record1", 0);
records.push_back(r);
- std::vector<char> buf = r.BinaryFormat();
- cache.Push(buf.data(), buf.size());
+ cache.Push(std::unique_ptr<Record>(new MmapRecord(r)));
}
std::vector<std::unique_ptr<Record>> out_records = cache.PopAll();
ASSERT_EQ(records.size(), out_records.size());
diff --git a/simpleperf/report_sample.proto b/simpleperf/report_sample.proto
new file mode 100644
index 0000000..47e3353
--- /dev/null
+++ b/simpleperf/report_sample.proto
@@ -0,0 +1,38 @@
+// The file format generated by report_sample.proto is as below:
+// LittleEndian32(record_size_0)
+// message Record(record_0) (having record_size_0 bytes)
+// LittleEndian32(record_size_1)
+// message Record(record_1) (having record_size_1 bytes)
+// ...
+// LittleEndian32(record_size_N)
+// message Record(record_N) (having record_size_N bytes)
+// LittleEndian32(0)
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+package simpleperf_report_proto;
+
+message Sample {
+ optional uint64 time = 1;
+
+ message CallChainEntry {
+ optional uint64 ip = 1;
+ optional string symbol = 2;
+ optional string file = 3;
+ }
+
+ repeated CallChainEntry callchain = 2;
+}
+
+message Record {
+ enum Type {
+ UNKOWN = 1;
+ SAMPLE = 2;
+ }
+
+ // Identifies which field is filled in.
+ optional Type type = 1;
+
+ // One of the following will be filled in.
+ optional Sample sample = 2;
+}
\ No newline at end of file
diff --git a/simpleperf/runtest/comm_change.cpp b/simpleperf/runtest/comm_change.cpp
index f8b2ae6..12d64fa 100644
--- a/simpleperf/runtest/comm_change.cpp
+++ b/simpleperf/runtest/comm_change.cpp
@@ -8,9 +8,9 @@
}
int main() {
- prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM1"), 0, 0, 0);
+ prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM1"), 0, 0, 0); // NOLINT
Function1();
- prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM2"), 0, 0, 0);
+ prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM2"), 0, 0, 0); // NOLINT
Function1();
return 0;
}
diff --git a/simpleperf/sample_tree.cpp b/simpleperf/sample_tree.cpp
deleted file mode 100644
index a34107b..0000000
--- a/simpleperf/sample_tree.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "sample_tree.h"
-
-#include <android-base/logging.h>
-
-#include "environment.h"
-
-void SampleTree::SetFilters(const std::unordered_set<int>& pid_filter,
- const std::unordered_set<int>& tid_filter,
- const std::unordered_set<std::string>& comm_filter,
- const std::unordered_set<std::string>& dso_filter) {
- pid_filter_ = pid_filter;
- tid_filter_ = tid_filter;
- comm_filter_ = comm_filter;
- dso_filter_ = dso_filter;
-}
-
-SampleEntry* SampleTree::AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
- bool in_kernel) {
- const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
- const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
- const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
-
- SampleEntry value(ip, time, period, 0, 1, thread, map, symbol);
-
- if (IsFilteredOut(value)) {
- return nullptr;
- }
- return InsertSample(value);
-}
-
-void SampleTree::AddBranchSample(int pid, int tid, uint64_t from_ip, uint64_t to_ip,
- uint64_t branch_flags, uint64_t time, uint64_t period) {
- const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
- const MapEntry* from_map = thread_tree_->FindMap(thread, from_ip, false);
- if (from_map == thread_tree_->UnknownMap()) {
- from_map = thread_tree_->FindMap(thread, from_ip, true);
- }
- const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, from_ip);
- const MapEntry* to_map = thread_tree_->FindMap(thread, to_ip, false);
- if (to_map == thread_tree_->UnknownMap()) {
- to_map = thread_tree_->FindMap(thread, to_ip, true);
- }
- const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, to_ip);
-
- SampleEntry value(to_ip, time, period, 0, 1, thread, to_map, to_symbol);
- value.branch_from.ip = from_ip;
- value.branch_from.map = from_map;
- value.branch_from.symbol = from_symbol;
- value.branch_from.flags = branch_flags;
-
- if (IsFilteredOut(value)) {
- return;
- }
- InsertSample(value);
-}
-
-SampleEntry* SampleTree::AddCallChainSample(int pid, int tid, uint64_t ip, uint64_t time,
- uint64_t period, bool in_kernel,
- const std::vector<SampleEntry*>& callchain) {
- const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
- const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
- const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
-
- SampleEntry value(ip, time, 0, period, 0, thread, map, symbol);
-
- if (IsFilteredOut(value)) {
- // Store in callchain_sample_tree_ for use in other SampleEntry's callchain.
- auto it = callchain_sample_tree_.find(&value);
- if (it != callchain_sample_tree_.end()) {
- return *it;
- }
- SampleEntry* sample = AllocateSample(value);
- callchain_sample_tree_.insert(sample);
- return sample;
- }
-
- auto it = sample_tree_.find(&value);
- if (it != sample_tree_.end()) {
- SampleEntry* sample = *it;
- // Process only once for recursive function call.
- if (std::find(callchain.begin(), callchain.end(), sample) != callchain.end()) {
- return sample;
- }
- }
- return InsertSample(value);
-}
-
-bool SampleTree::IsFilteredOut(const SampleEntry& value) {
- if (!pid_filter_.empty() && pid_filter_.find(value.thread->pid) == pid_filter_.end()) {
- return true;
- }
- if (!tid_filter_.empty() && tid_filter_.find(value.thread->tid) == tid_filter_.end()) {
- return true;
- }
- if (!comm_filter_.empty() && comm_filter_.find(value.thread_comm) == comm_filter_.end()) {
- return true;
- }
- if (!dso_filter_.empty() && dso_filter_.find(value.map->dso->Path()) == dso_filter_.end()) {
- return true;
- }
- return false;
-}
-
-SampleEntry* SampleTree::InsertSample(SampleEntry& value) {
- SampleEntry* result;
- auto it = sample_tree_.find(&value);
- if (it == sample_tree_.end()) {
- result = AllocateSample(value);
- auto pair = sample_tree_.insert(result);
- CHECK(pair.second);
- } else {
- result = *it;
- result->period += value.period;
- result->accumulated_period += value.accumulated_period;
- result->sample_count += value.sample_count;
- }
- total_samples_ += value.sample_count;
- total_period_ += value.period;
- return result;
-}
-
-SampleEntry* SampleTree::AllocateSample(SampleEntry& value) {
- SampleEntry* sample = new SampleEntry(std::move(value));
- sample_storage_.push_back(std::unique_ptr<SampleEntry>(sample));
- return sample;
-}
-
-void SampleTree::InsertCallChainForSample(SampleEntry* sample,
- const std::vector<SampleEntry*>& callchain,
- uint64_t period) {
- sample->callchain.AddCallChain(callchain, period);
-}
-
-void SampleTree::VisitAllSamples(std::function<void(const SampleEntry&)> callback) {
- if (sorted_sample_tree_.size() != sample_tree_.size()) {
- sorted_sample_tree_.clear();
- for (auto& sample : sample_tree_) {
- sample->callchain.SortByPeriod();
- sorted_sample_tree_.insert(sample);
- }
- }
- for (auto& sample : sorted_sample_tree_) {
- callback(*sample);
- }
-}
diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h
index 6eb7372..86becbe 100644
--- a/simpleperf/sample_tree.h
+++ b/simpleperf/sample_tree.h
@@ -17,146 +17,299 @@
#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 "dwarf_unwind.h"
+#include "perf_regs.h"
+#include "record.h"
+#include "SampleComparator.h"
+#include "SampleDisplayer.h"
#include "thread_tree.h"
-struct BranchFromEntry {
- uint64_t ip;
- const MapEntry* map;
- const Symbol* symbol;
- uint64_t flags;
+// A SampleTree is a collection of samples. A profiling report is mainly about
+// constructing a SampleTree and display it. There are three steps involved:
+// build the tree, sort the tree, and display it. For example, if we want to
+// show how many cpu-cycles are spent in different functions, we should do as
+// follows:
+// 1. Build a SampleTree from SampleRecords with each sample containing
+// (cpu-cycles, function name). When building the tree, we should merge
+// samples containing the same function name.
+// 2. Sort the SampleTree by cpu-cycles in the sample. As we want to display the
+// samples in a decreasing order of cpu-cycles, we should sort it like this.
+// 3. Display the SampleTree, each sample prints its (cpu-cycles, function name)
+// pair.
+//
+// We represent the three steps with three template classes.
+// 1. A SampleTree is built by SampleTreeBuilder. The comparator passed in
+// SampleTreeBuilder's constructor decides the property of samples should be
+// merged together.
+// 2. After a SampleTree is built and got from SampleTreeBuilder, it should be
+// sorted by SampleTreeSorter. The sort result decides the order to show
+// samples.
+// 3. At last, the sorted SampleTree is passed to SampleTreeDisplayer, which
+// displays each sample in the SampleTree.
- 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 {
+template <typename EntryT, typename AccumulateInfoT>
+class SampleTreeBuilder {
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) {
+ SampleTreeBuilder(SampleComparator<EntryT> comparator)
+ : sample_set_(comparator),
+ accumulate_callchain_(false),
+ callchain_sample_set_(comparator),
+ use_branch_address_(false),
+ build_callchain_(false),
+ use_caller_as_callchain_root_(false),
+ strict_unwind_arch_check_(false) {}
+
+ virtual ~SampleTreeBuilder() {}
+
+ void SetBranchSampleOption(bool use_branch_address) {
+ use_branch_address_ = use_branch_address;
}
- 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_;
+ void SetCallChainSampleOptions(bool accumulate_callchain,
+ bool build_callchain,
+ bool use_caller_as_callchain_root,
+ bool strict_unwind_arch_check) {
+ accumulate_callchain_ = accumulate_callchain;
+ build_callchain_ = build_callchain;
+ use_caller_as_callchain_root_ = use_caller_as_callchain_root;
+ strict_unwind_arch_check_ = strict_unwind_arch_check;
}
- uint64_t TotalPeriod() const {
- return total_period_;
+ void 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) {
+ CreateBranchSample(r, item);
+ }
+ }
+ return;
+ }
+ bool in_kernel = r.InKernel();
+ AccumulateInfoT acc_info;
+ EntryT* sample = CreateSample(r, in_kernel, &acc_info);
+ 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());
+ }
+ const ThreadEntry* thread = GetThreadOfSample(sample);
+ // 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 (thread != nullptr && (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());
+ ArchType arch = GetArchForAbi(ScopedCurrentArch::GetCurrentArch(),
+ r.regs_user_data.abi);
+ std::vector<uint64_t> unwind_ips = UnwindCallChain(
+ arch, *thread, regs, stack, strict_unwind_arch_check_);
+ if (!unwind_ips.empty()) {
+ ips.push_back(PERF_CONTEXT_USER);
+ ips.insert(ips.end(), unwind_ips.begin(), unwind_ips.end());
+ }
+ }
+
+ std::vector<EntryT*> 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(DEBUG) << "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;
+ }
+ }
+ EntryT* callchain_sample =
+ CreateCallChainSample(sample, ip, in_kernel, callchain, acc_info);
+ if (callchain_sample == nullptr) {
+ break;
+ }
+ callchain.push_back(callchain_sample);
+ }
+ }
+
+ if (build_callchain_) {
+ std::set<EntryT*> added_set;
+ if (use_caller_as_callchain_root_) {
+ std::reverse(callchain.begin(), callchain.end());
+ }
+ while (callchain.size() >= 2) {
+ EntryT* 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);
+ InsertCallChainForSample(sample, callchain, acc_info);
+ }
+ }
+ }
+ }
+
+ std::vector<EntryT*> GetSamples() const {
+ std::vector<EntryT*> result;
+ for (auto& entry : sample_set_) {
+ result.push_back(entry);
+ }
+ return result;
+ }
+
+ protected:
+ virtual EntryT* CreateSample(const SampleRecord& r, bool in_kernel,
+ AccumulateInfoT* acc_info) = 0;
+ virtual EntryT* CreateBranchSample(const SampleRecord& r,
+ const BranchStackItemType& item) = 0;
+ virtual EntryT* CreateCallChainSample(const EntryT* sample, uint64_t ip,
+ bool in_kernel,
+ const std::vector<EntryT*>& callchain,
+ const AccumulateInfoT& acc_info) = 0;
+ virtual const ThreadEntry* GetThreadOfSample(EntryT*) = 0;
+ virtual void InsertCallChainForSample(EntryT* sample,
+ const std::vector<EntryT*>& callchain,
+ const AccumulateInfoT& acc_info) = 0;
+ virtual bool FilterSample(const EntryT*) { return true; }
+
+ virtual void UpdateSummary(const EntryT*) {}
+
+ virtual void MergeSample(EntryT* sample1, EntryT* sample2) = 0;
+
+ EntryT* InsertSample(std::unique_ptr<EntryT> sample) {
+ if (sample == nullptr || !FilterSample(sample.get())) {
+ return nullptr;
+ }
+ UpdateSummary(sample.get());
+ EntryT* result;
+ auto it = sample_set_.find(sample.get());
+ if (it == sample_set_.end()) {
+ result = sample.get();
+ sample_set_.insert(sample.get());
+ sample_storage_.push_back(std::move(sample));
+ } else {
+ result = *it;
+ MergeSample(*it, sample.get());
+ }
+ return result;
+ }
+
+ EntryT* InsertCallChainSample(std::unique_ptr<EntryT> sample,
+ const std::vector<EntryT*>& callchain) {
+ if (sample == nullptr) {
+ return nullptr;
+ }
+ if (!FilterSample(sample.get())) {
+ // Store in callchain_sample_set_ for use in other EntryT's callchain.
+ auto it = callchain_sample_set_.find(sample.get());
+ if (it != callchain_sample_set_.end()) {
+ return *it;
+ }
+ EntryT* result = sample.get();
+ callchain_sample_set_.insert(sample.get());
+ sample_storage_.push_back(std::move(sample));
+ return result;
+ }
+
+ auto it = sample_set_.find(sample.get());
+ if (it != sample_set_.end()) {
+ EntryT* sample = *it;
+ // Process only once for recursive function call.
+ if (std::find(callchain.begin(), callchain.end(), sample) !=
+ callchain.end()) {
+ return sample;
+ }
+ }
+ return InsertSample(std::move(sample));
+ }
+
+ std::set<EntryT*, SampleComparator<EntryT>> sample_set_;
+ bool accumulate_callchain_;
+
+ private:
+ // If a CallChainSample is filtered out, it is stored in callchain_sample_set_
+ // and only used in other EntryT's callchain.
+ std::set<EntryT*, SampleComparator<EntryT>> callchain_sample_set_;
+ std::vector<std::unique_ptr<EntryT>> sample_storage_;
+
+ bool use_branch_address_;
+ bool build_callchain_;
+ bool use_caller_as_callchain_root_;
+ bool strict_unwind_arch_check_;
+};
+
+template <typename EntryT>
+class SampleTreeSorter {
+ public:
+ SampleTreeSorter(SampleComparator<EntryT> comparator)
+ : comparator_(comparator) {}
+
+ virtual ~SampleTreeSorter() {}
+
+ void Sort(std::vector<EntryT*>& v, bool sort_callchain) {
+ if (sort_callchain) {
+ for (auto& sample : v) {
+ SortCallChain(sample);
+ }
+ }
+ if (!comparator_.empty()) {
+ std::sort(v.begin(), v.end(), [this](const EntryT* s1, const EntryT* s2) {
+ return comparator_(s1, s2);
+ });
+ }
+ }
+
+ protected:
+ void SortCallChain(EntryT* sample) { sample->callchain.SortByPeriod(); }
+
+ private:
+ SampleComparator<EntryT> comparator_;
+};
+
+template <typename EntryT, typename InfoT>
+class SampleTreeDisplayer {
+ public:
+ SampleTreeDisplayer(SampleDisplayer<EntryT, InfoT> displayer)
+ : displayer_(displayer) {}
+
+ virtual ~SampleTreeDisplayer() {}
+
+ void DisplaySamples(FILE* fp, const std::vector<EntryT*>& samples,
+ const InfoT* info) {
+ displayer_.SetInfo(info);
+ for (const auto& sample : samples) {
+ displayer_.AdjustWidth(sample);
+ }
+ displayer_.PrintNames(fp);
+ for (const auto& sample : samples) {
+ displayer_.PrintSample(fp, sample);
+ }
}
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_;
+ SampleDisplayer<EntryT, InfoT> displayer_;
};
#endif // SIMPLE_PERF_SAMPLE_TREE_H_
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
index 434ee71..a88ecf9 100644
--- a/simpleperf/sample_tree_test.cpp
+++ b/simpleperf/sample_tree_test.cpp
@@ -17,64 +17,105 @@
#include <gtest/gtest.h>
#include "sample_tree.h"
+#include "thread_tree.h"
-struct ExpectedSampleInMap {
+namespace {
+
+struct SampleEntry {
int pid;
int tid;
- const char* comm;
+ const char* thread_comm;
std::string dso_name;
uint64_t map_start_addr;
size_t sample_count;
+
+ SampleEntry(int pid, int tid, const char* thread_comm,
+ const std::string& dso_name, uint64_t map_start_addr,
+ size_t sample_count = 1u)
+ : pid(pid),
+ tid(tid),
+ thread_comm(thread_comm),
+ dso_name(dso_name),
+ map_start_addr(map_start_addr),
+ sample_count(sample_count) {}
};
-static void SampleMatchExpectation(const SampleEntry& sample, const ExpectedSampleInMap& expected,
+BUILD_COMPARE_VALUE_FUNCTION(TestComparePid, pid);
+BUILD_COMPARE_VALUE_FUNCTION(TestCompareTid, tid);
+BUILD_COMPARE_STRING_FUNCTION(TestCompareDsoName, dso_name.c_str());
+BUILD_COMPARE_VALUE_FUNCTION(TestCompareMapStartAddr, map_start_addr);
+
+class TestSampleComparator : public SampleComparator<SampleEntry> {
+ public:
+ TestSampleComparator() {
+ AddCompareFunction(TestComparePid);
+ AddCompareFunction(TestCompareTid);
+ AddCompareFunction(CompareComm);
+ AddCompareFunction(TestCompareDsoName);
+ AddCompareFunction(TestCompareMapStartAddr);
+ }
+};
+
+class TestSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, int> {
+ public:
+ TestSampleTreeBuilder(ThreadTree* thread_tree)
+ : SampleTreeBuilder(TestSampleComparator()), thread_tree_(thread_tree) {}
+
+ void AddSample(int pid, int tid, uint64_t ip, bool in_kernel) {
+ const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
+ const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
+ InsertSample(std::unique_ptr<SampleEntry>(new SampleEntry(
+ pid, tid, thread->comm, map->dso->Path(), map->start_addr)));
+ }
+
+ protected:
+ SampleEntry* CreateSample(const SampleRecord&, bool, int*) override {
+ return nullptr;
+ }
+ SampleEntry* CreateBranchSample(const SampleRecord&,
+ const BranchStackItemType&) override {
+ return nullptr;
+ };
+ SampleEntry* CreateCallChainSample(const SampleEntry*, uint64_t, bool,
+ const std::vector<SampleEntry*>&,
+ const int&) override {
+ return nullptr;
+ }
+ const ThreadEntry* GetThreadOfSample(SampleEntry*) override {
+ return nullptr;
+ }
+ void InsertCallChainForSample(SampleEntry*, const std::vector<SampleEntry*>&,
+ const int&) override {}
+ void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override {
+ sample1->sample_count += sample2->sample_count;
+ }
+
+ private:
+ ThreadTree* thread_tree_;
+};
+
+static void SampleMatchExpectation(const SampleEntry& sample,
+ const SampleEntry& 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.pid, sample.pid);
+ ASSERT_EQ(expected.tid, sample.tid);
+ ASSERT_STREQ(expected.thread_comm, sample.thread_comm);
+ ASSERT_EQ(expected.dso_name, sample.dso_name);
+ 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 void CheckSamples(const std::vector<SampleEntry*>& samples,
+ const std::vector<SampleEntry>& expected_samples) {
+ ASSERT_EQ(samples.size(), expected_samples.size());
+ for (size_t i = 0; i < samples.size(); ++i) {
+ bool has_error;
+ SampleMatchExpectation(*samples[i], expected_samples[i], &has_error);
+ ASSERT_FALSE(has_error) << "Error matching sample at pos " << i;
+ }
}
-
-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 {
@@ -88,102 +129,107 @@
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));
+ sample_tree_builder.reset(new TestSampleTreeBuilder(&thread_tree));
}
- void VisitSampleTree(const std::vector<ExpectedSampleInMap>& expected_samples) {
- ::VisitSampleTree(sample_tree.get(), expected_samples);
+ void CheckSamples(const std::vector<SampleEntry>& expected_samples) {
+ ::CheckSamples(sample_tree_builder->GetSamples(), expected_samples);
}
ThreadTree thread_tree;
- std::unique_ptr<SampleTree> sample_tree;
+ std::unique_ptr<TestSampleTreeBuilder> sample_tree_builder;
};
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},
+ sample_tree_builder->AddSample(1, 1, 1, false);
+ sample_tree_builder->AddSample(1, 1, 2, false);
+ sample_tree_builder->AddSample(1, 1, 5, false);
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 3),
};
- VisitSampleTree(expected_samples);
+ CheckSamples(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},
+ sample_tree_builder->AddSample(1, 1, 1, false);
+ sample_tree_builder->AddSample(2, 2, 1, false);
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+ SampleEntry(2, 2, "p2t2", "process2_thread2", 1, 1),
};
- VisitSampleTree(expected_samples);
+ CheckSamples(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},
+ sample_tree_builder->AddSample(1, 1, 1, false);
+ sample_tree_builder->AddSample(1, 11, 1, false);
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+ SampleEntry(1, 11, "p1t11", "process1_thread11", 1, 1),
};
- VisitSampleTree(expected_samples);
+ CheckSamples(expected_samples);
}
TEST_F(SampleTreeTest, different_comm) {
- sample_tree->AddSample(1, 1, 1, 0, 0, false);
+ sample_tree_builder->AddSample(1, 1, 1, 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},
+ sample_tree_builder->AddSample(1, 1, 1, false);
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+ SampleEntry(1, 1, "p1t1_comm2", "process1_thread1", 1, 1),
};
- VisitSampleTree(expected_samples);
+ CheckSamples(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},
+ sample_tree_builder->AddSample(1, 1, 1, false);
+ sample_tree_builder->AddSample(1, 1, 6, false);
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+ SampleEntry(1, 1, "p1t1", "process1_thread1_map2", 6, 1),
};
- VisitSampleTree(expected_samples);
+ CheckSamples(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);
+ sample_tree_builder->AddSample(1, 1, 0, false);
+ sample_tree_builder->AddSample(1, 1, 31, false);
+ sample_tree_builder->AddSample(1, 1, 70, false);
// Match the unknown map.
- std::vector<ExpectedSampleInMap> expected_samples = {
- {1, 1, "p1t1", "unknown", 0, 3},
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "p1t1", "unknown", 0, 3),
};
- VisitSampleTree(expected_samples);
+ CheckSamples(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},
+ sample_tree_builder->AddSample(1, 1, 10, true);
+ sample_tree_builder->AddSample(1, 1, 10, false);
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "p1t1", "kernel", 10, 1),
+ SampleEntry(1, 1, "p1t1", "process1_thread1_map2", 6, 1),
};
- VisitSampleTree(expected_samples);
+ CheckSamples(expected_samples);
}
TEST(sample_tree, overlapped_map) {
ThreadTree thread_tree;
- SampleTree sample_tree(&thread_tree, CompareSampleFunction);
+ TestSampleTreeBuilder sample_tree_builder(&thread_tree);
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.
+ sample_tree_builder.AddSample(1, 1, 5, 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.
+ sample_tree_builder.AddSample(1, 1, 6, false); // Hit map 2.
+ sample_tree_builder.AddSample(1, 1, 4, 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.
+ sample_tree_builder.AddSample(1, 1, 7, false); // Hit map 3.
+ sample_tree_builder.AddSample(1, 1, 10, 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},
+ std::vector<SampleEntry> expected_samples = {
+ SampleEntry(1, 1, "thread1", "map1", 1, 2),
+ SampleEntry(1, 1, "thread1", "map2", 5, 1),
+ SampleEntry(1, 1, "thread1", "map2", 9, 1),
+ SampleEntry(1, 1, "thread1", "map3", 2, 1),
};
- VisitSampleTree(&sample_tree, expected_samples);
+ CheckSamples(sample_tree_builder.GetSamples(), expected_samples);
}
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index cfbe493..15d11cb 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -15,7 +15,9 @@
*/
#include <map>
+#include <memory>
#include <string>
+#include <vector>
#include "read_elf.h"
#include "workload.h"
@@ -26,3 +28,14 @@
void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols);
void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols);
+
+bool IsRoot();
+
+#define TEST_IN_ROOT(TestStatement) \
+ do { \
+ if (IsRoot()) { \
+ TestStatement; \
+ } else { \
+ GTEST_LOG_(INFO) << "Didn't test \"" << #TestStatement << "\" requires root privileges"; \
+ } \
+ } while (0)
diff --git a/simpleperf/testdata/elf_with_mini_debug_info b/simpleperf/testdata/elf_with_mini_debug_info
new file mode 100644
index 0000000..b3aa967
--- /dev/null
+++ b/simpleperf/testdata/elf_with_mini_debug_info
Binary files differ
diff --git a/simpleperf/testdata/perf_with_kernel_symbol.data b/simpleperf/testdata/perf_with_kernel_symbol.data
new file mode 100644
index 0000000..8b1fda1
--- /dev/null
+++ b/simpleperf/testdata/perf_with_kernel_symbol.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_kmem_slab_callgraph.data b/simpleperf/testdata/perf_with_kmem_slab_callgraph.data
new file mode 100644
index 0000000..cdb691f
--- /dev/null
+++ b/simpleperf/testdata/perf_with_kmem_slab_callgraph.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_mini_debug_info.data b/simpleperf/testdata/perf_with_mini_debug_info.data
new file mode 100644
index 0000000..0b02b3b
--- /dev/null
+++ b/simpleperf/testdata/perf_with_mini_debug_info.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_symbols.data b/simpleperf/testdata/perf_with_symbols.data
new file mode 100644
index 0000000..8e51145
--- /dev/null
+++ b/simpleperf/testdata/perf_with_symbols.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_two_event_types.data b/simpleperf/testdata/perf_with_two_event_types.data
new file mode 100644
index 0000000..ba9a606
--- /dev/null
+++ b/simpleperf/testdata/perf_with_two_event_types.data
Binary files differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index daf3ff5..8e4e9e9 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -87,7 +87,7 @@
return;
}
Dso* dso = FindKernelDsoOrNew(filename);
- MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso));
+ MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, true));
FixOverlappedMap(&kernel_map_tree_, map);
auto pair = kernel_map_tree_.insert(map);
CHECK(pair.second);
@@ -95,9 +95,6 @@
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);
@@ -112,7 +109,7 @@
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));
+ MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, false));
FixOverlappedMap(&thread->maps, map);
auto pair = thread->maps.insert(map);
CHECK(pair.second);
@@ -145,13 +142,13 @@
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));
+ old->pgoff, old->time, old->dso, old->in_kernel));
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->get_end_addr() - old->start_addr + old->pgoff, old->time, old->dso, old->in_kernel));
map_set->insert(after);
}
@@ -167,7 +164,7 @@
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);
+ std::numeric_limits<uint64_t>::max(), nullptr, false);
auto it = maps.upper_bound(&find_map);
if (it != maps.begin() && IsAddrInMap(addr, *--it)) {
return *it;
@@ -185,6 +182,15 @@
return result != nullptr ? result : &unknown_map_;
}
+const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip) {
+ MapEntry* result = FindMapByAddr(thread->maps, ip);
+ if (result != nullptr) {
+ return result;
+ }
+ 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()) {
@@ -193,50 +199,77 @@
vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
}
const Symbol* symbol = map->dso->FindSymbol(vaddr_in_file);
+ if (symbol == nullptr && map->in_kernel && map->dso != kernel_dso_.get()) {
+ // It is in a kernel module, but we can't find the kernel module file, or
+ // the kernel module file contains no symbol. Try finding the symbol in
+ // /proc/kallsyms.
+ vaddr_in_file = ip;
+ symbol = kernel_dso_->FindSymbol(vaddr_in_file);
+ }
if (symbol == nullptr) {
symbol = &unknown_symbol_;
}
return symbol;
}
-void ThreadTree::Clear() {
+const Symbol* ThreadTree::FindKernelSymbol(uint64_t ip) {
+ const MapEntry* map = FindMap(nullptr, ip, true);
+ return FindSymbol(map, ip);
+}
+
+void ThreadTree::ClearThreadAndMap() {
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) {
+void ThreadTree::Update(const Record& record) {
+ if (record.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,
+ if (r.InKernel()) {
+ 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,
+ 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) {
+ } else if (record.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,
+ if (r.InKernel()) {
+ 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,
+ 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) {
+ } else if (record.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) {
+ AddThread(r.data.pid, r.data.tid, r.comm);
+ } else if (record.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);
+ ForkThread(r.data.pid, r.data.tid, r.data.ppid, r.data.ptid);
+ } else if (record.type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
+ const auto& r = *static_cast<const KernelSymbolRecord*>(&record);
+ Dso::SetKallsyms(std::move(r.kallsyms));
+ } else if (record.type() == SIMPLE_PERF_RECORD_DSO) {
+ auto& r = *static_cast<const DsoRecord*>(&record);
+ Dso* dso = nullptr;
+ if (r.dso_type == DSO_KERNEL || r.dso_type == DSO_KERNEL_MODULE) {
+ dso = FindKernelDsoOrNew(r.dso_name);
+ } else {
+ dso = FindUserDsoOrNew(r.dso_name);
+ }
+ dso_id_to_dso_map_[r.dso_id] = dso;
+ } else if (record.type() == SIMPLE_PERF_RECORD_SYMBOL) {
+ auto& r = *static_cast<const SymbolRecord*>(&record);
+ Dso* dso = dso_id_to_dso_map_[r.dso_id];
+ CHECK(dso != nullptr);
+ dso->InsertSymbol(Symbol(r.name, r.addr, r.len));
}
}
+
+} // namespace simpleperf
+
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index de10138..b58c506 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -24,6 +24,9 @@
#include <set>
#include "dso.h"
+#include "environment.h"
+
+struct Record;
namespace simpleperf {
@@ -33,9 +36,10 @@
uint64_t pgoff;
uint64_t time; // Map creation time.
Dso* dso;
+ bool in_kernel;
- 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 start_addr, uint64_t len, uint64_t pgoff, uint64_t time, Dso* dso, bool in_kernel)
+ : start_addr(start_addr), len(len), pgoff(pgoff), time(time), dso(dso), in_kernel(in_kernel) {
}
MapEntry() {
}
@@ -56,12 +60,16 @@
std::set<MapEntry*, MapComparator> maps;
};
+// ThreadTree contains thread information (in ThreadEntry) and mmap information
+// (in MapEntry) of the monitored threads. It also has interface to access
+// symbols in executable binaries mapped in the monitored threads.
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());
+ MapEntry(0, std::numeric_limits<unsigned long long>::max(), 0, 0, unknown_dso_.get(), false);
+ kernel_dso_ = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
}
void AddThread(int pid, int tid, const std::string& comm);
@@ -72,12 +80,20 @@
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);
+ // Find map for an ip address when we don't know whether it is in kernel.
+ const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip);
const Symbol* FindSymbol(const MapEntry* map, uint64_t ip);
+ const Symbol* FindKernelSymbol(uint64_t ip);
const MapEntry* UnknownMap() const {
return &unknown_map_;
}
- void Clear();
+ // Clear thread and map information, but keep loaded dso information. It saves
+ // the time to reload dso information.
+ void ClearThreadAndMap();
+
+ // Update thread tree with information provided by record.
+ void Update(const Record& record);
private:
Dso* FindKernelDsoOrNew(const std::string& filename);
@@ -97,6 +113,7 @@
std::unordered_map<std::string, std::unique_ptr<Dso>> user_dso_tree_;
std::unique_ptr<Dso> unknown_dso_;
Symbol unknown_symbol_;
+ std::unordered_map<uint64_t, Dso*> dso_id_to_dso_map_;
};
} // namespace simpleperf
@@ -105,8 +122,5 @@
using ThreadEntry = simpleperf::ThreadEntry;
using ThreadTree = simpleperf::ThreadTree;
-struct Record;
-
-void BuildThreadTree(const Record& record, ThreadTree* thread_tree);
#endif // SIMPLE_PERF_THREAD_TREE_H_
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
new file mode 100644
index 0000000..884a883
--- /dev/null
+++ b/simpleperf/tracing.cpp
@@ -0,0 +1,426 @@
+/*
+ * 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 "tracing.h"
+
+#include <string.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "perf_event.h"
+#include "utils.h"
+
+const char TRACING_INFO_MAGIC[10] = {23, 8, 68, 't', 'r',
+ 'a', 'c', 'i', 'n', 'g'};
+
+template <class T>
+void AppendData(std::vector<char>& data, const T& s) {
+ const char* p = reinterpret_cast<const char*>(&s);
+ data.insert(data.end(), p, p + sizeof(T));
+}
+
+static void AppendData(std::vector<char>& data, const char* s) {
+ data.insert(data.end(), s, s + strlen(s) + 1);
+}
+
+template <>
+void AppendData(std::vector<char>& data, const std::string& s) {
+ data.insert(data.end(), s.c_str(), s.c_str() + s.size() + 1);
+}
+
+template <>
+void MoveFromBinaryFormat(std::string& data, const char*& p) {
+ data.clear();
+ while (*p != '\0') {
+ data.push_back(*p++);
+ }
+ p++;
+}
+
+static void AppendFile(std::vector<char>& data, const std::string& file,
+ uint32_t file_size_bytes = 8) {
+ if (file_size_bytes == 8) {
+ uint64_t file_size = file.size();
+ AppendData(data, file_size);
+ } else if (file_size_bytes == 4) {
+ uint32_t file_size = file.size();
+ AppendData(data, file_size);
+ }
+ data.insert(data.end(), file.begin(), file.end());
+}
+
+static void DetachFile(const char*& p, std::string& file,
+ uint32_t file_size_bytes = 8) {
+ uint64_t file_size = ConvertBytesToValue(p, file_size_bytes);
+ p += file_size_bytes;
+ file.clear();
+ file.insert(file.end(), p, p + file_size);
+ p += file_size;
+}
+
+struct TraceType {
+ std::string system;
+ std::string name;
+};
+
+class TracingFile {
+ public:
+ TracingFile();
+ bool RecordHeaderFiles();
+ void RecordFtraceFiles(const std::vector<TraceType>& trace_types);
+ bool RecordEventFiles(const std::vector<TraceType>& trace_types);
+ bool RecordKallsymsFile();
+ bool RecordPrintkFormatsFile();
+ std::vector<char> BinaryFormat() const;
+ void LoadFromBinary(const std::vector<char>& data);
+ void Dump(size_t indent) const;
+ std::vector<TracingFormat> LoadTracingFormatsFromEventFiles() const;
+ const std::string& GetKallsymsFile() const { return kallsyms_file; }
+ uint32_t GetPageSize() const { return page_size; }
+
+ private:
+ char magic[10];
+ std::string version;
+ char endian;
+ uint8_t size_of_long;
+ uint32_t page_size;
+ std::string header_page_file;
+ std::string header_event_file;
+
+ std::vector<std::string> ftrace_format_files;
+ // pair of system, format_file_data.
+ std::vector<std::pair<std::string, std::string>> event_format_files;
+
+ std::string kallsyms_file;
+ std::string printk_formats_file;
+};
+
+TracingFile::TracingFile() {
+ memcpy(magic, TRACING_INFO_MAGIC, sizeof(TRACING_INFO_MAGIC));
+ version = "0.5";
+ endian = 0;
+ size_of_long = static_cast<int>(sizeof(long));
+ page_size = static_cast<uint32_t>(::GetPageSize());
+}
+
+bool TracingFile::RecordHeaderFiles() {
+ if (!android::base::ReadFileToString(
+ "/sys/kernel/debug/tracing/events/header_page", &header_page_file)) {
+ PLOG(ERROR)
+ << "failed to read /sys/kernel/debug/tracing/events/header_page";
+ return false;
+ }
+ if (!android::base::ReadFileToString(
+ "/sys/kernel/debug/tracing/events/header_event",
+ &header_event_file)) {
+ PLOG(ERROR)
+ << "failed to read /sys/kernel/debug/tracing/events/header_event";
+ return false;
+ }
+ return true;
+}
+
+void TracingFile::RecordFtraceFiles(const std::vector<TraceType>& trace_types) {
+ for (const auto& type : trace_types) {
+ std::string format_path = android::base::StringPrintf(
+ "/sys/kernel/debug/tracing/events/ftrace/%s/format", type.name.c_str());
+ std::string format_data;
+ if (android::base::ReadFileToString(format_path, &format_data)) {
+ ftrace_format_files.push_back(std::move(format_data));
+ }
+ }
+}
+
+bool TracingFile::RecordEventFiles(const std::vector<TraceType>& trace_types) {
+ for (const auto& type : trace_types) {
+ std::string format_path = android::base::StringPrintf(
+ "/sys/kernel/debug/tracing/events/%s/%s/format", type.system.c_str(),
+ type.name.c_str());
+ std::string format_data;
+ if (!android::base::ReadFileToString(format_path, &format_data)) {
+ PLOG(ERROR) << "failed to read " << format_path;
+ return false;
+ }
+ event_format_files.push_back(
+ std::make_pair(type.system, std::move(format_data)));
+ }
+ return true;
+}
+
+bool TracingFile::RecordPrintkFormatsFile() {
+ if (!android::base::ReadFileToString(
+ "/sys/kernel/debug/tracing/printk_formats", &printk_formats_file)) {
+ PLOG(ERROR) << "failed to read /sys/kernel/debug/tracing/printk_formats";
+ return false;
+ }
+ return true;
+}
+
+std::vector<char> TracingFile::BinaryFormat() const {
+ std::vector<char> ret;
+ ret.insert(ret.end(), magic, magic + sizeof(magic));
+ AppendData(ret, version);
+ ret.push_back(endian);
+ AppendData(ret, size_of_long);
+ AppendData(ret, page_size);
+ AppendData(ret, "header_page");
+ AppendFile(ret, header_page_file);
+ AppendData(ret, "header_event");
+ AppendFile(ret, header_event_file);
+ int count = static_cast<int>(ftrace_format_files.size());
+ AppendData(ret, count);
+ for (const auto& format : ftrace_format_files) {
+ AppendFile(ret, format);
+ }
+ count = static_cast<int>(event_format_files.size());
+ AppendData(ret, count);
+ for (const auto& pair : event_format_files) {
+ AppendData(ret, pair.first);
+ AppendData(ret, 1);
+ AppendFile(ret, pair.second);
+ }
+ AppendFile(ret, kallsyms_file, 4);
+ AppendFile(ret, printk_formats_file, 4);
+ return ret;
+}
+
+void TracingFile::LoadFromBinary(const std::vector<char>& data) {
+ const char* p = data.data();
+ const char* end = data.data() + data.size();
+ CHECK(memcmp(p, magic, sizeof(magic)) == 0);
+ p += sizeof(magic);
+ MoveFromBinaryFormat(version, p);
+ MoveFromBinaryFormat(endian, p);
+ MoveFromBinaryFormat(size_of_long, p);
+ MoveFromBinaryFormat(page_size, p);
+ std::string filename;
+ MoveFromBinaryFormat(filename, p);
+ CHECK_EQ(filename, "header_page");
+ DetachFile(p, header_page_file);
+ MoveFromBinaryFormat(filename, p);
+ CHECK_EQ(filename, "header_event");
+ DetachFile(p, header_event_file);
+ uint32_t count;
+ MoveFromBinaryFormat(count, p);
+ ftrace_format_files.resize(count);
+ for (uint32_t i = 0; i < count; ++i) {
+ DetachFile(p, ftrace_format_files[i]);
+ }
+ MoveFromBinaryFormat(count, p);
+ event_format_files.clear();
+ for (uint32_t i = 0; i < count; ++i) {
+ std::string system;
+ MoveFromBinaryFormat(system, p);
+ uint32_t count_in_system;
+ MoveFromBinaryFormat(count_in_system, p);
+ for (uint32_t i = 0; i < count_in_system; ++i) {
+ std::string format;
+ DetachFile(p, format);
+ event_format_files.push_back(std::make_pair(system, std::move(format)));
+ }
+ }
+ DetachFile(p, kallsyms_file, 4);
+ DetachFile(p, printk_formats_file, 4);
+ CHECK_EQ(p, end);
+}
+
+void TracingFile::Dump(size_t indent) const {
+ PrintIndented(indent, "tracing data:\n");
+ PrintIndented(indent + 1, "magic: ");
+ for (size_t i = 0; i < 3u; ++i) {
+ printf("0x%x ", magic[i]);
+ }
+ for (size_t i = 3; i < sizeof(magic); ++i) {
+ printf("%c", magic[i]);
+ }
+ printf("\n");
+ PrintIndented(indent + 1, "version: %s\n", version.c_str());
+ PrintIndented(indent + 1, "endian: %d\n", endian);
+ PrintIndented(indent + 1, "header_page:\n%s\n\n", header_page_file.c_str());
+ PrintIndented(indent + 1, "header_event:\n%s\n\n", header_event_file.c_str());
+ for (size_t i = 0; i < ftrace_format_files.size(); ++i) {
+ PrintIndented(indent + 1, "ftrace format file %zu/%zu:\n%s\n\n", i + 1,
+ ftrace_format_files.size(), ftrace_format_files[i].c_str());
+ }
+ for (size_t i = 0; i < event_format_files.size(); ++i) {
+ PrintIndented(indent + 1, "event format file %zu/%zu %s:\n%s\n\n", i + 1,
+ event_format_files.size(),
+ event_format_files[i].first.c_str(),
+ event_format_files[i].second.c_str());
+ }
+ PrintIndented(indent + 1, "kallsyms:\n%s\n\n", kallsyms_file.c_str());
+ PrintIndented(indent + 1, "printk_formats:\n%s\n\n",
+ printk_formats_file.c_str());
+}
+
+enum class FormatParsingState {
+ READ_NAME,
+ READ_ID,
+ READ_FIELDS,
+ READ_PRINTFMT,
+};
+
+// Parse lines like: field:char comm[16]; offset:8; size:16; signed:1;
+static TracingField ParseTracingField(const std::string& s) {
+ TracingField field;
+ size_t start = 0;
+ std::string name;
+ std::string value;
+ for (size_t i = 0; i < s.size(); ++i) {
+ if (!isspace(s[i]) && (i == 0 || isspace(s[i - 1]))) {
+ start = i;
+ } else if (s[i] == ':') {
+ name = s.substr(start, i - start);
+ start = i + 1;
+ } else if (s[i] == ';') {
+ value = s.substr(start, i - start);
+ if (name == "field") {
+ size_t pos = value.find_first_of('[');
+ if (pos == std::string::npos) {
+ field.name = value;
+ field.elem_count = 1;
+ } else {
+ field.name = value.substr(0, pos);
+ field.elem_count =
+ static_cast<size_t>(strtoull(&value[pos + 1], nullptr, 10));
+ }
+ } else if (name == "offset") {
+ field.offset =
+ static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+ } else if (name == "size") {
+ size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+ CHECK_EQ(size % field.elem_count, 0u);
+ field.elem_size = size / field.elem_count;
+ } else if (name == "signed") {
+ int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
+ field.is_signed = (is_signed == 1);
+ }
+ }
+ }
+ return field;
+}
+
+std::vector<TracingFormat> TracingFile::LoadTracingFormatsFromEventFiles()
+ const {
+ std::vector<TracingFormat> formats;
+ for (const auto& pair : event_format_files) {
+ TracingFormat format;
+ format.system_name = pair.first;
+ std::vector<std::string> strs = android::base::Split(pair.second, "\n");
+ FormatParsingState state = FormatParsingState::READ_NAME;
+ for (const auto& s : strs) {
+ if (state == FormatParsingState::READ_NAME) {
+ size_t pos = s.find_first_of("name:");
+ if (pos != std::string::npos) {
+ format.name = android::base::Trim(s.substr(pos + strlen("name:")));
+ state = FormatParsingState::READ_ID;
+ }
+ } else if (state == FormatParsingState::READ_ID) {
+ size_t pos = s.find_first_of("ID:");
+ if (pos != std::string::npos) {
+ format.id =
+ strtoull(s.substr(pos + strlen("ID:")).c_str(), nullptr, 10);
+ state = FormatParsingState::READ_FIELDS;
+ }
+ } else if (state == FormatParsingState::READ_FIELDS) {
+ size_t pos = s.find_first_of("field:");
+ if (pos != std::string::npos) {
+ TracingField field = ParseTracingField(s);
+ format.fields.push_back(field);
+ }
+ }
+ }
+ formats.push_back(format);
+ }
+ return formats;
+}
+
+Tracing::Tracing(const std::vector<char>& data) {
+ tracing_file_ = new TracingFile;
+ tracing_file_->LoadFromBinary(data);
+}
+
+Tracing::~Tracing() { delete tracing_file_; }
+
+void Tracing::Dump(size_t indent) { tracing_file_->Dump(indent); }
+
+TracingFormat Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
+ if (tracing_formats_.empty()) {
+ tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
+ }
+ for (const auto& format : tracing_formats_) {
+ if (format.id == trace_event_id) {
+ return format;
+ }
+ }
+ LOG(FATAL) << "no tracing format for id " << trace_event_id;
+ return TracingFormat();
+}
+
+std::string Tracing::GetTracingEventNameHavingId(uint64_t trace_event_id) {
+ if (tracing_formats_.empty()) {
+ tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
+ }
+ for (const auto& format : tracing_formats_) {
+ if (format.id == trace_event_id) {
+ return android::base::StringPrintf("%s:%s", format.system_name.c_str(),
+ format.name.c_str());
+ }
+ }
+ return "";
+}
+
+const std::string& Tracing::GetKallsyms() const {
+ return tracing_file_->GetKallsymsFile();
+}
+
+uint32_t Tracing::GetPageSize() const { return tracing_file_->GetPageSize(); }
+
+bool GetTracingData(const std::vector<const EventType*>& event_types,
+ std::vector<char>* data) {
+ data->clear();
+ std::vector<TraceType> trace_types;
+ for (const auto& type : event_types) {
+ CHECK_EQ(PERF_TYPE_TRACEPOINT, type->type);
+ size_t pos = type->name.find(':');
+ TraceType trace_type;
+ trace_type.system = type->name.substr(0, pos);
+ trace_type.name = type->name.substr(pos + 1);
+ trace_types.push_back(trace_type);
+ }
+ TracingFile tracing_file;
+ if (!tracing_file.RecordHeaderFiles()) {
+ return false;
+ }
+ tracing_file.RecordFtraceFiles(trace_types);
+ if (!tracing_file.RecordEventFiles(trace_types)) {
+ return false;
+ }
+ // Don't record /proc/kallsyms here, as it will be contained in
+ // KernelSymbolRecord.
+ if (!tracing_file.RecordPrintkFormatsFile()) {
+ return false;
+ }
+ *data = tracing_file.BinaryFormat();
+ return true;
+}
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
new file mode 100644
index 0000000..4e21398
--- /dev/null
+++ b/simpleperf/tracing.h
@@ -0,0 +1,89 @@
+/*
+ * 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_TRACING_H_
+#define SIMPLE_PERF_TRACING_H_
+
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "event_type.h"
+#include "utils.h"
+
+struct TracingField {
+ std::string name;
+ size_t offset;
+ size_t elem_size;
+ size_t elem_count;
+ bool is_signed;
+};
+
+struct TracingFieldPlace {
+ uint32_t offset;
+ uint32_t size;
+
+ uint64_t ReadFromData(const char* raw_data) {
+ return ConvertBytesToValue(raw_data + offset, size);
+ }
+};
+
+struct TracingFormat {
+ std::string system_name;
+ std::string name;
+ uint64_t id;
+ std::vector<TracingField> fields;
+
+ void GetField(const std::string& name, TracingFieldPlace& place) {
+ const TracingField& field = GetField(name);
+ place.offset = field.offset;
+ place.size = field.elem_size;
+ }
+
+ private:
+ const TracingField& GetField(const std::string& name) {
+ for (const auto& field : fields) {
+ if (field.name == name) {
+ return field;
+ }
+ }
+ LOG(FATAL) << "Couldn't find field " << name << "in TracingFormat of "
+ << this->name;
+ return fields[0];
+ }
+};
+
+class TracingFile;
+
+class Tracing {
+ public:
+ Tracing(const std::vector<char>& data);
+ ~Tracing();
+ void Dump(size_t indent);
+ TracingFormat GetTracingFormatHavingId(uint64_t trace_event_id);
+ std::string GetTracingEventNameHavingId(uint64_t trace_event_id);
+ const std::string& GetKallsyms() const;
+ uint32_t GetPageSize() const;
+
+ private:
+ TracingFile* tracing_file_;
+ std::vector<TracingFormat> tracing_formats_;
+};
+
+bool GetTracingData(const std::vector<const EventType*>& event_types,
+ std::vector<char>* data);
+
+#endif // SIMPLE_PERF_TRACING_H_
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index d14d20c..2df15e6 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -19,6 +19,7 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
@@ -31,6 +32,10 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <7zCrc.h>
+#include <Xz.h>
+#include <XzCrc64.h>
+
void OneTimeFreeAllocator::Clear() {
for (auto& p : v_) {
delete[] p;
@@ -95,6 +100,14 @@
va_end(ap);
}
+void FprintIndented(FILE* fp, size_t indent, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(fp, "%*s", static_cast<int>(indent * 2), "");
+ vfprintf(fp, fmt, ap);
+ va_end(ap);
+}
+
bool IsPowerOfTwo(uint64_t value) {
return (value != 0 && ((value & (value - 1)) == 0));
}
@@ -182,10 +195,57 @@
return true;
}
+static void* xz_alloc(void*, size_t size) {
+ return malloc(size);
+}
+
+static void xz_free(void*, void* address) {
+ free(address);
+}
+
+bool XzDecompress(const std::string& compressed_data, std::string* decompressed_data) {
+ ISzAlloc alloc;
+ CXzUnpacker state;
+ alloc.Alloc = xz_alloc;
+ alloc.Free = xz_free;
+ XzUnpacker_Construct(&state, &alloc);
+ CrcGenerateTable();
+ Crc64GenerateTable();
+ size_t src_offset = 0;
+ size_t dst_offset = 0;
+ std::string dst(compressed_data.size(), ' ');
+
+ ECoderStatus status = CODER_STATUS_NOT_FINISHED;
+ while (status == CODER_STATUS_NOT_FINISHED) {
+ dst.resize(dst.size() * 2);
+ size_t src_remaining = compressed_data.size() - src_offset;
+ size_t dst_remaining = dst.size() - dst_offset;
+ int res = XzUnpacker_Code(&state, reinterpret_cast<Byte*>(&dst[dst_offset]), &dst_remaining,
+ reinterpret_cast<const Byte*>(&compressed_data[src_offset]),
+ &src_remaining, CODER_FINISH_ANY, &status);
+ if (res != SZ_OK) {
+ LOG(ERROR) << "LZMA decompression failed with error " << res;
+ XzUnpacker_Free(&state);
+ return false;
+ }
+ src_offset += src_remaining;
+ dst_offset += dst_remaining;
+ }
+ XzUnpacker_Free(&state);
+ if (!XzUnpacker_IsStreamWasFinished(&state)) {
+ LOG(ERROR) << "LZMA decompresstion failed due to incomplete stream";
+ return false;
+ }
+ dst.resize(dst_offset);
+ *decompressed_data = std::move(dst);
+ 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},
+ {"info", android::base::INFO},
{"warning", android::base::WARNING},
{"error", android::base::ERROR},
{"fatal", android::base::FATAL},
@@ -209,3 +269,67 @@
}
return is_root == 1;
}
+
+bool ProcessKernelSymbols(std::string& symbol_data,
+ std::function<bool(const KernelSymbol&)> callback) {
+ char* p = &symbol_data[0];
+ char* data_end = p + symbol_data.size();
+ while (p < data_end) {
+ char* line_end = strchr(p, '\n');
+ if (line_end != nullptr) {
+ *line_end = '\0';
+ }
+ size_t line_size = (line_end != nullptr) ? (line_end - p) : (data_end - p);
+ // Parse line like: ffffffffa005c4e4 d __warned.41698 [libsas]
+ char name[line_size];
+ char module[line_size];
+ strcpy(module, "");
+
+ KernelSymbol symbol;
+ int ret = sscanf(p, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module);
+ if (line_end != nullptr) {
+ *line_end = '\n';
+ p = line_end + 1;
+ } else {
+ p = data_end;
+ }
+ if (ret >= 3) {
+ symbol.name = name;
+ size_t module_len = strlen(module);
+ if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
+ module[module_len - 1] = '\0';
+ symbol.module = &module[1];
+ } else {
+ symbol.module = nullptr;
+ }
+
+ if (callback(symbol)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+size_t GetPageSize() {
+#if defined(__linux__)
+ return sysconf(_SC_PAGE_SIZE);
+#else
+ return 4096;
+#endif
+}
+
+uint64_t ConvertBytesToValue(const char* bytes, uint32_t size) {
+ switch (size) {
+ case 1:
+ return *reinterpret_cast<const uint8_t*>(bytes);
+ case 2:
+ return *reinterpret_cast<const uint16_t*>(bytes);
+ case 4:
+ return *reinterpret_cast<const uint32_t*>(bytes);
+ case 8:
+ return *reinterpret_cast<const uint64_t*>(bytes);
+ }
+ LOG(FATAL) << "unexpected size " << size << " in ConvertBytesToValue";
+ return 0;
+}
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index 420fcc9..9ab0f86 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -26,7 +26,9 @@
#include <android-base/macros.h>
#include <ziparchive/zip_archive.h>
-#define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1))
+static inline uint64_t Align(uint64_t value, uint64_t alignment) {
+ return (value + alignment - 1) & ~(alignment - 1);
+}
#ifdef _WIN32
#define CLOSE_ON_EXEC_MODE ""
@@ -109,6 +111,7 @@
}
void PrintIndented(size_t indent, const char* fmt, ...);
+void FprintIndented(FILE* fp, size_t indent, const char* fmt, ...);
bool IsPowerOfTwo(uint64_t value);
@@ -119,7 +122,24 @@
uint64_t GetFileSize(const std::string& filename);
bool MkdirWithParents(const std::string& path);
+bool XzDecompress(const std::string& compressed_data, std::string* decompressed_data);
+
bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity);
bool IsRoot();
+
+struct KernelSymbol {
+ uint64_t addr;
+ char type;
+ const char* name;
+ const char* module; // If nullptr, the symbol is not in a kernel module.
+};
+
+bool ProcessKernelSymbols(std::string& symbol_data,
+ std::function<bool(const KernelSymbol&)> callback);
+
+size_t GetPageSize();
+
+uint64_t ConvertBytesToValue(const char* bytes, uint32_t size);
+
#endif // SIMPLE_PERF_UTILS_H_
diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp
new file mode 100644
index 0000000..23c669e
--- /dev/null
+++ b/simpleperf/utils_test.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "utils.h"
+
+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) {
+ std::string data =
+ "ffffffffa005c4e4 d __warned.41698 [libsas]\n"
+ "aaaaaaaaaaaaaaaa T _text\n"
+ "cccccccccccccccc c ccccc\n";
+ KernelSymbol expected_symbol;
+ expected_symbol.addr = 0xffffffffa005c4e4ULL;
+ expected_symbol.type = 'd';
+ expected_symbol.name = "__warned.41698";
+ expected_symbol.module = "libsas";
+ ASSERT_TRUE(ProcessKernelSymbols(
+ data,
+ 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(
+ data,
+ std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+
+ expected_symbol.name = "non_existent_symbol";
+ ASSERT_FALSE(ProcessKernelSymbols(
+ data,
+ std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+}
diff --git a/tests/binder/benchmarks/binderAddInts.cpp b/tests/binder/benchmarks/binderAddInts.cpp
index d0b2910..afa464a 100644
--- a/tests/binder/benchmarks/binderAddInts.cpp
+++ b/tests/binder/benchmarks/binderAddInts.cpp
@@ -62,7 +62,7 @@
class AddIntsService : public BBinder
{
public:
- AddIntsService(int cpu = unbound);
+ explicit AddIntsService(int cpu = unbound);
virtual ~AddIntsService() {}
enum command {
diff --git a/tests/ext4/rand_emmc_perf.c b/tests/ext4/rand_emmc_perf.c
index ebd10c8..fed7a54 100644
--- a/tests/ext4/rand_emmc_perf.c
+++ b/tests/ext4/rand_emmc_perf.c
@@ -51,8 +51,8 @@
{
int i;
struct timeval t;
- struct timeval sum = { 0 };
- struct timeval max = { 0 };
+ struct timeval sum = { 0, 0 };
+ struct timeval max = { 0, 0 };
long long total_usecs;
long long avg_usecs;
long long max_usecs;
@@ -217,6 +217,7 @@
break;
case 'f':
+ free(full_stats_file);
full_stats_file = strdup(optarg);
if (full_stats_file == NULL) {
fprintf(stderr, "Cannot get full stats filename\n");
@@ -258,6 +259,7 @@
} else {
perf_test(fd, write_mode, max_blocks);
}
+ free(full_stats_file);
exit(0);
}
diff --git a/tests/fstest/recovery_test.cpp b/tests/fstest/recovery_test.cpp
index b93de83..02c7e8f 100644
--- a/tests/fstest/recovery_test.cpp
+++ b/tests/fstest/recovery_test.cpp
@@ -48,7 +48,7 @@
class DataFileVerifier {
public:
- DataFileVerifier(const char* file_name) {
+ explicit DataFileVerifier(const char* file_name) {
strncpy(test_file_, file_name, FILENAME_MAX);
}
diff --git a/tests/kernel.config/Android.mk b/tests/kernel.config/Android.mk
index fc90f66..7a9354d 100644
--- a/tests/kernel.config/Android.mk
+++ b/tests/kernel.config/Android.mk
@@ -38,22 +38,6 @@
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)
@@ -72,6 +56,23 @@
endif # ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64))
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
+LOCAL_CTS_GTEST_LIST_EXECUTABLE := $(ALL_MODULES.$(cts_executable)_list$(HOST_2ND_ARCH_MODULE_SUFFIX).INSTALLED)
+include $(BUILD_CTS_EXECUTABLE)
+
+include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
scrape_mmap_addr.cpp
diff --git a/tests/lib/testUtil/testUtil.c b/tests/lib/testUtil/testUtil.c
index 983e7a4..d4dcea2 100644
--- a/tests/lib/testUtil/testUtil.c
+++ b/tests/lib/testUtil/testUtil.c
@@ -32,7 +32,7 @@
#include <cutils/log.h>
-#define ALEN(a) (sizeof(a) / sizeof(a [0])) // Array length
+#define ALEN(a) (sizeof(a) / sizeof((a)[0])) // Array length
typedef unsigned int bool_t;
#define true (0 == 0)
#define false (!true)
diff --git a/tests/net_test/README b/tests/net_test/README
deleted file mode 100644
index f45c3d5..0000000
--- a/tests/net_test/README
+++ /dev/null
@@ -1,77 +0,0 @@
- net_test v0.1
- =============
-
-A simple framework for blackbox testing of kernel networking code.
-
-
-Why use it?
-===========
-
-- Fast test / boot cycle.
-- Access to host filesystem and networking via L2 bridging.
-- Full Linux userland including Python, etc.
-- Kernel bugs don't crash the system.
-
-
-How to use it
-=============
-
-cd <kerneldir>
-path/to/net_test/run_net_test.sh <test>
-
-where <test> is the name of a test binary in the net_test directory. This can
-be an x86 binary, a shell script, a Python script. etc.
-
-
-How it works
-============
-
-net_test compiles the kernel to a user-mode linux binary, which runs as a
-process on the host machine. It runs the binary to start a Linux "virtual
-machine" whose root filesystem is the supplied Debian disk image. The machine
-boots, mounts the root filesystem read-only, runs the specified test from init, and then drops to a shell.
-
-
-Access to host filesystem
-=========================
-
-The VM mounts the host filesystem at /host, so the test can be modified and
-re-run without rebooting the VM.
-
-
-Access to host networking
-=========================
-
-Access to host networking is provided by tap interfaces. On the host, the
-interfaces are named <user>TAP0, <user>TAP1, etc., where <user> is the first
-10 characters of the username running net_test. (10 characters because
-IFNAMSIZ = 16). On the guest, they are named eth0, eth1, etc.
-
-net_test does not do any networking setup beyond creating the tap interfaces.
-IP connectivity can be provided on the host side by setting up a DHCP server
-and NAT, sending IPv6 router advertisements, etc. By default, the VM has IPv6
-privacy addresses disabled, so its IPv6 addresses can be predicted using a tool
-such as ipv6calc.
-
-The provided filesystem contains a DHCPv4 client and simple networking
-utilities such as ping[6], traceroute[6], and wget.
-
-The number of tap interfaces is currently hardcoded to two. To change this
-number, modify run_net_test.sh.
-
-
-Logging into the VM, installing packages, etc.
-==============================================
-
-net_test mounts the root filesystem read-only, and runs the test from init, but
-since the filesystem contains a full Linux userland, it's possible to boot into
-userland and modify the filesystem, for example to install packages using
-apt-get install. Log in as root with no password. By default, the filesystem is
-configured to perform DHCPv4 on eth0 and listen to RAs.
-
-
-Bugs
-====
-
-Since the test mounts the filesystem read-only, tests cannot modify
-/etc/resolv.conf and the system resolver is hardcoded to 8.8.8.8.
diff --git a/tests/net_test/all_tests.sh b/tests/net_test/all_tests.sh
deleted file mode 100755
index ce147d3..0000000
--- a/tests/net_test/all_tests.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-readonly PREFIX="#####"
-
-function maybePlural() {
- # $1 = integer to use for plural check
- # $2 = singular string
- # $3 = plural string
- if [ $1 -ne 1 ]; then
- echo "$3"
- else
- echo "$2"
- fi
-}
-
-
-readonly tests=$(find . -name '*_test.py' -type f -executable)
-readonly count=$(echo $tests | wc -w)
-echo "$PREFIX Found $count $(maybePlural $count test tests)."
-
-exit_code=0
-
-i=0
-for test in $tests; do
- i=$((i + 1))
- echo ""
- echo "$PREFIX $test ($i/$count)"
- echo ""
- $test || exit_code=$(( exit_code + 1 ))
- echo ""
-done
-
-echo "$PREFIX $exit_code failed $(maybePlural $exit_code test tests)."
-exit $exit_code
diff --git a/tests/net_test/anycast_test.py b/tests/net_test/anycast_test.py
deleted file mode 100755
index 82130db..0000000
--- a/tests/net_test/anycast_test.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/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()
diff --git a/tests/net_test/csocket.py b/tests/net_test/csocket.py
deleted file mode 100644
index 5dc495c..0000000
--- a/tests/net_test/csocket.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# 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.
-
-"""Python wrapper for C socket calls and data structures."""
-
-import ctypes
-import ctypes.util
-import os
-import socket
-import struct
-
-import cstruct
-
-
-# Data structures.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-CMsgHdr = cstruct.Struct("cmsghdr", "@Lii", "len level type")
-Iovec = cstruct.Struct("iovec", "@LL", "base len")
-MsgHdr = cstruct.Struct("msghdr", "@LLLLLLi",
- "name namelen iov iovlen control msg_controllen flags")
-SockaddrIn = cstruct.Struct("sockaddr_in", "=HH4sxxxxxxxx", "family port addr")
-SockaddrIn6 = cstruct.Struct("sockaddr_in6", "=HHI16sI",
- "family port flowinfo addr scope_id")
-
-# Constants.
-CMSG_ALIGNTO = struct.calcsize("@L") # The kernel defines this as sizeof(long).
-MSG_CONFIRM = 0X800
-
-# Find the C library.
-libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
-
-
-def PaddedLength(length):
- return CMSG_ALIGNTO * ((length / CMSG_ALIGNTO) + (length % CMSG_ALIGNTO != 0))
-
-
-def MaybeRaiseSocketError(ret):
- if ret < 0:
- errno = ctypes.get_errno()
- raise socket.error(errno, os.strerror(errno))
-
-
-def Sockaddr(addr):
- if ":" in addr[0]:
- family = socket.AF_INET6
- if len(addr) == 4:
- addr, port, flowinfo, scope_id = addr
- else:
- (addr, port), flowinfo, scope_id = addr, 0, 0
- addr = socket.inet_pton(family, addr)
- return SockaddrIn6((family, socket.ntohs(port), socket.ntohl(flowinfo),
- addr, scope_id))
- else:
- family = socket.AF_INET
- addr, port = addr
- addr = socket.inet_pton(family, addr)
- return SockaddrIn((family, socket.ntohs(port), addr))
-
-
-def _MakeMsgControl(optlist):
- """Creates a msg_control blob from a list of cmsg attributes.
-
- Takes a list of cmsg attributes. Each attribute is a tuple of:
- - level: An integer, e.g., SOL_IPV6.
- - type: An integer, the option identifier, e.g., IPV6_HOPLIMIT.
- - data: The option data. This is either a string or an integer. If it's an
- integer it will be written as an unsigned integer in host byte order. If
- it's a string, it's used as is.
-
- Data is padded to an integer multiple of CMSG_ALIGNTO.
-
- Args:
- optlist: A list of tuples describing cmsg options.
-
- Returns:
- A string, a binary blob usable as the control data for a sendmsg call.
-
- Raises:
- TypeError: Option data is neither an integer nor a string.
- """
- msg_control = ""
-
- for i, opt in enumerate(optlist):
- msg_level, msg_type, data = opt
- if isinstance(data, int):
- data = struct.pack("=I", data)
- elif not isinstance(data, str):
- raise TypeError("unknown data type for opt %i: %s" % (i, type(data)))
-
- datalen = len(data)
- msg_len = len(CMsgHdr) + datalen
- padding = "\x00" * (PaddedLength(datalen) - datalen)
- msg_control += CMsgHdr((msg_len, msg_level, msg_type)).Pack()
- msg_control += data + padding
-
- return msg_control
-
-
-def Bind(s, to):
- """Python wrapper for connect."""
- ret = libc.bind(s.fileno(), to.CPointer(), len(to))
- MaybeRaiseSocketError(ret)
- return ret
-
-
-def Connect(s, to):
- """Python wrapper for connect."""
- ret = libc.connect(s.fileno(), to.CPointer(), len(to))
- MaybeRaiseSocketError(ret)
- return ret
-
-
-def Sendmsg(s, to, data, control, flags):
- """Python wrapper for sendmsg.
-
- Args:
- s: A Python socket object. Becomes sockfd.
- to: An address tuple, or a SockaddrIn[6] struct. Becomes msg->msg_name.
- data: A string, the data to write. Goes into msg->msg_iov.
- control: A list of cmsg options. Becomes msg->msg_control.
- flags: An integer. Becomes msg->msg_flags.
-
- Returns:
- If sendmsg succeeds, returns the number of bytes written as an integer.
-
- Raises:
- socket.error: If sendmsg fails.
- """
- # Create ctypes buffers and pointers from our structures. We need to hang on
- # to the underlying Python objects, because we don't want them to be garbage
- # collected and freed while we have C pointers to them.
-
- # Convert the destination address into a struct sockaddr.
- if to:
- if isinstance(to, tuple):
- to = Sockaddr(to)
- msg_name = to.CPointer()
- msg_namelen = len(to)
- else:
- msg_name = 0
- msg_namelen = 0
-
- # Convert the data to a data buffer and a struct iovec pointing at it.
- if data:
- databuf = ctypes.create_string_buffer(data)
- iov = Iovec((ctypes.addressof(databuf), len(data)))
- msg_iov = iov.CPointer()
- msg_iovlen = 1
- else:
- msg_iov = 0
- msg_iovlen = 0
-
- # Marshal the cmsg options.
- if control:
- control = _MakeMsgControl(control)
- controlbuf = ctypes.create_string_buffer(control)
- msg_control = ctypes.addressof(controlbuf)
- msg_controllen = len(control)
- else:
- msg_control = 0
- msg_controllen = 0
-
- # Assemble the struct msghdr.
- msghdr = MsgHdr((msg_name, msg_namelen, msg_iov, msg_iovlen,
- msg_control, msg_controllen, flags)).Pack()
-
- # Call sendmsg.
- ret = libc.sendmsg(s.fileno(), msghdr, 0)
- MaybeRaiseSocketError(ret)
-
- return ret
diff --git a/tests/net_test/cstruct.py b/tests/net_test/cstruct.py
deleted file mode 100644
index 91cd72e..0000000
--- a/tests/net_test/cstruct.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# 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.
-
-"""A simple module for declaring C-like structures.
-
-Example usage:
-
->>> # Declare a struct type by specifying name, field formats and field names.
-... # Field formats are the same as those used in the struct module.
-... import cstruct
->>> NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
->>>
->>>
->>> # Create instances from tuples or raw bytes. Data past the end is ignored.
-... n1 = NLMsgHdr((44, 32, 0x2, 0, 491))
->>> print n1
-NLMsgHdr(length=44, type=32, flags=2, seq=0, pid=491)
->>>
->>> n2 = NLMsgHdr("\x2c\x00\x00\x00\x21\x00\x02\x00"
-... "\x00\x00\x00\x00\xfe\x01\x00\x00" + "junk at end")
->>> print n2
-NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510)
->>>
->>> # Serialize to raw bytes.
-... print n1.Pack().encode("hex")
-2c0000002000020000000000eb010000
->>>
->>> # Parse the beginning of a byte stream as a struct, and return the struct
-... # and the remainder of the stream for further reading.
-... data = ("\x2c\x00\x00\x00\x21\x00\x02\x00"
-... "\x00\x00\x00\x00\xfe\x01\x00\x00"
-... "more data")
->>> cstruct.Read(data, NLMsgHdr)
-(NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510), 'more data')
->>>
-"""
-
-import ctypes
-import string
-import struct
-
-
-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):
-
- def __len__(cls):
- return cls._length
-
- def __init__(cls, unused_name, unused_bases, namespace):
- # Make the class object have the name that's passed in.
- type.__init__(cls, namespace["_name"], unused_bases, namespace)
-
- class CStruct(object):
- """Class representing a C-like structure."""
-
- __metaclass__ = Meta
-
- # Name of the struct.
- _name = name
- # 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)
-
- 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):
- # Initializing from a string.
- if isinstance(values, str):
- if len(values) < self._length:
- raise TypeError("%s requires string of length %d, got %d" %
- (self._name, self._length, len(values)))
- self._Parse(values)
- else:
- # Initializing from a tuple.
- 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._fieldnames.index(attr)
- except ValueError:
- raise AttributeError("'%s' has no attribute '%s'" %
- (self._name, attr))
-
- def __getattr__(self, name):
- return self._values[self._FieldIndex(name)]
-
- def __setattr__(self, name, value):
- self._values[self._FieldIndex(name)] = value
-
- @classmethod
- 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):
- values = [self._MaybePackStruct(v) for v in self._values]
- return struct.pack(self._format, *values)
-
- def __str__(self):
- 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)
-
- def CPointer(self):
- """Returns a C pointer to the serialized structure."""
- buf = ctypes.create_string_buffer(self.Pack())
- # Store the C buffer in the object so it doesn't get garbage collected.
- super(CStruct, self).__setattr__("_buffer", buf)
- return ctypes.addressof(self._buffer)
-
- return CStruct
-
-
-def Read(data, struct_type):
- length = len(struct_type)
- return struct_type(data), data[length:]
diff --git a/tests/net_test/cstruct_test.py b/tests/net_test/cstruct_test.py
deleted file mode 100755
index 2d5a408..0000000
--- a/tests/net_test/cstruct_test.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import unittest
-
-import cstruct
-
-
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-TestStructA = cstruct.Struct("TestStructA", "=BI", "byte1 int2")
-TestStructB = cstruct.Struct("TestStructB", "=BI", "byte1 int2")
-
-
-class CstructTest(unittest.TestCase):
-
- def CheckEquals(self, a, b):
- self.assertEquals(a, b)
- self.assertEquals(b, a)
- assert a == b
- assert b == a
- assert not (a != b) # pylint: disable=g-comparison-negation,superfluous-parens
- assert not (b != a) # pylint: disable=g-comparison-negation,superfluous-parens
-
- def CheckNotEquals(self, a, b):
- self.assertNotEquals(a, b)
- self.assertNotEquals(b, a)
- assert a != b
- assert b != a
- assert not (a == b) # pylint: disable=g-comparison-negation,superfluous-parens
- assert not (b == a) # pylint: disable=g-comparison-negation,superfluous-parens
-
- def testEqAndNe(self):
- a1 = TestStructA((1, 2))
- a2 = TestStructA((2, 3))
- a3 = TestStructA((1, 2))
- b = TestStructB((1, 2))
- self.CheckNotEquals(a1, b)
- self.CheckNotEquals(a2, b)
- self.CheckNotEquals(a1, a2)
- self.CheckNotEquals(a2, a3)
- for i in [a1, a2, a3, b]:
- self.CheckEquals(i, i)
- self.CheckEquals(a1, a3)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/net_test/forwarding_test.py b/tests/net_test/forwarding_test.py
deleted file mode 100755
index 185e477..0000000
--- a/tests/net_test/forwarding_test.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/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()
diff --git a/tests/net_test/iproute.py b/tests/net_test/iproute.py
deleted file mode 100644
index 2c63993..0000000
--- a/tests/net_test/iproute.py
+++ /dev/null
@@ -1,541 +0,0 @@
-#!/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
-import netlink
-
-
-### Base netlink constants. See include/uapi/linux/netlink.h.
-NETLINK_ROUTE = 0
-
-# Request constants.
-NLM_F_REQUEST = 1
-NLM_F_ACK = 4
-NLM_F_REPLACE = 0x100
-NLM_F_EXCL = 0x200
-NLM_F_CREATE = 0x400
-NLM_F_DUMP = 0x300
-
-# 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
-
-
-### rtnetlink constants. See include/uapi/linux/rtnetlink.h.
-# Message types.
-RTM_NEWLINK = 16
-RTM_DELLINK = 17
-RTM_GETLINK = 18
-RTM_NEWADDR = 20
-RTM_DELADDR = 21
-RTM_GETADDR = 22
-RTM_NEWROUTE = 24
-RTM_DELROUTE = 25
-RTM_GETROUTE = 26
-RTM_NEWNEIGH = 28
-RTM_DELNEIGH = 29
-RTM_GETNEIGH = 30
-RTM_NEWRULE = 32
-RTM_DELRULE = 33
-RTM_GETRULE = 34
-
-# Routing message type values (rtm_type).
-RTN_UNSPEC = 0
-RTN_UNICAST = 1
-RTN_UNREACHABLE = 7
-
-# Routing protocol values (rtm_protocol).
-RTPROT_UNSPEC = 0
-RTPROT_STATIC = 4
-
-# Route scope values (rtm_scope).
-RT_SCOPE_UNIVERSE = 0
-RT_SCOPE_LINK = 253
-
-# Named routing tables.
-RT_TABLE_UNSPEC = 0
-
-# Routing attributes.
-RTA_DST = 1
-RTA_SRC = 2
-RTA_OIF = 4
-RTA_GATEWAY = 5
-RTA_PRIORITY = 6
-RTA_PREFSRC = 7
-RTA_METRICS = 8
-RTA_CACHEINFO = 12
-RTA_TABLE = 15
-RTA_MARK = 16
-RTA_UID = 18
-
-# Route metric attributes.
-RTAX_MTU = 2
-RTAX_HOPLIMIT = 10
-
-# Data structure formats.
-IfinfoMsg = cstruct.Struct(
- "IfinfoMsg", "=BBHiII", "family pad type index flags change")
-RTMsg = cstruct.Struct(
- "RTMsg", "=BBBBBBBBI",
- "family dst_len src_len tos table protocol scope type flags")
-RTACacheinfo = cstruct.Struct(
- "RTACacheinfo", "=IIiiI", "clntref lastuse expires error used")
-
-
-### Interface address constants. See include/uapi/linux/if_addr.h.
-# Interface address attributes.
-IFA_ADDRESS = 1
-IFA_LOCAL = 2
-IFA_CACHEINFO = 6
-
-# Address flags.
-IFA_F_SECONDARY = 0x01
-IFA_F_TEMPORARY = IFA_F_SECONDARY
-IFA_F_NODAD = 0x02
-IFA_F_OPTIMISTIC = 0x04
-IFA_F_DADFAILED = 0x08
-IFA_F_HOMEADDRESS = 0x10
-IFA_F_DEPRECATED = 0x20
-IFA_F_TENTATIVE = 0x40
-IFA_F_PERMANENT = 0x80
-
-# Data structure formats.
-IfAddrMsg = cstruct.Struct(
- "IfAddrMsg", "=BBBBI",
- "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
-
-# Data structure formats.
-NdMsg = cstruct.Struct(
- "NdMsg", "=BxxxiHBB",
- "family ifindex state flags type")
-
-
-### FIB rule constants. See include/uapi/linux/fib_rules.h.
-FRA_IIFNAME = 3
-FRA_PRIORITY = 6
-FRA_FWMARK = 10
-FRA_SUPPRESS_PREFIXLEN = 14
-FRA_TABLE = 15
-FRA_FWMASK = 16
-FRA_OIFNAME = 17
-FRA_UID_START = 18
-FRA_UID_END = 19
-
-
-# Link constants. See include/uapi/linux/if_link.h.
-IFLA_ADDRESS = 1
-IFLA_BROADCAST = 2
-IFLA_IFNAME = 3
-IFLA_MTU = 4
-IFLA_QDISC = 6
-IFLA_STATS = 7
-IFLA_TXQLEN = 13
-IFLA_MAP = 14
-IFLA_OPERSTATE = 16
-IFLA_LINKMODE = 17
-IFLA_STATS64 = 23
-IFLA_AF_SPEC = 26
-IFLA_GROUP = 27
-IFLA_EXT_MASK = 29
-IFLA_PROMISCUITY = 30
-IFLA_NUM_TX_QUEUES = 31
-IFLA_NUM_RX_QUEUES = 32
-IFLA_CARRIER = 33
-
-
-def CommandVerb(command):
- return ["NEW", "DEL", "GET", "SET"][command % 4]
-
-
-def CommandSubject(command):
- return ["LINK", "ADDR", "ROUTE", "NEIGH", "RULE"][(command - 16) / 4]
-
-
-def CommandName(command):
- try:
- return "RTM_%s%s" % (CommandVerb(command), CommandSubject(command))
- except IndexError:
- return "RTM_%d" % command
-
-
-class IPRoute(netlink.NetlinkSocket):
- """Provides a tiny subset of iproute functionality."""
-
- FAMILY = NETLINK_ROUTE
-
- def _NlAttrIPAddress(self, nla_type, family, address):
- return self._NlAttr(nla_type, socket.inet_pton(family, address))
-
- def _NlAttrInterfaceName(self, nla_type, interface):
- return self._NlAttr(nla_type, interface + "\x00")
-
- def _GetConstantName(self, value, prefix):
- 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
- RTM_NEWRULE command) are decoded to Python integers, strings, etc. Values
- of unknown type are returned as raw byte strings.
-
- Args:
- command: An integer.
- - If positive, the number of the rtnetlink command being carried out.
- This is used to interpret the attributes. For example, for an
- RTM_NEWROUTE command, attribute type 3 is the incoming interface and
- is an integer, but for a RTM_NEWRULE command, attribute type 3 is the
- incoming interface name and is a string.
- - If negative, one of the following (negative) values:
- - RTA_METRICS: Interpret as nested route metrics.
- family: The address family. Used to convert IP addresses into strings.
- nla_type: An integer, then netlink attribute type.
- nla_data: A byte string, the netlink attribute data.
-
- Returns:
- A tuple (name, data):
- - name is a string (e.g., "FRA_PRIORITY") if we understood the attribute,
- or an integer if we didn't.
- - data can be an integer, a string, a nested dict of attributes as
- returned by _ParseAttributes (e.g., for RTA_METRICS), a cstruct.Struct
- (e.g., RTACacheinfo), etc. If we didn't understand the attribute, it
- will be the raw byte string.
- """
- if command == -RTA_METRICS:
- name = self._GetConstantName(nla_type, "RTAX_")
- elif CommandSubject(command) == "ADDR":
- name = self._GetConstantName(nla_type, "IFA_")
- elif CommandSubject(command) == "LINK":
- name = self._GetConstantName(nla_type, "IFLA_")
- elif CommandSubject(command) == "RULE":
- name = self._GetConstantName(nla_type, "FRA_")
- elif CommandSubject(command) == "ROUTE":
- name = self._GetConstantName(nla_type, "RTA_")
- elif CommandSubject(command) == "NEIGH":
- name = self._GetConstantName(nla_type, "NDA_")
- else:
- # Don't know what this is. Leave it as an integer.
- name = nla_type
-
- 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", "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 ["IFLA_LINKMODE", "IFLA_OPERSTATE", "IFLA_CARRIER"]:
- data = ord(nla_data)
- elif name in ["IFA_ADDRESS", "IFA_LOCAL", "RTA_DST", "RTA_SRC",
- "RTA_GATEWAY", "RTA_PREFSRC", "RTA_UID",
- "NDA_DST"]:
- 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, 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:
- data = nla_data
-
- return name, data
-
- def __init__(self):
- super(IPRoute, self).__init__()
-
- def _AddressFamily(self, version):
- return {4: socket.AF_INET, 6: socket.AF_INET6}[version]
-
- def _SendNlRequest(self, command, data, flags=0):
- """Sends a netlink request and expects an ack."""
-
- flags |= NLM_F_REQUEST
- if CommandVerb(command) != "GET":
- flags |= NLM_F_ACK
- if CommandVerb(command) == "NEW":
- if not flags & NLM_F_REPLACE:
- flags |= (NLM_F_EXCL | NLM_F_CREATE)
-
- 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>".
-
- Args:
- version: An integer, 4 or 6.
- is_add: True to add a rule, False to delete it.
- rule_type: Type of rule, e.g., RTN_UNICAST or RTN_UNREACHABLE.
- table: If nonzero, rule looks up this table.
- match_nlattr: A blob of struct nlattrs that express the match condition.
- If None, match everything.
- priority: An integer, the priority.
-
- Raises:
- IOError: If the netlink request returns an error.
- ValueError: If the kernel's response could not be parsed.
- """
- # Create a struct rtmsg specifying the table and the given match attributes.
- family = self._AddressFamily(version)
- rtmsg = RTMsg((family, 0, 0, 0, RT_TABLE_UNSPEC,
- RTPROT_STATIC, RT_SCOPE_UNIVERSE, rule_type, 0)).Pack()
- rtmsg += self._NlAttrU32(FRA_PRIORITY, priority)
- if match_nlattr:
- rtmsg += match_nlattr
- if table:
- rtmsg += self._NlAttrU32(FRA_TABLE, table)
-
- # Create a netlink request containing the rtmsg.
- command = RTM_NEWRULE if is_add else RTM_DELRULE
- self._SendNlRequest(command, rtmsg)
-
- def DeleteRulesAtPriority(self, version, priority):
- family = self._AddressFamily(version)
- rtmsg = RTMsg((family, 0, 0, 0, RT_TABLE_UNSPEC,
- RTPROT_STATIC, RT_SCOPE_UNIVERSE, RTN_UNICAST, 0)).Pack()
- rtmsg += self._NlAttrU32(FRA_PRIORITY, priority)
- while True:
- try:
- self._SendNlRequest(RTM_DELRULE, rtmsg)
- except IOError, e:
- if e.errno == -errno.ENOENT:
- break
- else:
- raise
-
- def FwmarkRule(self, version, is_add, fwmark, table, priority):
- 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 UidRangeRule(self, version, is_add, start, end, table, priority):
- nlattr = (self._NlAttrInterfaceName(FRA_IIFNAME, "lo") +
- self._NlAttrU32(FRA_UID_START, start) +
- self._NlAttrU32(FRA_UID_END, end))
- return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
-
- def UnreachableRule(self, version, is_add, priority):
- return self._Rule(version, is_add, RTN_UNREACHABLE, None, None, priority)
-
- def DefaultRule(self, version, is_add, table, priority):
- return self.FwmarkRule(version, is_add, 0, table, priority)
-
- def CommandToString(self, command, data):
- try:
- name = CommandName(command)
- subject = CommandSubject(command)
- struct_type = {
- "ADDR": IfAddrMsg,
- "LINK": IfinfoMsg,
- "NEIGH": NdMsg,
- "ROUTE": RTMsg,
- "RULE": RTMsg,
- }[subject]
- parsed = self._ParseNLMsg(data, struct_type)
- return "%s %s" % (name, str(parsed))
- except IndexError:
- raise ValueError("Don't know how to print command type %s" % name)
-
- def MaybeDebugCommand(self, command, data):
- subject = CommandSubject(command)
- if "ALL" not in self.NL_DEBUG and subject not in self.NL_DEBUG:
- return
- print self.CommandToString(command, data)
-
- def MaybeDebugMessage(self, message):
- hdr = NLMsgHdr(message)
- self.MaybeDebugCommand(hdr.type, message)
-
- def PrintMessage(self, message):
- hdr = NLMsgHdr(message)
- print self.CommandToString(hdr.type, message)
-
- def 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, "")
-
- def DumpLinks(self):
- ifinfomsg = IfinfoMsg((0, 0, 0, 0, 0, 0))
- return self._Dump(RTM_GETLINK, ifinfomsg, IfinfoMsg, "")
-
- def _Address(self, version, command, addr, prefixlen, flags, scope, ifindex):
- """Adds or deletes an IP address."""
- family = self._AddressFamily(version)
- ifaddrmsg = IfAddrMsg((family, prefixlen, flags, scope, ifindex)).Pack()
- ifaddrmsg += self._NlAttrIPAddress(IFA_ADDRESS, family, addr)
- if version == 4:
- ifaddrmsg += self._NlAttrIPAddress(IFA_LOCAL, family, addr)
- self._SendNlRequest(command, ifaddrmsg)
-
- def AddAddress(self, address, prefixlen, ifindex):
- self._Address(6 if ":" in address else 4,
- RTM_NEWADDR, address, prefixlen,
- IFA_F_PERMANENT, RT_SCOPE_UNIVERSE, ifindex)
-
- def DelAddress(self, address, prefixlen, ifindex):
- self._Address(6 if ":" in address else 4,
- RTM_DELADDR, address, prefixlen, 0, 0, ifindex)
-
- def GetAddress(self, address, ifindex=0):
- """Returns an ifaddrmsg for the requested address."""
- if ":" not in address:
- # The address is likely an IPv4 address. RTM_GETADDR without the
- # NLM_F_DUMP flag is not supported by the kernel. We do not currently
- # implement parsing dump results.
- raise NotImplementedError("IPv4 RTM_GETADDR not implemented.")
- self._Address(6, RTM_GETADDR, address, 0, 0, RT_SCOPE_UNIVERSE, ifindex)
- return self._GetMsg(IfAddrMsg)
-
- def _Route(self, version, command, table, dest, prefixlen, nexthop, dev,
- mark, uid):
- """Adds, deletes, or queries a route."""
- family = self._AddressFamily(version)
- scope = RT_SCOPE_UNIVERSE if nexthop else RT_SCOPE_LINK
- rtmsg = RTMsg((family, prefixlen, 0, 0, RT_TABLE_UNSPEC,
- RTPROT_STATIC, scope, RTN_UNICAST, 0)).Pack()
- if command == RTM_NEWROUTE and not table:
- # Don't allow setting routes in table 0, since its behaviour is confusing
- # and differs between IPv4 and IPv6.
- raise ValueError("Cowardly refusing to add a route to table 0")
- if table:
- rtmsg += self._NlAttrU32(FRA_TABLE, table)
- if dest != "default": # The default is the default route.
- rtmsg += self._NlAttrIPAddress(RTA_DST, family, dest)
- if nexthop:
- rtmsg += self._NlAttrIPAddress(RTA_GATEWAY, family, nexthop)
- if dev:
- rtmsg += self._NlAttrU32(RTA_OIF, dev)
- if mark is not None:
- rtmsg += self._NlAttrU32(RTA_MARK, mark)
- if uid is not None:
- rtmsg += self._NlAttrU32(RTA_UID, uid)
- self._SendNlRequest(command, rtmsg)
-
- def AddRoute(self, version, table, dest, prefixlen, nexthop, dev):
- self._Route(version, RTM_NEWROUTE, table, dest, prefixlen, nexthop, dev,
- None, None)
-
- def DelRoute(self, version, table, dest, prefixlen, nexthop, dev):
- self._Route(version, RTM_DELROUTE, table, dest, prefixlen, nexthop, dev,
- None, None)
-
- def GetRoutes(self, dest, oif, mark, uid):
- version = 6 if ":" in dest else 4
- prefixlen = {4: 32, 6: 128}[version]
- self._Route(version, RTM_GETROUTE, 0, dest, prefixlen, None, oif, mark, uid)
- data = self._Recv()
- # The response will either be an error or a list of routes.
- if NLMsgHdr(data).type == NLMSG_ERROR:
- self._ParseAck(data)
- routes = self._GetMsgList(RTMsg, data, False)
- return routes
-
- 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 and lladdr:
- lladdr = lladdr.split(":")
- if len(lladdr) != 6:
- raise ValueError("Invalid lladdr %s" % ":".join(lladdr))
- lladdr = "".join(chr(int(hexbyte, 16)) for hexbyte in lladdr)
-
- ndmsg = NdMsg((family, dev, state, 0, RTN_UNICAST)).Pack()
- ndmsg += self._NlAttrIPAddress(NDA_DST, family, addr)
- if is_add and lladdr:
- ndmsg += self._NlAttr(NDA_LLADDR, lladdr)
- command = RTM_NEWNEIGH if is_add else RTM_DELNEIGH
- 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()
- iproute.DEBUG = True
- iproute.DumpRules(6)
- iproute.DumpLinks()
- print iproute.GetRoutes("2001:4860:4860::8888", 0, 0, None)
diff --git a/tests/net_test/multinetwork_base.py b/tests/net_test/multinetwork_base.py
deleted file mode 100644
index 31fcc4c..0000000
--- a/tests/net_test/multinetwork_base.py
+++ /dev/null
@@ -1,642 +0,0 @@
-#!/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.
-
-"""Base module for multinetwork tests."""
-
-import errno
-import fcntl
-import os
-import posix
-import random
-import re
-from socket import * # pylint: disable=wildcard-import
-import struct
-
-from scapy import all as scapy
-
-import csocket
-import cstruct
-import iproute
-import net_test
-
-
-IFF_TUN = 1
-IFF_TAP = 2
-IFF_NO_PI = 0x1000
-TUNSETIFF = 0x400454ca
-
-SO_BINDTODEVICE = 25
-
-# Setsockopt values.
-IP_UNICAST_IF = 50
-IPV6_MULTICAST_IF = 17
-IPV6_UNICAST_IF = 76
-
-# Cmsg values.
-IP_TTL = 2
-IP_PKTINFO = 8
-IPV6_2292PKTOPTIONS = 6
-IPV6_FLOWINFO = 11
-IPV6_PKTINFO = 50
-IPV6_HOPLIMIT = 52 # Different from IPV6_UNICAST_HOPS, this is cmsg only.
-
-# Data structures.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-InPktinfo = cstruct.Struct("in_pktinfo", "@i4s4s", "ifindex spec_dst addr")
-In6Pktinfo = cstruct.Struct("in6_pktinfo", "@16si", "addr ifindex")
-
-
-def HaveUidRouting():
- """Checks whether the kernel supports UID routing."""
- # Create a rule with the UID range selector. If the kernel doesn't understand
- # the selector, it will create a rule with no selectors.
- try:
- iproute.IPRoute().UidRangeRule(6, True, 1000, 2000, 100, 10000)
- except IOError:
- return False
-
- # Dump all the rules. If we find a rule using the UID range selector, then the
- # kernel supports UID range routing.
- rules = iproute.IPRoute().DumpRules(6)
- result = any("FRA_UID_START" in attrs for rule, attrs in rules)
-
- # Delete the rule.
- iproute.IPRoute().UidRangeRule(6, False, 1000, 2000, 100, 10000)
- return result
-
-AUTOCONF_TABLE_SYSCTL = "/proc/sys/net/ipv6/conf/default/accept_ra_rt_table"
-
-HAVE_AUTOCONF_TABLE = os.path.isfile(AUTOCONF_TABLE_SYSCTL)
-HAVE_UID_ROUTING = HaveUidRouting()
-
-
-class UnexpectedPacketError(AssertionError):
- pass
-
-
-def MakePktInfo(version, addr, ifindex):
- family = {4: AF_INET, 6: AF_INET6}[version]
- if not addr:
- addr = {4: "0.0.0.0", 6: "::"}[version]
- if addr:
- addr = inet_pton(family, addr)
- if version == 6:
- return In6Pktinfo((addr, ifindex)).Pack()
- else:
- return InPktinfo((ifindex, addr, "\x00" * 4)).Pack()
-
-
-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
- tear a multi-network environment using multiple tun interfaces. The
- environment is designed to be similar to a real Android device in terms of
- rules and routes, and supports IPv4 and IPv6.
-
- Tests wishing to use this environment should inherit from this class and
- ensure that any setupClass, tearDownClass, setUp, and tearDown methods they
- implement also call the superclass versions.
- """
-
- # Must be between 1 and 256, since we put them in MAC addresses and IIDs.
- NETIDS = [100, 150, 200, 250]
-
- # Stores sysctl values to write back when the test completes.
- saved_sysctls = {}
-
- # Wether to output setup commands.
- DEBUG = False
-
- # The size of our UID ranges.
- UID_RANGE_SIZE = 1000
-
- # Rule priorities.
- PRIORITY_UID = 100
- PRIORITY_OIF = 200
- PRIORITY_FWMARK = 300
- PRIORITY_IIF = 400
- PRIORITY_DEFAULT = 999
- PRIORITY_UNREACHABLE = 1000
-
- # For convenience.
- IPV4_ADDR = net_test.IPV4_ADDR
- IPV6_ADDR = net_test.IPV6_ADDR
- IPV4_PING = net_test.IPV4_PING
- IPV6_PING = net_test.IPV6_PING
-
- @classmethod
- def UidRangeForNetid(cls, netid):
- return (
- cls.UID_RANGE_SIZE * netid,
- cls.UID_RANGE_SIZE * (netid + 1) - 1
- )
-
- @classmethod
- def UidForNetid(cls, netid):
- return random.randint(*cls.UidRangeForNetid(netid))
-
- @classmethod
- def _TableForNetid(cls, netid):
- if cls.AUTOCONF_TABLE_OFFSET and netid in cls.ifindices:
- return cls.ifindices[netid] + (-cls.AUTOCONF_TABLE_OFFSET)
- else:
- return netid
-
- @staticmethod
- def GetInterfaceName(netid):
- return "nettest%d" % netid
-
- @staticmethod
- def RouterMacAddress(netid):
- return "02:00:00:00:%02x:00" % netid
-
- @staticmethod
- def MyMacAddress(netid):
- return "02:00:00:00:%02x:01" % netid
-
- @staticmethod
- def _RouterAddress(netid, version):
- if version == 6:
- return "fe80::%02x00" % netid
- elif version == 4:
- return "10.0.%d.1" % netid
- else:
- raise ValueError("Don't support IPv%s" % version)
-
- @classmethod
- def _MyIPv4Address(cls, netid):
- return "10.0.%d.2" % netid
-
- @classmethod
- def _MyIPv6Address(cls, netid):
- return net_test.GetLinkAddress(cls.GetInterfaceName(netid), False)
-
- @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
-
- @staticmethod
- def GetRandomDestination(prefix):
- if "." in prefix:
- return prefix + "%d.%d" % (random.randint(0, 31), random.randint(0, 255))
- else:
- return prefix + "%x:%x" % (random.randint(0, 65535),
- random.randint(0, 65535))
-
- def GetProtocolFamily(self, version):
- return {4: AF_INET, 6: AF_INET6}[version]
-
- @classmethod
- def CreateTunInterface(cls, netid):
- iface = cls.GetInterfaceName(netid)
- f = open("/dev/net/tun", "r+b")
- ifr = struct.pack("16sH", iface, IFF_TAP | IFF_NO_PI)
- ifr += "\x00" * (40 - len(ifr))
- fcntl.ioctl(f, TUNSETIFF, ifr)
- # Give ourselves a predictable MAC address.
- 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, reachabletime=0):
- validity = 300 # seconds
- macaddr = cls.RouterMacAddress(netid)
- lladdr = cls._RouterAddress(netid, 6)
-
- if retranstimer is None:
- # If no retrans timer was specified, pick one that's as long as the
- # router lifetime. This ensures that no spurious ND retransmits
- # will interfere with test expectations.
- retranstimer = validity
-
- # We don't want any routes in the main table. If the kernel doesn't support
- # putting RA routes into per-interface tables, configure routing manually.
- routerlifetime = validity if HAVE_AUTOCONF_TABLE else 0
-
- ra = (scapy.Ether(src=macaddr, dst="33:33:00:00:00:01") /
- scapy.IPv6(src=lladdr, hlim=255) /
- scapy.ICMPv6ND_RA(reachabletime=reachabletime,
- retranstimer=retranstimer,
- routerlifetime=routerlifetime) /
- scapy.ICMPv6NDOptSrcLLAddr(lladdr=macaddr) /
- scapy.ICMPv6NDOptPrefixInfo(prefix=cls.IPv6Prefix(netid),
- prefixlen=64,
- L=1, A=1,
- validlifetime=validity,
- preferredlifetime=validity))
- posix.write(cls.tuns[netid].fileno(), str(ra))
-
- @classmethod
- def _RunSetupCommands(cls, netid, is_add):
- for version in [4, 6]:
- # Find out how to configure things.
- iface = cls.GetInterfaceName(netid)
- ifindex = cls.ifindices[netid]
- macaddr = cls.RouterMacAddress(netid)
- router = cls._RouterAddress(netid, version)
- table = cls._TableForNetid(netid)
-
- # Set up routing rules.
- if HAVE_UID_ROUTING:
- start, end = cls.UidRangeForNetid(netid)
- cls.iproute.UidRangeRule(version, is_add, start, end, table,
- cls.PRIORITY_UID)
- cls.iproute.OifRule(version, is_add, iface, table, cls.PRIORITY_OIF)
- cls.iproute.FwmarkRule(version, is_add, netid, table,
- cls.PRIORITY_FWMARK)
-
- # Configure routing and addressing.
- #
- # IPv6 uses autoconf for everything, except if per-device autoconf routing
- # tables are not supported, in which case the default route (only) is
- # configured manually. For IPv4 we have to manually configure addresses,
- # routes, and neighbour cache entries (since we don't reply to ARP or ND).
- #
- # Since deleting addresses also causes routes to be deleted, we need to
- # be careful with ordering or the delete commands will fail with ENOENT.
- do_routing = (version == 4 or cls.AUTOCONF_TABLE_OFFSET is None)
- if is_add:
- if version == 4:
- cls.iproute.AddAddress(cls._MyIPv4Address(netid), 24, ifindex)
- cls.iproute.AddNeighbour(version, router, macaddr, ifindex)
- if do_routing:
- cls.iproute.AddRoute(version, table, "default", 0, router, ifindex)
- if version == 6:
- cls.iproute.AddRoute(version, table,
- cls.IPv6Prefix(netid), 64, None, ifindex)
- else:
- if do_routing:
- cls.iproute.DelRoute(version, table, "default", 0, router, ifindex)
- if version == 6:
- cls.iproute.DelRoute(version, table,
- cls.IPv6Prefix(netid), 64, None, ifindex)
- if version == 4:
- cls.iproute.DelNeighbour(version, router, macaddr, ifindex)
- cls.iproute.DelAddress(cls._MyIPv4Address(netid), 24, ifindex)
-
- @classmethod
- def SetDefaultNetwork(cls, netid):
- table = cls._TableForNetid(netid) if netid else None
- for version in [4, 6]:
- is_add = table is not None
- cls.iproute.DefaultRule(version, is_add, table, cls.PRIORITY_DEFAULT)
-
- @classmethod
- def ClearDefaultNetwork(cls):
- cls.SetDefaultNetwork(None)
-
- @classmethod
- def GetSysctl(cls, sysctl):
- return open(sysctl, "r").read()
-
- @classmethod
- def SetSysctl(cls, sysctl, value):
- # Only save each sysctl value the first time we set it. This is so we can
- # set it to arbitrary values multiple times and still write it back
- # correctly at the end.
- if sysctl not in cls.saved_sysctls:
- cls.saved_sysctls[sysctl] = cls.GetSysctl(sysctl)
- 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:
- open(sysctl, "w").write(value)
- except IOError:
- pass
-
- @classmethod
- def _ICMPRatelimitFilename(cls, version):
- return "/proc/sys/net/" + {4: "ipv4/icmp_ratelimit",
- 6: "ipv6/icmp/ratelimit"}[version]
-
- @classmethod
- def _SetICMPRatelimit(cls, version, limit):
- cls.SetSysctl(cls._ICMPRatelimitFilename(version), limit)
-
- @classmethod
- def setUpClass(cls):
- # This is per-class setup instead of per-testcase setup because shelling out
- # to ip and iptables is slow, and because routing configuration doesn't
- # change during the test.
- cls.iproute = iproute.IPRoute()
- cls.tuns = {}
- cls.ifindices = {}
- if HAVE_AUTOCONF_TABLE:
- cls.SetSysctl(AUTOCONF_TABLE_SYSCTL, -1000)
- cls.AUTOCONF_TABLE_OFFSET = -1000
- else:
- cls.AUTOCONF_TABLE_OFFSET = None
-
- # Disable ICMP rate limits. These will be restored by _RestoreSysctls.
- for version in [4, 6]:
- cls._SetICMPRatelimit(version, 0)
-
- for netid in cls.NETIDS:
- cls.tuns[netid] = cls.CreateTunInterface(netid)
- iface = cls.GetInterfaceName(netid)
- cls.ifindices[netid] = net_test.GetInterfaceIndex(iface)
-
- cls.SendRA(netid)
- cls._RunSetupCommands(netid, True)
-
- for version in [4, 6]:
- cls.iproute.UnreachableRule(version, True, 1000)
-
- # Uncomment to look around at interface and rule configuration while
- # running in the background. (Once the test finishes running, all the
- # interfaces and rules are gone.)
- # time.sleep(30)
-
- @classmethod
- def tearDownClass(cls):
- for version in [4, 6]:
- try:
- cls.iproute.UnreachableRule(version, False, 1000)
- except IOError:
- pass
-
- for netid in cls.tuns:
- cls._RunSetupCommands(netid, False)
- cls.tuns[netid].close()
- cls._RestoreSysctls()
-
- def setUp(self):
- self.ClearTunQueues()
-
- def SetSocketMark(self, s, netid):
- if netid is None:
- netid = 0
- s.setsockopt(SOL_SOCKET, net_test.SO_MARK, netid)
-
- def GetSocketMark(self, s):
- return s.getsockopt(SOL_SOCKET, net_test.SO_MARK)
-
- def ClearSocketMark(self, s):
- self.SetSocketMark(s, 0)
-
- def BindToDevice(self, s, iface):
- if not iface:
- iface = ""
- s.setsockopt(SOL_SOCKET, SO_BINDTODEVICE, iface)
-
- def SetUnicastInterface(self, s, ifindex):
- # Otherwise, Python thinks it's a 1-byte option.
- ifindex = struct.pack("!I", ifindex)
-
- # Always set the IPv4 interface, because it will be used even on IPv6
- # sockets if the destination address is a mapped address.
- s.setsockopt(net_test.SOL_IP, IP_UNICAST_IF, ifindex)
- if s.family == AF_INET6:
- s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_IF, ifindex)
-
- def GetRemoteAddress(self, 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("Can't change UID on an existing socket")
- elif mode == "mark":
- self.SetSocketMark(s, netid)
- elif mode == "oif":
- iface = self.GetInterfaceName(netid) if netid else ""
- self.BindToDevice(s, iface)
- elif mode == "ucast_oif":
- self.SetUnicastInterface(s, self.ifindices.get(netid, 0))
- else:
- raise ValueError("Unknown interface selection mode %s" % mode)
-
- def BuildSocket(self, version, constructor, netid, routing_mode):
- 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
-
- def SendOnNetid(self, version, s, dstaddr, dstport, netid, payload, cmsgs):
- if netid is not None:
- pktinfo = MakePktInfo(version, None, self.ifindices[netid])
- cmsg_level, cmsg_name = {
- 4: (net_test.SOL_IP, IP_PKTINFO),
- 6: (net_test.SOL_IPV6, IPV6_PKTINFO)}[version]
- cmsgs.append((cmsg_level, cmsg_name, pktinfo))
- csocket.Sendmsg(s, (dstaddr, dstport), payload, cmsgs, csocket.MSG_CONFIRM)
-
- def ReceiveEtherPacketOn(self, netid, packet):
- posix.write(self.tuns[netid].fileno(), str(packet))
-
- def ReceivePacketOn(self, netid, ip_packet):
- routermac = self.RouterMacAddress(netid)
- mymac = self.MyMacAddress(netid)
- packet = scapy.Ether(src=routermac, dst=mymac) / ip_packet
- self.ReceiveEtherPacketOn(netid, packet)
-
- def ReadAllPacketsOn(self, netid, include_multicast=False):
- packets = []
- while True:
- try:
- packet = posix.read(self.tuns[netid].fileno(), 4096)
- if not packet:
- break
- ether = scapy.Ether(packet)
- # Multicast frames are frames where the first byte of the destination
- # MAC address has 1 in the least-significant bit.
- if include_multicast or not int(ether.dst.split(":")[0], 16) & 0x1:
- packets.append(ether.payload)
- except OSError, e:
- # EAGAIN means there are no more packets waiting.
- if re.match(e.message, os.strerror(errno.EAGAIN)):
- break
- # Anything else is unexpected.
- else:
- raise e
- return packets
-
- def ClearTunQueues(self):
- # Keep reading packets on all netids until we get no packets on any of them.
- waiting = None
- while waiting != 0:
- waiting = sum(len(self.ReadAllPacketsOn(netid)) for netid in self.NETIDS)
-
- def assertPacketMatches(self, expected, actual):
- # The expected packet is just a rough sketch of the packet we expect to
- # receive. For example, it doesn't contain fields we can't predict, such as
- # initial TCP sequence numbers, or that depend on the host implementation
- # and settings, such as TCP options. To check whether the packet matches
- # what we expect, instead of just checking all the known fields one by one,
- # we blank out fields in the actual packet and then compare the whole
- # packets to each other as strings. Because we modify the actual packet,
- # make a copy here.
- actual = actual.copy()
-
- # Blank out IPv4 fields that we can't predict, like ID and the DF bit.
- actualip = actual.getlayer("IP")
- expectedip = expected.getlayer("IP")
- if actualip and expectedip:
- actualip.id = expectedip.id
- 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")
- expectedudp = expected.getlayer("UDP")
- if actualudp and expectedudp:
- if expectedudp.sport is None:
- actualudp.sport = None
- actualudp.chksum = None
-
- # Since the TCP code below messes with options, recalculate the length.
- if actualip:
- actualip.len = None
- if actualipv6:
- actualipv6.plen = None
-
- # Blank out TCP fields that we can't predict.
- actualtcp = actual.getlayer("TCP")
- expectedtcp = expected.getlayer("TCP")
- if actualtcp and expectedtcp:
- actualtcp.dataofs = expectedtcp.dataofs
- actualtcp.options = expectedtcp.options
- actualtcp.window = expectedtcp.window
- if expectedtcp.sport is None:
- actualtcp.sport = None
- if expectedtcp.seq is None:
- actualtcp.seq = None
- if expectedtcp.ack is None:
- actualtcp.ack = None
- actualtcp.chksum = None
-
- # Serialize the packet so that expected packet fields that are only set when
- # a packet is serialized e.g., the checksum) are filled in.
- expected_real = expected.__class__(str(expected))
- actual_real = actual.__class__(str(actual))
- # repr() can be expensive. Call it only if the test is going to fail and we
- # want to see the error.
- if expected_real != actual_real:
- self.assertEquals(repr(expected_real), repr(actual_real))
-
- def PacketMatches(self, expected, actual):
- try:
- self.assertPacketMatches(expected, actual)
- return True
- except AssertionError:
- return False
-
- def ExpectNoPacketsOn(self, netid, msg):
- packets = self.ReadAllPacketsOn(netid)
- if packets:
- firstpacket = repr(packets[0])
- else:
- firstpacket = ""
- self.assertFalse(packets, msg + ": unexpected packet: " + firstpacket)
-
- def ExpectPacketOn(self, netid, msg, expected):
- # To avoid confusion due to lots of ICMPv6 ND going on all the time, drop
- # multicast packets unless the packet we expect to see is a multicast
- # packet. For now the only tests that use this are IPv6.
- ipv6 = expected.getlayer("IPv6")
- if ipv6 and ipv6.dst.startswith("ff"):
- include_multicast = True
- else:
- include_multicast = False
-
- packets = self.ReadAllPacketsOn(netid, include_multicast=include_multicast)
- self.assertTrue(packets, msg + ": received no packets")
-
- # If we receive a packet that matches what we expected, return it.
- for packet in packets:
- if self.PacketMatches(expected, packet):
- return packet
-
- # None of the packets matched. Call assertPacketMatches to output a diff
- # between the expected packet and the last packet we received. In theory,
- # we'd output a diff to the packet that's the best match for what we
- # expected, but this is good enough for now.
- try:
- self.assertPacketMatches(expected, packets[-1])
- except Exception, e:
- raise UnexpectedPacketError(
- "%s: diff with last packet:\n%s" % (msg, e.message))
-
- def Combinations(self, version):
- """Produces a list of combinations to test."""
- combinations = []
-
- # Check packets addressed to the IP addresses of all our interfaces...
- for dest_ip_netid in self.tuns:
- ip_if = self.GetInterfaceName(dest_ip_netid)
- myaddr = self.MyAddress(version, dest_ip_netid)
- remoteaddr = self.GetRemoteAddress(version)
-
- # ... coming in on all our interfaces.
- for netid in self.tuns:
- iif = self.GetInterfaceName(netid)
- combinations.append((netid, iif, ip_if, myaddr, remoteaddr))
-
- return combinations
-
- def _FormatMessage(self, iif, ip_if, extra, desc, reply_desc):
- msg = "Receiving %s on %s to %s IP, %s" % (desc, iif, ip_if, extra)
- if reply_desc:
- msg += ": Expecting %s on %s" % (reply_desc, iif)
- else:
- msg += ": Expecting no packets on %s" % iif
- return msg
-
- def _ReceiveAndExpectResponse(self, netid, packet, reply, msg):
- self.ReceivePacketOn(netid, packet)
- if reply:
- return self.ExpectPacketOn(netid, msg, reply)
- else:
- self.ExpectNoPacketsOn(netid, msg)
- return None
diff --git a/tests/net_test/multinetwork_test.py b/tests/net_test/multinetwork_test.py
deleted file mode 100755
index 660fdf6..0000000
--- a/tests/net_test/multinetwork_test.py
+++ /dev/null
@@ -1,926 +0,0 @@
-#!/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 errno
-import os
-import random
-from socket import * # pylint: disable=wildcard-import
-import struct
-import time # pylint: disable=unused-import
-import unittest
-
-from scapy import all as scapy
-
-import iproute
-import multinetwork_base
-import net_test
-import packets
-
-# For brevity.
-UDP_PAYLOAD = net_test.UDP_PAYLOAD
-
-IPV6_FLOWINFO = 11
-
-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"
-
-# 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)
-
-
-class ConfigurationError(AssertionError):
- pass
-
-
-class InboundMarkingTest(multinetwork_base.MultiNetworkBaseTest):
-
- @classmethod
- def _SetInboundMarking(cls, netid, is_add):
- for version in [4, 6]:
- # Run iptables to set up incoming packet marking.
- iface = cls.GetInterfaceName(netid)
- add_del = "-A" if is_add else "-D"
- iptables = {4: "iptables", 6: "ip6tables"}[version]
- args = "%s %s INPUT -t mangle -i %s -j MARK --set-mark %d" % (
- iptables, add_del, iface, netid)
- iptables = "/sbin/" + iptables
- ret = os.spawnvp(os.P_WAIT, iptables, args.split(" "))
- if ret:
- raise ConfigurationError("Setup command failed: %s" % args)
-
- @classmethod
- def setUpClass(cls):
- super(InboundMarkingTest, cls).setUpClass()
- for netid in cls.tuns:
- cls._SetInboundMarking(netid, True)
-
- @classmethod
- def tearDownClass(cls):
- for netid in cls.tuns:
- cls._SetInboundMarking(netid, False)
- super(InboundMarkingTest, cls).tearDownClass()
-
- @classmethod
- def SetMarkReflectSysctls(cls, value):
- cls.SetSysctl(IPV4_MARK_REFLECT_SYSCTL, value)
- try:
- cls.SetSysctl(IPV6_MARK_REFLECT_SYSCTL, value)
- except IOError:
- # This does not exist if we use the version of the patch that uses a
- # common sysctl for IPv4 and IPv6.
- pass
-
-
-class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
-
- # How many times to run outgoing packet tests.
- ITERATIONS = 5
-
- def CheckPingPacket(self, version, netid, routing_mode, dstaddr, packet):
- s = self.BuildSocket(version, net_test.PingSocket, netid, routing_mode)
-
- myaddr = self.MyAddress(version, netid)
- s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
- s.bind((myaddr, packets.PING_IDENT))
- net_test.SetSocketTos(s, packets.PING_TOS)
-
- desc, expected = packets.ICMPEcho(version, myaddr, dstaddr)
- msg = "IPv%d ping: expected %s on %s" % (
- version, desc, self.GetInterfaceName(netid))
-
- s.sendto(packet + packets.PING_PAYLOAD, (dstaddr, 19321))
-
- self.ExpectPacketOn(netid, msg, expected)
-
- def CheckTCPSYNPacket(self, version, netid, routing_mode, dstaddr):
- s = self.BuildSocket(version, net_test.TCPSocket, netid, routing_mode)
-
- if version == 6 and dstaddr.startswith("::ffff"):
- version = 4
- myaddr = self.MyAddress(version, netid)
- desc, expected = packets.SYN(53, version, myaddr, dstaddr,
- sport=None, seq=None)
-
- # Non-blocking TCP connects always return EINPROGRESS.
- self.assertRaisesErrno(errno.EINPROGRESS, s.connect, (dstaddr, 53))
- msg = "IPv%s TCP connect: expected %s on %s" % (
- version, desc, self.GetInterfaceName(netid))
- self.ExpectPacketOn(netid, msg, expected)
- s.close()
-
- def CheckUDPPacket(self, version, netid, routing_mode, dstaddr):
- s = self.BuildSocket(version, net_test.UDPSocket, netid, routing_mode)
-
- if version == 6 and dstaddr.startswith("::ffff"):
- version = 4
- myaddr = self.MyAddress(version, netid)
- desc, expected = packets.UDP(version, myaddr, dstaddr, sport=None)
- msg = "IPv%s UDP %%s: expected %s on %s" % (
- version, desc, self.GetInterfaceName(netid))
-
- s.sendto(UDP_PAYLOAD, (dstaddr, 53))
- self.ExpectPacketOn(netid, msg % "sendto", expected)
-
- # IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
- if routing_mode != "ucast_oif":
- s.connect((dstaddr, 53))
- s.send(UDP_PAYLOAD)
- self.ExpectPacketOn(netid, msg % "connect/send", expected)
- s.close()
-
- def CheckRawGrePacket(self, version, netid, routing_mode, dstaddr):
- s = self.BuildSocket(version, net_test.RawGRESocket, netid, routing_mode)
-
- 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])
-
- 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.
- packet = struct.pack("!i", ethertype) + inner
- myaddr = self.MyAddress(version, netid)
-
- s.sendto(packet, (dstaddr, IPPROTO_GRE))
- 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)
-
- def CheckOutgoingPackets(self, routing_mode):
- v4addr = self.IPV4_ADDR
- v6addr = self.IPV6_ADDR
- v4mapped = "::ffff:" + v4addr
-
- for _ in xrange(self.ITERATIONS):
- for netid in self.tuns:
-
- self.CheckPingPacket(4, netid, routing_mode, v4addr, self.IPV4_PING)
- # Kernel bug.
- if routing_mode != "oif":
- self.CheckPingPacket(6, netid, routing_mode, v6addr, self.IPV6_PING)
-
- # IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
- if routing_mode != "ucast_oif":
- self.CheckTCPSYNPacket(4, netid, routing_mode, v4addr)
- self.CheckTCPSYNPacket(6, netid, routing_mode, v6addr)
- self.CheckTCPSYNPacket(6, netid, routing_mode, v4mapped)
-
- self.CheckUDPPacket(4, netid, routing_mode, v4addr)
- self.CheckUDPPacket(6, netid, routing_mode, v6addr)
- self.CheckUDPPacket(6, netid, routing_mode, v4mapped)
-
- # Creating raw sockets on non-root UIDs requires properly setting
- # capabilities, which is hard to do from Python.
- # IP_UNICAST_IF is not supported on raw sockets.
- if routing_mode not in ["uid", "ucast_oif"]:
- self.CheckRawGrePacket(4, netid, routing_mode, v4addr)
- self.CheckRawGrePacket(6, netid, routing_mode, v6addr)
-
- def testMarkRouting(self):
- """Checks that socket marking selects the right outgoing interface."""
- self.CheckOutgoingPackets("mark")
-
- @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
- def testUidRouting(self):
- """Checks that UID routing selects the right outgoing interface."""
- self.CheckOutgoingPackets("uid")
-
- def testOifRouting(self):
- """Checks that oif routing selects the right outgoing interface."""
- self.CheckOutgoingPackets("oif")
-
- @unittest.skipUnless(HAVE_UNICAST_IF, "no support for UNICAST_IF")
- def testUcastOifRouting(self):
- """Checks that ucast oif routing selects the right outgoing interface."""
- self.CheckOutgoingPackets("ucast_oif")
-
- def CheckRemarking(self, version, use_connect):
- # Remarking or resetting UNICAST_IF on connected sockets does not work.
- if use_connect:
- modes = ["oif"]
- else:
- modes = ["mark", "oif"]
- if HAVE_UNICAST_IF:
- modes += ["ucast_oif"]
-
- for mode in modes:
- s = net_test.UDPSocket(self.GetProtocolFamily(version))
-
- # Figure out what packets to expect.
- unspec = {4: "0.0.0.0", 6: "::"}[version]
- 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)
-
- # If we're testing connected sockets, connect the socket on the first
- # netid now.
- if use_connect:
- netid = self.tuns.keys()[0]
- self.SelectInterface(s, netid, mode)
- s.connect((dstaddr, 53))
- expected.src = self.MyAddress(version, netid)
-
- # For each netid, select that network without closing the socket, and
- # check that the packets sent on that socket go out on the right network.
- for netid in self.tuns:
- self.SelectInterface(s, netid, mode)
- if not use_connect:
- expected.src = self.MyAddress(version, netid)
- s.sendto(UDP_PAYLOAD, (dstaddr, 53))
- connected_str = "Connected" if use_connect else "Unconnected"
- msg = "%s UDPv%d socket remarked using %s: expecting %s on %s" % (
- connected_str, version, mode, desc, self.GetInterfaceName(netid))
- self.ExpectPacketOn(netid, msg, expected)
- self.SelectInterface(s, None, mode)
-
- def testIPv4Remarking(self):
- """Checks that updating the mark on an IPv4 socket changes routing."""
- self.CheckRemarking(4, False)
- self.CheckRemarking(4, True)
-
- def testIPv6Remarking(self):
- """Checks that updating the mark on an IPv6 socket changes routing."""
- self.CheckRemarking(6, False)
- self.CheckRemarking(6, True)
-
- def testIPv6StickyPktinfo(self):
- for _ in xrange(self.ITERATIONS):
- for netid in self.tuns:
- s = net_test.UDPSocket(AF_INET6)
-
- # Set a flowlabel.
- net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xdead)
- s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_FLOWINFO_SEND, 1)
-
- # Set some destination options.
- nonce = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
- dstopts = "".join([
- "\x11\x02", # Next header=UDP, 24 bytes of options.
- "\x01\x06", "\x00" * 6, # PadN, 6 bytes of padding.
- "\x8b\x0c", # ILNP nonce, 12 bytes.
- nonce
- ])
- s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, dstopts)
- s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_HOPS, 255)
-
- pktinfo = multinetwork_base.MakePktInfo(6, None, self.ifindices[netid])
-
- # Set the sticky pktinfo option.
- s.setsockopt(net_test.SOL_IPV6, IPV6_PKTINFO, pktinfo)
-
- # Specify the flowlabel in the destination address.
- s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 53, 0xdead, 0))
-
- sport = s.getsockname()[1]
- srcaddr = self.MyAddress(6, netid)
- expected = (scapy.IPv6(src=srcaddr, dst=net_test.IPV6_ADDR,
- fl=0xdead, hlim=255) /
- scapy.IPv6ExtHdrDestOpt(
- options=[scapy.PadN(optdata="\x00\x00\x00\x00\x00\x00"),
- scapy.HBHOptUnknown(otype=0x8b,
- optdata=nonce)]) /
- scapy.UDP(sport=sport, dport=53) /
- UDP_PAYLOAD)
- msg = "IPv6 UDP using sticky pktinfo: expected UDP packet on %s" % (
- self.GetInterfaceName(netid))
- self.ExpectPacketOn(netid, msg, expected)
-
- def CheckPktinfoRouting(self, version):
- for _ in xrange(self.ITERATIONS):
- for netid in self.tuns:
- family = self.GetProtocolFamily(version)
- s = net_test.UDPSocket(family)
-
- if version == 6:
- # Create a flowlabel so we can use it.
- net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xbeef)
-
- # Specify some arbitrary options.
- cmsgs = [
- (net_test.SOL_IPV6, IPV6_HOPLIMIT, 39),
- (net_test.SOL_IPV6, IPV6_TCLASS, 0x83),
- (net_test.SOL_IPV6, IPV6_FLOWINFO, int(htonl(0xbeef))),
- ]
- else:
- # Support for setting IPv4 TOS and TTL via cmsg only appeared in 3.13.
- cmsgs = []
- s.setsockopt(net_test.SOL_IP, IP_TTL, 39)
- s.setsockopt(net_test.SOL_IP, IP_TOS, 0x83)
-
- dstaddr = self.GetRemoteAddress(version)
- self.SendOnNetid(version, s, dstaddr, 53, netid, UDP_PAYLOAD, cmsgs)
-
- sport = s.getsockname()[1]
- srcaddr = self.MyAddress(version, netid)
-
- desc, expected = packets.UDPWithOptions(version, srcaddr, dstaddr,
- sport=sport)
-
- msg = "IPv%d UDP using pktinfo routing: expected %s on %s" % (
- version, desc, self.GetInterfaceName(netid))
- self.ExpectPacketOn(netid, msg, expected)
-
- def testIPv4PktinfoRouting(self):
- self.CheckPktinfoRouting(4)
-
- def testIPv6PktinfoRouting(self):
- self.CheckPktinfoRouting(6)
-
-
-class MarkTest(InboundMarkingTest):
-
- def CheckReflection(self, version, gen_packet, gen_reply):
- """Checks that replies go out on the same interface as the original.
-
- For each combination:
- - Calls gen_packet to generate a packet to that IP address.
- - Writes the packet generated by gen_packet on the given tun
- interface, causing the kernel to receive it.
- - Checks that the kernel's reply matches the packet generated by
- gen_reply.
-
- Args:
- version: An integer, 4 or 6.
- gen_packet: A function taking an IP version (an integer), a source
- address and a destination address (strings), and returning a scapy
- packet.
- gen_reply: A function taking the same arguments as gen_packet,
- plus a scapy packet, and returning a scapy packet.
- """
- for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
- # Generate a test packet.
- desc, packet = gen_packet(version, remoteaddr, myaddr)
-
- # Test with mark reflection enabled and disabled.
- for reflect in [0, 1]:
- self.SetMarkReflectSysctls(reflect)
- # HACK: IPv6 ping replies always do a routing lookup with the
- # interface the ping came in on. So even if mark reflection is not
- # working, IPv6 ping replies will be properly reflected. Don't
- # fail when that happens.
- if reflect or desc == "ICMPv6 echo":
- reply_desc, reply = gen_reply(version, myaddr, remoteaddr, packet)
- else:
- reply_desc, reply = None, None
-
- msg = self._FormatMessage(iif, ip_if, "reflect=%d" % reflect,
- desc, reply_desc)
- self._ReceiveAndExpectResponse(netid, packet, reply, msg)
-
- def SYNToClosedPort(self, *args):
- return packets.SYN(999, *args)
-
- def testIPv4ICMPErrorsReflectMark(self):
- self.CheckReflection(4, packets.UDP, packets.ICMPPortUnreachable)
-
- def testIPv6ICMPErrorsReflectMark(self):
- self.CheckReflection(6, packets.UDP, packets.ICMPPortUnreachable)
-
- def testIPv4PingRepliesReflectMarkAndTos(self):
- self.CheckReflection(4, packets.ICMPEcho, packets.ICMPReply)
-
- def testIPv6PingRepliesReflectMarkAndTos(self):
- self.CheckReflection(6, packets.ICMPEcho, packets.ICMPReply)
-
- def testIPv4RSTsReflectMark(self):
- self.CheckReflection(4, self.SYNToClosedPort, packets.RST)
-
- def testIPv6RSTsReflectMark(self):
- self.CheckReflection(6, self.SYNToClosedPort, packets.RST)
-
-
-class TCPAcceptTest(InboundMarkingTest):
-
- MODE_BINDTODEVICE = "SO_BINDTODEVICE"
- MODE_INCOMING_MARK = "incoming mark"
- MODE_EXPLICIT_MARK = "explicit mark"
- MODE_UID = "uid"
-
- @classmethod
- def setUpClass(cls):
- super(TCPAcceptTest, cls).setUpClass()
-
- # Open a port so we can observe SYN+ACKs. Since it's a dual-stack socket it
- # will accept both IPv4 and IPv6 connections. We do this here instead of in
- # each test so we can use the same socket every time. That way, if a kernel
- # bug causes incoming packets to mark the listening socket instead of the
- # accepted socket, the test will fail as soon as the next address/interface
- # combination is tried.
- cls.listenport = 1234
- cls.listensocket = net_test.IPv6TCPSocket()
- cls.listensocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
- cls.listensocket.bind(("::", cls.listenport))
- cls.listensocket.listen(100)
-
- def BounceSocket(self, s):
- """Attempts to invalidate a socket's destination cache entry."""
- if s.family == AF_INET:
- tos = s.getsockopt(SOL_IP, IP_TOS)
- s.setsockopt(net_test.SOL_IP, IP_TOS, 53)
- s.setsockopt(net_test.SOL_IP, IP_TOS, tos)
- else:
- # UDP, 8 bytes dstopts; PAD1, 4 bytes padding; 4 bytes zeros.
- pad8 = "".join(["\x11\x00", "\x01\x04", "\x00" * 4])
- s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, pad8)
- s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, "")
-
- def _SetTCPMarkAcceptSysctl(self, value):
- self.SetSysctl(TCP_MARK_ACCEPT_SYSCTL, value)
-
- def CheckTCPConnection(self, mode, listensocket, netid, version,
- myaddr, remoteaddr, packet, reply, msg):
- establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
-
- # Attempt to confuse the kernel.
- self.BounceSocket(listensocket)
-
- self.ReceivePacketOn(netid, establishing_ack)
-
- # If we're using UID routing, the accept() call has to be run as a UID that
- # is routed to the specified netid, because the UID of the socket returned
- # by accept() is the effective UID of the process that calls it. It doesn't
- # need to be the same UID; any UID that selects the same interface will do.
- with net_test.RunAsUid(self.UidForNetid(netid)):
- s, _ = listensocket.accept()
-
- try:
- # Check that data sent on the connection goes out on the right interface.
- 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]
- self.BounceSocket(listensocket)
- self.ReceivePacketOn(netid, ack)
-
- mark = self.GetSocketMark(s)
- finally:
- self.BounceSocket(s)
- s.close()
-
- if mode == self.MODE_INCOMING_MARK:
- self.assertEquals(netid, mark,
- msg + ": Accepted socket: Expected mark %d, got %d" % (
- netid, mark))
- elif mode != self.MODE_EXPLICIT_MARK:
- self.assertEquals(0, self.GetSocketMark(listensocket))
-
- # Check the FIN was sent on the right interface, and ack it. We don't expect
- # this to fail because by the time the connection is established things are
- # 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)
- self.ExpectPacketOn(netid, msg + ": expecting %s after close" % desc, 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)
- if mode != self.MODE_UID:
- self.ExpectPacketOn(netid, msg + ": expecting final ack", finackack)
- else:
- self.ClearTunQueues()
-
- def CheckTCP(self, version, modes):
- """Checks that incoming TCP connections work.
-
- Args:
- version: An integer, 4 or 6.
- modes: A list of modes to excercise.
- """
- for syncookies in [0, 2]:
- for mode in modes:
- for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
- if mode == self.MODE_UID:
- listensocket = self.BuildSocket(6, net_test.TCPSocket, netid, mode)
- listensocket.listen(100)
- else:
- listensocket = self.listensocket
-
- listenport = listensocket.getsockname()[1]
-
- 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)
-
- mark = netid if mode == self.MODE_EXPLICIT_MARK else 0
- self.SetSocketMark(listensocket, mark)
-
- # Generate the packet here instead of in the outer loop, so
- # 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)
-
- if mode:
- reply_desc, reply = packets.SYNACK(version, myaddr, remoteaddr,
- packet)
- else:
- reply_desc, reply = None, None
-
- extra = "mode=%s, syncookies=%d" % (mode, syncookies)
- msg = self._FormatMessage(iif, ip_if, extra, desc, reply_desc)
- reply = self._ReceiveAndExpectResponse(netid, packet, reply, msg)
- if reply:
- self.CheckTCPConnection(mode, listensocket, netid, version, myaddr,
- remoteaddr, packet, reply, msg)
-
- def testBasicTCP(self):
- self.CheckTCP(4, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
- self.CheckTCP(6, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
-
- def testIPv4MarkAccept(self):
- self.CheckTCP(4, [self.MODE_INCOMING_MARK])
-
- def testIPv6MarkAccept(self):
- self.CheckTCP(6, [self.MODE_INCOMING_MARK])
-
- @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
- def testIPv4UidAccept(self):
- self.CheckTCP(4, [self.MODE_UID])
-
- @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
- def testIPv6UidAccept(self):
- self.CheckTCP(6, [self.MODE_UID])
-
- def testIPv6ExplicitMark(self):
- self.CheckTCP(6, [self.MODE_EXPLICIT_MARK])
-
-
-class RATest(multinetwork_base.MultiNetworkBaseTest):
-
- def testDoesNotHaveObsoleteSysctl(self):
- self.assertFalse(os.path.isfile(
- "/proc/sys/net/ipv6/route/autoconf_table_offset"))
-
- @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
- "no support for per-table autoconf")
- def testPurgeDefaultRouters(self):
-
- def CheckIPv6Connectivity(expect_connectivity):
- for netid in self.NETIDS:
- s = net_test.UDPSocket(AF_INET6)
- self.SetSocketMark(s, netid)
- if expect_connectivity:
- self.assertTrue(s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 1234)))
- else:
- self.assertRaisesErrno(errno.ENETUNREACH, s.sendto, UDP_PAYLOAD,
- (net_test.IPV6_ADDR, 1234))
-
- try:
- CheckIPv6Connectivity(True)
- self.SetIPv6SysctlOnAllIfaces("accept_ra", 1)
- self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
- CheckIPv6Connectivity(False)
- finally:
- self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 0)
- for netid in self.NETIDS:
- self.SendRA(netid)
- CheckIPv6Connectivity(True)
-
- def testOnlinkCommunication(self):
- """Checks that on-link communication goes direct and not through routers."""
- for netid in self.tuns:
- # Send a UDP packet to a random on-link destination.
- s = net_test.UDPSocket(AF_INET6)
- iface = self.GetInterfaceName(netid)
- self.BindToDevice(s, iface)
- # dstaddr can never be our address because GetRandomDestination only fills
- # in the lower 32 bits, but our address has 0xff in the byte before that
- # (since it's constructed from the EUI-64 and so has ff:fe in the middle).
- dstaddr = self.GetRandomDestination(self.IPv6Prefix(netid))
- s.sendto(UDP_PAYLOAD, (dstaddr, 53))
-
- # 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)
- 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)
- # 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.
- reply = scapy.Ether(src=tgtmac, dst=mymac) / reply
- self.ReceiveEtherPacketOn(netid, reply)
-
- # 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)
- msg = "After NA response, expecting %s" % desc
- self.ExpectPacketOn(netid, msg, expected)
-
- # This test documents a known issue: routing tables are never deleted.
- @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
- "no support for per-table autoconf")
- def testLeftoverRoutes(self):
- def GetNumRoutes():
- return len(open("/proc/net/ipv6_route").readlines())
-
- num_routes = GetNumRoutes()
- for i in xrange(10, 20):
- try:
- self.tuns[i] = self.CreateTunInterface(i)
- self.SendRA(i)
- self.tuns[i].close()
- finally:
- del self.tuns[i]
- self.assertLess(num_routes, GetNumRoutes())
-
-
-class PMTUTest(InboundMarkingTest):
-
- PAYLOAD_SIZE = 1400
-
- # Socket options to change PMTU behaviour.
- IP_MTU_DISCOVER = 10
- IP_PMTUDISC_DO = 1
- IPV6_DONTFRAG = 62
-
- # Socket options to get the MTU.
- IP_MTU = 14
- IPV6_PATHMTU = 61
-
- def GetSocketMTU(self, version, s):
- if version == 6:
- ip6_mtuinfo = s.getsockopt(net_test.SOL_IPV6, self.IPV6_PATHMTU, 32)
- unused_sockaddr, mtu = struct.unpack("=28sI", ip6_mtuinfo)
- return mtu
- else:
- return s.getsockopt(net_test.SOL_IP, self.IP_MTU)
-
- def DisableFragmentationAndReportErrors(self, version, s):
- if version == 4:
- s.setsockopt(net_test.SOL_IP, self.IP_MTU_DISCOVER, self.IP_PMTUDISC_DO)
- s.setsockopt(net_test.SOL_IP, net_test.IP_RECVERR, 1)
- else:
- s.setsockopt(net_test.SOL_IPV6, self.IPV6_DONTFRAG, 1)
- s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_RECVERR, 1)
-
- def CheckPMTU(self, version, use_connect, modes):
-
- def SendBigPacket(version, s, dstaddr, netid, payload):
- if use_connect:
- s.send(payload)
- else:
- self.SendOnNetid(version, s, dstaddr, 1234, netid, payload, [])
-
- for netid in self.tuns:
- for mode in modes:
- s = self.BuildSocket(version, net_test.UDPSocket, netid, mode)
- self.DisableFragmentationAndReportErrors(version, s)
-
- srcaddr = self.MyAddress(version, netid)
- dst_prefix, intermediate = {
- 4: ("172.19.", "172.16.9.12"),
- 6: ("2001:db8::", "2001:db8::1")
- }[version]
- dstaddr = self.GetRandomDestination(dst_prefix)
-
- if use_connect:
- s.connect((dstaddr, 1234))
-
- payload = self.PAYLOAD_SIZE * "a"
-
- # Send a packet and receive a packet too big.
- SendBigPacket(version, s, dstaddr, netid, payload)
- 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.assertRaisesErrno(
- errno.EMSGSIZE,
- SendBigPacket, version, s, dstaddr, netid, payload)
-
- # If this is a connected socket, make sure the socket MTU was set.
- # Note that in IPv4 this only started working in Linux 3.6!
- if use_connect and (version == 6 or net_test.LINUX_VERSION >= (3, 6)):
- self.assertEquals(1280, self.GetSocketMTU(version, s))
-
- s.close()
-
- # Check that other sockets pick up the PMTU we have been told about by
- # connecting another socket to the same destination and getting its MTU.
- # This new socket can use any method to select its outgoing interface;
- # here we use a mark for simplicity.
- s2 = self.BuildSocket(version, net_test.UDPSocket, netid, "mark")
- s2.connect((dstaddr, 1234))
- self.assertEquals(1280, self.GetSocketMTU(version, s2))
-
- # Also check the MTU reported by ip route get, this time using the oif.
- routes = self.iproute.GetRoutes(dstaddr, self.ifindices[netid], 0, None)
- self.assertTrue(routes)
- route = routes[0]
- rtmsg, attributes = route
- self.assertEquals(iproute.RTN_UNICAST, rtmsg.type)
- metrics = attributes["RTA_METRICS"]
- 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"])
-
- def testIPv6BasicPMTU(self):
- self.CheckPMTU(6, True, ["mark", "oif"])
- self.CheckPMTU(6, False, ["mark", "oif"])
-
- @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
- def testIPv4UIDPMTU(self):
- self.CheckPMTU(4, True, ["uid"])
- self.CheckPMTU(4, False, ["uid"])
-
- @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
- def testIPv6UIDPMTU(self):
- self.CheckPMTU(6, True, ["uid"])
- self.CheckPMTU(6, False, ["uid"])
-
- # Making Path MTU Discovery work on unmarked sockets requires that mark
- # reflection be enabled. Otherwise the kernel has no way to know what routing
- # table the original packet used, and thus it won't be able to clone the
- # correct route.
-
- def testIPv4UnmarkedSocketPMTU(self):
- self.SetMarkReflectSysctls(1)
- try:
- self.CheckPMTU(4, False, [None])
- finally:
- self.SetMarkReflectSysctls(0)
-
- def testIPv6UnmarkedSocketPMTU(self):
- self.SetMarkReflectSysctls(1)
- try:
- self.CheckPMTU(6, False, [None])
- finally:
- self.SetMarkReflectSysctls(0)
-
-
-@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)
- out = [(rule, attributes) for rule, attributes in rules
- if attributes.get("FRA_PRIORITY", 0) == priority]
- return out
-
- def CheckInitialTablesHaveNoUIDs(self, version):
- rules = []
- for priority in [0, 32766, 32767]:
- rules.extend(self.GetRulesAtPriority(version, priority))
- for _, attributes in rules:
- self.assertNotIn("FRA_UID_START", attributes)
- self.assertNotIn("FRA_UID_END", attributes)
-
- def testIPv4InitialTablesHaveNoUIDs(self):
- self.CheckInitialTablesHaveNoUIDs(4)
-
- def testIPv6InitialTablesHaveNoUIDs(self):
- self.CheckInitialTablesHaveNoUIDs(6)
-
- def CheckGetAndSetRules(self, version):
- def Random():
- return random.randint(1000000, 2000000)
-
- start, end = tuple(sorted([Random(), Random()]))
- table = Random()
- priority = Random()
-
- try:
- self.iproute.UidRangeRule(version, True, start, end, table,
- priority=priority)
-
- rules = self.GetRulesAtPriority(version, priority)
- self.assertTrue(rules)
- _, attributes = rules[-1]
- self.assertEquals(priority, attributes["FRA_PRIORITY"])
- self.assertEquals(start, attributes["FRA_UID_START"])
- self.assertEquals(end, attributes["FRA_UID_END"])
- self.assertEquals(table, attributes["FRA_TABLE"])
- finally:
- self.iproute.UidRangeRule(version, False, start, end, table,
- priority=priority)
-
- def testIPv4GetAndSetRules(self):
- self.CheckGetAndSetRules(4)
-
- def testIPv6GetAndSetRules(self):
- self.CheckGetAndSetRules(6)
-
- def ExpectNoRoute(self, addr, oif, mark, uid):
- # The lack of a route may be either an error, or an unreachable route.
- try:
- routes = self.iproute.GetRoutes(addr, oif, mark, uid)
- rtmsg, _ = routes[0]
- self.assertEquals(iproute.RTN_UNREACHABLE, rtmsg.type)
- except IOError, e:
- if int(e.errno) != -int(errno.ENETUNREACH):
- raise e
-
- def ExpectRoute(self, addr, oif, mark, uid):
- routes = self.iproute.GetRoutes(addr, oif, mark, uid)
- rtmsg, _ = routes[0]
- self.assertEquals(iproute.RTN_UNICAST, rtmsg.type)
-
- def CheckGetRoute(self, version, addr):
- self.ExpectNoRoute(addr, 0, 0, 0)
- for netid in self.NETIDS:
- uid = self.UidForNetid(netid)
- self.ExpectRoute(addr, 0, 0, uid)
- self.ExpectNoRoute(addr, 0, 0, 0)
-
- def testIPv4RouteGet(self):
- self.CheckGetRoute(4, net_test.IPV4_ADDR)
-
- def testIPv6RouteGet(self):
- self.CheckGetRoute(6, net_test.IPV6_ADDR)
-
-
-class RulesTest(net_test.NetworkTest):
-
- RULE_PRIORITY = 99999
-
- def setUp(self):
- self.iproute = iproute.IPRoute()
- for version in [4, 6]:
- self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
-
- def tearDown(self):
- for version in [4, 6]:
- self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
-
- def testRuleDeletionMatchesTable(self):
- for version in [4, 6]:
- # Add rules with mark 300 pointing at tables 301 and 302.
- # This checks for a kernel bug where deletion request for tables > 256
- # ignored the table.
- self.iproute.FwmarkRule(version, True, 300, 301,
- priority=self.RULE_PRIORITY)
- self.iproute.FwmarkRule(version, True, 300, 302,
- priority=self.RULE_PRIORITY)
- # Delete rule with mark 300 pointing at table 302.
- self.iproute.FwmarkRule(version, False, 300, 302,
- priority=self.RULE_PRIORITY)
- # Check that the rule pointing at table 301 is still around.
- attributes = [a for _, a in self.iproute.DumpRules(version)
- if a.get("FRA_PRIORITY", 0) == self.RULE_PRIORITY]
- self.assertEquals(1, len(attributes))
- self.assertEquals(301, attributes[0]["FRA_TABLE"])
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/net_test/neighbour_test.py b/tests/net_test/neighbour_test.py
deleted file mode 100755
index 1e7739e..0000000
--- a/tests/net_test/neighbour_test.py
+++ /dev/null
@@ -1,297 +0,0 @@
-#!/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()
diff --git a/tests/net_test/net_test.py b/tests/net_test/net_test.py
deleted file mode 100755
index d7ea013..0000000
--- a/tests/net_test/net_test.py
+++ /dev/null
@@ -1,394 +0,0 @@
-#!/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 fcntl
-import os
-import random
-import re
-from socket import * # pylint: disable=wildcard-import
-import struct
-import unittest
-
-from scapy import all as scapy
-
-SOL_IPV6 = 41
-IP_RECVERR = 11
-IPV6_RECVERR = 25
-IP_TRANSPARENT = 19
-IPV6_TRANSPARENT = 75
-IPV6_TCLASS = 67
-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
-
-IPPROTO_GRE = 47
-
-SIOCSIFHWADDR = 0x8924
-
-IPV6_FL_A_GET = 0
-IPV6_FL_A_PUT = 1
-IPV6_FL_A_RENEW = 1
-
-IPV6_FL_F_CREATE = 1
-IPV6_FL_F_EXCL = 2
-
-IPV6_FL_S_NONE = 0
-IPV6_FL_S_EXCL = 1
-IPV6_FL_S_ANY = 255
-
-IFNAMSIZ = 16
-
-IPV4_PING = "\x08\x00\x00\x00\x0a\xce\x00\x03"
-IPV6_PING = "\x80\x00\x00\x00\x0a\xce\x00\x03"
-
-IPV4_ADDR = "8.8.8.8"
-IPV6_ADDR = "2001:4860:4860::8888"
-
-IPV6_SEQ_DGRAM_HEADER = (" sl "
- "local_address "
- "remote_address "
- "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
-
-
-def LinuxVersion():
- # Example: "3.4.67-00753-gb7a556f".
- # Get the part before the dash.
- version = os.uname()[2].split("-")[0]
- # Convert it into a tuple such as (3, 4, 67). That allows comparing versions
- # using < and >, since tuples are compared lexicographically.
- version = tuple(int(i) for i in version.split("."))
- return version
-
-
-LINUX_VERSION = LinuxVersion()
-
-
-def SetSocketTimeout(sock, ms):
- s = ms / 1000
- us = (ms % 1000) * 1000
- sock.setsockopt(SOL_SOCKET, SO_RCVTIMEO, struct.pack("LL", s, us))
-
-
-def SetSocketTos(s, tos):
- level = {AF_INET: SOL_IP, AF_INET6: SOL_IPV6}[s.family]
- option = {AF_INET: IP_TOS, AF_INET6: IPV6_TCLASS}[s.family]
- s.setsockopt(level, option, tos)
-
-
-def SetNonBlocking(fd):
- flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
- fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
-
-
-# Convenience functions to create sockets.
-def Socket(family, sock_type, protocol):
- s = socket(family, sock_type, protocol)
- SetSocketTimeout(s, 1000)
- return s
-
-
-def PingSocket(family):
- proto = {AF_INET: IPPROTO_ICMP, AF_INET6: IPPROTO_ICMPV6}[family]
- return Socket(family, SOCK_DGRAM, proto)
-
-
-def IPv4PingSocket():
- return PingSocket(AF_INET)
-
-
-def IPv6PingSocket():
- return PingSocket(AF_INET6)
-
-
-def TCPSocket(family):
- s = Socket(family, SOCK_STREAM, IPPROTO_TCP)
- SetNonBlocking(s.fileno())
- return s
-
-
-def IPv4TCPSocket():
- return TCPSocket(AF_INET)
-
-
-def IPv6TCPSocket():
- return TCPSocket(AF_INET6)
-
-
-def UDPSocket(family):
- return Socket(family, SOCK_DGRAM, IPPROTO_UDP)
-
-
-def RawGRESocket(family):
- s = Socket(family, SOCK_RAW, IPPROTO_GRE)
- 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("%dsi" % IFNAMSIZ, ifname, 0)
- ifr = fcntl.ioctl(s, scapy.SIOCGIFINDEX, ifr)
- return struct.unpack("%dsi" % IFNAMSIZ, ifr)[1]
-
-
-def SetInterfaceHWAddr(ifname, hwaddr):
- s = IPv4PingSocket()
- hwaddr = hwaddr.replace(":", "")
- hwaddr = hwaddr.decode("hex")
- if len(hwaddr) != 6:
- raise ValueError("Unknown hardware address length %d" % len(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("%dsH" % IFNAMSIZ, ifname, 0)
- ifr = fcntl.ioctl(s, scapy.SIOCGIFFLAGS, ifr)
- _, flags = struct.unpack("%dsH" % IFNAMSIZ, ifr)
- if up:
- flags |= scapy.IFF_UP
- else:
- flags &= ~scapy.IFF_UP
- ifr = struct.pack("%dsH" % IFNAMSIZ, ifname, flags)
- ifr = fcntl.ioctl(s, scapy.SIOCSIFFLAGS, ifr)
-
-
-def SetInterfaceUp(ifname):
- return SetInterfaceState(ifname, True)
-
-
-def SetInterfaceDown(ifname):
- return SetInterfaceState(ifname, False)
-
-
-def FormatProcAddress(unformatted):
- groups = []
- for i in xrange(0, len(unformatted), 4):
- groups.append(unformatted[i:i+4])
- formatted = ":".join(groups)
- # Compress the address.
- address = inet_ntop(AF_INET6, inet_pton(AF_INET6, formatted))
- return address
-
-
-def FormatSockStatAddress(address):
- if ":" in address:
- family = AF_INET6
- else:
- family = AF_INET
- binary = inet_pton(family, address)
- out = ""
- for i in xrange(0, len(binary), 4):
- out += "%08X" % struct.unpack("=L", binary[i:i+4])
- return out
-
-
-def GetLinkAddress(ifname, linklocal):
- addresses = open("/proc/net/if_inet6").readlines()
- for address in addresses:
- address = [s for s in address.strip().split(" ") if s]
- if address[5] == ifname:
- if (linklocal and address[0].startswith("fe80")
- or not linklocal and not address[0].startswith("fe80")):
- # Convert the address from raw hex to something with colons in it.
- return FormatProcAddress(address[0])
- return None
-
-
-def GetDefaultRoute(version=6):
- if version == 6:
- routes = open("/proc/net/ipv6_route").readlines()
- for route in routes:
- route = [s for s in route.strip().split(" ") if s]
- if (route[0] == "00000000000000000000000000000000" and route[1] == "00"
- # Routes in non-default tables end up in /proc/net/ipv6_route!!!
- and route[9] != "lo" and not route[9].startswith("nettest")):
- return FormatProcAddress(route[4]), route[9]
- raise ValueError("No IPv6 default route found")
- elif version == 4:
- routes = open("/proc/net/route").readlines()
- for route in routes:
- route = [s for s in route.strip().split("\t") if s]
- if route[1] == "00000000" and route[7] == "00000000":
- gw, iface = route[2], route[0]
- gw = inet_ntop(AF_INET, gw.decode("hex")[::-1])
- return gw, iface
- raise ValueError("No IPv4 default route found")
- else:
- raise ValueError("Don't know about IPv%s" % version)
-
-
-def GetDefaultRouteInterface():
- unused_gw, iface = GetDefaultRoute()
- return iface
-
-
-def MakeFlowLabelOption(addr, label):
- # struct in6_flowlabel_req {
- # struct in6_addr flr_dst;
- # __be32 flr_label;
- # __u8 flr_action;
- # __u8 flr_share;
- # __u16 flr_flags;
- # __u16 flr_expires;
- # __u16 flr_linger;
- # __u32 __flr_pad;
- # /* Options in format of IPV6_PKTOPTIONS */
- # };
- fmt = "16sIBBHHH4s"
- assert struct.calcsize(fmt) == 32
- addr = inet_pton(AF_INET6, addr)
- assert len(addr) == 16
- label = htonl(label & 0xfffff)
- action = IPV6_FL_A_GET
- share = IPV6_FL_S_ANY
- flags = IPV6_FL_F_CREATE
- pad = "\x00" * 4
- return struct.pack(fmt, addr, label, action, share, flags, 0, 0, pad)
-
-
-def SetFlowLabel(s, addr, label):
- opt = MakeFlowLabelOption(addr, label)
- s.setsockopt(SOL_IPV6, IPV6_FLOWLABEL_MGR, opt)
- # Caller also needs to do s.setsockopt(SOL_IPV6, IPV6_FLOWINFO_SEND, 1).
-
-
-# Determine network configuration.
-try:
- GetDefaultRoute(version=4)
- HAVE_IPV4 = True
-except ValueError:
- HAVE_IPV4 = False
-
-try:
- GetDefaultRoute(version=6)
- HAVE_IPV6 = True
-except ValueError:
- HAVE_IPV6 = False
-
-
-class RunAsUid(object):
- """Context guard to run a code block as a given UID."""
-
- def __init__(self, uid):
- self.uid = uid
-
- def __enter__(self):
- if self.uid:
- self.saved_uid = os.geteuid()
- self.saved_groups = os.getgroups()
- if self.uid:
- os.setgroups(self.saved_groups + [AID_INET])
- os.seteuid(self.uid)
-
- def __exit__(self, unused_type, unused_value, unused_traceback):
- if self.uid:
- os.seteuid(self.saved_uid)
- os.setgroups(self.saved_groups)
-
-
-class NetworkTest(unittest.TestCase):
-
- def assertRaisesErrno(self, err_num, f, *args):
- msg = os.strerror(err_num)
- self.assertRaisesRegexp(EnvironmentError, msg, f, *args)
-
- def ReadProcNetSocket(self, protocol):
- # Read file.
- filename = "/proc/net/%s" % protocol
- lines = open(filename).readlines()
-
- # Possibly check, and strip, header.
- if protocol in ["icmp6", "raw6", "udp6"]:
- self.assertEqual(IPV6_SEQ_DGRAM_HEADER, lines[0])
- lines = lines[1:]
-
- # Check contents.
- if protocol.endswith("6"):
- addrlen = 32
- else:
- addrlen = 8
-
- if protocol.startswith("tcp"):
- # Real sockets have 5 extra numbers, timewait sockets have none.
- end_regexp = "(| +[0-9]+ [0-9]+ [0-9]+ [0-9]+ -?[0-9]+|)$"
- elif re.match("icmp|udp|raw", protocol):
- # Drops.
- end_regexp = " +([0-9]+) *$"
- else:
- raise ValueError("Don't know how to parse %s" % filename)
-
- regexp = re.compile(r" *(\d+): " # bucket
- "([0-9A-F]{%d}:[0-9A-F]{4}) " # srcaddr, port
- "([0-9A-F]{%d}:[0-9A-F]{4}) " # dstaddr, port
- "([0-9A-F][0-9A-F]) " # state
- "([0-9A-F]{8}:[0-9A-F]{8}) " # mem
- "([0-9A-F]{2}:[0-9A-F]{8}) " # ?
- "([0-9A-F]{8}) +" # ?
- "([0-9]+) +" # uid
- "([0-9]+) +" # timeout
- "([0-9]+) +" # inode
- "([0-9]+) +" # refcnt
- "([0-9a-f]+)" # sp
- "%s" # icmp has spaces
- % (addrlen, addrlen, end_regexp))
- # Return a list of lists with only source / dest addresses for now.
- # TODO: consider returning a dict or namedtuple instead.
- out = []
- for line in lines:
- (_, src, dst, state, mem,
- _, _, uid, _, _, refcnt, _, extra) = regexp.match(line).groups()
- out.append([src, dst, state, mem, uid, refcnt, extra])
- return out
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/net_test/net_test.sh b/tests/net_test/net_test.sh
deleted file mode 100755
index acac660..0000000
--- a/tests/net_test/net_test.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# In case IPv6 is compiled as a module.
-[ -f /proc/net/if_inet6 ] || insmod $DIR/kernel/net-next/net/ipv6/ipv6.ko
-
-# Minimal network setup.
-ip link set lo up
-ip link set lo mtu 16436
-ip link set eth0 up
-
-# Allow people to run ping.
-echo "0 65536" > /proc/sys/net/ipv4/ping_group_range
-
-# Fall out to a shell once the test completes or if there's an error.
-trap "exec /bin/bash" ERR EXIT
-
-# Find and run the test.
-test=$(cat /proc/cmdline | sed -re 's/.*net_test=([^ ]*).*/\1/g')
-echo -e "Running $test\n"
-$test
diff --git a/tests/net_test/netlink.py b/tests/net_test/netlink.py
deleted file mode 100644
index 2b8f744..0000000
--- a/tests/net_test/netlink.py
+++ /dev/null
@@ -1,255 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Partial Python implementation of iproute functionality."""
-
-# pylint: disable=g-bad-todo
-
-import errno
-import os
-import socket
-import struct
-import sys
-
-import cstruct
-
-
-# Request constants.
-NLM_F_REQUEST = 1
-NLM_F_ACK = 4
-NLM_F_REPLACE = 0x100
-NLM_F_EXCL = 0x200
-NLM_F_CREATE = 0x400
-NLM_F_DUMP = 0x300
-
-# Message types.
-NLMSG_ERROR = 2
-NLMSG_DONE = 3
-
-# Data structure formats.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
-NLMsgErr = cstruct.Struct("NLMsgErr", "=i", "error")
-NLAttr = cstruct.Struct("NLAttr", "=HH", "nla_len nla_type")
-
-# Alignment / padding.
-NLA_ALIGNTO = 4
-
-
-def PaddedLength(length):
- # TODO: This padding is probably overly simplistic.
- return NLA_ALIGNTO * ((length / NLA_ALIGNTO) + (length % NLA_ALIGNTO != 0))
-
-
-class NetlinkSocket(object):
- """A basic netlink socket object."""
-
- BUFSIZE = 65536
- DEBUG = False
- # List of netlink messages to print, e.g., [], ["NEIGH", "ROUTE"], or ["ALL"]
- NL_DEBUG = []
-
- def _Debug(self, s):
- if self.DEBUG:
- print s
-
- def _NlAttr(self, nla_type, data):
- datalen = len(data)
- # Pad the data if it's not a multiple of NLA_ALIGNTO bytes long.
- padding = "\x00" * (PaddedLength(datalen) - datalen)
- nla_len = datalen + len(NLAttr)
- return NLAttr((nla_len, nla_type)).Pack() + data + padding
-
- def _NlAttrU32(self, nla_type, value):
- return self._NlAttr(nla_type, struct.pack("=I", value))
-
- def _GetConstantName(self, module, value, prefix):
- thismodule = sys.modules[module]
- for name in dir(thismodule):
- if name.startswith("INET_DIAG_BC"):
- break
- if (name.startswith(prefix) and
- not name.startswith(prefix + "F_") and
- name.isupper() and getattr(thismodule, name) == value):
- return name
- return value
-
- def _Decode(self, command, msg, nla_type, nla_data):
- """No-op, nonspecific version of decode."""
- return nla_type, nla_data
-
- def _ParseAttributes(self, command, family, msg, data):
- """Parses and decodes netlink attributes.
-
- Takes a block of NLAttr data structures, decodes them using Decode, and
- returns the result in a dict keyed by attribute number.
-
- Args:
- command: An integer, the rtnetlink command being carried out.
- family: The address family.
- msg: A Struct, the type of the data after the netlink header.
- data: A byte string containing a sequence of NLAttr data structures.
-
- Returns:
- A dictionary mapping attribute types (integers) to decoded values.
-
- Raises:
- ValueError: There was a duplicate attribute type.
- """
- attributes = {}
- while data:
- # Read the nlattr header.
- nla, data = cstruct.Read(data, NLAttr)
-
- # Read the data.
- datalen = nla.nla_len - len(nla)
- padded_len = PaddedLength(nla.nla_len) - len(nla)
- nla_data, data = data[:datalen], data[padded_len:]
-
- # If it's an attribute we know about, try to decode it.
- nla_name, nla_data = self._Decode(command, msg, nla.nla_type, nla_data)
-
- # We only support unique attributes for now, except for INET_DIAG_NONE,
- # which can appear more than once but doesn't seem to contain any data.
- if nla_name in attributes and nla_name != "INET_DIAG_NONE":
- raise ValueError("Duplicate attribute %s" % nla_name)
-
- attributes[nla_name] = nla_data
- self._Debug(" %s" % str((nla_name, nla_data)))
-
- return attributes
-
- def __init__(self):
- # Global sequence number.
- self.seq = 0
- self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.FAMILY)
- self.sock.connect((0, 0)) # The kernel.
- self.pid = self.sock.getsockname()[1]
-
- def _Send(self, msg):
- # self._Debug(msg.encode("hex"))
- self.seq += 1
- self.sock.send(msg)
-
- def _Recv(self):
- data = self.sock.recv(self.BUFSIZE)
- # self._Debug(data.encode("hex"))
- return data
-
- def _ExpectDone(self):
- response = self._Recv()
- hdr = NLMsgHdr(response)
- if hdr.type != NLMSG_DONE:
- raise ValueError("Expected DONE, got type %d" % hdr.type)
-
- def _ParseAck(self, response):
- # Find the error code.
- hdr, data = cstruct.Read(response, NLMsgHdr)
- if hdr.type == NLMSG_ERROR:
- error = NLMsgErr(data).error
- if error:
- raise IOError(error, os.strerror(-error))
- else:
- raise ValueError("Expected ACK, got type %d" % hdr.type)
-
- def _ExpectAck(self):
- response = self._Recv()
- self._ParseAck(response)
-
- def _SendNlRequest(self, command, data, flags):
- """Sends a netlink request and expects an ack."""
- length = len(NLMsgHdr) + len(data)
- nlmsg = NLMsgHdr((length, command, flags, self.seq, self.pid)).Pack()
-
- self.MaybeDebugCommand(command, nlmsg + data)
-
- # Send the message.
- self._Send(nlmsg + data)
-
- if flags & NLM_F_ACK:
- self._ExpectAck()
-
- def _ParseNLMsg(self, data, msgtype):
- """Parses a Netlink message into a header and a dictionary of attributes."""
- nlmsghdr, data = cstruct.Read(data, NLMsgHdr)
- self._Debug(" %s" % nlmsghdr)
-
- if nlmsghdr.type == NLMSG_ERROR or nlmsghdr.type == NLMSG_DONE:
- print "done"
- return (None, None), data
-
- nlmsg, data = cstruct.Read(data, msgtype)
- self._Debug(" %s" % nlmsg)
-
- # Parse the attributes in the nlmsg.
- attrlen = nlmsghdr.length - len(nlmsghdr) - len(nlmsg)
- attributes = self._ParseAttributes(nlmsghdr.type, nlmsg.family,
- nlmsg, data[:attrlen])
- data = data[attrlen:]
- return (nlmsg, attributes), data
-
- def _GetMsg(self, msgtype):
- data = self._Recv()
- if NLMsgHdr(data).type == NLMSG_ERROR:
- self._ParseAck(data)
- return self._ParseNLMsg(data, msgtype)[0]
-
- def _GetMsgList(self, msgtype, data, expect_done):
- out = []
- while data:
- msg, data = self._ParseNLMsg(data, msgtype)
- if msg is None:
- break
- out.append(msg)
- if expect_done:
- self._ExpectDone()
- return out
-
- def _Dump(self, command, msg, msgtype, attrs):
- """Sends a dump request and returns a list of decoded messages.
-
- Args:
- command: An integer, the command to run (e.g., RTM_NEWADDR).
- msg: A string, the raw bytes of the request (e.g., a packed RTMsg).
- msgtype: A cstruct.Struct, the data type to parse the dump results as.
- attrs: A string, the raw bytes of any request attributes to include.
-
- Returns:
- A list of (msg, attrs) tuples where msg is of type msgtype and attrs is
- a dict of attributes.
- """
- # Create a netlink dump request containing the msg.
- flags = NLM_F_DUMP | NLM_F_REQUEST
- length = len(NLMsgHdr) + len(msg) + len(attrs)
- nlmsghdr = NLMsgHdr((length, command, flags, self.seq, self.pid))
-
- # Send the request.
- self._Send(nlmsghdr.Pack() + msg.Pack() + attrs)
-
- # Keep reading netlink messages until we get a NLMSG_DONE.
- out = []
- while True:
- data = self._Recv()
- response_type = NLMsgHdr(data).type
- if response_type == NLMSG_DONE:
- break
- elif response_type == NLMSG_ERROR:
- # Likely means that the kernel didn't like our dump request.
- # Parse the error and throw an exception.
- self._ParseAck(data)
- out.extend(self._GetMsgList(msgtype, data, False))
-
- return out
diff --git a/tests/net_test/packets.py b/tests/net_test/packets.py
deleted file mode 100644
index c02adc0..0000000
--- a/tests/net_test/packets.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/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)
-
diff --git a/tests/net_test/ping6_test.py b/tests/net_test/ping6_test.py
deleted file mode 100755
index bf51cfa..0000000
--- a/tests/net_test/ping6_test.py
+++ /dev/null
@@ -1,709 +0,0 @@
-#!/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.
-
-# pylint: disable=g-bad-todo
-
-import errno
-import os
-import posix
-import random
-from socket import * # pylint: disable=wildcard-import
-import threading
-import time
-import unittest
-
-from scapy import all as scapy
-
-import csocket
-import multinetwork_base
-import net_test
-
-
-HAVE_PROC_NET_ICMP6 = os.path.isfile("/proc/net/icmp6")
-
-ICMP_ECHO = 8
-ICMP_ECHOREPLY = 0
-ICMPV6_ECHO_REQUEST = 128
-ICMPV6_ECHO_REPLY = 129
-
-
-class PingReplyThread(threading.Thread):
-
- MIN_TTL = 10
- INTERMEDIATE_IPV4 = "192.0.2.2"
- INTERMEDIATE_IPV6 = "2001:db8:1:2::ace:d00d"
- NEIGHBOURS = ["fe80::1"]
-
- def __init__(self, tun, mymac, routermac):
- super(PingReplyThread, self).__init__()
- self._tun = tun
- self._stopped = False
- self._mymac = mymac
- self._routermac = routermac
-
- def Stop(self):
- self._stopped = True
-
- def ChecksumValid(self, packet):
- # Get and clear the checksums.
- def GetAndClearChecksum(layer):
- if not layer:
- return
- try:
- checksum = layer.chksum
- del layer.chksum
- except AttributeError:
- checksum = layer.cksum
- del layer.cksum
- return checksum
-
- def GetChecksum(layer):
- try:
- return layer.chksum
- except AttributeError:
- return layer.cksum
-
- layers = ["IP", "ICMP", scapy.ICMPv6EchoRequest]
- sums = {}
- for name in layers:
- sums[name] = GetAndClearChecksum(packet.getlayer(name))
-
- # Serialize the packet, so scapy recalculates the checksums, and compare
- # them with the ones in the packet.
- packet = packet.__class__(str(packet))
- for name in layers:
- layer = packet.getlayer(name)
- if layer and GetChecksum(layer) != sums[name]:
- return False
-
- return True
-
- def SendTimeExceeded(self, version, packet):
- if version == 4:
- src = packet.getlayer(scapy.IP).src
- self.SendPacket(
- scapy.IP(src=self.INTERMEDIATE_IPV4, dst=src) /
- scapy.ICMP(type=11, code=0) /
- packet)
- elif version == 6:
- src = packet.getlayer(scapy.IPv6).src
- self.SendPacket(
- scapy.IPv6(src=self.INTERMEDIATE_IPV6, dst=src) /
- scapy.ICMPv6TimeExceeded(code=0) /
- packet)
-
- def IPv4Packet(self, ip):
- icmp = ip.getlayer(scapy.ICMP)
-
- # We only support ping for now.
- if (ip.proto != IPPROTO_ICMP or
- icmp.type != ICMP_ECHO or
- icmp.code != 0):
- return
-
- # Check the checksums.
- if not self.ChecksumValid(ip):
- return
-
- if ip.ttl < self.MIN_TTL:
- self.SendTimeExceeded(4, ip)
- return
-
- icmp.type = ICMP_ECHOREPLY
- self.SwapAddresses(ip)
- self.SendPacket(ip)
-
- def IPv6Packet(self, ipv6):
- icmpv6 = ipv6.getlayer(scapy.ICMPv6EchoRequest)
-
- # We only support ping for now.
- if (ipv6.nh != IPPROTO_ICMPV6 or
- not icmpv6 or
- icmpv6.type != ICMPV6_ECHO_REQUEST or
- icmpv6.code != 0):
- return
-
- # Check the checksums.
- if not self.ChecksumValid(ipv6):
- return
-
- if ipv6.dst.startswith("ff02::"):
- ipv6.dst = ipv6.src
- for src in self.NEIGHBOURS:
- ipv6.src = src
- icmpv6.type = ICMPV6_ECHO_REPLY
- self.SendPacket(ipv6)
- elif ipv6.hlim < self.MIN_TTL:
- self.SendTimeExceeded(6, ipv6)
- else:
- icmpv6.type = ICMPV6_ECHO_REPLY
- self.SwapAddresses(ipv6)
- self.SendPacket(ipv6)
-
- def SwapAddresses(self, packet):
- src = packet.src
- packet.src = packet.dst
- packet.dst = src
-
- def SendPacket(self, packet):
- packet = scapy.Ether(src=self._routermac, dst=self._mymac) / packet
- try:
- posix.write(self._tun.fileno(), str(packet))
- except ValueError:
- pass
-
- def run(self):
- while not self._stopped:
-
- try:
- packet = posix.read(self._tun.fileno(), 4096)
- except OSError, e:
- if e.errno == errno.EAGAIN:
- continue
- else:
- break
-
- ether = scapy.Ether(packet)
- if ether.type == net_test.ETH_P_IPV6:
- self.IPv6Packet(ether.payload)
- elif ether.type == net_test.ETH_P_IP:
- self.IPv4Packet(ether.payload)
-
-
-class Ping6Test(multinetwork_base.MultiNetworkBaseTest):
-
- @classmethod
- def setUpClass(cls):
- super(Ping6Test, cls).setUpClass()
- cls.netid = random.choice(cls.NETIDS)
- cls.reply_thread = PingReplyThread(
- cls.tuns[cls.netid],
- cls.MyMacAddress(cls.netid),
- cls.RouterMacAddress(cls.netid))
- cls.SetDefaultNetwork(cls.netid)
- cls.reply_thread.start()
-
- @classmethod
- def tearDownClass(cls):
- cls.reply_thread.Stop()
- cls.ClearDefaultNetwork()
- super(Ping6Test, cls).tearDownClass()
-
- def setUp(self):
- self.ifname = self.GetInterfaceName(self.netid)
- self.ifindex = self.ifindices[self.netid]
- self.lladdr = net_test.GetLinkAddress(self.ifname, True)
- self.globaladdr = net_test.GetLinkAddress(self.ifname, False)
-
- def assertValidPingResponse(self, s, data):
- family = s.family
-
- # Receive the reply.
- rcvd, src = s.recvfrom(32768)
- self.assertNotEqual(0, len(rcvd), "No data received")
-
- # If this is a dual-stack socket sending to a mapped IPv4 address, treat it
- # as IPv4.
- if src[0].startswith("::ffff:"):
- family = AF_INET
- src = (src[0].replace("::ffff:", ""), src[1:])
-
- # Check the data being sent is valid.
- self.assertGreater(len(data), 7, "Not enough data for ping packet")
- if family == AF_INET:
- self.assertTrue(data.startswith("\x08\x00"), "Not an IPv4 echo request")
- elif family == AF_INET6:
- self.assertTrue(data.startswith("\x80\x00"), "Not an IPv6 echo request")
- else:
- self.fail("Unknown socket address family %d" * s.family)
-
- # Check address, ICMP type, and ICMP code.
- if family == AF_INET:
- addr, unused_port = src
- self.assertGreaterEqual(len(addr), len("1.1.1.1"))
- self.assertTrue(rcvd.startswith("\x00\x00"), "Not an IPv4 echo reply")
- else:
- addr, unused_port, flowlabel, scope_id = src # pylint: disable=unbalanced-tuple-unpacking
- self.assertGreaterEqual(len(addr), len("::"))
- self.assertTrue(rcvd.startswith("\x81\x00"), "Not an IPv6 echo reply")
- # Check that the flow label is zero and that the scope ID is sane.
- self.assertEqual(flowlabel, 0)
- if addr.startswith("fe80::"):
- self.assertTrue(scope_id in self.ifindices.values())
- else:
- self.assertEquals(0, scope_id)
-
- # TODO: check the checksum. We can't do this easily now for ICMPv6 because
- # we don't have the IP addresses so we can't construct the pseudoheader.
-
- # Check the sequence number and the data.
- self.assertEqual(len(data), len(rcvd))
- self.assertEqual(data[6:].encode("hex"), rcvd[6:].encode("hex"))
-
- def CheckSockStatFile(self, name, srcaddr, srcport, dstaddr, dstport, state,
- txmem=0, rxmem=0):
- expected = ["%s:%04X" % (net_test.FormatSockStatAddress(srcaddr), srcport),
- "%s:%04X" % (net_test.FormatSockStatAddress(dstaddr), dstport),
- "%02X" % state,
- "%08X:%08X" % (txmem, rxmem),
- str(os.getuid()), "2", "0"]
- actual = self.ReadProcNetSocket(name)[-1]
- self.assertListEqual(expected, actual)
-
- def testIPv4SendWithNoConnection(self):
- s = net_test.IPv4PingSocket()
- self.assertRaisesErrno(errno.EDESTADDRREQ, s.send, net_test.IPV4_PING)
-
- def testIPv6SendWithNoConnection(self):
- s = net_test.IPv6PingSocket()
- self.assertRaisesErrno(errno.EDESTADDRREQ, s.send, net_test.IPV6_PING)
-
- def testIPv4LoopbackPingWithConnect(self):
- s = net_test.IPv4PingSocket()
- s.connect(("127.0.0.1", 55))
- data = net_test.IPV4_PING + "foobarbaz"
- s.send(data)
- self.assertValidPingResponse(s, data)
-
- def testIPv6LoopbackPingWithConnect(self):
- s = net_test.IPv6PingSocket()
- s.connect(("::1", 55))
- s.send(net_test.IPV6_PING)
- self.assertValidPingResponse(s, net_test.IPV6_PING)
-
- def testIPv4PingUsingSendto(self):
- s = net_test.IPv4PingSocket()
- written = s.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 55))
- self.assertEquals(len(net_test.IPV4_PING), written)
- self.assertValidPingResponse(s, net_test.IPV4_PING)
-
- def testIPv6PingUsingSendto(self):
- s = net_test.IPv6PingSocket()
- written = s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
- self.assertEquals(len(net_test.IPV6_PING), written)
- self.assertValidPingResponse(s, net_test.IPV6_PING)
-
- def testIPv4NoCrash(self):
- # Python 2.x does not provide either read() or recvmsg.
- s = net_test.IPv4PingSocket()
- written = s.sendto(net_test.IPV4_PING, ("127.0.0.1", 55))
- self.assertEquals(len(net_test.IPV4_PING), written)
- fd = s.fileno()
- reply = posix.read(fd, 4096)
- self.assertEquals(written, len(reply))
-
- def testIPv6NoCrash(self):
- # Python 2.x does not provide either read() or recvmsg.
- s = net_test.IPv6PingSocket()
- written = s.sendto(net_test.IPV6_PING, ("::1", 55))
- self.assertEquals(len(net_test.IPV6_PING), written)
- fd = s.fileno()
- reply = posix.read(fd, 4096)
- self.assertEquals(written, len(reply))
-
- def testCrossProtocolCrash(self):
- # Checks that an ICMP error containing a ping packet that matches the ID
- # of a socket of the wrong protocol (which can happen when using 464xlat)
- # doesn't crash the kernel.
-
- # We can only test this using IPv6 unreachables and IPv4 ping sockets,
- # because IPv4 packets sent by scapy.send() on loopback are not received by
- # the kernel. So we don't actually use this function yet.
- def GetIPv4Unreachable(port): # pylint: disable=unused-variable
- return (scapy.IP(src="192.0.2.1", dst="127.0.0.1") /
- scapy.ICMP(type=3, code=0) /
- scapy.IP(src="127.0.0.1", dst="127.0.0.1") /
- scapy.ICMP(type=8, id=port, seq=1))
-
- def GetIPv6Unreachable(port):
- return (scapy.IPv6(src="::1", dst="::1") /
- scapy.ICMPv6DestUnreach() /
- scapy.IPv6(src="::1", dst="::1") /
- scapy.ICMPv6EchoRequest(id=port, seq=1, data="foobarbaz"))
-
- # An unreachable matching the ID of a socket of the wrong protocol
- # shouldn't crash.
- s = net_test.IPv4PingSocket()
- s.connect(("127.0.0.1", 12345))
- _, port = s.getsockname()
- scapy.send(GetIPv6Unreachable(port))
- # No crash? Good.
-
- def testCrossProtocolCalls(self):
- """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)
-
- ipv6sockaddr = csocket.Sockaddr((net_test.IPV6_ADDR, 53))
-
- # In order to check that IPv6 socket calls return EAFNOSUPPORT when passed
- # IPv4 socket address structures, we need to pass down a socket address
- # length argument that's at least sizeof(sockaddr_in6). Otherwise, the calls
- # will fail immediately with EINVAL because the passed-in socket length is
- # too short. So create a sockaddr_in that's as long as a sockaddr_in6.
- ipv4sockaddr = csocket.Sockaddr((net_test.IPV4_ADDR, 53))
- ipv4sockaddr = csocket.SockaddrIn6(
- ipv4sockaddr.Pack() +
- "\x00" * (len(csocket.SockaddrIn6) - len(csocket.SockaddrIn)))
-
- s4 = net_test.IPv4PingSocket()
- s6 = net_test.IPv6PingSocket()
-
- # We can't just call s.connect(), s.bind() etc. with a tuple of the wrong
- # address family, because the Python implementation will just pass garbage
- # down to the kernel. So call the C functions directly.
- CheckEAFNoSupport(csocket.Bind, s4, ipv6sockaddr)
- CheckEAFNoSupport(csocket.Bind, s6, ipv4sockaddr)
- CheckEAFNoSupport(csocket.Connect, s4, ipv6sockaddr)
- CheckEAFNoSupport(csocket.Connect, s6, ipv4sockaddr)
- CheckEAFNoSupport(csocket.Sendmsg,
- s4, ipv6sockaddr, net_test.IPV4_PING, None, 0)
- CheckEAFNoSupport(csocket.Sendmsg,
- s6, ipv4sockaddr, net_test.IPV6_PING, None, 0)
-
- def testIPv4Bind(self):
- # Bind to unspecified address.
- s = net_test.IPv4PingSocket()
- s.bind(("0.0.0.0", 544))
- self.assertEquals(("0.0.0.0", 544), s.getsockname())
-
- # Bind to loopback.
- s = net_test.IPv4PingSocket()
- s.bind(("127.0.0.1", 99))
- self.assertEquals(("127.0.0.1", 99), s.getsockname())
-
- # Binding twice is not allowed.
- self.assertRaisesErrno(errno.EINVAL, s.bind, ("127.0.0.1", 22))
-
- # But binding two different sockets to the same ID is allowed.
- s2 = net_test.IPv4PingSocket()
- s2.bind(("127.0.0.1", 99))
- self.assertEquals(("127.0.0.1", 99), s2.getsockname())
- s3 = net_test.IPv4PingSocket()
- s3.bind(("127.0.0.1", 99))
- self.assertEquals(("127.0.0.1", 99), s3.getsockname())
-
- # If two sockets bind to the same port, the first one to call read() gets
- # the response.
- s4 = net_test.IPv4PingSocket()
- s5 = net_test.IPv4PingSocket()
- s4.bind(("0.0.0.0", 167))
- s5.bind(("0.0.0.0", 167))
- s4.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 44))
- self.assertValidPingResponse(s5, net_test.IPV4_PING)
- net_test.SetSocketTimeout(s4, 100)
- self.assertRaisesErrno(errno.EAGAIN, s4.recv, 32768)
-
- # If SO_REUSEADDR is turned off, then we get EADDRINUSE.
- s6 = net_test.IPv4PingSocket()
- s4.setsockopt(SOL_SOCKET, SO_REUSEADDR, 0)
- self.assertRaisesErrno(errno.EADDRINUSE, s6.bind, ("0.0.0.0", 167))
-
- # Can't bind after sendto.
- s = net_test.IPv4PingSocket()
- s.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 9132))
- self.assertRaisesErrno(errno.EINVAL, s.bind, ("0.0.0.0", 5429))
-
- def testIPv6Bind(self):
- # Bind to unspecified address.
- s = net_test.IPv6PingSocket()
- s.bind(("::", 769))
- self.assertEquals(("::", 769, 0, 0), s.getsockname())
-
- # Bind to loopback.
- s = net_test.IPv6PingSocket()
- s.bind(("::1", 99))
- self.assertEquals(("::1", 99, 0, 0), s.getsockname())
-
- # Binding twice is not allowed.
- self.assertRaisesErrno(errno.EINVAL, s.bind, ("::1", 22))
-
- # But binding two different sockets to the same ID is allowed.
- s2 = net_test.IPv6PingSocket()
- s2.bind(("::1", 99))
- self.assertEquals(("::1", 99, 0, 0), s2.getsockname())
- s3 = net_test.IPv6PingSocket()
- s3.bind(("::1", 99))
- self.assertEquals(("::1", 99, 0, 0), s3.getsockname())
-
- # Binding both IPv4 and IPv6 to the same socket works.
- s4 = net_test.IPv4PingSocket()
- s6 = net_test.IPv6PingSocket()
- s4.bind(("0.0.0.0", 444))
- s6.bind(("::", 666, 0, 0))
-
- # Can't bind after sendto.
- s = net_test.IPv6PingSocket()
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 9132))
- self.assertRaisesErrno(errno.EINVAL, s.bind, ("::", 5429))
-
- def testIPv4InvalidBind(self):
- s = net_test.IPv4PingSocket()
- self.assertRaisesErrno(errno.EADDRNOTAVAIL,
- s.bind, ("255.255.255.255", 1026))
- self.assertRaisesErrno(errno.EADDRNOTAVAIL,
- s.bind, ("224.0.0.1", 651))
- # Binding to an address we don't have only works with IP_TRANSPARENT.
- self.assertRaisesErrno(errno.EADDRNOTAVAIL,
- s.bind, (net_test.IPV4_ADDR, 651))
- try:
- s.setsockopt(SOL_IP, net_test.IP_TRANSPARENT, 1)
- s.bind((net_test.IPV4_ADDR, 651))
- except IOError, e:
- if e.errno == errno.EACCES:
- pass # We're not root. let it go for now.
-
- def testIPv6InvalidBind(self):
- s = net_test.IPv6PingSocket()
- self.assertRaisesErrno(errno.EINVAL,
- s.bind, ("ff02::2", 1026))
-
- # Binding to an address we don't have only works with IPV6_TRANSPARENT.
- self.assertRaisesErrno(errno.EADDRNOTAVAIL,
- s.bind, (net_test.IPV6_ADDR, 651))
- try:
- s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_TRANSPARENT, 1)
- s.bind((net_test.IPV6_ADDR, 651))
- except IOError, e:
- if e.errno == errno.EACCES:
- pass # We're not root. let it go for now.
-
- def testAfUnspecBind(self):
- # Binding to AF_UNSPEC is treated as IPv4 if the address is 0.0.0.0.
- s4 = net_test.IPv4PingSocket()
- sockaddr = csocket.Sockaddr(("0.0.0.0", 12996))
- sockaddr.family = AF_UNSPEC
- csocket.Bind(s4, sockaddr)
- self.assertEquals(("0.0.0.0", 12996), s4.getsockname())
-
- # But not if the address is anything else.
- sockaddr = csocket.Sockaddr(("127.0.0.1", 58234))
- sockaddr.family = AF_UNSPEC
- self.assertRaisesErrno(errno.EAFNOSUPPORT, csocket.Bind, s4, sockaddr)
-
- # This doesn't work for IPv6.
- s6 = net_test.IPv6PingSocket()
- sockaddr = csocket.Sockaddr(("::1", 58997))
- sockaddr.family = AF_UNSPEC
- self.assertRaisesErrno(errno.EAFNOSUPPORT, csocket.Bind, s6, sockaddr)
-
- def testIPv6ScopedBind(self):
- # Can't bind to a link-local address without a scope ID.
- s = net_test.IPv6PingSocket()
- self.assertRaisesErrno(errno.EINVAL,
- s.bind, (self.lladdr, 1026, 0, 0))
-
- # Binding to a link-local address with a scope ID works, and the scope ID is
- # returned by a subsequent getsockname. Interestingly, Python's getsockname
- # returns "fe80:1%foo", even though it does not understand it.
- expected = self.lladdr + "%" + self.ifname
- s.bind((self.lladdr, 4646, 0, self.ifindex))
- self.assertEquals((expected, 4646, 0, self.ifindex), s.getsockname())
-
- # Of course, for the above to work the address actually has to be configured
- # on the machine.
- self.assertRaisesErrno(errno.EADDRNOTAVAIL,
- s.bind, ("fe80::f00", 1026, 0, 1))
-
- # Scope IDs on non-link-local addresses are silently ignored.
- s = net_test.IPv6PingSocket()
- s.bind(("::1", 1234, 0, 1))
- self.assertEquals(("::1", 1234, 0, 0), s.getsockname())
-
- def testBindAffectsIdentifier(self):
- s = net_test.IPv6PingSocket()
- s.bind((self.globaladdr, 0xf976))
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
- self.assertEquals("\xf9\x76", s.recv(32768)[4:6])
-
- s = net_test.IPv6PingSocket()
- s.bind((self.globaladdr, 0xace))
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
- self.assertEquals("\x0a\xce", s.recv(32768)[4:6])
-
- def testLinkLocalAddress(self):
- s = net_test.IPv6PingSocket()
- # Sending to a link-local address with no scope fails with EINVAL.
- self.assertRaisesErrno(errno.EINVAL,
- s.sendto, net_test.IPV6_PING, ("fe80::1", 55))
- # Sending to link-local address with a scope succeeds. Note that Python
- # doesn't understand the "fe80::1%lo" format, even though it returns it.
- s.sendto(net_test.IPV6_PING, ("fe80::1", 55, 0, self.ifindex))
- # No exceptions? Good.
-
- def testMappedAddressFails(self):
- s = net_test.IPv6PingSocket()
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
- self.assertValidPingResponse(s, net_test.IPV6_PING)
- s.sendto(net_test.IPV6_PING, ("2001:4860:4860::8844", 55))
- self.assertValidPingResponse(s, net_test.IPV6_PING)
- self.assertRaisesErrno(errno.EINVAL, s.sendto, net_test.IPV6_PING,
- ("::ffff:192.0.2.1", 55))
-
- @unittest.skipUnless(False, "skipping: does not work yet")
- def testFlowLabel(self):
- s = net_test.IPv6PingSocket()
-
- # Specifying a flowlabel without having set IPV6_FLOWINFO_SEND succeeds but
- # the flow label in the packet is not set.
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 93, 0xdead, 0))
- self.assertValidPingResponse(s, net_test.IPV6_PING) # Checks flow label==0.
-
- # If IPV6_FLOWINFO_SEND is set on the socket, attempting to set a flow label
- # that is not registered with the flow manager should return EINVAL...
- s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_FLOWINFO_SEND, 1)
- # ... but this doesn't work yet.
- if False:
- self.assertRaisesErrno(errno.EINVAL, s.sendto, net_test.IPV6_PING,
- (net_test.IPV6_ADDR, 93, 0xdead, 0))
-
- # After registering the flow label, it gets sent properly, appears in the
- # output packet, and is returned in the response.
- net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xdead)
- self.assertEqual(1, s.getsockopt(net_test.SOL_IPV6,
- net_test.IPV6_FLOWINFO_SEND))
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 93, 0xdead, 0))
- _, src = s.recvfrom(32768)
- _, _, flowlabel, _ = src
- self.assertEqual(0xdead, flowlabel & 0xfffff)
-
- def testIPv4Error(self):
- s = net_test.IPv4PingSocket()
- s.setsockopt(SOL_IP, IP_TTL, 2)
- s.setsockopt(SOL_IP, net_test.IP_RECVERR, 1)
- s.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 55))
- # We can't check the actual error because Python 2.7 doesn't implement
- # recvmsg, but we can at least check that the socket returns an error.
- self.assertRaisesErrno(errno.EHOSTUNREACH, s.recv, 32768) # No response.
-
- def testIPv6Error(self):
- s = net_test.IPv6PingSocket()
- s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_HOPS, 2)
- s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_RECVERR, 1)
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
- # We can't check the actual error because Python 2.7 doesn't implement
- # recvmsg, but we can at least check that the socket returns an error.
- self.assertRaisesErrno(errno.EHOSTUNREACH, s.recv, 32768) # No response.
-
- def testIPv6MulticastPing(self):
- s = net_test.IPv6PingSocket()
- # Send a multicast ping and check we get at least one duplicate.
- # The setsockopt should not be necessary, but ping_v6_sendmsg has a bug.
- s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_MULTICAST_IF, self.ifindex)
- s.sendto(net_test.IPV6_PING, ("ff02::1", 55, 0, self.ifindex))
- self.assertValidPingResponse(s, net_test.IPV6_PING)
- self.assertValidPingResponse(s, net_test.IPV6_PING)
-
- def testIPv4LargePacket(self):
- s = net_test.IPv4PingSocket()
- data = net_test.IPV4_PING + 20000 * "a"
- s.sendto(data, ("127.0.0.1", 987))
- self.assertValidPingResponse(s, data)
-
- def testIPv6LargePacket(self):
- s = net_test.IPv6PingSocket()
- s.bind(("::", 0xace))
- data = net_test.IPV6_PING + "\x01" + 19994 * "\x00" + "aaaaa"
- s.sendto(data, ("::1", 953))
-
- @unittest.skipUnless(HAVE_PROC_NET_ICMP6, "skipping: no /proc/net/icmp6")
- def testIcmpSocketsNotInIcmp6(self):
- numrows = len(self.ReadProcNetSocket("icmp"))
- numrows6 = len(self.ReadProcNetSocket("icmp6"))
- s = net_test.Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)
- s.bind(("127.0.0.1", 0xace))
- s.connect(("127.0.0.1", 0xbeef))
- self.assertEquals(numrows + 1, len(self.ReadProcNetSocket("icmp")))
- self.assertEquals(numrows6, len(self.ReadProcNetSocket("icmp6")))
-
- @unittest.skipUnless(HAVE_PROC_NET_ICMP6, "skipping: no /proc/net/icmp6")
- def testIcmp6SocketsNotInIcmp(self):
- numrows = len(self.ReadProcNetSocket("icmp"))
- numrows6 = len(self.ReadProcNetSocket("icmp6"))
- s = net_test.IPv6PingSocket()
- s.bind(("::1", 0xace))
- s.connect(("::1", 0xbeef))
- self.assertEquals(numrows, len(self.ReadProcNetSocket("icmp")))
- self.assertEquals(numrows6 + 1, len(self.ReadProcNetSocket("icmp6")))
-
- def testProcNetIcmp(self):
- s = net_test.Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)
- s.bind(("127.0.0.1", 0xace))
- s.connect(("127.0.0.1", 0xbeef))
- self.CheckSockStatFile("icmp", "127.0.0.1", 0xace, "127.0.0.1", 0xbeef, 1)
-
- @unittest.skipUnless(HAVE_PROC_NET_ICMP6, "skipping: no /proc/net/icmp6")
- def testProcNetIcmp6(self):
- numrows6 = len(self.ReadProcNetSocket("icmp6"))
- s = net_test.IPv6PingSocket()
- s.bind(("::1", 0xace))
- s.connect(("::1", 0xbeef))
- self.CheckSockStatFile("icmp6", "::1", 0xace, "::1", 0xbeef, 1)
-
- # Check the row goes away when the socket is closed.
- s.close()
- self.assertEquals(numrows6, len(self.ReadProcNetSocket("icmp6")))
-
- # Try send, bind and connect to check the addresses and the state.
- s = net_test.IPv6PingSocket()
- self.assertEqual(0, len(self.ReadProcNetSocket("icmp6")))
- s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 12345))
- self.assertEqual(1, len(self.ReadProcNetSocket("icmp6")))
-
- # Can't bind after sendto, apparently.
- s = net_test.IPv6PingSocket()
- self.assertEqual(0, len(self.ReadProcNetSocket("icmp6")))
- s.bind((self.lladdr, 0xd00d, 0, self.ifindex))
- self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "::", 0, 7)
-
- # Check receive bytes.
- s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_MULTICAST_IF, self.ifindex)
- s.connect(("ff02::1", 0xdead))
- self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "ff02::1", 0xdead, 1)
- s.send(net_test.IPV6_PING)
- time.sleep(0.01) # Give the other thread time to reply.
- self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "ff02::1", 0xdead, 1,
- txmem=0, rxmem=0x300)
- self.assertValidPingResponse(s, net_test.IPV6_PING)
- self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "ff02::1", 0xdead, 1,
- txmem=0, rxmem=0)
-
- def testProcNetUdp6(self):
- s = net_test.Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
- s.bind(("::1", 0xace))
- s.connect(("::1", 0xbeef))
- self.CheckSockStatFile("udp6", "::1", 0xace, "::1", 0xbeef, 1)
-
- def testProcNetRaw6(self):
- s = net_test.Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)
- s.bind(("::1", 0xace))
- s.connect(("::1", 0xbeef))
- self.CheckSockStatFile("raw6", "::1", 0xff, "::1", 0, 1)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/net_test/ping6_test.sh b/tests/net_test/ping6_test.sh
deleted file mode 100755
index 41dabce..0000000
--- a/tests/net_test/ping6_test.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-# Minimal network initialization.
-ip link set eth0 up
-
-# Wait for autoconf and DAD to complete.
-sleep 3 &
-
-# Block on starting DHCPv4.
-udhcpc -i eth0
-
-# If DHCPv4 took less than 3 seconds, keep waiting.
-wait
-
-# Run the test.
-$(dirname $0)/ping6_test.py
diff --git a/tests/net_test/run_net_test.sh b/tests/net_test/run_net_test.sh
deleted file mode 100755
index 080aac7..0000000
--- a/tests/net_test/run_net_test.sh
+++ /dev/null
@@ -1,125 +0,0 @@
-#!/bin/bash
-
-# 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"
-
-# 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
-COMPRESSED_ROOTFS=$ROOTFS.xz
-URL=https://dl.google.com/dl/android/$COMPRESSED_ROOTFS
-
-# Figure out which test to run.
-if [ -z "$1" ]; then
- echo "Usage: $0 <test>" >&2
- exit 1
-fi
-test=$1
-
-set -e
-
-# Check if we need to uncompress the disk image.
-# We use xz because it compresses better: to 42M vs 72M (gzip) / 62M (bzip2).
-cd $(dirname $0)
-if [ ! -f $ROOTFS ]; then
- echo "Deleting $COMPRESSED_ROOTFS" >&2
- rm -f $COMPRESSED_ROOTFS
- echo "Downloading $URL" >&2
- wget $URL
- echo "Uncompressing $COMPRESSED_ROOTFS" >&2
- unxz $COMPRESSED_ROOTFS
-fi
-echo "Using $ROOTFS"
-cd -
-
-# 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
-
-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
-
- # Enable the kernel config options listed in $OPTIONS.
- cmdline=${OPTIONS// / -e }
- ./scripts/config $cmdline
-
- # Disable the kernel config options listed in $DISABLE_OPTIONS.
- cmdline=${DISABLE_OPTIONS// / -d }
- ./scripts/config $cmdline
-
- # olddefconfig doesn't work on old kernels.
- if ! make olddefconfig ARCH=um SUBARCH=x86_64 CROSS_COMPILE= ; then
- cat >&2 << EOF
-
-Warning: "make olddefconfig" failed.
-Perhaps this kernel is too old to support it.
-You may get asked lots of questions.
-Keep enter pressed to accept the defaults.
-
-EOF
- fi
-
- # Compile the kernel.
- make -j32 linux ARCH=um SUBARCH=x86_64 CROSS_COMPILE=
- KERNEL_BINARY=./linux
-fi
-
-
-# Get the absolute path to the test file that's being run.
-dir=/host$(dirname $(readlink -f $0))
-
-# Start the VM.
-exec $KERNEL_BINARY umid=net_test ubda=$(dirname $0)/$ROOTFS \
- mem=512M init=/sbin/net_test.sh net_test=$dir/$test \
- $netconfig
diff --git a/tests/net_test/sock_diag.py b/tests/net_test/sock_diag.py
deleted file mode 100755
index b4d9cf6..0000000
--- a/tests/net_test/sock_diag.py
+++ /dev/null
@@ -1,342 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Partial Python implementation of sock_diag functionality."""
-
-# pylint: disable=g-bad-todo
-
-import errno
-from socket import * # pylint: disable=wildcard-import
-import struct
-
-import cstruct
-import net_test
-import netlink
-
-### Base netlink constants. See include/uapi/linux/netlink.h.
-NETLINK_SOCK_DIAG = 4
-
-### sock_diag constants. See include/uapi/linux/sock_diag.h.
-# Message types.
-SOCK_DIAG_BY_FAMILY = 20
-SOCK_DESTROY = 21
-
-### inet_diag_constants. See include/uapi/linux/inet_diag.h
-# Message types.
-TCPDIAG_GETSOCK = 18
-
-# Request attributes.
-INET_DIAG_REQ_BYTECODE = 1
-
-# Extensions.
-INET_DIAG_NONE = 0
-INET_DIAG_MEMINFO = 1
-INET_DIAG_INFO = 2
-INET_DIAG_VEGASINFO = 3
-INET_DIAG_CONG = 4
-INET_DIAG_TOS = 5
-INET_DIAG_TCLASS = 6
-INET_DIAG_SKMEMINFO = 7
-INET_DIAG_SHUTDOWN = 8
-INET_DIAG_DCTCPINFO = 9
-
-# Bytecode operations.
-INET_DIAG_BC_NOP = 0
-INET_DIAG_BC_JMP = 1
-INET_DIAG_BC_S_GE = 2
-INET_DIAG_BC_S_LE = 3
-INET_DIAG_BC_D_GE = 4
-INET_DIAG_BC_D_LE = 5
-INET_DIAG_BC_AUTO = 6
-INET_DIAG_BC_S_COND = 7
-INET_DIAG_BC_D_COND = 8
-
-# Data structure formats.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-InetDiagSockId = cstruct.Struct(
- "InetDiagSockId", "!HH16s16sI8s", "sport dport src dst iface cookie")
-InetDiagReqV2 = cstruct.Struct(
- "InetDiagReqV2", "=BBBxIS", "family protocol ext states id",
- [InetDiagSockId])
-InetDiagMsg = cstruct.Struct(
- "InetDiagMsg", "=BBBBSLLLLL",
- "family state timer retrans id expires rqueue wqueue uid inode",
- [InetDiagSockId])
-InetDiagMeminfo = cstruct.Struct(
- "InetDiagMeminfo", "=IIII", "rmem wmem fmem tmem")
-InetDiagBcOp = cstruct.Struct("InetDiagBcOp", "BBH", "code yes no")
-InetDiagHostcond = cstruct.Struct("InetDiagHostcond", "=BBxxi",
- "family prefix_len port")
-
-SkMeminfo = cstruct.Struct(
- "SkMeminfo", "=IIIIIIII",
- "rmem_alloc rcvbuf wmem_alloc sndbuf fwd_alloc wmem_queued optmem backlog")
-TcpInfo = cstruct.Struct(
- "TcpInfo", "=BBBBBBBxIIIIIIIIIIIIIIIIIIIIIIII",
- "state ca_state retransmits probes backoff options wscale "
- "rto ato snd_mss rcv_mss "
- "unacked sacked lost retrans fackets "
- "last_data_sent last_ack_sent last_data_recv last_ack_recv "
- "pmtu rcv_ssthresh rtt rttvar snd_ssthresh snd_cwnd advmss reordering "
- "rcv_rtt rcv_space "
- "total_retrans") # As of linux 3.13, at least.
-
-TCP_TIME_WAIT = 6
-ALL_NON_TIME_WAIT = 0xffffffff & ~(1 << TCP_TIME_WAIT)
-
-
-class SockDiag(netlink.NetlinkSocket):
-
- FAMILY = NETLINK_SOCK_DIAG
- NL_DEBUG = []
-
- def _Decode(self, command, msg, nla_type, nla_data):
- """Decodes netlink attributes to Python types."""
- if msg.family == AF_INET or msg.family == AF_INET6:
- name = self._GetConstantName(__name__, nla_type, "INET_DIAG")
- else:
- # Don't know what this is. Leave it as an integer.
- name = nla_type
-
- if name in ["INET_DIAG_SHUTDOWN", "INET_DIAG_TOS", "INET_DIAG_TCLASS"]:
- data = ord(nla_data)
- elif name == "INET_DIAG_CONG":
- data = nla_data.strip("\x00")
- elif name == "INET_DIAG_MEMINFO":
- data = InetDiagMeminfo(nla_data)
- elif name == "INET_DIAG_INFO":
- # TODO: Catch the exception and try something else if it's not TCP.
- data = TcpInfo(nla_data)
- elif name == "INET_DIAG_SKMEMINFO":
- data = SkMeminfo(nla_data)
- else:
- data = nla_data
-
- return name, data
-
- def MaybeDebugCommand(self, command, data):
- name = self._GetConstantName(__name__, command, "SOCK_")
- if "ALL" not in self.NL_DEBUG and "SOCK" not in self.NL_DEBUG:
- return
- parsed = self._ParseNLMsg(data, InetDiagReqV2)
- print "%s %s" % (name, str(parsed))
-
- @staticmethod
- def _EmptyInetDiagSockId():
- return InetDiagSockId(("\x00" * len(InetDiagSockId)))
-
- def PackBytecode(self, instructions):
- """Compiles instructions to inet_diag bytecode.
-
- The input is a list of (INET_DIAG_BC_xxx, yes, no, arg) tuples, where yes
- and no are relative jump offsets measured in instructions. The yes branch
- is taken if the instruction matches.
-
- To accept, jump 1 past the last instruction. To reject, jump 2 past the
- last instruction.
-
- The target of a no jump is only valid if it is reachable by following
- only yes jumps from the first instruction - see inet_diag_bc_audit and
- valid_cc. This means that if cond1 and cond2 are two mutually exclusive
- filter terms, it is not possible to implement cond1 OR cond2 using:
-
- ...
- cond1 2 1 arg
- cond2 1 2 arg
- accept
- reject
-
- but only using:
-
- ...
- cond1 1 2 arg
- jmp 1 2
- cond2 1 2 arg
- accept
- reject
-
- The jmp instruction ignores yes and always jumps to no, but yes must be 1
- or the bytecode won't validate. It doesn't have to be jmp - any instruction
- that is guaranteed not to match on real data will do.
-
- Args:
- instructions: list of instruction tuples
-
- Returns:
- A string, the raw bytecode.
- """
- args = []
- positions = [0]
-
- for op, yes, no, arg in instructions:
-
- if yes <= 0 or no <= 0:
- raise ValueError("Jumps must be > 0")
-
- if op in [INET_DIAG_BC_NOP, INET_DIAG_BC_JMP, INET_DIAG_BC_AUTO]:
- arg = ""
- elif op in [INET_DIAG_BC_S_GE, INET_DIAG_BC_S_LE,
- INET_DIAG_BC_D_GE, INET_DIAG_BC_D_LE]:
- arg = "\x00\x00" + struct.pack("=H", arg)
- elif op in [INET_DIAG_BC_S_COND, INET_DIAG_BC_D_COND]:
- addr, prefixlen, port = arg
- family = AF_INET6 if ":" in addr else AF_INET
- addr = inet_pton(family, addr)
- arg = InetDiagHostcond((family, prefixlen, port)).Pack() + addr
- else:
- raise ValueError("Unsupported opcode %d" % op)
-
- args.append(arg)
- length = len(InetDiagBcOp) + len(arg)
- positions.append(positions[-1] + length)
-
- # Reject label.
- positions.append(positions[-1] + 4) # Why 4? Because the kernel uses 4.
- assert len(args) == len(instructions) == len(positions) - 2
-
- # print positions
-
- packed = ""
- for i, (op, yes, no, arg) in enumerate(instructions):
- yes = positions[i + yes] - positions[i]
- no = positions[i + no] - positions[i]
- instruction = InetDiagBcOp((op, yes, no)).Pack() + args[i]
- #print "%3d: %d %3d %3d %s %s" % (positions[i], op, yes, no,
- # arg, instruction.encode("hex"))
- packed += instruction
- #print
-
- return packed
-
- def Dump(self, diag_req, bytecode=""):
- out = self._Dump(SOCK_DIAG_BY_FAMILY, diag_req, InetDiagMsg, bytecode)
- return out
-
- def DumpAllInetSockets(self, protocol, bytecode, sock_id=None, ext=0,
- states=ALL_NON_TIME_WAIT):
- """Dumps IPv4 or IPv6 sockets matching the specified parameters."""
- # DumpSockets(AF_UNSPEC) does not result in dumping all inet sockets, it
- # results in ENOENT.
- if sock_id is None:
- sock_id = self._EmptyInetDiagSockId()
-
- if bytecode:
- bytecode = self._NlAttr(INET_DIAG_REQ_BYTECODE, bytecode)
-
- sockets = []
- for family in [AF_INET, AF_INET6]:
- diag_req = InetDiagReqV2((family, protocol, ext, states, sock_id))
- sockets += self.Dump(diag_req, bytecode)
-
- return sockets
-
- @staticmethod
- def GetRawAddress(family, addr):
- """Fetches the source address from an InetDiagMsg."""
- addrlen = {AF_INET:4, AF_INET6: 16}[family]
- return inet_ntop(family, addr[:addrlen])
-
- @staticmethod
- def GetSourceAddress(diag_msg):
- """Fetches the source address from an InetDiagMsg."""
- return SockDiag.GetRawAddress(diag_msg.family, diag_msg.id.src)
-
- @staticmethod
- def GetDestinationAddress(diag_msg):
- """Fetches the source address from an InetDiagMsg."""
- return SockDiag.GetRawAddress(diag_msg.family, diag_msg.id.dst)
-
- @staticmethod
- def RawAddress(addr):
- """Converts an IP address string to binary format."""
- family = AF_INET6 if ":" in addr else AF_INET
- return inet_pton(family, addr)
-
- @staticmethod
- def PaddedAddress(addr):
- """Converts an IP address string to binary format for InetDiagSockId."""
- padded = SockDiag.RawAddress(addr)
- if len(padded) < 16:
- padded += "\x00" * (16 - len(padded))
- return padded
-
- @staticmethod
- def DiagReqFromSocket(s):
- """Creates an InetDiagReqV2 that matches the specified socket."""
- family = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_DOMAIN)
- protocol = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_PROTOCOL)
- if net_test.LINUX_VERSION >= (3, 8):
- iface = s.getsockopt(SOL_SOCKET, net_test.SO_BINDTODEVICE,
- net_test.IFNAMSIZ)
- iface = GetInterfaceIndex(iface) if iface else 0
- else:
- iface = 0
- src, sport = s.getsockname()[:2]
- try:
- dst, dport = s.getpeername()[:2]
- except error, e:
- if e.errno == errno.ENOTCONN:
- dport = 0
- dst = "::" if family == AF_INET6 else "0.0.0.0"
- else:
- raise e
- src = SockDiag.PaddedAddress(src)
- dst = SockDiag.PaddedAddress(dst)
- sock_id = InetDiagSockId((sport, dport, src, dst, iface, "\x00" * 8))
- return InetDiagReqV2((family, protocol, 0, 0xffffffff, sock_id))
-
- def FindSockDiagFromReq(self, req):
- for diag_msg, attrs in self.Dump(req):
- return diag_msg
- raise ValueError("Dump of %s returned no sockets" % req)
-
- def FindSockDiagFromFd(self, s):
- """Gets an InetDiagMsg from the kernel for the specified socket."""
- req = self.DiagReqFromSocket(s)
- return self.FindSockDiagFromReq(req)
-
- def GetSockDiag(self, req):
- """Gets an InetDiagMsg from the kernel for the specified request."""
- self._SendNlRequest(SOCK_DIAG_BY_FAMILY, req.Pack(), netlink.NLM_F_REQUEST)
- return self._GetMsg(InetDiagMsg)[0]
-
- @staticmethod
- def DiagReqFromDiagMsg(d, protocol):
- """Constructs a diag_req from a diag_msg the kernel has given us."""
- return InetDiagReqV2((d.family, protocol, 0, 1 << d.state, d.id))
-
- def CloseSocket(self, req):
- self._SendNlRequest(SOCK_DESTROY, req.Pack(),
- netlink.NLM_F_REQUEST | netlink.NLM_F_ACK)
-
- def CloseSocketFromFd(self, s):
- diag_msg = self.FindSockDiagFromFd(s)
- protocol = s.getsockopt(SOL_SOCKET, net_test.SO_PROTOCOL)
- req = self.DiagReqFromDiagMsg(diag_msg, protocol)
- return self.CloseSocket(req)
-
-
-if __name__ == "__main__":
- n = SockDiag()
- n.DEBUG = True
- bytecode = ""
- sock_id = n._EmptyInetDiagSockId()
- sock_id.dport = 443
- ext = 1 << (INET_DIAG_TOS - 1) | 1 << (INET_DIAG_TCLASS - 1)
- states = 0xffffffff
- diag_msgs = n.DumpAllInetSockets(IPPROTO_TCP, "",
- sock_id=sock_id, ext=ext, states=states)
- print diag_msgs
diff --git a/tests/net_test/sock_diag_test.py b/tests/net_test/sock_diag_test.py
deleted file mode 100755
index 3c5d0a9..0000000
--- a/tests/net_test/sock_diag_test.py
+++ /dev/null
@@ -1,548 +0,0 @@
-#!/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()
diff --git a/tests/net_test/srcaddr_selection_test.py b/tests/net_test/srcaddr_selection_test.py
deleted file mode 100755
index d3efdd9..0000000
--- a/tests/net_test/srcaddr_selection_test.py
+++ /dev/null
@@ -1,345 +0,0 @@
-#!/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 errno
-import random
-from socket import * # pylint: disable=wildcard-import
-import time
-import unittest
-
-from scapy import all as scapy
-
-import csocket
-import iproute
-import multinetwork_base
-import packets
-import net_test
-
-# Setsockopt values.
-IPV6_ADDR_PREFERENCES = 72
-IPV6_PREFER_SRC_PUBLIC = 0x0002
-
-
-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)
- self.SetSysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits" % ifname, value)
-
- def SetOptimisticDAD(self, ifname, value):
- self.SetSysctl("/proc/sys/net/ipv6/conf/%s/optimistic_dad" % ifname, value)
-
- def SetUseTempaddrs(self, ifname, value):
- self.SetSysctl("/proc/sys/net/ipv6/conf/%s/use_tempaddr" % ifname, value)
-
- def SetUseOptimistic(self, ifname, value):
- self.SetSysctl("/proc/sys/net/ipv6/conf/%s/use_optimistic" % ifname, value)
-
- def GetSourceIP(self, netid, mode="mark"):
- s = self.BuildSocket(6, net_test.UDPSocket, netid, mode)
- # Because why not...testing for temporary addresses is a separate thing.
- s.setsockopt(IPPROTO_IPV6, IPV6_ADDR_PREFERENCES, IPV6_PREFER_SRC_PUBLIC)
-
- s.connect((net_test.IPV6_ADDR, 123))
- src_addr = s.getsockname()[0]
- self.assertTrue(src_addr)
- return src_addr
-
- def assertAddressNotPresent(self, address):
- self.assertRaises(IOError, self.iproute.GetAddress, address)
-
- def assertAddressHasExpectedAttributes(
- self, address, expected_ifindex, expected_flags):
- ifa_msg = self.iproute.GetAddress(address)[0]
- self.assertEquals(AF_INET6 if ":" in address else AF_INET, ifa_msg.family)
- self.assertEquals(64, ifa_msg.prefixlen)
- self.assertEquals(iproute.RT_SCOPE_UNIVERSE, ifa_msg.scope)
- self.assertEquals(expected_ifindex, ifa_msg.index)
- self.assertEquals(expected_flags, ifa_msg.flags & expected_flags)
-
- def AddressIsTentative(self, address):
- ifa_msg = self.iproute.GetAddress(address)[0]
- return ifa_msg.flags & iproute.IFA_F_TENTATIVE
-
- def BindToAddress(self, address):
- s = net_test.UDPSocket(AF_INET6)
- s.bind((address, 0, 0, 0))
-
- def SendWithSourceAddress(self, address, netid, dest=net_test.IPV6_ADDR):
- pktinfo = multinetwork_base.MakePktInfo(6, address, 0)
- cmsgs = [(net_test.SOL_IPV6, IPV6_PKTINFO, pktinfo)]
- s = self.BuildSocket(6, net_test.UDPSocket, netid, "mark")
- return csocket.Sendmsg(s, (dest, 53), "Hello", cmsgs, 0)
-
- def assertAddressUsable(self, address, netid):
- self.BindToAddress(address)
- self.SendWithSourceAddress(address, netid)
- # No exceptions? Good.
-
- def assertAddressNotUsable(self, address, netid):
- self.assertRaisesErrno(errno.EADDRNOTAVAIL, self.BindToAddress, address)
- self.assertRaisesErrno(errno.EINVAL,
- self.SendWithSourceAddress, address, netid)
-
- def assertAddressSelected(self, address, netid):
- self.assertEquals(address, self.GetSourceIP(netid))
-
- def assertAddressNotSelected(self, address, netid):
- self.assertNotEquals(address, self.GetSourceIP(netid))
-
- def WaitForDad(self, address):
- for _ in xrange(20):
- if not self.AddressIsTentative(address):
- return
- time.sleep(0.1)
- raise AssertionError("%s did not complete DAD after 2 seconds")
-
-
-class MultiInterfaceSourceAddressSelectionTest(IPv6SourceAddressSelectionTest):
-
- def setUp(self):
- # [0] Make sure DAD, optimistic DAD, and the use_optimistic option
- # are all consistently disabled at the outset.
- for netid in self.tuns:
- 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):
-
- def testRfc6724Behaviour(self):
- # [3] Get an IPv6 address back, in DAD start-up.
- self.SetDAD(self.test_ifname, 1) # Enable DAD
- # Send a RA to start SLAAC and subsequent DAD.
- self.SendRA(self.test_netid, 0)
- # Get flags and prove tentative-ness.
- self.assertAddressHasExpectedAttributes(
- self.test_ip, self.test_ifindex, iproute.IFA_F_TENTATIVE)
-
- # Even though the interface has an IPv6 address, its tentative nature
- # prevents it from being selected.
- self.assertAddressNotUsable(self.test_ip, self.test_netid)
- self.assertAddressNotSelected(self.test_ip, self.test_netid)
-
- # Busy wait for DAD to complete (should be less than 1 second).
- self.WaitForDad(self.test_ip)
-
- # The test_ip should have completed DAD by now, and should be the
- # chosen source address, eligible to bind to, etc.
- self.assertAddressUsable(self.test_ip, self.test_netid)
- self.assertAddressSelected(self.test_ip, self.test_netid)
-
-
-class OptimisticAddressTest(MultiInterfaceSourceAddressSelectionTest):
-
- def testRfc6724Behaviour(self):
- # [3] Get an IPv6 address back, in optimistic DAD start-up.
- self.SetDAD(self.test_ifname, 1) # Enable DAD
- self.SetOptimisticDAD(self.test_ifname, 1)
- # Send a RA to start SLAAC and subsequent DAD.
- self.SendRA(self.test_netid, 0)
- # Get flags and prove optimism.
- self.assertAddressHasExpectedAttributes(
- self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-
- # Optimistic addresses are usable but are not selected.
- if net_test.LinuxVersion() >= (3, 18, 0):
- # The version checked in to android kernels <= 3.10 requires the
- # use_optimistic sysctl to be turned on.
- self.assertAddressUsable(self.test_ip, self.test_netid)
- self.assertAddressNotSelected(self.test_ip, self.test_netid)
-
- # Busy wait for DAD to complete (should be less than 1 second).
- self.WaitForDad(self.test_ip)
-
- # The test_ip should have completed DAD by now, and should be the
- # chosen source address.
- self.assertAddressUsable(self.test_ip, self.test_netid)
- self.assertAddressSelected(self.test_ip, self.test_netid)
-
-
-class OptimisticAddressOkayTest(MultiInterfaceSourceAddressSelectionTest):
-
- def testModifiedRfc6724Behaviour(self):
- # [3] Get an IPv6 address back, in optimistic DAD start-up.
- self.SetDAD(self.test_ifname, 1) # Enable DAD
- self.SetOptimisticDAD(self.test_ifname, 1)
- self.SetUseOptimistic(self.test_ifname, 1)
- # Send a RA to start SLAAC and subsequent DAD.
- self.SendRA(self.test_netid, 0)
- # Get flags and prove optimistism.
- self.assertAddressHasExpectedAttributes(
- self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-
- # The interface has an IPv6 address and, despite its optimistic nature,
- # the use_optimistic option allows it to be selected.
- self.assertAddressUsable(self.test_ip, self.test_netid)
- self.assertAddressSelected(self.test_ip, self.test_netid)
-
-
-class ValidBeforeOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
-
- def testModifiedRfc6724Behaviour(self):
- # [3] Add a valid IPv6 address to this interface and verify it is
- # selected as the source address.
- preferred_ip = self.IPv6Prefix(self.test_netid) + "cafe"
- self.iproute.AddAddress(preferred_ip, 64, self.test_ifindex)
- self.assertAddressHasExpectedAttributes(
- preferred_ip, self.test_ifindex, iproute.IFA_F_PERMANENT)
- self.assertEquals(preferred_ip, self.GetSourceIP(self.test_netid))
-
- # [4] Get another IPv6 address, in optimistic DAD start-up.
- self.SetDAD(self.test_ifname, 1) # Enable DAD
- self.SetOptimisticDAD(self.test_ifname, 1)
- self.SetUseOptimistic(self.test_ifname, 1)
- # Send a RA to start SLAAC and subsequent DAD.
- self.SendRA(self.test_netid, 0)
- # Get flags and prove optimism.
- self.assertAddressHasExpectedAttributes(
- self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-
- # Since the interface has another IPv6 address, the optimistic address
- # is not selected--the other, valid address is chosen.
- self.assertAddressUsable(self.test_ip, self.test_netid)
- self.assertAddressNotSelected(self.test_ip, self.test_netid)
- self.assertAddressSelected(preferred_ip, self.test_netid)
-
-
-class DadFailureTest(MultiInterfaceSourceAddressSelectionTest):
-
- def testDadFailure(self):
- # [3] Get an IPv6 address back, in optimistic DAD start-up.
- self.SetDAD(self.test_ifname, 1) # Enable DAD
- self.SetOptimisticDAD(self.test_ifname, 1)
- self.SetUseOptimistic(self.test_ifname, 1)
- # Send a RA to start SLAAC and subsequent DAD.
- self.SendRA(self.test_netid, 0)
- # Prove optimism and usability.
- self.assertAddressHasExpectedAttributes(
- self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
- self.assertAddressUsable(self.test_ip, self.test_netid)
- self.assertAddressSelected(self.test_ip, self.test_netid)
-
- # Send a NA for the optimistic address, indicating address conflict
- # ("DAD defense").
- conflict_macaddr = "02:00:0b:ad:d0:0d"
- dad_defense = (scapy.Ether(src=conflict_macaddr, dst="33:33:33:00:00:01") /
- scapy.IPv6(src=self.test_ip, dst="ff02::1") /
- scapy.ICMPv6ND_NA(tgt=self.test_ip, R=0, S=0, O=1) /
- scapy.ICMPv6NDOptDstLLAddr(lladdr=conflict_macaddr))
- self.ReceiveEtherPacketOn(self.test_netid, dad_defense)
-
- # The address should have failed DAD, and therefore no longer be usable.
- self.assertAddressNotUsable(self.test_ip, self.test_netid)
- self.assertAddressNotSelected(self.test_ip, self.test_netid)
-
- # TODO(ek): verify that an RTM_DELADDR issued for the DAD-failed address.
-
-
-class NoNsFromOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
-
- def testSendToOnlinkDestination(self):
- # [3] Get an IPv6 address back, in optimistic DAD start-up.
- self.SetDAD(self.test_ifname, 1) # Enable DAD
- self.SetOptimisticDAD(self.test_ifname, 1)
- self.SetUseOptimistic(self.test_ifname, 1)
- # Send a RA to start SLAAC and subsequent DAD.
- self.SendRA(self.test_netid, 0)
- # Prove optimism and usability.
- self.assertAddressHasExpectedAttributes(
- self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
- self.assertAddressUsable(self.test_ip, self.test_netid)
- self.assertAddressSelected(self.test_ip, self.test_netid)
-
- # [4] Send to an on-link destination and observe a Neighbor Solicitation
- # packet with a source address that is NOT the optimistic address.
- # In this setup, the only usable address is the link-local address.
- onlink_dest = self.GetRandomDestination(self.IPv6Prefix(self.test_netid))
- self.SendWithSourceAddress(self.test_ip, self.test_netid, onlink_dest)
-
- if net_test.LinuxVersion() >= (3, 18, 0):
- # Older versions will actually choose the optimistic address to
- # originate Neighbor Solications (RFC violation).
- expected_ns = packets.NS(
- self.test_lladdr,
- onlink_dest,
- self.MyMacAddress(self.test_netid))[1]
- self.ExpectPacketOn(self.test_netid, "link-local NS", expected_ns)
-
-
-# TODO(ek): add tests listening for netlink events.
-
-
-class DefaultCandidateSrcAddrsTest(MultiInterfaceSourceAddressSelectionTest):
-
- def testChoosesNonInterfaceSourceAddress(self):
- self.SetIPv6Sysctl(self.test_ifname, "use_oif_addrs_only", 0)
- src_ip = self.GetSourceIP(self.test_netid)
- self.assertFalse(src_ip in [self.test_ip, self.test_lladdr])
- self.assertTrue(src_ip in
- [self.MyAddress(6, netid)
- for netid in self.tuns if netid != self.test_netid])
-
-
-class RestrictedCandidateSrcAddrsTest(MultiInterfaceSourceAddressSelectionTest):
-
- def testChoosesOnlyInterfaceSourceAddress(self):
- self.SetIPv6Sysctl(self.test_ifname, "use_oif_addrs_only", 1)
- # self.test_ifname does not have a global IPv6 address, so the only
- # candidate is the existing link-local address.
- self.assertAddressSelected(self.test_lladdr, self.test_netid)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/net_test/tcp_nuke_addr_test.py b/tests/net_test/tcp_nuke_addr_test.py
deleted file mode 100755
index b0ba27d..0000000
--- a/tests/net_test/tcp_nuke_addr_test.py
+++ /dev/null
@@ -1,250 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import contextlib
-import errno
-import fcntl
-import resource
-import os
-from socket import * # pylint: disable=wildcard-import
-import struct
-import threading
-import time
-import unittest
-
-import csocket
-import cstruct
-import net_test
-
-IPV4_LOOPBACK_ADDR = "127.0.0.1"
-IPV6_LOOPBACK_ADDR = "::1"
-LOOPBACK_DEV = "lo"
-LOOPBACK_IFINDEX = 1
-
-SIOCKILLADDR = 0x8939
-
-DEFAULT_TCP_PORT = 8001
-DEFAULT_BUFFER_SIZE = 20
-DEFAULT_TEST_MESSAGE = "TCP NUKE ADDR TEST"
-DEFAULT_TEST_RUNS = 100
-HASH_TEST_RUNS = 4000
-HASH_TEST_NOFILE = 16384
-
-
-Ifreq = cstruct.Struct("Ifreq", "=16s16s", "name data")
-In6Ifreq = cstruct.Struct("In6Ifreq", "=16sIi", "addr prefixlen ifindex")
-
-@contextlib.contextmanager
-def RunInBackground(thread):
- """Starts a thread and waits until it joins.
-
- Args:
- thread: A not yet started threading.Thread object.
- """
- try:
- thread.start()
- yield thread
- finally:
- thread.join()
-
-
-def TcpAcceptAndReceive(listening_sock, buffer_size=DEFAULT_BUFFER_SIZE):
- """Accepts a single connection and blocks receiving data from it.
-
- Args:
- listening_socket: A socket in LISTEN state.
- buffer_size: Size of buffer where to read a message.
- """
- connection, _ = listening_sock.accept()
- with contextlib.closing(connection):
- _ = connection.recv(buffer_size)
-
-
-def ExchangeMessage(addr_family, ip_addr):
- """Creates a listening socket, accepts a connection and sends data to it.
-
- Args:
- addr_family: The address family (e.g. AF_INET6).
- ip_addr: The IP address (IPv4 or IPv6 depending on the addr_family).
- tcp_port: The TCP port to listen on.
- """
- # Bind to a random port and connect to it.
- test_addr = (ip_addr, 0)
- with contextlib.closing(
- socket(addr_family, SOCK_STREAM)) as listening_socket:
- listening_socket.bind(test_addr)
- test_addr = listening_socket.getsockname()
- listening_socket.listen(1)
- with RunInBackground(threading.Thread(target=TcpAcceptAndReceive,
- args=(listening_socket,))):
- with contextlib.closing(
- socket(addr_family, SOCK_STREAM)) as client_socket:
- client_socket.connect(test_addr)
- client_socket.send(DEFAULT_TEST_MESSAGE)
-
-
-def KillAddrIoctl(addr):
- """Calls the SIOCKILLADDR ioctl on the provided IP address.
-
- Args:
- addr The IP address to pass to the ioctl.
-
- Raises:
- ValueError: If addr is of an unsupported address family.
- """
- family, _, _, _, _ = getaddrinfo(addr, None, AF_UNSPEC, SOCK_DGRAM, 0,
- AI_NUMERICHOST)[0]
- if family == AF_INET6:
- addr = inet_pton(AF_INET6, addr)
- ifreq = In6Ifreq((addr, 128, LOOPBACK_IFINDEX)).Pack()
- elif family == AF_INET:
- addr = inet_pton(AF_INET, addr)
- sockaddr = csocket.SockaddrIn((AF_INET, 0, addr)).Pack()
- ifreq = Ifreq((LOOPBACK_DEV, sockaddr)).Pack()
- else:
- raise ValueError('Address family %r not supported.' % family)
- datagram_socket = socket(family, SOCK_DGRAM)
- fcntl.ioctl(datagram_socket.fileno(), SIOCKILLADDR, ifreq)
- datagram_socket.close()
-
-
-class ExceptionalReadThread(threading.Thread):
-
- def __init__(self, sock):
- self.sock = sock
- self.exception = None
- super(ExceptionalReadThread, self).__init__()
- self.daemon = True
-
- def run(self):
- try:
- read = self.sock.recv(4096)
- except Exception, e:
- self.exception = e
-
-# For convenience.
-def CreateIPv4SocketPair():
- return net_test.CreateSocketPair(AF_INET, SOCK_STREAM, IPV4_LOOPBACK_ADDR)
-
-def CreateIPv6SocketPair():
- return net_test.CreateSocketPair(AF_INET6, SOCK_STREAM, IPV6_LOOPBACK_ADDR)
-
-
-class TcpNukeAddrTest(net_test.NetworkTest):
-
- def testTimewaitSockets(self):
- """Tests that SIOCKILLADDR works as expected.
-
- Relevant kernel commits:
- https://www.codeaurora.org/cgit/quic/la/kernel/msm-3.18/commit/net/ipv4/tcp.c?h=aosp/android-3.10&id=1dcd3a1fa2fe78251cc91700eb1d384ab02e2dd6
- """
- for i in xrange(DEFAULT_TEST_RUNS):
- ExchangeMessage(AF_INET6, IPV6_LOOPBACK_ADDR)
- KillAddrIoctl(IPV6_LOOPBACK_ADDR)
- ExchangeMessage(AF_INET, IPV4_LOOPBACK_ADDR)
- KillAddrIoctl(IPV4_LOOPBACK_ADDR)
- # Test passes if kernel does not crash.
-
- def testClosesIPv6Sockets(self):
- """Tests that SIOCKILLADDR closes IPv6 sockets and unblocks threads."""
-
- threadpairs = []
-
- for i in xrange(DEFAULT_TEST_RUNS):
- clientsock, acceptedsock = CreateIPv6SocketPair()
- clientthread = ExceptionalReadThread(clientsock)
- clientthread.start()
- serverthread = ExceptionalReadThread(acceptedsock)
- serverthread.start()
- threadpairs.append((clientthread, serverthread))
-
- KillAddrIoctl(IPV6_LOOPBACK_ADDR)
-
- def CheckThreadException(thread):
- thread.join(100)
- self.assertFalse(thread.is_alive())
- self.assertIsNotNone(thread.exception)
- self.assertTrue(isinstance(thread.exception, IOError))
- self.assertEquals(errno.ETIMEDOUT, thread.exception.errno)
- self.assertRaisesErrno(errno.ENOTCONN, thread.sock.getpeername)
- self.assertRaisesErrno(errno.EISCONN, thread.sock.connect,
- (IPV6_LOOPBACK_ADDR, 53))
- self.assertRaisesErrno(errno.EPIPE, thread.sock.send, "foo")
-
- for clientthread, serverthread in threadpairs:
- CheckThreadException(clientthread)
- CheckThreadException(serverthread)
-
- def assertSocketsClosed(self, socketpair):
- for sock in socketpair:
- self.assertRaisesErrno(errno.ENOTCONN, sock.getpeername)
-
- def assertSocketsNotClosed(self, socketpair):
- for sock in socketpair:
- self.assertTrue(sock.getpeername())
-
- def testAddresses(self):
- socketpair = CreateIPv4SocketPair()
- KillAddrIoctl("::")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("::1")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("127.0.0.3")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("0.0.0.0")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("127.0.0.1")
- self.assertSocketsClosed(socketpair)
-
- socketpair = CreateIPv6SocketPair()
- KillAddrIoctl("0.0.0.0")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("127.0.0.1")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("::2")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("::")
- self.assertSocketsNotClosed(socketpair)
- KillAddrIoctl("::1")
- self.assertSocketsClosed(socketpair)
-
-
-class TcpNukeAddrHashTest(net_test.NetworkTest):
-
- def setUp(self):
- self.nofile = resource.getrlimit(resource.RLIMIT_NOFILE)
- resource.setrlimit(resource.RLIMIT_NOFILE, (HASH_TEST_NOFILE,
- HASH_TEST_NOFILE))
-
- def tearDown(self):
- resource.setrlimit(resource.RLIMIT_NOFILE, self.nofile)
-
- def testClosesAllSockets(self):
- socketpairs = []
- for i in xrange(HASH_TEST_RUNS):
- socketpairs.append(CreateIPv4SocketPair())
- socketpairs.append(CreateIPv6SocketPair())
-
- KillAddrIoctl(IPV4_LOOPBACK_ADDR)
- KillAddrIoctl(IPV6_LOOPBACK_ADDR)
-
- for socketpair in socketpairs:
- for sock in socketpair:
- self.assertRaisesErrno(errno.ENOTCONN, sock.getpeername)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/net_test/tcp_test.py b/tests/net_test/tcp_test.py
deleted file mode 100644
index 81a6884..0000000
--- a/tests/net_test/tcp_test.py
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/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)
diff --git a/tests/pagingtest/pageinout_test.c b/tests/pagingtest/pageinout_test.c
index b9b20de..887794e 100644
--- a/tests/pagingtest/pageinout_test.c
+++ b/tests/pagingtest/pageinout_test.c
@@ -1,5 +1,6 @@
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -8,12 +9,12 @@
#include "pagingtest.h"
-int pageinout_test(int test_runs, unsigned long long file_size) {
+int pageinout_test(int test_runs, bool cache, unsigned long long file_size) {
int fd;
char tmpname[] = "pageinoutXXXXXX";
unsigned char *vec;
int i;
- long long j;
+ unsigned long long j;
volatile char *buf;
int ret = -1;
int rc;
@@ -43,10 +44,19 @@
goto err;
}
+ if (!cache) {
+ //madvise and fadvise as random to prevent prefetching
+ rc = madvise((void *)buf, file_size, MADV_RANDOM) ||
+ posix_fadvise(fd, 0, file_size, POSIX_FADV_RANDOM);
+ if (rc) {
+ goto err;
+ }
+ }
+
for (i = 0; i < test_runs; i++) {
gettimeofday(&begin_time, NULL);
- //Read backwards to prevent mmap prefetching
- for (j = ((file_size - 1) & ~(pagesize - 1)); j >= 0; j -= pagesize) {
+ //read every page into the page cache
+ for (j = 0; j < file_size; j += pagesize) {
buf[j];
}
gettimeofday(&end_time, NULL);
@@ -75,9 +85,11 @@
}
}
- printf("page-in: %llu MB/s\n", (file_size * test_runs * USEC_PER_SEC) /
+ printf("%scached page-in: %llu MB/s\n", cache ? "" : "un",
+ (file_size * test_runs * USEC_PER_SEC) /
(1024 * 1024 * (total_time_in.tv_sec * USEC_PER_SEC + total_time_in.tv_usec)));
- printf("page-out (clean): %llu MB/s\n", (file_size * test_runs * USEC_PER_SEC) /
+ printf("%scached page-out (clean): %llu MB/s\n", cache ? "" : "un",
+ (file_size * test_runs * USEC_PER_SEC) /
(1024 * 1024 * (total_time_out.tv_sec * USEC_PER_SEC + total_time_out.tv_usec)));
ret = 0;
diff --git a/tests/pagingtest/pagingtest.c b/tests/pagingtest/pagingtest.c
index db8512c..158d8a3 100644
--- a/tests/pagingtest/pagingtest.c
+++ b/tests/pagingtest/pagingtest.c
@@ -2,7 +2,9 @@
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
@@ -16,6 +18,8 @@
int create_tmp_file(char *filename, off_t size) {
void *buf;
+ uint8_t *tmp_buf;
+ off_t tmp_size;
ssize_t rc;
int fd;
int urandom;
@@ -48,17 +52,19 @@
goto err_mmap;
}
- rc = read(urandom, buf, size);
+ tmp_buf = buf;
+ tmp_size = size;
+ do {
+ rc = read(urandom, tmp_buf, tmp_size);
- if (rc < 0) {
- fprintf(stderr, "write random data failed: %s\n", strerror(errno));
- goto err;
- }
+ if (rc < 0) {
+ fprintf(stderr, "write random data failed: %s\n", strerror(errno));
+ goto err;
+ }
- if (rc != size) {
- fprintf(stderr, "write random data incomplete\n");
- goto err;
- }
+ tmp_buf += rc;
+ tmp_size -= rc;
+ } while (tmp_size > 0);
if (madvise(buf, size, MADV_DONTNEED)) {
fprintf(stderr, "madvise DONTNEED failed: %s\n", strerror(errno));
@@ -161,11 +167,19 @@
if (rc) {
return rc;
}
- rc = pageinout_test(test_runs, file_size);
+ rc = pageinout_test(test_runs, true, file_size);
if (rc) {
return rc;
}
- rc = thrashing_test(test_runs);
+ rc = pageinout_test(test_runs, false, file_size);
+ if (rc) {
+ return rc;
+ }
+ rc = thrashing_test(test_runs, true);
+ if (rc) {
+ return rc;
+ }
+ rc = thrashing_test(test_runs, false);
return rc;
}
diff --git a/tests/pagingtest/pagingtest.h b/tests/pagingtest/pagingtest.h
index 2da9818..a6f3d03 100644
--- a/tests/pagingtest/pagingtest.h
+++ b/tests/pagingtest/pagingtest.h
@@ -14,7 +14,7 @@
//Tests
int mmap_test(int test_runs, unsigned long long alloc_size);
-int pageinout_test(int test_runs, unsigned long long file_size);
-int thrashing_test(int test_runs);
+int pageinout_test(int test_runs, bool cache, unsigned long long file_size);
+int thrashing_test(int test_runs, bool cache);
#endif //__PAGINGTEST_H__
diff --git a/tests/pagingtest/thrashing_test.c b/tests/pagingtest/thrashing_test.c
index 7ecd3ad..0f4547f 100644
--- a/tests/pagingtest/thrashing_test.c
+++ b/tests/pagingtest/thrashing_test.c
@@ -1,5 +1,6 @@
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -10,11 +11,11 @@
#define LINESIZE 32
-int thrashing_test(int test_runs) {
+int thrashing_test(int test_runs, bool cache) {
int fds[4] = {-1, -1, -1, -1};
char tmpnames[4][17] = { "thrashing1XXXXXX", "thrashing2XXXXXX", "thrashing3XXXXXX", "thrashing4XXXXXX" };
volatile char *bufs[4] = {0};
- long long k;
+ unsigned long long k;
int ret = -1;
struct timeval begin_time, end_time, elapsed_time, total_time;
unsigned long long filesize;
@@ -45,14 +46,20 @@
fprintf(stderr, "Failed to mmap file: %s\n", strerror(errno));
goto err;
}
+ if (!cache) {
+ //madvise and fadvise as random to prevent prefetching
+ ret = madvise((void *)bufs[i], filesize, MADV_RANDOM) ||
+ posix_fadvise(fds[i], 0, filesize, POSIX_FADV_RANDOM);
+ if (ret) {
+ goto err;
+ }
+ }
}
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
- for (k = ((filesize - 1) & ~(pagesize - 1)); k >= 0; k -= pagesize) {
+ for (k = 0; k < filesize; k += pagesize) {
bufs[j][k];
}
gettimeofday(&end_time, NULL);
@@ -62,7 +69,8 @@
}
}
- printf("thrashing: %llu MB/s\n", (filesize * ARRAY_SIZE(fds) * test_runs * USEC_PER_SEC) /
+ printf("%scached thrashing: %llu MB/s\n", cache ? "" : "un",
+ (filesize * ARRAY_SIZE(fds) * test_runs * USEC_PER_SEC) /
(1024 * 1024 * (total_time.tv_sec * USEC_PER_SEC + total_time.tv_usec)));
ret = 0;
diff --git a/tools/graph_lockdep_chains b/tools/graph_lockdep_chains
new file mode 100755
index 0000000..c0c11f4
--- /dev/null
+++ b/tools/graph_lockdep_chains
@@ -0,0 +1,284 @@
+#! /bin/sh
+progname="${0##*/}"
+progname="${progname%.sh}"
+
+usage() {
+ echo "Host side filter pipeline tool to convert kernel /proc/lockdep_chains via"
+ echo "graphviz into dependency chart for visualization. Watch out for any up-arrows"
+ echo "as they signify a circular dependency."
+ echo
+ echo "Usage: ${progname} [flags...] [regex...] < input-file > output-file"
+ echo
+ echo "flags:"
+ echo " --format={png|ps|svg|fig|imap|cmapx} | -T<format>"
+ echo " Output format, default png"
+ echo " --debug | -d"
+ echo " Leave intermediate files /tmp/${progname}.*"
+ echo " --verbose | -v"
+ echo " Do not strip address from lockname"
+ echo " --focus | -f"
+ echo " Show only primary references for regex matches"
+ echo " --cluster"
+ echo " Cluster the primary references for regex matches"
+ echo " --serial=<serial> | -s <serial>"
+ echo " Input from 'adb -s <serial> shell su 0 cat /proc/lockdep_chains'"
+ echo " --input=<filename> | -i <filename>"
+ echo " Input lockdeps from filename, otherwise from standard in"
+ echo " --output=<filename> | -o <filename>"
+ echo " Output formatted graph to filename, otherwise to standard out"
+ echo
+ echo "Chart is best viewed in portrait. ps or pdf formats tend to pixelate. png tends"
+ echo "to hit a bug in cairo rendering at scale. Not having a set of regex matches for"
+ echo "locknames will probably give you what you deserve ..."
+ echo
+ echo "Kernel Prerequisite to get /proc/lockdep_chains:"
+ echo " CONFIG_PROVE_LOCKING=y"
+ echo " CONFIG_LOCK_STAT=y"
+ echo " CONFIG_DEBUG_LOCKDEP=y"
+}
+
+rm -f /tmp/${progname}.*
+
+# Indent rules and strip out address (may be overridden below)
+beautify() {
+ sed 's/^./ &/
+ s/"[[][0-9a-f]*[]] /"/g'
+}
+
+input="cat -"
+output="cat -"
+
+dot_format="-Tpng"
+filter=
+debug=
+focus=
+cluster=
+
+while [ ${#} -gt 0 ]; do
+ case ${1} in
+
+ -T | --format)
+ dot_format="-T${2}"
+ shift
+ ;;
+
+ -T*)
+ dot_format="${1}"
+ ;;
+
+ --format=*)
+ dot_format="-T${1#--format=}"
+ ;;
+
+ --debug | -d)
+ debug=1
+ ;;
+
+ --verbose | -v)
+ # indent, but do _not_ strip out addresses
+ beautify() {
+ sed 's/^./ &/'
+ }
+ ;;
+
+ --focus | -f | --primary) # reserving --primary
+ focus=1
+ ;;
+
+ --secondary) # reserving --secondary
+ focus=
+ ;;
+
+ --cluster) # reserve -c for dot (configure plugins)
+ cluster=1
+ ;;
+
+ --serial | -s)
+ if [ "${input}" != "cat -" ]; then
+ usage >&2
+ echo "ERROR: --input or --serial can only be specified once" >&2
+ exit 1
+ fi
+ input="adb -s ${2} shell su 0 cat /proc/lockdep_chains"
+ shift
+ ;;
+
+ --serial=*)
+ input="adb -s ${1#--serial=} shell su 0 cat /proc/lockdep_chains"
+ ;;
+
+ --input | -i)
+ if [ "${input}" != "cat -" ]; then
+ usage >&2
+ echo "ERROR: --input or --serial can only be specified once" >&2
+ exit 1
+ fi
+ input="cat ${2}"
+ shift
+ ;;
+
+ --input=*)
+ if [ "${input}" != "cat -" ]; then
+ usage >&2
+ echo "ERROR: --input or --serial can only be specified once" >&2
+ exit 1
+ fi
+ input="cat ${1#--input=}"
+ ;;
+
+ --output | -o)
+ if [ "${output}" != "cat -" ]; then
+ usage >&2
+ echo "ERROR: --output can only be specified once" >&2
+ exit 1
+ fi
+ output="cat - > ${2}" # run through eval
+ shift
+ ;;
+
+ --output=*)
+ if [ "${output}" != "cat -" ]; then
+ usage >&2
+ echo "ERROR: --output can only be specified once" >&2
+ exit 1
+ fi
+ output="cat - > ${1#--output=}" # run through eval
+ ;;
+
+ --help | -h | -\?)
+ usage
+ exit
+ ;;
+
+ *)
+ # Everything else is a filter, which will also hide bad option flags,
+ # which is an as-designed price we pay to allow "->rwlock" for instance.
+ if [ X"${1}" = X"${1#* }" ]; then
+ if [ -z "${filter}" ]; then
+ filter="${1}"
+ else
+ filter="${filter}|${1}"
+ fi
+ else
+ if [ -z "${filter}" ]; then
+ filter=" ${1}"
+ else
+ filter="${filter}| ${1}"
+ fi
+ fi
+ ;;
+
+ esac
+ shift
+done
+
+if [ -z "${filter}" ]; then
+ echo "WARNING: no regex specified will give you what you deserve!" >&2
+fi
+if [ -n "${focus}" -a -z "${filter}" ]; then
+ echo "WARNING: --focus without regex, ignored" >&2
+fi
+if [ -n "${cluster}" -a -z "${filter}" ]; then
+ echo "WARNING: --cluster without regex, ignored" >&2
+fi
+if [ -n "${cluster}" -a -n "${focus}" -a -n "${filter}" ]; then
+ echo "WARNING: orthogonal options --cluster & --focus, ignoring --cluster" >&2
+ cluster=
+fi
+
+# convert to dot digraph series
+${input} |
+ sed '/^all lock chains:$/d
+ / [&]__lockdep_no_validate__$/d
+ /irq_context: 0/d
+ s/irq_context: [1-9]/irq_context/
+ s/..*/"&" ->/
+ s/^$/;/' |
+ sed ': loop
+ N
+ s/ ->\n;$/ ;/
+ t
+ s/ ->\n/ -> /
+ b loop' > /tmp/${progname}.formed
+
+if [ ! -s /tmp/${progname}.formed ]; then
+ echo "ERROR: no input" >&2
+ if [ -z "${debug}" ]; then
+ rm -f /tmp/${progname}.*
+ fi
+ exit 2
+fi
+
+if [ -n "${filter}" ]; then
+ grep "${filter}" /tmp/${progname}.formed |
+ sed 's/ ;//
+ s/ -> /|/g' |
+ tr '|' '\n' |
+ sort -u > /tmp/${progname}.symbols
+fi
+
+(
+ echo 'digraph G {'
+ (
+ echo 'remincross="true";'
+ echo 'concentrate="true";'
+ echo
+
+ if [ -s /tmp/${progname}.symbols ]; then
+ if [ -n "${cluster}" ]; then
+ echo 'subgraph cluster_symbols {'
+ (
+ grep "${filter}" /tmp/${progname}.symbols |
+ sed 's/.*/& [shape=box] ;/'
+ grep -v "${filter}" /tmp/${progname}.symbols |
+ sed 's/.*/& [shape=diamond] ;/'
+ ) | beautify
+ echo '}'
+ else
+ grep "${filter}" /tmp/${progname}.symbols |
+ sed 's/.*/& [shape=box] ;/'
+ grep -v "${filter}" /tmp/${progname}.symbols |
+ sed 's/.*/& [shape=diamond] ;/'
+ fi
+
+ echo
+ fi
+ ) | beautify
+
+ if [ -s /tmp/${progname}.symbols ]; then
+ if [ -z "${focus}" ]; then
+ # Secondary relationships
+ fgrep -f /tmp/${progname}.symbols /tmp/${progname}.formed
+ else
+ # Focus only on primary relationships
+ grep "${filter}" /tmp/${progname}.formed
+ fi
+ else
+ cat /tmp/${progname}.formed
+ fi |
+ # optimize int A -> B ; single references
+ sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' |
+ sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' |
+ tr '|' '\n' |
+ beautify |
+ grep ' -> ' |
+ sort -u |
+ if [ -s /tmp/${progname}.symbols ]; then
+ beautify < /tmp/${progname}.symbols |
+ sed 's/^ */ /' > /tmp/${progname}.short
+ tee /tmp/${progname}.split |
+ fgrep -f /tmp/${progname}.short |
+ sed 's/ ;$/ [color=red] ;/'
+ fgrep -v -f /tmp/${progname}.short /tmp/${progname}.split
+ rm -f /tmp/${progname}.short /tmp/${progname}.split
+ else
+ cat -
+ fi
+
+ echo '}'
+) |
+ tee /tmp/${progname}.input |
+ if dot ${dot_format} && [ -z "${debug}" ]; then
+ rm -f /tmp/${progname}.*
+ fi |
+ eval ${output}
diff --git a/verity/Android.mk b/verity/Android.mk
index 7b3d13f..ffa4a2c 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -18,7 +18,7 @@
LOCAL_SRC_FILES := generate_verity_key.c
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
-LOCAL_SHARED_LIBRARIES := libcrypto-host
+LOCAL_SHARED_LIBRARIES := libcrypto_utils libcrypto-host
include $(BUILD_HOST_EXECUTABLE)
include $(CLEAR_VARS)
@@ -46,14 +46,6 @@
include $(BUILD_HOST_JAVA_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := BootSignature.java KeystoreSigner.java Utils.java
-LOCAL_MODULE := BootKeystoreSigner
-LOCAL_JAR_MANIFEST := KeystoreSigner.mf
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
LOCAL_SRC_FILES := verity_verifier
LOCAL_MODULE := verity_verifier
LOCAL_MODULE_CLASS := EXECUTABLES
@@ -81,15 +73,6 @@
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := keystore_signer
-LOCAL_MODULE := keystore_signer
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_IS_HOST_MODULE := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_REQUIRED_MODULES := KeystoreSigner
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
LOCAL_MODULE := build_verity_metadata.py
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := build_verity_metadata.py
diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java
deleted file mode 100644
index 0927d54..0000000
--- a/verity/KeystoreSigner.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.verity;
-
-import java.io.IOException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.Signature;
-import java.security.cert.X509Certificate;
-import java.util.Enumeration;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Integer;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERPrintableString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RSAPublicKey;
-import org.bouncycastle.asn1.util.ASN1Dump;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
-/**
- * AndroidVerifiedBootKeystore DEFINITIONS ::=
- * BEGIN
- * FormatVersion ::= INTEGER
- * KeyBag ::= SEQUENCE {
- * Key ::= SEQUENCE {
- * AlgorithmIdentifier ::= SEQUENCE {
- * algorithm OBJECT IDENTIFIER,
- * parameters ANY DEFINED BY algorithm OPTIONAL
- * }
- * KeyMaterial ::= RSAPublicKey
- * }
- * }
- * Signature ::= AndroidVerifiedBootSignature
- * END
- */
-
-class BootKey extends ASN1Object
-{
- private AlgorithmIdentifier algorithmIdentifier;
- private RSAPublicKey keyMaterial;
-
- public BootKey(PublicKey key) throws Exception {
- java.security.interfaces.RSAPublicKey k =
- (java.security.interfaces.RSAPublicKey) key;
- this.keyMaterial = new RSAPublicKey(
- k.getModulus(),
- k.getPublicExponent());
- this.algorithmIdentifier = Utils.getSignatureAlgorithmIdentifier(key);
- }
-
- public ASN1Primitive toASN1Primitive() {
- ASN1EncodableVector v = new ASN1EncodableVector();
- v.add(algorithmIdentifier);
- v.add(keyMaterial);
- return new DERSequence(v);
- }
-
- public void dump() throws Exception {
- System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
- }
-}
-
-class BootKeystore extends ASN1Object
-{
- private ASN1Integer formatVersion;
- private ASN1EncodableVector keyBag;
- private BootSignature signature;
- private X509Certificate certificate;
-
- private static final int FORMAT_VERSION = 0;
-
- public BootKeystore() {
- this.formatVersion = new ASN1Integer(FORMAT_VERSION);
- this.keyBag = new ASN1EncodableVector();
- }
-
- public void addPublicKey(byte[] der) throws Exception {
- PublicKey pubkey = Utils.loadDERPublicKey(der);
- BootKey k = new BootKey(pubkey);
- keyBag.add(k);
- }
-
- public void setCertificate(X509Certificate cert) {
- certificate = cert;
- }
-
- public byte[] getInnerKeystore() throws Exception {
- ASN1EncodableVector v = new ASN1EncodableVector();
- v.add(formatVersion);
- v.add(new DERSequence(keyBag));
- return new DERSequence(v).getEncoded();
- }
-
- public ASN1Primitive toASN1Primitive() {
- ASN1EncodableVector v = new ASN1EncodableVector();
- v.add(formatVersion);
- v.add(new DERSequence(keyBag));
- v.add(signature);
- return new DERSequence(v);
- }
-
- public void parse(byte[] input) throws Exception {
- ASN1InputStream stream = new ASN1InputStream(input);
- ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
-
- formatVersion = (ASN1Integer) sequence.getObjectAt(0);
- if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
- throw new IllegalArgumentException("Unsupported format version");
- }
-
- ASN1Sequence keys = (ASN1Sequence) sequence.getObjectAt(1);
- Enumeration e = keys.getObjects();
- while (e.hasMoreElements()) {
- keyBag.add((ASN1Encodable) e.nextElement());
- }
-
- ASN1Object sig = sequence.getObjectAt(2).toASN1Primitive();
- signature = new BootSignature(sig.getEncoded());
- }
-
- public boolean verify() throws Exception {
- byte[] innerKeystore = getInnerKeystore();
- return Utils.verify(signature.getPublicKey(), innerKeystore,
- signature.getSignature(), signature.getAlgorithmIdentifier());
- }
-
- public void sign(PrivateKey privateKey) throws Exception {
- byte[] innerKeystore = getInnerKeystore();
- byte[] rawSignature = Utils.sign(privateKey, innerKeystore);
- signature = new BootSignature("keystore", innerKeystore.length);
- signature.setCertificate(certificate);
- signature.setSignature(rawSignature,
- Utils.getSignatureAlgorithmIdentifier(privateKey));
- }
-
- public void dump() throws Exception {
- System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
- }
-
- private static void usage() {
- System.err.println("usage: KeystoreSigner <privatekey.pk8> " +
- "<certificate.x509.pem> <outfile> <publickey0.der> " +
- "... <publickeyN-1.der> | -verify <keystore>");
- System.exit(1);
- }
-
- public static void main(String[] args) throws Exception {
- if (args.length < 2) {
- usage();
- return;
- }
-
- Security.addProvider(new BouncyCastleProvider());
- BootKeystore ks = new BootKeystore();
-
- if ("-verify".equals(args[0])) {
- ks.parse(Utils.read(args[1]));
-
- try {
- if (ks.verify()) {
- System.err.println("Signature is VALID");
- System.exit(0);
- } else {
- System.err.println("Signature is INVALID");
- }
- } catch (Exception e) {
- e.printStackTrace(System.err);
- }
- System.exit(1);
- } else {
- String privkeyFname = args[0];
- String certFname = args[1];
- String outfileFname = args[2];
-
- ks.setCertificate(Utils.loadPEMCertificate(certFname));
-
- for (int i = 3; i < args.length; i++) {
- ks.addPublicKey(Utils.read(args[i]));
- }
-
- ks.sign(Utils.loadDERPrivateKeyFromFile(privkeyFname));
- Utils.write(ks.getEncoded(), outfileFname);
- }
- }
-}
diff --git a/verity/KeystoreSigner.mf b/verity/KeystoreSigner.mf
deleted file mode 100644
index 472b7c4..0000000
--- a/verity/KeystoreSigner.mf
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.verity.BootKeystore
diff --git a/verity/fec/Android.mk b/verity/fec/Android.mk
index c13f577..0b742c1 100644
--- a/verity/fec/Android.mk
+++ b/verity/fec/Android.mk
@@ -11,6 +11,7 @@
LOCAL_STATIC_LIBRARIES := \
libsparse_host \
libz \
+ libcrypto_utils_static \
libcrypto_static \
libfec_host \
libfec_rs_host \
@@ -29,6 +30,7 @@
LOCAL_SRC_FILES := main.cpp image.cpp
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_LIBRARIES := \
+ libcrypto_utils_static \
libcrypto_static \
libfec \
libfec_rs \
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
index 0da978f..c598afb 100644
--- a/verity/generate_verity_key.c
+++ b/verity/generate_verity_key.c
@@ -23,11 +23,7 @@
#include <sys/types.h>
#include <unistd.h>
-/* HACK: we need the RSAPublicKey struct
- * but RSA_verify conflits with openssl */
-#define RSA_verify RSA_verify_mincrypt
-#include "mincrypt/rsa.h"
-#undef RSA_verify
+#include <crypto_utils/android_pubkey.h>
#include <openssl/evp.h>
#include <openssl/objects.h>
@@ -35,58 +31,9 @@
#include <openssl/rsa.h>
#include <openssl/sha.h>
-// Convert OpenSSL RSA private key to android pre-computed RSAPublicKey format.
-// Lifted from secure adb's mincrypt key generation.
-static int convert_to_mincrypt_format(RSA *rsa, RSAPublicKey *pkey)
-{
- int ret = -1;
- unsigned int i;
-
- if (RSA_size(rsa) != RSANUMBYTES)
- goto out;
-
- BN_CTX* ctx = BN_CTX_new();
- BIGNUM* r32 = BN_new();
- BIGNUM* rr = BN_new();
- BIGNUM* r = BN_new();
- BIGNUM* rem = BN_new();
- BIGNUM* n = BN_new();
- BIGNUM* n0inv = BN_new();
-
- BN_set_bit(r32, 32);
- BN_copy(n, rsa->n);
- BN_set_bit(r, RSANUMWORDS * 32);
- BN_mod_sqr(rr, r, n, ctx);
- BN_div(NULL, rem, n, r32, ctx);
- BN_mod_inverse(n0inv, rem, r32, ctx);
-
- pkey->len = RSANUMWORDS;
- pkey->n0inv = 0 - BN_get_word(n0inv);
- for (i = 0; i < RSANUMWORDS; i++) {
- BN_div(rr, rem, rr, r32, ctx);
- pkey->rr[i] = BN_get_word(rem);
- BN_div(n, rem, n, r32, ctx);
- pkey->n[i] = BN_get_word(rem);
- }
- pkey->exponent = BN_get_word(rsa->e);
-
- ret = 0;
-
- BN_free(n0inv);
- BN_free(n);
- BN_free(rem);
- BN_free(r);
- BN_free(rr);
- BN_free(r32);
- BN_CTX_free(ctx);
-
-out:
- return ret;
-}
-
static int write_public_keyfile(RSA *private_key, const char *private_key_path)
{
- RSAPublicKey pkey;
+ uint8_t key_data[ANDROID_PUBKEY_ENCODED_SIZE];
BIO *bfile = NULL;
char *path = NULL;
int ret = -1;
@@ -94,14 +41,14 @@
if (asprintf(&path, "%s.pub", private_key_path) < 0)
goto out;
- if (convert_to_mincrypt_format(private_key, &pkey) < 0)
+ if (!android_pubkey_encode(private_key, key_data, sizeof(key_data)))
goto out;
bfile = BIO_new_file(path, "w");
if (!bfile)
goto out;
- BIO_write(bfile, &pkey, sizeof(pkey));
+ BIO_write(bfile, key_data, sizeof(key_data));
BIO_flush(bfile);
ret = 0;
diff --git a/verity/keystore_signer b/verity/keystore_signer
deleted file mode 100755
index 445f0c9..0000000
--- a/verity/keystore_signer
+++ /dev/null
@@ -1,8 +0,0 @@
-#! /bin/sh
-
-# Start-up script for KeystoreSigner
-
-KEYSTORESIGNER_HOME=`dirname "$0"`
-KEYSTORESIGNER_HOME=`dirname "$KEYSTORESIGNER_HOME"`
-
-java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/BootKeystoreSigner.jar "$@"