// 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 "base/trace_event/memory_allocator_dump.h" #include #include "base/format_macros.h" #include "base/strings/stringprintf.h" #include "base/trace_event/memory_allocator_dump_guid.h" #include "base/trace_event/memory_dump_provider.h" #include "base/trace_event/memory_dump_session_state.h" #include "base/trace_event/process_memory_dump.h" #include "base/trace_event/trace_event_argument.h" #include "base/values.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace trace_event { namespace { class FakeMemoryAllocatorDumpProvider : public MemoryDumpProvider { public: bool OnMemoryDump(const MemoryDumpArgs& args, ProcessMemoryDump* pmd) override { MemoryAllocatorDump* root_heap = pmd->CreateAllocatorDump("foobar_allocator"); root_heap->AddScalar(MemoryAllocatorDump::kNameSize, MemoryAllocatorDump::kUnitsBytes, 4096); root_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount, MemoryAllocatorDump::kUnitsObjects, 42); root_heap->AddScalar("attr1", "units1", 1234); root_heap->AddString("attr2", "units2", "string_value"); root_heap->AddScalarF("attr3", "units3", 42.5f); MemoryAllocatorDump* sub_heap = pmd->CreateAllocatorDump("foobar_allocator/sub_heap"); sub_heap->AddScalar(MemoryAllocatorDump::kNameSize, MemoryAllocatorDump::kUnitsBytes, 1); sub_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount, MemoryAllocatorDump::kUnitsObjects, 3); pmd->CreateAllocatorDump("foobar_allocator/sub_heap/empty"); // Leave the rest of sub heap deliberately uninitialized, to check that // CreateAllocatorDump returns a properly zero-initialized object. return true; } }; scoped_ptr CheckAttribute(const MemoryAllocatorDump* dump, const std::string& name, const char* expected_type, const char* expected_units) { scoped_ptr raw_attrs = dump->attributes_for_testing()->ToBaseValue(); DictionaryValue* args = nullptr; DictionaryValue* arg = nullptr; std::string arg_value; const Value* out_value = nullptr; EXPECT_TRUE(raw_attrs->GetAsDictionary(&args)); EXPECT_TRUE(args->GetDictionary(name, &arg)); EXPECT_TRUE(arg->GetString("type", &arg_value)); EXPECT_EQ(expected_type, arg_value); EXPECT_TRUE(arg->GetString("units", &arg_value)); EXPECT_EQ(expected_units, arg_value); EXPECT_TRUE(arg->Get("value", &out_value)); return out_value ? out_value->CreateDeepCopy() : scoped_ptr(); } void CheckString(const MemoryAllocatorDump* dump, const std::string& name, const char* expected_type, const char* expected_units, const std::string& expected_value) { std::string attr_str_value; auto attr_value = CheckAttribute(dump, name, expected_type, expected_units); EXPECT_TRUE(attr_value->GetAsString(&attr_str_value)); EXPECT_EQ(expected_value, attr_str_value); } void CheckScalar(const MemoryAllocatorDump* dump, const std::string& name, const char* expected_units, uint64_t expected_value) { CheckString(dump, name, MemoryAllocatorDump::kTypeScalar, expected_units, StringPrintf("%" PRIx64, expected_value)); } void CheckScalarF(const MemoryAllocatorDump* dump, const std::string& name, const char* expected_units, double expected_value) { auto attr_value = CheckAttribute(dump, name, MemoryAllocatorDump::kTypeScalar, expected_units); double attr_double_value; EXPECT_TRUE(attr_value->GetAsDouble(&attr_double_value)); EXPECT_EQ(expected_value, attr_double_value); } } // namespace TEST(MemoryAllocatorDumpTest, GuidGeneration) { scoped_ptr mad( new MemoryAllocatorDump("foo", nullptr, MemoryAllocatorDumpGuid(0x42u))); ASSERT_EQ("42", mad->guid().ToString()); // If the dumper does not provide a Guid, the MAD will make one up on the // flight. Furthermore that Guid will stay stable across across multiple // snapshots if the |absolute_name| of the dump doesn't change mad.reset(new MemoryAllocatorDump("bar", nullptr)); const MemoryAllocatorDumpGuid guid_bar = mad->guid(); ASSERT_FALSE(guid_bar.empty()); ASSERT_FALSE(guid_bar.ToString().empty()); ASSERT_EQ(guid_bar, mad->guid()); mad.reset(new MemoryAllocatorDump("bar", nullptr)); const MemoryAllocatorDumpGuid guid_bar_2 = mad->guid(); ASSERT_EQ(guid_bar, guid_bar_2); mad.reset(new MemoryAllocatorDump("baz", nullptr)); const MemoryAllocatorDumpGuid guid_baz = mad->guid(); ASSERT_NE(guid_bar, guid_baz); } TEST(MemoryAllocatorDumpTest, DumpIntoProcessMemoryDump) { FakeMemoryAllocatorDumpProvider fmadp; ProcessMemoryDump pmd(new MemoryDumpSessionState(nullptr, nullptr)); MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED}; fmadp.OnMemoryDump(dump_args, &pmd); ASSERT_EQ(3u, pmd.allocator_dumps().size()); const MemoryAllocatorDump* root_heap = pmd.GetAllocatorDump("foobar_allocator"); ASSERT_NE(nullptr, root_heap); EXPECT_EQ("foobar_allocator", root_heap->absolute_name()); CheckScalar(root_heap, MemoryAllocatorDump::kNameSize, MemoryAllocatorDump::kUnitsBytes, 4096); CheckScalar(root_heap, MemoryAllocatorDump::kNameObjectCount, MemoryAllocatorDump::kUnitsObjects, 42); CheckScalar(root_heap, "attr1", "units1", 1234); CheckString(root_heap, "attr2", MemoryAllocatorDump::kTypeString, "units2", "string_value"); CheckScalarF(root_heap, "attr3", "units3", 42.5f); const MemoryAllocatorDump* sub_heap = pmd.GetAllocatorDump("foobar_allocator/sub_heap"); ASSERT_NE(nullptr, sub_heap); EXPECT_EQ("foobar_allocator/sub_heap", sub_heap->absolute_name()); CheckScalar(sub_heap, MemoryAllocatorDump::kNameSize, MemoryAllocatorDump::kUnitsBytes, 1); CheckScalar(sub_heap, MemoryAllocatorDump::kNameObjectCount, MemoryAllocatorDump::kUnitsObjects, 3); const MemoryAllocatorDump* empty_sub_heap = pmd.GetAllocatorDump("foobar_allocator/sub_heap/empty"); ASSERT_NE(nullptr, empty_sub_heap); EXPECT_EQ("foobar_allocator/sub_heap/empty", empty_sub_heap->absolute_name()); auto raw_attrs = empty_sub_heap->attributes_for_testing()->ToBaseValue(); DictionaryValue* attrs = nullptr; ASSERT_TRUE(raw_attrs->GetAsDictionary(&attrs)); ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameSize)); ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameObjectCount)); // Check that the AsValueInfo doesn't hit any DCHECK. scoped_refptr traced_value(new TracedValue()); pmd.AsValueInto(traced_value.get()); } // DEATH tests are not supported in Android / iOS. #if !defined(NDEBUG) && !defined(OS_ANDROID) && !defined(OS_IOS) TEST(MemoryAllocatorDumpTest, ForbidDuplicatesDeathTest) { FakeMemoryAllocatorDumpProvider fmadp; ProcessMemoryDump pmd(new MemoryDumpSessionState(nullptr, nullptr)); pmd.CreateAllocatorDump("foo_allocator"); pmd.CreateAllocatorDump("bar_allocator/heap"); ASSERT_DEATH(pmd.CreateAllocatorDump("foo_allocator"), ""); ASSERT_DEATH(pmd.CreateAllocatorDump("bar_allocator/heap"), ""); ASSERT_DEATH(pmd.CreateAllocatorDump(""), ""); } #endif } // namespace trace_event } // namespace base