• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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