blob: 6317886b0d7fb59c48f8f8e80471258cfce75f2a [file] [log] [blame]
// 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, 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 category name of the last seen trace event.
TRACE_EVENT0("Testing", kCupcake);
AllocationContext ctx1;
ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
->GetContextSnapshot(&ctx1));
ASSERT_EQ("Testing", std::string(ctx1.type_name));
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("Testing"), kCupcake);
AllocationContext ctx2;
ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
->GetContextSnapshot(&ctx2));
ASSERT_EQ(TRACE_DISABLED_BY_DEFAULT("Testing"),
std::string(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