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

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

Issue 1468883003: Remove memory category from chrome://tracing (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@format-trace-event
Patch Set: Rebase Created 5 years 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 | « base/trace_event/trace_event_memory.h ('k') | base/trace_event/trace_event_memory_unittest.cc » ('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 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/trace_event/trace_event_memory.h"
6
7 #include <utility>
8
9 #include "base/debug/leak_annotations.h"
10 #include "base/lazy_instance.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/threading/thread_local_storage.h"
19 #include "base/trace_event/trace_event.h"
20
21 namespace base {
22 namespace trace_event {
23
24 namespace {
25
26 // Maximum number of nested TRACE_EVENT scopes to record. Must be less than
27 // or equal to HeapProfileTable::kMaxStackDepth / 2 because we record two
28 // entries on the pseudo-stack per scope.
29 const size_t kMaxScopeDepth = 16;
30
31 /////////////////////////////////////////////////////////////////////////////
32 // Holds a memory dump until the tracing system needs to serialize it.
33 class MemoryDumpHolder : public base::trace_event::ConvertableToTraceFormat {
34 public:
35 // Takes ownership of dump, which must be a JSON string, allocated with
36 // malloc() and NULL terminated.
37 explicit MemoryDumpHolder(char* dump) : dump_(dump) {}
38
39 // base::trace_event::ConvertableToTraceFormat overrides:
40 void AppendAsTraceFormat(std::string* out) const override {
41 AppendHeapProfileAsTraceFormat(dump_, out);
42 }
43
44 private:
45 ~MemoryDumpHolder() override { free(dump_); }
46
47 char* dump_;
48
49 DISALLOW_COPY_AND_ASSIGN(MemoryDumpHolder);
50 };
51
52 /////////////////////////////////////////////////////////////////////////////
53 // Records a stack of TRACE_MEMORY events. One per thread is required.
54 struct TraceMemoryStack {
55 TraceMemoryStack() : scope_depth(0) {
56 memset(scope_data, 0, kMaxScopeDepth * sizeof(scope_data[0]));
57 }
58
59 // Depth of the currently nested TRACE_EVENT scopes. Allowed to be greater
60 // than kMaxScopeDepth so we can match scope pushes and pops even if we don't
61 // have enough space to store the EventData.
62 size_t scope_depth;
63
64 // Stack of categories and names.
65 ScopedTraceMemory::ScopeData scope_data[kMaxScopeDepth];
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.
78 void InitThreadLocalStorage() {
79 if (tls_trace_memory_stack.initialized())
80 return;
81 // Initialize the thread-local storage key.
82 tls_trace_memory_stack.Initialize(&DeleteStackOnThreadCleanup);
83 }
84
85 // Clean up thread-local-storage in the main thread.
86 void CleanupThreadLocalStorage() {
87 if (!tls_trace_memory_stack.initialized())
88 return;
89 TraceMemoryStack* stack =
90 static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get());
91 delete stack;
92 tls_trace_memory_stack.Set(NULL);
93 // Intentionally do not release the thread-local-storage key here, that is,
94 // do not call tls_trace_memory_stack.Free(). Other threads have lazily
95 // created pointers in thread-local-storage via GetTraceMemoryStack() below.
96 // Those threads need to run the DeleteStack() destructor function when they
97 // exit. If we release the key the destructor will not be called and those
98 // threads will not clean up their memory.
99 }
100
101 // Returns the thread-local trace memory stack for the current thread, creating
102 // one if needed. Returns NULL if the thread-local storage key isn't
103 // initialized, which indicates that heap profiling isn't running.
104 TraceMemoryStack* GetTraceMemoryStack() {
105 TraceMemoryStack* stack =
106 static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get());
107 // Lazily initialize TraceMemoryStack objects for new threads.
108 if (!stack) {
109 stack = new TraceMemoryStack;
110 tls_trace_memory_stack.Set(stack);
111 }
112 return stack;
113 }
114
115 // Returns a "pseudo-stack" of pointers to trace event categories and names.
116 // Because tcmalloc stores one pointer per stack frame this converts N nested
117 // trace events into N * 2 pseudo-stack entries. Thus this macro invocation:
118 // TRACE_EVENT0("category1", "name1");
119 // TRACE_EVENT0("category2", "name2");
120 // becomes this pseudo-stack:
121 // stack_out[0] = "category1"
122 // stack_out[1] = "name1"
123 // stack_out[2] = "category2"
124 // stack_out[3] = "name2"
125 // Returns int instead of size_t to match the signature required by tcmalloc.
126 int GetPseudoStack(int skip_count_ignored, void** stack_out) {
127 // If the tracing system isn't fully initialized, just skip this allocation.
128 // Attempting to initialize will allocate memory, causing this function to
129 // be called recursively from inside the allocator.
130 if (!tls_trace_memory_stack.initialized() || !tls_trace_memory_stack.Get())
131 return 0;
132 TraceMemoryStack* stack =
133 static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get());
134 // Copy at most kMaxScopeDepth scope entries.
135 const size_t count = std::min(stack->scope_depth, kMaxScopeDepth);
136 // Notes that memcpy() works for zero bytes.
137 memcpy(stack_out,
138 stack->scope_data,
139 count * sizeof(stack->scope_data[0]));
140 // Each item in the trace event stack contains both name and category so tell
141 // tcmalloc that we have returned |count| * 2 stack frames.
142 return static_cast<int>(count * 2);
143 }
144
145 } // namespace
146
147 //////////////////////////////////////////////////////////////////////////////
148
149 TraceMemoryController::TraceMemoryController(
150 scoped_refptr<SingleThreadTaskRunner> task_runner,
151 HeapProfilerStartFunction heap_profiler_start_function,
152 HeapProfilerStopFunction heap_profiler_stop_function,
153 GetHeapProfileFunction get_heap_profile_function)
154 : task_runner_(std::move(task_runner)),
155 heap_profiler_start_function_(heap_profiler_start_function),
156 heap_profiler_stop_function_(heap_profiler_stop_function),
157 get_heap_profile_function_(get_heap_profile_function),
158 weak_factory_(this) {
159 // Force the "memory" category to show up in the trace viewer.
160 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("memory"), "init");
161 // Watch for the tracing system being enabled.
162 TraceLog::GetInstance()->AddEnabledStateObserver(this);
163 }
164
165 TraceMemoryController::~TraceMemoryController() {
166 if (dump_timer_.IsRunning())
167 StopProfiling();
168 TraceLog::GetInstance()->RemoveEnabledStateObserver(this);
169 }
170
171 // base::trace_event::TraceLog::EnabledStateChangedObserver overrides:
172 void TraceMemoryController::OnTraceLogEnabled() {
173 // Check to see if tracing is enabled for the memory category.
174 bool enabled;
175 TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("memory"),
176 &enabled);
177 if (!enabled)
178 return;
179 DVLOG(1) << "OnTraceLogEnabled";
180 task_runner_->PostTask(FROM_HERE,
181 base::Bind(&TraceMemoryController::StartProfiling,
182 weak_factory_.GetWeakPtr()));
183 }
184
185 void TraceMemoryController::OnTraceLogDisabled() {
186 // The memory category is always disabled before OnTraceLogDisabled() is
187 // called, so we cannot tell if it was enabled before. Always try to turn
188 // off profiling.
189 DVLOG(1) << "OnTraceLogDisabled";
190 task_runner_->PostTask(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 DVLOG(1) << "Starting trace memory";
200 InitThreadLocalStorage();
201 ScopedTraceMemory::set_enabled(true);
202 // Call ::HeapProfilerWithPseudoStackStart().
203 heap_profiler_start_function_(&GetPseudoStack);
204 const int kDumpIntervalSeconds = 5;
205 dump_timer_.Start(FROM_HERE,
206 TimeDelta::FromSeconds(kDumpIntervalSeconds),
207 base::Bind(&TraceMemoryController::DumpMemoryProfile,
208 weak_factory_.GetWeakPtr()));
209 }
210
211 void TraceMemoryController::DumpMemoryProfile() {
212 // Don't trace allocations here in the memory tracing system.
213 INTERNAL_TRACE_MEMORY(TRACE_DISABLED_BY_DEFAULT("memory"),
214 TRACE_MEMORY_IGNORE);
215
216 DVLOG(1) << "DumpMemoryProfile";
217 // MemoryDumpHolder takes ownership of this string. See GetHeapProfile() in
218 // tcmalloc for details.
219 char* dump = get_heap_profile_function_();
220 const int kSnapshotId = 1;
221 TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
222 TRACE_DISABLED_BY_DEFAULT("memory"),
223 "memory::Heap",
224 kSnapshotId,
225 scoped_refptr<ConvertableToTraceFormat>(new MemoryDumpHolder(dump)));
226 }
227
228 void TraceMemoryController::StopProfiling() {
229 // Watch for the tracing framework sending disabled more than once.
230 if (!dump_timer_.IsRunning())
231 return;
232 DVLOG(1) << "Stopping trace memory";
233 dump_timer_.Stop();
234 ScopedTraceMemory::set_enabled(false);
235 CleanupThreadLocalStorage();
236 // Call ::HeapProfilerStop().
237 heap_profiler_stop_function_();
238 }
239
240 bool TraceMemoryController::IsTimerRunningForTest() const {
241 return dump_timer_.IsRunning();
242 }
243
244 /////////////////////////////////////////////////////////////////////////////
245
246 // static
247 bool ScopedTraceMemory::enabled_ = false;
248
249 void ScopedTraceMemory::Initialize(const char* category, const char* name) {
250 DCHECK(enabled_);
251 // Get our thread's copy of the stack.
252 TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack();
253 const size_t index = trace_memory_stack->scope_depth;
254 // Don't record data for deeply nested scopes, but continue to increment
255 // |stack_depth| so we can match pushes and pops.
256 if (index < kMaxScopeDepth) {
257 ScopeData& event = trace_memory_stack->scope_data[index];
258 event.category = category;
259 event.name = name;
260 }
261 trace_memory_stack->scope_depth++;
262 }
263
264 void ScopedTraceMemory::Destroy() {
265 DCHECK(enabled_);
266 // Get our thread's copy of the stack.
267 TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack();
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->scope_depth > 0)
271 trace_memory_stack->scope_depth--;
272 }
273
274 // static
275 void ScopedTraceMemory::InitForTest() {
276 InitThreadLocalStorage();
277 enabled_ = true;
278 }
279
280 // static
281 void ScopedTraceMemory::CleanupForTest() {
282 enabled_ = false;
283 CleanupThreadLocalStorage();
284 }
285
286 // static
287 int ScopedTraceMemory::GetStackDepthForTest() {
288 TraceMemoryStack* stack = GetTraceMemoryStack();
289 return static_cast<int>(stack->scope_depth);
290 }
291
292 // static
293 ScopedTraceMemory::ScopeData ScopedTraceMemory::GetScopeDataForTest(
294 int stack_index) {
295 TraceMemoryStack* stack = GetTraceMemoryStack();
296 return stack->scope_data[stack_index];
297 }
298
299 /////////////////////////////////////////////////////////////////////////////
300
301 void AppendHeapProfileAsTraceFormat(const char* input, std::string* output) {
302 // Heap profile output has a header total line, then a list of stacks with
303 // memory totals, like this:
304 //
305 // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile
306 // 95: 40940 [ 649: 114260] @ 0x7fa7f4b3be13
307 // 77: 32546 [ 742: 106234] @
308 // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13
309 //
310 // MAPPED_LIBRARIES:
311 // 1be411fc1000-1be4139e4000 rw-p 00000000 00:00 0
312 // 1be4139e4000-1be4139e5000 ---p 00000000 00:00 0
313 // ...
314 //
315 // Skip input after MAPPED_LIBRARIES.
316 std::string input_string;
317 const char* mapped_libraries = strstr(input, "MAPPED_LIBRARIES");
318 if (mapped_libraries) {
319 input_string.assign(input, mapped_libraries - input);
320 } else {
321 input_string.assign(input);
322 }
323
324 std::vector<std::string> lines = base::SplitString(
325 input_string, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
326 if (lines.empty()) {
327 DLOG(WARNING) << "No lines found";
328 return;
329 }
330
331 // Handle the initial summary line.
332 output->append("[");
333 AppendHeapProfileTotalsAsTraceFormat(lines[0], output);
334
335 // Handle the following stack trace lines.
336 for (size_t i = 1; i < lines.size(); i++)
337 AppendHeapProfileLineAsTraceFormat(lines[i], output);
338 output->append("]\n");
339 }
340
341 void AppendHeapProfileTotalsAsTraceFormat(const std::string& line,
342 std::string* output) {
343 // This is what a line looks like:
344 // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile
345 //
346 // The numbers represent total allocations since profiling was enabled.
347 // From the example above:
348 // 357 = Outstanding allocations (mallocs - frees)
349 // 55227 = Outstanding bytes (malloc bytes - free bytes)
350 // 14653 = Total allocations (mallocs)
351 // 2624014 = Total bytes (malloc bytes)
352 std::vector<std::string> tokens = base::SplitString(
353 line, " :[]@", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
354 if (tokens.size() < 4) {
355 DLOG(WARNING) << "Invalid totals line " << line;
356 return;
357 }
358 DCHECK_EQ(tokens[0], "heap");
359 DCHECK_EQ(tokens[1], "profile");
360 output->append("{\"current_allocs\": ");
361 output->append(tokens[2]);
362 output->append(", \"current_bytes\": ");
363 output->append(tokens[3]);
364 output->append(", \"trace\": \"\"}");
365 }
366
367 bool AppendHeapProfileLineAsTraceFormat(const std::string& line,
368 std::string* output) {
369 // This is what a line looks like:
370 // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13
371 //
372 // The numbers represent allocations for a particular stack trace since
373 // profiling was enabled. From the example above:
374 // 68 = Outstanding allocations (mallocs - frees)
375 // 4195 = Outstanding bytes (malloc bytes - free bytes)
376 // 1087 = Total allocations (mallocs)
377 // 98009 = Total bytes (malloc bytes)
378 //
379 // 0x7fa7fa9b9ba0 0x7fa7f4b3be13 = Stack trace represented as pointers to
380 // static strings from trace event categories
381 // and names.
382 std::vector<std::string> tokens = base::SplitString(
383 line, " :[]@", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
384 // It's valid to have no stack addresses, so only require 4 tokens.
385 if (tokens.size() < 4) {
386 DLOG(WARNING) << "Invalid line " << line;
387 return false;
388 }
389 // Don't bother with stacks that have no current allocations.
390 if (tokens[0] == "0")
391 return false;
392 output->append(",\n");
393 output->append("{\"current_allocs\": ");
394 output->append(tokens[0]);
395 output->append(", \"current_bytes\": ");
396 output->append(tokens[1]);
397 output->append(", \"trace\": \"");
398
399 // Convert pairs of "stack addresses" into category and name strings.
400 const std::string kSingleQuote = "'";
401 for (size_t t = 4; t < tokens.size(); t += 2) {
402 // Casting strings into pointers is ugly but otherwise tcmalloc would need
403 // to gain a special output serializer just for pseudo-stacks.
404 const char* trace_category = StringFromHexAddress(tokens[t]);
405 DCHECK_LT(t + 1, tokens.size());
406 const char* trace_name = StringFromHexAddress(tokens[t + 1]);
407
408 // TODO(jamescook): Report the trace category and name separately to the
409 // trace viewer and allow it to decide what decorations to apply. For now
410 // just hard-code a decoration for posted tasks (toplevel).
411 std::string trace_string(trace_name);
412 if (!strcmp(trace_category, "toplevel"))
413 trace_string.append("->PostTask");
414
415 // Some trace name strings have double quotes, convert them to single.
416 ReplaceChars(trace_string, "\"", kSingleQuote, &trace_string);
417
418 output->append(trace_string);
419
420 // Trace viewer expects a trailing space.
421 output->append(" ");
422 }
423 output->append("\"}");
424 return true;
425 }
426
427 const char* StringFromHexAddress(const std::string& hex_address) {
428 uint64 address = 0;
429 if (!base::HexStringToUInt64(hex_address, &address))
430 return "error";
431 if (!address)
432 return "null";
433 // Note that this cast handles 64-bit to 32-bit conversion if necessary.
434 return reinterpret_cast<const char*>(address);
435 }
436
437 } // namespace trace_event
438 } // namespace base
OLDNEW
« no previous file with comments | « base/trace_event/trace_event_memory.h ('k') | base/trace_event/trace_event_memory_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698