| /* | 
 |  * 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 |