Chromium Code Reviews| Index: tools/android/heap_profiler/heap_dump.c |
| diff --git a/tools/android/heap_profiler/heap_dump.c b/tools/android/heap_profiler/heap_dump.c |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..aa4b113c5d53f5d849be4a85b8a65b92002cc9c2 |
| --- /dev/null |
| +++ b/tools/android/heap_profiler/heap_dump.c |
| @@ -0,0 +1,300 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +// The client dump tool for libheap_profiler. It attaches to a process (given |
| +// its pid) and dumps all the libheap_profiler tracking information in JSON. |
| +// The target process is frozen (SIGSTOP) while dumping, unless the -n opt. |
| +// is passed (in which case the caller is responsible for un/freezing). |
| +// The JSON output looks like this: |
| +// { |
| +// "total_allocated": 908748493, # Total bytes allocated and not freed. |
| +// "num_allocs": 37542, # Number of virtual memory areas (VMAs) |
|
Dai Mikurube (NOT FULLTIME)
2014/07/02 04:49:52
What do you mean by "alloc" here? To me, "alloc" s
Primiano Tucci (use gerrit)
2014/07/02 07:43:28
Right, let me clarify. The short version is: an al
Dai Mikurube (NOT FULLTIME)
2014/07/02 09:39:32
Hmm, okay, but I think you shouldn't use the word
Primiano Tucci (use gerrit)
2014/07/02 09:49:53
Hmm agree. I'll reword it.
Dai Mikurube (NOT FULLTIME)
2014/07/02 10:04:58
Sure. I agree with the basic idea. My suggestion i
|
| +// "num_stacks": 3723, # Number of allocation call-sites. |
| +// "allocs": # Optional. Printed only with the -x arg. |
| +// { |
| +// "beef1234": {"l": 17, "f": 1, "s": "1a"}, |
| +// ^ ^ ^ ^ Index of the corresponding entry in the |
| +// | | | next "stacks" section. Essentially a ref |
| +// | | | to the call site which created the vma. |
| +// | | | |
| +// | | +-------> Last arg of heap_profiler_alloc(). |
| +// | +----------------> Length of the VMA. |
| +// +-----------------------------> Start address of the VMA (hex). |
|
Dai Mikurube (NOT FULLTIME)
2014/07/02 04:49:52
Where does this start address point?
In my trial,
Primiano Tucci (use gerrit)
2014/07/02 07:43:28
I think it should be clear now. Very likely, that
|
| +// }, |
| +// "stacks": |
| +// { |
| +// "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]}, |
| +// ^ ^ ^ |
| +// | | +-----> Stack frames (absolute virtual addresses). |
| +// | +--------------> Bytes allocated and not freed by the call site. |
| +// +---------------------> Index of the entry (as for "allocs" xref). |
| +// Indexes are hex and might not be monotonic. |
| + |
| +#include <fcntl.h> |
| +#include <inttypes.h> |
| +#include <signal.h> |
| +#include <stdbool.h> |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| +#include <unistd.h> |
| +#include <sys/stat.h> |
| + |
| +#include "tools/android/heap_profiler/heap_profiler.h" |
| + |
| + |
| +static void lseek_abs(int fd, size_t off); |
| + |
| +static int pid; |
| + |
| + |
| +static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs) { |
| + HeapStats stats; |
| + |
| + // Look for the mmap which contains the HeapStats in the target process vmem. |
| + // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The |
| + // region furthermore starts with a magic marker to disambiguate. |
| + bool stats_mmap_found = false; |
| + for (;;) { |
| + char line[1024]; |
| + if (fgets(line, sizeof(line), fmaps) == NULL) |
| + break; |
| + |
| + uintptr_t start; |
| + uintptr_t end; |
| + char map_file[32]; |
| + int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s", |
| + &start, &end, map_file); |
| + const size_t size = end - start + 1; |
| + if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats)) |
| + continue; |
| + |
| + // The mmap looks promising. Let's check for the magic marker. |
| + lseek_abs(mem_fd, start); |
| + if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats)) |
| + continue; |
| + |
| + if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) { |
| + stats_mmap_found = true; |
| + break; |
| + } |
| + } |
| + |
| + if (!stats_mmap_found) { |
| + fprintf(stderr, "Could not find the HeapStats area. " |
| + "It looks like libheap_profiler is not loaded.\n"); |
| + return -1; |
| + } |
| + |
| + // Print JSON-formatted output. |
| + printf("{\n"); |
| + printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes); |
| + printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs); |
| + printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces); |
| + |
| + uint32_t dbg_counted_vmas = 0; |
| + size_t dbg_counted_total_alloc_bytes = 0; |
| + bool prepend_trailing_comma = false; // JSON syntax, I hate you. |
| + uint32_t i; |
| + |
| + // Dump the optional allocation table. |
| + if (dump_also_allocs) { |
| + printf(" \"allocs\": {"); |
| + lseek_abs(mem_fd, (uintptr_t) stats.allocs); |
| + for (i = 0; i < stats.max_allocs; ++i) { |
| + VMA vma; |
| + if (read(mem_fd, &vma, sizeof(vma)) != sizeof(vma)) { |
| + fprintf(stderr, "ERROR: cannot read allocation table\n"); |
| + perror("read"); |
| + return -1; |
| + } |
| + |
| + // Skip empty (i.e. freed) entries. |
| + if (vma.start == 0 && vma.end == 0) |
| + continue; |
| + |
| + if (vma.end < vma.start) { |
| + fprintf(stderr, "ERROR: found inconsistent vma.\n"); |
| + return -1; |
| + } |
| + |
| + size_t vma_size = vma.end - vma.start + 1; |
| + size_t stack_idx = ((uintptr_t) vma.st - (uintptr_t) stats.stack_traces) / |
| + sizeof(StacktraceEntry); |
| + dbg_counted_total_alloc_bytes += vma_size; |
| + ++dbg_counted_vmas; |
| + |
| + if (prepend_trailing_comma) |
| + printf(","); |
| + prepend_trailing_comma = true; |
| + printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}", |
| + vma.start, vma_size, vma.flags, stack_idx); |
| + } |
| + printf("},\n"); |
| + |
| + if (dbg_counted_vmas != stats.num_allocs) { |
| + fprintf(stderr, |
| + "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n", |
| + dbg_counted_vmas, stats.num_allocs); |
| + return -1; |
| + } |
| + |
| + if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { |
| + fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n", |
| + dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); |
| + return -1; |
| + } |
| + } |
| + |
| + // Dump the distinct stack traces. |
| + printf(" \"stacks\": {"); |
| + prepend_trailing_comma = false; |
| + dbg_counted_total_alloc_bytes = 0; |
| + lseek_abs(mem_fd, (uintptr_t) stats.stack_traces); |
| + for (i = 0; i < stats.max_stack_traces; ++i) { |
| + StacktraceEntry st; |
| + if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) { |
| + fprintf(stderr, "ERROR: cannot read stack trace table\n"); |
| + perror("read"); |
| + return -1; |
| + } |
| + |
| + // Skip empty (i.e. freed) entries. |
| + if (st.alloc_bytes == 0) |
| + continue; |
| + |
| + dbg_counted_total_alloc_bytes += st.alloc_bytes; |
| + |
| + if (prepend_trailing_comma) |
| + printf(","); |
| + prepend_trailing_comma = true; |
| + |
| + printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes); |
| + size_t n = 0; |
| + for (;;) { |
| + printf("%" PRIuPTR, st.frames[n]); |
| + ++n; |
| + if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0) |
| + break; |
| + else |
| + printf(","); |
| + } |
| + printf("]}"); |
| + } |
| + printf("}\n}\n"); |
| + |
| + if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) { |
| + fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n", |
| + dbg_counted_total_alloc_bytes, stats.total_alloc_bytes); |
| + return -1; |
| + } |
| + |
| + fflush(stdout); |
| + return 0; |
| +} |
| + |
| +// If the dump is interrupted, resume the target process before exiting. |
| +static void exit_handler() { |
| + kill(pid, SIGCONT); |
| + waitpid(pid, NULL, 0); |
| + exit(-1); |
| +} |
| + |
| +static bool freeze_process() { |
| + if (kill(pid, SIGSTOP) != 0) { |
| + fprintf(stderr, "Could not freeze the target process.\n"); |
| + perror("kill"); |
| + return false; |
| + } |
| + |
| + signal(SIGPIPE, exit_handler); |
| + signal(SIGINT, exit_handler); |
| + return true; |
| +} |
| + |
| +// Unfortunately lseek takes a *signed* offset, which is unsuitable for large |
| +// files like /proc/X/mem on 64-bit. |
| +static void lseek_abs(int fd, size_t off) { |
| +#define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1))) |
| + if (off <= OFF_T_MAX) { |
| + lseek(fd, (off_t) off, SEEK_SET); |
| + return; |
| + } |
| + lseek(fd, (off_t) OFF_T_MAX, SEEK_SET); |
| + lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR); |
| +} |
| + |
| +static int open_proc_mem_fd() { |
| + char path[64]; |
| + snprintf(path, sizeof(path), "/proc/%d/mem", pid); |
| + int mem_fd = open(path, O_RDONLY); |
| + if (mem_fd < 0) { |
| + fprintf(stderr, "Could not attach to target process virtual memory.\n"); |
| + perror("open"); |
| + } |
| + return mem_fd; |
| +} |
| + |
| +static FILE* open_proc_maps() { |
| + char path[64]; |
| + snprintf(path, sizeof(path), "/proc/%d/maps", pid); |
| + FILE* fmaps = fopen(path, "r"); |
| + if (fmaps == NULL) { |
| + fprintf(stderr, "Could not open %s.\n", path); |
| + perror("fopen"); |
| + } |
| + return fmaps; |
| +} |
| + |
| +int main(int argc, char** argv) { |
| + char c; |
| + int ret = 0; |
| + bool should_freeze_process = true; |
| + bool dump_also_allocs = false; |
| + |
| + |
| + while (((c = getopt(argc, argv, "nx")) & 0x80) == 0) { |
| + switch (c) { |
| + case 'n': |
| + should_freeze_process = false; |
| + break; |
| + case 'x': |
| + dump_also_allocs = true; |
| + break; |
| + } |
| + } |
| + |
| + if (optind >= argc) { |
| + printf("Usage: %s [-n] [-x] pid\n", argv[0]); |
| + return -1; |
| + } |
| + |
| + pid = atoi(argv[optind]); |
| + |
| + if (should_freeze_process && !freeze_process()) |
| + return -1; |
| + |
| + // Wait for the process to actually freeze. |
| + waitpid(pid, NULL, 0); |
| + |
| + int mem_fd = open_proc_mem_fd(); |
| + if (mem_fd < 0) |
| + ret = -1; |
| + |
| + FILE* fmaps = open_proc_maps(); |
| + if (fmaps == NULL) |
| + ret = -1; |
| + |
| + if (ret == 0) |
| + ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs); |
| + |
| + if (should_freeze_process) |
| + kill(pid, SIGCONT); |
| + |
| + // Cleanup. |
| + fflush(stdout); |
| + close(mem_fd); |
| + fclose(fmaps); |
| + return ret; |
| +} |