| /* |
| * Copyright (C) 2018 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/profiling/memory/bookkeeping.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <cinttypes> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/file_utils.h" |
| #include "perfetto/ext/base/scoped_file.h" |
| #include "src/profiling/common/callstack_trie.h" |
| |
| namespace perfetto { |
| namespace profiling { |
| |
| void HeapTracker::RecordMalloc( |
| const std::vector<unwindstack::FrameData>& callstack, |
| const std::vector<std::string>& build_ids, |
| uint64_t address, |
| uint64_t sample_size, |
| uint64_t alloc_size, |
| uint64_t sequence_number, |
| uint64_t timestamp) { |
| PERFETTO_CHECK(callstack.size() == build_ids.size()); |
| std::vector<Interned<Frame>> frames; |
| frames.reserve(callstack.size()); |
| for (size_t i = 0; i < callstack.size(); ++i) { |
| const unwindstack::FrameData& loc = callstack[i]; |
| const std::string& build_id = build_ids[i]; |
| auto frame_it = frame_cache_.find(loc.pc); |
| if (frame_it != frame_cache_.end()) { |
| frames.emplace_back(frame_it->second); |
| } else { |
| frames.emplace_back(callsites_->InternCodeLocation(loc, build_id)); |
| frame_cache_.emplace(loc.pc, frames.back()); |
| } |
| } |
| |
| auto it = allocations_.find(address); |
| if (it != allocations_.end()) { |
| Allocation& alloc = it->second; |
| PERFETTO_DCHECK(alloc.sequence_number != sequence_number); |
| if (alloc.sequence_number < sequence_number) { |
| // As we are overwriting the previous allocation, the previous allocation |
| // must have been freed. |
| // |
| // This makes the sequencing a bit incorrect. We are overwriting this |
| // allocation, so we prentend both the alloc and the free for this have |
| // already happened at committed_sequence_number_, while in fact the free |
| // might not have happened until right before this operation. |
| |
| if (alloc.sequence_number > committed_sequence_number_) { |
| // Only count the previous allocation if it hasn't already been |
| // committed to avoid double counting it. |
| AddToCallstackAllocations(timestamp, alloc); |
| } |
| |
| SubtractFromCallstackAllocations(alloc); |
| GlobalCallstackTrie::Node* node = callsites_->CreateCallsite(frames); |
| alloc.sample_size = sample_size; |
| alloc.alloc_size = alloc_size; |
| alloc.sequence_number = sequence_number; |
| alloc.SetCallstackAllocations(MaybeCreateCallstackAllocations(node)); |
| } |
| } else { |
| GlobalCallstackTrie::Node* node = callsites_->CreateCallsite(frames); |
| allocations_.emplace(address, |
| Allocation(sample_size, alloc_size, sequence_number, |
| MaybeCreateCallstackAllocations(node))); |
| } |
| |
| RecordOperation(sequence_number, {address, timestamp}); |
| } |
| |
| void HeapTracker::RecordOperation(uint64_t sequence_number, |
| const PendingOperation& operation) { |
| if (sequence_number != committed_sequence_number_ + 1) { |
| pending_operations_.emplace(sequence_number, operation); |
| return; |
| } |
| |
| CommitOperation(sequence_number, operation); |
| |
| // At this point some other pending operations might be eligible to be |
| // committed. |
| auto it = pending_operations_.begin(); |
| while (it != pending_operations_.end() && |
| it->first == committed_sequence_number_ + 1) { |
| CommitOperation(it->first, it->second); |
| it = pending_operations_.erase(it); |
| } |
| } |
| |
| void HeapTracker::CommitOperation(uint64_t sequence_number, |
| const PendingOperation& operation) { |
| committed_sequence_number_++; |
| if (operation.timestamp) |
| committed_timestamp_ = operation.timestamp; |
| |
| uint64_t address = operation.allocation_address; |
| |
| // We will see many frees for addresses we do not know about. |
| auto leaf_it = allocations_.find(address); |
| if (leaf_it == allocations_.end()) |
| return; |
| |
| Allocation& value = leaf_it->second; |
| if (value.sequence_number == sequence_number) { |
| AddToCallstackAllocations(operation.timestamp, value); |
| } else if (value.sequence_number < sequence_number) { |
| SubtractFromCallstackAllocations(value); |
| allocations_.erase(leaf_it); |
| } |
| // else (value.sequence_number > sequence_number: |
| // This allocation has been replaced by a newer one in RecordMalloc. |
| // This code commits ther previous allocation's malloc (and implicit free |
| // that must have happened, as there is now a new allocation at the same |
| // address). This means that this operation, be it a malloc or a free, must |
| // be treated as a no-op. |
| } |
| |
| uint64_t HeapTracker::GetSizeForTesting( |
| const std::vector<unwindstack::FrameData>& stack, |
| std::vector<std::string> build_ids) { |
| PERFETTO_DCHECK(!dump_at_max_mode_); |
| GlobalCallstackTrie::Node* node = |
| callsites_->CreateCallsite(stack, build_ids); |
| // Hack to make it go away again if it wasn't used before. |
| // This is only good because this is used for testing only. |
| GlobalCallstackTrie::IncrementNode(node); |
| GlobalCallstackTrie::DecrementNode(node); |
| auto it = callstack_allocations_.find(node); |
| if (it == callstack_allocations_.end()) { |
| return 0; |
| } |
| const CallstackAllocations& alloc = it->second; |
| return alloc.value.totals.allocated - alloc.value.totals.freed; |
| } |
| |
| uint64_t HeapTracker::GetMaxForTesting( |
| const std::vector<unwindstack::FrameData>& stack, |
| std::vector<std::string> build_ids) { |
| PERFETTO_DCHECK(dump_at_max_mode_); |
| GlobalCallstackTrie::Node* node = |
| callsites_->CreateCallsite(stack, build_ids); |
| // Hack to make it go away again if it wasn't used before. |
| // This is only good because this is used for testing only. |
| GlobalCallstackTrie::IncrementNode(node); |
| GlobalCallstackTrie::DecrementNode(node); |
| auto it = callstack_allocations_.find(node); |
| if (it == callstack_allocations_.end()) { |
| return 0; |
| } |
| const CallstackAllocations& alloc = it->second; |
| return alloc.value.retain_max.max; |
| } |
| |
| uint64_t HeapTracker::GetMaxCountForTesting( |
| const std::vector<unwindstack::FrameData>& stack, |
| std::vector<std::string> build_ids) { |
| PERFETTO_DCHECK(dump_at_max_mode_); |
| GlobalCallstackTrie::Node* node = |
| callsites_->CreateCallsite(stack, build_ids); |
| // Hack to make it go away again if it wasn't used before. |
| // This is only good because this is used for testing only. |
| GlobalCallstackTrie::IncrementNode(node); |
| GlobalCallstackTrie::DecrementNode(node); |
| auto it = callstack_allocations_.find(node); |
| if (it == callstack_allocations_.end()) { |
| return 0; |
| } |
| const CallstackAllocations& alloc = it->second; |
| return alloc.value.retain_max.max_count; |
| } |
| |
| } // namespace profiling |
| } // namespace perfetto |