Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: tools/android/heap_profiler/heap_dump.c

Issue 323893002: [Android] Introduce libheap_profiler for memory profiling. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: s/vma/alloc/ Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/android/heap_profiler/DEPS ('k') | tools/android/heap_profiler/heap_profiler.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // The client dump tool for libheap_profiler. It attaches to a process (given
6 // its pid) and dumps all the libheap_profiler tracking information in JSON.
7 // The target process is frozen (SIGSTOP) while dumping, unless the -n opt.
8 // is passed (in which case the caller is responsible for un/freezing).
9 // The JSON output looks like this:
10 // {
11 // "total_allocated": 908748493, # Total bytes allocated and not freed.
12 // "num_allocs": 37542, # Number of allocations.
13 // "num_stacks": 3723, # Number of allocation call-sites.
14 // "allocs": # Optional. Printed only with the -x arg.
15 // {
16 // "beef1234": {"l": 17, "f": 1, "s": "1a"},
17 // ^ ^ ^ ^ Index of the corresponding entry in the
18 // | | | next "stacks" section. Essentially a ref
19 // | | | to the call site that created the alloc.
20 // | | |
21 // | | +-------> Flags (last arg of heap_profiler_alloc).
22 // | +----------------> Length of the Alloc.
23 // +-----------------------------> Start address of the Alloc (hex).
24 // },
25 // "stacks":
26 // {
27 // "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]},
28 // ^ ^ ^
29 // | | +-----> Stack frames (absolute virtual addresses).
30 // | +--------------> Bytes allocated and not freed by the call site.
31 // +---------------------> Index of the entry (as for "allocs" xref).
32 // Indexes are hex and might not be monotonic.
33
34 #include <fcntl.h>
35 #include <inttypes.h>
36 #include <signal.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <sys/stat.h>
43
44 #include "tools/android/heap_profiler/heap_profiler.h"
45
46
47 static void lseek_abs(int fd, size_t off);
48
49 static int pid;
50
51
52 static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs) {
53 HeapStats stats;
54
55 // Look for the mmap which contains the HeapStats in the target process vmem.
56 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The
57 // region furthermore starts with a magic marker to disambiguate.
58 bool stats_mmap_found = false;
59 for (;;) {
60 char line[1024];
61 if (fgets(line, sizeof(line), fmaps) == NULL)
62 break;
63
64 uintptr_t start;
65 uintptr_t end;
66 char map_file[32];
67 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s",
68 &start, &end, map_file);
69 const size_t size = end - start + 1;
70 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats))
71 continue;
72
73 // The mmap looks promising. Let's check for the magic marker.
74 lseek_abs(mem_fd, start);
75 if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats))
76 continue;
77
78 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) {
79 stats_mmap_found = true;
80 break;
81 }
82 }
83
84 if (!stats_mmap_found) {
85 fprintf(stderr, "Could not find the HeapStats area. "
86 "It looks like libheap_profiler is not loaded.\n");
87 return -1;
88 }
89
90 // Print JSON-formatted output.
91 printf("{\n");
92 printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes);
93 printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs);
94 printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces);
95
96 uint32_t dbg_counted_allocs = 0;
97 size_t dbg_counted_total_alloc_bytes = 0;
98 bool prepend_trailing_comma = false; // JSON syntax, I hate you.
99 uint32_t i;
100
101 // Dump the optional allocation table.
102 if (dump_also_allocs) {
103 printf(" \"allocs\": {");
104 lseek_abs(mem_fd, (uintptr_t) stats.allocs);
105 for (i = 0; i < stats.max_allocs; ++i) {
106 Alloc alloc;
107 if (read(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) {
108 fprintf(stderr, "ERROR: cannot read allocation table\n");
109 perror("read");
110 return -1;
111 }
112
113 // Skip empty (i.e. freed) entries.
114 if (alloc.start == 0 && alloc.end == 0)
115 continue;
116
117 if (alloc.end < alloc.start) {
118 fprintf(stderr, "ERROR: found inconsistent alloc.\n");
119 return -1;
120 }
121
122 size_t alloc_size = alloc.end - alloc.start + 1;
123 size_t stack_idx = (
124 (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) /
125 sizeof(StacktraceEntry);
126 dbg_counted_total_alloc_bytes += alloc_size;
127 ++dbg_counted_allocs;
128
129 if (prepend_trailing_comma)
130 printf(",");
131 prepend_trailing_comma = true;
132 printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}",
133 alloc.start, alloc_size, alloc.flags, stack_idx);
134 }
135 printf("},\n");
136
137 if (dbg_counted_allocs != stats.num_allocs) {
138 fprintf(stderr,
139 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n",
140 dbg_counted_allocs, stats.num_allocs);
141 return -1;
142 }
143
144 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
145 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n",
146 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
147 return -1;
148 }
149 }
150
151 // Dump the distinct stack traces.
152 printf(" \"stacks\": {");
153 prepend_trailing_comma = false;
154 dbg_counted_total_alloc_bytes = 0;
155 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces);
156 for (i = 0; i < stats.max_stack_traces; ++i) {
157 StacktraceEntry st;
158 if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) {
159 fprintf(stderr, "ERROR: cannot read stack trace table\n");
160 perror("read");
161 return -1;
162 }
163
164 // Skip empty (i.e. freed) entries.
165 if (st.alloc_bytes == 0)
166 continue;
167
168 dbg_counted_total_alloc_bytes += st.alloc_bytes;
169
170 if (prepend_trailing_comma)
171 printf(",");
172 prepend_trailing_comma = true;
173
174 printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes);
175 size_t n = 0;
176 for (;;) {
177 printf("%" PRIuPTR, st.frames[n]);
178 ++n;
179 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0)
180 break;
181 else
182 printf(",");
183 }
184 printf("]}");
185 }
186 printf("}\n}\n");
187
188 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
189 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n",
190 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
191 return -1;
192 }
193
194 fflush(stdout);
195 return 0;
196 }
197
198 // If the dump is interrupted, resume the target process before exiting.
199 static void exit_handler() {
200 kill(pid, SIGCONT);
201 waitpid(pid, NULL, 0);
202 exit(-1);
203 }
204
205 static bool freeze_process() {
206 if (kill(pid, SIGSTOP) != 0) {
207 fprintf(stderr, "Could not freeze the target process.\n");
208 perror("kill");
209 return false;
210 }
211
212 signal(SIGPIPE, exit_handler);
213 signal(SIGINT, exit_handler);
214 return true;
215 }
216
217 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large
218 // files like /proc/X/mem on 64-bit.
219 static void lseek_abs(int fd, size_t off) {
220 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1)))
221 if (off <= OFF_T_MAX) {
222 lseek(fd, (off_t) off, SEEK_SET);
223 return;
224 }
225 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET);
226 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR);
227 }
228
229 static int open_proc_mem_fd() {
230 char path[64];
231 snprintf(path, sizeof(path), "/proc/%d/mem", pid);
232 int mem_fd = open(path, O_RDONLY);
233 if (mem_fd < 0) {
234 fprintf(stderr, "Could not attach to target process virtual memory.\n");
235 perror("open");
236 }
237 return mem_fd;
238 }
239
240 static FILE* open_proc_maps() {
241 char path[64];
242 snprintf(path, sizeof(path), "/proc/%d/maps", pid);
243 FILE* fmaps = fopen(path, "r");
244 if (fmaps == NULL) {
245 fprintf(stderr, "Could not open %s.\n", path);
246 perror("fopen");
247 }
248 return fmaps;
249 }
250
251 int main(int argc, char** argv) {
252 char c;
253 int ret = 0;
254 bool should_freeze_process = true;
255 bool dump_also_allocs = false;
256
257
258 while (((c = getopt(argc, argv, "nx")) & 0x80) == 0) {
259 switch (c) {
260 case 'n':
261 should_freeze_process = false;
262 break;
263 case 'x':
264 dump_also_allocs = true;
265 break;
266 }
267 }
268
269 if (optind >= argc) {
270 printf("Usage: %s [-n] [-x] pid\n", argv[0]);
271 return -1;
272 }
273
274 pid = atoi(argv[optind]);
275
276 if (should_freeze_process && !freeze_process())
277 return -1;
278
279 // Wait for the process to actually freeze.
280 waitpid(pid, NULL, 0);
281
282 int mem_fd = open_proc_mem_fd();
283 if (mem_fd < 0)
284 ret = -1;
285
286 FILE* fmaps = open_proc_maps();
287 if (fmaps == NULL)
288 ret = -1;
289
290 if (ret == 0)
291 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs);
292
293 if (should_freeze_process)
294 kill(pid, SIGCONT);
295
296 // Cleanup.
297 fflush(stdout);
298 close(mem_fd);
299 fclose(fmaps);
300 return ret;
301 }
OLDNEW
« no previous file with comments | « tools/android/heap_profiler/DEPS ('k') | tools/android/heap_profiler/heap_profiler.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698