| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <iterator> |
| |
| #include "base/memory/ref_counted.h" |
| #include "base/pending_task.h" |
| #include "base/trace_event/heap_profiler.h" |
| #include "base/trace_event/heap_profiler_allocation_context.h" |
| #include "base/trace_event/heap_profiler_allocation_context_tracker.h" |
| #include "base/trace_event/memory_dump_manager.h" |
| #include "base/trace_event/trace_event.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace trace_event { |
| |
| // Define all strings once, because the pseudo stack requires pointer equality, |
| // and string interning is unreliable. |
| const char kThreadName[] = "TestThread"; |
| const char kCupcake[] = "Cupcake"; |
| const char kDonut[] = "Donut"; |
| const char kEclair[] = "Eclair"; |
| const char kFroyo[] = "Froyo"; |
| const char kGingerbread[] = "Gingerbread"; |
| |
| const char kFilteringTraceConfig[] = |
| "{" |
| " \"event_filters\": [" |
| " {" |
| " \"excluded_categories\": []," |
| " \"filter_args\": {}," |
| " \"filter_predicate\": \"heap_profiler_predicate\"," |
| " \"included_categories\": [" |
| " \"*\"," |
| " \"" TRACE_DISABLED_BY_DEFAULT("Testing") "\"]" |
| " }" |
| " ]" |
| "}"; |
| |
| // Asserts that the fixed-size array |expected_backtrace| matches the backtrace |
| // in |AllocationContextTracker::GetContextSnapshot|. |
| template <size_t N> |
| void AssertBacktraceEquals(const StackFrame(&expected_backtrace)[N]) { |
| AllocationContext ctx; |
| ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx)); |
| |
| auto* actual = std::begin(ctx.backtrace.frames); |
| auto* actual_bottom = actual + ctx.backtrace.frame_count; |
| auto expected = std::begin(expected_backtrace); |
| auto expected_bottom = std::end(expected_backtrace); |
| |
| // Note that this requires the pointers to be equal, this is not doing a deep |
| // string comparison. |
| for (; actual != actual_bottom && expected != expected_bottom; |
| actual++, expected++) |
| ASSERT_EQ(*expected, *actual); |
| |
| // Ensure that the height of the stacks is the same. |
| ASSERT_EQ(actual, actual_bottom); |
| ASSERT_EQ(expected, expected_bottom); |
| } |
| |
| void AssertBacktraceContainsOnlyThreadName() { |
| StackFrame t = StackFrame::FromThreadName(kThreadName); |
| AllocationContext ctx; |
| ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx)); |
| |
| ASSERT_EQ(1u, ctx.backtrace.frame_count); |
| ASSERT_EQ(t, ctx.backtrace.frames[0]); |
| } |
| |
| class AllocationContextTrackerTest : public testing::Test { |
| public: |
| void SetUp() override { |
| AllocationContextTracker::SetCaptureMode( |
| AllocationContextTracker::CaptureMode::PSEUDO_STACK); |
| // Enabling memory-infra category sets default memory dump config which |
| // includes filters for capturing pseudo stack. |
| TraceConfig config(kFilteringTraceConfig); |
| TraceLog::GetInstance()->SetEnabled(config, TraceLog::FILTERING_MODE); |
| AllocationContextTracker::SetCurrentThreadName(kThreadName); |
| } |
| |
| void TearDown() override { |
| AllocationContextTracker::SetCaptureMode( |
| AllocationContextTracker::CaptureMode::DISABLED); |
| TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE); |
| } |
| }; |
| |
| // Check that |TRACE_EVENT| macros push and pop to the pseudo stack correctly. |
| TEST_F(AllocationContextTrackerTest, PseudoStackScopedTrace) { |
| StackFrame t = StackFrame::FromThreadName(kThreadName); |
| StackFrame c = StackFrame::FromTraceEventName(kCupcake); |
| StackFrame d = StackFrame::FromTraceEventName(kDonut); |
| StackFrame e = StackFrame::FromTraceEventName(kEclair); |
| StackFrame f = StackFrame::FromTraceEventName(kFroyo); |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| { |
| TRACE_EVENT0("Testing", kCupcake); |
| StackFrame frame_c[] = {t, c}; |
| AssertBacktraceEquals(frame_c); |
| |
| { |
| TRACE_EVENT0("Testing", kDonut); |
| StackFrame frame_cd[] = {t, c, d}; |
| AssertBacktraceEquals(frame_cd); |
| } |
| |
| AssertBacktraceEquals(frame_c); |
| |
| { |
| TRACE_EVENT0("Testing", kEclair); |
| StackFrame frame_ce[] = {t, c, e}; |
| AssertBacktraceEquals(frame_ce); |
| } |
| |
| { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("NotTesting"), kDonut); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("Testing"), kCupcake); |
| StackFrame frame_cc[] = {t, c, c}; |
| AssertBacktraceEquals(frame_cc); |
| } |
| |
| AssertBacktraceEquals(frame_c); |
| } |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| { |
| TRACE_EVENT0("Testing", kFroyo); |
| StackFrame frame_f[] = {t, f}; |
| AssertBacktraceEquals(frame_f); |
| } |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| } |
| |
| // Same as |PseudoStackScopedTrace|, but now test the |TRACE_EVENT_BEGIN| and |
| // |TRACE_EVENT_END| macros. |
| TEST_F(AllocationContextTrackerTest, PseudoStackBeginEndTrace) { |
| StackFrame t = StackFrame::FromThreadName(kThreadName); |
| StackFrame c = StackFrame::FromTraceEventName(kCupcake); |
| StackFrame d = StackFrame::FromTraceEventName(kDonut); |
| StackFrame e = StackFrame::FromTraceEventName(kEclair); |
| StackFrame f = StackFrame::FromTraceEventName(kFroyo); |
| |
| StackFrame frame_c[] = {t, c}; |
| StackFrame frame_cd[] = {t, c, d}; |
| StackFrame frame_ce[] = {t, c, e}; |
| StackFrame frame_f[] = {t, f}; |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| TRACE_EVENT_BEGIN0("Testing", kCupcake); |
| AssertBacktraceEquals(frame_c); |
| |
| TRACE_EVENT_BEGIN0("Testing", kDonut); |
| AssertBacktraceEquals(frame_cd); |
| TRACE_EVENT_END0("Testing", kDonut); |
| |
| AssertBacktraceEquals(frame_c); |
| |
| TRACE_EVENT_BEGIN0("Testing", kEclair); |
| AssertBacktraceEquals(frame_ce); |
| TRACE_EVENT_END0("Testing", kEclair); |
| |
| AssertBacktraceEquals(frame_c); |
| TRACE_EVENT_END0("Testing", kCupcake); |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| TRACE_EVENT_BEGIN0("Testing", kFroyo); |
| AssertBacktraceEquals(frame_f); |
| TRACE_EVENT_END0("Testing", kFroyo); |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| } |
| |
| TEST_F(AllocationContextTrackerTest, PseudoStackMixedTrace) { |
| StackFrame t = StackFrame::FromThreadName(kThreadName); |
| StackFrame c = StackFrame::FromTraceEventName(kCupcake); |
| StackFrame d = StackFrame::FromTraceEventName(kDonut); |
| StackFrame e = StackFrame::FromTraceEventName(kEclair); |
| StackFrame f = StackFrame::FromTraceEventName(kFroyo); |
| |
| StackFrame frame_c[] = {t, c}; |
| StackFrame frame_cd[] = {t, c, d}; |
| StackFrame frame_e[] = {t, e}; |
| StackFrame frame_ef[] = {t, e, f}; |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| TRACE_EVENT_BEGIN0("Testing", kCupcake); |
| AssertBacktraceEquals(frame_c); |
| |
| { |
| TRACE_EVENT0("Testing", kDonut); |
| AssertBacktraceEquals(frame_cd); |
| } |
| |
| AssertBacktraceEquals(frame_c); |
| TRACE_EVENT_END0("Testing", kCupcake); |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| { |
| TRACE_EVENT0("Testing", kEclair); |
| AssertBacktraceEquals(frame_e); |
| |
| TRACE_EVENT_BEGIN0("Testing", kFroyo); |
| AssertBacktraceEquals(frame_ef); |
| TRACE_EVENT_END0("Testing", kFroyo); |
| AssertBacktraceEquals(frame_e); |
| } |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| } |
| |
| TEST_F(AllocationContextTrackerTest, MixedStackWithProgramCounter) { |
| StackFrame t = StackFrame::FromThreadName(kThreadName); |
| StackFrame c = StackFrame::FromTraceEventName(kCupcake); |
| StackFrame f = StackFrame::FromTraceEventName(kFroyo); |
| const void* pc1 = reinterpret_cast<void*>(0x1000); |
| const void* pc2 = reinterpret_cast<void*>(0x2000); |
| StackFrame n1 = StackFrame::FromProgramCounter(pc1); |
| StackFrame n2 = StackFrame::FromProgramCounter(pc2); |
| |
| StackFrame frame_c[] = {t, c}; |
| StackFrame frame_cd[] = {t, c, n1}; |
| StackFrame frame_e[] = {t, n2, n1}; |
| StackFrame frame_ef[] = {t, n2, n1, f}; |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| AllocationContextTracker::SetCaptureMode( |
| AllocationContextTracker::CaptureMode::MIXED_STACK); |
| |
| TRACE_EVENT_BEGIN0("Testing", kCupcake); |
| AssertBacktraceEquals(frame_c); |
| |
| { |
| TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER e1(pc1); |
| AssertBacktraceEquals(frame_cd); |
| } |
| |
| AssertBacktraceEquals(frame_c); |
| TRACE_EVENT_END0("Testing", kCupcake); |
| AssertBacktraceContainsOnlyThreadName(); |
| |
| { |
| TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER e1(pc2); |
| TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER e2(pc1); |
| AssertBacktraceEquals(frame_e); |
| |
| TRACE_EVENT0("Testing", kFroyo); |
| AssertBacktraceEquals(frame_ef); |
| } |
| |
| AssertBacktraceContainsOnlyThreadName(); |
| AllocationContextTracker::SetCaptureMode( |
| AllocationContextTracker::CaptureMode::DISABLED); |
| } |
| |
| TEST_F(AllocationContextTrackerTest, BacktraceTakesTop) { |
| StackFrame t = StackFrame::FromThreadName(kThreadName); |
| StackFrame c = StackFrame::FromTraceEventName(kCupcake); |
| StackFrame f = StackFrame::FromTraceEventName(kFroyo); |
| |
| // Push 11 events onto the pseudo stack. |
| TRACE_EVENT0("Testing", kCupcake); |
| TRACE_EVENT0("Testing", kCupcake); |
| TRACE_EVENT0("Testing", kCupcake); |
| |
| TRACE_EVENT0("Testing", kCupcake); |
| TRACE_EVENT0("Testing", kCupcake); |
| TRACE_EVENT0("Testing", kCupcake); |
| TRACE_EVENT0("Testing", kCupcake); |
| |
| TRACE_EVENT0("Testing", kCupcake); |
| TRACE_EVENT0("Testing", kDonut); |
| TRACE_EVENT0("Testing", kEclair); |
| TRACE_EVENT0("Testing", kFroyo); |
| |
| { |
| TRACE_EVENT0("Testing", kGingerbread); |
| AllocationContext ctx; |
| ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx)); |
| |
| // The pseudo stack relies on pointer equality, not deep string comparisons. |
| ASSERT_EQ(t, ctx.backtrace.frames[0]); |
| ASSERT_EQ(c, ctx.backtrace.frames[1]); |
| ASSERT_EQ(f, ctx.backtrace.frames[11]); |
| } |
| |
| { |
| AllocationContext ctx; |
| ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx)); |
| ASSERT_EQ(t, ctx.backtrace.frames[0]); |
| ASSERT_EQ(c, ctx.backtrace.frames[1]); |
| ASSERT_EQ(f, ctx.backtrace.frames[11]); |
| } |
| } |
| |
| TEST_F(AllocationContextTrackerTest, TrackCategoryName) { |
| const char kContext1[] = "context1"; |
| const char kContext2[] = "context2"; |
| { |
| // The context from the scoped task event should be used as type name. |
| TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event1(kContext1); |
| AllocationContext ctx1; |
| ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx1)); |
| ASSERT_EQ(kContext1, ctx1.type_name); |
| |
| // In case of nested events, the last event's context should be used. |
| TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event2(kContext2); |
| AllocationContext ctx2; |
| ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx2)); |
| ASSERT_EQ(kContext2, ctx2.type_name); |
| } |
| |
| // Type should be nullptr without task event. |
| AllocationContext ctx; |
| ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx)); |
| ASSERT_FALSE(ctx.type_name); |
| } |
| |
| TEST_F(AllocationContextTrackerTest, IgnoreAllocationTest) { |
| TRACE_EVENT0("Testing", kCupcake); |
| TRACE_EVENT0("Testing", kDonut); |
| HEAP_PROFILER_SCOPED_IGNORE; |
| AllocationContext ctx; |
| ASSERT_FALSE(AllocationContextTracker::GetInstanceForCurrentThread() |
| ->GetContextSnapshot(&ctx)); |
| } |
| |
| } // namespace trace_event |
| } // namespace base |