1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/dfx/hprof/heap_profiler.h"
17
18 #include "ecmascript/dfx/hprof/file_stream.h"
19 #include "ecmascript/dfx/hprof/heap_snapshot.h"
20 #include "ecmascript/ecma_vm.h"
21 #include "ecmascript/js_thread.h"
22 #include "ecmascript/mem/assert_scope.h"
23 #include "ecmascript/mem/concurrent_sweeper.h"
24 #include "ecmascript/mem/heap-inl.h"
25
26 namespace panda::ecmascript {
FindId(Address addr)27 std::pair<bool, uint32_t> EntryIdMap::FindId(Address addr)
28 {
29 auto it = idMap_.find(addr);
30 if (it == idMap_.end()) {
31 return std::make_pair(false, GetNextId()); // return nextId if entry not exits
32 } else {
33 return std::make_pair(true, it->second);
34 }
35 }
36
InsertId(Address addr,uint32_t id)37 bool EntryIdMap::InsertId(Address addr, uint32_t id)
38 {
39 auto it = idMap_.find(addr);
40 if (it == idMap_.end()) {
41 idMap_.emplace(addr, id);
42 return true;
43 }
44 idMap_[addr] = id;
45 return false;
46 }
47
EraseId(Address addr)48 bool EntryIdMap::EraseId(Address addr)
49 {
50 auto it = idMap_.find(addr);
51 if (it == idMap_.end()) {
52 return false;
53 }
54 idMap_.erase(it);
55 return true;
56 }
57
Move(Address oldAddr,Address forwardAddr)58 bool EntryIdMap::Move(Address oldAddr, Address forwardAddr)
59 {
60 if (oldAddr == forwardAddr) {
61 return true;
62 }
63 auto it = idMap_.find(oldAddr);
64 if (it != idMap_.end()) {
65 uint32_t id = it->second;
66 idMap_.erase(it);
67 idMap_[forwardAddr] = id;
68 return true;
69 }
70 return false;
71 }
72
RemoveDeadEntryId(HeapSnapshot * snapshot)73 void EntryIdMap::RemoveDeadEntryId(HeapSnapshot *snapshot)
74 {
75 auto nodes = snapshot->GetNodes();
76 CUnorderedMap<Address, uint32_t> newIdMap;
77 for (auto node : *nodes) {
78 auto addr = node->GetAddress();
79 auto it = idMap_.find(addr);
80 if (it != idMap_.end()) {
81 newIdMap.emplace(addr, it->second);
82 }
83 }
84 idMap_ = newIdMap;
85 }
86
HeapProfiler(const EcmaVM * vm)87 HeapProfiler::HeapProfiler(const EcmaVM *vm) : vm_(vm), chunk_(vm->GetNativeAreaAllocator())
88 {
89 isProfiling_ = false;
90 entryIdMap_ = GetChunk()->New<EntryIdMap>();
91 jsonSerializer_ = GetChunk()->New<HeapSnapshotJSONSerializer>();
92 if (UNLIKELY(jsonSerializer_ == nullptr)) {
93 LOG_FULL(FATAL) << "alloc snapshot json serializer failed";
94 UNREACHABLE();
95 }
96 }
97
~HeapProfiler()98 HeapProfiler::~HeapProfiler()
99 {
100 ClearSnapshot();
101 GetChunk()->Delete(entryIdMap_);
102 GetChunk()->Delete(jsonSerializer_);
103 jsonSerializer_ = nullptr;
104 }
105
AllocationEvent(TaggedObject * address,size_t size)106 void HeapProfiler::AllocationEvent(TaggedObject *address, size_t size)
107 {
108 DISALLOW_GARBAGE_COLLECTION;
109 if (isProfiling_) {
110 // Id will be allocated later while add new node
111 if (heapTracker_ != nullptr) {
112 heapTracker_->AllocationEvent(address, size);
113 }
114 }
115 }
116
MoveEvent(uintptr_t address,TaggedObject * forwardAddress,size_t size)117 void HeapProfiler::MoveEvent(uintptr_t address, TaggedObject *forwardAddress, size_t size)
118 {
119 os::memory::LockHolder lock(mutex_);
120 if (isProfiling_) {
121 entryIdMap_->Move(address, reinterpret_cast<Address>(forwardAddress));
122 if (heapTracker_ != nullptr) {
123 heapTracker_->MoveEvent(address, forwardAddress, size);
124 }
125 }
126 }
127
UpdateHeapObjects(HeapSnapshot * snapshot)128 void HeapProfiler::UpdateHeapObjects(HeapSnapshot *snapshot)
129 {
130 vm_->CollectGarbage(TriggerGCType::OLD_GC);
131 vm_->GetHeap()->GetSweeper()->EnsureAllTaskFinished();
132 snapshot->UpdateNodes();
133 }
134
DumpHeapSnapshot(DumpFormat dumpFormat,Stream * stream,Progress * progress,bool isVmMode,bool isPrivate,bool captureNumericValue)135 bool HeapProfiler::DumpHeapSnapshot(DumpFormat dumpFormat, Stream *stream, Progress *progress,
136 bool isVmMode, bool isPrivate, bool captureNumericValue)
137 {
138 [[maybe_unused]] bool heapClean = ForceFullGC(vm_);
139 ASSERT(heapClean);
140 LOG_ECMA(INFO) << "HeapProfiler DumpSnapshot start";
141 size_t heapSize = vm_->GetHeap()->GetHeapObjectSize();
142 LOG_ECMA(INFO) << "HeapProfiler DumpSnapshot heap size " << heapSize;
143 int32_t heapCount = static_cast<int32_t>(vm_->GetHeap()->GetHeapObjectCount());
144 if (progress != nullptr) {
145 progress->ReportProgress(0, heapCount);
146 }
147 HeapSnapshot *snapshot = MakeHeapSnapshot(SampleType::ONE_SHOT, isVmMode, isPrivate, captureNumericValue);
148 ASSERT(snapshot != nullptr);
149 entryIdMap_->RemoveDeadEntryId(snapshot);
150 isProfiling_ = true;
151 if (progress != nullptr) {
152 progress->ReportProgress(heapCount, heapCount);
153 }
154 if (!stream->Good()) {
155 FileStream newStream(GenDumpFileName(dumpFormat));
156 auto serializerResult = jsonSerializer_->Serialize(snapshot, &newStream);
157 GetChunk()->Delete(snapshot);
158 return serializerResult;
159 }
160 auto serializerResult = jsonSerializer_->Serialize(snapshot, stream);
161 GetChunk()->Delete(snapshot);
162 return serializerResult;
163 }
164
StartHeapTracking(double timeInterval,bool isVmMode,Stream * stream,bool traceAllocation,bool newThread)165 bool HeapProfiler::StartHeapTracking(double timeInterval, bool isVmMode, Stream *stream,
166 bool traceAllocation, bool newThread)
167 {
168 HeapSnapshot *snapshot = MakeHeapSnapshot(SampleType::REAL_TIME, isVmMode, false, false, traceAllocation);
169 if (snapshot == nullptr) {
170 return false;
171 }
172 isProfiling_ = true;
173 UpdateHeapObjects(snapshot);
174 heapTracker_ = std::make_unique<HeapTracker>(snapshot, timeInterval, stream);
175 const_cast<EcmaVM *>(vm_)->StartHeapTracking();
176 if (newThread) {
177 heapTracker_->StartTracing();
178 }
179
180 return true;
181 }
182
UpdateHeapTracking(Stream * stream)183 bool HeapProfiler::UpdateHeapTracking(Stream *stream)
184 {
185 if (heapTracker_ == nullptr) {
186 return false;
187 }
188 HeapSnapshot *snapshot = heapTracker_->GetHeapSnapshot();
189 if (snapshot == nullptr) {
190 return false;
191 }
192 snapshot->RecordSampleTime();
193 UpdateHeapObjects(snapshot);
194
195 if (stream != nullptr) {
196 snapshot->PushHeapStat(stream);
197 }
198
199 return true;
200 }
201
StopHeapTracking(Stream * stream,Progress * progress,bool newThread)202 bool HeapProfiler::StopHeapTracking(Stream *stream, Progress *progress, bool newThread)
203 {
204 if (heapTracker_ == nullptr) {
205 return false;
206 }
207 int32_t heapCount = static_cast<int32_t>(vm_->GetHeap()->GetHeapObjectCount());
208
209 const_cast<EcmaVM *>(vm_)->StopHeapTracking();
210 if (newThread) {
211 heapTracker_->StopTracing();
212 }
213
214 HeapSnapshot *snapshot = heapTracker_->GetHeapSnapshot();
215 if (snapshot == nullptr) {
216 return false;
217 }
218
219 if (progress != nullptr) {
220 progress->ReportProgress(0, heapCount);
221 }
222 snapshot->FinishSnapshot();
223 isProfiling_ = false;
224 if (progress != nullptr) {
225 progress->ReportProgress(heapCount, heapCount);
226 }
227 return jsonSerializer_->Serialize(snapshot, stream);
228 }
229
GenDumpFileName(DumpFormat dumpFormat)230 std::string HeapProfiler::GenDumpFileName(DumpFormat dumpFormat)
231 {
232 CString filename("hprof_");
233 switch (dumpFormat) {
234 case DumpFormat::JSON:
235 filename.append(GetTimeStamp());
236 break;
237 case DumpFormat::BINARY:
238 filename.append("unimplemented");
239 break;
240 case DumpFormat::OTHER:
241 filename.append("unimplemented");
242 break;
243 default:
244 filename.append("unimplemented");
245 break;
246 }
247 filename.append(".heapsnapshot");
248 return ConvertToStdString(filename);
249 }
250
GetTimeStamp()251 CString HeapProfiler::GetTimeStamp()
252 {
253 std::time_t timeSource = std::time(nullptr);
254 struct tm tm {
255 };
256 struct tm *timeData = localtime_r(&timeSource, &tm);
257 if (timeData == nullptr) {
258 LOG_FULL(FATAL) << "localtime_r failed";
259 UNREACHABLE();
260 }
261 CString stamp;
262 const int TIME_START = 1900;
263 stamp.append(ToCString(timeData->tm_year + TIME_START))
264 .append("-")
265 .append(ToCString(timeData->tm_mon + 1))
266 .append("-")
267 .append(ToCString(timeData->tm_mday))
268 .append("_")
269 .append(ToCString(timeData->tm_hour))
270 .append("-")
271 .append(ToCString(timeData->tm_min))
272 .append("-")
273 .append(ToCString(timeData->tm_sec));
274 return stamp;
275 }
276
ForceFullGC(const EcmaVM * vm)277 bool HeapProfiler::ForceFullGC(const EcmaVM *vm)
278 {
279 if (vm->IsInitialized()) {
280 const_cast<Heap *>(vm->GetHeap())->CollectGarbage(TriggerGCType::FULL_GC);
281 return true;
282 }
283 return false;
284 }
285
MakeHeapSnapshot(SampleType sampleType,bool isVmMode,bool isPrivate,bool captureNumericValue,bool traceAllocation)286 HeapSnapshot *HeapProfiler::MakeHeapSnapshot(SampleType sampleType, bool isVmMode, bool isPrivate,
287 bool captureNumericValue, bool traceAllocation)
288 {
289 LOG_ECMA(INFO) << "HeapProfiler::MakeHeapSnapshot";
290 DISALLOW_GARBAGE_COLLECTION;
291 const_cast<Heap *>(vm_->GetHeap())->Prepare();
292 switch (sampleType) {
293 case SampleType::ONE_SHOT: {
294 auto *snapshot = GetChunk()->New<HeapSnapshot>(vm_, isVmMode, isPrivate, captureNumericValue,
295 traceAllocation, entryIdMap_, GetChunk());
296 if (snapshot == nullptr) {
297 LOG_FULL(FATAL) << "alloc snapshot failed";
298 UNREACHABLE();
299 }
300 snapshot->BuildUp();
301 return snapshot;
302 }
303 case SampleType::REAL_TIME: {
304 auto *snapshot = GetChunk()->New<HeapSnapshot>(vm_, isVmMode, isPrivate, captureNumericValue,
305 traceAllocation, entryIdMap_, GetChunk());
306 if (snapshot == nullptr) {
307 LOG_FULL(FATAL) << "alloc snapshot failed";
308 UNREACHABLE();
309 }
310 AddSnapshot(snapshot);
311 snapshot->PrepareSnapshot();
312 return snapshot;
313 }
314 default:
315 return nullptr;
316 }
317 }
318
AddSnapshot(HeapSnapshot * snapshot)319 void HeapProfiler::AddSnapshot(HeapSnapshot *snapshot)
320 {
321 if (hprofs_.size() >= MAX_NUM_HPROF) {
322 ClearSnapshot();
323 }
324 ASSERT(snapshot != nullptr);
325 hprofs_.emplace_back(snapshot);
326 }
327
ClearSnapshot()328 void HeapProfiler::ClearSnapshot()
329 {
330 for (auto *snapshot : hprofs_) {
331 GetChunk()->Delete(snapshot);
332 }
333 hprofs_.clear();
334 }
335
StartHeapSampling(uint64_t samplingInterval,int stackDepth)336 bool HeapProfiler::StartHeapSampling(uint64_t samplingInterval, int stackDepth)
337 {
338 if (heapSampling_.get()) {
339 LOG_ECMA(ERROR) << "Do not start heap sampling twice in a row.";
340 return false;
341 }
342 heapSampling_ = std::make_unique<HeapSampling>(vm_, const_cast<Heap *>(vm_->GetHeap()),
343 samplingInterval, stackDepth);
344 return true;
345 }
346
StopHeapSampling()347 void HeapProfiler::StopHeapSampling()
348 {
349 heapSampling_.reset();
350 }
351
GetAllocationProfile()352 const struct SamplingInfo *HeapProfiler::GetAllocationProfile()
353 {
354 if (!heapSampling_.get()) {
355 LOG_ECMA(ERROR) << "Heap sampling was not started, please start firstly.";
356 return nullptr;
357 }
358 return heapSampling_->GetAllocationProfile();
359 }
360 } // namespace panda::ecmascript
361