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

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: . 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_storage.h"
16
17 // TODO(jamescook): Windows support for memory tracing.
18 #if !defined(NO_TCMALLOC) && !defined(OS_NACL) && \
19 (defined(OS_LINUX) || defined(OS_ANDROID))
20 #define TRACE_MEMORY_SUPPORTED 1
21 #endif
22
23 #if defined(TRACE_MEMORY_SUPPORTED)
24 #include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
25 #endif
26
27 namespace base {
28 namespace debug {
29
30 namespace {
31
32 // Maximum number of nested TRACE_MEMORY scopes to record. Must be greater than
33 // or equal to HeapProfileTable::kMaxStackDepth.
34 const int kMaxStackSize = 32;
35
36 /////////////////////////////////////////////////////////////////////////////
37 // Holds a memory dump until the tracing system needs to serialize it.
38 class MemoryDumpHolder : public base::debug::ConvertableToTraceFormat {
39 public:
40 // Takes ownership of dump, which must be a JSON string, allocated with
41 // malloc() and NULL terminated.
42 explicit MemoryDumpHolder(char* dump) : dump_(dump) {}
43 virtual ~MemoryDumpHolder() { free(dump_); }
44
45 // base::debug::ConvertableToTraceFormat overrides:
46 virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE {
47 AppendHeapProfileAsTraceFormat(dump_, out);
48 }
49
50 private:
51 char* dump_;
52
53 DISALLOW_COPY_AND_ASSIGN(MemoryDumpHolder);
54 };
55
56 /////////////////////////////////////////////////////////////////////////////
57 // Records a stack of TRACE_MEMORY events. One per thread is required.
58 struct TraceMemoryStack {
59 TraceMemoryStack() : index_(0) {
60 memset(category_stack_, 0, kMaxStackSize * sizeof(category_stack_[0]));
61 }
62
63 // Points to the next free entry.
64 int index_;
jar (doing other things) 2013/07/12 01:24:26 nit: better is size_t
James Cook 2013/07/12 17:40:27 Done.
65 const char* category_stack_[kMaxStackSize];
66 };
67
68 // Pointer to a TraceMemoryStack per thread.
69 base::ThreadLocalStorage::StaticSlot tls_trace_memory_stack = TLS_INITIALIZER;
70
71 // Clean up memory pointed to by our thread-local storage.
72 void DeleteStackOnThreadCleanup(void* value) {
73 TraceMemoryStack* stack = static_cast<TraceMemoryStack*>(value);
74 delete stack;
75 }
76
77 // Initializes the thread-local TraceMemoryStack pointer. Returns true on
78 // success or if it is already initialized.
79 bool InitThreadLocalStorage() {
80 if (tls_trace_memory_stack.initialized())
81 return true;
82 // Initialize the thread-local storage key, returning true on success.
83 return tls_trace_memory_stack.Initialize(&DeleteStackOnThreadCleanup);
84 }
85
86 void CleanupThreadLocalStorage() {
87 if (tls_trace_memory_stack.initialized())
88 tls_trace_memory_stack.Free();
89 }
90
91 // Returns the thread-local trace memory stack for the current thread, creating
92 // one if needed. Returns NULL if the thread-local storage key isn't
93 // initialized, which indicates that heap profiling isn't running.
94 TraceMemoryStack* GetTraceMemoryStack() {
95 if (!tls_trace_memory_stack.initialized())
96 return NULL;
97 TraceMemoryStack* stack =
98 static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get());
99 // Lazily initialize TraceMemoryStack objects for new threads.
100 if (!stack) {
101 stack = new TraceMemoryStack;
102 tls_trace_memory_stack.Set(stack);
103 }
104 return stack;
105 }
106
107 // Returns a "pseudo-stack" of pointers to trace events.
108 // TODO(jamescook): Record both category and name, perhaps in a pair for speed.
109 int GetPseudoStack(int skip_count_ignored, void** stack_out) {
110 // If the tracing system isn't fully initialized, just skip this allocation.
111 // Attempting to initialize will allocate memory, causing this function to
112 // be called recursively from inside the allocator.
113 if (!tls_trace_memory_stack.initialized() || !tls_trace_memory_stack.Get())
114 return 0;
115 TraceMemoryStack* stack =
116 static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get());
117 // Copy at most kMaxStackSize stack entries.
118 const int count = std::min(stack->index_, kMaxStackSize);
119 // Notes that memcpy() works for zero bytes.
120 memcpy(stack_out,
121 stack->category_stack_,
122 count * sizeof(stack->category_stack_[0]));
123 return count;
124 }
125
126 // Caller owns the returned char* and must release it with free().
127 char* TraceMemoryDumpAsString() {
128 #if defined(TRACE_MEMORY_SUPPORTED)
129 DVLOG(1) << "TraceMemoryDumpAsString";
130 return ::GetHeapProfile();
131 #else
132 NOTREACHED();
133 return NULL;
134 #endif
135 }
136
137 // If memory tracing is enabled, dumps a memory profile to the tracing system.
138 void DumpMemoryProfile() {
139 // Don't trace allocations here in the memory tracing system.
140 INTERNAL_TRACE_MEMORY(TRACE_DISABLED_BY_DEFAULT("memory"),
141 TRACE_MEMORY_IGNORE);
142 DVLOG(1) << "DumpMemoryProfile";
143
144 // Check to see if tracing is enabled for the memory category.
145 bool enabled;
146 TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("memory"),
147 &enabled);
148 if (!enabled)
149 return;
150
151 // MemoryDumpHolder takes ownership of this string.
152 char* dump = TraceMemoryDumpAsString();
153 scoped_ptr<MemoryDumpHolder> dump_holder(new MemoryDumpHolder(dump));
154 const int kSnapshotId = 1;
155 TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
156 TRACE_DISABLED_BY_DEFAULT("memory"),
157 "memory::Heap",
158 kSnapshotId,
159 dump_holder.PassAs<base::debug::ConvertableToTraceFormat>());
160 }
161
162 void TraceMemoryStart() {
163 #if defined(TRACE_MEMORY_SUPPORTED)
164 DVLOG(1) << "Starting trace memory";
165 if (!InitThreadLocalStorage())
166 return;
167 ::HeapProfilerWithPseudoStackStart(&GetPseudoStack);
168 #else
169 NOTREACHED();
170 #endif
171 }
172
173 void TraceMemoryStop() {
174 #if defined(TRACE_MEMORY_SUPPORTED)
175 DVLOG(1) << "Stopping trace memory";
176 CleanupThreadLocalStorage();
177 ::HeapProfilerStop();
178 #else
179 NOTREACHED();
180 #endif
181 }
182
183 } // namespace
184
185 //////////////////////////////////////////////////////////////////////////////
186
187 TraceMemoryController::TraceMemoryController(
188 scoped_refptr<MessageLoopProxy> message_loop_proxy)
189 : message_loop_proxy_(message_loop_proxy),
190 weak_factory_(this) {
191 // Force the "memory" category to show up in the trace viewer.
192 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("memory"), "init");
193 // Allow this to be instantiated on unsupported platforms, but don't run.
194 #if defined(TRACE_MEMORY_SUPPORTED)
195 TraceLog::GetInstance()->AddEnabledStateObserver(this);
196 #endif
197 }
198
199 TraceMemoryController::~TraceMemoryController() {
200 #if defined(TRACE_MEMORY_SUPPORTED)
201 if (dump_timer_.IsRunning())
202 StopProfiling();
203 TraceLog::GetInstance()->RemoveEnabledStateObserver(this);
204 #endif
205 }
206
207 // base::debug::TraceLog::EnabledStateChangedObserver overrides:
208 void TraceMemoryController::OnTraceLogEnabled() {
209 DVLOG(1) << "OnTraceLogEnabled";
210 message_loop_proxy_->PostTask(
211 FROM_HERE,
212 base::Bind(&TraceMemoryController::StartProfiling,
213 weak_factory_.GetWeakPtr()));
214 }
215
216 void TraceMemoryController::OnTraceLogDisabled() {
217 DVLOG(1) << "OnTraceLogDisabled";
218 message_loop_proxy_->PostTask(
219 FROM_HERE,
220 base::Bind(&TraceMemoryController::StopProfiling,
221 weak_factory_.GetWeakPtr()));
222 }
223
224 void TraceMemoryController::StartProfiling() {
225 // Watch for the tracing framework sending enabling more than once.
226 if (dump_timer_.IsRunning())
227 return;
228 TraceMemoryStart();
229 const int kDumpIntervalSeconds = 5;
230 dump_timer_.Start(FROM_HERE,
231 TimeDelta::FromSeconds(kDumpIntervalSeconds),
232 base::Bind(&DumpMemoryProfile));
233 }
234
235 void TraceMemoryController::StopProfiling() {
236 dump_timer_.Stop();
237 TraceMemoryStop();
238 }
239
240 bool TraceMemoryController::IsTimerRunningForTest() const {
241 return dump_timer_.IsRunning();
242 }
243
244 /////////////////////////////////////////////////////////////////////////////
245
246 ScopedTraceMemory::ScopedTraceMemory(const char* category) {
247 // Get our thread's copy of the stack.
248 TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack();
249 // No TraceMemoryStack indicates that the trace system isn't running, so don't
250 // record anything.
251 if (!trace_memory_stack)
252 return;
253 const int index = trace_memory_stack->index_;
jar (doing other things) 2013/07/12 01:24:26 nit: size_t
James Cook 2013/07/12 17:40:27 Done.
254 // Allow deep nesting of stacks (needed for tests), but only record
255 // |kMaxStackSize| entries.
256 if (index < kMaxStackSize)
257 trace_memory_stack->category_stack_[index] = category;
258 trace_memory_stack->index_++;
259 }
260
261 ScopedTraceMemory::~ScopedTraceMemory() {
262 // Get our thread's copy of the stack.
263 TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack();
264 // No TraceMemoryStack indicates that the trace system isn't running, so don't
265 // record anything.
266 if (!trace_memory_stack)
267 return;
268 // The tracing system can be turned on with ScopedTraceMemory objects
269 // allocated on the stack, so avoid potential underflow as they are destroyed.
270 if (trace_memory_stack->index_ > 0)
271 trace_memory_stack->index_--;
272 }
273
274 // static
275 void ScopedTraceMemory::InitForTest() {
276 InitThreadLocalStorage();
277 }
278
279 // static
280 void ScopedTraceMemory::CleanupForTest() {
281 CleanupThreadLocalStorage();
282 }
283
284 // static
285 int ScopedTraceMemory::GetStackIndexForTest() {
286 TraceMemoryStack* stack = GetTraceMemoryStack();
287 return stack->index_;
288 }
289
290 // static
291 const char* ScopedTraceMemory::GetItemForTest(int index) {
292 TraceMemoryStack* stack = GetTraceMemoryStack();
293 return stack->category_stack_[index];
294 }
295
296 /////////////////////////////////////////////////////////////////////////////
297
298 void AppendHeapProfileAsTraceFormat(const char* input, std::string* output) {
299 // Heap profile output has a header total line, then a list of stacks with
300 // memory totals, like this:
301 //
302 // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile
303 // 95: 40940 [ 649: 114260] @ 0x7fa7f4b3be13
304 // 77: 32546 [ 742: 106234] @
305 // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13
306 //
307 // MAPPED_LIBRARIES:
308 // 1be411fc1000-1be4139e4000 rw-p 00000000 00:00 0
309 // 1be4139e4000-1be4139e5000 ---p 00000000 00:00 0
310 // ...
311 //
312 // Skip input after MAPPED_LIBRARIES.
313 std::string input_string;
314 const char* mapped_libraries = strstr(input, "MAPPED_LIBRARIES");
315 if (mapped_libraries) {
316 input_string.assign(input, mapped_libraries - input);
317 } else {
318 input_string.assign(input);
319 }
320
321 std::vector<std::string> lines;
322 size_t line_count = Tokenize(input_string, "\n", &lines);
323 if (line_count == 0) {
324 DLOG(WARNING) << "No lines found";
325 return;
326 }
327
328 // Handle the initial summary line.
329 output->append("[");
330 AppendHeapProfileTotalsAsTraceFormat(lines[0], output);
331 output->append(",\n");
332
333 // Handle the following stack trace lines.
334 for (size_t i = 1; i < line_count; ++i) {
335 const std::string& line = lines[i];
336 bool added_entry = AppendHeapProfileLineAsTraceFormat(line, output);
337 if (added_entry)
338 output->append(",\n");
339 }
340 // Remove trailing ",\n".
341 output->resize(output->size() - 2);
jar (doing other things) 2013/07/12 01:24:26 I think this works... but the approach of backing-
James Cook 2013/07/12 17:40:27 Yes, that's cleaner, thanks. Used your suggestion
342 output->append("]\n");
343 }
344
345 void AppendHeapProfileTotalsAsTraceFormat(const std::string& line,
346 std::string* output) {
347 // This is what a line looks like:
348 // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile
349 std::vector<std::string> tokens;
350 Tokenize(line, " :[]@", &tokens);
351 if (tokens.size() < 4) {
352 DLOG(WARNING) << "Invalid totals line " << line;
353 return;
354 }
355 DCHECK_EQ(tokens[0], "heap");
356 DCHECK_EQ(tokens[1], "profile");
357 output->append("{\"current_allocs\": ");
358 output->append(tokens[2]);
359 output->append(", \"current_bytes\": ");
360 output->append(tokens[3]);
361 output->append(", \"trace\": \"\"}");
362 }
363
364 bool AppendHeapProfileLineAsTraceFormat(const std::string& line,
365 std::string* output) {
366 // This is what a line looks like:
367 // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13
jar (doing other things) 2013/07/12 01:24:26 nit: Please add comments telling what these number
James Cook 2013/07/12 17:40:27 Done.
368 std::vector<std::string> tokens;
369 Tokenize(line, " :[]@", &tokens);
370 // It's valid to have no stack addresses, so only require 4 tokens.
371 if (tokens.size() < 4) {
372 DLOG(WARNING) << "Invalid line " << line;
373 return false;
374 }
375 // Don't bother with stacks that have no current allocations.
376 if (tokens[0] == "0")
377 return false;
378 output->append("{\"current_allocs\": ");
379 output->append(tokens[0]);
380 output->append(", \"current_bytes\": ");
381 output->append(tokens[1]);
382 output->append(", \"trace\": \"");
383
384 // Convert the "stack addresses" into strings.
385 const std::string kSingleQuote = "'";
386 for (size_t t = 4; t < tokens.size(); ++t) {
387 // Each stack address is a pointer to a constant trace name string.
388 uint64 address = 0;
389 if (!base::HexStringToUInt64(tokens[t], &address))
390 break;
391 // This is ugly but otherwise tcmalloc would need to gain a special output
392 // serializer for pseudo-stacks. Note that this cast also handles 64-bit to
393 // 32-bit conversion if necessary. Tests use a null address.
394 const char* trace_name =
395 address ? reinterpret_cast<const char*>(address) : "null";
396
397 // Some trace name strings have double quotes, convert them to single.
398 std::string trace_name_string(trace_name);
399 ReplaceChars(trace_name_string, "\"", kSingleQuote, &trace_name_string);
400
401 output->append(trace_name_string);
402
403 // Trace viewer expects a trailing space.
404 output->append(" ");
405 }
406 output->append("\"}");
407 return true;
408 }
409
410 } // namespace debug
411 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698