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

Side by Side Diff: base/debug/trace_event_memory.cc

Issue 15418002: Record Chrome trace events in tcmalloc heap profiles (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: review comments 4 Created 7 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2013 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 #include "base/debug/trace_event_memory.h"
6
7 #include "base/debug/leak_annotations.h"
8 #include "base/debug/trace_event.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/message_loop.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/threading/thread_local.h"
16
17 // TODO(jamescook): Windows support for memory tracing.
18 #if !defined(NO_TCMALLOC) && !defined(OS_NACL) && (defined(OS_LINUX) || defined( OS_ANDROID))
jar (doing other things) 2013/07/04 02:18:30 nit: I think you can backslash break this line.
James Cook 2013/07/08 23:21:34 Done.
19 #define TRACE_MEMORY_SUPPORTED 1
20 #endif
21
22 #if defined(TRACE_MEMORY_SUPPORTED)
23 #include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
24 #endif
25
26 namespace base {
27 namespace debug {
28
29 namespace {
30
31 // Maximum number of nested TRACE_MEMORY scopes to record. Must be greater than
32 // or equal to HeapProfileTable::kMaxStackDepth.
33 const int kMaxStackSize = 32;
34
35 /////////////////////////////////////////////////////////////////////////////
36 // Holds a memory dump until the tracing system needs to serialize it.
37 class MemoryDumpHolder : public base::debug::ConvertableToTraceFormat {
38 public:
39 // Takes ownership of dump, which must be a JSON string, allocated with
40 // malloc() and NULL terminated.
41 explicit MemoryDumpHolder(char* dump) : dump_(dump) {}
42 virtual ~MemoryDumpHolder() { free(dump_); }
43
44 // base::debug::ConvertableToTraceFormat overrides:
45 virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE {
46 AppendHeapProfileAsTraceFormat(dump_, out);
47 }
48
49 private:
50 char* dump_;
51
52 DISALLOW_COPY_AND_ASSIGN(MemoryDumpHolder);
53 };
54
55 /////////////////////////////////////////////////////////////////////////////
56 // Records a stack of TRACE_MEMORY events. One per thread is required.
57 struct TraceMemoryStack {
58 TraceMemoryStack() : index_(0) {
59 memset(category_stack_, 0, kMaxStackSize * sizeof(const char*));
jar (doing other things) 2013/07/04 02:18:30 nit: Always try to use variable, rather than a raw
James Cook 2013/07/08 23:21:34 Ah, I had forgotten that part of the style guide.
60 }
61
62 // Points to the next free entry.
63 int index_;
64 const char* category_stack_[kMaxStackSize];
65 };
66
67 // One stack of TRACE_MEMORY event data per thread.
68 LazyInstance<ThreadLocalPointer<TraceMemoryStack> >::Leaky
69 g_trace_memory_stack = LAZY_INSTANCE_INITIALIZER;
70
71 // Returns the thread-local trace memory stack, initializing if necessary.
72 TraceMemoryStack* GetTraceMemoryStack() {
73 TraceMemoryStack* stack = g_trace_memory_stack.Get().Get();
74 if (stack)
75 return stack;
76 // Intentionally leak one stack per thread.
jar (doing other things) 2013/07/04 02:18:30 How does this play with worker threads? Is this r
77 TraceMemoryStack* leaked_stack = new TraceMemoryStack;
78 ANNOTATE_LEAKING_OBJECT_PTR(leaked_stack);
79 g_trace_memory_stack.Get().Set(leaked_stack);
80 return leaked_stack;
81 }
82
83 // Returns a "pseudo-stack" of pointers to trace events.
84 // TODO(jamescook): Record both category and name, perhaps in a pair for speed.
85 int GetPseudoStack(int skip_count_ignored, void** stack_out) {
86 TraceMemoryStack* stack = g_trace_memory_stack.Get().Get();
87 // If the tracing system isn't fully initialized, just skip this allocation.
88 // Attempting to initialize will allocate memory, causing this function to
89 // be called recursively from inside the allocator.
90 if (!stack)
91 return 0;
92 // Copy out a maximum of kMaxStackSize stack entries.
93 const int count =
94 stack->index_ < kMaxStackSize ? stack->index_ : kMaxStackSize;
jar (doing other things) 2013/07/04 02:18:30 nit: std::max()
James Cook 2013/07/08 23:21:34 Done (I actually need std::min here).
95 // Notes that memcpy() works for zero bytes.
96 memcpy(stack_out, stack->category_stack_, count * sizeof(void*));
jar (doing other things) 2013/07/04 02:18:30 nit: sizeof(stack->category_[0])
James Cook 2013/07/08 23:21:34 Done.
97 return count;
98 }
99
100 // Caller owns the returned char* and must release it with free().
101 char* TraceMemoryDumpAsString() {
102 #if defined(TRACE_MEMORY_SUPPORTED)
103 DVLOG(1) << "TraceMemoryDumpAsString";
104 return ::GetHeapProfile();
105 #else
106 NOTREACHED();
107 return NULL;
108 #endif
109 }
110
111 // If memory tracing is enabled, dumps a memory profile to the tracing system.
112 void DumpMemoryProfile() {
113 DVLOG(1) << "DumpMemoryProfile";
jar (doing other things) 2013/07/04 02:18:30 Do you care about allocations done by these logs?
James Cook 2013/07/08 23:21:34 Good point. Moved below the macro that disables tr
114 // Don't trace allocations here in the memory tracing system.
115 INTERNAL_TRACE_MEMORY(TRACE_DISABLED_BY_DEFAULT("memory"),
116 TRACE_MEMORY_IGNORE);
117 // Check to see if tracing is enabled for the memory category.
118 bool enabled;
119 TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("memory"),
120 &enabled);
121 if (enabled) {
jar (doing other things) 2013/07/04 02:18:30 nit: (personal?): early returns often help readabi
James Cook 2013/07/08 23:21:34 Done.
122 // MemoryDumpHolder takes ownership of this string.
123 char* dump = TraceMemoryDumpAsString();
124 scoped_ptr<MemoryDumpHolder> dump_holder(new MemoryDumpHolder(dump));
125 const int kSnapshotId = 1;
126 TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
127 TRACE_DISABLED_BY_DEFAULT("memory"),
128 "memory::Heap",
129 kSnapshotId,
130 dump_holder.PassAs<base::debug::ConvertableToTraceFormat>());
131 }
132 }
133
134 void TraceMemoryStart() {
135 #if defined(TRACE_MEMORY_SUPPORTED)
136 DVLOG(1) << "Starting trace memory";
137 // Ensure thread-local-storage is initialized.
138 GetTraceMemoryStack();
139 ::HeapProfilerWithPseudoStackStart(&GetPseudoStack);
140 #else
141 NOTREACHED();
142 #endif
143 }
144
145 void TraceMemoryStop() {
146 #if defined(TRACE_MEMORY_SUPPORTED)
147 DVLOG(1) << "Stopping trace memory";
148 ::HeapProfilerStop();
149 #else
150 NOTREACHED();
151 #endif
152 }
153
154 } // namespace
155
156 //////////////////////////////////////////////////////////////////////////////
157
158 TraceMemoryController::TraceMemoryController(
159 scoped_refptr<MessageLoopProxy> message_loop_proxy)
160 : message_loop_proxy_(message_loop_proxy),
161 weak_factory_(this) {
162 // Force the "memory" category to show up in the trace viewer.
163 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("memory"), "init");
164 // Allow this to be instantiated on unsupported platforms, but don't run.
165 #if defined(TRACE_MEMORY_SUPPORTED)
166 TraceLog::GetInstance()->AddEnabledStateObserver(this);
167 #endif
168 }
169
170 TraceMemoryController::~TraceMemoryController() {
171 #if defined(TRACE_MEMORY_SUPPORTED)
172 if (dump_timer_.IsRunning())
173 StopProfiling();
174 TraceLog::GetInstance()->RemoveEnabledStateObserver(this);
175 #endif
176 }
177
178 // base::debug::TraceLog::EnabledStateChangedObserver overrides:
179 void TraceMemoryController::OnTraceLogEnabled() {
180 DVLOG(1) << "OnTraceLogEnabled";
181 DCHECK(message_loop_proxy_->PostTask(
jar (doing other things) 2013/07/04 02:18:30 DCHECKs should never have side effects. If you me
182 FROM_HERE,
183 base::Bind(&TraceMemoryController::StartProfiling,
184 weak_factory_.GetWeakPtr())));
185 }
186
187 void TraceMemoryController::OnTraceLogDisabled() {
188 DVLOG(1) << "OnTraceLogDisabled";
189 DCHECK(message_loop_proxy_->PostTask(
190 FROM_HERE,
191 base::Bind(&TraceMemoryController::StopProfiling,
192 weak_factory_.GetWeakPtr())));
193 }
194
195 void TraceMemoryController::StartProfiling() {
196 // Watch for the tracing framework sending enabling more than once.
197 if (dump_timer_.IsRunning())
198 return;
199 TraceMemoryStart();
200 const int kDumpIntervalSeconds = 5;
201 dump_timer_.Start(FROM_HERE,
202 TimeDelta::FromSeconds(kDumpIntervalSeconds),
203 base::Bind(&DumpMemoryProfile));
204 }
205
206 void TraceMemoryController::StopProfiling() {
207 dump_timer_.Stop();
208 TraceMemoryStop();
209 }
210
211 bool TraceMemoryController::IsTimerRunningForTest() const {
212 return dump_timer_.IsRunning();
213 }
214
215 /////////////////////////////////////////////////////////////////////////////
216
217 ScopedTraceMemory::ScopedTraceMemory(const char* category) {
218 // Get our thread's copy of the stack.
219 TraceMemoryStack* stack = GetTraceMemoryStack();
220 const int index = stack->index_;
221 // Allow deep nesting of stacks (needed for tests), but only record
222 // |kMaxStackSize| entries.
223 if (index < kMaxStackSize)
224 stack->category_stack_[index] = category;
225 stack->index_++;
226 }
227
228 ScopedTraceMemory::~ScopedTraceMemory() {
229 // Get our thread's copy of the stack.
230 TraceMemoryStack* stack = g_trace_memory_stack.Get().Get();
231 stack->index_--;
232 DCHECK_GE(stack->index_, 0) << "stack underflow";
233 }
234
235 // static
236 int ScopedTraceMemory::GetStackIndexForTest() {
237 TraceMemoryStack* stack = GetTraceMemoryStack();
238 return stack->index_;
239 }
240
241 // static
242 const char* ScopedTraceMemory::GetItemForTest(int index) {
243 TraceMemoryStack* stack = GetTraceMemoryStack();
244 return stack->category_stack_[index];
245 }
246
247 /////////////////////////////////////////////////////////////////////////////
248
249 void AppendHeapProfileAsTraceFormat(const char* input, std::string* output) {
250 // Heap profile output has a header total line, then a list of stacks with
251 // memory totals, like this:
252 //
253 // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile
254 // 95: 40940 [ 649: 114260] @ 0x7fa7f4b3be13
255 // 77: 32546 [ 742: 106234] @
256 // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13
257 //
258 // MAPPED_LIBRARIES:
259 // 1be411fc1000-1be4139e4000 rw-p 00000000 00:00 0
260 // 1be4139e4000-1be4139e5000 ---p 00000000 00:00 0
261 // ...
262 //
263 // Skip input after MAPPED_LIBRARIES.
264 std::string input_string;
265 const char* mapped_libraries = strstr(input, "MAPPED_LIBRARIES");
266 if (mapped_libraries) {
267 input_string.assign(input, mapped_libraries - input);
268 } else {
269 input_string.assign(input);
270 }
271
272 std::vector<std::string> lines;
273 size_t line_count = Tokenize(input_string, "\n", &lines);
274 if (line_count == 0) {
275 DLOG(WARNING) << "No lines found";
276 return;
277 }
278
279 // Handle the initial summary line.
280 output->append("[");
281 AppendHeapProfileTotalsAsTraceFormat(lines[0], output);
282 output->append(",\n");
283
284 // Handle the following stack trace lines.
285 for (size_t i = 1; i < line_count; ++i) {
286 const std::string& line = lines[i];
287 bool added_entry = AppendHeapProfileLineAsTraceFormat(line, output);
288 if (added_entry)
289 output->append(",\n");
290 }
291 // Remove trailing ",\n".
292 output->resize(output->size() - 2);
293 output->append("]\n");
294 }
295
296 void AppendHeapProfileTotalsAsTraceFormat(const std::string& line,
297 std::string* output) {
298 // This is what a line looks like:
299 // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile
300 std::vector<std::string> tokens;
301 Tokenize(line, " :[]@", &tokens);
302 if (tokens.size() < 4) {
303 DLOG(WARNING) << "Invalid totals line " << line;
304 return;
305 }
306 DCHECK_EQ(tokens[0], "heap");
307 DCHECK_EQ(tokens[1], "profile");
308 output->append("{\"current_allocs\": ");
309 output->append(tokens[2]);
310 output->append(", \"current_bytes\": ");
311 output->append(tokens[3]);
312 output->append(", \"trace\": \"\"}");
313 }
314
315 bool AppendHeapProfileLineAsTraceFormat(const std::string& line,
316 std::string* output) {
317 // This is what a line looks like:
318 // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13
319 std::vector<std::string> tokens;
320 Tokenize(line, " :[]@", &tokens);
321 // It's valid to have no stack addresses, so only require 4 tokens.
322 if (tokens.size() < 4) {
323 DLOG(WARNING) << "Invalid line " << line;
324 return false;
325 }
326 // Don't bother with stacks that have no current allocations.
327 if (tokens[0] == "0")
328 return false;
329 output->append("{\"current_allocs\": ");
330 output->append(tokens[0]);
331 output->append(", \"current_bytes\": ");
332 output->append(tokens[1]);
333 output->append(", \"trace\": \"");
334
335 // Convert the "stack addresses" into strings.
336 const std::string kSingleQuote = "'";
337 for (size_t t = 4; t < tokens.size(); ++t) {
338 // Each stack address is a pointer to a constant trace name string.
339 uint64 address = 0;
340 if (!base::HexStringToUInt64(tokens[t], &address))
341 break;
342 // This is ugly but otherwise tcmalloc would need to gain a special output
343 // serializer for pseudo-stacks. Note that this cast also handles 64-bit to
344 // 32-bit conversion if necessary. Tests use a null address.
345 const char* trace_name =
346 address ? reinterpret_cast<const char*>(address) : "null";
347
348 // Some trace name strings have double quotes, convert them to single.
349 std::string trace_name_string(trace_name);
350 ReplaceChars(trace_name_string, "\"", kSingleQuote, &trace_name_string);
351
352 output->append(trace_name_string);
353
354 // Trace viewer expects a trailing space.
355 output->append(" ");
356 }
357 output->append("\"}");
358 return true;
359 }
360
361 } // namespace debug
362 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698