Chromium Code Reviews| Index: base/trace_event/heap_profiler_event_writer_unittest.cc |
| diff --git a/base/trace_event/heap_profiler_event_writer_unittest.cc b/base/trace_event/heap_profiler_event_writer_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4e453ed8268e1384bf046a9ef4d2ff269eda66f5 |
| --- /dev/null |
| +++ b/base/trace_event/heap_profiler_event_writer_unittest.cc |
| @@ -0,0 +1,291 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
|
Primiano Tucci (use gerrit)
2017/03/09 11:47:44
ditto
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "base/trace_event/heap_profiler_event_writer.h" |
| + |
| +#include <stdint.h> |
| + |
| +#include <algorithm> |
| + |
| +#include "base/memory/ptr_util.h" |
| +#include "base/trace_event/heap_profiler_allocation_context.h" |
| +#include "base/trace_event/heap_profiler_allocation_register.h" |
| +#include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" |
| +#include "base/trace_event/heap_profiler_type_name_deduplicator.h" |
| +#include "base/trace_event/memory_dump_session_state.h" |
| +#include "base/trace_event/trace_event_argument.h" |
| +#include "base/values.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace base { |
| +namespace trace_event { |
| + |
| +namespace { |
| + |
| +using base::trace_event::StackFrame; |
| + |
| +// Define all strings once, because the deduplicator requires pointer equality, |
| +// and string interning is unreliable. |
| +StackFrame kBrowserMain = StackFrame::FromTraceEventName("BrowserMain"); |
| +StackFrame kRendererMain = StackFrame::FromTraceEventName("RendererMain"); |
| +StackFrame kCreateWidget = StackFrame::FromTraceEventName("CreateWidget"); |
| +StackFrame kInitialize = StackFrame::FromTraceEventName("Initialize"); |
| +StackFrame kGetBitmap = StackFrame::FromTraceEventName("GetBitmap"); |
| + |
| +const char kInt[] = "int"; |
| +const char kBool[] = "bool"; |
| + |
| +class AllocationRegisterHelper : public AllocationRegister { |
| + public: |
| + AllocationRegisterHelper() |
| + : AllocationRegister(kAllocationCapacity, kBacktraceCapacity), |
| + next_address_(0x100000) {} |
| + |
| + void Allocate(size_t size, |
| + const char* type_name, |
| + std::initializer_list<StackFrame> backtrace) { |
| + AllocationContext context; |
| + context.backtrace.frame_count = backtrace.size(); |
| + std::copy(backtrace.begin(), backtrace.end(), |
| + std::begin(context.backtrace.frames)); |
| + context.type_name = type_name; |
| + Insert(reinterpret_cast<void*>(next_address_), size, context); |
| + next_address_ += size; |
| + } |
| + |
| + private: |
| + constexpr static size_t kAllocationCapacity = 100; |
| + constexpr static size_t kBacktraceCapacity = 10; |
| + |
| + uintptr_t next_address_; |
| +}; |
| + |
| +struct HeapDumpEntry { |
| + int backtrace_id; |
| + int type_id; |
| + int size; |
| + int count; |
| + |
| + bool operator==(const HeapDumpEntry& other) const { |
| + return backtrace_id == other.backtrace_id && type_id == other.type_id && |
| + size == other.size && count == other.count; |
| + } |
| +}; |
| + |
| +::testing::AssertionResult AssertHeapDump( |
| + const DictionaryValue& heap_dump, |
| + std::initializer_list<HeapDumpEntry> expected_entries) { |
| + auto get_list_value = [&](const char* list_name, size_t index, |
| + int* value) -> ::testing::AssertionResult { |
| + const ListValue* list = nullptr; |
| + if (!heap_dump.GetList(list_name, &list)) { |
| + return ::testing::AssertionFailure() |
| + << "'" << list_name << "' doesn't exist or is not a list"; |
| + } |
| + if (list->GetSize() != expected_entries.size()) { |
| + return ::testing::AssertionFailure() |
| + << "size of '" << list_name << "' is " << list->GetSize() |
| + << ", expected " << expected_entries.size(); |
| + } |
| + if (!list->GetInteger(index, value)) { |
| + return ::testing::AssertionFailure() |
| + << "'" << list_name << "' value at index " << index |
| + << " is not an integer"; |
| + } |
| + return ::testing::AssertionSuccess(); |
| + }; |
| + |
| + constexpr size_t kValueCount = 4; // nodes, types, counts, sizes |
| + if (heap_dump.size() != kValueCount) { |
| + return ::testing::AssertionFailure() |
| + << "heap dump has " << heap_dump.size() << " values" |
| + << ", expected " << kValueCount; |
| + } |
| + |
| + for (size_t i = 0; i != expected_entries.size(); ++i) { |
| + HeapDumpEntry entry; |
| + |
| + ::testing::AssertionResult assertion = ::testing::AssertionSuccess(); |
| + if (!(assertion = get_list_value("nodes", i, &entry.backtrace_id)) || |
| + !(assertion = get_list_value("types", i, &entry.type_id)) || |
| + !(assertion = get_list_value("sizes", i, &entry.size)) || |
| + !(assertion = get_list_value("counts", i, &entry.count))) { |
| + return assertion; |
| + } |
| + |
| + auto* entry_iter = |
| + std::find(expected_entries.begin(), expected_entries.end(), entry); |
| + if (entry_iter == expected_entries.end()) { |
| + return ::testing::AssertionFailure() |
| + << "unexpected HeapDumpEntry{" << entry.backtrace_id << ", " |
| + << entry.type_id << ", " << entry.size << ", " << entry.count |
| + << "} at index " << i; |
| + } |
| + } |
| + |
| + return ::testing::AssertionSuccess(); |
| +} |
| + |
| +std::unique_ptr<DictionaryValue> ToDictionary( |
| + const std::unique_ptr<TracedValue>& traced_value) { |
| + if (!traced_value) { |
| + return nullptr; |
| + } |
| + return DictionaryValue::From(traced_value->ToBaseValue()); |
| +} |
| + |
| +} // namespace |
| + |
| +TEST(EventWriterTest, HeapDumpNoBacktraceNoType) { |
| + AllocationRegisterHelper allocation_register; |
| + auto bt = {kBrowserMain}; |
| + std::initializer_list<StackFrame> empty_bt = {}; |
| + allocation_register.Allocate(10, nullptr, bt); |
| + allocation_register.Allocate(100, kInt, empty_bt); |
| + allocation_register.Allocate(1000, nullptr, empty_bt); |
| + |
| + auto state = make_scoped_refptr(new MemoryDumpSessionState); |
| + state->CreateDeduplicators(); |
| + auto heap_dump = ToDictionary(ExportHeapDump(allocation_register, *state)); |
| + ASSERT_TRUE(heap_dump); |
| + |
| + int bt_id = |
| + state->stack_frame_deduplicator()->Insert(std::begin(bt), std::end(bt)); |
| + int int_id = state->type_name_deduplicator()->Insert(kInt); |
| + |
| + // NULL type and empty backtrace ids should be 0. |
| + auto expected_entries = { |
| + HeapDumpEntry{bt_id, 0, 10, 1}, // no type |
| + HeapDumpEntry{0, int_id, 100, 1}, // no backtrace |
| + HeapDumpEntry{0, 0, 1000, 1}, // no type, no backtrace |
| + }; |
| + ASSERT_TRUE(AssertHeapDump(*heap_dump, expected_entries)) |
| + << "heap_dump = " << *heap_dump; |
| +} |
| + |
| +TEST(EventWriterTest, HeapDumpAggregation) { |
| + // |
| + // |- (no backtrace) int*1, int*2, bool*3, |
| + // | (no type)*4, (no type)*5 |
| + // | |
| + // |- kBrowserMain (no type)*6, (no type)*7, (no type)*8 |
| + // |-- kCreateWidget int*10, bool*20 |
| + // |---- kGetBitmap int*100, int*200, bool*300 |
| + // |
| + // Aggregation is done by {backtrace_id, type_id}, so the following |
| + // entries should be aggregated: |
| + // - int*1 + int*2 |
| + // - (no type)*4 + (no type)*5 |
| + // - (no type)*6 + (no type)*7 + (no type)*8 |
| + // - int*100 + int*200 |
| + |
| + AllocationRegisterHelper allocation_register; |
| + |
| + std::initializer_list<StackFrame> empty_bt = {}; |
| + allocation_register.Allocate(1, kInt, empty_bt); |
| + allocation_register.Allocate(2, kInt, empty_bt); |
| + allocation_register.Allocate(3, kBool, empty_bt); |
| + allocation_register.Allocate(4, nullptr, empty_bt); |
| + allocation_register.Allocate(5, nullptr, empty_bt); |
| + |
| + auto bt1 = {kBrowserMain}; |
| + allocation_register.Allocate(6, nullptr, bt1); |
| + allocation_register.Allocate(7, nullptr, bt1); |
| + allocation_register.Allocate(8, nullptr, bt1); |
| + |
| + auto bt2 = {kBrowserMain, kCreateWidget}; |
| + allocation_register.Allocate(10, kInt, bt2); |
| + allocation_register.Allocate(20, kBool, bt2); |
| + |
| + auto bt3 = {kBrowserMain, kCreateWidget, kGetBitmap}; |
| + allocation_register.Allocate(100, kInt, bt3); |
| + allocation_register.Allocate(200, kInt, bt3); |
| + allocation_register.Allocate(300, kBool, bt3); |
| + |
| + auto state = make_scoped_refptr(new MemoryDumpSessionState); |
| + state->CreateDeduplicators(); |
| + |
| + auto heap_dump = ToDictionary(ExportHeapDump(allocation_register, *state)); |
| + ASSERT_TRUE(heap_dump); |
| + |
| + int bt1_id = |
| + state->stack_frame_deduplicator()->Insert(std::begin(bt1), std::end(bt1)); |
| + int bt2_id = |
| + state->stack_frame_deduplicator()->Insert(std::begin(bt2), std::end(bt2)); |
| + int bt3_id = |
| + state->stack_frame_deduplicator()->Insert(std::begin(bt3), std::end(bt3)); |
| + |
| + int int_id = state->type_name_deduplicator()->Insert(kInt); |
| + int bool_id = state->type_name_deduplicator()->Insert(kBool); |
| + |
| + auto expected_entries = { |
| + HeapDumpEntry{0, int_id, 3, 2}, |
| + HeapDumpEntry{0, bool_id, 3, 1}, |
| + HeapDumpEntry{0, 0, 9, 2}, |
| + HeapDumpEntry{bt1_id, 0, 21, 3}, |
| + HeapDumpEntry{bt2_id, int_id, 10, 1}, |
| + HeapDumpEntry{bt2_id, bool_id, 20, 1}, |
| + HeapDumpEntry{bt3_id, int_id, 300, 2}, |
| + HeapDumpEntry{bt3_id, bool_id, 300, 1}, |
| + }; |
| + ASSERT_TRUE(AssertHeapDump(*heap_dump, expected_entries)) |
| + << "heap_dump = " << *heap_dump; |
| +} |
| + |
| +TEST(EventWriterTest, ExportHeapProfileEventData) { |
| + AllocationRegisterHelper foo_register; |
| + foo_register.Allocate(10, "Widget", {kBrowserMain, kCreateWidget}); |
| + foo_register.Allocate(16, "int[]", {kBrowserMain, kCreateWidget}); |
| + |
| + AllocationRegisterHelper bar_register; |
| + bar_register.Allocate(10, "Widget", {kRendererMain, kCreateWidget}); |
| + bar_register.Allocate(71, "char[]", {kRendererMain}); |
| + |
| + auto state = make_scoped_refptr(new MemoryDumpSessionState); |
| + state->CreateDeduplicators(); |
| + |
| + ExportedHeapDumpsMap heap_dumps; |
| + heap_dumps["foo"] = ExportHeapDump(foo_register, *state); |
| + heap_dumps["bar"] = ExportHeapDump(bar_register, *state); |
| + |
| + auto event_data = |
| + ToDictionary(ExportHeapProfileEventData(heap_dumps, *state)); |
| + ASSERT_TRUE(event_data); |
| + |
| + constexpr size_t kTopCount = 3; // version, allocators, maps |
| + ASSERT_EQ(kTopCount, event_data->size()); |
| + |
| + int version; |
| + ASSERT_TRUE(event_data->GetInteger("version", &version)); |
| + ASSERT_EQ(1, version); |
| + |
| + const DictionaryValue* allocators; |
| + ASSERT_TRUE(event_data->GetDictionary("allocators", &allocators)); |
| + { |
| + constexpr size_t kAllocatorCount = 2; // foo, bar |
| + ASSERT_EQ(kAllocatorCount, allocators->size()); |
| + |
| + const DictionaryValue* foo_dump; |
| + ASSERT_TRUE(allocators->GetDictionary("foo", &foo_dump)); |
| + ASSERT_TRUE(ToDictionary(heap_dumps["foo"])->Equals(foo_dump)); |
| + |
| + const DictionaryValue* bar_dump; |
| + ASSERT_TRUE(allocators->GetDictionary("bar", &bar_dump)); |
| + ASSERT_TRUE(ToDictionary(heap_dumps["bar"])->Equals(bar_dump)); |
| + } |
| + |
| + const DictionaryValue* maps; |
| + ASSERT_TRUE(event_data->GetDictionary("maps", &maps)); |
| + { |
| + constexpr size_t kMapCount = 3; // nodes, types, strings |
| + ASSERT_EQ(kMapCount, maps->size()); |
| + |
| + ASSERT_TRUE(maps->HasKey("nodes")); |
| + ASSERT_TRUE(maps->HasKey("types")); |
| + ASSERT_TRUE(maps->HasKey("strings")); |
| + } |
| +} |
| + |
| +} // namespace trace_event |
| +} // namespace base |