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

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

Issue 323893002: [Android] Introduce libheap_profiler for memory profiling. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 6 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
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 virtual memory areas (VMAs)
13 // "num_stacks": 3723, # Number of allocation call-sites.
14 // "allocs": # Optional. Printed only with the -x arg.
15 // {
16 // 1121435680: {"len": 17, "flags": 1, "stack": 0},
17 // ^ ^ ^ ^ Index of the corresponding entry
18 // | | | in the next "stacks" section. The
19 // | | | call site which created the vma.
20 // | | |
21 // | | +---------> Last arg of heap_profiler_alloc().
22 // | +---------------------> Length of the VMA.
23 // +-----------------------------------> Start address of the VMA.
24 // },
25 // "stacks":
26 // {
27 // 3: {"total": 17, "frames": [1074792772, 1100849864, 1100850688, ...]},
bulach 2014/06/09 17:23:28 I suppose this is running on the device, right? tr
Primiano Tucci (use gerrit) 2014/06/09 19:10:52 Oh, nice point. It's a lot of data actually (perha
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 might not be continuous or 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 #include "heap_profiler.h"
44
45
46 static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs);
47 static void lseek_abs(int fd, size_t off);
48
49 static int pid;
50
51 // If the dump is interrupted, resume the target process before exiting.
52 static void exit_handler() {
53 kill(pid, SIGCONT);
54 waitpid(pid, NULL, 0);
55 exit(-1);
56 }
57
58 int main(int argc, char** argv) {
bulach 2014/06/09 17:23:28 I'd suggest moving the main to the bottom, and the
Primiano Tucci (use gerrit) 2014/06/09 19:10:52 Done. To be honest in the long term I consdider me
59 char c;
60 bool should_stop_process = true;
61 bool dump_also_allocs = false;
62
63 while (((c = getopt(argc, argv, "nx")) & 0x80) == 0) {
64 switch (c) {
65 case 'n':
66 should_stop_process = false;
67 break;
68 case 'x':
69 dump_also_allocs = true;
70 break;
71 }
72 }
73
74 if (optind >= argc) {
75 printf("Usage: %s [-n] [-x] pid\n", argv[0]);
76 return -1;
77 }
78
79 pid = atoi(argv[optind]);
80
81 if (should_stop_process) {
82 if (kill(pid, SIGSTOP) != 0) {
83 fprintf(stderr, "Could not freeze the target process.\n");
84 perror("kill");
85 return -1;
86 }
87 signal(SIGPIPE, exit_handler);
88 signal(SIGINT, exit_handler);
bulach 2014/06/09 17:23:28 could split this into a few smaller functions, say
89 }
90
91 // Wait for the process to actually freeze.
92 waitpid(pid, NULL, 0);
93
94 int ret = 0;
95 char path[64];
96 snprintf(path, sizeof(path), "/proc/%d/mem", pid);
97 int mem_fd = open(path, O_RDONLY);
98 if (mem_fd < 0) {
99 fprintf(stderr, "Could not attach to target process virtual memory.\n");
100 perror("open");
101 ret = -1;
102 }
103
104 snprintf(path, sizeof(path), "/proc/%d/maps", pid);
105 FILE* fmaps = fopen(path, "r");
106 if (fmaps == NULL) {
107 fprintf(stderr, "Could not open %s.\n", path);
108 perror("fopen");
109 ret = -1;
110 }
bulach 2014/06/09 17:23:28 similarly, something like |get_mem_fd()|, and |get
Primiano Tucci (use gerrit) 2014/06/09 19:10:52 Done.
111
112 if (ret == 0)
113 ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs);
114
115 if (should_stop_process)
116 kill(pid, SIGCONT);
117
118 // Cleanup.
119 fflush(stdout);
120 close(mem_fd);
121 fclose(fmaps);
122 return ret;
123 }
124
125 static int dump_process_heap(int mem_fd, FILE* fmaps, bool dump_also_allocs) {
126 HeapStats stats;
127
128 // Look for the mmap which contains the HeapStats in the target process vmem.
129 // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The
130 // region furthermore starts with a magic marker to disambiguate.
131 bool stats_mmap_found = false;
132 for (;;) {
133 char line[1024];
134 if (fgets(line, sizeof(line), fmaps) == NULL)
135 break;
136
137 uintptr_t start;
138 uintptr_t end;
139 char map_file[32];
140 int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s",
141 &start, &end, map_file);
142 const size_t size = end - start + 1;
143 if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats))
144 continue;
145
146 // The mmap looks promising. Let's check for the magic marker.
147 lseek_abs(mem_fd, start);
148 if (read(mem_fd, &stats, sizeof(stats)) < sizeof(stats))
149 continue;
150
151 if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) {
152 stats_mmap_found = true;
153 break;
154 }
155 }
156
157 if (!stats_mmap_found) {
158 fprintf(stderr, "Could not find the HeapStats area. "
159 "It looks like libheap_profiler is not loaded.\n");
160 return -1;
161 }
162
163 // Print JSON-formatted output.
164 printf("{\n");
165 printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes);
166 printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs);
167 printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces);
168
169 uint32_t dbg_counted_vmas = 0;
170 size_t dbg_counted_total_alloc_bytes = 0;
171 bool prepend_trailing_comma = false; // JSON syntax, I hate you.
172 uint32_t i;
173
174 // Dump the optional allocation table.
175 if (dump_also_allocs) {
176 printf(" \"allocs\":\n");
177 printf(" {\n");
178 lseek_abs(mem_fd, (uintptr_t) stats.allocs);
179 for (i = 0; i < stats.max_allocs; ++i) {
180 VMA vma;
181 if (read(mem_fd, &vma, sizeof(vma)) != sizeof(vma)) {
182 fprintf(stderr, "ERROR: cannot read allocation table\n");
183 perror("read");
184 return -1;
185 }
186
187 // Skip empty (i.e. freed) entries.
188 if (vma.start == 0 && vma.end == 0)
189 continue;
190
191 if (vma.end < vma.start) {
192 fprintf(stderr, "ERROR: found inconsistent vma.\n");
193 return -1;
194 }
195
196 size_t vma_size = vma.end - vma.start + 1;
197 size_t stack_idx = ((uintptr_t) vma.st - (uintptr_t) stats.stack_traces) /
198 sizeof(StacktraceEntry);
199 dbg_counted_total_alloc_bytes += vma_size;
200 ++dbg_counted_vmas;
201
202 if (prepend_trailing_comma)
203 printf(",\n");
204 prepend_trailing_comma = true;
205 printf(" %"PRIuPTR": "
206 "{\"len\": %zu, \"flags\": %"PRIu32", \"stack\": %zu}",
207 vma.start, vma_size, vma.flags, stack_idx);
208 }
209 printf("\n },\n");
210
211 if (dbg_counted_vmas != stats.num_allocs) {
212 fprintf(stderr,
213 "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n",
214 dbg_counted_vmas, stats.num_allocs);
215 return -1;
216 }
217
218 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
219 fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n",
220 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
221 return -1;
222 }
223 }
224
225 // Dump the distinct stack traces.
226 printf(" \"stacks\":\n");
227 printf(" {\n");
228 prepend_trailing_comma = false;
229 dbg_counted_total_alloc_bytes = 0;
230 lseek_abs(mem_fd, (uintptr_t) stats.stack_traces);
231 for (i = 0; i < stats.max_stack_traces; ++i) {
232 StacktraceEntry st;
233 if (read(mem_fd, &st, sizeof(st)) != sizeof(st)) {
234 fprintf(stderr, "ERROR: cannot read stack trace table\n");
235 perror("read");
236 return -1;
237 }
238
239 // Skip empty (i.e. freed) entries.
240 if (st.alloc_bytes == 0)
241 continue;
242
243 dbg_counted_total_alloc_bytes += st.alloc_bytes;
244
245 if (prepend_trailing_comma)
246 printf(",\n");
247 prepend_trailing_comma = true;
248
249 printf(" %"PRIu32": {\"total\": %zu, \"frames\": [", i, st.alloc_bytes);
250 size_t n =
251 0;
252 for (;;) {
253 printf("%" PRIuPTR, st.frames[n]);
254 ++n;
255 if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0)
256 break;
257 else
258 printf(", ");
259 }
260 printf("]}");
261 }
262 printf("\n }\n");
263 printf("}\n");
264
265 if (dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
266 fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n",
267 dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
268 return -1;
269 }
270
271 fflush(stdout);
272 return 0;
273 }
274
275 // Unfortunately lseek takes a *signed* offset, which is unsuitable for large
276 // files like /proc/X/mem on 64-bit.
277 static void lseek_abs(int fd, size_t off) {
278 #define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1)))
279 if (off <= OFF_T_MAX) {
280 lseek(fd, (off_t) off, SEEK_SET);
281 return;
282 }
283 lseek(fd, (off_t) OFF_T_MAX, SEEK_SET);
284 lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR);
285 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698