xref: /aosp_15_r20/external/perfetto/src/profiling/memory/bookkeeping.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/profiling/memory/bookkeeping.h"
18 
19 #include <fcntl.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 
23 #include <cinttypes>
24 
25 #include "perfetto/base/logging.h"
26 #include "perfetto/ext/base/file_utils.h"
27 #include "perfetto/ext/base/scoped_file.h"
28 #include "src/profiling/common/callstack_trie.h"
29 
30 namespace perfetto {
31 namespace profiling {
32 
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)33 void HeapTracker::RecordMalloc(
34     const std::vector<unwindstack::FrameData>& callstack,
35     const std::vector<std::string>& build_ids,
36     uint64_t address,
37     uint64_t sample_size,
38     uint64_t alloc_size,
39     uint64_t sequence_number,
40     uint64_t timestamp) {
41   PERFETTO_CHECK(callstack.size() == build_ids.size());
42   std::vector<Interned<Frame>> frames;
43   frames.reserve(callstack.size());
44   for (size_t i = 0; i < callstack.size(); ++i) {
45     const unwindstack::FrameData& loc = callstack[i];
46     const std::string& build_id = build_ids[i];
47     auto frame_it = frame_cache_.find(loc.pc);
48     if (frame_it != frame_cache_.end()) {
49       frames.emplace_back(frame_it->second);
50     } else {
51       frames.emplace_back(callsites_->InternCodeLocation(loc, build_id));
52       frame_cache_.emplace(loc.pc, frames.back());
53     }
54   }
55 
56   auto it = allocations_.find(address);
57   if (it != allocations_.end()) {
58     Allocation& alloc = it->second;
59     PERFETTO_DCHECK(alloc.sequence_number != sequence_number);
60     if (alloc.sequence_number < sequence_number) {
61       // As we are overwriting the previous allocation, the previous allocation
62       // must have been freed.
63       //
64       // This makes the sequencing a bit incorrect. We are overwriting this
65       // allocation, so we prentend both the alloc and the free for this have
66       // already happened at committed_sequence_number_, while in fact the free
67       // might not have happened until right before this operation.
68 
69       if (alloc.sequence_number > committed_sequence_number_) {
70         // Only count the previous allocation if it hasn't already been
71         // committed to avoid double counting it.
72         AddToCallstackAllocations(timestamp, alloc);
73       }
74 
75       SubtractFromCallstackAllocations(alloc);
76       GlobalCallstackTrie::Node* node = callsites_->CreateCallsite(frames);
77       alloc.sample_size = sample_size;
78       alloc.alloc_size = alloc_size;
79       alloc.sequence_number = sequence_number;
80       alloc.SetCallstackAllocations(MaybeCreateCallstackAllocations(node));
81     }
82   } else {
83     GlobalCallstackTrie::Node* node = callsites_->CreateCallsite(frames);
84     allocations_.emplace(address,
85                          Allocation(sample_size, alloc_size, sequence_number,
86                                     MaybeCreateCallstackAllocations(node)));
87   }
88 
89   RecordOperation(sequence_number, {address, timestamp});
90 }
91 
RecordOperation(uint64_t sequence_number,const PendingOperation & operation)92 void HeapTracker::RecordOperation(uint64_t sequence_number,
93                                   const PendingOperation& operation) {
94   if (sequence_number != committed_sequence_number_ + 1) {
95     pending_operations_.emplace(sequence_number, operation);
96     return;
97   }
98 
99   CommitOperation(sequence_number, operation);
100 
101   // At this point some other pending operations might be eligible to be
102   // committed.
103   auto it = pending_operations_.begin();
104   while (it != pending_operations_.end() &&
105          it->first == committed_sequence_number_ + 1) {
106     CommitOperation(it->first, it->second);
107     it = pending_operations_.erase(it);
108   }
109 }
110 
CommitOperation(uint64_t sequence_number,const PendingOperation & operation)111 void HeapTracker::CommitOperation(uint64_t sequence_number,
112                                   const PendingOperation& operation) {
113   committed_sequence_number_++;
114   if (operation.timestamp)
115     committed_timestamp_ = operation.timestamp;
116 
117   uint64_t address = operation.allocation_address;
118 
119   // We will see many frees for addresses we do not know about.
120   auto leaf_it = allocations_.find(address);
121   if (leaf_it == allocations_.end())
122     return;
123 
124   Allocation& value = leaf_it->second;
125   if (value.sequence_number == sequence_number) {
126     AddToCallstackAllocations(operation.timestamp, value);
127   } else if (value.sequence_number < sequence_number) {
128     SubtractFromCallstackAllocations(value);
129     allocations_.erase(leaf_it);
130   }
131   // else (value.sequence_number > sequence_number:
132   //  This allocation has been replaced by a newer one in RecordMalloc.
133   //  This code commits ther previous allocation's malloc (and implicit free
134   //  that must have happened, as there is now a new allocation at the same
135   //  address). This means that this operation, be it a malloc or a free, must
136   //  be treated as a no-op.
137 }
138 
GetSizeForTesting(const std::vector<unwindstack::FrameData> & stack,std::vector<std::string> build_ids)139 uint64_t HeapTracker::GetSizeForTesting(
140     const std::vector<unwindstack::FrameData>& stack,
141     std::vector<std::string> build_ids) {
142   PERFETTO_DCHECK(!dump_at_max_mode_);
143   GlobalCallstackTrie::Node* node =
144       callsites_->CreateCallsite(stack, build_ids);
145   // Hack to make it go away again if it wasn't used before.
146   // This is only good because this is used for testing only.
147   GlobalCallstackTrie::IncrementNode(node);
148   GlobalCallstackTrie::DecrementNode(node);
149   auto it = callstack_allocations_.find(node);
150   if (it == callstack_allocations_.end()) {
151     return 0;
152   }
153   const CallstackAllocations& alloc = it->second;
154   return alloc.value.totals.allocated - alloc.value.totals.freed;
155 }
156 
GetMaxForTesting(const std::vector<unwindstack::FrameData> & stack,std::vector<std::string> build_ids)157 uint64_t HeapTracker::GetMaxForTesting(
158     const std::vector<unwindstack::FrameData>& stack,
159     std::vector<std::string> build_ids) {
160   PERFETTO_DCHECK(dump_at_max_mode_);
161   GlobalCallstackTrie::Node* node =
162       callsites_->CreateCallsite(stack, build_ids);
163   // Hack to make it go away again if it wasn't used before.
164   // This is only good because this is used for testing only.
165   GlobalCallstackTrie::IncrementNode(node);
166   GlobalCallstackTrie::DecrementNode(node);
167   auto it = callstack_allocations_.find(node);
168   if (it == callstack_allocations_.end()) {
169     return 0;
170   }
171   const CallstackAllocations& alloc = it->second;
172   return alloc.value.retain_max.max;
173 }
174 
GetMaxCountForTesting(const std::vector<unwindstack::FrameData> & stack,std::vector<std::string> build_ids)175 uint64_t HeapTracker::GetMaxCountForTesting(
176     const std::vector<unwindstack::FrameData>& stack,
177     std::vector<std::string> build_ids) {
178   PERFETTO_DCHECK(dump_at_max_mode_);
179   GlobalCallstackTrie::Node* node =
180       callsites_->CreateCallsite(stack, build_ids);
181   // Hack to make it go away again if it wasn't used before.
182   // This is only good because this is used for testing only.
183   GlobalCallstackTrie::IncrementNode(node);
184   GlobalCallstackTrie::DecrementNode(node);
185   auto it = callstack_allocations_.find(node);
186   if (it == callstack_allocations_.end()) {
187     return 0;
188   }
189   const CallstackAllocations& alloc = it->second;
190   return alloc.value.retain_max.max_count;
191 }
192 
193 }  // namespace profiling
194 }  // namespace perfetto
195