|  | /* | 
|  | * Copyright (C) 2019 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include "src/trace_processor/heap_profile_tracker.h" | 
|  |  | 
|  | #include "src/trace_processor/trace_processor_context.h" | 
|  |  | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | namespace perfetto { | 
|  | namespace trace_processor { | 
|  | namespace { | 
|  |  | 
|  | constexpr auto kFirstPacket = 0; | 
|  | constexpr auto kFirstPacketMappingNameId = 1; | 
|  | constexpr auto kFirstPacketBuildId = 2; | 
|  | constexpr auto kFirstPacketFrameNameId = 3; | 
|  |  | 
|  | constexpr auto kFirstPacketMappingId = 1; | 
|  | constexpr auto kFirstPacketFrameId = 1; | 
|  |  | 
|  | constexpr auto kSecondPacket = 1; | 
|  | constexpr auto kSecondPacketMappingNameId = 3; | 
|  | constexpr auto kSecondPacketBuildId = 2; | 
|  | constexpr auto kSecondPacketFrameNameId = 1; | 
|  |  | 
|  | constexpr auto kSecondPacketFrameId = 2; | 
|  | constexpr auto kSecondPacketMappingId = 2; | 
|  |  | 
|  | constexpr auto kMappingOffset = 123; | 
|  | constexpr auto kMappingStart = 234; | 
|  | constexpr auto kMappingEnd = 345; | 
|  | constexpr auto kMappingLoadBias = 456; | 
|  |  | 
|  | static constexpr auto kFrameRelPc = 567; | 
|  |  | 
|  | using ::testing::ElementsAre; | 
|  |  | 
|  | class HeapProfileTrackerDupTest : public ::testing::Test { | 
|  | public: | 
|  | HeapProfileTrackerDupTest() { | 
|  | context.storage.reset(new TraceStorage()); | 
|  | context.heap_profile_tracker.reset(new HeapProfileTracker(&context)); | 
|  |  | 
|  | mapping_name = context.storage->InternString("[mapping]"); | 
|  | build = context.storage->InternString("[build id]"); | 
|  | frame_name = context.storage->InternString("[frame]"); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void InsertMapping() { | 
|  | context.heap_profile_tracker->AddString( | 
|  | kFirstPacket, kFirstPacketMappingNameId, mapping_name); | 
|  | context.heap_profile_tracker->AddString( | 
|  | kSecondPacket, kSecondPacketMappingNameId, mapping_name); | 
|  |  | 
|  | context.heap_profile_tracker->AddString(kFirstPacket, kFirstPacketBuildId, | 
|  | build); | 
|  | context.heap_profile_tracker->AddString(kSecondPacket, kSecondPacketBuildId, | 
|  | build); | 
|  |  | 
|  | HeapProfileTracker::SourceMapping first_frame; | 
|  | first_frame.build_id = kFirstPacketBuildId; | 
|  | first_frame.offset = kMappingOffset; | 
|  | first_frame.start = kMappingStart; | 
|  | first_frame.end = kMappingEnd; | 
|  | first_frame.load_bias = kMappingLoadBias; | 
|  | first_frame.name_id = kFirstPacketMappingNameId; | 
|  |  | 
|  | HeapProfileTracker::SourceMapping second_frame; | 
|  | second_frame.build_id = kSecondPacketBuildId; | 
|  | second_frame.offset = kMappingOffset; | 
|  | second_frame.start = kMappingStart; | 
|  | second_frame.end = kMappingEnd; | 
|  | second_frame.load_bias = kMappingLoadBias; | 
|  | second_frame.name_id = kSecondPacketMappingNameId; | 
|  |  | 
|  | context.heap_profile_tracker->AddMapping( | 
|  | kFirstPacket, kFirstPacketMappingId, first_frame); | 
|  | context.heap_profile_tracker->AddMapping( | 
|  | kSecondPacket, kSecondPacketMappingId, second_frame); | 
|  | } | 
|  |  | 
|  | void InsertFrame() { | 
|  | InsertMapping(); | 
|  | context.heap_profile_tracker->AddString( | 
|  | kFirstPacket, kFirstPacketFrameNameId, frame_name); | 
|  | context.heap_profile_tracker->AddString( | 
|  | kSecondPacket, kSecondPacketFrameNameId, frame_name); | 
|  |  | 
|  | HeapProfileTracker::SourceFrame first_frame; | 
|  | first_frame.name_id = kFirstPacketFrameNameId; | 
|  | first_frame.mapping_id = kFirstPacketMappingId; | 
|  | first_frame.rel_pc = kFrameRelPc; | 
|  |  | 
|  | HeapProfileTracker::SourceFrame second_frame; | 
|  | second_frame.name_id = kSecondPacketFrameNameId; | 
|  | second_frame.mapping_id = kSecondPacketMappingId; | 
|  | second_frame.rel_pc = kFrameRelPc; | 
|  |  | 
|  | context.heap_profile_tracker->AddFrame(kFirstPacket, kFirstPacketFrameId, | 
|  | first_frame); | 
|  | context.heap_profile_tracker->AddFrame(kSecondPacket, kSecondPacketFrameId, | 
|  | second_frame); | 
|  | } | 
|  |  | 
|  | void InsertCallsite() { | 
|  | InsertFrame(); | 
|  |  | 
|  | HeapProfileTracker::SourceCallstack first_callsite = {kFirstPacketFrameId, | 
|  | kFirstPacketFrameId}; | 
|  | HeapProfileTracker::SourceCallstack second_callsite = { | 
|  | kSecondPacketFrameId, kSecondPacketFrameId}; | 
|  |  | 
|  | context.heap_profile_tracker->AddCallstack(kFirstPacket, 0, first_callsite); | 
|  | context.heap_profile_tracker->AddCallstack(kSecondPacket, 0, | 
|  | second_callsite); | 
|  | } | 
|  |  | 
|  | StringId mapping_name; | 
|  | StringId build; | 
|  | StringId frame_name; | 
|  | TraceProcessorContext context; | 
|  | }; | 
|  |  | 
|  | // Insert the same mapping from two different packets, with different strings | 
|  | // interned, and assert we only store one. | 
|  | TEST_F(HeapProfileTrackerDupTest, Mapping) { | 
|  | InsertMapping(); | 
|  |  | 
|  | EXPECT_THAT(context.storage->heap_profile_mappings().build_ids(), | 
|  | ElementsAre(build)); | 
|  | EXPECT_THAT(context.storage->heap_profile_mappings().offsets(), | 
|  | ElementsAre(kMappingOffset)); | 
|  | EXPECT_THAT(context.storage->heap_profile_mappings().starts(), | 
|  | ElementsAre(kMappingStart)); | 
|  | EXPECT_THAT(context.storage->heap_profile_mappings().ends(), | 
|  | ElementsAre(kMappingEnd)); | 
|  | EXPECT_THAT(context.storage->heap_profile_mappings().load_biases(), | 
|  | ElementsAre(kMappingLoadBias)); | 
|  | EXPECT_THAT(context.storage->heap_profile_mappings().names(), | 
|  | ElementsAre(mapping_name)); | 
|  | } | 
|  |  | 
|  | // Insert the same mapping from two different packets, with different strings | 
|  | // interned, and assert we only store one. | 
|  | TEST_F(HeapProfileTrackerDupTest, Frame) { | 
|  | InsertFrame(); | 
|  |  | 
|  | EXPECT_THAT(context.storage->heap_profile_frames().names(), | 
|  | ElementsAre(frame_name)); | 
|  | EXPECT_THAT(context.storage->heap_profile_frames().mappings(), | 
|  | ElementsAre(0)); | 
|  | EXPECT_THAT(context.storage->heap_profile_frames().rel_pcs(), | 
|  | ElementsAre(kFrameRelPc)); | 
|  | } | 
|  |  | 
|  | // Insert the same callstack from two different packets, assert it is only | 
|  | // stored once. | 
|  | TEST_F(HeapProfileTrackerDupTest, Callstack) { | 
|  | InsertCallsite(); | 
|  |  | 
|  | EXPECT_THAT(context.storage->heap_profile_callsites().frame_depths(), | 
|  | ElementsAre(0, 1)); | 
|  | EXPECT_THAT(context.storage->heap_profile_callsites().parent_callsite_ids(), | 
|  | ElementsAre(0, 0)); | 
|  | EXPECT_THAT(context.storage->heap_profile_callsites().frame_ids(), | 
|  | ElementsAre(0, 0)); | 
|  | } | 
|  |  | 
|  | int64_t FindCallstack(const TraceStorage& storage, | 
|  | int64_t depth, | 
|  | int64_t parent, | 
|  | int64_t frame_id) { | 
|  | const auto& callsites = storage.heap_profile_callsites(); | 
|  | for (size_t i = 0; i < callsites.frame_depths().size(); ++i) { | 
|  | if (callsites.frame_depths()[i] == depth && | 
|  | callsites.parent_callsite_ids()[i] == parent && | 
|  | callsites.frame_ids()[i] == frame_id) { | 
|  | return static_cast<int64_t>(i); | 
|  | } | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | // Insert multiple mappings, frames and callstacks and check result. | 
|  | TEST(HeapProfileTrackerTest, Functional) { | 
|  | TraceProcessorContext context; | 
|  | context.storage.reset(new TraceStorage()); | 
|  | context.heap_profile_tracker.reset(new HeapProfileTracker(&context)); | 
|  |  | 
|  | HeapProfileTracker* hpt = context.heap_profile_tracker.get(); | 
|  |  | 
|  | constexpr auto kPacket = 0; | 
|  | uint64_t next_string_intern_id = 1; | 
|  |  | 
|  | const std::string build_ids[] = {"build1", "build2", "build3"}; | 
|  | uint64_t build_id_ids[base::ArraySize(build_ids)]; | 
|  | for (size_t i = 0; i < base::ArraySize(build_ids); ++i) | 
|  | build_id_ids[i] = next_string_intern_id++; | 
|  |  | 
|  | const std::string mapping_names[] = {"map1", "map2", "map3"}; | 
|  | uint64_t mapping_name_ids[base::ArraySize(mapping_names)]; | 
|  | for (size_t i = 0; i < base::ArraySize(mapping_names); ++i) | 
|  | mapping_name_ids[i] = next_string_intern_id++; | 
|  |  | 
|  | HeapProfileTracker::SourceMapping mappings[base::ArraySize(mapping_names)] = | 
|  | {}; | 
|  | mappings[0].build_id = build_id_ids[0]; | 
|  | mappings[0].offset = 1; | 
|  | mappings[0].start = 2; | 
|  | mappings[0].end = 3; | 
|  | mappings[0].load_bias = 0; | 
|  | mappings[0].name_id = mapping_name_ids[0]; | 
|  |  | 
|  | mappings[1].build_id = build_id_ids[1]; | 
|  | mappings[1].offset = 1; | 
|  | mappings[1].start = 2; | 
|  | mappings[1].end = 3; | 
|  | mappings[1].load_bias = 1; | 
|  | mappings[1].name_id = mapping_name_ids[1]; | 
|  |  | 
|  | mappings[2].build_id = build_id_ids[2]; | 
|  | mappings[2].offset = 1; | 
|  | mappings[2].start = 2; | 
|  | mappings[2].end = 3; | 
|  | mappings[2].load_bias = 2; | 
|  | mappings[2].name_id = mapping_name_ids[2]; | 
|  |  | 
|  | const std::string function_names[] = {"fun1", "fun2", "fun3", "fun4"}; | 
|  | uint64_t function_name_ids[base::ArraySize(function_names)]; | 
|  | for (size_t i = 0; i < base::ArraySize(function_names); ++i) | 
|  | function_name_ids[i] = next_string_intern_id++; | 
|  |  | 
|  | HeapProfileTracker::SourceFrame frames[base::ArraySize(function_names)]; | 
|  | frames[0].name_id = function_name_ids[0]; | 
|  | frames[0].mapping_id = 0; | 
|  | frames[0].rel_pc = 123; | 
|  |  | 
|  | frames[1].name_id = function_name_ids[1]; | 
|  | frames[1].mapping_id = 0; | 
|  | frames[1].rel_pc = 123; | 
|  |  | 
|  | frames[2].name_id = function_name_ids[2]; | 
|  | frames[2].mapping_id = 1; | 
|  | frames[2].rel_pc = 123; | 
|  |  | 
|  | frames[3].name_id = function_name_ids[3]; | 
|  | frames[3].mapping_id = 2; | 
|  | frames[3].rel_pc = 123; | 
|  |  | 
|  | HeapProfileTracker::SourceCallstack callstacks[3]; | 
|  | callstacks[0] = {2, 1, 0}; | 
|  | callstacks[1] = {2, 1, 0, 1, 0}; | 
|  | callstacks[2] = {0, 2, 0, 1, 2}; | 
|  |  | 
|  | for (size_t i = 0; i < base::ArraySize(build_ids); ++i) { | 
|  | auto interned = context.storage->InternString( | 
|  | {build_ids[i].data(), build_ids[i].size()}); | 
|  | hpt->AddString(kPacket, build_id_ids[i], interned); | 
|  | } | 
|  | for (size_t i = 0; i < base::ArraySize(mapping_names); ++i) { | 
|  | auto interned = context.storage->InternString( | 
|  | {mapping_names[i].data(), mapping_names[i].size()}); | 
|  | hpt->AddString(kPacket, mapping_name_ids[i], interned); | 
|  | } | 
|  | for (size_t i = 0; i < base::ArraySize(function_names); ++i) { | 
|  | auto interned = context.storage->InternString( | 
|  | {function_names[i].data(), function_names[i].size()}); | 
|  | hpt->AddString(kPacket, function_name_ids[i], interned); | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < base::ArraySize(mappings); ++i) | 
|  | hpt->AddMapping(kPacket, i, mappings[i]); | 
|  | for (size_t i = 0; i < base::ArraySize(frames); ++i) | 
|  | hpt->AddFrame(kPacket, i, frames[i]); | 
|  | for (size_t i = 0; i < base::ArraySize(callstacks); ++i) | 
|  | hpt->AddCallstack(kPacket, i, callstacks[i]); | 
|  |  | 
|  | for (size_t i = 0; i < base::ArraySize(callstacks); ++i) { | 
|  | int64_t parent = 0; | 
|  | const HeapProfileTracker::SourceCallstack& callstack = callstacks[i]; | 
|  | for (size_t depth = 0; depth < callstack.size(); ++depth) { | 
|  | auto frame_id = | 
|  | hpt->GetDatabaseFrameIdForTesting(kPacket, callstack[depth]); | 
|  | ASSERT_NE(frame_id, -1); | 
|  | int64_t self = FindCallstack( | 
|  | *context.storage, static_cast<int64_t>(depth), parent, frame_id); | 
|  | ASSERT_NE(self, -1); | 
|  | parent = self; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace trace_processor | 
|  | }  // namespace perfetto |