1 /* 2 * Copyright (c) 2021-2023 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 #ifndef ECMASCRIPT_DFX_HPROF_HEAP_PROFILER_H 17 #define ECMASCRIPT_DFX_HPROF_HEAP_PROFILER_H 18 19 #include "ecmascript/ecma_macros.h" 20 #include "ecmascript/dfx/hprof/file_stream.h" 21 #include "ecmascript/dfx/hprof/heap_profiler_interface.h" 22 #include "ecmascript/dfx/hprof/heap_snapshot_json_serializer.h" 23 #include "ecmascript/dfx/hprof/heap_tracker.h" 24 #include "ecmascript/dfx/hprof/heap_sampling.h" 25 #include "ecmascript/dfx/hprof/progress.h" 26 #include "ecmascript/dfx/hprof/string_hashmap.h" 27 #include "ecmascript/mem/c_containers.h" 28 #if defined(ENABLE_LOCAL_HANDLE_LEAK_DETECT) 29 #include "ecmascript/mem/clock_scope.h" 30 #endif 31 32 namespace panda::ecmascript { 33 class HeapSnapshot; 34 class EcmaVM; 35 using NodeId = uint64_t; 36 37 class EntryIdMap { 38 public: 39 EntryIdMap() = default; 40 ~EntryIdMap() = default; 41 NO_COPY_SEMANTIC(EntryIdMap); 42 NO_MOVE_SEMANTIC(EntryIdMap); 43 44 static constexpr uint64_t SEQ_STEP = 2; 45 std::pair<bool, NodeId> FindId(JSTaggedType addr); 46 NodeId FindOrInsertNodeId(JSTaggedType addr); 47 bool InsertId(JSTaggedType addr, NodeId id); 48 bool EraseId(JSTaggedType addr); 49 bool Move(JSTaggedType oldAddr, JSTaggedType forwardAddr); 50 void UpdateEntryIdMap(HeapSnapshot *snapshot); GetNextId()51 NodeId GetNextId() 52 { 53 nextId_ += SEQ_STEP; 54 return nextId_ - SEQ_STEP; 55 } GetLastId()56 NodeId GetLastId() 57 { 58 return nextId_ - SEQ_STEP; 59 } GetIdCount()60 size_t GetIdCount() 61 { 62 return idMap_.size(); 63 } GetIdMap()64 CUnorderedMap<JSTaggedType, NodeId>* GetIdMap() 65 { 66 return &idMap_; 67 } GetId()68 NodeId GetId() 69 { 70 return nextId_; 71 } SetId(NodeId id)72 void SetId(NodeId id) 73 { 74 nextId_ = id; 75 } 76 77 private: 78 NodeId nextId_ {3U}; // 1 Reversed for SyntheticRoot 79 CUnorderedMap<JSTaggedType, NodeId> idMap_ {}; 80 }; 81 82 struct AddrTableItem { 83 uint64_t addr; 84 uint64_t id; 85 uint32_t objSize; 86 uint32_t offset; // offset to the file 87 }; 88 89 #define PER_GROUP_MEM_SIZE (128 * 1024 * 1024) // 128 MB 90 #define HEAD_NUM_PER_GROUP (PER_GROUP_MEM_SIZE / sizeof(AddrTableItem)) 91 #define VERSION_ID_SIZE 8 92 #define MAX_FILE_SIZE (4 * 1024 * 1024 * 1024ULL) // 4 * 1024 * 1024 * 1024 : file size bigger than 4GB 93 #define MAX_OBJ_SIZE (MAX_FILE_SIZE >> 1) 94 95 struct NewAddr { 96 char *data; 97 uint32_t objSize; 98 NewAddrNewAddr99 NewAddr(uint32_t actSize, uint32_t objSize) : objSize(objSize) 100 { 101 if (actSize == 0 || actSize > MAX_OBJ_SIZE) { 102 LOG_ECMA(ERROR) << "ark raw heap decode abnormal obj size=" << actSize; 103 data = nullptr; 104 return; 105 } 106 data = new char[actSize]; 107 } ~NewAddrNewAddr108 ~NewAddr() 109 { 110 delete[] data; 111 } DataNewAddr112 char *Data() 113 { 114 return data; 115 } 116 }; 117 118 #if defined(ENABLE_LOCAL_HANDLE_LEAK_DETECT) 119 struct ScopeWrapper { 120 LocalScope *localScope_ {nullptr}; 121 EcmaHandleScope *ecmaHandleScope_ {nullptr}; 122 ClockScope clockScope_; 123 ScopeWrapperScopeWrapper124 ScopeWrapper(LocalScope *localScope, EcmaHandleScope *ecmaHandleScope) 125 : localScope_(localScope), ecmaHandleScope_(ecmaHandleScope) {} 126 }; 127 #endif // ENABLE_LOCAL_HANDLE_LEAK_DETECT 128 129 enum class DumpHeapSnapshotStatus : uint8_t { 130 SUCCESS, 131 FORK_FAILED, 132 FAILED_TO_WAIT, 133 WAIT_FORK_PROCESS_TIMEOUT, 134 }; 135 136 class HeapProfiler : public HeapProfilerInterface { 137 public: 138 NO_MOVE_SEMANTIC(HeapProfiler); 139 NO_COPY_SEMANTIC(HeapProfiler); 140 explicit HeapProfiler(const EcmaVM *vm); 141 ~HeapProfiler() override; 142 143 enum class SampleType { ONE_SHOT, REAL_TIME }; 144 145 void AllocationEvent(TaggedObject *address, size_t size) override; 146 void MoveEvent(uintptr_t address, TaggedObject *forwardAddress, size_t size) override; 147 /** 148 * dump the specific snapshot in target format 149 */ 150 bool DumpHeapSnapshot(Stream *stream, const DumpSnapShotOption &dumpOption, Progress *progress = nullptr, 151 std::function<void(uint8_t)> callback = [] (uint8_t) {}) override; 152 void DumpHeapSnapshotForOOM(const DumpSnapShotOption &dumpOption, bool fromSharedGC = false) override; 153 void AddSnapshot(HeapSnapshot *snapshot); 154 155 bool StartHeapTracking(double timeInterval, bool isVmMode = true, Stream *stream = nullptr, 156 bool traceAllocation = false, bool newThread = true) override; 157 bool StopHeapTracking(Stream *stream, Progress *progress = nullptr, bool newThread = true) override; 158 bool UpdateHeapTracking(Stream *stream) override; 159 bool StartHeapSampling(uint64_t samplingInterval, int stackDepth = 128) override; 160 void StopHeapSampling() override; 161 const struct SamplingInfo *GetAllocationProfile() override; GetIdCount()162 size_t GetIdCount() override 163 { 164 return entryIdMap_->GetIdCount(); 165 } GetEntryIdMap()166 EntryIdMap *GetEntryIdMap() const 167 { 168 return const_cast<EntryIdMap *>(entryIdMap_); 169 } GetChunk()170 Chunk *GetChunk() const 171 { 172 return const_cast<Chunk *>(&chunk_); 173 } GetEcmaStringTable()174 StringHashMap *GetEcmaStringTable() const 175 { 176 return const_cast<StringHashMap *>(&stringTable_); 177 } 178 bool GenerateHeapSnapshot(std::string &inputFilePath, std::string &outputPath) override; 179 #if defined(ENABLE_LOCAL_HANDLE_LEAK_DETECT) 180 bool IsStartLocalHandleLeakDetect() const; 181 void SwitchStartLocalHandleLeakDetect(); 182 void IncreaseScopeCount(); 183 void DecreaseScopeCount(); 184 void PushToActiveScopeStack(LocalScope *localScope, EcmaHandleScope *ecmaHandleScope); 185 void PopFromActiveScopeStack(); 186 void ClearHandleBackTrace(); 187 std::string_view GetBackTraceOfHandle(uintptr_t handle) const; 188 void WriteToLeakStackTraceFd(std::ostringstream &buffer) const; 189 void SetLeakStackTraceFd(int32_t fd); 190 int32_t GetLeakStackTraceFd() const; 191 void CloseLeakStackTraceFd(); 192 void StorePotentiallyLeakHandles(uintptr_t handle); 193 #endif // ENABLE_LOCAL_HANDLE_LEAK_DETECT 194 195 private: 196 /** 197 * trigger full gc to make sure no unreachable objects in heap 198 */ 199 bool ForceFullGC(const EcmaVM *vm); 200 void ForceSharedGC(); 201 void DumpHeapSnapshotFromSharedGC(Stream *stream, const DumpSnapShotOption &dumpOption); 202 203 /** 204 * make a new heap snapshot and put it into a container eg, vector 205 */ 206 HeapSnapshot *MakeHeapSnapshot(SampleType sampleType, const DumpSnapShotOption &dumpOption, 207 bool traceAllocation = false); 208 bool DoDump(Stream *stream, Progress *progress, const DumpSnapShotOption &dumpOption); 209 std::string GenDumpFileName(DumpFormat dumpFormat); 210 CString GetTimeStamp(); 211 void UpdateHeapObjects(HeapSnapshot *snapshot); 212 void ClearSnapshot(); 213 void FillIdMap(); 214 bool BinaryDump(Stream *stream, const DumpSnapShotOption &dumpOption); 215 uint32_t WriteToBinFile(Stream *stream, char *objTab, uint32_t objNum, 216 CVector<std::pair<char *, uint32_t>> &memBuf); 217 uint32_t CopyObjectMem2Buf(char *objTable, uint32_t objNum, CVector<std::pair<char *, uint32_t>> &memBufVec); 218 uint32_t GenObjTable(CUnorderedMap<char *, uint32_t> &headerMap, HeapSnapshot *snapshot, 219 CUnorderedMap<uint64_t, CVector<uint64_t>> &strIdMapObjVec); 220 uint32_t GenRootTable(Stream *stream); 221 bool DumpRawHeap(Stream *stream, uint32_t &fileOffset, CVector<uint32_t> &secIndexVec); 222 #if defined(ENABLE_LOCAL_HANDLE_LEAK_DETECT) 223 uint32_t GetScopeCount() const; 224 std::shared_ptr<ScopeWrapper> GetLastActiveScope() const; 225 bool InsertHandleBackTrace(uintptr_t handle, const std::string &backTrace); 226 #endif // ENABLE_LOCAL_HANDLE_LEAK_DETECT 227 228 const size_t MAX_NUM_HPROF = 5; // ~10MB 229 const EcmaVM *vm_; 230 CVector<HeapSnapshot *> hprofs_; 231 StringHashMap stringTable_; 232 bool isProfiling_ {false}; 233 EntryIdMap* entryIdMap_; 234 std::unique_ptr<HeapTracker> heapTracker_; 235 Chunk chunk_; 236 std::unique_ptr<HeapSampling> heapSampling_ {nullptr}; 237 Mutex mutex_; 238 #if defined(ENABLE_LOCAL_HANDLE_LEAK_DETECT) 239 static const long LOCAL_HANDLE_LEAK_TIME_MS {5000}; 240 bool startLocalHandleLeakDetect_ {false}; 241 uint32_t scopeCount_ {0}; 242 std::stack<std::shared_ptr<ScopeWrapper>> activeScopeStack_; 243 std::map<uintptr_t, std::string> handleBackTrace_; 244 int32_t leakStackTraceFd_ {-1}; 245 #endif // ENABLE_LOCAL_HANDLE_LEAK_DETECT 246 247 friend class HeapProfilerFriendTest; 248 }; 249 250 class RawHeapDump final : public RootVisitor, public EcmaObjectRangeVisitor<RawHeapDump> { 251 public: RawHeapDump(const EcmaVM * vm,Stream * stream,HeapSnapshot * snapshot,EntryIdMap * entryIdMap)252 RawHeapDump(const EcmaVM *vm, Stream *stream, HeapSnapshot *snapshot, EntryIdMap* entryIdMap) 253 : vm_(vm), stream_(stream), snapshot_(snapshot), entryIdMap_(entryIdMap), chunk_(vm->GetNativeAreaAllocator()), 254 startTime_(std::chrono::steady_clock::now()) 255 { 256 buffer_ = chunk_.NewArray<char>(PER_GROUP_MEM_SIZE); 257 } 258 ~RawHeapDump()259 ~RawHeapDump() 260 { 261 auto endTime = std::chrono::steady_clock::now(); 262 double duration = std::chrono::duration<double>(endTime - startTime_).count(); 263 LOG_ECMA(INFO) << "rawheap dump sucess, cost " << duration << 's'; 264 chunk_.Delete<char>(buffer_); 265 strIdMapObjVec_.clear(); 266 secIndexVec_.clear(); 267 roots_.clear(); 268 readOnlyObjects_.clear(); 269 } 270 271 void BinaryDump(const DumpSnapShotOption &dumpOption); 272 VisitRoot(Root type,ObjectSlot slot)273 void VisitRoot([[maybe_unused]] Root type, ObjectSlot slot) override 274 { 275 HandleRootValue(slot.GetTaggedValue()); 276 } 277 VisitRangeRoot(Root type,ObjectSlot start,ObjectSlot end)278 void VisitRangeRoot([[maybe_unused]] Root type, ObjectSlot start, ObjectSlot end) override 279 { 280 for (ObjectSlot slot = start; slot < end; slot++) { 281 HandleRootValue(slot.GetTaggedValue()); 282 } 283 } 284 VisitBaseAndDerivedRoot(Root type,ObjectSlot base,ObjectSlot derived,uintptr_t baseOldObject)285 void VisitBaseAndDerivedRoot([[maybe_unused]] Root type, [[maybe_unused]] ObjectSlot base, 286 [[maybe_unused]] ObjectSlot derived, [[maybe_unused]] uintptr_t baseOldObject) override 287 { 288 // do nothing 289 } 290 291 void VisitObjectRangeImpl(TaggedObject *root, ObjectSlot start, ObjectSlot end, VisitObjectArea area) override; 292 293 private: 294 void DumpVersion(); 295 void DumpRootTable(); 296 void DumpObjectTable(const DumpSnapShotOption &dumpOption); 297 void DumpObjectMemory(); 298 void DumpStringTable(); 299 void DumpSectionIndex(); 300 301 void WriteU64(uint64_t number); 302 void WriteChunk(char *data, size_t size); 303 void MaybeWriteBuffer(); 304 void WriteBinBlock(); 305 void UpdateStringTable(TaggedObject *object); 306 void HandleRootValue(JSTaggedValue value); 307 void IterateMarkedObjects(const std::function<void(void *)> &visitor); 308 void ProcessMarkObjectsFromRoot(TaggedObject *root); 309 bool MarkObject(TaggedObject *object); 310 void ClearVisitMark(); 311 void EndVisitMark(); 312 313 const char versionID[VERSION_ID_SIZE] = {0}; // 0: current version id 314 315 const EcmaVM *vm_ {nullptr}; 316 Stream *stream_ {nullptr}; 317 HeapSnapshot *snapshot_ {nullptr}; 318 EntryIdMap *entryIdMap_ {nullptr}; 319 char *buffer_ {nullptr}; 320 Chunk chunk_; 321 CUnorderedMap<uint64_t, CVector<uint64_t>> strIdMapObjVec_ {}; 322 CUnorderedSet<TaggedObject*> roots_ {}; 323 CUnorderedSet<TaggedObject*> readOnlyObjects_ {}; 324 CVector<uint32_t> secIndexVec_ {}; 325 CQueue<TaggedObject *> bfsQueue_ {}; 326 size_t objCnt_ = 0; 327 size_t bufIndex_ = 0; 328 uint32_t fileOffset_ = 0; 329 std::chrono::time_point<std::chrono::steady_clock> startTime_; 330 }; 331 } // namespace panda::ecmascript 332 #endif // ECMASCRIPT_DFX_HPROF_HEAP_PROFILER_H 333